haiway 0.24.3__py3-none-any.whl → 0.25.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/__init__.py +4 -1
- haiway/context/__init__.py +4 -0
- haiway/context/access.py +312 -70
- haiway/context/disposables.py +5 -119
- haiway/context/identifier.py +19 -44
- haiway/context/observability.py +22 -142
- haiway/context/presets.py +337 -0
- haiway/context/state.py +38 -84
- haiway/context/tasks.py +7 -10
- haiway/helpers/observability.py +4 -6
- haiway/opentelemetry/observability.py +5 -5
- haiway/state/__init__.py +2 -0
- haiway/state/immutable.py +127 -0
- haiway/state/structure.py +63 -117
- haiway/state/validation.py +95 -60
- {haiway-0.24.3.dist-info → haiway-0.25.1.dist-info}/METADATA +1 -1
- {haiway-0.24.3.dist-info → haiway-0.25.1.dist-info}/RECORD +19 -17
- {haiway-0.24.3.dist-info → haiway-0.25.1.dist-info}/WHEEL +0 -0
- {haiway-0.24.3.dist-info → haiway-0.25.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,127 @@
|
|
1
|
+
import inspect
|
2
|
+
from types import EllipsisType
|
3
|
+
from typing import Any, ClassVar, Self, dataclass_transform, final, get_origin, get_type_hints
|
4
|
+
|
5
|
+
from haiway.types.default import DefaultValue
|
6
|
+
|
7
|
+
__all__ = ("Immutable",)
|
8
|
+
|
9
|
+
|
10
|
+
@dataclass_transform(
|
11
|
+
kw_only_default=True,
|
12
|
+
frozen_default=True,
|
13
|
+
field_specifiers=(),
|
14
|
+
)
|
15
|
+
class ImmutableMeta(type):
|
16
|
+
def __new__(
|
17
|
+
mcs,
|
18
|
+
name: str,
|
19
|
+
bases: tuple[type, ...],
|
20
|
+
namespace: dict[str, Any],
|
21
|
+
**kwargs: Any,
|
22
|
+
) -> type:
|
23
|
+
state_type = type.__new__(
|
24
|
+
mcs,
|
25
|
+
name,
|
26
|
+
bases,
|
27
|
+
namespace,
|
28
|
+
**kwargs,
|
29
|
+
)
|
30
|
+
|
31
|
+
state_type.__ATTRIBUTES__ = _collect_attributes(state_type) # pyright: ignore[reportAttributeAccessIssue]
|
32
|
+
state_type.__slots__ = tuple(state_type.__ATTRIBUTES__.keys()) # pyright: ignore[reportAttributeAccessIssue]
|
33
|
+
state_type.__match_args__ = state_type.__slots__ # pyright: ignore[reportAttributeAccessIssue]
|
34
|
+
|
35
|
+
# Only mark subclasses as final (not the base Immutable class itself)
|
36
|
+
if name != "Immutable":
|
37
|
+
state_type = final(state_type)
|
38
|
+
|
39
|
+
return state_type
|
40
|
+
|
41
|
+
|
42
|
+
def _collect_attributes(
|
43
|
+
cls: type[Any],
|
44
|
+
) -> dict[str, DefaultValue[Any] | None]:
|
45
|
+
attributes: dict[str, DefaultValue[Any] | None] = {}
|
46
|
+
for key, annotation in get_type_hints(cls, localns={cls.__name__: cls}).items():
|
47
|
+
# do not include ClassVars
|
48
|
+
if (get_origin(annotation) or annotation) is ClassVar:
|
49
|
+
continue
|
50
|
+
|
51
|
+
field_value: Any = getattr(cls, key, inspect.Parameter.empty)
|
52
|
+
|
53
|
+
# Create a Field instance with the default value
|
54
|
+
if field_value is inspect.Parameter.empty:
|
55
|
+
attributes[key] = None
|
56
|
+
|
57
|
+
elif isinstance(field_value, DefaultValue):
|
58
|
+
attributes[key] = field_value
|
59
|
+
|
60
|
+
else:
|
61
|
+
attributes[key] = DefaultValue(field_value)
|
62
|
+
|
63
|
+
return attributes
|
64
|
+
|
65
|
+
|
66
|
+
class Immutable(metaclass=ImmutableMeta):
|
67
|
+
__IMMUTABLE__: ClassVar[EllipsisType] = ...
|
68
|
+
__ATTRIBUTES__: ClassVar[dict[str, DefaultValue | None]]
|
69
|
+
|
70
|
+
def __init__(
|
71
|
+
self,
|
72
|
+
**kwargs: Any,
|
73
|
+
) -> None:
|
74
|
+
for name, default in self.__ATTRIBUTES__.items():
|
75
|
+
if name in kwargs:
|
76
|
+
object.__setattr__(
|
77
|
+
self,
|
78
|
+
name,
|
79
|
+
kwargs[name],
|
80
|
+
)
|
81
|
+
|
82
|
+
elif default is not None:
|
83
|
+
object.__setattr__(
|
84
|
+
self,
|
85
|
+
name,
|
86
|
+
default(),
|
87
|
+
)
|
88
|
+
|
89
|
+
else:
|
90
|
+
raise AttributeError(
|
91
|
+
f"Missing required attribute: {name}@{self.__class__.__qualname__}"
|
92
|
+
)
|
93
|
+
|
94
|
+
def __setattr__(
|
95
|
+
self,
|
96
|
+
name: str,
|
97
|
+
value: Any,
|
98
|
+
) -> Any:
|
99
|
+
raise AttributeError(
|
100
|
+
f"Can't modify immutable {self.__class__.__qualname__},"
|
101
|
+
f" attribute - '{name}' cannot be modified"
|
102
|
+
)
|
103
|
+
|
104
|
+
def __delattr__(
|
105
|
+
self,
|
106
|
+
name: str,
|
107
|
+
) -> None:
|
108
|
+
raise AttributeError(
|
109
|
+
f"Can't modify immutable {self.__class__.__qualname__},"
|
110
|
+
f" attribute - '{name}' cannot be deleted"
|
111
|
+
)
|
112
|
+
|
113
|
+
def __str__(self) -> str:
|
114
|
+
attributes: str = ", ".join([f"{key}: {value}" for key, value in vars(self).items()])
|
115
|
+
return f"{self.__class__.__name__}({attributes})"
|
116
|
+
|
117
|
+
def __repr__(self) -> str:
|
118
|
+
return str(self)
|
119
|
+
|
120
|
+
def __copy__(self) -> Self:
|
121
|
+
return self # Immutable, no need to provide an actual copy
|
122
|
+
|
123
|
+
def __deepcopy__(
|
124
|
+
self,
|
125
|
+
memo: dict[int, Any] | None,
|
126
|
+
) -> Self:
|
127
|
+
return self # Immutable, no need to provide an actual copy
|
haiway/state/structure.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
import typing
|
2
1
|
from collections.abc import Mapping
|
3
2
|
from types import EllipsisType, GenericAlias
|
4
3
|
from typing import (
|
@@ -131,7 +130,7 @@ class StateAttribute[Value]:
|
|
131
130
|
@dataclass_transform(
|
132
131
|
kw_only_default=True,
|
133
132
|
frozen_default=True,
|
134
|
-
field_specifiers=(
|
133
|
+
field_specifiers=(),
|
135
134
|
)
|
136
135
|
class StateMeta(type):
|
137
136
|
"""
|
@@ -150,7 +149,7 @@ class StateMeta(type):
|
|
150
149
|
"""
|
151
150
|
|
152
151
|
def __new__(
|
153
|
-
|
152
|
+
mcs,
|
154
153
|
/,
|
155
154
|
name: str,
|
156
155
|
bases: tuple[type, ...],
|
@@ -158,29 +157,8 @@ class StateMeta(type):
|
|
158
157
|
type_parameters: dict[str, Any] | None = None,
|
159
158
|
**kwargs: Any,
|
160
159
|
) -> Any:
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
Parameters
|
165
|
-
----------
|
166
|
-
name : str
|
167
|
-
The name of the new class
|
168
|
-
bases : tuple[type, ...]
|
169
|
-
The base classes
|
170
|
-
namespace : dict[str, Any]
|
171
|
-
The class namespace (attributes and methods)
|
172
|
-
type_parameters : dict[str, Any] | None
|
173
|
-
Type parameters for generic specialization
|
174
|
-
**kwargs : Any
|
175
|
-
Additional arguments for class creation
|
176
|
-
|
177
|
-
Returns
|
178
|
-
-------
|
179
|
-
Any
|
180
|
-
The new class object
|
181
|
-
"""
|
182
|
-
state_type = type.__new__(
|
183
|
-
cls,
|
160
|
+
cls = type.__new__(
|
161
|
+
mcs,
|
184
162
|
name,
|
185
163
|
bases,
|
186
164
|
namespace,
|
@@ -190,29 +168,27 @@ class StateMeta(type):
|
|
190
168
|
attributes: dict[str, StateAttribute[Any]] = {}
|
191
169
|
|
192
170
|
for key, annotation in attribute_annotations(
|
193
|
-
|
171
|
+
cls,
|
194
172
|
type_parameters=type_parameters or {},
|
195
173
|
).items():
|
196
|
-
default: Any = getattr(
|
174
|
+
default: Any = getattr(cls, key, MISSING)
|
197
175
|
attributes[key] = StateAttribute(
|
198
176
|
name=key,
|
199
177
|
annotation=annotation.update_required(default is MISSING),
|
200
178
|
default=_resolve_default(default),
|
201
179
|
validator=AttributeValidator.of(
|
202
180
|
annotation,
|
203
|
-
recursion_guard={
|
204
|
-
str(AttributeAnnotation(origin=state_type)): state_type.validator
|
205
|
-
},
|
181
|
+
recursion_guard={str(AttributeAnnotation(origin=cls)): cls.validator},
|
206
182
|
),
|
207
183
|
)
|
208
184
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
185
|
+
cls.__TYPE_PARAMETERS__ = type_parameters # pyright: ignore[reportAttributeAccessIssue]
|
186
|
+
cls.__ATTRIBUTES__ = attributes # pyright: ignore[reportAttributeAccessIssue]
|
187
|
+
cls.__slots__ = frozenset(attributes.keys()) # pyright: ignore[reportAttributeAccessIssue]
|
188
|
+
cls.__match_args__ = cls.__slots__ # pyright: ignore[reportAttributeAccessIssue]
|
189
|
+
cls._ = AttributePath(cls, attribute=cls) # pyright: ignore[reportCallIssue, reportUnknownMemberType, reportAttributeAccessIssue]
|
214
190
|
|
215
|
-
return
|
191
|
+
return cls
|
216
192
|
|
217
193
|
def validator(
|
218
194
|
cls,
|
@@ -241,24 +217,8 @@ class StateMeta(type):
|
|
241
217
|
self,
|
242
218
|
instance: Any,
|
243
219
|
) -> bool:
|
244
|
-
"""
|
245
|
-
Check if an instance is an instance of this class.
|
246
|
-
|
247
|
-
Implements isinstance() behavior for State classes, with special handling
|
248
|
-
for generic type parameters and validation.
|
249
|
-
|
250
|
-
Parameters
|
251
|
-
----------
|
252
|
-
instance : Any
|
253
|
-
The instance to check
|
254
|
-
|
255
|
-
Returns
|
256
|
-
-------
|
257
|
-
bool
|
258
|
-
True if the instance is an instance of this class, False otherwise
|
259
|
-
"""
|
260
220
|
# check for type match
|
261
|
-
if self.__subclasscheck__(type(instance)):
|
221
|
+
if self.__subclasscheck__(type(instance)):
|
262
222
|
return True
|
263
223
|
|
264
224
|
# otherwise check if we are dealing with unparametrized base
|
@@ -276,85 +236,69 @@ class StateMeta(type):
|
|
276
236
|
else:
|
277
237
|
return True
|
278
238
|
|
279
|
-
def __subclasscheck__(
|
239
|
+
def __subclasscheck__(
|
280
240
|
self,
|
281
241
|
subclass: type[Any],
|
282
242
|
) -> bool:
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
Implements issubclass() behavior for State classes, with special handling
|
287
|
-
for generic type parameters.
|
243
|
+
if self is subclass:
|
244
|
+
return True
|
288
245
|
|
289
|
-
|
290
|
-
|
291
|
-
subclass : type[Any]
|
292
|
-
The class to check
|
246
|
+
self_origin: type[Any] = getattr(self, "__origin__", self)
|
247
|
+
subclass_origin: type[Any] = getattr(subclass, "__origin__", subclass)
|
293
248
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
True if the class is a subclass of this class, False otherwise
|
249
|
+
# Handle case where we're checking a parameterized type against unparameterized
|
250
|
+
if self_origin is self:
|
251
|
+
return type.__subclasscheck__(self, subclass)
|
298
252
|
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
If there is an issue with type parametrization
|
303
|
-
"""
|
304
|
-
# check if we are the same class for early exit
|
305
|
-
if self == subclass:
|
306
|
-
return True
|
253
|
+
# Both must be based on the same generic class
|
254
|
+
if not issubclass(subclass_origin, self_origin):
|
255
|
+
return False
|
307
256
|
|
308
|
-
|
309
|
-
checked_parameters: Mapping[str, Any] | None = getattr(
|
310
|
-
self,
|
311
|
-
"__TYPE_PARAMETERS__",
|
312
|
-
None,
|
313
|
-
)
|
314
|
-
if checked_parameters is None:
|
315
|
-
# if we are not parametrized allow any subclass
|
316
|
-
return self in subclass.__bases__
|
317
|
-
|
318
|
-
# verify if we have common base next - our generic subtypes have the same base
|
319
|
-
if self.__bases__ == subclass.__bases__:
|
320
|
-
# if we have the same bases we have different generic subtypes
|
321
|
-
# we can verify all of the attributes to check if we have common base
|
322
|
-
available_parameters: Mapping[str, Any] | None = getattr(
|
323
|
-
subclass,
|
324
|
-
"__TYPE_PARAMETERS__",
|
325
|
-
None,
|
326
|
-
)
|
257
|
+
return self._check_type_parameters(subclass)
|
327
258
|
|
328
|
-
|
329
|
-
|
330
|
-
|
259
|
+
def _check_type_parameters(
|
260
|
+
self,
|
261
|
+
subclass: type[Any],
|
262
|
+
) -> bool:
|
263
|
+
self_params: Mapping[str, Any] | None = getattr(self, "__TYPE_PARAMETERS__", None)
|
264
|
+
subclass_params: Mapping[str, Any] | None = getattr(subclass, "__TYPE_PARAMETERS__", None)
|
331
265
|
|
332
|
-
|
333
|
-
|
334
|
-
case None: # if any parameter is missing we should not be there already
|
335
|
-
return False
|
266
|
+
if self_params is None:
|
267
|
+
return True
|
336
268
|
|
337
|
-
|
338
|
-
|
269
|
+
# If subclass doesn't have type parameters, look in the MRO for a parametrized base
|
270
|
+
if subclass_params is None:
|
271
|
+
subclass_params = self._find_parametrized_base(subclass)
|
272
|
+
if subclass_params is None:
|
273
|
+
return False
|
339
274
|
|
340
|
-
|
341
|
-
|
342
|
-
|
275
|
+
# Check if the type parameters are compatible (covariant)
|
276
|
+
for key, self_param in self_params.items():
|
277
|
+
subclass_param: type[Any] = subclass_params.get(key, Any)
|
278
|
+
if self_param is Any:
|
279
|
+
continue
|
343
280
|
|
344
|
-
|
345
|
-
|
281
|
+
# For covariance: GenericState[Child] should be subclass of GenericState[Parent]
|
282
|
+
# This means subclass_param should be a subclass of self_param
|
283
|
+
if not issubclass(subclass_param, self_param):
|
284
|
+
return False
|
346
285
|
|
347
|
-
|
348
|
-
return False # types are not matching
|
286
|
+
return True
|
349
287
|
|
350
|
-
|
288
|
+
def _find_parametrized_base(
|
289
|
+
self,
|
290
|
+
subclass: type[Any],
|
291
|
+
) -> Mapping[str, Any] | None:
|
292
|
+
self_origin: type[Any] = getattr(self, "__origin__", self)
|
293
|
+
for base in getattr(subclass, "__mro__", ()):
|
294
|
+
if getattr(base, "__origin__", None) is not self_origin:
|
295
|
+
continue
|
351
296
|
|
352
|
-
|
353
|
-
|
354
|
-
|
297
|
+
subclass_params: Mapping[str, Any] | None = getattr(base, "__TYPE_PARAMETERS__", None)
|
298
|
+
if subclass_params is not None:
|
299
|
+
return subclass_params
|
355
300
|
|
356
|
-
|
357
|
-
return False # we have different base / comparing to not parametrized
|
301
|
+
return None
|
358
302
|
|
359
303
|
|
360
304
|
def _resolve_default[Value](
|
@@ -514,6 +458,8 @@ class State(metaclass=StateMeta):
|
|
514
458
|
namespace={"__module__": cls.__module__},
|
515
459
|
type_parameters=type_parameters,
|
516
460
|
)
|
461
|
+
# Set origin for subclass checks
|
462
|
+
parametrized_type.__origin__ = cls # pyright: ignore[reportAttributeAccessIssue]
|
517
463
|
_types_cache[(cls, type_arguments)] = parametrized_type
|
518
464
|
return parametrized_type
|
519
465
|
|
haiway/state/validation.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
from collections.abc import Callable, Mapping, MutableMapping, Sequence, Set
|
1
|
+
from collections.abc import Callable, Collection, 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
|
@@ -132,6 +132,14 @@ class AttributeValidator[Type]:
|
|
132
132
|
_prepare_validator_of_type(annotation, recursion_guard),
|
133
133
|
)
|
134
134
|
|
135
|
+
elif isinstance(annotation.origin, type):
|
136
|
+
# Handle arbitrary types as valid type annotations
|
137
|
+
object.__setattr__(
|
138
|
+
validator,
|
139
|
+
"validation",
|
140
|
+
_prepare_validator_of_type(annotation, recursion_guard),
|
141
|
+
)
|
142
|
+
|
135
143
|
else:
|
136
144
|
raise TypeError(f"Unsupported type annotation: {annotation}")
|
137
145
|
|
@@ -397,12 +405,11 @@ def _prepare_validator_of_type(
|
|
397
405
|
value: Any,
|
398
406
|
/,
|
399
407
|
) -> Any:
|
400
|
-
|
401
|
-
|
402
|
-
return value
|
408
|
+
if isinstance(value, validated_type):
|
409
|
+
return value
|
403
410
|
|
404
|
-
|
405
|
-
|
411
|
+
else:
|
412
|
+
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
406
413
|
|
407
414
|
return validator
|
408
415
|
|
@@ -440,8 +447,8 @@ def _prepare_validator_of_set(
|
|
440
447
|
value: Any,
|
441
448
|
/,
|
442
449
|
) -> Any:
|
443
|
-
if isinstance(value,
|
444
|
-
return frozenset(element_validator(element) for element in value)
|
450
|
+
if isinstance(value, Set):
|
451
|
+
return frozenset(element_validator(element) for element in value)
|
445
452
|
|
446
453
|
else:
|
447
454
|
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
@@ -482,12 +489,53 @@ def _prepare_validator_of_sequence(
|
|
482
489
|
value: Any,
|
483
490
|
/,
|
484
491
|
) -> Any:
|
485
|
-
|
486
|
-
|
487
|
-
return tuple(element_validator(element) for element in elements)
|
492
|
+
if isinstance(value, Sequence) and not isinstance(value, str | bytes):
|
493
|
+
return tuple(element_validator(element) for element in value)
|
488
494
|
|
489
|
-
|
490
|
-
|
495
|
+
else:
|
496
|
+
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
497
|
+
|
498
|
+
return validator
|
499
|
+
|
500
|
+
|
501
|
+
def _prepare_validator_of_collection(
|
502
|
+
annotation: AttributeAnnotation,
|
503
|
+
/,
|
504
|
+
recursion_guard: MutableMapping[str, AttributeValidation[Any]],
|
505
|
+
) -> AttributeValidation[Any]:
|
506
|
+
"""
|
507
|
+
Create a validator for collection types.
|
508
|
+
|
509
|
+
This validator checks if the value is a collection and validates each element
|
510
|
+
according to the collection's element type. Collections are converted to tuples.
|
511
|
+
|
512
|
+
Parameters
|
513
|
+
----------
|
514
|
+
annotation : AttributeAnnotation
|
515
|
+
The collection type annotation
|
516
|
+
recursion_guard : MutableMapping[str, AttributeValidation[Any]]
|
517
|
+
Mapping to prevent infinite recursion for recursive types
|
518
|
+
|
519
|
+
Returns
|
520
|
+
-------
|
521
|
+
AttributeValidation[Any]
|
522
|
+
A validator that validates collections and their elements
|
523
|
+
"""
|
524
|
+
element_validator: AttributeValidation[Any] = AttributeValidator.of(
|
525
|
+
annotation.arguments[0],
|
526
|
+
recursion_guard=recursion_guard,
|
527
|
+
)
|
528
|
+
formatted_type: str = str(annotation)
|
529
|
+
|
530
|
+
def validator(
|
531
|
+
value: Any,
|
532
|
+
/,
|
533
|
+
) -> Any:
|
534
|
+
if isinstance(value, Collection) and not isinstance(value, str | bytes):
|
535
|
+
return tuple(element_validator(element) for element in value)
|
536
|
+
|
537
|
+
else:
|
538
|
+
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
491
539
|
|
492
540
|
return validator
|
493
541
|
|
@@ -529,16 +577,12 @@ def _prepare_validator_of_mapping(
|
|
529
577
|
value: Any,
|
530
578
|
/,
|
531
579
|
) -> Any:
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
}
|
539
|
-
|
540
|
-
case _:
|
541
|
-
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
580
|
+
if isinstance(value, Mapping):
|
581
|
+
# TODO: make sure dict is not mutable with MappingProxyType?
|
582
|
+
return {key_validator(key): value_validator(element) for key, element in value.items()}
|
583
|
+
|
584
|
+
else:
|
585
|
+
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
542
586
|
|
543
587
|
return validator
|
544
588
|
|
@@ -580,14 +624,11 @@ def _prepare_validator_of_tuple(
|
|
580
624
|
value: Any,
|
581
625
|
/,
|
582
626
|
) -> Any:
|
583
|
-
|
584
|
-
|
585
|
-
return tuple(element_validator(element) for element in elements)
|
627
|
+
if isinstance(value, Collection) and not isinstance(value, str | bytes):
|
628
|
+
return tuple(element_validator(element) for element in value)
|
586
629
|
|
587
|
-
|
588
|
-
|
589
|
-
f"'{value}' is not matching expected type of '{formatted_type}'"
|
590
|
-
)
|
630
|
+
else:
|
631
|
+
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
591
632
|
|
592
633
|
return validator
|
593
634
|
|
@@ -603,22 +644,17 @@ def _prepare_validator_of_tuple(
|
|
603
644
|
value: Any,
|
604
645
|
/,
|
605
646
|
) -> Any:
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
raise ValueError(
|
610
|
-
f"'{value}' is not matching expected type of '{formatted_type}'"
|
611
|
-
)
|
612
|
-
|
613
|
-
return tuple(
|
614
|
-
element_validators[idx](element) for idx, element in enumerate(elements)
|
615
|
-
)
|
616
|
-
|
617
|
-
case _:
|
618
|
-
raise TypeError(
|
647
|
+
if isinstance(value, Sequence):
|
648
|
+
if len(value) != elements_count:
|
649
|
+
raise ValueError(
|
619
650
|
f"'{value}' is not matching expected type of '{formatted_type}'"
|
620
651
|
)
|
621
652
|
|
653
|
+
return tuple(element_validators[idx](element) for idx, element in enumerate(value))
|
654
|
+
|
655
|
+
else:
|
656
|
+
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
657
|
+
|
622
658
|
return validator
|
623
659
|
|
624
660
|
|
@@ -738,12 +774,11 @@ def _prepare_validator_of_typed_dict(
|
|
738
774
|
value: Any,
|
739
775
|
/,
|
740
776
|
) -> Any:
|
741
|
-
|
742
|
-
|
743
|
-
return value
|
777
|
+
if isinstance(value, str):
|
778
|
+
return value
|
744
779
|
|
745
|
-
|
746
|
-
|
780
|
+
else:
|
781
|
+
raise TypeError(f"'{value}' is not matching expected type of 'str'")
|
747
782
|
|
748
783
|
formatted_type: str = str(annotation)
|
749
784
|
values_validators: dict[str, AttributeValidation[Any]] = {
|
@@ -756,21 +791,20 @@ def _prepare_validator_of_typed_dict(
|
|
756
791
|
value: Any,
|
757
792
|
/,
|
758
793
|
) -> Any:
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
continue # skip missing and not required
|
794
|
+
if isinstance(value, Mapping):
|
795
|
+
validated: MutableMapping[str, Any] = {}
|
796
|
+
for key, validator in values_validators.items():
|
797
|
+
element: Any = value.get(key, MISSING)
|
798
|
+
if element is MISSING and key not in required_values:
|
799
|
+
continue # skip missing and not required
|
766
800
|
|
767
|
-
|
801
|
+
validated[key_validator(key)] = validator(element)
|
768
802
|
|
769
|
-
|
770
|
-
|
803
|
+
# TODO: make sure dict is not mutable with MappingProxyType?
|
804
|
+
return validated
|
771
805
|
|
772
|
-
|
773
|
-
|
806
|
+
else:
|
807
|
+
raise TypeError(f"'{value}' is not matching expected type of '{formatted_type}'")
|
774
808
|
|
775
809
|
return validator
|
776
810
|
|
@@ -799,6 +833,7 @@ VALIDATORS: Mapping[
|
|
799
833
|
frozenset: _prepare_validator_of_set,
|
800
834
|
Set: _prepare_validator_of_set,
|
801
835
|
Sequence: _prepare_validator_of_sequence,
|
836
|
+
Collection: _prepare_validator_of_collection,
|
802
837
|
Mapping: _prepare_validator_of_mapping,
|
803
838
|
Literal: _prepare_validator_of_literal,
|
804
839
|
range: _prepare_validator_of_type,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: haiway
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.25.1
|
4
4
|
Summary: Framework for dependency injection and state management within structured concurrency model.
|
5
5
|
Project-URL: Homepage, https://miquido.com
|
6
6
|
Project-URL: Repository, https://github.com/miquido/haiway.git
|