haiway 0.6.4__py3-none-any.whl → 0.7.1__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/state/attributes.py +632 -260
- haiway/state/structure.py +135 -20
- haiway/state/validation.py +197 -47
- {haiway-0.6.4.dist-info → haiway-0.7.1.dist-info}/METADATA +1 -1
- {haiway-0.6.4.dist-info → haiway-0.7.1.dist-info}/RECORD +8 -8
- {haiway-0.6.4.dist-info → haiway-0.7.1.dist-info}/LICENSE +0 -0
- {haiway-0.6.4.dist-info → haiway-0.7.1.dist-info}/WHEEL +0 -0
- {haiway-0.6.4.dist-info → haiway-0.7.1.dist-info}/top_level.txt +0 -0
haiway/state/structure.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
import typing
|
2
|
+
from collections.abc import Mapping
|
2
3
|
from copy import deepcopy
|
3
4
|
from types import EllipsisType, GenericAlias
|
4
5
|
from typing import (
|
@@ -10,13 +11,15 @@ from typing import (
|
|
10
11
|
cast,
|
11
12
|
dataclass_transform,
|
12
13
|
final,
|
13
|
-
get_origin,
|
14
14
|
)
|
15
15
|
from weakref import WeakValueDictionary
|
16
16
|
|
17
17
|
from haiway.state.attributes import AttributeAnnotation, attribute_annotations
|
18
18
|
from haiway.state.path import AttributePath
|
19
|
-
from haiway.state.validation import
|
19
|
+
from haiway.state.validation import (
|
20
|
+
AttributeValidation,
|
21
|
+
AttributeValidator,
|
22
|
+
)
|
20
23
|
from haiway.types import MISSING, Missing, not_missing
|
21
24
|
|
22
25
|
__all__ = [
|
@@ -28,13 +31,15 @@ __all__ = [
|
|
28
31
|
class StateAttribute[Value]:
|
29
32
|
def __init__(
|
30
33
|
self,
|
34
|
+
name: str,
|
31
35
|
annotation: AttributeAnnotation,
|
32
36
|
default: Value | Missing,
|
33
|
-
validator:
|
37
|
+
validator: AttributeValidation[Value],
|
34
38
|
) -> None:
|
39
|
+
self.name: str = name
|
35
40
|
self.annotation: AttributeAnnotation = annotation
|
36
41
|
self.default: Value | Missing = default
|
37
|
-
self.validator:
|
42
|
+
self.validator: AttributeValidation[Value] = validator
|
38
43
|
|
39
44
|
def validated(
|
40
45
|
self,
|
@@ -69,28 +74,119 @@ class StateMeta(type):
|
|
69
74
|
|
70
75
|
attributes: dict[str, StateAttribute[Any]] = {}
|
71
76
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
annotation
|
83
|
-
|
84
|
-
|
85
|
-
|
77
|
+
for key, annotation in attribute_annotations(
|
78
|
+
state_type,
|
79
|
+
type_parameters=type_parameters or {},
|
80
|
+
).items():
|
81
|
+
default: Any = getattr(state_type, key, MISSING)
|
82
|
+
attributes[key] = StateAttribute(
|
83
|
+
name=key,
|
84
|
+
annotation=annotation.update_required(default is MISSING),
|
85
|
+
default=default,
|
86
|
+
validator=AttributeValidator.of(
|
87
|
+
annotation,
|
88
|
+
recursion_guard={
|
89
|
+
str(AttributeAnnotation(origin=state_type)): state_type.validator
|
90
|
+
},
|
91
|
+
),
|
92
|
+
)
|
86
93
|
|
94
|
+
state_type.__PARAMETERS__ = type_parameters # pyright: ignore[reportAttributeAccessIssue]
|
87
95
|
state_type.__ATTRIBUTES__ = attributes # pyright: ignore[reportAttributeAccessIssue]
|
88
96
|
state_type.__slots__ = frozenset(attributes.keys()) # pyright: ignore[reportAttributeAccessIssue]
|
89
97
|
state_type.__match_args__ = state_type.__slots__ # pyright: ignore[reportAttributeAccessIssue]
|
90
|
-
state_type._ = AttributePath(state_type, attribute=state_type) # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
|
98
|
+
state_type._ = AttributePath(state_type, attribute=state_type) # pyright: ignore[reportCallIssue, reportUnknownMemberType, reportAttributeAccessIssue]
|
91
99
|
|
92
100
|
return state_type
|
93
101
|
|
102
|
+
def validator(
|
103
|
+
cls,
|
104
|
+
value: Any,
|
105
|
+
/,
|
106
|
+
) -> Any: ...
|
107
|
+
|
108
|
+
def __instancecheck__(
|
109
|
+
self,
|
110
|
+
instance: Any,
|
111
|
+
) -> bool:
|
112
|
+
# check for type match
|
113
|
+
if self.__subclasscheck__(type(instance)): # pyright: ignore[reportUnknownArgumentType]
|
114
|
+
return True
|
115
|
+
|
116
|
+
# otherwise check if we are dealing with unparametrized base
|
117
|
+
# against the parametrized one, our generic subtypes have base of unparametrized type
|
118
|
+
if type(instance) not in self.__bases__:
|
119
|
+
return False
|
120
|
+
|
121
|
+
try:
|
122
|
+
# validate instance to check unparametrized fields
|
123
|
+
_ = self(**vars(instance))
|
124
|
+
|
125
|
+
except Exception:
|
126
|
+
return False
|
127
|
+
|
128
|
+
else:
|
129
|
+
return True
|
130
|
+
|
131
|
+
def __subclasscheck__( # noqa: C901, PLR0911, PLR0912
|
132
|
+
self,
|
133
|
+
subclass: type[Any],
|
134
|
+
) -> bool:
|
135
|
+
# check if we are the same class for early exit
|
136
|
+
if self == subclass:
|
137
|
+
return True
|
138
|
+
|
139
|
+
# then check if we are parametrized
|
140
|
+
checked_parameters: Mapping[str, Any] | None = getattr(
|
141
|
+
self,
|
142
|
+
"__PARAMETERS__",
|
143
|
+
None,
|
144
|
+
)
|
145
|
+
if checked_parameters is None:
|
146
|
+
# if we are not parametrized allow any subclass
|
147
|
+
return self in subclass.__bases__
|
148
|
+
|
149
|
+
# verify if we have common base next - our generic subtypes have the same base
|
150
|
+
if self.__bases__ == subclass.__bases__:
|
151
|
+
# if we have the same bases we have different generic subtypes
|
152
|
+
# we can verify all of the attributes to check if we have common base
|
153
|
+
available_parameters: Mapping[str, Any] | None = getattr(
|
154
|
+
subclass,
|
155
|
+
"__PARAMETERS__",
|
156
|
+
None,
|
157
|
+
)
|
158
|
+
|
159
|
+
if available_parameters is None:
|
160
|
+
# if we have no parameters at this stage this is a serious bug
|
161
|
+
raise RuntimeError("Invalid type parametrization for %s", subclass)
|
162
|
+
|
163
|
+
for key, param in checked_parameters.items():
|
164
|
+
match available_parameters.get(key):
|
165
|
+
case None: # if any parameter is missing we should not be there already
|
166
|
+
return False
|
167
|
+
|
168
|
+
case typing.Any:
|
169
|
+
continue # Any ignores type checks
|
170
|
+
|
171
|
+
case checked:
|
172
|
+
if param is Any:
|
173
|
+
continue # Any ignores type checks
|
174
|
+
|
175
|
+
elif issubclass(checked, param):
|
176
|
+
continue # if we have matching type we are fine
|
177
|
+
|
178
|
+
else:
|
179
|
+
return False # types are not matching
|
180
|
+
|
181
|
+
return True # when all parameters were matching we have matching subclass
|
182
|
+
|
183
|
+
elif subclass in self.__bases__: # our generic subtypes have base of unparametrized type
|
184
|
+
# if subclass parameters were not provided then we can be valid ony if all were Any
|
185
|
+
return all(param is Any for param in checked_parameters.values())
|
186
|
+
|
187
|
+
else:
|
188
|
+
return False # we have different base / comparing to not parametrized
|
189
|
+
|
94
190
|
|
95
191
|
_types_cache: WeakValueDictionary[
|
96
192
|
tuple[
|
@@ -108,13 +204,16 @@ class State(metaclass=StateMeta):
|
|
108
204
|
|
109
205
|
_: ClassVar[Self]
|
110
206
|
__IMMUTABLE__: ClassVar[EllipsisType] = ...
|
207
|
+
__PARAMETERS__: ClassVar[Mapping[str, Any] | None] = None
|
111
208
|
__ATTRIBUTES__: ClassVar[dict[str, StateAttribute[Any]]]
|
112
209
|
|
210
|
+
@classmethod
|
113
211
|
def __class_getitem__(
|
114
212
|
cls,
|
115
213
|
type_argument: tuple[type[Any], ...] | type[Any],
|
116
214
|
) -> type[Self]:
|
117
215
|
assert Generic in cls.__bases__, "Can't specialize non generic type!" # nosec: B101
|
216
|
+
assert cls.__PARAMETERS__ is None, "Can't specialize already specialized type!" # nosec: B101
|
118
217
|
|
119
218
|
type_arguments: tuple[type[Any], ...]
|
120
219
|
match type_argument:
|
@@ -165,6 +264,22 @@ class State(metaclass=StateMeta):
|
|
165
264
|
_types_cache[(cls, type_arguments)] = parametrized_type
|
166
265
|
return parametrized_type
|
167
266
|
|
267
|
+
@classmethod
|
268
|
+
def validator(
|
269
|
+
cls,
|
270
|
+
value: Any,
|
271
|
+
/,
|
272
|
+
) -> Self:
|
273
|
+
match value:
|
274
|
+
case validated if isinstance(validated, cls):
|
275
|
+
return validated
|
276
|
+
|
277
|
+
case {**values}:
|
278
|
+
return cls(**values)
|
279
|
+
|
280
|
+
case _:
|
281
|
+
raise TypeError(f"Expected '{cls.__name__}', received '{type(value).__name__}'")
|
282
|
+
|
168
283
|
def __init__(
|
169
284
|
self,
|
170
285
|
**kwargs: Any,
|
haiway/state/validation.py
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
from collections.abc import Callable, Mapping, Sequence, Set
|
1
|
+
from collections.abc import Callable, Mapping, MutableMapping, Sequence, Set
|
2
2
|
from datetime import date, datetime, time, timedelta, timezone
|
3
3
|
from enum import Enum
|
4
4
|
from pathlib import Path
|
5
5
|
from re import Pattern
|
6
|
-
from types import
|
7
|
-
from typing import Any, Literal, Protocol, Union
|
6
|
+
from types import EllipsisType, NoneType, UnionType
|
7
|
+
from typing import Any, Literal, Protocol, Self, Union, is_typeddict
|
8
8
|
from typing import Mapping as MappingType # noqa: UP035
|
9
9
|
from typing import Sequence as SequenceType # noqa: UP035
|
10
10
|
from typing import Sequence as SetType # noqa: UP035
|
@@ -14,36 +14,90 @@ from haiway.state.attributes import AttributeAnnotation
|
|
14
14
|
from haiway.types import MISSING, Missing
|
15
15
|
|
16
16
|
__all__ = [
|
17
|
-
"
|
17
|
+
"AttributeValidation",
|
18
|
+
"AttributeValidationError",
|
19
|
+
"AttributeValidator",
|
18
20
|
]
|
19
21
|
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
class AttributeValidation[Type](Protocol):
|
24
|
+
def __call__(
|
25
|
+
self,
|
26
|
+
value: Any,
|
27
|
+
/,
|
28
|
+
) -> Type: ...
|
27
29
|
|
28
|
-
elif hasattr(annotation.origin, "__IMMUTABLE__"):
|
29
|
-
return _prepare_validator_of_type(annotation)
|
30
30
|
|
31
|
-
|
32
|
-
|
31
|
+
class AttributeValidationError(Exception):
|
32
|
+
pass
|
33
33
|
|
34
|
-
elif issubclass(annotation.origin, Enum):
|
35
|
-
return _prepare_validator_of_type(annotation)
|
36
34
|
|
37
|
-
|
38
|
-
|
35
|
+
class AttributeValidator[Type]:
|
36
|
+
@classmethod
|
37
|
+
def of(
|
38
|
+
cls,
|
39
|
+
annotation: AttributeAnnotation,
|
40
|
+
/,
|
41
|
+
*,
|
42
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
43
|
+
) -> AttributeValidation[Any]:
|
44
|
+
if isinstance(annotation.origin, NotImplementedError | RuntimeError):
|
45
|
+
raise annotation.origin # raise an error if origin was not properly resolved
|
46
|
+
|
47
|
+
if recursive := recursion_guard.get(str(annotation)):
|
48
|
+
return recursive
|
49
|
+
|
50
|
+
validator: Self = cls(
|
51
|
+
annotation,
|
52
|
+
validation=MISSING,
|
53
|
+
)
|
54
|
+
recursion_guard[str(annotation)] = validator
|
55
|
+
|
56
|
+
if common := VALIDATORS.get(annotation.origin):
|
57
|
+
validator.validation = common(annotation, recursion_guard)
|
58
|
+
|
59
|
+
elif hasattr(annotation.origin, "__IMMUTABLE__"):
|
60
|
+
validator.validation = _prepare_validator_of_type(annotation, recursion_guard)
|
61
|
+
|
62
|
+
elif is_typeddict(annotation.origin):
|
63
|
+
validator.validation = _prepare_validator_of_typed_dict(annotation, recursion_guard)
|
64
|
+
|
65
|
+
elif issubclass(annotation.origin, Protocol):
|
66
|
+
validator.validation = _prepare_validator_of_type(annotation, recursion_guard)
|
67
|
+
|
68
|
+
elif issubclass(annotation.origin, Enum):
|
69
|
+
validator.validation = _prepare_validator_of_type(annotation, recursion_guard)
|
70
|
+
|
71
|
+
else:
|
72
|
+
raise TypeError(f"Unsupported type annotation: {annotation}")
|
73
|
+
|
74
|
+
return validator
|
75
|
+
|
76
|
+
def __init__(
|
77
|
+
self,
|
78
|
+
annotation: AttributeAnnotation,
|
79
|
+
validation: AttributeValidation[Type] | Missing,
|
80
|
+
) -> None:
|
81
|
+
self.annotation: AttributeAnnotation = annotation
|
82
|
+
self.validation: AttributeValidation[Type] | Missing = validation
|
83
|
+
|
84
|
+
def __call__(
|
85
|
+
self,
|
86
|
+
value: Any,
|
87
|
+
/,
|
88
|
+
) -> Any:
|
89
|
+
assert self.validation is not MISSING # nosec: B101
|
90
|
+
return self.validation(value) # pyright: ignore[reportCallIssue, reportUnknownVariableType]
|
39
91
|
|
40
92
|
|
41
93
|
def _prepare_validator_of_any(
|
42
94
|
annotation: AttributeAnnotation,
|
43
95
|
/,
|
44
|
-
|
96
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
97
|
+
) -> AttributeValidation[Any]:
|
45
98
|
def validator(
|
46
99
|
value: Any,
|
100
|
+
/,
|
47
101
|
) -> Any:
|
48
102
|
return value # any is always valid
|
49
103
|
|
@@ -53,9 +107,11 @@ def _prepare_validator_of_any(
|
|
53
107
|
def _prepare_validator_of_none(
|
54
108
|
annotation: AttributeAnnotation,
|
55
109
|
/,
|
56
|
-
|
110
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
111
|
+
) -> AttributeValidation[Any]:
|
57
112
|
def validator(
|
58
113
|
value: Any,
|
114
|
+
/,
|
59
115
|
) -> Any:
|
60
116
|
if value is None:
|
61
117
|
return value
|
@@ -69,9 +125,11 @@ def _prepare_validator_of_none(
|
|
69
125
|
def _prepare_validator_of_missing(
|
70
126
|
annotation: AttributeAnnotation,
|
71
127
|
/,
|
72
|
-
|
128
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
129
|
+
) -> AttributeValidation[Any]:
|
73
130
|
def validator(
|
74
131
|
value: Any,
|
132
|
+
/,
|
75
133
|
) -> Any:
|
76
134
|
if value is MISSING:
|
77
135
|
return value
|
@@ -85,12 +143,14 @@ def _prepare_validator_of_missing(
|
|
85
143
|
def _prepare_validator_of_literal(
|
86
144
|
annotation: AttributeAnnotation,
|
87
145
|
/,
|
88
|
-
|
89
|
-
|
146
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
147
|
+
) -> AttributeValidation[Any]:
|
148
|
+
elements: Sequence[Any] = annotation.arguments
|
90
149
|
formatted_type: str = str(annotation)
|
91
150
|
|
92
151
|
def validator(
|
93
152
|
value: Any,
|
153
|
+
/,
|
94
154
|
) -> Any:
|
95
155
|
if value in elements:
|
96
156
|
return value
|
@@ -104,12 +164,14 @@ def _prepare_validator_of_literal(
|
|
104
164
|
def _prepare_validator_of_type(
|
105
165
|
annotation: AttributeAnnotation,
|
106
166
|
/,
|
107
|
-
|
167
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
168
|
+
) -> AttributeValidation[Any]:
|
108
169
|
validated_type: type[Any] = annotation.origin
|
109
170
|
formatted_type: str = str(annotation)
|
110
171
|
|
111
|
-
def
|
172
|
+
def validator(
|
112
173
|
value: Any,
|
174
|
+
/,
|
113
175
|
) -> Any:
|
114
176
|
match value:
|
115
177
|
case value if isinstance(value, validated_type):
|
@@ -118,18 +180,23 @@ def _prepare_validator_of_type(
|
|
118
180
|
case _:
|
119
181
|
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
120
182
|
|
121
|
-
return
|
183
|
+
return validator
|
122
184
|
|
123
185
|
|
124
186
|
def _prepare_validator_of_set(
|
125
187
|
annotation: AttributeAnnotation,
|
126
188
|
/,
|
127
|
-
|
128
|
-
|
189
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
190
|
+
) -> AttributeValidation[Any]:
|
191
|
+
element_validator: AttributeValidation[Any] = AttributeValidator.of(
|
192
|
+
annotation.arguments[0],
|
193
|
+
recursion_guard=recursion_guard,
|
194
|
+
)
|
129
195
|
formatted_type: str = str(annotation)
|
130
196
|
|
131
197
|
def validator(
|
132
198
|
value: Any,
|
199
|
+
/,
|
133
200
|
) -> Any:
|
134
201
|
if isinstance(value, set):
|
135
202
|
return frozenset(element_validator(element) for element in value) # pyright: ignore[reportUnknownVariableType]
|
@@ -143,12 +210,17 @@ def _prepare_validator_of_set(
|
|
143
210
|
def _prepare_validator_of_sequence(
|
144
211
|
annotation: AttributeAnnotation,
|
145
212
|
/,
|
146
|
-
|
147
|
-
|
213
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
214
|
+
) -> AttributeValidation[Any]:
|
215
|
+
element_validator: AttributeValidation[Any] = AttributeValidator.of(
|
216
|
+
annotation.arguments[0],
|
217
|
+
recursion_guard=recursion_guard,
|
218
|
+
)
|
148
219
|
formatted_type: str = str(annotation)
|
149
220
|
|
150
221
|
def validator(
|
151
222
|
value: Any,
|
223
|
+
/,
|
152
224
|
) -> Any:
|
153
225
|
match value:
|
154
226
|
case [*elements]:
|
@@ -163,19 +235,29 @@ def _prepare_validator_of_sequence(
|
|
163
235
|
def _prepare_validator_of_mapping(
|
164
236
|
annotation: AttributeAnnotation,
|
165
237
|
/,
|
166
|
-
|
167
|
-
|
168
|
-
|
238
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
239
|
+
) -> AttributeValidation[Any]:
|
240
|
+
key_validator: AttributeValidation[Any] = AttributeValidator.of(
|
241
|
+
annotation.arguments[0],
|
242
|
+
recursion_guard=recursion_guard,
|
243
|
+
)
|
244
|
+
value_validator: AttributeValidation[Any] = AttributeValidator.of(
|
245
|
+
annotation.arguments[1],
|
246
|
+
recursion_guard=recursion_guard,
|
247
|
+
)
|
169
248
|
formatted_type: str = str(annotation)
|
170
249
|
|
171
250
|
def validator(
|
172
251
|
value: Any,
|
252
|
+
/,
|
173
253
|
) -> Any:
|
174
254
|
match value:
|
175
255
|
case {**elements}:
|
176
|
-
|
177
|
-
|
178
|
-
|
256
|
+
# TODO: make sure dict is not mutable with MappingProxyType?
|
257
|
+
return {
|
258
|
+
key_validator(key): value_validator(element)
|
259
|
+
for key, element in elements.items()
|
260
|
+
}
|
179
261
|
|
180
262
|
case _:
|
181
263
|
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
@@ -186,13 +268,21 @@ def _prepare_validator_of_mapping(
|
|
186
268
|
def _prepare_validator_of_tuple(
|
187
269
|
annotation: AttributeAnnotation,
|
188
270
|
/,
|
189
|
-
|
190
|
-
|
191
|
-
|
271
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
272
|
+
) -> AttributeValidation[Any]:
|
273
|
+
if (
|
274
|
+
annotation.arguments[-1].origin == Ellipsis
|
275
|
+
or annotation.arguments[-1].origin == EllipsisType
|
276
|
+
):
|
277
|
+
element_validator: AttributeValidation[Any] = AttributeValidator.of(
|
278
|
+
annotation.arguments[0],
|
279
|
+
recursion_guard=recursion_guard,
|
280
|
+
)
|
192
281
|
formatted_type: str = str(annotation)
|
193
282
|
|
194
283
|
def validator(
|
195
284
|
value: Any,
|
285
|
+
/,
|
196
286
|
) -> Any:
|
197
287
|
match value:
|
198
288
|
case [*elements]:
|
@@ -206,14 +296,16 @@ def _prepare_validator_of_tuple(
|
|
206
296
|
return validator
|
207
297
|
|
208
298
|
else:
|
209
|
-
element_validators: list[
|
210
|
-
|
299
|
+
element_validators: list[AttributeValidation[Any]] = [
|
300
|
+
AttributeValidator.of(alternative, recursion_guard=recursion_guard)
|
301
|
+
for alternative in annotation.arguments
|
211
302
|
]
|
212
303
|
elements_count: int = len(element_validators)
|
213
304
|
formatted_type: str = str(annotation)
|
214
305
|
|
215
306
|
def validator(
|
216
307
|
value: Any,
|
308
|
+
/,
|
217
309
|
) -> Any:
|
218
310
|
match value:
|
219
311
|
case [*elements]:
|
@@ -223,7 +315,7 @@ def _prepare_validator_of_tuple(
|
|
223
315
|
)
|
224
316
|
|
225
317
|
return tuple(
|
226
|
-
element_validators[idx](
|
318
|
+
element_validators[idx](element) for idx, element in enumerate(elements)
|
227
319
|
)
|
228
320
|
|
229
321
|
case _:
|
@@ -237,14 +329,17 @@ def _prepare_validator_of_tuple(
|
|
237
329
|
def _prepare_validator_of_union(
|
238
330
|
annotation: AttributeAnnotation,
|
239
331
|
/,
|
240
|
-
|
241
|
-
|
242
|
-
|
332
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
333
|
+
) -> AttributeValidation[Any]:
|
334
|
+
validators: list[AttributeValidation[Any]] = [
|
335
|
+
AttributeValidator.of(alternative, recursion_guard=recursion_guard)
|
336
|
+
for alternative in annotation.arguments
|
243
337
|
]
|
244
338
|
formatted_type: str = str(annotation)
|
245
339
|
|
246
340
|
def validator(
|
247
341
|
value: Any,
|
342
|
+
/,
|
248
343
|
) -> Any:
|
249
344
|
errors: list[Exception] = []
|
250
345
|
for validator in validators:
|
@@ -265,13 +360,16 @@ def _prepare_validator_of_union(
|
|
265
360
|
def _prepare_validator_of_callable(
|
266
361
|
annotation: AttributeAnnotation,
|
267
362
|
/,
|
268
|
-
|
363
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
364
|
+
) -> AttributeValidation[Any]:
|
269
365
|
formatted_type: str = str(annotation)
|
270
366
|
|
271
367
|
def validator(
|
272
368
|
value: Any,
|
369
|
+
/,
|
273
370
|
) -> Any:
|
274
371
|
if callable(value):
|
372
|
+
# TODO: we could verify callable signature here
|
275
373
|
return value
|
276
374
|
|
277
375
|
else:
|
@@ -280,7 +378,59 @@ def _prepare_validator_of_callable(
|
|
280
378
|
return validator
|
281
379
|
|
282
380
|
|
283
|
-
|
381
|
+
def _prepare_validator_of_typed_dict(
|
382
|
+
annotation: AttributeAnnotation,
|
383
|
+
/,
|
384
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
385
|
+
) -> AttributeValidation[Any]:
|
386
|
+
def key_validator(
|
387
|
+
value: Any,
|
388
|
+
/,
|
389
|
+
) -> Any:
|
390
|
+
match value:
|
391
|
+
case value if isinstance(value, str):
|
392
|
+
return value
|
393
|
+
|
394
|
+
case _:
|
395
|
+
raise TypeError(f"'{value}' is not matching expected type of 'str'")
|
396
|
+
|
397
|
+
values_validators: dict[str, AttributeValidation[Any]] = {
|
398
|
+
key: AttributeValidator.of(element, recursion_guard=recursion_guard)
|
399
|
+
for key, element in annotation.extra.items()
|
400
|
+
}
|
401
|
+
formatted_type: str = str(annotation)
|
402
|
+
|
403
|
+
def validator(
|
404
|
+
value: Any,
|
405
|
+
/,
|
406
|
+
) -> Any:
|
407
|
+
match value:
|
408
|
+
case {**elements}:
|
409
|
+
validated: MutableMapping[str, Any] = {}
|
410
|
+
for key, validator in values_validators.items():
|
411
|
+
element: Any = elements.get(key, MISSING)
|
412
|
+
if element is not MISSING:
|
413
|
+
validated[key_validator(key)] = validator(element)
|
414
|
+
|
415
|
+
# TODO: make sure dict is not mutable with MappingProxyType?
|
416
|
+
return validated
|
417
|
+
|
418
|
+
case _:
|
419
|
+
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
420
|
+
|
421
|
+
return validator
|
422
|
+
|
423
|
+
|
424
|
+
VALIDATORS: Mapping[
|
425
|
+
Any,
|
426
|
+
Callable[
|
427
|
+
[
|
428
|
+
AttributeAnnotation,
|
429
|
+
MutableMapping[str, AttributeValidation[Any]],
|
430
|
+
],
|
431
|
+
AttributeValidation[Any],
|
432
|
+
],
|
433
|
+
] = {
|
284
434
|
Any: _prepare_validator_of_any,
|
285
435
|
NoneType: _prepare_validator_of_none,
|
286
436
|
Missing: _prepare_validator_of_missing,
|
@@ -15,11 +15,11 @@ haiway/helpers/throttling.py,sha256=zo0OwFq64si5KUwhd58cFHLmGAmYwRbFRJMbv9suhPs,
|
|
15
15
|
haiway/helpers/timeouted.py,sha256=1xU09hQnFdj6p48BwZl5xUvtIr3zC0ZUXehkdrduCjs,3074
|
16
16
|
haiway/helpers/tracing.py,sha256=eQpkIoGSB51jRF8RcLaihvHX3VzJIRdyRxTx3I14Pzg,3346
|
17
17
|
haiway/state/__init__.py,sha256=emTuwGFn7HyjyTJ_ass69J5jQIA7_WHO4teZz_dR05Y,355
|
18
|
-
haiway/state/attributes.py,sha256=
|
18
|
+
haiway/state/attributes.py,sha256=iQ7TJHnT3hlcYwKcxchXE56zU8WbOTJZhsVn_HocXBc,22903
|
19
19
|
haiway/state/path.py,sha256=4vh-fYQv8_xRWjS0ErMQslKDWRI6-KVECAr8JhYk0UY,17503
|
20
20
|
haiway/state/requirement.py,sha256=3iQqzp5Q7w6y5uClamJGH7S5Hib9pciuTAV27PP5lS8,6161
|
21
|
-
haiway/state/structure.py,sha256=
|
22
|
-
haiway/state/validation.py,sha256=
|
21
|
+
haiway/state/structure.py,sha256=dqLXUJPnaQqdRxbNwtGgUqGNCA8EDPj16M3Ob_sEz5U,11837
|
22
|
+
haiway/state/validation.py,sha256=n5cHcJTbv3Zf-qs05yzuLJIMBReV_4yYVwcH6IL58N0,13836
|
23
23
|
haiway/types/__init__.py,sha256=00Ulp2BxcIWm9vWXKQPodpFEwE8hpqj6OYgrNxelp5s,252
|
24
24
|
haiway/types/frozen.py,sha256=CZhFCXnWAKEhuWSfILxA8smfdpMd5Ku694ycfLh98R8,76
|
25
25
|
haiway/types/missing.py,sha256=JiXo5xdi7H-PbIJr0fuK5wpOuQZhjrDYUkMlfIFcsaE,1705
|
@@ -31,8 +31,8 @@ haiway/utils/logs.py,sha256=oDsc1ZdqKDjlTlctLbDcp9iX98Acr-1tdw-Pyg3DElo,1577
|
|
31
31
|
haiway/utils/mimic.py,sha256=BkVjTVP2TxxC8GChPGyDV6UXVwJmiRiSWeOYZNZFHxs,1828
|
32
32
|
haiway/utils/noop.py,sha256=qgbZlOKWY6_23Zs43OLukK2HagIQKRyR04zrFVm5rWI,344
|
33
33
|
haiway/utils/queue.py,sha256=oQ3GXCJ-PGNtMEr6EPdgqAvYZoj8lAa7Z2drBKBEoBM,2345
|
34
|
-
haiway-0.
|
35
|
-
haiway-0.
|
36
|
-
haiway-0.
|
37
|
-
haiway-0.
|
38
|
-
haiway-0.
|
34
|
+
haiway-0.7.1.dist-info/LICENSE,sha256=GehQEW_I1pkmxkkj3NEa7rCTQKYBn7vTPabpDYJlRuo,1063
|
35
|
+
haiway-0.7.1.dist-info/METADATA,sha256=XacjUXNqPQaTSoCxEcqcOcGgjAGr6uxBguhLwrEaIMM,3898
|
36
|
+
haiway-0.7.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
37
|
+
haiway-0.7.1.dist-info/top_level.txt,sha256=_LdXVLzUzgkvAGQnQJj5kQfoFhpPW6EF4Kj9NapniLg,7
|
38
|
+
haiway-0.7.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|