pulse-framework 0.1.54__py3-none-any.whl → 0.1.56__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.
Files changed (80) hide show
  1. pulse/__init__.py +5 -6
  2. pulse/app.py +144 -57
  3. pulse/channel.py +139 -7
  4. pulse/cli/cmd.py +16 -2
  5. pulse/code_analysis.py +38 -0
  6. pulse/codegen/codegen.py +61 -62
  7. pulse/codegen/templates/route.py +100 -56
  8. pulse/component.py +128 -6
  9. pulse/components/for_.py +30 -4
  10. pulse/components/if_.py +28 -5
  11. pulse/components/react_router.py +61 -3
  12. pulse/context.py +39 -5
  13. pulse/cookies.py +108 -4
  14. pulse/decorators.py +193 -24
  15. pulse/env.py +56 -2
  16. pulse/form.py +198 -5
  17. pulse/helpers.py +7 -1
  18. pulse/hooks/core.py +135 -5
  19. pulse/hooks/effects.py +61 -77
  20. pulse/hooks/init.py +60 -1
  21. pulse/hooks/runtime.py +241 -0
  22. pulse/hooks/setup.py +77 -0
  23. pulse/hooks/stable.py +58 -1
  24. pulse/hooks/state.py +107 -20
  25. pulse/js/__init__.py +41 -25
  26. pulse/js/array.py +9 -6
  27. pulse/js/console.py +15 -12
  28. pulse/js/date.py +9 -6
  29. pulse/js/document.py +5 -2
  30. pulse/js/error.py +7 -4
  31. pulse/js/json.py +9 -6
  32. pulse/js/map.py +8 -5
  33. pulse/js/math.py +9 -6
  34. pulse/js/navigator.py +5 -2
  35. pulse/js/number.py +9 -6
  36. pulse/js/obj.py +16 -13
  37. pulse/js/object.py +9 -6
  38. pulse/js/promise.py +19 -13
  39. pulse/js/pulse.py +28 -25
  40. pulse/js/react.py +190 -44
  41. pulse/js/regexp.py +7 -4
  42. pulse/js/set.py +8 -5
  43. pulse/js/string.py +9 -6
  44. pulse/js/weakmap.py +8 -5
  45. pulse/js/weakset.py +8 -5
  46. pulse/js/window.py +6 -3
  47. pulse/messages.py +5 -0
  48. pulse/middleware.py +147 -76
  49. pulse/plugin.py +76 -5
  50. pulse/queries/client.py +186 -39
  51. pulse/queries/common.py +52 -3
  52. pulse/queries/infinite_query.py +154 -2
  53. pulse/queries/mutation.py +127 -7
  54. pulse/queries/query.py +112 -11
  55. pulse/react_component.py +66 -3
  56. pulse/reactive.py +314 -30
  57. pulse/reactive_extensions.py +106 -26
  58. pulse/render_session.py +304 -173
  59. pulse/request.py +46 -11
  60. pulse/routing.py +140 -4
  61. pulse/serializer.py +71 -0
  62. pulse/state.py +177 -9
  63. pulse/test_helpers.py +15 -0
  64. pulse/transpiler/__init__.py +13 -3
  65. pulse/transpiler/assets.py +66 -0
  66. pulse/transpiler/dynamic_import.py +131 -0
  67. pulse/transpiler/emit_context.py +49 -0
  68. pulse/transpiler/function.py +6 -2
  69. pulse/transpiler/imports.py +33 -27
  70. pulse/transpiler/js_module.py +64 -8
  71. pulse/transpiler/py_module.py +1 -7
  72. pulse/transpiler/transpiler.py +4 -0
  73. pulse/user_session.py +119 -18
  74. {pulse_framework-0.1.54.dist-info → pulse_framework-0.1.56.dist-info}/METADATA +5 -5
  75. pulse_framework-0.1.56.dist-info/RECORD +127 -0
  76. pulse/js/react_dom.py +0 -30
  77. pulse/transpiler/react_component.py +0 -51
  78. pulse_framework-0.1.54.dist-info/RECORD +0 -124
  79. {pulse_framework-0.1.54.dist-info → pulse_framework-0.1.56.dist-info}/WHEEL +0 -0
  80. {pulse_framework-0.1.54.dist-info → pulse_framework-0.1.56.dist-info}/entry_points.txt +0 -0
