from typing import Any, Optional, Set, Dict import pydantic_core from pydantic import BaseModel, PrivateAttr, ValidationError, TypeAdapter from pydantic._internal import _fields from lazy_model.nao import NAO ROOT_KEY = "__root__" _object_setattr = object.__setattr__ class LazyModel(BaseModel): _store: Dict[str, Any] = PrivateAttr(default_factory=dict) _lazily_parsed: bool = PrivateAttr(default=False) @classmethod def lazy_parse( cls, data: Dict[str, Any], fields: Optional[Set[str]] = None, ): fields_values = {} field_alias_map = {} if fields is None: fields = set() for name, field in cls.model_fields.items(): fields_values[name] = NAO alias = field.alias or name if alias in fields: field_alias_map[alias] = name field_set = set(fields_values.keys()) m = cls.model_construct(field_set, **fields_values) m._store = data for alias in fields: m._set_attr(field_alias_map[alias], data[alias]) return m def _parse_value(self, name, value): field_type = self.__class__.model_fields.get(name).annotation try: value = TypeAdapter(field_type).validate_python(value) except ValidationError as e: if ( value is None and self.__class__.model_fields.get(name).required is False ): value = None else: raise e return value def parse_store(self): for name in self.__class__.model_fields: self.__getattribute__(name) def _set_attr(self, name: str, value: Any) -> None: """ Stolen from Pydantic. :param name: :param value: :return: TODO rework """ if name in self.__class_vars__: raise AttributeError( f"{name!r} is a ClassVar of `{self.__class__.__name__}` and cannot be set on an instance. " # noqa: E501 f"If you want to set a value on the class, use `{self.__class__.__name__}.{name} = value`." # noqa: E501 ) elif not _fields.is_valid_field_name(name): if ( self.__pydantic_private__ is None or name not in self.__private_attributes__ ): _object_setattr(self, name, value) else: attribute = self.__private_attributes__[name] if hasattr(attribute, "__set__"): attribute.__set__(self, value) # type: ignore else: self.__pydantic_private__[name] = value return elif self.model_config.get("frozen", None): error: pydantic_core.InitErrorDetails = { "type": "frozen_instance", "loc": (name,), "input": value, } raise pydantic_core.ValidationError.from_exception_data( self.__class__.__name__, [error] ) attr = getattr(self.__class__, name, None) if isinstance(attr, property): attr.__set__(self, value) self.__pydantic_validator__.validate_assignment(self, name, value) def __getattribute__(self, item): # If __class__ is accessed, return it directly to avoid recursion if item == "__class__": return super().__getattribute__(item) # If called on the class itself, delegate to super's __getattribute__ if type(self) is type: # Check if self is a class return super(type, self).__getattribute__(item) # For instances, use the object's __getattribute__ to prevent recursion res = object.__getattribute__(self, item) if res is NAO: field_info = self.__class__.model_fields.get(item) alias = field_info.alias or item value = self._store.get(alias, NAO) if value is NAO: value = field_info.get_default() else: value = self._parse_value(item, value) self._set_attr(item, value) res = super(LazyModel, self).__getattribute__(item) return res