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/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),