@@ -77,11 +77,21 @@ class ReactiveDictValues(Generic[T1, T2]):
77
77
  class ReactiveDict(dict[T1, T2]):
78
78
  """A dict-like container with per-key reactivity.
79
79
 
80
- - Reading a key registers a dependency on that key's Signal
81
- - Writing a key updates only that key's Signal
82
- - Deleting a key removes the key from the mapping but preserves the Signal object
83
- (writes `None` to it) for existing subscribers
84
- - Iteration, membership checks, and len are reactive to structural changes
80
+ Reading a key registers a dependency on that key's Signal. Writing a key
81
+ updates only that key's Signal. Iteration, membership checks, and len are
82
+ reactive to structural changes.
83
+
84
+ Args:
85
+ initial: Initial key-value pairs to populate the dict.
86
+
87
+ Example:
88
+
89
+ ```python
90
+ data = ReactiveDict({"name": "Alice", "age": 30})
91
+ print(data["name"]) # "Alice" (registers dependency)
92
+ data["age"] = 31 # Updates age signal only
93
+ data.unwrap() # {"name": "Alice", "age": 31}
94
+ ```
85
95
  """
86
96
 
87
97
  __slots__ = ("_signals", "_structure") # pyright: ignore[reportUnannotatedClassAttribute]
@@ -365,7 +375,11 @@ class ReactiveDict(dict[T1, T2]):
365
375
  return result
366
376
 
367
377
  def unwrap(self) -> dict[T1, _Any]:
368
- """Return a plain dict while subscribing to contained signals."""
378
+ """Return a plain dict while subscribing to contained signals.
379
+
380
+ Returns:
381
+ A plain dict with all reactive containers recursively unwrapped.
382
+ """
369
383
  self._structure.read()
370
384
  result: dict[T1, _Any] = {}
371
385
  for key in dict.__iter__(self):
