File: new.py

package info (click to toggle)
python-lazy-model 0.4.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 152 kB
  • sloc: python: 280; makefile: 7
file content (125 lines) | stat: -rw-r--r-- 4,438 bytes parent folder | download
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