haiway 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- haiway/__init__.py +75 -0
- haiway/context/__init__.py +14 -0
- haiway/context/access.py +416 -0
- haiway/context/dependencies.py +61 -0
- haiway/context/metrics.py +329 -0
- haiway/context/state.py +115 -0
- haiway/context/tasks.py +65 -0
- haiway/context/types.py +17 -0
- haiway/helpers/__init__.py +13 -0
- haiway/helpers/asynchronous.py +226 -0
- haiway/helpers/cache.py +326 -0
- haiway/helpers/retry.py +210 -0
- haiway/helpers/throttling.py +133 -0
- haiway/helpers/timeout.py +112 -0
- haiway/py.typed +0 -0
- haiway/state/__init__.py +8 -0
- haiway/state/attributes.py +360 -0
- haiway/state/structure.py +254 -0
- haiway/state/validation.py +125 -0
- haiway/types/__init__.py +11 -0
- haiway/types/frozen.py +5 -0
- haiway/types/missing.py +91 -0
- haiway/utils/__init__.py +23 -0
- haiway/utils/always.py +61 -0
- haiway/utils/env.py +164 -0
- haiway/utils/immutable.py +28 -0
- haiway/utils/logs.py +57 -0
- haiway/utils/mimic.py +77 -0
- haiway/utils/noop.py +24 -0
- haiway/utils/queue.py +89 -0
- haiway-0.1.0.dist-info/LICENSE +21 -0
- haiway-0.1.0.dist-info/METADATA +86 -0
- haiway-0.1.0.dist-info/RECORD +35 -0
- haiway-0.1.0.dist-info/WHEEL +5 -0
- haiway-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,360 @@
|
|
1
|
+
import sys
|
2
|
+
import types
|
3
|
+
import typing
|
4
|
+
from collections.abc import Mapping
|
5
|
+
from types import NoneType, UnionType
|
6
|
+
from typing import (
|
7
|
+
Any,
|
8
|
+
ClassVar,
|
9
|
+
ForwardRef,
|
10
|
+
Generic,
|
11
|
+
Literal,
|
12
|
+
TypeAliasType,
|
13
|
+
TypeVar,
|
14
|
+
get_args,
|
15
|
+
get_origin,
|
16
|
+
get_type_hints,
|
17
|
+
)
|
18
|
+
|
19
|
+
__all__ = [
|
20
|
+
"attribute_annotations",
|
21
|
+
"AttributeAnnotation",
|
22
|
+
]
|
23
|
+
|
24
|
+
|
25
|
+
class AttributeAnnotation:
|
26
|
+
def __init__(
|
27
|
+
self,
|
28
|
+
*,
|
29
|
+
origin: Any,
|
30
|
+
arguments: list[Any],
|
31
|
+
) -> None:
|
32
|
+
self.origin: Any = origin
|
33
|
+
self.arguments: list[Any] = arguments
|
34
|
+
|
35
|
+
def __eq__(
|
36
|
+
self,
|
37
|
+
other: Any,
|
38
|
+
) -> bool:
|
39
|
+
return self is other or (
|
40
|
+
isinstance(other, self.__class__)
|
41
|
+
and self.origin == other.origin
|
42
|
+
and self.arguments == other.arguments
|
43
|
+
)
|
44
|
+
|
45
|
+
|
46
|
+
def attribute_annotations(
|
47
|
+
cls: type[Any],
|
48
|
+
/,
|
49
|
+
type_parameters: dict[str, Any] | None = None,
|
50
|
+
) -> dict[str, AttributeAnnotation]:
|
51
|
+
type_parameters = type_parameters or {}
|
52
|
+
|
53
|
+
self_annotation = AttributeAnnotation(
|
54
|
+
origin=cls,
|
55
|
+
arguments=[], # ignore self arguments here, Structure will have them resolved at this stage
|
56
|
+
)
|
57
|
+
localns: dict[str, Any] = {cls.__name__: cls}
|
58
|
+
recursion_guard: dict[Any, AttributeAnnotation] = {cls: self_annotation}
|
59
|
+
attributes: dict[str, AttributeAnnotation] = {}
|
60
|
+
|
61
|
+
for key, annotation in get_type_hints(cls, localns=localns).items():
|
62
|
+
# do not include ClassVars, private or dunder items
|
63
|
+
if ((get_origin(annotation) or annotation) is ClassVar) or key.startswith("_"):
|
64
|
+
continue
|
65
|
+
|
66
|
+
attributes[key] = _resolve_attribute_annotation(
|
67
|
+
annotation,
|
68
|
+
self_annotation=self_annotation,
|
69
|
+
type_parameters=type_parameters,
|
70
|
+
module=cls.__module__,
|
71
|
+
localns=localns,
|
72
|
+
recursion_guard=recursion_guard,
|
73
|
+
)
|
74
|
+
|
75
|
+
return attributes
|
76
|
+
|
77
|
+
|
78
|
+
def _resolve_attribute_annotation( # noqa: C901, PLR0911, PLR0912, PLR0913
|
79
|
+
annotation: Any,
|
80
|
+
/,
|
81
|
+
self_annotation: AttributeAnnotation | None,
|
82
|
+
type_parameters: dict[str, Any],
|
83
|
+
module: str,
|
84
|
+
localns: dict[str, Any],
|
85
|
+
recursion_guard: Mapping[Any, AttributeAnnotation], # TODO: verify recursion!
|
86
|
+
) -> AttributeAnnotation:
|
87
|
+
# resolve annotation directly if able
|
88
|
+
match annotation:
|
89
|
+
# None
|
90
|
+
case types.NoneType | types.NoneType():
|
91
|
+
return AttributeAnnotation(
|
92
|
+
origin=NoneType,
|
93
|
+
arguments=[],
|
94
|
+
)
|
95
|
+
|
96
|
+
# forward reference through string
|
97
|
+
case str() as forward_ref:
|
98
|
+
return _resolve_attribute_annotation(
|
99
|
+
ForwardRef(forward_ref, module=module)._evaluate(
|
100
|
+
globalns=None,
|
101
|
+
localns=localns,
|
102
|
+
recursive_guard=frozenset(),
|
103
|
+
),
|
104
|
+
self_annotation=self_annotation,
|
105
|
+
type_parameters=type_parameters,
|
106
|
+
module=module,
|
107
|
+
localns=localns,
|
108
|
+
recursion_guard=recursion_guard, # we might need to update it somehow?
|
109
|
+
)
|
110
|
+
|
111
|
+
# forward reference directly
|
112
|
+
case typing.ForwardRef() as reference:
|
113
|
+
return _resolve_attribute_annotation(
|
114
|
+
reference._evaluate(
|
115
|
+
globalns=None,
|
116
|
+
localns=localns,
|
117
|
+
recursive_guard=frozenset(),
|
118
|
+
),
|
119
|
+
self_annotation=self_annotation,
|
120
|
+
type_parameters=type_parameters,
|
121
|
+
module=module,
|
122
|
+
localns=localns,
|
123
|
+
recursion_guard=recursion_guard, # we might need to update it somehow?
|
124
|
+
)
|
125
|
+
|
126
|
+
# generic alias aka parametrized type
|
127
|
+
case types.GenericAlias() as generic_alias:
|
128
|
+
match get_origin(generic_alias):
|
129
|
+
# check for an alias with parameters
|
130
|
+
case typing.TypeAliasType() as alias: # pyright: ignore[reportUnnecessaryComparison]
|
131
|
+
type_alias: AttributeAnnotation = AttributeAnnotation(
|
132
|
+
origin=TypeAliasType,
|
133
|
+
arguments=[],
|
134
|
+
)
|
135
|
+
resolved: AttributeAnnotation = _resolve_attribute_annotation(
|
136
|
+
alias.__value__,
|
137
|
+
self_annotation=None,
|
138
|
+
type_parameters=type_parameters,
|
139
|
+
module=module,
|
140
|
+
localns=localns,
|
141
|
+
recursion_guard=recursion_guard,
|
142
|
+
)
|
143
|
+
type_alias.origin = resolved.origin
|
144
|
+
type_alias.arguments = resolved.arguments
|
145
|
+
return type_alias
|
146
|
+
|
147
|
+
# check if we can resolve it as generic
|
148
|
+
case parametrized if issubclass(parametrized, Generic):
|
149
|
+
parametrized_type: Any = parametrized.__class_getitem__( # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
|
150
|
+
*(
|
151
|
+
type_parameters.get(
|
152
|
+
arg.__name__,
|
153
|
+
arg.__bound__ or Any,
|
154
|
+
)
|
155
|
+
if isinstance(arg, TypeVar)
|
156
|
+
else arg
|
157
|
+
for arg in get_args(generic_alias)
|
158
|
+
)
|
159
|
+
)
|
160
|
+
|
161
|
+
match parametrized_type:
|
162
|
+
# verify if we got any specific type or generic alias again
|
163
|
+
case types.GenericAlias():
|
164
|
+
return AttributeAnnotation(
|
165
|
+
origin=parametrized,
|
166
|
+
arguments=[
|
167
|
+
_resolve_attribute_annotation(
|
168
|
+
argument,
|
169
|
+
self_annotation=self_annotation,
|
170
|
+
type_parameters=type_parameters,
|
171
|
+
module=module,
|
172
|
+
localns=localns,
|
173
|
+
recursion_guard=recursion_guard,
|
174
|
+
)
|
175
|
+
for argument in get_args(generic_alias)
|
176
|
+
],
|
177
|
+
)
|
178
|
+
|
179
|
+
# use resolved type if it is not an alias again
|
180
|
+
case _:
|
181
|
+
return AttributeAnnotation(
|
182
|
+
origin=parametrized_type,
|
183
|
+
arguments=[],
|
184
|
+
)
|
185
|
+
|
186
|
+
# anything else - try to resolve a concrete type or use as is
|
187
|
+
case origin:
|
188
|
+
return AttributeAnnotation(
|
189
|
+
origin=origin,
|
190
|
+
arguments=[
|
191
|
+
_resolve_attribute_annotation(
|
192
|
+
argument,
|
193
|
+
self_annotation=self_annotation,
|
194
|
+
type_parameters=type_parameters,
|
195
|
+
module=module,
|
196
|
+
localns=localns,
|
197
|
+
recursion_guard=recursion_guard,
|
198
|
+
)
|
199
|
+
for argument in get_args(generic_alias)
|
200
|
+
],
|
201
|
+
)
|
202
|
+
|
203
|
+
# type alias
|
204
|
+
case typing.TypeAliasType() as alias:
|
205
|
+
type_alias: AttributeAnnotation = AttributeAnnotation(
|
206
|
+
origin=TypeAliasType,
|
207
|
+
arguments=[],
|
208
|
+
)
|
209
|
+
resolved: AttributeAnnotation = _resolve_attribute_annotation(
|
210
|
+
alias.__value__,
|
211
|
+
self_annotation=None,
|
212
|
+
type_parameters=type_parameters,
|
213
|
+
module=module,
|
214
|
+
localns=localns,
|
215
|
+
recursion_guard=recursion_guard,
|
216
|
+
)
|
217
|
+
type_alias.origin = resolved.origin
|
218
|
+
type_alias.arguments = resolved.arguments
|
219
|
+
return type_alias
|
220
|
+
|
221
|
+
# type parameter
|
222
|
+
case typing.TypeVar():
|
223
|
+
return _resolve_attribute_annotation(
|
224
|
+
# try to resolve it from current parameters if able
|
225
|
+
type_parameters.get(
|
226
|
+
annotation.__name__,
|
227
|
+
# use bound as default or Any otherwise
|
228
|
+
annotation.__bound__ or Any,
|
229
|
+
),
|
230
|
+
self_annotation=None,
|
231
|
+
type_parameters=type_parameters,
|
232
|
+
module=module,
|
233
|
+
localns=localns,
|
234
|
+
recursion_guard=recursion_guard,
|
235
|
+
)
|
236
|
+
|
237
|
+
case typing.ParamSpec():
|
238
|
+
sys.stderr.write(
|
239
|
+
"ParamSpec is not supported for attribute annotations,"
|
240
|
+
" ignoring with Any type - it might incorrectly validate types\n"
|
241
|
+
)
|
242
|
+
return AttributeAnnotation(
|
243
|
+
origin=Any,
|
244
|
+
arguments=[],
|
245
|
+
)
|
246
|
+
|
247
|
+
case typing.TypeVarTuple():
|
248
|
+
sys.stderr.write(
|
249
|
+
"TypeVarTuple is not supported for attribute annotations,"
|
250
|
+
" ignoring with Any type - it might incorrectly validate types\n"
|
251
|
+
)
|
252
|
+
return AttributeAnnotation(
|
253
|
+
origin=Any,
|
254
|
+
arguments=[],
|
255
|
+
)
|
256
|
+
|
257
|
+
case _:
|
258
|
+
pass # proceed to resolving based on origin
|
259
|
+
|
260
|
+
# resolve based on origin if any
|
261
|
+
match get_origin(annotation) or annotation:
|
262
|
+
case types.UnionType | typing.Union:
|
263
|
+
return AttributeAnnotation(
|
264
|
+
origin=UnionType, # pyright: ignore[reportArgumentType]
|
265
|
+
arguments=[
|
266
|
+
recursion_guard.get(
|
267
|
+
argument,
|
268
|
+
_resolve_attribute_annotation(
|
269
|
+
argument,
|
270
|
+
self_annotation=self_annotation,
|
271
|
+
type_parameters=type_parameters,
|
272
|
+
module=module,
|
273
|
+
localns=localns,
|
274
|
+
recursion_guard=recursion_guard,
|
275
|
+
),
|
276
|
+
)
|
277
|
+
for argument in get_args(annotation)
|
278
|
+
],
|
279
|
+
)
|
280
|
+
|
281
|
+
case typing.Callable: # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
|
282
|
+
return AttributeAnnotation(
|
283
|
+
origin=typing.Callable,
|
284
|
+
arguments=[
|
285
|
+
_resolve_attribute_annotation(
|
286
|
+
argument,
|
287
|
+
self_annotation=self_annotation,
|
288
|
+
type_parameters=type_parameters,
|
289
|
+
module=module,
|
290
|
+
localns=localns,
|
291
|
+
recursion_guard=recursion_guard,
|
292
|
+
)
|
293
|
+
for argument in get_args(annotation)
|
294
|
+
],
|
295
|
+
)
|
296
|
+
|
297
|
+
case typing.Self: # pyright: ignore[reportUnknownMemberType]
|
298
|
+
if not self_annotation:
|
299
|
+
sys.stderr.write(
|
300
|
+
"Unresolved Self attribute annotation,"
|
301
|
+
" ignoring with Any type - it might incorrectly validate types\n"
|
302
|
+
)
|
303
|
+
return AttributeAnnotation(
|
304
|
+
origin=Any,
|
305
|
+
arguments=[],
|
306
|
+
)
|
307
|
+
|
308
|
+
return self_annotation
|
309
|
+
|
310
|
+
# unwrap from irrelevant type wrappers
|
311
|
+
case typing.Annotated | typing.Final | typing.Required | typing.NotRequired:
|
312
|
+
return _resolve_attribute_annotation(
|
313
|
+
get_args(annotation)[0],
|
314
|
+
self_annotation=self_annotation,
|
315
|
+
type_parameters=type_parameters,
|
316
|
+
module=module,
|
317
|
+
localns=localns,
|
318
|
+
recursion_guard=recursion_guard,
|
319
|
+
)
|
320
|
+
|
321
|
+
case typing.Optional: # optional is a Union[Value, None]
|
322
|
+
return AttributeAnnotation(
|
323
|
+
origin=UnionType, # pyright: ignore[reportArgumentType]
|
324
|
+
arguments=[
|
325
|
+
_resolve_attribute_annotation(
|
326
|
+
get_args(annotation)[0],
|
327
|
+
self_annotation=self_annotation,
|
328
|
+
type_parameters=type_parameters,
|
329
|
+
module=module,
|
330
|
+
localns=localns,
|
331
|
+
recursion_guard=recursion_guard,
|
332
|
+
),
|
333
|
+
AttributeAnnotation(
|
334
|
+
origin=NoneType,
|
335
|
+
arguments=[],
|
336
|
+
),
|
337
|
+
],
|
338
|
+
)
|
339
|
+
|
340
|
+
case typing.Literal:
|
341
|
+
return AttributeAnnotation(
|
342
|
+
origin=Literal,
|
343
|
+
arguments=list(get_args(annotation)),
|
344
|
+
)
|
345
|
+
|
346
|
+
case other: # finally use whatever there was
|
347
|
+
return AttributeAnnotation(
|
348
|
+
origin=other,
|
349
|
+
arguments=[
|
350
|
+
_resolve_attribute_annotation(
|
351
|
+
argument,
|
352
|
+
self_annotation=self_annotation,
|
353
|
+
type_parameters=type_parameters,
|
354
|
+
module=module,
|
355
|
+
localns=localns,
|
356
|
+
recursion_guard=recursion_guard,
|
357
|
+
)
|
358
|
+
for argument in get_args(other)
|
359
|
+
],
|
360
|
+
)
|
@@ -0,0 +1,254 @@
|
|
1
|
+
from collections.abc import Callable
|
2
|
+
from copy import deepcopy
|
3
|
+
from types import GenericAlias
|
4
|
+
from typing import (
|
5
|
+
Any,
|
6
|
+
ClassVar,
|
7
|
+
Generic,
|
8
|
+
Self,
|
9
|
+
TypeVar,
|
10
|
+
cast,
|
11
|
+
dataclass_transform,
|
12
|
+
final,
|
13
|
+
get_origin,
|
14
|
+
)
|
15
|
+
from weakref import WeakValueDictionary
|
16
|
+
|
17
|
+
from haiway.state.attributes import AttributeAnnotation, attribute_annotations
|
18
|
+
from haiway.state.validation import attribute_type_validator
|
19
|
+
from haiway.types.missing import MISSING, Missing
|
20
|
+
|
21
|
+
__all__ = [
|
22
|
+
"Structure",
|
23
|
+
]
|
24
|
+
|
25
|
+
|
26
|
+
@final
|
27
|
+
class StructureAttribute[Value]:
|
28
|
+
def __init__(
|
29
|
+
self,
|
30
|
+
annotation: AttributeAnnotation,
|
31
|
+
default: Value | Missing,
|
32
|
+
validator: Callable[[Any], Value],
|
33
|
+
) -> None:
|
34
|
+
self.annotation: AttributeAnnotation = annotation
|
35
|
+
self.default: Value | Missing = default
|
36
|
+
self.validator: Callable[[Any], Value] = validator
|
37
|
+
|
38
|
+
def validated(
|
39
|
+
self,
|
40
|
+
value: Any | Missing,
|
41
|
+
/,
|
42
|
+
) -> Value:
|
43
|
+
return self.validator(self.default if value is MISSING else value)
|
44
|
+
|
45
|
+
|
46
|
+
@dataclass_transform(
|
47
|
+
kw_only_default=True,
|
48
|
+
frozen_default=True,
|
49
|
+
field_specifiers=(),
|
50
|
+
)
|
51
|
+
class StructureMeta(type):
|
52
|
+
def __new__(
|
53
|
+
cls,
|
54
|
+
/,
|
55
|
+
name: str,
|
56
|
+
bases: tuple[type, ...],
|
57
|
+
namespace: dict[str, Any],
|
58
|
+
type_parameters: dict[str, Any] | None = None,
|
59
|
+
**kwargs: Any,
|
60
|
+
) -> Any:
|
61
|
+
structure_type = type.__new__(
|
62
|
+
cls,
|
63
|
+
name,
|
64
|
+
bases,
|
65
|
+
namespace,
|
66
|
+
**kwargs,
|
67
|
+
)
|
68
|
+
|
69
|
+
attributes: dict[str, StructureAttribute[Any]] = {}
|
70
|
+
|
71
|
+
if bases: # handle base class
|
72
|
+
for key, annotation in attribute_annotations(
|
73
|
+
structure_type,
|
74
|
+
type_parameters=type_parameters,
|
75
|
+
).items():
|
76
|
+
# do not include ClassVars and dunder items
|
77
|
+
if ((get_origin(annotation) or annotation) is ClassVar) or key.startswith("__"):
|
78
|
+
continue
|
79
|
+
|
80
|
+
attributes[key] = StructureAttribute(
|
81
|
+
annotation=annotation,
|
82
|
+
default=getattr(structure_type, key, MISSING),
|
83
|
+
validator=attribute_type_validator(annotation),
|
84
|
+
)
|
85
|
+
|
86
|
+
structure_type.__ATTRIBUTES__ = attributes # pyright: ignore[reportAttributeAccessIssue]
|
87
|
+
structure_type.__slots__ = frozenset(attributes.keys()) # pyright: ignore[reportAttributeAccessIssue]
|
88
|
+
structure_type.__match_args__ = structure_type.__slots__ # pyright: ignore[reportAttributeAccessIssue]
|
89
|
+
|
90
|
+
return structure_type
|
91
|
+
|
92
|
+
|
93
|
+
_types_cache: WeakValueDictionary[
|
94
|
+
tuple[
|
95
|
+
Any,
|
96
|
+
tuple[Any, ...],
|
97
|
+
],
|
98
|
+
Any,
|
99
|
+
] = WeakValueDictionary()
|
100
|
+
|
101
|
+
|
102
|
+
class Structure(metaclass=StructureMeta):
|
103
|
+
"""
|
104
|
+
Base class for immutable data structures.
|
105
|
+
"""
|
106
|
+
|
107
|
+
__ATTRIBUTES__: ClassVar[dict[str, StructureAttribute[Any]]]
|
108
|
+
|
109
|
+
def __class_getitem__(
|
110
|
+
cls,
|
111
|
+
type_argument: tuple[type[Any], ...] | type[Any],
|
112
|
+
) -> type[Self]:
|
113
|
+
assert Generic in cls.__bases__, "Can't specialize non generic type!" # nosec: B101
|
114
|
+
|
115
|
+
type_arguments: tuple[type[Any], ...]
|
116
|
+
match type_argument:
|
117
|
+
case [*arguments]:
|
118
|
+
type_arguments = tuple(arguments)
|
119
|
+
|
120
|
+
case argument:
|
121
|
+
type_arguments = (argument,)
|
122
|
+
|
123
|
+
if any(isinstance(argument, TypeVar) for argument in type_arguments): # pyright: ignore[reportUnnecessaryIsInstance]
|
124
|
+
# if we got unfinished type treat it as an alias instead of resolving
|
125
|
+
return cast(type[Self], GenericAlias(cls, type_arguments))
|
126
|
+
|
127
|
+
assert len(type_arguments) == len( # nosec: B101
|
128
|
+
cls.__type_params__
|
129
|
+
), "Type arguments count has to match type parameters count"
|
130
|
+
|
131
|
+
if cached := _types_cache.get((cls, type_arguments)):
|
132
|
+
return cached
|
133
|
+
|
134
|
+
type_parameters: dict[str, Any] = {
|
135
|
+
parameter.__name__: argument
|
136
|
+
for (parameter, argument) in zip(
|
137
|
+
cls.__type_params__ or (),
|
138
|
+
type_arguments or (),
|
139
|
+
strict=False,
|
140
|
+
)
|
141
|
+
}
|
142
|
+
|
143
|
+
parameter_names: str = ",".join(
|
144
|
+
getattr(
|
145
|
+
argument,
|
146
|
+
"__name__",
|
147
|
+
str(argument),
|
148
|
+
)
|
149
|
+
for argument in type_arguments
|
150
|
+
)
|
151
|
+
name: str = f"{cls.__name__}[{parameter_names}]"
|
152
|
+
bases: tuple[type[Self]] = (cls,)
|
153
|
+
|
154
|
+
parametrized_type: type[Self] = StructureMeta.__new__(
|
155
|
+
cls.__class__,
|
156
|
+
name=name,
|
157
|
+
bases=bases,
|
158
|
+
namespace={"__module__": cls.__module__},
|
159
|
+
type_parameters=type_parameters,
|
160
|
+
)
|
161
|
+
_types_cache[(cls, type_arguments)] = parametrized_type
|
162
|
+
return parametrized_type
|
163
|
+
|
164
|
+
def __init__(
|
165
|
+
self,
|
166
|
+
**kwargs: Any,
|
167
|
+
) -> None:
|
168
|
+
for name, attribute in self.__ATTRIBUTES__.items():
|
169
|
+
object.__setattr__(
|
170
|
+
self, # pyright: ignore[reportUnknownArgumentType]
|
171
|
+
name,
|
172
|
+
attribute.validated(
|
173
|
+
kwargs.get(
|
174
|
+
name,
|
175
|
+
MISSING,
|
176
|
+
),
|
177
|
+
),
|
178
|
+
)
|
179
|
+
|
180
|
+
def updated(
|
181
|
+
self,
|
182
|
+
**kwargs: Any,
|
183
|
+
) -> Self:
|
184
|
+
return self.__replace__(**kwargs)
|
185
|
+
|
186
|
+
def as_dict(self) -> dict[str, Any]:
|
187
|
+
return vars(self)
|
188
|
+
|
189
|
+
def __str__(self) -> str:
|
190
|
+
attributes: str = ", ".join([f"{key}: {value}" for key, value in vars(self).items()])
|
191
|
+
return f"{self.__class__.__name__}({attributes})"
|
192
|
+
|
193
|
+
def __repr__(self) -> str:
|
194
|
+
return str(self)
|
195
|
+
|
196
|
+
def __eq__(
|
197
|
+
self,
|
198
|
+
other: Any,
|
199
|
+
) -> bool:
|
200
|
+
if not issubclass(other.__class__, self.__class__):
|
201
|
+
return False
|
202
|
+
|
203
|
+
return all(
|
204
|
+
getattr(self, key, MISSING) == getattr(other, key, MISSING)
|
205
|
+
for key in self.__ATTRIBUTES__.keys()
|
206
|
+
)
|
207
|
+
|
208
|
+
def __setattr__(
|
209
|
+
self,
|
210
|
+
name: str,
|
211
|
+
value: Any,
|
212
|
+
) -> Any:
|
213
|
+
raise AttributeError(
|
214
|
+
f"Can't modify immutable structure {self.__class__.__qualname__},"
|
215
|
+
f" attribute - '{name}' cannot be modified"
|
216
|
+
)
|
217
|
+
|
218
|
+
def __delattr__(
|
219
|
+
self,
|
220
|
+
name: str,
|
221
|
+
) -> None:
|
222
|
+
raise AttributeError(
|
223
|
+
f"Can't modify immutable structure {self.__class__.__qualname__},"
|
224
|
+
f" attribute - '{name}' cannot be deleted"
|
225
|
+
)
|
226
|
+
|
227
|
+
def __copy__(self) -> Self:
|
228
|
+
return self.__class__(**vars(self))
|
229
|
+
|
230
|
+
def __deepcopy__(
|
231
|
+
self,
|
232
|
+
memo: dict[int, Any] | None,
|
233
|
+
) -> Self:
|
234
|
+
copy: Self = self.__class__(
|
235
|
+
**{
|
236
|
+
key: deepcopy(
|
237
|
+
value,
|
238
|
+
memo,
|
239
|
+
)
|
240
|
+
for key, value in vars(self).items()
|
241
|
+
}
|
242
|
+
)
|
243
|
+
return copy
|
244
|
+
|
245
|
+
def __replace__(
|
246
|
+
self,
|
247
|
+
**kwargs: Any,
|
248
|
+
) -> Self:
|
249
|
+
return self.__class__(
|
250
|
+
**{
|
251
|
+
**vars(self),
|
252
|
+
**kwargs,
|
253
|
+
}
|
254
|
+
)
|