pulse-framework 0.1.72__py3-none-any.whl → 0.1.73__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.
@@ -5,223 +5,23 @@ This module provides the base State class and reactive property system
5
5
  that enables automatic re-rendering when state changes.
6
6
  """
7
7
 
8
- import inspect
9
- from abc import ABC, ABCMeta, abstractmethod
10
- from collections.abc import Callable, Iterator
8
+ import sys
9
+ from abc import ABC, ABCMeta
10
+ from collections.abc import Iterator
11
11
  from enum import IntEnum
12
- from typing import Any, Generic, Never, TypeVar, override
12
+ from types import SimpleNamespace
13
+ from typing import Any, get_type_hints, override
13
14
 
14
15
  from pulse.helpers import Disposable
15
- from pulse.reactive import (
16
- AsyncEffect,
17
- Computed,
18
- Effect,
19
- Scope,
20
- Signal,
21
- )
16
+ from pulse.reactive import Computed, Effect, Scope, Signal
22
17
  from pulse.reactive_extensions import ReactiveProperty
23
-
24
- T = TypeVar("T")
25
-
26
-
27
- class StateProperty(ReactiveProperty[Any]):
28
- """
29
- Descriptor for reactive properties on State classes.
30
-
31
- StateProperty wraps a Signal and provides automatic reactivity for
32
- class attributes. When a property is read, it subscribes to the underlying
33
- Signal. When written, it updates the Signal and triggers re-renders.
34
-
35
- This class is typically not used directly. Instead, declare typed attributes
36
- on a State subclass, and the StateMeta metaclass will automatically convert
37
- them into StateProperty instances.
38
-
39
- Example:
40
-
41
- ```python
42
- class MyState(ps.State):
43
- count: int = 0 # Automatically becomes a StateProperty
44
- name: str = "default"
45
-
46
- state = MyState()
47
- state.count = 5 # Updates the underlying Signal
48
- print(state.count) # Reads from the Signal, subscribes to changes
49
- ```
50
- """
51
-
52
- pass
53
-
54
-
55
- class InitializableProperty(ABC):
56
- @abstractmethod
57
- def initialize(self, state: "State", name: str) -> Any: ...
58
-
59
-
60
- class ComputedProperty(Generic[T]):
61
- """
62
- Descriptor for computed (derived) properties on State classes.
63
-
64
- ComputedProperty wraps a method that derives its value from other reactive
65
- properties. The computed value is cached and only recalculated when its
66
- dependencies change. Reading a computed property subscribes to it.
67
-
68
- Created automatically when using the @ps.computed decorator on a State method.
69
-
70
- Args:
71
- name: The property name (used for debugging and the private storage key).
72
- fn: The method that computes the value. Must take only `self` as argument.
73
-
74
- Example:
75
-
76
- ```python
77
- class MyState(ps.State):
78
- count: int = 0
79
-
80
- @ps.computed
81
- def doubled(self):
82
- return self.count * 2
83
-
84
- state = MyState()
85
- print(state.doubled) # 0
86
- state.count = 5
87
- print(state.doubled) # 10 (automatically recomputed)
88
- ```
89
- """
90
-
91
- name: str
92
- private_name: str
93
- fn: "Callable[[State], T]"
94
-
95
- def __init__(self, name: str, fn: "Callable[[State], T]"):
96
- self.name = name
97
- self.private_name = f"__computed_{name}"
98
- # The computed_template holds the original method
99
- self.fn = fn
100
-
101
- def get_computed(self, obj: Any) -> Computed[T]:
102
- if not isinstance(obj, State):
103
- raise ValueError(
104
- f"Computed property {self.name} defined on a non-State class"
105
- )
106
- if not hasattr(obj, self.private_name):
107
- # Create the computed on first access for this instance
108
- bound_method = self.fn.__get__(obj, obj.__class__)
109
- new_computed = Computed(
110
- bound_method,
111
- name=f"{obj.__class__.__name__}.{self.name}",
112
- )
113
- setattr(obj, self.private_name, new_computed)
114
- return getattr(obj, self.private_name)
115
-
116
- def __get__(self, obj: Any, objtype: Any = None) -> T:
117
- if obj is None:
118
- return self # pyright: ignore[reportReturnType]
119
-
120
- return self.get_computed(obj).read()
121
-
122
- def __set__(self, obj: Any, value: Any) -> Never:
123
- raise AttributeError(f"Cannot set computed property '{self.name}'")
124
-
125
-
126
- class StateEffect(Generic[T], InitializableProperty):
127
- """
128
- Descriptor for side effects on State classes.
129
-
130
- StateEffect wraps a method that performs side effects when its dependencies
131
- change. The effect is initialized when the State instance is created and
132
- disposed when the State is disposed.
133
-
134
- Created automatically when using the @ps.effect decorator on a State method.
135
- Supports both sync and async methods.
136
-
137
- Args:
138
- fn: The effect function. Must take only `self` as argument.
139
- Can return a cleanup function that runs before the next execution
140
- or when the effect is disposed.
141
- name: Debug name for the effect. Defaults to "ClassName.method_name".
142
- immediate: If True, run synchronously when scheduled (sync effects only).
143
- lazy: If True, don't run on creation; wait for first dependency change.
144
- on_error: Callback for handling errors during effect execution.
145
- deps: Explicit dependencies. If provided, auto-tracking is disabled.
146
- interval: Re-run interval in seconds for polling effects.
147
-
148
- Example:
149
-
150
- ```python
151
- class MyState(ps.State):
152
- count: int = 0
153
-
154
- @ps.effect
155
- def log_count(self):
156
- print(f"Count changed to: {self.count}")
157
-
158
- @ps.effect
159
- async def fetch_data(self):
160
- data = await api.fetch(self.query)
161
- self.data = data
162
-
163
- @ps.effect
164
- def subscribe(self):
165
- unsub = event_bus.subscribe(self.handle_event)
166
- return unsub # Cleanup function
167
- ```
168
- """
169
-
170
- fn: "Callable[[State], T]"
171
- name: str | None
172
- immediate: bool
173
- on_error: "Callable[[Exception], None] | None"
174
- lazy: bool
175
- deps: "list[Signal[Any] | Computed[Any]] | None"
176
- update_deps: bool | None
177
- interval: float | None
178
-
179
- def __init__(
180
- self,
181
- fn: "Callable[[State], T]",
182
- name: str | None = None,
183
- immediate: bool = False,
184
- lazy: bool = False,
185
- on_error: "Callable[[Exception], None] | None" = None,
186
- deps: "list[Signal[Any] | Computed[Any]] | None" = None,
187
- update_deps: bool | None = None,
188
- interval: float | None = None,
189
- ):
190
- self.fn = fn
191
- self.name = name
192
- self.immediate = immediate
193
- self.on_error = on_error
194
- self.lazy = lazy
195
- self.deps = deps
196
- self.update_deps = update_deps
197
- self.interval = interval
198
-
199
- @override
200
- def initialize(self, state: "State", name: str):
201
- bound_method = self.fn.__get__(state, state.__class__)
202
- # Select sync/async effect type based on bound method
203
- if inspect.iscoroutinefunction(bound_method):
204
- effect: Effect = AsyncEffect(
205
- bound_method, # type: ignore[arg-type]
206
- name=self.name or f"{state.__class__.__name__}.{name}",
207
- lazy=self.lazy,
208
- on_error=self.on_error,
209
- deps=self.deps,
210
- update_deps=self.update_deps,
211
- interval=self.interval,
212
- )
213
- else:
214
- effect = Effect(
215
- bound_method, # type: ignore[arg-type]
216
- name=self.name or f"{state.__class__.__name__}.{name}",
217
- immediate=self.immediate,
218
- lazy=self.lazy,
219
- on_error=self.on_error,
220
- deps=self.deps,
221
- update_deps=self.update_deps,
222
- interval=self.interval,
223
- )
224
- setattr(state, name, effect)
18
+ from pulse.state.property import (
19
+ ComputedProperty,
20
+ InitializableProperty,
21
+ StateEffect,
22
+ StateProperty,
23
+ )
24
+ from pulse.state.query_param import QueryParam, QueryParamProperty, extract_query_param
225
25
 
226
26
 
227
27
  class StateMeta(ABCMeta):
@@ -258,18 +58,62 @@ class StateMeta(ABCMeta):
258
58
  namespace: dict[str, Any],
259
59
  **kwargs: Any,
260
60
  ):
261
- annotations = namespace.get("__annotations__", {})
61
+ declared_annotations = dict(namespace.get("__annotations__", {}))
62
+ cls = super().__new__(mcs, name, bases, namespace)
63
+ resolved_annotations: dict[str, Any] = {}
64
+ if declared_annotations:
65
+ module = sys.modules.get(cls.__module__)
66
+ globalns = module.__dict__ if module else {}
67
+ if "QueryParam" not in globalns:
68
+ globalns["QueryParam"] = QueryParam
69
+ localns = dict(cls.__dict__)
70
+ try:
71
+ hints = get_type_hints(
72
+ cls,
73
+ globalns=globalns,
74
+ localns=localns,
75
+ )
76
+ except Exception:
77
+ hints = None
78
+ if hints is not None:
79
+ for key, value in declared_annotations.items():
80
+ resolved_annotations[key] = hints.get(key, value)
81
+ else:
82
+ for key, value in declared_annotations.items():
83
+ try:
84
+ holder = SimpleNamespace(__annotations__={key: value})
85
+ resolved = get_type_hints(
86
+ holder,
87
+ globalns=globalns,
88
+ localns=localns,
89
+ ).get(key, value)
90
+ except Exception:
91
+ resolved = value
92
+ resolved_annotations[key] = resolved
262
93
 
263
94
  # 1) Turn annotated fields into StateProperty descriptors
264
- for attr_name in annotations:
95
+ for attr_name, annotation in resolved_annotations.items():
265
96
  # Do not wrap private/dunder attributes as reactive
266
97
  if attr_name.startswith("_"):
267
98
  continue
268
- default_value = namespace.get(attr_name)
269
- namespace[attr_name] = StateProperty(attr_name, default_value)
99
+ default_value = cls.__dict__.get(attr_name)
100
+ value_type, is_query_param = extract_query_param(annotation)
101
+ if is_query_param:
102
+ cls.__annotations__[attr_name] = value_type
103
+ prop = QueryParamProperty(
104
+ attr_name,
105
+ default_value,
106
+ value_type,
107
+ )
108
+ setattr(cls, attr_name, prop)
109
+ prop.__set_name__(cls, attr_name)
110
+ else:
111
+ prop = StateProperty(attr_name, default_value)
112
+ setattr(cls, attr_name, prop)
113
+ prop.__set_name__(cls, attr_name)
270
114
 
271
115
  # 2) Turn non-annotated plain values into StateProperty descriptors
272
- for attr_name, value in list(namespace.items()):
116
+ for attr_name, value in list(cls.__dict__.items()):
273
117
  # Do not wrap private/dunder attributes as reactive
274
118
  if attr_name.startswith("_"):
275
119
  continue
@@ -285,9 +129,11 @@ class StateMeta(ABCMeta):
285
129
  ):
286
130
  continue
287
131
  # Convert plain class var into a StateProperty
288
- namespace[attr_name] = StateProperty(attr_name, value)
132
+ prop = StateProperty(attr_name, value)
133
+ setattr(cls, attr_name, prop)
134
+ prop.__set_name__(cls, attr_name)
289
135
 
290
- return super().__new__(mcs, name, bases, namespace)
136
+ return cls
291
137
 
292
138
  @override
293
139
  def __call__(cls, *args: Any, **kwargs: Any):
pulse/transpiler/nodes.py CHANGED
@@ -1150,6 +1150,15 @@ class Unary(Expr):
1150
1150
  out.append(" ")
1151
1151
  else:
1152
1152
  out.append(self.op)
1153
+ if (
1154
+ self.op in {"+", "-"}
1155
+ and isinstance(self.operand, Unary)
1156
+ and self.operand.op == self.op
1157
+ ):
1158
+ out.append("(")
1159
+ self.operand.emit(out)
1160
+ out.append(")")
1161
+ return
1153
1162
  _emit_paren(self.operand, self.op, "unary", out)
1154
1163
 
1155
1164
  @override
@@ -1574,17 +1583,32 @@ class Assign(Stmt):
1574
1583
  op: None for =, or "+", "-", etc. for augmented assignment
1575
1584
  """
