1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
|
from typing import TYPE_CHECKING, 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)
if not TYPE_CHECKING:
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
|