haiway 0.19.5__py3-none-any.whl → 0.20.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/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
@@ -332,10 +551,35 @@ class State(metaclass=StateMeta):
332
551
  case _:
333
552
  raise TypeError(f"Expected '{cls.__name__}', received '{type(value).__name__}'")
334
553
 
554
+ @classmethod
555
+ def from_mapping(
556
+ cls,
557
+ value: Mapping[str, Any],
558
+ /,
559
+ ) -> Self:
560
+ return cls(**value)
561
+
335
562
  def __init__(
336
563
  self,
337
564
  **kwargs: Any,
338
565
  ) -> None:
566
+ """
567
+ Initialize a new State instance.
568
+
569
+ Creates a new instance with the provided attribute values.
570
+ Attributes not specified will use their default values.
571
+ All attributes are validated according to their type annotations.
572
+
573
+ Parameters
574
+ ----------
575
+ **kwargs : Any
576
+ Attribute values for the new instance
577
+
578
+ Raises
579
+ ------
580
+ Exception
581
+ If validation fails for any attribute
582
+ """
339
583
  for name, attribute in self.__ATTRIBUTES__.items():
340
584
  object.__setattr__(
341
585
  self, # pyright: ignore[reportUnknownArgumentType]
@@ -354,6 +598,26 @@ class State(metaclass=StateMeta):
354
598
  /,
355
599
  value: Value,
356
600
  ) -> Self:
601
+ """
602
+ Create a new instance with an updated value at the specified path.
603
+
604
+ Parameters
605
+ ----------
606
+ path : AttributePath[Self, Value] | Value
607
+ An attribute path created with Class._.attribute syntax
608
+ value : Value
609
+ The new value for the specified attribute
610
+
611
+ Returns
612
+ -------
613
+ Self
614
+ A new instance with the updated value
615
+
616
+ Raises
617
+ ------
618
+ AssertionError
619
+ If path is not an AttributePath
620
+ """
357
621
  assert isinstance( # nosec: B101
358
622
  path, AttributePath
359
623
  ), "Prepare parameter path by using Self._.path.to.property or explicitly"
@@ -364,15 +628,52 @@ class State(metaclass=StateMeta):
364
628
  self,
365
629
  **kwargs: Any,
366
630
  ) -> Self:
631
+ """
632
+ Create a new instance with updated attribute values.
633
+
634
+ This method creates a new instance with the same attribute values as this
635
+ instance, but with any provided values updated.
636
+
637
+ Parameters
638
+ ----------
639
+ **kwargs : Any
640
+ New values for attributes to update
641
+
642
+ Returns
643
+ -------
644
+ Self
645
+ A new instance with updated values
646
+ """
367
647
  return self.__replace__(**kwargs)
368
648
 
369
649
  def to_str(self) -> str:
650
+ """
651
+ Convert this instance to a string representation.
652
+
653
+ Returns
654
+ -------
655
+ str
656
+ A string representation of this instance
657
+ """
370
658
  return self.__str__()
371
659
 
372
660
  def to_mapping(
373
661
  self,
374
662
  recursive: bool = False,
375
663
  ) -> Mapping[str, Any]:
664
+ """
665
+ Convert this instance to a mapping of attribute names to values.
666
+
667
+ Parameters
668
+ ----------
669
+ recursive : bool, default=False
670
+ If True, nested State instances are also converted to mappings
671
+
672
+ Returns
673
+ -------
674
+ Mapping[str, Any]
675
+ A mapping of attribute names to values
676
+ """
376
677
  dict_result: dict[str, Any] = {}
377
678
  for key in self.__ATTRIBUTES__.keys():
378
679
  value: Any | Missing = getattr(self, key, MISSING)
@@ -385,6 +686,14 @@ class State(metaclass=StateMeta):
385
686
  return dict_result
386
687
 
387
688
  def __str__(self) -> str:
689
+ """
690
+ Get a string representation of this instance.
691
+
692
+ Returns
693
+ -------
694
+ str
695
+ A string representation in the format "ClassName(attr1: value1, attr2: value2)"
696
+ """
388
697
  attributes: str = ", ".join([f"{key}: {value}" for key, value in vars(self).items()])
389
698
  return f"{self.__class__.__name__}({attributes})"
390
699
 
@@ -395,6 +704,22 @@ class State(metaclass=StateMeta):
395
704
  self,
396
705
  other: Any,
397
706
  ) -> bool:
707
+ """
708
+ Check if this instance is equal to another object.
709
+
710
+ Two State instances are considered equal if they are instances of the
711
+ same class or subclass and have equal values for all attributes.
712
+
713
+ Parameters
714
+ ----------
715
+ other : Any
716
+ The object to compare with
717
+
718
+ Returns
719
+ -------
720
+ bool
721
+ True if the objects are equal, False otherwise
722
+ """
398
723
  if not issubclass(other.__class__, self.__class__):
399
724
  return False
400
725
 
@@ -423,18 +748,59 @@ class State(metaclass=StateMeta):
423
748
  )
424
749
 
425
750
  def __copy__(self) -> Self:
751
+ """
752
+ Create a shallow copy of this instance.
753
+
754
+ Since State is immutable, this returns the instance itself.
755
+
756
+ Returns
757
+ -------
758
+ Self
759
+ This instance
760
+ """
426
761
  return self # State is immutable, no need to provide an actual copy
427
762
 
428
763
  def __deepcopy__(
429
764
  self,
430
765
  memo: dict[int, Any] | None,
431
766
  ) -> Self:
767
+ """
768
+ Create a deep copy of this instance.
769
+
770
+ Since State is immutable, this returns the instance itself.
771
+
772
+ Parameters
773
+ ----------
774
+ memo : dict[int, Any] | None
775
+ Memoization dictionary for already copied objects
776
+
777
+ Returns
778
+ -------
779
+ Self
780
+ This instance
781
+ """
432
782
  return self # State is immutable, no need to provide an actual copy
433
783
 
434
784
  def __replace__(
435
785
  self,
436
786
  **kwargs: Any,
437
787
  ) -> Self:
788
+ """
789
+ Create a new instance with replaced attribute values.
790
+
791
+ This internal method is used by updated() to create a new instance
792
+ with updated values.
793
+
794
+ Parameters
795
+ ----------
796
+ **kwargs : Any
797
+ New values for attributes to replace
798
+
799
+ Returns
800
+ -------
801
+ Self
802
+ A new instance with replaced values
803
+ """
438
804
  return self.__class__(
439
805
  **{
440
806
  **vars(self),