1576
1585
 
1577
- target: str
1586
+ target: str | Identifier | Member | Subscript
1578
1587
  value: Expr
1579
1588
  declare: Lit["let", "const"] | None = None
1580
1589
  op: str | None = None # For augmented: +=, -=, etc.
1581
1590
 
1591
+ @staticmethod
1592
+ def _validate_target(target: object) -> None:
1593
+ if not isinstance(target, (str, Identifier, Member, Subscript)):
1594
+ raise TypeError(
1595
+ "Assign target must be str, Identifier, Member, or Subscript; "
1596
+ + f"got {type(target).__name__}: {target!r}"
1597
+ )
1598
+
1599
+ def __post_init__(self) -> None:
1600
+ self._validate_target(self.target)
1601
+
1582
1602
  @override
1583
1603
  def emit(self, out: list[str]) -> None:
1604
+ self._validate_target(self.target)
1584
1605
  if self.declare:
1585
1606
  out.append(self.declare)
1586
1607
  out.append(" ")
1587
- out.append(self.target)
1608
+ if isinstance(self.target, str):
1609
+ out.append(self.target)
1610
+ else:
1611
+ _emit_primary(self.target, out)
1588
1612
  if self.op:
1589
1613
  out.append(" ")
1590
1614
  out.append(self.op)
