hmr 0.1.2__tar.gz → 0.2.1__tar.gz
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.
- {hmr-0.1.2 → hmr-0.2.1}/PKG-INFO +2 -2
- {hmr-0.1.2 → hmr-0.2.1}/pyproject.toml +2 -2
- {hmr-0.1.2 → hmr-0.2.1}/reactivity/helpers.py +12 -15
- {hmr-0.1.2 → hmr-0.2.1}/reactivity/hmr.py +7 -5
- {hmr-0.1.2 → hmr-0.2.1}/reactivity/primitives.py +36 -18
- {hmr-0.1.2 → hmr-0.2.1}/reactivity/__init__.py +0 -0
- {hmr-0.1.2 → hmr-0.2.1}/reactivity/functional.py +0 -0
{hmr-0.1.2 → hmr-0.2.1}/PKG-INFO
RENAMED
@@ -1,8 +1,8 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: hmr
|
3
|
-
Version: 0.1
|
3
|
+
Version: 0.2.1
|
4
4
|
Summary: Hot Module Reload for Python
|
5
5
|
Project-URL: repository, https://github.com/promplate/pyth-on-line/tree/reactivity
|
6
6
|
Requires-Python: >=3.12
|
7
|
-
Requires-Dist: watchfiles<2,>=0.21
|
7
|
+
Requires-Dist: watchfiles<2,>=0.21; sys_platform != "emscripten"
|
8
8
|
|
@@ -4,9 +4,9 @@ dynamic = []
|
|
4
4
|
requires-python = ">=3.12"
|
5
5
|
description = "Hot Module Reload for Python"
|
6
6
|
dependencies = [
|
7
|
-
"watchfiles>=0.21,<2",
|
7
|
+
"watchfiles>=0.21,<2 ; sys_platform != 'emscripten'",
|
8
8
|
]
|
9
|
-
version = "0.1
|
9
|
+
version = "0.2.1"
|
10
10
|
|
11
11
|
[project.scripts]
|
12
12
|
hmr = "reactivity.hmr:cli"
|
@@ -13,32 +13,29 @@ class Memoized[T](Subscribable, BaseComputation[T]):
|
|
13
13
|
self.fn = fn
|
14
14
|
self.is_stale = True
|
15
15
|
self.cached_value: T
|
16
|
-
|
16
|
+
|
17
|
+
def recompute(self):
|
18
|
+
self._before()
|
19
|
+
try:
|
20
|
+
self.cached_value = self.fn()
|
21
|
+
self.is_stale = False
|
22
|
+
finally:
|
23
|
+
self._after()
|
17
24
|
|
18
25
|
def trigger(self):
|
19
|
-
self.
|
20
|
-
if self._recompute:
|
21
|
-
self._recompute = False
|
22
|
-
self._before()
|
23
|
-
try:
|
24
|
-
self.cached_value = self.fn()
|
25
|
-
self.is_stale = False
|
26
|
-
finally:
|
27
|
-
self._after()
|
28
|
-
else:
|
29
|
-
self.invalidate()
|
26
|
+
self.invalidate()
|
30
27
|
|
31
28
|
def __call__(self):
|
29
|
+
self.track()
|
32
30
|
if self.is_stale:
|
33
|
-
self.
|
34
|
-
self.trigger()
|
35
|
-
assert not self._recompute
|
31
|
+
self.recompute()
|
36
32
|
return self.cached_value
|
37
33
|
|
38
34
|
def invalidate(self):
|
39
35
|
if not self.is_stale:
|
40
36
|
del self.cached_value
|
41
37
|
self.is_stale = True
|
38
|
+
self.notify()
|
42
39
|
|
43
40
|
|
44
41
|
class MemoizedProperty[T, I]:
|
@@ -8,6 +8,7 @@ from importlib.util import spec_from_loader
|
|
8
8
|
from inspect import currentframe
|
9
9
|
from pathlib import Path
|
10
10
|
from runpy import run_path
|
11
|
+
from site import getsitepackages
|
11
12
|
from types import ModuleType, TracebackType
|
12
13
|
from typing import Any
|
13
14
|
|
@@ -115,7 +116,7 @@ class ReactiveModuleFinder(MetaPathFinder):
|
|
115
116
|
def __init__(self, includes: Iterable[str] = ".", excludes: Iterable[str] = ()):
|
116
117
|
super().__init__()
|
117
118
|
self.includes = [Path(i).resolve() for i in includes]
|
118
|
-
self.excludes = [Path(e).resolve() for e in excludes]
|
119
|
+
self.excludes = [Path(e).resolve() for e in (*excludes, *getsitepackages())]
|
119
120
|
|
120
121
|
def _accept(self, path: Path):
|
121
122
|
return path.is_file() and not is_relative_to_any(path, self.excludes) and is_relative_to_any(path, self.includes)
|
@@ -149,7 +150,7 @@ def patch_module(name_or_module: str | ModuleType):
|
|
149
150
|
return m
|
150
151
|
|
151
152
|
|
152
|
-
def patch_meta_path(includes: Iterable[str] = (".",), excludes: Iterable[str] = (
|
153
|
+
def patch_meta_path(includes: Iterable[str] = (".",), excludes: Iterable[str] = ()):
|
153
154
|
sys.meta_path.insert(0, ReactiveModuleFinder(includes, excludes))
|
154
155
|
|
155
156
|
|
@@ -278,9 +279,10 @@ def cli():
|
|
278
279
|
print("\n Usage: hmr <entry file>, just like python <entry file>\n")
|
279
280
|
exit(1)
|
280
281
|
entry = sys.argv[-1]
|
281
|
-
|
282
|
+
if not (path := Path(entry)).is_file():
|
283
|
+
raise FileNotFoundError(path.resolve())
|
282
284
|
sys.path.insert(0, ".")
|
283
|
-
SyncReloader(entry
|
285
|
+
SyncReloader(entry).keep_watching_until_interrupt()
|
284
286
|
|
285
287
|
|
286
|
-
__version__ = "0.1
|
288
|
+
__version__ = "0.2.1"
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from collections.abc import Callable
|
1
|
+
from collections.abc import Callable, Iterable
|
2
2
|
from typing import Any, Self, overload
|
3
3
|
from weakref import WeakKeyDictionary, WeakSet
|
4
4
|
|
@@ -9,17 +9,19 @@ class Subscribable:
|
|
9
9
|
self.subscribers = set[BaseComputation]()
|
10
10
|
|
11
11
|
def track(self):
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
if not _current_computations:
|
13
|
+
return
|
14
|
+
last = _current_computations[-1]
|
15
|
+
if last is not self:
|
16
|
+
self.subscribers.add(last)
|
17
|
+
last.dependencies.add(self)
|
16
18
|
|
17
19
|
def notify(self):
|
18
20
|
if _batches:
|
19
|
-
|
21
|
+
schedule_callbacks(self.subscribers)
|
20
22
|
else:
|
21
|
-
|
22
|
-
|
23
|
+
with Batch(force_flush=False):
|
24
|
+
schedule_callbacks(self.subscribers)
|
23
25
|
|
24
26
|
|
25
27
|
class BaseComputation[T]:
|
@@ -46,9 +48,9 @@ class BaseComputation[T]:
|
|
46
48
|
def __exit__(self, *_):
|
47
49
|
self.dispose()
|
48
50
|
|
49
|
-
def trigger(self) ->
|
51
|
+
def trigger(self) -> Any: ...
|
50
52
|
|
51
|
-
def __call__(self):
|
53
|
+
def __call__(self) -> T:
|
52
54
|
return self.trigger()
|
53
55
|
|
54
56
|
|
@@ -119,22 +121,38 @@ class Effect[T](BaseComputation[T]):
|
|
119
121
|
|
120
122
|
|
121
123
|
class Batch:
|
122
|
-
def __init__(self):
|
123
|
-
self.callbacks
|
124
|
+
def __init__(self, force_flush=True):
|
125
|
+
self.callbacks = set[BaseComputation]()
|
126
|
+
self.force_flush = force_flush
|
124
127
|
|
125
128
|
def flush(self):
|
126
|
-
|
127
|
-
self.callbacks
|
128
|
-
|
129
|
-
|
129
|
+
triggered = set()
|
130
|
+
while self.callbacks:
|
131
|
+
callbacks = self.callbacks - triggered
|
132
|
+
self.callbacks.clear()
|
133
|
+
for computation in callbacks:
|
134
|
+
if computation in self.callbacks:
|
135
|
+
continue # skip if re-added during callback
|
136
|
+
computation.trigger()
|
137
|
+
triggered.add(computation)
|
130
138
|
|
131
139
|
def __enter__(self):
|
132
140
|
_batches.append(self)
|
133
141
|
|
134
142
|
def __exit__(self, *_):
|
135
|
-
|
143
|
+
if self.force_flush or len(_batches) == 1:
|
144
|
+
try:
|
145
|
+
self.flush()
|
146
|
+
finally:
|
147
|
+
last = _batches.pop()
|
148
|
+
else:
|
149
|
+
last = _batches.pop()
|
150
|
+
schedule_callbacks(self.callbacks)
|
136
151
|
assert last is self
|
137
|
-
self.flush()
|
138
152
|
|
139
153
|
|
140
154
|
_batches: list[Batch] = []
|
155
|
+
|
156
|
+
|
157
|
+
def schedule_callbacks(callbacks: Iterable[BaseComputation]):
|
158
|
+
_batches[-1].callbacks.update(callbacks)
|
File without changes
|
File without changes
|