haiway 0.19.4__py3-none-any.whl → 0.20.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 +4 -0
- haiway/context/__init__.py +2 -0
- haiway/context/access.py +88 -8
- haiway/context/disposables.py +63 -0
- haiway/context/identifier.py +81 -27
- haiway/context/observability.py +303 -7
- haiway/context/state.py +126 -0
- haiway/context/tasks.py +66 -0
- haiway/context/types.py +16 -0
- haiway/helpers/__init__.py +2 -0
- haiway/helpers/asynchrony.py +61 -12
- haiway/helpers/caching.py +31 -0
- haiway/helpers/concurrent.py +74 -0
- haiway/helpers/observability.py +94 -11
- haiway/helpers/retries.py +59 -18
- haiway/helpers/throttling.py +42 -15
- haiway/helpers/timeouted.py +25 -10
- haiway/helpers/tracing.py +31 -0
- haiway/opentelemetry/observability.py +346 -29
- haiway/state/attributes.py +104 -0
- haiway/state/path.py +427 -12
- haiway/state/requirement.py +196 -0
- haiway/state/structure.py +359 -1
- haiway/state/validation.py +293 -0
- haiway/types/default.py +56 -0
- haiway/types/frozen.py +18 -0
- haiway/types/missing.py +89 -0
- haiway/utils/collections.py +36 -28
- haiway/utils/env.py +145 -13
- haiway/utils/formatting.py +27 -0
- haiway/utils/freezing.py +21 -1
- haiway/utils/noop.py +34 -2
- haiway/utils/queue.py +68 -1
- haiway/utils/stream.py +83 -0
- {haiway-0.19.4.dist-info → haiway-0.20.0.dist-info}/METADATA +1 -1
- haiway-0.20.0.dist-info/RECORD +46 -0
- haiway-0.19.4.dist-info/RECORD +0 -45
- {haiway-0.19.4.dist-info → haiway-0.20.0.dist-info}/WHEEL +0 -0
- {haiway-0.19.4.dist-info → haiway-0.20.0.dist-info}/licenses/LICENSE +0 -0
haiway/state/structure.py
CHANGED
@@ -23,6 +23,15 @@ __all__ = ("State",)
|
|
23
23
|
|
24
24
|
@final
|
25
25
|
class StateAttribute[Value]:
|
26
|
+
"""
|
27
|
+
Represents an attribute in a State class with its metadata.
|
28
|
+
|
29
|
+
This class holds information about a specific attribute in a State class,
|
30
|
+
including its name, type annotation, default value, and validation rules.
|
31
|
+
It is used internally by the State metaclass to manage state attributes
|
32
|
+
and ensure their immutability and type safety.
|
33
|
+
"""
|
34
|
+
|
26
35
|
__slots__ = (
|
27
36
|
"annotation",
|
28
37
|
"default",
|
@@ -37,6 +46,20 @@ class StateAttribute[Value]:
|
|
37
46
|
default: DefaultValue[Value],
|
38
47
|
validator: AttributeValidation[Value],
|
39
48
|
) -> None:
|
49
|
+
"""
|
50
|
+
Initialize a new StateAttribute.
|
51
|
+
|
52
|
+
Parameters
|
53
|
+
----------
|
54
|
+
name : str
|
55
|
+
The name of the attribute
|
56
|
+
annotation : AttributeAnnotation
|
57
|
+
The type annotation of the attribute
|
58
|
+
default : DefaultValue[Value]
|
59
|
+
The default value provider for the attribute
|
60
|
+
validator : AttributeValidation[Value]
|
61
|
+
The validation function for the attribute values
|
62
|
+
"""
|
40
63
|
self.name: str
|
41
64
|
object.__setattr__(
|
42
65
|
self,
|
@@ -67,6 +90,22 @@ class StateAttribute[Value]:
|
|
67
90
|
value: Any | Missing,
|
68
91
|
/,
|
69
92
|
) -> Value:
|
93
|
+
"""
|
94
|
+
Validate and potentially transform the provided value.
|
95
|
+
|
96
|
+
If the value is MISSING, the default value is used instead.
|
97
|
+
The value (or default) is then passed through the validator.
|
98
|
+
|
99
|
+
Parameters
|
100
|
+
----------
|
101
|
+
value : Any | Missing
|
102
|
+
The value to validate, or MISSING to use the default
|
103
|
+
|
104
|
+
Returns
|
105
|
+
-------
|
106
|
+
Value
|
107
|
+
The validated and potentially transformed value
|
108
|
+
"""
|
70
109
|
return self.validator(self.default() if value is MISSING else value)
|
71
110
|
|
72
111
|
def __setattr__(
|
@@ -95,6 +134,21 @@ class StateAttribute[Value]:
|
|
95
134
|
field_specifiers=(DefaultValue,),
|
96
135
|
)
|
97
136
|
class StateMeta(type):
|
137
|
+
"""
|
138
|
+
Metaclass for State classes that manages attribute definitions and validation.
|
139
|
+
|
140
|
+
This metaclass is responsible for:
|
141
|
+
- Processing attribute annotations and defaults
|
142
|
+
- Creating StateAttribute instances for each attribute
|
143
|
+
- Setting up validation for attributes
|
144
|
+
- Managing generic type parameters and specialization
|
145
|
+
- Creating immutable class instances
|
146
|
+
|
147
|
+
The dataclass_transform decorator allows State classes to be treated
|
148
|
+
like dataclasses by static type checkers while using custom initialization
|
149
|
+
and validation logic.
|
150
|
+
"""
|
151
|
+
|
98
152
|
def __new__(
|
99
153
|
cls,
|
100
154
|
/,
|
@@ -104,6 +158,27 @@ class StateMeta(type):
|
|
104
158
|
type_parameters: dict[str, Any] | None = None,
|
105
159
|
**kwargs: Any,
|
106
160
|
) -> Any:
|
161
|
+
"""
|
162
|
+
Create a new State class with processed attributes and validation.
|
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
|
+
"""
|
107
182
|
state_type = type.__new__(
|
108
183
|
cls,
|
109
184
|
name,
|
@@ -143,12 +218,45 @@ class StateMeta(type):
|
|
143
218
|
cls,
|
144
219
|
value: Any,
|
145
220
|
/,
|
146
|
-
) -> Any:
|
221
|
+
) -> Any:
|
222
|
+
"""
|
223
|
+
Placeholder for the validator method that will be implemented in each State class.
|
224
|
+
|
225
|
+
This method validates and potentially transforms a value to ensure it
|
226
|
+
conforms to the class's requirements.
|
227
|
+
|
228
|
+
Parameters
|
229
|
+
----------
|
230
|
+
value : Any
|
231
|
+
The value to validate
|
232
|
+
|
233
|
+
Returns
|
234
|
+
-------
|
235
|
+
Any
|
236
|
+
The validated value
|
237
|
+
"""
|
238
|
+
...
|
147
239
|
|
148
240
|
def __instancecheck__(
|
149
241
|
self,
|
150
242
|
instance: Any,
|
151
243
|
) -> 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
|
+
"""
|
152
260
|
# check for type match
|
153
261
|
if self.__subclasscheck__(type(instance)): # pyright: ignore[reportUnknownArgumentType]
|
154
262
|
return True
|
@@ -172,6 +280,27 @@ class StateMeta(type):
|
|
172
280
|
self,
|
173
281
|
subclass: type[Any],
|
174
282
|
) -> bool:
|
283
|
+
"""
|
284
|
+
Check if a class is a subclass of this class.
|
285
|
+
|
286
|
+
Implements issubclass() behavior for State classes, with special handling
|
287
|
+
for generic type parameters.
|
288
|
+
|
289
|
+
Parameters
|
290
|
+
----------
|
291
|
+
subclass : type[Any]
|
292
|
+
The class to check
|
293
|
+
|
294
|
+
Returns
|
295
|
+
-------
|
296
|
+
bool
|
297
|
+
True if the class is a subclass of this class, False otherwise
|
298
|
+
|
299
|
+
Raises
|
300
|
+
------
|
301
|
+
RuntimeError
|
302
|
+
If there is an issue with type parametrization
|
303
|
+
"""
|
175
304
|
# check if we are the same class for early exit
|
176
305
|
if self == subclass:
|
177
306
|
return True
|
@@ -231,6 +360,19 @@ class StateMeta(type):
|
|
231
360
|
def _resolve_default[Value](
|
232
361
|
value: DefaultValue[Value] | Value | Missing,
|
233
362
|
) -> DefaultValue[Value]:
|
363
|
+
"""
|
364
|
+
Ensure a value is wrapped in a DefaultValue container.
|
365
|
+
|
366
|
+
Parameters
|
367
|
+
----------
|
368
|
+
value : DefaultValue[Value] | Value | Missing
|
369
|
+
The value or default value container to resolve
|
370
|
+
|
371
|
+
Returns
|
372
|
+
-------
|
373
|
+
DefaultValue[Value]
|
374
|
+
The value wrapped in a DefaultValue container
|
375
|
+
"""
|
234
376
|
if isinstance(value, DefaultValue):
|
235
377
|
return cast(DefaultValue[Value], value)
|
236
378
|
|
@@ -252,6 +394,44 @@ _types_cache: WeakValueDictionary[
|
|
252
394
|
class State(metaclass=StateMeta):
|
253
395
|
"""
|
254
396
|
Base class for immutable data structures.
|
397
|
+
|
398
|
+
State provides a framework for creating immutable, type-safe data classes
|
399
|
+
with validation. It's designed to represent application state that can be
|
400
|
+
safely shared and updated in a predictable manner.
|
401
|
+
|
402
|
+
Key features:
|
403
|
+
- Immutable: Instances cannot be modified after creation
|
404
|
+
- Type-safe: Attributes are validated based on type annotations
|
405
|
+
- Generic: Can be parameterized with type variables
|
406
|
+
- Declarative: Uses a class-based declaration syntax similar to dataclasses
|
407
|
+
- Validated: Custom validation rules can be applied to attributes
|
408
|
+
|
409
|
+
State classes can be created by subclassing State and declaring attributes:
|
410
|
+
|
411
|
+
```python
|
412
|
+
class User(State):
|
413
|
+
name: str
|
414
|
+
age: int
|
415
|
+
email: str | None = None
|
416
|
+
```
|
417
|
+
|
418
|
+
Instances are created using standard constructor syntax:
|
419
|
+
|
420
|
+
```python
|
421
|
+
user = User(name="Alice", age=30)
|
422
|
+
```
|
423
|
+
|
424
|
+
New instances with updated values can be created from existing ones:
|
425
|
+
|
426
|
+
```python
|
427
|
+
updated_user = user.updated(age=31)
|
428
|
+
```
|
429
|
+
|
430
|
+
Path-based updates are also supported:
|
431
|
+
|
432
|
+
```python
|
433
|
+
updated_user = user.updating(User._.age, 31)
|
434
|
+
```
|
255
435
|
"""
|
256
436
|
|
257
437
|
_: ClassVar[Self]
|
@@ -264,6 +444,27 @@ class State(metaclass=StateMeta):
|
|
264
444
|
cls,
|
265
445
|
type_argument: tuple[type[Any], ...] | type[Any],
|
266
446
|
) -> type[Self]:
|
447
|
+
"""
|
448
|
+
Create a specialized version of a generic State class.
|
449
|
+
|
450
|
+
This method enables the generic type syntax Class[TypeArg] for State classes.
|
451
|
+
|
452
|
+
Parameters
|
453
|
+
----------
|
454
|
+
type_argument : tuple[type[Any], ...] | type[Any]
|
455
|
+
The type arguments to specialize the class with
|
456
|
+
|
457
|
+
Returns
|
458
|
+
-------
|
459
|
+
type[Self]
|
460
|
+
A specialized version of the class
|
461
|
+
|
462
|
+
Raises
|
463
|
+
------
|
464
|
+
AssertionError
|
465
|
+
If the class is not generic or is already specialized,
|
466
|
+
or if the number of type arguments doesn't match the parameters
|
467
|
+
"""
|
267
468
|
assert Generic in cls.__bases__, "Can't specialize non generic type!" # nosec: B101
|
268
469
|
assert cls.__TYPE_PARAMETERS__ is None, "Can't specialize already specialized type!" # nosec: B101
|
269
470
|
|
@@ -322,6 +523,24 @@ class State(metaclass=StateMeta):
|
|
322
523
|
value: Any,
|
323
524
|
/,
|
324
525
|
) -> Self:
|
526
|
+
"""
|
527
|
+
Validate and convert a value to an instance of this class.
|
528
|
+
|
529
|
+
Parameters
|
530
|
+
----------
|
531
|
+
value : Any
|
532
|
+
The value to validate and convert
|
533
|
+
|
534
|
+
Returns
|
535
|
+
-------
|
536
|
+
Self
|
537
|
+
An instance of this class
|
538
|
+
|
539
|
+
Raises
|
540
|
+
------
|
541
|
+
TypeError
|
542
|
+
If the value cannot be converted to an instance of this class
|
543
|
+
"""
|
325
544
|
match value:
|
326
545
|
case validated if isinstance(validated, cls):
|
327
546
|
return validated
|
@@ -336,6 +555,23 @@ class State(metaclass=StateMeta):
|
|
336
555
|
self,
|
337
556
|
**kwargs: Any,
|
338
557
|
) -> None:
|
558
|
+
"""
|
559
|
+
Initialize a new State instance.
|
560
|
+
|
561
|
+
Creates a new instance with the provided attribute values.
|
562
|
+
Attributes not specified will use their default values.
|
563
|
+
All attributes are validated according to their type annotations.
|
564
|
+
|
565
|
+
Parameters
|
566
|
+
----------
|
567
|
+
**kwargs : Any
|
568
|
+
Attribute values for the new instance
|
569
|
+
|
570
|
+
Raises
|
571
|
+
------
|
572
|
+
Exception
|
573
|
+
If validation fails for any attribute
|
574
|
+
"""
|
339
575
|
for name, attribute in self.__ATTRIBUTES__.items():
|
340
576
|
object.__setattr__(
|
341
577
|
self, # pyright: ignore[reportUnknownArgumentType]
|
@@ -354,6 +590,26 @@ class State(metaclass=StateMeta):
|
|
354
590
|
/,
|
355
591
|
value: Value,
|
356
592
|
) -> Self:
|
593
|
+
"""
|
594
|
+
Create a new instance with an updated value at the specified path.
|
595
|
+
|
596
|
+
Parameters
|
597
|
+
----------
|
598
|
+
path : AttributePath[Self, Value] | Value
|
599
|
+
An attribute path created with Class._.attribute syntax
|
600
|
+
value : Value
|
601
|
+
The new value for the specified attribute
|
602
|
+
|
603
|
+
Returns
|
604
|
+
-------
|
605
|
+
Self
|
606
|
+
A new instance with the updated value
|
607
|
+
|
608
|
+
Raises
|
609
|
+
------
|
610
|
+
AssertionError
|
611
|
+
If path is not an AttributePath
|
612
|
+
"""
|
357
613
|
assert isinstance( # nosec: B101
|
358
614
|
path, AttributePath
|
359
615
|
), "Prepare parameter path by using Self._.path.to.property or explicitly"
|
@@ -364,15 +620,52 @@ class State(metaclass=StateMeta):
|
|
364
620
|
self,
|
365
621
|
**kwargs: Any,
|
366
622
|
) -> Self:
|
623
|
+
"""
|
624
|
+
Create a new instance with updated attribute values.
|
625
|
+
|
626
|
+
This method creates a new instance with the same attribute values as this
|
627
|
+
instance, but with any provided values updated.
|
628
|
+
|
629
|
+
Parameters
|
630
|
+
----------
|
631
|
+
**kwargs : Any
|
632
|
+
New values for attributes to update
|
633
|
+
|
634
|
+
Returns
|
635
|
+
-------
|
636
|
+
Self
|
637
|
+
A new instance with updated values
|
638
|
+
"""
|
367
639
|
return self.__replace__(**kwargs)
|
368
640
|
|
369
641
|
def to_str(self) -> str:
|
642
|
+
"""
|
643
|
+
Convert this instance to a string representation.
|
644
|
+
|
645
|
+
Returns
|
646
|
+
-------
|
647
|
+
str
|
648
|
+
A string representation of this instance
|
649
|
+
"""
|
370
650
|
return self.__str__()
|
371
651
|
|
372
652
|
def to_mapping(
|
373
653
|
self,
|
374
654
|
recursive: bool = False,
|
375
655
|
) -> Mapping[str, Any]:
|
656
|
+
"""
|
657
|
+
Convert this instance to a mapping of attribute names to values.
|
658
|
+
|
659
|
+
Parameters
|
660
|
+
----------
|
661
|
+
recursive : bool, default=False
|
662
|
+
If True, nested State instances are also converted to mappings
|
663
|
+
|
664
|
+
Returns
|
665
|
+
-------
|
666
|
+
Mapping[str, Any]
|
667
|
+
A mapping of attribute names to values
|
668
|
+
"""
|
376
669
|
dict_result: dict[str, Any] = {}
|
377
670
|
for key in self.__ATTRIBUTES__.keys():
|
378
671
|
value: Any | Missing = getattr(self, key, MISSING)
|
@@ -385,6 +678,14 @@ class State(metaclass=StateMeta):
|
|
385
678
|
return dict_result
|
386
679
|
|
387
680
|
def __str__(self) -> str:
|
681
|
+
"""
|
682
|
+
Get a string representation of this instance.
|
683
|
+
|
684
|
+
Returns
|
685
|
+
-------
|
686
|
+
str
|
687
|
+
A string representation in the format "ClassName(attr1: value1, attr2: value2)"
|
688
|
+
"""
|
388
689
|
attributes: str = ", ".join([f"{key}: {value}" for key, value in vars(self).items()])
|
389
690
|
return f"{self.__class__.__name__}({attributes})"
|
390
691
|
|
@@ -395,6 +696,22 @@ class State(metaclass=StateMeta):
|
|
395
696
|
self,
|
396
697
|
other: Any,
|
397
698
|
) -> bool:
|
699
|
+
"""
|
700
|
+
Check if this instance is equal to another object.
|
701
|
+
|
702
|
+
Two State instances are considered equal if they are instances of the
|
703
|
+
same class or subclass and have equal values for all attributes.
|
704
|
+
|
705
|
+
Parameters
|
706
|
+
----------
|
707
|
+
other : Any
|
708
|
+
The object to compare with
|
709
|
+
|
710
|
+
Returns
|
711
|
+
-------
|
712
|
+
bool
|
713
|
+
True if the objects are equal, False otherwise
|
714
|
+
"""
|
398
715
|
if not issubclass(other.__class__, self.__class__):
|
399
716
|
return False
|
400
717
|
|
@@ -423,18 +740,59 @@ class State(metaclass=StateMeta):
|
|
423
740
|
)
|
424
741
|
|
425
742
|
def __copy__(self) -> Self:
|
743
|
+
"""
|
744
|
+
Create a shallow copy of this instance.
|
745
|
+
|
746
|
+
Since State is immutable, this returns the instance itself.
|
747
|
+
|
748
|
+
Returns
|
749
|
+
-------
|
750
|
+
Self
|
751
|
+
This instance
|
752
|
+
"""
|
426
753
|
return self # State is immutable, no need to provide an actual copy
|
427
754
|
|
428
755
|
def __deepcopy__(
|
429
756
|
self,
|
430
757
|
memo: dict[int, Any] | None,
|
431
758
|
) -> Self:
|
759
|
+
"""
|
760
|
+
Create a deep copy of this instance.
|
761
|
+
|
762
|
+
Since State is immutable, this returns the instance itself.
|
763
|
+
|
764
|
+
Parameters
|
765
|
+
----------
|
766
|
+
memo : dict[int, Any] | None
|
767
|
+
Memoization dictionary for already copied objects
|
768
|
+
|
769
|
+
Returns
|
770
|
+
-------
|
771
|
+
Self
|
772
|
+
This instance
|
773
|
+
"""
|
432
774
|
return self # State is immutable, no need to provide an actual copy
|
433
775
|
|
434
776
|
def __replace__(
|
435
777
|
self,
|
436
778
|
**kwargs: Any,
|
437
779
|
) -> Self:
|
780
|
+
"""
|
781
|
+
Create a new instance with replaced attribute values.
|
782
|
+
|
783
|
+
This internal method is used by updated() to create a new instance
|
784
|
+
with updated values.
|
785
|
+
|
786
|
+
Parameters
|
787
|
+
----------
|
788
|
+
**kwargs : Any
|
789
|
+
New values for attributes to replace
|
790
|
+
|
791
|
+
Returns
|
792
|
+
-------
|
793
|
+
Self
|
794
|
+
A new instance with replaced values
|
795
|
+
"""
|
438
796
|
return self.__class__(
|
439
797
|
**{
|
440
798
|
**vars(self),
|