@@ -253,6 +253,10 @@ class Transpiler:
253
253
  return Block([])
254
254
 
255
255
  if isinstance(node, ast.AugAssign):
256
+ if isinstance(node.target, ast.Subscript):
257
+ return self._emit_augmented_subscript_assign(node)
258
+ if isinstance(node.target, ast.Attribute):
259
+ return self._emit_augmented_attribute_assign(node)
256
260
  if not isinstance(node.target, ast.Name):
257
261
  raise TranspileError(
258
262
  "Only simple augmented assignments supported", node=node
@@ -278,6 +282,12 @@ class Transpiler:
278
282
  if isinstance(target_node, (ast.Tuple, ast.List)):
279
283
  return self._emit_unpacking_assign(target_node, node.value)
280
284
 
285
+ if isinstance(target_node, ast.Subscript):
286
+ return self._emit_subscript_assign(target_node, node.value)
287
+
288
+ if isinstance(target_node, ast.Attribute):
289
+ return self._emit_attribute_assign(target_node, node.value)
290
+
281
291
  if not isinstance(target_node, ast.Name):
282
292
  raise TranspileError(
283
293
  "Only simple assignments to local names supported", node=node
@@ -356,6 +366,82 @@ class Transpiler:
356
366
 
357
367
  return StmtSequence(stmts)
358
368
 
369
+ def _emit_subscript_assign(self, target: ast.Subscript, value: ast.expr) -> Stmt:
370
+ """Emit subscript assignment: obj[key] = value"""
371
+ if isinstance(target.slice, ast.Tuple):
372
+ raise TranspileError(
373
+ "Multiple indices not supported in subscript", node=target.slice
374
+ )
375
+ if isinstance(target.slice, ast.Slice):
376
+ raise TranspileError("Slice assignment not supported", node=target.slice)
377
+ obj_expr = self.emit_expr(target.value)
378
+ target_expr = obj_expr.transpile_subscript(target.slice, self)
379
+ if not isinstance(target_expr, (Identifier, Member, Subscript)):
380
+ raise TranspileError(
381
+ "Only simple subscript assignments supported", node=target
382
+ )
383
+ value_expr = self.emit_expr(value)
384
+ return Assign(target_expr, value_expr)
385
+
386
+ def _emit_attribute_assign(self, target: ast.Attribute, value: ast.expr) -> Stmt:
387
+ """Emit attribute assignment: obj.attr = value"""
388
+ obj_expr = self.emit_expr(target.value)
389
+ value_expr = self.emit_expr(value)
390
+ target_expr = obj_expr.transpile_getattr(target.attr, self)
391
+ if not isinstance(target_expr, (Identifier, Member, Subscript)):
392
+ raise TranspileError(
393
+ "Only simple attribute assignments supported", node=target
394
+ )
395
+ return Assign(target_expr, value_expr)
396
+
397
+ def _emit_augmented_subscript_assign(self, node: ast.AugAssign) -> Stmt:
398
+ """Emit augmented subscript assignment: arr[i] += x"""
399
+ target = node.target
400
+ assert isinstance(target, ast.Subscript)
401
+
402
+ if isinstance(target.slice, ast.Tuple):
403
+ raise TranspileError(
404
+ "Multiple indices not supported in subscript", node=target.slice
405
+ )
406
+ if isinstance(target.slice, ast.Slice):
407
+ raise TranspileError("Slice assignment not supported", node=target.slice)
408
+
409
+ obj_expr = self.emit_expr(target.value)
410
+ op_type = type(node.op)
411
+ if op_type not in ALLOWED_BINOPS:
412
+ raise TranspileError(
413
+ f"Unsupported augmented assignment operator: {op_type.__name__}",
414
+ node=node,
415
+ )
416
+ target_expr = obj_expr.transpile_subscript(target.slice, self)
417
+ if not isinstance(target_expr, (Identifier, Member, Subscript)):
418
+ raise TranspileError(
419
+ "Only simple subscript assignments supported", node=target
420
+ )
421
+ value_expr = self.emit_expr(node.value)
422
+ return Assign(target_expr, value_expr, op=ALLOWED_BINOPS[op_type])
423
+
424
+ def _emit_augmented_attribute_assign(self, node: ast.AugAssign) -> Stmt:
425
+ """Emit augmented attribute assignment: obj.attr += x"""
426
+ target = node.target
427
+ assert isinstance(target, ast.Attribute)
428
+
429
+ obj_expr = self.emit_expr(target.value)
430
+ op_type = type(node.op)
431
+ if op_type not in ALLOWED_BINOPS:
432
+ raise TranspileError(
433
+ f"Unsupported augmented assignment operator: {op_type.__name__}",
434
+ node=node,
435
+ )
436
+
437
+ target_expr = obj_expr.transpile_getattr(target.attr, self)
438
+ if not isinstance(target_expr, (Identifier, Member, Subscript)):
439
+ raise TranspileError(
440
+ "Only simple attribute assignments supported", node=target
441
+ )
442
+ value_expr = self.emit_expr(node.value)
443
+ return Assign(target_expr, value_expr, op=ALLOWED_BINOPS[op_type])
444
+
359
445
  def _emit_for_loop(self, node: ast.For) -> Stmt:
360
446
  """Emit a for loop."""
361
447
  # Handle tuple unpacking in for target
@@ -772,11 +858,6 @@ class Transpiler:
772
858
  if isinstance(node.slice, ast.Slice):
773
859
  return self._emit_slice(value, node.slice)
774
860
 
775
- # Negative index: use .at()
776
- if isinstance(node.slice, ast.UnaryOp) and isinstance(node.slice.op, ast.USub):
777
- idx_expr = self.emit_expr(node.slice.operand)
778
- return Call(Member(value, "at"), [Unary("-", idx_expr)])
779
-
780
861
  # Delegate to Expr.transpile_subscript (default returns Subscript)
781
862
  return value.transpile_subscript(node.slice, self)
782
863
 
pulse/transpiler/vdom.py CHANGED
@@ -157,7 +157,7 @@ CallbackPlaceholder: TypeAlias = Literal["$cb"]
157
157
 
158
158
  The callback invocation target is derived from the element path + prop name.
159
159
  Because the prop name is known from `VDOMElement.eval`, the placeholder can be a
160
- single sentinel string.
160
+ single sentinel string. Debounced callbacks use "$cb:<delay_ms>" in the wire format.
161
161
  """
162
162
 
163
163
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pulse-framework
3
- Version: 0.1.72
3
+ Version: 0.1.73
4
4
  Summary: Pulse - Full-stack framework for building real-time React applications in Python
5
5
  Requires-Dist: fastapi>=0.128.0
6
6
  Requires-Dist: uvicorn>=0.24.0
@@ -15,7 +15,7 @@ Requires-Dist: urllib3>=2.6.3
15
15
  Requires-Dist: watchfiles>=1.1.0
16
16
  Requires-Dist: httpx>=0.28.1
17
17
  Requires-Dist: aiohttp>=3.12.0
18
- Requires-Python: >=3.11
18
+ Requires-Python: >=3.12
19
19
  Description-Content-Type: text/markdown
20
20
 
21
21
  # Pulse Python
@@ -1,4 +1,4 @@
1
- pulse/__init__.py,sha256=VV1oD_y0xnhQ_yBj1XqGWjIaw4Hi-xGHYQfEMv-Z1Bo,32453
1
+ pulse/__init__.py,sha256=GUPD8THGENBgHsZvixIP8wOiEtdEMtIVpr8N8MpuRL4,32675
2
2
  pulse/_examples.py,sha256=dFuhD2EVXsbvAeexoG57s4VuN4gWLaTMOEMNYvlPm9A,561
3
3
  pulse/app.py,sha256=Bi94rYG-MoldkGa-_CscLMstjTEV8BHVAgDbvapRGzI,36167
4
4
  pulse/channel.py,sha256=ePpvD2mDbddt_LMxxxDjNRgOLbVi8Ed6TmJFgkrALB0,15790
@@ -10,7 +10,7 @@ pulse/cli/helpers.py,sha256=XXRRXeGFgeq-jbp0QGFFVq_aGg_Kp7_AkYsTK8LfSdg,7810
10
10
  pulse/cli/logging.py,sha256=3uuB1dqI-lHJkodNUURN6UMWdKF5UQ9spNG-hBG7bA4,2516
11
11
  pulse/cli/models.py,sha256=NBV5byBDNoAQSk0vKwibLjoxuA85XBYIyOVJn64L8oU,858
12
12
  pulse/cli/packages.py,sha256=DSnhxz61AoLVvBre3c0dnVYSpiKPI0rKFq4YmgM_VlA,7220
13
- pulse/cli/processes.py,sha256=BFSKTRoNlCTAi3lDAjKHsKN1c-S032eol0tFd84pdVQ,7566
13
+ pulse/cli/processes.py,sha256=rtMTpl0e3Wx1IL_Kx7b6Loflst1HXW4Wm7XGk6N2-xc,7634
14
14
  pulse/cli/secrets.py,sha256=dNfQe6AzSYhZuWveesjCRHIbvaPd3-F9lEJ-kZA7ROw,921
15
15
  pulse/cli/uvicorn_log_config.py,sha256=f7ikDc5foXh3TmFMrnfnW8yev48ZAdlo8F4F_aMVoVk,2391
16
16
  pulse/code_analysis.py,sha256=NBba_7VtOxZYMyfku_p-bWkG0O_1pi1AxcaNyVM1nmY,1024
@@ -28,7 +28,8 @@ pulse/components/if_.py,sha256=5IOq3R70B-JdI-fvDNYDyAaSEtO8L5OaiqHp-jUn-Kw,2153
28
28
  pulse/components/react_router.py,sha256=Nl6juntLSowFc38q7g_VMdcc4ju6lj8DUhpNR2NuOKQ,2934
29
29
  pulse/context.py,sha256=odTQlOhVRIwNvtatrmPe_Fd8Zk0rMcbcqQHBxvWYH5o,2677
30
30
  pulse/cookies.py,sha256=ozfdBKExdbpeM5ileIA1z8BZA5hoUrZ5_iO9fIMrgRk,8768
31
- pulse/decorators.py,sha256=BtNaisiqaJvlCuoqBgqQWbeFklDYLDrO1o1MRzFlybY,9932
31
+ pulse/debounce.py,sha256=IUi5TAjPfavGXKJ2oQoJPwvBmeqnEf0Tsu29FFdFYJk,2262
32
+ pulse/decorators.py,sha256=Lskni9Keqfb-xmUliFQe5x-4AcNqrwdvoh0kuz2fXa0,9958
32
33
  pulse/dom/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
34
  pulse/dom/elements.py,sha256=YHXkVpfMAC4-0o61fK-E0LGTOM3KMCtBfpHHAwLx7dw,23241
34
35
  pulse/dom/events.py,sha256=yHioH8Y-b7raOaZ43JuCxk2lUBryUAcDSc-5VhXtiSI,14699
@@ -41,12 +42,12 @@ pulse/forms.py,sha256=0irpErCMJk8-YO1BrxjMkFb8dnvSz3rfzTywmMeib7g,14042
41
42
  pulse/helpers.py,sha256=imVA9XzkYrYmdeqEdD7ot0g99adL1SVKv5bQGkKb-aQ,9504
42
43
  pulse/hooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
44
  pulse/hooks/core.py,sha256=tDEcB_CTD4yI5bNKn7CtB40sRKIanGNqPD5_qLgSzf4,10982
44
- pulse/hooks/effects.py,sha256=IRb37K9p0egBqlPvAnlW5dPohPvQGzmcyLSgbuNk5FU,3005
45
+ pulse/hooks/effects.py,sha256=KjAS8CgDVWhM6vbEpzL87TKK9q7K_MVZFt7k_f0HF9M,2961
45
46
  pulse/hooks/init.py,sha256=PhjVBLlHpodPzVrRcx_QfEUrsx_6gEX_NuVhe6ojiYI,17834
46
- pulse/hooks/runtime.py,sha256=nxqwl8ByclIh5i3ZcCN6L3f0X3ZpwOBWajLb_FSbcDw,11839
47
- pulse/hooks/setup.py,sha256=ILbn2v6UFJPFBOWnJef1X2A9JLpIagEHN9Mx0d9947I,6925
48
- pulse/hooks/stable.py,sha256=y3V6gFEs7XU1ru92krOpjv2_rj_EzqUpy4lncSyLkQ4,3707
49
- pulse/hooks/state.py,sha256=ORaYNoPhqpwFBlKleBOSNZebJgFHtt7oMOuHxrmnBHk,5288
47
+ pulse/hooks/runtime.py,sha256=ogrm4Prvr9ZNaBb5bLfZBHrzbJuYe1zKpIPkbdnIzsw,12286
48
+ pulse/hooks/setup.py,sha256=NcQPKnMV5dO0vUsWi4u9c9LB0wqFstrtiPGdvihtGiQ,6872
49
+ pulse/hooks/stable.py,sha256=hLCOl_oAbgdNwiaWwwUTk7ZsHvMqXFFozFztJZMyGbQ,3627
50
+ pulse/hooks/state.py,sha256=zRFlcOUdl-SBkJ-EzVXRLrLXzAc4-uRzgH4hD9rM1oU,5251
50
51
  pulse/js/__init__.py,sha256=tj1A6-eR5WS83UNgHb3Dw23m37oJsEuyV0ezUB6kXbg,3636
51
52
  pulse/js/__init__.pyi,sha256=WN22WsJB-XFk6auL9zklwG2Kof3zeOsc56A56dJ3MWg,3097
52
53
  pulse/js/_types.py,sha256=F4Go2JtJ2dbxq1fXpc2ablG_nyvhvHzOlZLlEv0VmyU,7421
@@ -78,24 +79,27 @@ pulse/proxy.py,sha256=c13b0fE3sq82sFo46vv0emWLQ_ePwRkI7hiPZrnQDCE,22780
78
79
  pulse/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
79
80
  pulse/queries/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
80
81
  pulse/queries/client.py,sha256=KMGT92dESMrzpLlhd701fyh7Wrs3VKmM5cZoRQ0AEzg,18994
81
- pulse/queries/common.py,sha256=tjW5pqpWvRYUy3gsj-KxhUB9c5KBOgBqRKIuKJkob9A,4278
82
+ pulse/queries/common.py,sha256=Adz_qDEnHHKAb0L2tVwSc14J0PXghhYw6B3AO2_Ud-E,4284
82
83
  pulse/queries/effect.py,sha256=1ePUi2TwP49L9LhlkKI2qV_HhIO4jKj1r5jyPaWiUn8,1508
83
- pulse/queries/infinite_query.py,sha256=QZmXN2G8r0FA7cCez5bjPxEEvWZTB2Sl5_Pob3d5E6E,50489
84
- pulse/queries/mutation.py,sha256=fhEpOZ7CuHImH4Y02QapYdTJrwe6K52-keb0d67wmms,8274
84
+ pulse/queries/infinite_query.py,sha256=YrBI1TlgQ7edBs-LbY6R57fMPz8rj23uKHYHfsv0kp4,50527
85
+ pulse/queries/mutation.py,sha256=CrVFPrLJ7TNYOP5Sa-U9JwropxLfTFSxJiKZVNOE9lM,8312
85
86
  pulse/queries/protocol.py,sha256=TOrUiI4QK55xuh0i4ch1u96apNl12QeYafkf6RVDd08,3544
86
- pulse/queries/query.py,sha256=Ap1PRKoc1U1ddhnnHgBOuNX9oUcYD9nInpq87uTLnb8,41849
87
+ pulse/queries/query.py,sha256=67hohYL1Gj2RdsNxTs-GfoN_hhpmQvpjPtPVYL2d5XI,41887
87
88
  pulse/queries/store.py,sha256=iw05_EFpyfiXv5_FV_x4aHtCo00mk0dDPFD461cajcg,3850
88
89
  pulse/react_component.py,sha256=8RLg4Bi7IcjqbnbEnp4hJpy8t1UsE7mG0UR1Q655LDk,2332
89
90
  pulse/reactive.py,sha256=GSh9wSH3THCBjDTafwWttyx7djeKBWV_KqjaKRYUNsA,31393
90
91
  pulse/reactive_extensions.py,sha256=yQ1PpdAh4kMvll7R15T72FOg8NFdG_HGBsGc63dawYk,33754
91
- pulse/render_session.py,sha256=65mhbKZ9o1vIDSRrM2p3VNPmo6Z5vn1Aoz7we5mw7P8,23417
92
- pulse/renderer.py,sha256=fjSsUvCqV12jyN7Y5XspKUfjQJJzKX-Chha5oF5PrAk,16001
92
+ pulse/render_session.py,sha256=WKWDOqtIjy9n00HxMiViI-pBHw34QOEhLgZap28BCMg,23431
93
+ pulse/renderer.py,sha256=a4gTEFZuhAc1V5uTcFFcsOREDg6ZU9-jf4Ic7qLo2CY,16902
93
94
  pulse/request.py,sha256=N0oFOLiGxpbgSgxznjvu64lG3YyOcZPKC8JFyKx6X7w,6023
94
95
  pulse/requirements.py,sha256=nMnE25Uu-TUuQd88jW7m2xwus6fD-HvXxQ9UNb7OOGc,1254
95
- pulse/routing.py,sha256=LzTITvGgaLI1w7qTDZjFwoBcWAb4O8Dz7AmXeTNYrFU,16903
96
+ pulse/routing.py,sha256=oRfVaeIrsbDR9yW9BYwxVWV3HZI7wk21yZX69IVADIU,17279
96
97
  pulse/scheduling.py,sha256=D-L5mVsbakK_-1NCq62dU6wEJpq6I_HxI4PCmR9aj9w,11714
97
- pulse/serializer.py,sha256=HmQZgxQiaCx2SL2XwmEQLd_xsk_P8XfLtGciLLLOxx0,7616
98
- pulse/state.py,sha256=VMphVpYNU1CyHMMg1_kNJO3cfqLXJPAuq9gr9RYyUAw,15922
98
+ pulse/serializer.py,sha256=Fz02bUVdpUwI4tBW44naFKBlFcujfyh5ykpwxs5XjAU,8218
99
+ pulse/state/__init__.py,sha256=oipHut-JH15hWrxeo3JpKH1YqDHNc4-xAbSqQ1Yt-Pk,29
100
+ pulse/state/property.py,sha256=pitudvCGBNqK6lePdPrs4Inyy7szo8qcR_0BI30veek,6274
101
+ pulse/state/query_param.py,sha256=7faf244_KJ3yrsBimKTBlNLg6wP96FUuWoZl40oxpyY,14478
102
+ pulse/state/state.py,sha256=_4DcB-og2s5Ui01kDx9HzpNwmAVD_pdCLB0TfYbcnzM,11603
99
103
  pulse/test_helpers.py,sha256=4iO5Ymy3SMvSjh-UaAaSdqm1I_SAJMNjdY2iYVro5f8,436
100
104
  pulse/transpiler/__init__.py,sha256=wDDnzqxgHpp_OLtcgyrJEg2jVoTnFIe3SSSTOsMDW8w,4700
101
105
  pulse/transpiler/assets.py,sha256=digd5hKYPEgLOzMtDBHULX3Adj1sfngdvnx3quQmgPY,2299
@@ -114,15 +118,15 @@ pulse/transpiler/modules/math.py,sha256=8gjvdYTMqtuOnXrvX_Lwuo0ywAdSl7cpss4TMk6m
114
118
  pulse/transpiler/modules/pulse/__init__.py,sha256=TfMsiiB53ZFlxdNl7jfCAiMZs-vSRUTxUmqzkLTj-po,91
115
119
  pulse/transpiler/modules/pulse/tags.py,sha256=FMN1mWMlnsXa2qO6VmXxUAhFn1uOfGoKPQOjH4ZPlRE,6218
116
120
  pulse/transpiler/modules/typing.py,sha256=J9QCkXE6zzwMjiprX2q1BtK-iKLIiS21sQ78JH4RSMc,1716
117
- pulse/transpiler/nodes.py,sha256=xJSUb0PfqyfvG85ZnUwZMzUs4JVeCJTw87biI2K2UC8,51742
121
+ pulse/transpiler/nodes.py,sha256=vebA81QXkWoJMygX2CHH_94azKPRATaB6eBs5wdX4rE,52420
118
122
  pulse/transpiler/py_module.py,sha256=um4BYLrbs01bpgv2LEBHTbhXXh8Bs174c3ygv5tHHOg,4410
119
- pulse/transpiler/transpiler.py,sha256=28diEp1yZTs3RsUEJZZdCv1DfzgO9WyOGI-xSHe7y_4,32562
120
- pulse/transpiler/vdom.py,sha256=Ie36iHa2bkUbui5iMClbMSFDGlKaNxI98Ux0JLPCGT4,6399
123
+ pulse/transpiler/transpiler.py,sha256=pxNFVnJB_zpUnp12cfzeOqUINAb1ZKhlU2E1gYUE6mk,35752
124
+ pulse/transpiler/vdom.py,sha256=Bf1yw10hQl8BXa6rhr5byRa5ua3qgRsVGNgEtQneA2A,6460
121
125
  pulse/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
122
126
  pulse/types/event_handler.py,sha256=psQCydj-WEtBcFU5JU4mDwvyzkW8V2O0g_VFRU2EOHI,1618
123
127
  pulse/user_session.py,sha256=nsnsMgqq2xGJZLpbHRMHUHcLrElMP8WcA4gjGMrcoBk,10208
124
128
  pulse/version.py,sha256=711vaM1jVIQPgkisGgKZqwmw019qZIsc_QTae75K2pg,1895
125
- pulse_framework-0.1.72.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
126
- pulse_framework-0.1.72.dist-info/entry_points.txt,sha256=i7aohd3QaPu5IcuGKKvsQQEiMYMe5HcF56QEsaLVO64,46
127
- pulse_framework-0.1.72.dist-info/METADATA,sha256=Vn_d62C23dTizl4uuQ05BAkhQUDrBmnELQI3OrHkkE8,8299
128
- pulse_framework-0.1.72.dist-info/RECORD,,
129
+ pulse_framework-0.1.73.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
130
+ pulse_framework-0.1.73.dist-info/entry_points.txt,sha256=i7aohd3QaPu5IcuGKKvsQQEiMYMe5HcF56QEsaLVO64,46
131
+ pulse_framework-0.1.73.dist-info/METADATA,sha256=PIqxEHW98zMJlT1C_cLNisDzX73GDvGKFW8e4UtVTfk,8299
132
+ pulse_framework-0.1.73.dist-info/RECORD,,