Source code for myst_parser.config.dc_validators

"""Validators for dataclasses, mirroring those of https://github.com/python-attrs/attrs."""

from __future__ import annotations

import dataclasses as dc
from collections.abc import Sequence
from typing import Any, Protocol


[docs] def validate_field(inst: Any, field: dc.Field, value: Any) -> None: """Validate the field of a dataclass, according to a `validator` function set in the field.metadata. The validator function should take as input (inst, field, value) and raise an exception if the value is invalid. """ if "validator" not in field.metadata: return if isinstance(field.metadata["validator"], list): for validator in field.metadata["validator"]: validator(inst, field, value) else: field.metadata["validator"](inst, field, value)
[docs] def validate_fields(inst: Any) -> None: """Validate the fields of a dataclass, according to `validator` functions set in the field metadata. This function should be called in the `__post_init__` of the dataclass. The validator function should take as input (inst, field, value) and raise an exception if the value is invalid. """ for field in dc.fields(inst): validate_field(inst, field, getattr(inst, field.name))
[docs] class ValidatorType(Protocol): def __call__( self, inst: Any, field: dc.Field, value: Any, suffix: str = "" ) -> None: ...
[docs] def any_(inst, field, value, suffix=""): """ A validator that does not perform any validation. """
[docs] def instance_of(type_: type[Any] | tuple[type[Any], ...]) -> ValidatorType: """ A validator that raises a `TypeError` if the initializer is called with a wrong type for this particular attribute (checks are performed using `isinstance` therefore it's also valid to pass a tuple of types). :param type_: The type to check for. """ def _validator(inst, field, value, suffix=""): """ We use a callable class to be able to change the ``__repr__``. """ if not isinstance(value, type_): raise TypeError( f"'{field.name}{suffix}' must be of type {type_!r} " f"(got {value!r} that is a {value.__class__!r})." ) return _validator
[docs] def optional(validator: ValidatorType) -> ValidatorType: """ A validator that makes an attribute optional. An optional attribute is one which can be set to ``None`` in addition to satisfying the requirements of the sub-validator. """ def _validator(inst, field, value, suffix=""): if value is None: return validator(inst, field, value, suffix=suffix) return _validator
[docs] def is_callable(inst, field, value, suffix=""): """ A validator that raises a `TypeError` if the initializer is called with a value for this particular attribute that is not callable. """ if not callable(value): raise TypeError( f"'{field.name}{suffix}' must be callable " f"(got {value!r} that is a {value.__class__!r})." )
[docs] def in_(options: Sequence) -> ValidatorType: """ A validator that raises a `ValueError` if the initializer is called with a value that does not belong in the options provided. The check is performed using ``value in options``. :param options: Allowed options. """ def _validator(inst, field, value, suffix=""): try: in_options = value in options except TypeError: # e.g. `1 in "abc"` in_options = False if not in_options: raise ValueError( f"'{field.name}{suffix}' must be in {options!r} (got {value!r})" ) return _validator
[docs] def deep_iterable( member_validator: ValidatorType, iterable_validator: ValidatorType | None = None ) -> ValidatorType: """ A validator that performs deep validation of an iterable. :param member_validator: Validator to apply to iterable members :param iterable_validator: Validator to apply to iterable itself """ def _validator(inst, field, value, suffix=""): if iterable_validator is not None: iterable_validator(inst, field, value, suffix=suffix) for idx, member in enumerate(value): member_validator(inst, field, member, suffix=f"{suffix}[{idx}]") return _validator
[docs] def deep_mapping( key_validator: ValidatorType, value_validator: ValidatorType, mapping_validator: ValidatorType | None = None, ) -> ValidatorType: """ A validator that performs deep validation of a dictionary. :param key_validator: Validator to apply to dictionary keys :param value_validator: Validator to apply to dictionary values :param mapping_validator: Validator to apply to top-level mapping attribute (optional) """ def _validator(inst, field: dc.Field, value, suffix=""): if mapping_validator is not None: mapping_validator(inst, field, value) for key in value: key_validator(inst, field, key, suffix=f"{suffix}[{key!r}]") value_validator(inst, field, value[key], suffix=f"{suffix}[{key!r}]") return _validator