Source code for statham.dsl.elements.object

from typing import Any, ClassVar, Dict, Union

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


# TODO: Test and support Object inheritance.
# TODO: Test and support limited recursive models.
# TODO: Test and support validation of setting model attributes.


[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.dsl.elements import Object, String from statham.dsl.property import Property class Poll(Object, additionalProperties=False): questions: List[str] = Property(String(), required=True) poll = Poll({"questions": ["What's up?"]}) """ # TODO: Add an inline constructor # Object.inline("MyObject", properties={}, ...) properties: ClassVar[Dict[str, _Property]] default: ClassVar[Any] additionalProperties: ClassVar[Union[Element, bool]] 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 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]