From 943e3dc057eca53e68ddec51529bd6a1279ebd8e Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 29 Apr 2024 06:23:02 +0200 Subject: Adding upstream version 0.18.1. Signed-off-by: Daniel Baumann --- myst_parser/config/dc_validators.py | 161 ++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 myst_parser/config/dc_validators.py (limited to 'myst_parser/config/dc_validators.py') diff --git a/myst_parser/config/dc_validators.py b/myst_parser/config/dc_validators.py new file mode 100644 index 0000000..765cfb9 --- /dev/null +++ b/myst_parser/config/dc_validators.py @@ -0,0 +1,161 @@ +"""Validators for dataclasses, mirroring those of https://github.com/python-attrs/attrs.""" +from __future__ import annotations + +import dataclasses as dc +from typing import Any, Sequence + +from typing_extensions import Protocol + + +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) + + +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)) + + +class ValidatorType(Protocol): + def __call__( + self, inst: bytes, field: dc.Field, value: Any, suffix: str = "" + ) -> None: + ... + + +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 + + +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 + + +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})." + ) + + +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 + + +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 + + +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 -- cgit v1.2.3