pulse-framework 0.1.75__py3-none-any.whl → 0.1.77__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.
- pulse/__init__.py +12 -0
- pulse/channel.py +6 -3
- pulse/components/for_.py +7 -8
- pulse/dom/props.py +2 -0
- pulse/js/__init__.py +3 -0
- pulse/js/__init__.pyi +11 -0
- pulse/js/_types.py +3 -0
- pulse/js/animation.py +217 -0
- pulse/js/document.py +8 -0
- pulse/js/react.py +2 -2
- pulse/refs.py +893 -0
- pulse/render_session.py +27 -0
- pulse/renderer.py +23 -1
- pulse/transpiler/vdom.py +12 -1
- {pulse_framework-0.1.75.dist-info → pulse_framework-0.1.77.dist-info}/METADATA +1 -1
- {pulse_framework-0.1.75.dist-info → pulse_framework-0.1.77.dist-info}/RECORD +18 -16
- {pulse_framework-0.1.75.dist-info → pulse_framework-0.1.77.dist-info}/WHEEL +0 -0
- {pulse_framework-0.1.75.dist-info → pulse_framework-0.1.77.dist-info}/entry_points.txt +0 -0
pulse/render_session.py
CHANGED
|
@@ -6,6 +6,7 @@ from asyncio import iscoroutine
|
|
|
6
6
|
from collections.abc import Awaitable, Callable
|
|
7
7
|
from typing import TYPE_CHECKING, Any, Literal, TypeVar, overload
|
|
8
8
|
|
|
9
|
+
from pulse.channel import Channel
|
|
9
10
|
from pulse.context import PulseContext
|
|
10
11
|
from pulse.hooks.runtime import NotFoundInterrupt, RedirectInterrupt
|
|
11
12
|
from pulse.messages import (
|
|
@@ -257,6 +258,8 @@ class RenderSession:
|
|
|
257
258
|
_send_message: Callable[[ServerMessage], Any] | None
|
|
258
259
|
_pending_api: dict[str, asyncio.Future[dict[str, Any]]]
|
|
259
260
|
_pending_js_results: dict[str, asyncio.Future[Any]]
|
|
261
|
+
_ref_channel: Channel | None
|
|
262
|
+
_ref_channels_by_route: dict[str, Channel]
|
|
260
263
|
_global_states: dict[str, State]
|
|
261
264
|
_global_queue: list[ServerMessage]
|
|
262
265
|
_tasks: TaskRegistry
|
|
@@ -290,6 +293,8 @@ class RenderSession:
|
|
|
290
293
|
self.forms = FormRegistry(self)
|
|
291
294
|
self._pending_api = {}
|
|
292
295
|
self._pending_js_results = {}
|
|
296
|
+
self._ref_channel = None
|
|
297
|
+
self._ref_channels_by_route = {}
|
|
293
298
|
self._tasks = TaskRegistry(name=f"render:{id}")
|
|
294
299
|
self._timers = TimerRegistry(tasks=self._tasks, name=f"render:{id}")
|
|
295
300
|
self.query_store = QueryStore()
|
|
@@ -479,6 +484,7 @@ class RenderSession:
|
|
|
479
484
|
return
|
|
480
485
|
try:
|
|
481
486
|
self.route_mounts.pop(path, None)
|
|
487
|
+
self._ref_channels_by_route.pop(path, None)
|
|
482
488
|
mount.dispose()
|
|
483
489
|
except Exception as e:
|
|
484
490
|
self.report_error(path, "unmount", e)
|
|
@@ -486,6 +492,7 @@ class RenderSession:
|
|
|
486
492
|
def detach(self, path: str, *, timeout: float | None = None):
|
|
487
493
|
"""Client no longer wants updates. Queue briefly, then dispose."""
|
|
488
494
|
path = ensure_absolute_path(path)
|
|
495
|
+
self._ref_channels_by_route.pop(path, None)
|
|
489
496
|
mount = self.route_mounts.get(path)
|
|
490
497
|
if not mount:
|
|
491
498
|
return
|
|
@@ -598,6 +605,8 @@ class RenderSession:
|
|
|
598
605
|
if not fut.done():
|
|
599
606
|
fut.cancel()
|
|
600
607
|
self._pending_js_results.clear()
|
|
608
|
+
self._ref_channel = None
|
|
609
|
+
self._ref_channels_by_route.clear()
|
|
601
610
|
# Close any timer that may have been scheduled during cleanup (ex: query GC)
|
|
602
611
|
self._timers.cancel_all()
|
|
603
612
|
self._global_queue = []
|
|
@@ -619,6 +628,24 @@ class RenderSession:
|
|
|
619
628
|
self._global_states[key] = inst
|
|
620
629
|
return inst
|
|
621
630
|
|
|
631
|
+
def get_ref_channel(self) -> Channel:
|
|
632
|
+
ctx = PulseContext.get()
|
|
633
|
+
if ctx.route is None:
|
|
634
|
+
if self._ref_channel is not None and not self._ref_channel.closed:
|
|
635
|
+
return self._ref_channel
|
|
636
|
+
self._ref_channel = self.channels.create(bind_route=False)
|
|
637
|
+
return self._ref_channel
|
|
638
|
+
|
|
639
|
+
route_path = ctx.route.pulse_route.unique_path()
|
|
640
|
+
channel = self._ref_channels_by_route.get(route_path)
|
|
641
|
+
if channel is not None and channel.closed:
|
|
642
|
+
self._ref_channels_by_route.pop(route_path, None)
|
|
643
|
+
channel = None
|
|
644
|
+
if channel is None:
|
|
645
|
+
channel = self.channels.create(bind_route=True)
|
|
646
|
+
self._ref_channels_by_route[route_path] = channel
|
|
647
|
+
return channel
|
|
648
|
+
|
|
622
649
|
def flush(self):
|
|
623
650
|
with PulseContext.update(render=self):
|
|
624
651
|
flush_effects()
|
pulse/renderer.py
CHANGED
|
@@ -9,6 +9,7 @@ from typing import Any, NamedTuple, TypeAlias, cast
|
|
|
9
9
|
from pulse.debounce import Debounced
|
|
10
10
|
from pulse.helpers import values_equal
|
|
11
11
|
from pulse.hooks.core import HookContext
|
|
12
|
+
from pulse.refs import RefHandle
|
|
12
13
|
from pulse.transpiler import Import
|
|
13
14
|
from pulse.transpiler.function import Constant, JsFunction, JsxFunction
|
|
14
15
|
from pulse.transpiler.nodes import (
|
|
@@ -34,7 +35,7 @@ from pulse.transpiler.vdom import (
|
|
|
34
35
|
VDOMPropValue,
|
|
35
36
|
)
|
|
36
37
|
|
|
37
|
-
PropValue: TypeAlias = Node | Callable[..., Any] | Debounced[Any, Any]
|
|
38
|
+
PropValue: TypeAlias = Node | Callable[..., Any] | Debounced[Any, Any] | RefHandle[Any]
|
|
38
39
|
|
|
39
40
|
FRAGMENT_TAG = ""
|
|
40
41
|
MOUNT_PREFIX = "$$"
|
|
@@ -405,6 +406,25 @@ class Renderer:
|
|
|
405
406
|
updated[key] = value.render()
|
|
406
407
|
continue
|
|
407
408
|
|
|
409
|
+
if isinstance(value, RefHandle):
|
|
410
|
+
if key != "ref":
|
|
411
|
+
raise TypeError("RefHandle can only be used as the 'ref' prop")
|
|
412
|
+
eval_keys.add(key)
|
|
413
|
+
if isinstance(old_value, (Element, PulseNode)):
|
|
414
|
+
unmount_element(old_value)
|
|
415
|
+
if normalized is None:
|
|
416
|
+
normalized = current.copy()
|
|
417
|
+
normalized[key] = value
|
|
418
|
+
if not (
|
|
419
|
+
isinstance(old_value, RefHandle) and values_equal(old_value, value)
|
|
420
|
+
):
|
|
421
|
+
updated[key] = {
|
|
422
|
+
"__pulse_ref__": {
|
|
423
|
+
"channelId": value.channel_id,
|
|
424
|
+
"refId": value.id,
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
continue
|
|
408
428
|
if isinstance(value, Debounced):
|
|
409
429
|
eval_keys.add(key)
|
|
410
430
|
if isinstance(old_value, (Element, PulseNode)):
|
|
@@ -499,6 +519,8 @@ def prop_requires_eval(value: PropValue) -> bool:
|
|
|
499
519
|
return True
|
|
500
520
|
if isinstance(value, Expr):
|
|
501
521
|
return True
|
|
522
|
+
if isinstance(value, RefHandle):
|
|
523
|
+
return True
|
|
502
524
|
if isinstance(value, Debounced):
|
|
503
525
|
return True
|
|
504
526
|
return callable(value)
|
pulse/transpiler/vdom.py
CHANGED
|
@@ -161,7 +161,18 @@ single sentinel string. Debounced callbacks use "$cb:<delay_ms>" in the wire for
|
|
|
161
161
|
"""
|
|
162
162
|
|
|
163
163
|
|
|
164
|
-
|
|
164
|
+
class PulseRefPayload(TypedDict):
|
|
165
|
+
channelId: str
|
|
166
|
+
refId: str
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class PulseRefSpec(TypedDict):
|
|
170
|
+
__pulse_ref__: PulseRefPayload
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
VDOMPropValue: TypeAlias = (
|
|
174
|
+
"JsonValue | VDOMExpr | VDOMElement | CallbackPlaceholder | PulseRefSpec"
|
|
175
|
+
)
|
|
165
176
|
"""Allowed prop value types.
|
|
166
177
|
|
|
167
178
|
Hot path:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
pulse/__init__.py,sha256=
|
|
1
|
+
pulse/__init__.py,sha256=jFSqmTbDLp07bGVr8N7Pa6k0h7Ipq2pYn_tsrr2Ztu8,32881
|
|
2
2
|
pulse/_examples.py,sha256=dFuhD2EVXsbvAeexoG57s4VuN4gWLaTMOEMNYvlPm9A,561
|
|
3
3
|
pulse/app.py,sha256=Bi94rYG-MoldkGa-_CscLMstjTEV8BHVAgDbvapRGzI,36167
|
|
4
|
-
pulse/channel.py,sha256=
|
|
4
|
+
pulse/channel.py,sha256=UkImBCIFr5sWdkpB3dFwwFa-nWyEnl1W3EaLv0BRsMU,15845
|
|
5
5
|
pulse/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
pulse/cli/cmd.py,sha256=LQK_B6iANOAqcQCM0KMTfRbpqGYRaPDkEBvvaAS3qNI,15985
|
|
7
7
|
pulse/cli/dependencies.py,sha256=qU-rF7QyP0Rl1Fl0YKQubrGNBzj84BAbH1uUT3ehxik,4283
|
|
@@ -23,7 +23,7 @@ pulse/codegen/templates/routes_ts.py,sha256=nPgKCvU0gzue2k6KlOL1TJgrBqqRLmyy7K_q
|
|
|
23
23
|
pulse/codegen/utils.py,sha256=QoXcV-h-DLLmq_t03hDNUePS0fNnofUQLoR-TXzDFCY,539
|
|
24
24
|
pulse/component.py,sha256=mY4ZTX7XKXGXAiVwTec1YR3_HOJf6uTdZcxCT_WX5Gs,6230
|
|
25
25
|
pulse/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
-
pulse/components/for_.py,sha256=
|
|
26
|
+
pulse/components/for_.py,sha256=rBLWaZtY9r_HPx7AcUYHnmCSxkjBI1Wk7bUTm6XoxMs,2377
|
|
27
27
|
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
|
|
@@ -33,7 +33,7 @@ pulse/decorators.py,sha256=Lskni9Keqfb-xmUliFQe5x-4AcNqrwdvoh0kuz2fXa0,9958
|
|
|
33
33
|
pulse/dom/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
34
|
pulse/dom/elements.py,sha256=YHXkVpfMAC4-0o61fK-E0LGTOM3KMCtBfpHHAwLx7dw,23241
|
|
35
35
|
pulse/dom/events.py,sha256=yHioH8Y-b7raOaZ43JuCxk2lUBryUAcDSc-5VhXtiSI,14699
|
|
36
|
-
pulse/dom/props.py,sha256=
|
|
36
|
+
pulse/dom/props.py,sha256=6F3dE_bShI2WdAVfFG0DbIQom2GcM0iF8B3FBE5bj14,26775
|
|
37
37
|
pulse/dom/svg.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
38
|
pulse/dom/tags.py,sha256=U6mKmwB9JAFM6LTESMJcoIejNfnyxIdQo2-TLM5OaZ0,7585
|
|
39
39
|
pulse/dom/tags.pyi,sha256=0BC7zTh22roPBuMQawL8hgI6IrfN8xJZuDIoKMd4QKc,14393
|
|
@@ -48,10 +48,11 @@ pulse/hooks/runtime.py,sha256=ogrm4Prvr9ZNaBb5bLfZBHrzbJuYe1zKpIPkbdnIzsw,12286
|
|
|
48
48
|
pulse/hooks/setup.py,sha256=NcQPKnMV5dO0vUsWi4u9c9LB0wqFstrtiPGdvihtGiQ,6872
|
|
49
49
|
pulse/hooks/stable.py,sha256=hLCOl_oAbgdNwiaWwwUTk7ZsHvMqXFFozFztJZMyGbQ,3627
|
|
50
50
|
pulse/hooks/state.py,sha256=zRFlcOUdl-SBkJ-EzVXRLrLXzAc4-uRzgH4hD9rM1oU,5251
|
|
51
|
-
pulse/js/__init__.py,sha256=
|
|
52
|
-
pulse/js/__init__.pyi,sha256=
|
|
53
|
-
pulse/js/_types.py,sha256=
|
|
51
|
+
pulse/js/__init__.py,sha256=Pf2tNypjCIWz3IMj7qGqUlWsBdv74r2R9ZwCVYzbdS0,5182
|
|
52
|
+
pulse/js/__init__.pyi,sha256=GQYs2JBnenPcwzOhOLKXUz6gYvVLRdsPYP3ltgxfc-0,6241
|
|
53
|
+
pulse/js/_types.py,sha256=AZpZGu1_SzxrmzQ0lfzkzfeMaLOjIHVLubBlQdtbr70,7577
|
|
54
54
|
pulse/js/abort_controller.py,sha256=nKiUgS_uy2Rgh-MNG7fcGIm8Qd6g7hNV_UVk7OgK3qg,1161
|
|
55
|
+
pulse/js/animation.py,sha256=p14FhkYGjvwRFOWmLijVPXsntXbmo_PXAQPcxNEJ7C0,4514
|
|
55
56
|
pulse/js/array.py,sha256=_tC6QZlflWCXOXXUMMtowM3UK7iDWAtFM8BKqR5rjKk,8883
|
|
56
57
|
pulse/js/array_buffer.py,sha256=-iuiSZuv-rx3d0jpQtQYZLXZ2nLuMF93QrQBO-HXv5o,4100
|
|
57
58
|
pulse/js/blob.py,sha256=XiI4g_fKaKtB8OPzS6D4rFG0fRBZlCl9kbO7a5IFPPk,981
|
|
@@ -59,7 +60,7 @@ pulse/js/console.py,sha256=A-GNKEnPby10gdcTdYsBPVfz4m94PYzTXRwGhfaPRpc,1775
|
|
|
59
60
|
pulse/js/crypto.py,sha256=Atxtc2oqb4xnW9nQV3ruamQhCAl2O_H2XiECspN1JvI,1245
|
|
60
61
|
pulse/js/custom_event.py,sha256=SxtzPQJwgSXRmesd3SiaVdCGYHsFcrnAiPZA63YuunA,901
|
|
61
62
|
pulse/js/date.py,sha256=qJjdwupuUtKS95u8N8C8FKMKOIB8qjVMsYA3VYfe-tA,3363
|
|
62
|
-
pulse/js/document.py,sha256=
|
|
63
|
+
pulse/js/document.py,sha256=0SJD2_1aqMjTF3HgIZq9wBcdJjUvarITb1Oqq0Jp8PM,3260
|
|
63
64
|
pulse/js/dom_parser.py,sha256=0qRlAHVr1gDphCPUOSJRcZR94giJF6JmsWOJv4I6p80,818
|
|
64
65
|
pulse/js/error.py,sha256=v0_DmpN5ESt_CJTrIYfy8980eerjK8mHhQatNV_1M_8,2611
|
|
65
66
|
pulse/js/fetch_api.py,sha256=-4qy5DkQcCeE9CvFMtq-by-WgA7PirgOrmgMflMsluQ,2342
|
|
@@ -79,7 +80,7 @@ pulse/js/object.py,sha256=95WvnGWgB-PL-D7l12UgdxNy_fxO5sJXool3Rx5ahUQ,4433
|
|
|
79
80
|
pulse/js/performance_observer.py,sha256=uletaqnJ6kvJqFSSFtmNo0j--FduK4uZHd8cfRqeN3A,1156
|
|
80
81
|
pulse/js/promise.py,sha256=vBXcL-U9BuZN-q1jbYhyzQaOL2niDPw4LsD7q7Y_yco,4670
|
|
81
82
|
pulse/js/pulse.py,sha256=m-LgqwhYygVBj7GzjeO-uo8fK5ThyVe7c3QvOJt_vc0,2962
|
|
82
|
-
pulse/js/react.py,sha256=
|
|
83
|
+
pulse/js/react.py,sha256=BUfeTOdH3z7n6TXsIJiFWp9euLkbzPmGlrwjAZ6h1Rc,12057
|
|
83
84
|
pulse/js/regexp.py,sha256=qO-3nmt7uGN7V_bwimPCN-2RSsPfE6YiY7G1MjoP3YY,1055
|
|
84
85
|
pulse/js/resize_observer.py,sha256=MN0pqnBETmI7bSrWT1bus4_vTZKmJ0jwBoW-cYQWoT4,1162
|
|
85
86
|
pulse/js/set.py,sha256=omG3g-25GRHxgoKISSB4x-M8UDFlaXtFV9cSIpd5uB0,3017
|
|
@@ -106,8 +107,9 @@ pulse/queries/store.py,sha256=iw05_EFpyfiXv5_FV_x4aHtCo00mk0dDPFD461cajcg,3850
|
|
|
106
107
|
pulse/react_component.py,sha256=8RLg4Bi7IcjqbnbEnp4hJpy8t1UsE7mG0UR1Q655LDk,2332
|
|
107
108
|
pulse/reactive.py,sha256=GSh9wSH3THCBjDTafwWttyx7djeKBWV_KqjaKRYUNsA,31393
|
|
108
109
|
pulse/reactive_extensions.py,sha256=yQ1PpdAh4kMvll7R15T72FOg8NFdG_HGBsGc63dawYk,33754
|
|
109
|
-
pulse/
|
|
110
|
-
pulse/
|
|
110
|
+
pulse/refs.py,sha256=-6QlzJwJ_lLgGGvJDl6OTOkoA6799TvBGla54ZTH_jA,22889
|
|
111
|
+
pulse/render_session.py,sha256=bujJ0Ch7wUgCqjlq3Z0e8zlg6xpkMThCD-daUSCw-xc,24406
|
|
112
|
+
pulse/renderer.py,sha256=xFjF9Ttv7M74BpHDHpAp32rTFztwyQDZcEWhOFLI5MU,17553
|
|
111
113
|
pulse/request.py,sha256=N0oFOLiGxpbgSgxznjvu64lG3YyOcZPKC8JFyKx6X7w,6023
|
|
112
114
|
pulse/requirements.py,sha256=nMnE25Uu-TUuQd88jW7m2xwus6fD-HvXxQ9UNb7OOGc,1254
|
|
113
115
|
pulse/routing.py,sha256=oRfVaeIrsbDR9yW9BYwxVWV3HZI7wk21yZX69IVADIU,17279
|
|
@@ -139,12 +141,12 @@ pulse/transpiler/nodes.py,sha256=ObdCFIEvtKMVRO8iy1hIN4L-vC4yPqRvhPS6E344-bE,526
|
|
|
139
141
|
pulse/transpiler/parse.py,sha256=uz_KDnjmjzFSjGtVKRznWg95P0NHM8CafWgvqrqJcOs,1622
|
|
140
142
|
pulse/transpiler/py_module.py,sha256=um4BYLrbs01bpgv2LEBHTbhXXh8Bs174c3ygv5tHHOg,4410
|
|
141
143
|
pulse/transpiler/transpiler.py,sha256=bu33-wGNqHGheT_ZqMnQgEARyPG6xyOvuLuixjxIZnI,42761
|
|
142
|
-
pulse/transpiler/vdom.py,sha256=
|
|
144
|
+
pulse/transpiler/vdom.py,sha256=5ooW9uoWoBEEKBSds27m6Birj3eOuWZ2Qh2nZ4f_kvo,6609
|
|
143
145
|
pulse/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
144
146
|
pulse/types/event_handler.py,sha256=psQCydj-WEtBcFU5JU4mDwvyzkW8V2O0g_VFRU2EOHI,1618
|
|
145
147
|
pulse/user_session.py,sha256=nsnsMgqq2xGJZLpbHRMHUHcLrElMP8WcA4gjGMrcoBk,10208
|
|
146
148
|
pulse/version.py,sha256=711vaM1jVIQPgkisGgKZqwmw019qZIsc_QTae75K2pg,1895
|
|
147
|
-
pulse_framework-0.1.
|
|
148
|
-
pulse_framework-0.1.
|
|
149
|
-
pulse_framework-0.1.
|
|
150
|
-
pulse_framework-0.1.
|
|
149
|
+
pulse_framework-0.1.77.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
150
|
+
pulse_framework-0.1.77.dist-info/entry_points.txt,sha256=i7aohd3QaPu5IcuGKKvsQQEiMYMe5HcF56QEsaLVO64,46
|
|
151
|
+
pulse_framework-0.1.77.dist-info/METADATA,sha256=8xfoFxbwvqbwJzkm4dCNS3lz4xbSmfJLNM6ZnVHLSKI,8299
|
|
152
|
+
pulse_framework-0.1.77.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|