pulse-framework 0.1.70__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/queries/query.py CHANGED
@@ -26,11 +26,13 @@ from pulse.queries.common import (
26
26
  ActionError,
27
27
  ActionResult,
28
28
  ActionSuccess,
29
+ Key,
29
30
  OnErrorFn,
30
31
  OnSuccessFn,
31
32
  QueryKey,
32
33
  QueryStatus,
33
34
  bind_state,
35
+ normalize_key,
34
36
  )
35
37
  from pulse.queries.effect import AsyncQueryEffect
36
38
  from pulse.reactive import Computed, Effect, Signal, Untrack
@@ -239,7 +241,8 @@ async def run_fetch_with_retries(
239
241
  result = await fetch_fn()
240
242
  state.set_success(result)
241
243
  if on_success:
242
- await maybe_await(call_flexible(on_success, result))
244
+ with Untrack():
245
+ await maybe_await(call_flexible(on_success, result))
243
246
  return
244
247
  except asyncio.CancelledError:
245
248
  raise
@@ -252,7 +255,8 @@ async def run_fetch_with_retries(
252
255
  state.retry_reason.write(e)
253
256
  state.apply_error(e)
254
257
  if on_error:
255
- await maybe_await(call_flexible(on_error, e))
258
+ with Untrack():
259
+ await maybe_await(call_flexible(on_error, e))
256
260
  return
257
261
 
258
262
 
@@ -263,7 +267,7 @@ class KeyedQuery(Generic[T], Disposable):
263
267
  Multiple observers can share the same query.
264
268
  """
265
269
 
266
- key: QueryKey
270
+ key: Key
267
271
  state: QueryState[T]
268
272
  observers: "list[KeyedQueryResult[T]]"
269
273
  _task: asyncio.Task[None] | None
@@ -283,7 +287,7 @@ class KeyedQuery(Generic[T], Disposable):
283
287
  gc_time: float = 300.0,
284
288
  on_dispose: Callable[[Any], None] | None = None,
285
289
  ):
286
- self.key = key
290
+ self.key = normalize_key(key)
287
291
  self.state = QueryState(
288
292
  name=str(key),
289
293
  retries=retries,
@@ -559,7 +563,8 @@ class KeyedQuery(Generic[T], Disposable):
559
563
  )
560
564
 
561
565
  if len(self.observers) == 0:
562
- self.schedule_gc()
566
+ if not self.__disposed__:
567
+ self.schedule_gc()
563
568
 
564
569
  def schedule_gc(self):
565
570
  self.cancel_gc()
@@ -1055,7 +1060,7 @@ class QueryProperty(Generic[T, TState], InitializableProperty):
1055
1060
  _initial_data_updated_at: float | dt.datetime | None
1056
1061
  _enabled: bool
1057
1062
  _initial_data: T | Callable[[TState], T] | Missing | None
1058
- _key: QueryKey | Callable[[TState], QueryKey] | None
1063
+ _key: Key | Callable[[TState], Key] | None
1059
1064
  # Not using OnSuccessFn and OnErrorFn since unions of callables are not well
1060
1065
  # supported in the type system. We just need to be careful to use
1061
1066
  # call_flexible to invoke these functions.
@@ -1081,7 +1086,17 @@ class QueryProperty(Generic[T, TState], InitializableProperty):
1081
1086
  ):
1082
1087
  self.name = name
1083
1088
  self._fetch_fn = fetch_fn
1084
- self._key = key
1089
+ if key is None:
1090
+ self._key = None
1091
+ elif callable(key):
1092
+ key_fn = key
1093
+
1094
+ def normalized_key(state: TState) -> Key:
1095
+ return normalize_key(key_fn(state))
1096
+
1097
+ self._key = normalized_key
1098
+ else:
1099
+ self._key = normalize_key(key)
1085
1100
  self._on_success_fn = None
1086
1101
  self._on_error_fn = None
1087
1102
  self._keep_previous_data = keep_previous_data
@@ -1102,7 +1117,11 @@ class QueryProperty(Generic[T, TState], InitializableProperty):
1102
1117
  raise RuntimeError(
1103
1118
  f"Cannot use @{self.name}.key decorator when a key is already provided to @query(key=...)."
1104
1119
  )
1105
- self._key = fn
1120
+
1121
+ def normalized_key(state: TState) -> Key:
1122
+ return normalize_key(fn(state))
1123
+
1124
+ self._key = normalized_key
1106
1125
  return fn
1107
1126
 
1108
1127
  # Decorator to attach a function providing initial data
@@ -1270,6 +1289,7 @@ class QueryProperty(Generic[T, TState], InitializableProperty):
1270
1289
  def query(
1271
1290
  fn: Callable[[TState], Awaitable[T]],
1272
1291
  *,
1292
+ key: QueryKey | Callable[[TState], QueryKey] | None = None,
1273
1293
  stale_time: float = 0.0,
1274
1294
  gc_time: float | None = 300.0,
1275
1295
  refetch_interval: float | None = None,
@@ -1279,7 +1299,6 @@ def query(
1279
1299
  initial_data_updated_at: float | dt.datetime | None = None,
1280
1300
  enabled: bool = True,
1281
1301
  fetch_on_mount: bool = True,
1282
- key: QueryKey | None = None,
1283
1302
  ) -> QueryProperty[T, TState]: ...
1284
1303
 
1285
1304
 
@@ -1287,6 +1306,7 @@ def query(
1287
1306
  def query(
1288
1307
  fn: None = None,
1289
1308
  *,
1309
+ key: QueryKey | Callable[[TState], QueryKey] | None = None,
1290
1310
  stale_time: float = 0.0,
1291
1311
  gc_time: float | None = 300.0,
1292
1312
  refetch_interval: float | None = None,
@@ -1296,13 +1316,13 @@ def query(
1296
1316
  initial_data_updated_at: float | dt.datetime | None = None,
1297
1317
  enabled: bool = True,
1298
1318
  fetch_on_mount: bool = True,
1299
- key: QueryKey | None = None,
1300
1319
  ) -> Callable[[Callable[[TState], Awaitable[T]]], QueryProperty[T, TState]]: ...
1301
1320
 
1302
1321
 
1303
1322
  def query(
1304
1323
  fn: Callable[[TState], Awaitable[T]] | None = None,
1305
1324
  *,
1325
+ key: QueryKey | Callable[[TState], QueryKey] | None = None,
1306
1326
  stale_time: float = 0.0,
1307
1327
  gc_time: float | None = 300.0,
1308
1328
  refetch_interval: float | None = None,
@@ -1312,7 +1332,6 @@ def query(
1312
1332
  initial_data_updated_at: float | dt.datetime | None = None,
1313
1333
  enabled: bool = True,
1314
1334
  fetch_on_mount: bool = True,
1315
- key: QueryKey | None = None,
1316
1335
  ) -> (
1317
1336
  QueryProperty[T, TState]
1318
1337
  | Callable[[Callable[[TState], Awaitable[T]]], QueryProperty[T, TState]]
pulse/queries/store.py CHANGED
@@ -3,7 +3,7 @@ from collections.abc import Callable
3
3
  from typing import Any, TypeVar, cast
4
4
 
5
5
  from pulse.helpers import MISSING, Missing
6
- from pulse.queries.common import QueryKey
6
+ from pulse.queries.common import Key, QueryKey, normalize_key
7
7
  from pulse.queries.infinite_query import InfiniteQuery, Page
8
8
  from pulse.queries.query import RETRY_DELAY_DEFAULT, KeyedQuery
9
9
 
@@ -16,7 +16,7 @@ class QueryStore:
16
16
  """
17
17
 
18
18
  def __init__(self):
19
- self._entries: dict[QueryKey, KeyedQuery[Any] | InfiniteQuery[Any, Any]] = {}
19
+ self._entries: dict[Key, KeyedQuery[Any] | InfiniteQuery[Any, Any]] = {}
20
20
 
21
21
  def items(self):
22
22
  """Iterate over all (key, query) pairs in the store."""
@@ -24,7 +24,7 @@ class QueryStore:
24
24
 
25
25
  def get_any(self, key: QueryKey):
26
26
  """Get any query (regular or infinite) by key, or None if not found."""
27
- return self._entries.get(key)
27
+ return self._entries.get(normalize_key(key))
28
28
 
29
29
  def ensure(
30
30
  self,
@@ -35,8 +35,9 @@ class QueryStore:
35
35
  retries: int = 3,
36
36
  retry_delay: float = RETRY_DELAY_DEFAULT,
37
37
  ) -> KeyedQuery[T]:
38
+ nkey = normalize_key(key)
38
39
  # Return existing entry if present
39
- existing = self._entries.get(key)
40
+ existing = self._entries.get(nkey)
40
41
  if existing:
41
42
  if isinstance(existing, InfiniteQuery):
42
43
  raise TypeError(
@@ -49,7 +50,7 @@ class QueryStore:
49
50
  del self._entries[e.key]
50
51
 
51
52
  entry = KeyedQuery(
52
- key,
53
+ nkey,
53
54
  initial_data=initial_data,
54
55
  initial_data_updated_at=initial_data_updated_at,
55
56
  gc_time=gc_time,
@@ -57,14 +58,14 @@ class QueryStore:
57
58
  retry_delay=retry_delay,
58
59
  on_dispose=_on_dispose,
59
60
  )
60
- self._entries[key] = entry
61
+ self._entries[nkey] = entry
61
62
  return entry
62
63
 
63
64
  def get(self, key: QueryKey) -> KeyedQuery[Any] | None:
64
65
  """
65
66
  Get an existing regular query by key, or None if not found.
66
67
  """
67
- existing = self._entries.get(key)
68
+ existing = self._entries.get(normalize_key(key))
68
69
  if existing and isinstance(existing, InfiniteQuery):
69
70
  return None
70
71
  return existing
@@ -73,7 +74,7 @@ class QueryStore:
73
74
  """
74
75
  Get an existing infinite query by key, or None if not found.
75
76
  """
76
- existing = self._entries.get(key)
77
+ existing = self._entries.get(normalize_key(key))
77
78
  if existing and isinstance(existing, InfiniteQuery):
78
79
  return existing
79
80
  return None
@@ -93,7 +94,8 @@ class QueryStore:
93
94
  retries: int = 3,
94
95
  retry_delay: float = RETRY_DELAY_DEFAULT,
95
96
  ) -> InfiniteQuery[Any, Any]:
96
- existing = self._entries.get(key)
97
+ nkey = normalize_key(key)
98
+ existing = self._entries.get(nkey)
97
99
  if existing:
98
100
  if not isinstance(existing, InfiniteQuery):
99
101
  raise TypeError(
@@ -106,7 +108,7 @@ class QueryStore:
106
108
  del self._entries[e.key]
107
109
 
108
110
  entry = InfiniteQuery(
109
- key,
111
+ nkey,
110
112
  initial_page_param=initial_page_param,
111
113
  get_next_page_param=get_next_page_param,
112
114
  get_previous_page_param=get_previous_page_param,
@@ -118,7 +120,7 @@ class QueryStore:
118
120
  retry_delay=retry_delay,
119
121
  on_dispose=_on_dispose,
120
122
  )
121
- self._entries[key] = entry
123
+ self._entries[nkey] = entry
122
124
  return entry
123
125
 
124
126
  def dispose_all(self) -> None:
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.70
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.20
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=cXNVXz0aizbkOG1aj2zytgzodyVNv7nNylsXcWmH-Lc,32183
1
+ pulse/__init__.py,sha256=VV1oD_y0xnhQ_yBj1XqGWjIaw4Hi-xGHYQfEMv-Z1Bo,32453
2
2
  pulse/_examples.py,sha256=dFuhD2EVXsbvAeexoG57s4VuN4gWLaTMOEMNYvlPm9A,561
3
- pulse/app.py,sha256=TjMP21lpwYpRnkMkeKL8EA22V9xSKTA6_5GQI9Btw_o,36059
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=zh3Ah6c16cNg3o_v_If_S58Qe8rvxNe5M2VrTkwvDU8,15957
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=-AKld2iM91G0uHB3F5ARD0QAjOw0TmsYYGaFgy_V350,3477
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=xz2ImZrbpu-tUkHZ38U6npIDzeYhrR_s41BuNPJUnOU,4470
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=cEPurXwRQoFmxasI0tZE1cQsZYnZr6VB5mWMUK0Db8c,2604
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=tj30GIJJVNKMxMwPNJ39-gn3PAXbx5wua4PiQ7XupvQ,7857
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
- pulse/queries/client.py,sha256=52T4MvorDr-T5UsJlndcEgnrPDs3QxIYP-MZtBSEuvc,18583
81
- pulse/queries/common.py,sha256=TYhn6LyldfmOKYYurxINgCEr3C3WSEwB0cIki1a5iBM,2488
80
+ pulse/queries/client.py,sha256=KMGT92dESMrzpLlhd701fyh7Wrs3VKmM5cZoRQ0AEzg,18994
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=oyEheqi2z79_DaG0loZd5eIwfBSmqEURuTEyQeJRVBc,49110
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=PhPUx975f1rO8ACiCiOCndrtuOmy4VA48ii3rIHUWRM,41351
87
- pulse/queries/store.py,sha256=4pWTDSl71LUM7YqhWanKjZkFh3t8F_04o48js_H4ttQ,3728
86
+ pulse/queries/query.py,sha256=Ap1PRKoc1U1ddhnnHgBOuNX9oUcYD9nInpq87uTLnb8,41849
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=RJgxJp_yRpDBr3l9ZFW0Eb6-dSp_urTzzPhPcHd0_Vo,23224
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.70.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
126
- pulse_framework-0.1.70.dist-info/entry_points.txt,sha256=i7aohd3QaPu5IcuGKKvsQQEiMYMe5HcF56QEsaLVO64,46
127
- pulse_framework-0.1.70.dist-info/METADATA,sha256=zUio7-V0_fgct5WwsWMwjrTqqJLQpbIkcFoAVE8Syoc,8300
128
- pulse_framework-0.1.70.dist-info/RECORD,,
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,,