Source code for typedpy.fields.multified_wrappers

from typedpy.commons import wrap_val
from typedpy.structures import Field, FieldMeta, NoneField, ClassReference, TypedField
from .fields import _map_to_field


class _JSONSchemaDraft4ReuseMeta(FieldMeta):
    def __getitem__(cls, item):
        def validate_and_get_field(val):
            return FieldMeta.__getitem__(cls, val)

        if isinstance(item, tuple):
            fields = [validate_and_get_field(it) for it in item]
            return cls(fields)  # pylint: disable=E1120, E1123
        return cls([validate_and_get_field(item)])  # pylint: disable=E1120, E1123


def _str_for_multioption_field(instance):
    name = instance.__class__.__name__
    if instance.get_fields():
        fields_st = ", ".join([str(field) for field in instance.get_fields()])
        propst = f" [{fields_st}]"
    else:
        propst = ""
    return f"<{name}{propst}>"


class MultiFieldWrapper:
    """
    An abstract base class for AllOf, AnyOf, OneOf, etc.
    It provides flexibility in reading the "fields" argument.
    """

    def __init__(self, *arg, fields, **kwargs):
        if isinstance(fields, list):
            self._fields = []
            for item in fields:
                self._fields.append(_map_to_field(item))
        else:
            raise TypeError("Expected a Field class or instance")
        super().__init__(*arg, **kwargs)

    def get_fields(self):
        return self._fields


[docs]class AllOf(MultiFieldWrapper, Field, metaclass=_JSONSchemaDraft4ReuseMeta): """ Content must adhere to all requirements in the fields arguments. Arguments: fields( `list` of :class:`Field`): optional the content should match all of the fields in the list Example: .. code-block:: python AllOf[Number(maximum=20, minimum=-10), Integer, Positive] """ def __init__(self, fields): super().__init__(fields=fields) def __set__(self, instance, value): for field in self.get_fields(): setattr(field, "_name", self._name) field.__set__(instance, value) super().__set__(instance, value) def __str__(self): return _str_for_multioption_field(self) def serialize(self, value): field = self.get_fields()[0] return field.serialize(value)
def _get_type_name(field): if isinstance(field, ClassReference): return field._ty.__name__ clz = field.__class__ if clz is NoneField: return "None" if isinstance(field, TypedField): return clz._ty.__name__ return clz.__name__
[docs]class AnyOf(MultiFieldWrapper, Field, metaclass=_JSONSchemaDraft4ReuseMeta): """ Content must adhere to one or more of the requirements in the fields arguments. Arguments: fields( `list` of :class:`Field`): optional the content should match at least one of the fields in the list Example: .. code-block:: python AnyOf[Number(maximum=20, minimum=-10), Integer, Positive, String] """ def __init__(self, fields): super().__init__(fields=fields) if self.get_fields(): for f in self.get_fields(): if isinstance(f, NoneField): self._is_optional = True else: self._not_nonefield = f else: raise TypeError("AnyOf definition must include at least one field option") def __set__(self, instance, value): if getattr(instance, "_trust_supplied_values", False): super().__set__(instance, value) return matched = False for field in self.get_fields(): setattr(field, "_name", self._name) try: field.__set__(instance, value) matched = True break except TypeError: pass except ValueError: pass if not matched: prefix = f"{self._name}: " if self._name else "" valid_type_names = ", ".join([_get_type_name(f) for f in self.get_fields()]) raise ValueError( f"{prefix}{wrap_val(value)} of type {value.__class__.__name__} did not match" f" any field option. Valid types are: {valid_type_names}." ) super().__set__(instance, getattr(instance, self._name)) def __str__(self): return _str_for_multioption_field(self) def serialize(self, value): return None if value is None else self._not_nonefield.serialize(value)
[docs]class OneOf(MultiFieldWrapper, Field, metaclass=_JSONSchemaDraft4ReuseMeta): """ Content must adhere to one, and only one, of the requirements in the fields arguments. Arguments: fields( `list` of :class:`Field`): optional the content should match one, and only one, of the fields in the list Example: .. code-block:: python OneOf[Number(maximum=20, minimum=-10), Integer, Positive, String] """ def __init__(self, fields): super().__init__(fields=fields) def __set__(self, instance, value): matched = 0 for field in self.get_fields(): setattr(field, "_name", self._name) try: field.__set__(instance, value) matched += 1 except TypeError: pass except ValueError: pass if not matched: valid_type_names = ", ".join([_get_type_name(f) for f in self.get_fields()]) prefix = f"{self._name}: " if self._name else "" raise ValueError( f"{prefix}{wrap_val(value)} of type {value.__class__.__name__} did not match" f" any field option. Valid types are: {valid_type_names}." ) if matched > 1: prefix = f"{self._name}: " if self._name else "" raise ValueError( f"{prefix}: Got {wrap_val(value)}; Matched more than one field option" ) super().__set__(instance, value) def __str__(self): return _str_for_multioption_field(self) def serialize(self, value): raise TypeError("Not FastSerializable")
[docs]class NotField(MultiFieldWrapper, Field, metaclass=_JSONSchemaDraft4ReuseMeta): """ Content *must not* adhere to any of the requirements in the fields arguments. Arguments: fields( `list` of :class:`Field`): optional the content must not match any of the fields in the lists Examples: .. code-block:: python NotField([Number(multiplesOf=5, maximum=20, minimum=-10), String]) NotField[Positive] """ def __init__(self, fields): super().__init__(fields=fields) def __set__(self, instance, value): for field in self.get_fields(): setattr(field, "_name", self._name) try: field.__set__(instance, value) except TypeError: pass except ValueError: pass else: raise ValueError( f"{self._name}: Got {wrap_val(value)}; Expected not to match any field definition" ) super().__set__(instance, value) def __str__(self): return _str_for_multioption_field(self)