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/path.py
CHANGED
@@ -14,19 +14,58 @@ __all__ = ("AttributePath",)
|
|
14
14
|
|
15
15
|
|
16
16
|
class AttributePathComponent(ABC):
|
17
|
+
"""
|
18
|
+
Abstract base class for components in an attribute path.
|
19
|
+
|
20
|
+
This class defines the interface for components that make up an attribute path,
|
21
|
+
such as property access, sequence item access, or mapping item access.
|
22
|
+
Each component knows how to access and update values at its position in the path.
|
23
|
+
"""
|
24
|
+
|
17
25
|
@abstractmethod
|
18
26
|
def path_str(
|
19
27
|
self,
|
20
28
|
current: str | None = None,
|
21
29
|
/,
|
22
|
-
) -> str:
|
30
|
+
) -> str:
|
31
|
+
"""
|
32
|
+
Convert this path component to a string representation.
|
33
|
+
|
34
|
+
Parameters
|
35
|
+
----------
|
36
|
+
current : str | None
|
37
|
+
The current path string to append to
|
38
|
+
|
39
|
+
Returns
|
40
|
+
-------
|
41
|
+
str
|
42
|
+
String representation of the path including this component
|
43
|
+
"""
|
23
44
|
|
24
45
|
@abstractmethod
|
25
46
|
def access(
|
26
47
|
self,
|
27
48
|
subject: Any,
|
28
49
|
/,
|
29
|
-
) -> Any:
|
50
|
+
) -> Any:
|
51
|
+
"""
|
52
|
+
Access the property value from the subject.
|
53
|
+
|
54
|
+
Parameters
|
55
|
+
----------
|
56
|
+
subject : Any
|
57
|
+
The object to access the property from
|
58
|
+
|
59
|
+
Returns
|
60
|
+
-------
|
61
|
+
Any
|
62
|
+
The value of the property
|
63
|
+
|
64
|
+
Raises
|
65
|
+
------
|
66
|
+
AttributeError
|
67
|
+
If the property doesn't exist on the subject
|
68
|
+
"""
|
30
69
|
|
31
70
|
@abstractmethod
|
32
71
|
def assigning(
|
@@ -34,7 +73,28 @@ class AttributePathComponent(ABC):
|
|
34
73
|
subject: Any,
|
35
74
|
/,
|
36
75
|
value: Any,
|
37
|
-
) -> Any:
|
76
|
+
) -> Any:
|
77
|
+
"""
|
78
|
+
Create a new object with an updated value at this path component.
|
79
|
+
|
80
|
+
Parameters
|
81
|
+
----------
|
82
|
+
subject : Any
|
83
|
+
The original object to update
|
84
|
+
value : Any
|
85
|
+
The new value to assign at this path component
|
86
|
+
|
87
|
+
Returns
|
88
|
+
-------
|
89
|
+
Any
|
90
|
+
A new object with the updated value
|
91
|
+
|
92
|
+
Raises
|
93
|
+
------
|
94
|
+
TypeError
|
95
|
+
If the subject cannot be updated with the given value
|
96
|
+
"""
|
97
|
+
...
|
38
98
|
|
39
99
|
|
40
100
|
@final
|
@@ -151,17 +211,43 @@ class PropertyAttributePathComponent(AttributePathComponent):
|
|
151
211
|
current: str | None = None,
|
152
212
|
/,
|
153
213
|
) -> str:
|
154
|
-
|
155
|
-
|
214
|
+
"""
|
215
|
+
Convert this property component to a string representation.
|
216
|
+
|
217
|
+
Parameters
|
218
|
+
----------
|
219
|
+
current : str | None
|
220
|
+
The current path string to append to
|
221
|
+
|
222
|
+
Returns
|
223
|
+
-------
|
224
|
+
str
|
225
|
+
String representation with the property appended
|
226
|
+
"""
|
227
|
+
if current in (None, ""):
|
228
|
+
return self._name
|
156
229
|
|
157
230
|
else:
|
158
|
-
return self._name
|
231
|
+
return f"{current}.{self._name}"
|
159
232
|
|
160
233
|
def access(
|
161
234
|
self,
|
162
235
|
subject: Any,
|
163
236
|
/,
|
164
237
|
) -> Any:
|
238
|
+
"""
|
239
|
+
Access the property value from the subject.
|
240
|
+
|
241
|
+
Parameters
|
242
|
+
----------
|
243
|
+
subject : Any
|
244
|
+
The object to access the property from
|
245
|
+
|
246
|
+
Returns
|
247
|
+
-------
|
248
|
+
Any
|
249
|
+
The value of the property
|
250
|
+
"""
|
165
251
|
return self._access(subject)
|
166
252
|
|
167
253
|
def assigning(
|
@@ -170,11 +256,38 @@ class PropertyAttributePathComponent(AttributePathComponent):
|
|
170
256
|
/,
|
171
257
|
value: Any,
|
172
258
|
) -> Any:
|
259
|
+
"""
|
260
|
+
Create a new subject with an updated property value.
|
261
|
+
|
262
|
+
Parameters
|
263
|
+
----------
|
264
|
+
subject : Any
|
265
|
+
The original object
|
266
|
+
value : Any
|
267
|
+
The new value for the property
|
268
|
+
|
269
|
+
Returns
|
270
|
+
-------
|
271
|
+
Any
|
272
|
+
A new object with the updated property value
|
273
|
+
|
274
|
+
Raises
|
275
|
+
------
|
276
|
+
TypeError
|
277
|
+
If the subject doesn't support property updates
|
278
|
+
"""
|
173
279
|
return self._assigning(subject, value)
|
174
280
|
|
175
281
|
|
176
282
|
@final
|
177
|
-
class SequenceItemAttributePathComponent(AttributePathComponent):
|
283
|
+
class SequenceItemAttributePathComponent[Owner, Value](AttributePathComponent):
|
284
|
+
"""
|
285
|
+
Path component for accessing items in a sequence by index.
|
286
|
+
|
287
|
+
This component represents sequence item access using index notation (seq[index])
|
288
|
+
in an attribute path. It provides type-safe access and updates for sequence items.
|
289
|
+
"""
|
290
|
+
|
178
291
|
__slots__ = (
|
179
292
|
"_access",
|
180
293
|
"_assigning",
|
@@ -269,6 +382,19 @@ class SequenceItemAttributePathComponent(AttributePathComponent):
|
|
269
382
|
current: str | None = None,
|
270
383
|
/,
|
271
384
|
) -> str:
|
385
|
+
"""
|
386
|
+
Convert this sequence item component to a string representation.
|
387
|
+
|
388
|
+
Parameters
|
389
|
+
----------
|
390
|
+
current : str | None
|
391
|
+
The current path string to append to
|
392
|
+
|
393
|
+
Returns
|
394
|
+
-------
|
395
|
+
str
|
396
|
+
String representation with the sequence index appended
|
397
|
+
"""
|
272
398
|
return f"{current or ''}[{self._index}]"
|
273
399
|
|
274
400
|
def access(
|
@@ -276,6 +402,24 @@ class SequenceItemAttributePathComponent(AttributePathComponent):
|
|
276
402
|
subject: Any,
|
277
403
|
/,
|
278
404
|
) -> Any:
|
405
|
+
"""
|
406
|
+
Access the sequence item from the subject.
|
407
|
+
|
408
|
+
Parameters
|
409
|
+
----------
|
410
|
+
subject : Any
|
411
|
+
The sequence to access the item from
|
412
|
+
|
413
|
+
Returns
|
414
|
+
-------
|
415
|
+
Any
|
416
|
+
The value at the specified index
|
417
|
+
|
418
|
+
Raises
|
419
|
+
------
|
420
|
+
IndexError
|
421
|
+
If the index is out of bounds
|
422
|
+
"""
|
279
423
|
return self._access(subject)
|
280
424
|
|
281
425
|
def assigning(
|
@@ -284,6 +428,26 @@ class SequenceItemAttributePathComponent(AttributePathComponent):
|
|
284
428
|
/,
|
285
429
|
value: Any,
|
286
430
|
) -> Any:
|
431
|
+
"""
|
432
|
+
Create a new sequence with an updated item value.
|
433
|
+
|
434
|
+
Parameters
|
435
|
+
----------
|
436
|
+
subject : Any
|
437
|
+
The original sequence
|
438
|
+
value : Any
|
439
|
+
The new value for the item
|
440
|
+
|
441
|
+
Returns
|
442
|
+
-------
|
443
|
+
Any
|
444
|
+
A new sequence with the updated item
|
445
|
+
|
446
|
+
Raises
|
447
|
+
------
|
448
|
+
TypeError
|
449
|
+
If the subject doesn't support item updates
|
450
|
+
"""
|
287
451
|
return self._assigning(subject, value)
|
288
452
|
|
289
453
|
|
@@ -383,6 +547,19 @@ class MappingItemAttributePathComponent(AttributePathComponent):
|
|
383
547
|
current: str | None = None,
|
384
548
|
/,
|
385
549
|
) -> str:
|
550
|
+
"""
|
551
|
+
Convert this mapping item component to a string representation.
|
552
|
+
|
553
|
+
Parameters
|
554
|
+
----------
|
555
|
+
current : str | None
|
556
|
+
The current path string to append to
|
557
|
+
|
558
|
+
Returns
|
559
|
+
-------
|
560
|
+
str
|
561
|
+
String representation with the mapping key appended
|
562
|
+
"""
|
386
563
|
return f"{current or ''}[{self._key}]"
|
387
564
|
|
388
565
|
def access(
|
@@ -390,6 +567,24 @@ class MappingItemAttributePathComponent(AttributePathComponent):
|
|
390
567
|
subject: Any,
|
391
568
|
/,
|
392
569
|
) -> Any:
|
570
|
+
"""
|
571
|
+
Access the mapping item from the subject.
|
572
|
+
|
573
|
+
Parameters
|
574
|
+
----------
|
575
|
+
subject : Any
|
576
|
+
The mapping to access the item from
|
577
|
+
|
578
|
+
Returns
|
579
|
+
-------
|
580
|
+
Any
|
581
|
+
The value associated with the key
|
582
|
+
|
583
|
+
Raises
|
584
|
+
------
|
585
|
+
KeyError
|
586
|
+
If the key doesn't exist in the mapping
|
587
|
+
"""
|
393
588
|
return self._access(subject)
|
394
589
|
|
395
590
|
def assigning(
|
@@ -398,11 +593,70 @@ class MappingItemAttributePathComponent(AttributePathComponent):
|
|
398
593
|
/,
|
399
594
|
value: Any,
|
400
595
|
) -> Any:
|
596
|
+
"""
|
597
|
+
Create a new mapping with an updated item value.
|
598
|
+
|
599
|
+
Parameters
|
600
|
+
----------
|
601
|
+
subject : Any
|
602
|
+
The original mapping
|
603
|
+
value : Any
|
604
|
+
The new value for the item
|
605
|
+
|
606
|
+
Returns
|
607
|
+
-------
|
608
|
+
Any
|
609
|
+
A new mapping with the updated item
|
610
|
+
|
611
|
+
Raises
|
612
|
+
------
|
613
|
+
TypeError
|
614
|
+
If the subject doesn't support item updates
|
615
|
+
"""
|
401
616
|
return self._assigning(subject, value)
|
402
617
|
|
403
618
|
|
404
619
|
@final
|
405
620
|
class AttributePath[Root, Attribute]:
|
621
|
+
"""
|
622
|
+
Represents a path to an attribute within a nested structure.
|
623
|
+
|
624
|
+
AttributePath enables type-safe attribute access and updates for complex
|
625
|
+
nested structures, particularly State objects. It provides a fluent interface
|
626
|
+
for building paths using attribute access (obj.attr) and item access (obj[key])
|
627
|
+
syntax.
|
628
|
+
|
629
|
+
The class is generic over two type parameters:
|
630
|
+
- Root: The type of the root object the path starts from
|
631
|
+
- Attribute: The type of the attribute the path points to
|
632
|
+
|
633
|
+
AttributePaths are immutable and can be reused. When applied to different
|
634
|
+
root objects, they will access the same nested path in each object.
|
635
|
+
|
636
|
+
Examples
|
637
|
+
--------
|
638
|
+
Creating paths:
|
639
|
+
```python
|
640
|
+
# Access user.name
|
641
|
+
User._.name
|
642
|
+
|
643
|
+
# Access users[0].address.city
|
644
|
+
User._.users[0].address.city
|
645
|
+
|
646
|
+
# Access data["key"]
|
647
|
+
Data._["key"]
|
648
|
+
```
|
649
|
+
|
650
|
+
Using paths:
|
651
|
+
```python
|
652
|
+
# Get value
|
653
|
+
name = User._.name(user)
|
654
|
+
|
655
|
+
# Update value
|
656
|
+
updated_user = user.updating(User._.name, "New Name")
|
657
|
+
```
|
658
|
+
"""
|
659
|
+
|
406
660
|
__slots__ = (
|
407
661
|
"__attribute__",
|
408
662
|
"__components__",
|
@@ -425,7 +679,24 @@ class AttributePath[Root, Attribute]:
|
|
425
679
|
/,
|
426
680
|
*components: AttributePathComponent,
|
427
681
|
attribute: type[Attribute],
|
428
|
-
) -> None:
|
682
|
+
) -> None:
|
683
|
+
"""
|
684
|
+
Initialize a new attribute path.
|
685
|
+
|
686
|
+
Parameters
|
687
|
+
----------
|
688
|
+
root : type[Root]
|
689
|
+
The root type this path starts from
|
690
|
+
*components : AttributePathComponent
|
691
|
+
Path components defining the traversal from root to attribute
|
692
|
+
attribute : type[Attribute]
|
693
|
+
The type of the attribute at the end of this path
|
694
|
+
|
695
|
+
Raises
|
696
|
+
------
|
697
|
+
AssertionError
|
698
|
+
If no components are provided and root != attribute
|
699
|
+
"""
|
429
700
|
|
430
701
|
def __init__(
|
431
702
|
self,
|
@@ -475,9 +746,27 @@ class AttributePath[Root, Attribute]:
|
|
475
746
|
|
476
747
|
@property
|
477
748
|
def components(self) -> Sequence[str]:
|
749
|
+
"""
|
750
|
+
Get the components of this path as strings.
|
751
|
+
|
752
|
+
Returns
|
753
|
+
-------
|
754
|
+
Sequence[str]
|
755
|
+
String representations of each path component
|
756
|
+
"""
|
478
757
|
return tuple(component.path_str() for component in self.__components__)
|
479
758
|
|
480
759
|
def __str__(self) -> str:
|
760
|
+
"""
|
761
|
+
Get a string representation of this path.
|
762
|
+
|
763
|
+
The string starts empty and builds up by appending each component.
|
764
|
+
|
765
|
+
Returns
|
766
|
+
-------
|
767
|
+
str
|
768
|
+
A string representation of the path (e.g., ".attr1.attr2[0]")
|
769
|
+
"""
|
481
770
|
path: str = ""
|
482
771
|
for component in self.__components__:
|
483
772
|
path = component.path_str(path)
|
@@ -485,6 +774,16 @@ class AttributePath[Root, Attribute]:
|
|
485
774
|
return path
|
486
775
|
|
487
776
|
def __repr__(self) -> str:
|
777
|
+
"""
|
778
|
+
Get a detailed string representation of this path.
|
779
|
+
|
780
|
+
Unlike __str__, this includes the root type name at the beginning.
|
781
|
+
|
782
|
+
Returns
|
783
|
+
-------
|
784
|
+
str
|
785
|
+
A detailed string representation of the path (e.g., "User.name[0]")
|
786
|
+
"""
|
488
787
|
path: str = self.__root__.__name__
|
489
788
|
for component in self.__components__:
|
490
789
|
path = component.path_str(path)
|
@@ -495,6 +794,28 @@ class AttributePath[Root, Attribute]:
|
|
495
794
|
self,
|
496
795
|
name: str,
|
497
796
|
) -> Any:
|
797
|
+
"""
|
798
|
+
Extend the path with property access to the specified attribute.
|
799
|
+
|
800
|
+
This method is called when using dot notation (path.attribute) on an
|
801
|
+
AttributePath instance. It creates a new AttributePath that includes
|
802
|
+
the additional property access.
|
803
|
+
|
804
|
+
Parameters
|
805
|
+
----------
|
806
|
+
name : str
|
807
|
+
The attribute name to access
|
808
|
+
|
809
|
+
Returns
|
810
|
+
-------
|
811
|
+
AttributePath
|
812
|
+
A new AttributePath extended with the attribute access
|
813
|
+
|
814
|
+
Raises
|
815
|
+
------
|
816
|
+
AttributeError
|
817
|
+
If the attribute is not found or cannot be accessed
|
818
|
+
"""
|
498
819
|
try:
|
499
820
|
return object.__getattribute__(self, name)
|
500
821
|
|
@@ -531,6 +852,54 @@ class AttributePath[Root, Attribute]:
|
|
531
852
|
self,
|
532
853
|
key: str | int,
|
533
854
|
) -> Any:
|
855
|
+
"""
|
856
|
+
Extend the path with item access using the specified key.
|
857
|
+
|
858
|
+
This method is called when using item access notation (path[key]) on an
|
859
|
+
AttributePath instance. It creates a new AttributePath that includes the
|
860
|
+
additional item access component.
|
861
|
+
|
862
|
+
Parameters
|
863
|
+
----------
|
864
|
+
key : str | int
|
865
|
+
The key or index to access. String keys are used for mapping access
|
866
|
+
and integer keys for sequence/tuple access.
|
867
|
+
|
868
|
+
Returns
|
869
|
+
-------
|
870
|
+
AttributePath
|
871
|
+
A new AttributePath extended with the item access component
|
872
|
+
|
873
|
+
Raises
|
874
|
+
------
|
875
|
+
TypeError
|
876
|
+
If the key type is incompatible with the attribute type or if the
|
877
|
+
attribute type does not support item access
|
878
|
+
"""
|
879
|
+
"""
|
880
|
+
Extend the path with item access using the specified key.
|
881
|
+
|
882
|
+
This method is called when using item access notation (path[key]) on an
|
883
|
+
AttributePath instance. It creates a new AttributePath that includes the
|
884
|
+
additional item access component.
|
885
|
+
|
886
|
+
Parameters
|
887
|
+
----------
|
888
|
+
key : str | int
|
889
|
+
The key or index to access. String keys are used for mapping access
|
890
|
+
and integer keys for sequence/tuple access.
|
891
|
+
|
892
|
+
Returns
|
893
|
+
-------
|
894
|
+
AttributePath
|
895
|
+
A new AttributePath extended with the item access component
|
896
|
+
|
897
|
+
Raises
|
898
|
+
------
|
899
|
+
TypeError
|
900
|
+
If the key type is incompatible with the attribute type or if the
|
901
|
+
attribute type does not support item access
|
902
|
+
"""
|
534
903
|
match _unaliased_origin(self.__attribute__):
|
535
904
|
case collections_abc.Mapping | typing.Mapping | builtins.dict:
|
536
905
|
match get_args(_unaliased(self.__attribute__)):
|
@@ -624,17 +993,63 @@ class AttributePath[Root, Attribute]:
|
|
624
993
|
@overload
|
625
994
|
def __call__(
|
626
995
|
self,
|
627
|
-
|
996
|
+
source: Root,
|
628
997
|
/,
|
629
|
-
) -> Attribute:
|
998
|
+
) -> Attribute:
|
999
|
+
"""
|
1000
|
+
Access the attribute value at this path in the source object.
|
1001
|
+
|
1002
|
+
This overload is used when retrieving a value without updating it.
|
1003
|
+
|
1004
|
+
Parameters
|
1005
|
+
----------
|
1006
|
+
source : Root
|
1007
|
+
The source object to access the attribute in
|
1008
|
+
|
1009
|
+
Returns
|
1010
|
+
-------
|
1011
|
+
Attribute
|
1012
|
+
The attribute value at this path
|
1013
|
+
|
1014
|
+
Raises
|
1015
|
+
------
|
1016
|
+
AttributeError
|
1017
|
+
If any component in the path doesn't exist
|
1018
|
+
TypeError
|
1019
|
+
If any component in the path is of the wrong type
|
1020
|
+
"""
|
630
1021
|
|
631
1022
|
@overload
|
632
1023
|
def __call__(
|
633
1024
|
self,
|
634
|
-
|
1025
|
+
source: Root,
|
635
1026
|
/,
|
636
1027
|
updated: Attribute,
|
637
|
-
) -> Root:
|
1028
|
+
) -> Root:
|
1029
|
+
"""
|
1030
|
+
Create a new root object with an updated attribute value at this path.
|
1031
|
+
|
1032
|
+
This overload is used when updating a value.
|
1033
|
+
|
1034
|
+
Parameters
|
1035
|
+
----------
|
1036
|
+
source : Root
|
1037
|
+
The source object to update
|
1038
|
+
updated : Attribute
|
1039
|
+
The new value to set at this path
|
1040
|
+
|
1041
|
+
Returns
|
1042
|
+
-------
|
1043
|
+
Root
|
1044
|
+
A new root object with the updated attribute value
|
1045
|
+
|
1046
|
+
Raises
|
1047
|
+
------
|
1048
|
+
AttributeError
|
1049
|
+
If any component in the path doesn't exist
|
1050
|
+
TypeError
|
1051
|
+
If any component in the path is of the wrong type
|
1052
|
+
"""
|
638
1053
|
|
639
1054
|
def __call__(
|
640
1055
|
self,
|