@@ -398,15 +412,24 @@ SupportsRichComparisonT = TypeVar(
398
412
 
399
413
 
400
414
  class ReactiveList(list[T1]):
401
- """A list with item-level reactivity where possible and structural change signaling.
402
-
403
- Semantics:
404
- - Index reads depend on that index's Signal
405
- - Setting an index writes to that index's Signal
406
- - Structural operations (append/insert/pop/remove/clear/extend/sort/reverse/slice assigns)
407
- trigger a structural version Signal so consumers can listen for changes that affect layout
408
- - Iteration subscribes to all item signals and structural changes
409
- - len() subscribes to structural changes
415
+ """A list with item-level reactivity and structural change signaling.
416
+
417
+ Index reads depend on that index's Signal. Setting an index writes to that
418
+ index's Signal. Structural operations (append/insert/pop/etc.) trigger a
419
+ structural version Signal. Iteration subscribes to all item signals and
420
+ structural changes. len() subscribes to structural changes.
421
+
422
+ Args:
423
+ initial: Initial items to populate the list.
424
+
425
+ Example:
426
+
427
+ ```python
428
+ items = ReactiveList([1, 2, 3])
429
+ print(items[0]) # 1 (registers dependency on index 0)
430
+ items.append(4) # Triggers structural change
431
+ items.unwrap() # [1, 2, 3, 4]
432
+ ```
410
433
  """
411
434
 
412
435
  __slots__ = ("_signals", "_structure") # pyright: ignore[reportUnannotatedClassAttribute]
@@ -528,7 +551,11 @@ class ReactiveList(list[T1]):
528
551
  return val
529
552
 
530
553
  def unwrap(self) -> list[_Any]:
531
- """Return a plain list while subscribing to element signals."""
554
+ """Return a plain list while subscribing to element signals.
555
+
556
+ Returns:
557
+ A plain list with all reactive containers recursively unwrapped.
558
+ """
532
559
  self._structure()
533
560
  return [unwrap(self[i]) for i in range(len(self._signals))]
534
561
 
@@ -640,9 +667,21 @@ class ReactiveList(list[T1]):
640
667
  class ReactiveSet(set[T1]):
641
668
  """A set with per-element membership reactivity.
642
669
 
643
- - `x in s` reads a membership Signal for element `x`
644
- - Mutations update membership Signals for affected elements
645
- - Iteration subscribes to membership signals for all elements
670
+ `x in s` reads a membership Signal for element `x`. Mutations update
671
+ membership Signals for affected elements. Iteration subscribes to
672
+ membership signals for all elements.
673
+
674
+ Args:
675
+ initial: Initial elements to populate the set.
676
+
677
+ Example:
678
+
679
+ ```python
680
+ tags = ReactiveSet({"python", "react"})
681
+ print("python" in tags) # True (registers dependency)
682
+ tags.add("typescript") # Updates membership signal
683
+ tags.unwrap() # {"python", "react", "typescript"}
684
+ ```
646
685
  """
647
686
 
648
687
  __slots__ = ("_signals",) # pyright: ignore[reportUnannotatedClassAttribute]
@@ -721,7 +760,11 @@ class ReactiveSet(set[T1]):
721
760
  self.discard(v)
722
761
 
723
762
  def unwrap(self) -> set[_Any]:
724
- """Return a plain set while subscribing to membership signals."""
763
+ """Return a plain set while subscribing to membership signals.
764
+
765
+ Returns:
766
+ A plain set with all reactive containers recursively unwrapped.
767
+ """
725
768
  result: set[_Any] = set()
726
769
  for value in set.__iter__(self):
727
770
  _ = value in self
@@ -1009,10 +1052,28 @@ def reactive(value: T1) -> T1: ...
1009
1052
  def reactive(value: _Any) -> _Any:
1010
1053
  """Wrap built-in collections in their reactive counterparts if not already reactive.
1011
1054
 
1012
- - dict -> ReactiveDict
1013
- - list -> ReactiveList
1014
- - set -> ReactiveSet
1015
- Leaves other values untouched.
1055
+ Converts:
1056
+ - dict -> ReactiveDict
1057
+ - list -> ReactiveList
1058
+ - set -> ReactiveSet
1059
+ - dataclass instance -> reactive dataclass with Signal-backed fields
1060
+
1061
+ Leaves other values (primitives, already-reactive containers) untouched.
1062
+
1063
+ Args:
1064
+ value: The value to make reactive.
1065
+
1066
+ Returns:
1067
+ The reactive version of the value, or the original if already reactive
1068
+ or not a supported collection type.
1069
+
1070
+ Example:
1071
+
1072
+ ```python
1073
+ data = reactive({"key": "value"}) # ReactiveDict
1074
+ items = reactive([1, 2, 3]) # ReactiveList
1075
+ tags = reactive({"a", "b"}) # ReactiveSet
1076
+ ```
1016
1077
  """
1017
1078
  if isinstance(value, ReactiveDict | ReactiveList | ReactiveSet):
1018
1079
  return value
@@ -1058,9 +1119,28 @@ def reactive(value: _Any) -> _Any:
1058
1119
  def unwrap(value: _Any, untrack: bool = False) -> _Any:
1059
1120
  """Recursively unwrap reactive containers into plain Python values.
1060
1121
 
1122
+ Converts:
1123
+ - Signal/Computed -> their read() value
1124
+ - ReactiveDict -> dict
1125
+ - ReactiveList -> list
1126
+ - ReactiveSet -> set
1127
+ - Other Mapping/Sequence types are recursively unwrapped
1128
+
1061
1129
  Args:
1062
- value: The value to unwrap
1063
- untrack: Whether to not track dependencies during unwrapping. Defaults to False.
1130
+ value: The value to unwrap.
1131
+ untrack: If True, don't track dependencies during unwrapping.
1132
+ Defaults to False.
1133
+
1134
+ Returns:
1135
+ A plain Python value with all reactive containers unwrapped.
1136
+
1137
+ Example:
1138
+
1139
+ ```python
1140
+ count = Signal(5)
1141
+ data = ReactiveDict({"count": count})
1142
+ unwrap(data) # {"count": 5}
1143
+ ```
1064
1144
  """
1065
1145
 
1066
1146
  def _unwrap(v: _Any) -> _Any: