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/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
- if current:
155
- return f"{current}.{self._name}"
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
- root: Root,
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
- root: Root,
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,