pulse-framework 0.1.71__py3-none-any.whl → 0.1.72__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 +3 -0
- pulse/app.py +27 -24
- pulse/cli/cmd.py +1 -1
- pulse/cli/folder_lock.py +25 -6
- pulse/codegen/templates/layout.py +3 -1
- pulse/hooks/effects.py +15 -1
- pulse/proxy.py +719 -185
- pulse/queries/common.py +16 -4
- pulse/queries/infinite_query.py +12 -2
- pulse/queries/query.py +2 -1
- pulse/render_session.py +5 -2
- {pulse_framework-0.1.71.dist-info → pulse_framework-0.1.72.dist-info}/METADATA +3 -3
- {pulse_framework-0.1.71.dist-info → pulse_framework-0.1.72.dist-info}/RECORD +15 -15
- {pulse_framework-0.1.71.dist-info → pulse_framework-0.1.72.dist-info}/WHEEL +0 -0
- {pulse_framework-0.1.71.dist-info → pulse_framework-0.1.72.dist-info}/entry_points.txt +0 -0
pulse/queries/common.py
CHANGED
|
@@ -34,19 +34,31 @@ class Key(tuple[Hashable, ...]):
|
|
|
34
34
|
parts = tuple(key)
|
|
35
35
|
try:
|
|
36
36
|
key_hash = hash(parts)
|
|
37
|
-
except TypeError:
|
|
38
|
-
raise TypeError(
|
|
37
|
+
except TypeError as e:
|
|
38
|
+
raise TypeError(
|
|
39
|
+
f"Query key contains unhashable value: {e}.\n\n"
|
|
40
|
+
+ "Keys must contain only hashable values (strings, numbers, tuples).\n"
|
|
41
|
+
+ f"Got: {key!r}\n\n"
|
|
42
|
+
+ "If using a dict or list inside the key, convert it to a tuple:\n"
|
|
43
|
+
+ " key=('users', tuple(user_ids)) # instead of list"
|
|
44
|
+
) from None
|
|
39
45
|
obj = super().__new__(cls, parts)
|
|
40
46
|
obj._hash = key_hash
|
|
41
47
|
return obj
|
|
42
|
-
raise TypeError(
|
|
48
|
+
raise TypeError(
|
|
49
|
+
f"Query key must be a tuple or list, got {type(key).__name__}: {key!r}\n\n"
|
|
50
|
+
+ "Examples of valid keys:\n"
|
|
51
|
+
+ " key=('users',) # single-element tuple\n"
|
|
52
|
+
+ " key=('user', user_id) # tuple with dynamic value\n"
|
|
53
|
+
+ " key=['posts', 'feed'] # list form also works"
|
|
54
|
+
)
|
|
43
55
|
|
|
44
56
|
@override
|
|
45
57
|
def __hash__(self) -> int:
|
|
46
58
|
return self._hash
|
|
47
59
|
|
|
48
60
|
|
|
49
|
-
QueryKey: TypeAlias = tuple[Hashable, ...] | list[Hashable] | Key
|
|
61
|
+
QueryKey: TypeAlias = tuple[Hashable, ...] | list[Hashable] | Key # pyright: ignore[reportImplicitStringConcatenation]
|
|
50
62
|
"""List/tuple of hashable values identifying a query in the store.
|
|
51
63
|
|
|
52
64
|
Used to uniquely identify queries for caching, deduplication, and invalidation.
|
pulse/queries/infinite_query.py
CHANGED
|
@@ -432,7 +432,8 @@ class InfiniteQuery(Generic[T, TParam], Disposable):
|
|
|
432
432
|
self._cancel_observer_actions(observer)
|
|
433
433
|
|
|
434
434
|
if len(self._observers) == 0:
|
|
435
|
-
self.
|
|
435
|
+
if not self.__disposed__:
|
|
436
|
+
self.schedule_gc()
|
|
436
437
|
|
|
437
438
|
def invalidate(
|
|
438
439
|
self,
|
|
@@ -1308,8 +1309,17 @@ class InfiniteQueryProperty(Generic[T, TParam, TState], InitializableProperty):
|
|
|
1308
1309
|
)
|
|
1309
1310
|
|
|
1310
1311
|
if self._key is None:
|
|
1312
|
+
# pyright: ignore[reportImplicitStringConcatenation]
|
|
1311
1313
|
raise RuntimeError(
|
|
1312
|
-
f"
|
|
1314
|
+
f"Missing query key for @infinite_query '{self.name}'. "
|
|
1315
|
+
+ "A key is required to cache and share query results.\n\n"
|
|
1316
|
+
+ f"Fix: Add key=(...) to the decorator or use the @{self.name}.key decorator:\n\n"
|
|
1317
|
+
+ " @ps.infinite_query(initial_page_param=..., key=('my_query',))\n"
|
|
1318
|
+
+ f" async def {self.name}(self, param): ...\n\n"
|
|
1319
|
+
+ "Or with a dynamic key:\n\n"
|
|
1320
|
+
+ f" @{self.name}.key\n"
|
|
1321
|
+
+ f" def _{self.name}_key(self):\n"
|
|
1322
|
+
+ " return ('my_query', self.some_param)"
|
|
1313
1323
|
)
|
|
1314
1324
|
raw_initial = (
|
|
1315
1325
|
call_flexible(self._initial_data, state)
|
pulse/queries/query.py
CHANGED
pulse/render_session.py
CHANGED
|
@@ -285,7 +285,6 @@ class RenderSession:
|
|
|
285
285
|
self._send_message = None
|
|
286
286
|
self._global_states = {}
|
|
287
287
|
self._global_queue = []
|
|
288
|
-
self.query_store = QueryStore()
|
|
289
288
|
self.connected = False
|
|
290
289
|
self.channels = ChannelsManager(self)
|
|
291
290
|
self.forms = FormRegistry(self)
|
|
@@ -293,6 +292,7 @@ class RenderSession:
|
|
|
293
292
|
self._pending_js_results = {}
|
|
294
293
|
self._tasks = TaskRegistry(name=f"render:{id}")
|
|
295
294
|
self._timers = TimerRegistry(tasks=self._tasks, name=f"render:{id}")
|
|
295
|
+
self.query_store = QueryStore()
|
|
296
296
|
self.prerender_queue_timeout = prerender_queue_timeout
|
|
297
297
|
self.detach_queue_timeout = detach_queue_timeout
|
|
298
298
|
self.disconnect_queue_timeout = disconnect_queue_timeout
|
|
@@ -574,9 +574,10 @@ class RenderSession:
|
|
|
574
574
|
# ---- Helpers ----
|
|
575
575
|
|
|
576
576
|
def close(self):
|
|
577
|
+
# Close all pending timers at the start, to avoid anything firing while we clean up
|
|
578
|
+
self._timers.cancel_all()
|
|
577
579
|
self.forms.dispose()
|
|
578
580
|
self._tasks.cancel_all()
|
|
579
|
-
self._timers.cancel_all()
|
|
580
581
|
for path in list(self.route_mounts.keys()):
|
|
581
582
|
self.detach(path, timeout=0)
|
|
582
583
|
self.route_mounts.clear()
|
|
@@ -597,6 +598,8 @@ class RenderSession:
|
|
|
597
598
|
if not fut.done():
|
|
598
599
|
fut.cancel()
|
|
599
600
|
self._pending_js_results.clear()
|
|
601
|
+
# Close any timer that may have been scheduled during cleanup (ex: query GC)
|
|
602
|
+
self._timers.cancel_all()
|
|
600
603
|
self._global_queue = []
|
|
601
604
|
self._send_message = None
|
|
602
605
|
self.connected = False
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pulse-framework
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.72
|
|
4
4
|
Summary: Pulse - Full-stack framework for building real-time React applications in Python
|
|
5
|
-
Requires-Dist: websockets>=12.0
|
|
6
5
|
Requires-Dist: fastapi>=0.128.0
|
|
7
6
|
Requires-Dist: uvicorn>=0.24.0
|
|
8
7
|
Requires-Dist: mako>=1.3.10
|
|
9
8
|
Requires-Dist: typer>=0.16.0
|
|
10
9
|
Requires-Dist: python-socketio>=5.16.0
|
|
11
10
|
Requires-Dist: rich>=13.7.1
|
|
12
|
-
Requires-Dist: python-multipart>=0.0.
|
|
11
|
+
Requires-Dist: python-multipart>=0.0.22
|
|
13
12
|
Requires-Dist: python-dateutil>=2.9.0.post0
|
|
14
13
|
Requires-Dist: starlette>=0.50.0,<0.51.0
|
|
15
14
|
Requires-Dist: urllib3>=2.6.3
|
|
16
15
|
Requires-Dist: watchfiles>=1.1.0
|
|
17
16
|
Requires-Dist: httpx>=0.28.1
|
|
17
|
+
Requires-Dist: aiohttp>=3.12.0
|
|
18
18
|
Requires-Python: >=3.11
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
pulse/__init__.py,sha256=
|
|
1
|
+
pulse/__init__.py,sha256=VV1oD_y0xnhQ_yBj1XqGWjIaw4Hi-xGHYQfEMv-Z1Bo,32453
|
|
2
2
|
pulse/_examples.py,sha256=dFuhD2EVXsbvAeexoG57s4VuN4gWLaTMOEMNYvlPm9A,561
|
|
3
|
-
pulse/app.py,sha256=
|
|
3
|
+
pulse/app.py,sha256=Bi94rYG-MoldkGa-_CscLMstjTEV8BHVAgDbvapRGzI,36167
|
|
4
4
|
pulse/channel.py,sha256=ePpvD2mDbddt_LMxxxDjNRgOLbVi8Ed6TmJFgkrALB0,15790
|
|
5
5
|
pulse/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
pulse/cli/cmd.py,sha256=
|
|
6
|
+
pulse/cli/cmd.py,sha256=LQK_B6iANOAqcQCM0KMTfRbpqGYRaPDkEBvvaAS3qNI,15985
|
|
7
7
|
pulse/cli/dependencies.py,sha256=qU-rF7QyP0Rl1Fl0YKQubrGNBzj84BAbH1uUT3ehxik,4283
|
|
8
|
-
pulse/cli/folder_lock.py,sha256
|
|
8
|
+
pulse/cli/folder_lock.py,sha256=CGJCovbSxZ59dllwNUqtPDx6YCED8k3ct5ZsEL0gzfw,4035
|
|
9
9
|
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
|
|
@@ -17,7 +17,7 @@ pulse/code_analysis.py,sha256=NBba_7VtOxZYMyfku_p-bWkG0O_1pi1AxcaNyVM1nmY,1024
|
|
|
17
17
|
pulse/codegen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
18
|
pulse/codegen/codegen.py,sha256=Zw55vzevg_17hFtSi6KLl-EWSiABKRfZe6fB-cWpLAk,10330
|
|
19
19
|
pulse/codegen/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
-
pulse/codegen/templates/layout.py,sha256=
|
|
20
|
+
pulse/codegen/templates/layout.py,sha256=nJ9k0MDUHOGjtwbt8QAo9XdCPR06rqJSU9E8Qi4Uot4,4597
|
|
21
21
|
pulse/codegen/templates/route.py,sha256=UjBrb3e_8tMkd1OjBjEsnYmK6PCQqOYZBWDuU59FcrI,9234
|
|
22
22
|
pulse/codegen/templates/routes_ts.py,sha256=nPgKCvU0gzue2k6KlOL1TJgrBqqRLmyy7K_qKAI8zAE,1129
|
|
23
23
|
pulse/codegen/utils.py,sha256=QoXcV-h-DLLmq_t03hDNUePS0fNnofUQLoR-TXzDFCY,539
|
|
@@ -41,7 +41,7 @@ pulse/forms.py,sha256=0irpErCMJk8-YO1BrxjMkFb8dnvSz3rfzTywmMeib7g,14042
|
|
|
41
41
|
pulse/helpers.py,sha256=imVA9XzkYrYmdeqEdD7ot0g99adL1SVKv5bQGkKb-aQ,9504
|
|
42
42
|
pulse/hooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
43
43
|
pulse/hooks/core.py,sha256=tDEcB_CTD4yI5bNKn7CtB40sRKIanGNqPD5_qLgSzf4,10982
|
|
44
|
-
pulse/hooks/effects.py,sha256=
|
|
44
|
+
pulse/hooks/effects.py,sha256=IRb37K9p0egBqlPvAnlW5dPohPvQGzmcyLSgbuNk5FU,3005
|
|
45
45
|
pulse/hooks/init.py,sha256=PhjVBLlHpodPzVrRcx_QfEUrsx_6gEX_NuVhe6ojiYI,17834
|
|
46
46
|
pulse/hooks/runtime.py,sha256=nxqwl8ByclIh5i3ZcCN6L3f0X3ZpwOBWajLb_FSbcDw,11839
|
|
47
47
|
pulse/hooks/setup.py,sha256=ILbn2v6UFJPFBOWnJef1X2A9JLpIagEHN9Mx0d9947I,6925
|
|
@@ -74,21 +74,21 @@ pulse/js/window.py,sha256=yC1BjyH2jqp1x-CXJCUFta-ASyZ5668ozQ0AmAjZcxA,4097
|
|
|
74
74
|
pulse/messages.py,sha256=hz5EUFVHbzXHkcByZcV_Y199vb-M9cGjMMBL1HXPctE,4024
|
|
75
75
|
pulse/middleware.py,sha256=2syzmJ0r9fEa0k1pD7zC_1DHUMs9qLSWRzo5XXtKqsA,10896
|
|
76
76
|
pulse/plugin.py,sha256=bu90qaUVFtZsIsW41dpshVK1vvIGHUsg6mFoiF0Wfso,2370
|
|
77
|
-
pulse/proxy.py,sha256=
|
|
77
|
+
pulse/proxy.py,sha256=c13b0fE3sq82sFo46vv0emWLQ_ePwRkI7hiPZrnQDCE,22780
|
|
78
78
|
pulse/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
79
79
|
pulse/queries/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
80
80
|
pulse/queries/client.py,sha256=KMGT92dESMrzpLlhd701fyh7Wrs3VKmM5cZoRQ0AEzg,18994
|
|
81
|
-
pulse/queries/common.py,sha256=
|
|
81
|
+
pulse/queries/common.py,sha256=tjW5pqpWvRYUy3gsj-KxhUB9c5KBOgBqRKIuKJkob9A,4278
|
|
82
82
|
pulse/queries/effect.py,sha256=1ePUi2TwP49L9LhlkKI2qV_HhIO4jKj1r5jyPaWiUn8,1508
|
|
83
|
-
pulse/queries/infinite_query.py,sha256=
|
|
83
|
+
pulse/queries/infinite_query.py,sha256=QZmXN2G8r0FA7cCez5bjPxEEvWZTB2Sl5_Pob3d5E6E,50489
|
|
84
84
|
pulse/queries/mutation.py,sha256=fhEpOZ7CuHImH4Y02QapYdTJrwe6K52-keb0d67wmms,8274
|
|
85
85
|
pulse/queries/protocol.py,sha256=TOrUiI4QK55xuh0i4ch1u96apNl12QeYafkf6RVDd08,3544
|
|
86
|
-
pulse/queries/query.py,sha256=
|
|
86
|
+
pulse/queries/query.py,sha256=Ap1PRKoc1U1ddhnnHgBOuNX9oUcYD9nInpq87uTLnb8,41849
|
|
87
87
|
pulse/queries/store.py,sha256=iw05_EFpyfiXv5_FV_x4aHtCo00mk0dDPFD461cajcg,3850
|
|
88
88
|
pulse/react_component.py,sha256=8RLg4Bi7IcjqbnbEnp4hJpy8t1UsE7mG0UR1Q655LDk,2332
|
|
89
89
|
pulse/reactive.py,sha256=GSh9wSH3THCBjDTafwWttyx7djeKBWV_KqjaKRYUNsA,31393
|
|
90
90
|
pulse/reactive_extensions.py,sha256=yQ1PpdAh4kMvll7R15T72FOg8NFdG_HGBsGc63dawYk,33754
|
|
91
|
-
pulse/render_session.py,sha256=
|
|
91
|
+
pulse/render_session.py,sha256=65mhbKZ9o1vIDSRrM2p3VNPmo6Z5vn1Aoz7we5mw7P8,23417
|
|
92
92
|
pulse/renderer.py,sha256=fjSsUvCqV12jyN7Y5XspKUfjQJJzKX-Chha5oF5PrAk,16001
|
|
93
93
|
pulse/request.py,sha256=N0oFOLiGxpbgSgxznjvu64lG3YyOcZPKC8JFyKx6X7w,6023
|
|
94
94
|
pulse/requirements.py,sha256=nMnE25Uu-TUuQd88jW7m2xwus6fD-HvXxQ9UNb7OOGc,1254
|
|
@@ -122,7 +122,7 @@ pulse/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
122
122
|
pulse/types/event_handler.py,sha256=psQCydj-WEtBcFU5JU4mDwvyzkW8V2O0g_VFRU2EOHI,1618
|
|
123
123
|
pulse/user_session.py,sha256=nsnsMgqq2xGJZLpbHRMHUHcLrElMP8WcA4gjGMrcoBk,10208
|
|
124
124
|
pulse/version.py,sha256=711vaM1jVIQPgkisGgKZqwmw019qZIsc_QTae75K2pg,1895
|
|
125
|
-
pulse_framework-0.1.
|
|
126
|
-
pulse_framework-0.1.
|
|
127
|
-
pulse_framework-0.1.
|
|
128
|
-
pulse_framework-0.1.
|
|
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,,
|
|
File without changes
|
|
File without changes
|