Source code for statham.schema.elements.object

from typing import Any, ClassVar, Dict, Union

from statham.schema.elements.base import Element, UNBOUND_PROPERTY
from statham.schema.elements.meta import ObjectClassDict, ObjectMeta
from statham.schema.exceptions import ValidationError
from statham.schema.property import _Property
from statham.schema.constants import NotPassed


# TODO: Test and support limited recursive models.


[docs]class Object(metaclass=ObjectMeta): """Base model for JSON Schema ``"object"`` elements. ``"object"`` schemas are defined by declaring subclasses of :class:`Object`. Properties are declared as class attributes, and other keywords are set as class arguments. For example: .. code:: python from statham.schema.elements import Object, String from statham.schema.property import Property class Poll(Object, additionalProperties=False): questions: List[str] = Property(String(), required=True) poll = Poll({"questions": ["What's up?"]}) """ properties: ClassVar[Dict[str, _Property]] default: ClassVar[Any] additionalProperties: ClassVar[Union[Element, bool]] description: ClassVar[str] def __init_subclass__(cls, *args, **kwargs): if cls.__doc__ and cls.description is NotPassed(): cls.description = cls.__doc__ def __new__( cls, value: Any = NotPassed(), property_: _Property = UNBOUND_PROPERTY ): """Preprocess new instances. If value isn't passed, attempt to instantiate the default, but allow non-matching defaults. This is the equivalent of `Element.__call__`. """ if isinstance(value, cls): return value if not isinstance(cls.default, NotPassed) and isinstance( value, NotPassed ): try: return cls(cls.default, property_) except (TypeError, ValidationError): return cls.default if isinstance(value, NotPassed): return value for validator in cls.validators: validator(value, property_) return object.__new__(cls) def __init__( self, value: Any = NotPassed(), _property: _Property = UNBOUND_PROPERTY ): """Initialise the object. The equivalent of `Element.__call__`, but on a class/instance. """ if value is self: return if isinstance(value, NotPassed) and not isinstance( self.default, NotPassed ): value = self.default self._dict: Dict[str, Any] = {} for attr_name, attr_value in type(self).__properties__(value).items(): if attr_name in type(self).properties: setattr(self, attr_name, attr_value) self._dict[attr_name] = attr_value def __repr__(self): attr_values = { attr: getattr(self, attr) for attr in type(self).properties } attr_repr = ", ".join( [ f"{attr}={repr(value)}" for attr, value in attr_values.items() if not isinstance(value, NotPassed) ] ) return f"{self.__class__.__name__}({attr_repr})" def __eq__(self, other): return ( type(self) is type(other) # pylint: disable=protected-access and self._dict == other._dict ) def __getitem__(self, key: str) -> Any: return self._dict[key]
[docs] @staticmethod def inline( name: str, *, properties: Dict[str, Any] = None, **kwargs ) -> ObjectMeta: """Inline constructor for object schema elements. Useful for minor objects, at the cost of reduced type checking support. :param name: The name of the schema element. :param properties: Dictionary of properties accepted by the schema element. :param kwargs: Any accepted class args to `Object` subclasses. :return: A new subclass of `Object` with the appropriate validation rules. """ properties = properties or {} object_properties = ObjectClassDict() for prop_name, prop in properties.items(): object_properties[prop_name] = prop return ObjectMeta(name, (Object,), object_properties, **kwargs)