hmr 0.3.3.4__py3-none-any.whl → 0.4.0.1__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.
- {hmr-0.3.3.4.dist-info → hmr-0.4.0.1.dist-info}/METADATA +1 -1
- hmr-0.4.0.1.dist-info/RECORD +14 -0
- {hmr-0.3.3.4.dist-info → hmr-0.4.0.1.dist-info}/WHEEL +1 -1
- reactivity/helpers.py +49 -7
- reactivity/hmr/__main__.py +4 -0
- reactivity/hmr/api.py +0 -2
- reactivity/hmr/core.py +56 -31
- reactivity/hmr/utils.py +1 -1
- reactivity/primitives.py +57 -0
- hmr-0.3.3.4.dist-info/RECORD +0 -13
- {hmr-0.3.3.4.dist-info → hmr-0.4.0.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
hmr-0.4.0.1.dist-info/METADATA,sha256=jO4oStYy7M8q_RJXVWSiX-fMvpZFz8M8QqO2ARmPz_E,260
|
2
|
+
hmr-0.4.0.1.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
|
3
|
+
hmr-0.4.0.1.dist-info/entry_points.txt,sha256=g_T0uJ43WgsdG14kkkdaBQuIL0HO-m1qvtjXMP6d060,59
|
4
|
+
reactivity/__init__.py,sha256=pX-RUzkezCC1x4eOWGxNhXbwrbvBLP_3pQuZr9eZz1Y,300
|
5
|
+
reactivity/functional.py,sha256=U06vshcVhZ0sb218gcmHtEhfgTNAGtQ7zyvPz2w5qKM,1292
|
6
|
+
reactivity/helpers.py,sha256=1KCpre2HTFZrngEKkI2HwSFMkCmsUCq2aPEbp0y3kKg,5140
|
7
|
+
reactivity/hmr/__init__.py,sha256=S5ZIHqCRpevdzWuhS0aCua_S8F0LkK0YNg6IgeTScFQ,177
|
8
|
+
reactivity/hmr/__main__.py,sha256=uIcyjR5gMFIXH_3hS0B3SD00RirVf7GIct-uItx675o,64
|
9
|
+
reactivity/hmr/api.py,sha256=USwQUtMIdhGRJFGYTp-S23ZelbB39FkV9bB0UEqlAvc,1579
|
10
|
+
reactivity/hmr/core.py,sha256=0K8QTZ1CZFzP41i6xqtyAbwENTQzdBQbIzkqDCNc9cE,11559
|
11
|
+
reactivity/hmr/hooks.py,sha256=-yLr5ktiyqPb1nDbHsgv6-c_ZkziBjNqCU-0PCfXGYU,592
|
12
|
+
reactivity/hmr/utils.py,sha256=-PO-LMP4sc3IP-Bn_baq2w9IFWBZ3zGesgRn5wR6bS0,1555
|
13
|
+
reactivity/primitives.py,sha256=mB6cbHKDqtilOfgaEhshtRWJq9s0nPEKqRK0hfCoyFE,5671
|
14
|
+
hmr-0.4.0.1.dist-info/RECORD,,
|
reactivity/helpers.py
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
from collections import defaultdict
|
2
2
|
from collections.abc import Callable, Mapping, MutableMapping
|
3
|
-
from functools import partial
|
4
3
|
from typing import Self, overload
|
5
4
|
from weakref import WeakKeyDictionary
|
6
5
|
|
7
|
-
from .primitives import BaseComputation, Batch, Signal, Subscribable
|
6
|
+
from .primitives import BaseComputation, Batch, Derived, Signal, Subscribable
|
8
7
|
|
9
8
|
|
10
9
|
class Memoized[T](Subscribable, BaseComputation[T]):
|
@@ -54,7 +53,7 @@ class MemoizedProperty[T, I]:
|
|
54
53
|
return self
|
55
54
|
if func := self.map.get(instance):
|
56
55
|
return func()
|
57
|
-
self.map[instance] = func = Memoized(
|
56
|
+
self.map[instance] = func = Memoized(self.method.__get__(instance, owner))
|
58
57
|
return func()
|
59
58
|
|
60
59
|
|
@@ -74,7 +73,7 @@ class MemoizedMethod[T, I]:
|
|
74
73
|
return self
|
75
74
|
if memo := self.map.get(instance):
|
76
75
|
return memo
|
77
|
-
self.map[instance] = memo = Memoized(
|
76
|
+
self.map[instance] = memo = Memoized(self.method.__get__(instance, owner))
|
78
77
|
return memo
|
79
78
|
|
80
79
|
|
@@ -99,7 +98,7 @@ class Reactive[K, V](Subscribable, MutableMapping[K, V]):
|
|
99
98
|
return value
|
100
99
|
|
101
100
|
def __setitem__(self, key: K, value: V):
|
102
|
-
with Batch():
|
101
|
+
with Batch(force_flush=False):
|
103
102
|
self._signals[key].set(value)
|
104
103
|
self.notify()
|
105
104
|
|
@@ -107,7 +106,7 @@ class Reactive[K, V](Subscribable, MutableMapping[K, V]):
|
|
107
106
|
state = self._signals[key]
|
108
107
|
if state.get(track=False) is self.UNSET:
|
109
108
|
raise KeyError(key)
|
110
|
-
with Batch():
|
109
|
+
with Batch(force_flush=False):
|
111
110
|
state.set(self.UNSET)
|
112
111
|
self.notify()
|
113
112
|
|
@@ -121,8 +120,51 @@ class Reactive[K, V](Subscribable, MutableMapping[K, V]):
|
|
121
120
|
|
122
121
|
def __repr__(self):
|
123
122
|
self.track()
|
124
|
-
|
123
|
+
unset = self.UNSET
|
124
|
+
return repr({k: value for k, v in self._signals.items() if (value := v.get()) is not unset})
|
125
125
|
|
126
126
|
def items(self):
|
127
127
|
self.track()
|
128
128
|
return ({k: v.get() for k, v in self._signals.items()}).items()
|
129
|
+
|
130
|
+
|
131
|
+
class DerivedProperty[T, I]:
|
132
|
+
def __init__(self, method: Callable[[I], T]):
|
133
|
+
super().__init__()
|
134
|
+
self.method = method
|
135
|
+
self.map = WeakKeyDictionary[I, Derived[T]]()
|
136
|
+
|
137
|
+
@overload
|
138
|
+
def __get__(self, instance: None, owner: type[I]) -> Self: ...
|
139
|
+
@overload
|
140
|
+
def __get__(self, instance: I, owner: type[I]) -> T: ...
|
141
|
+
|
142
|
+
def __get__(self, instance: I | None, owner):
|
143
|
+
if instance is None:
|
144
|
+
return self
|
145
|
+
if func := self.map.get(instance):
|
146
|
+
return func()
|
147
|
+
self.map[instance] = func = Derived(self.method.__get__(instance, owner))
|
148
|
+
return func()
|
149
|
+
|
150
|
+
|
151
|
+
class DerivedMethod[T, I]:
|
152
|
+
def __init__(self, method: Callable[[I], T], check_equality=True):
|
153
|
+
super().__init__()
|
154
|
+
self.method = method
|
155
|
+
self.check_equality = check_equality
|
156
|
+
self.map = WeakKeyDictionary[I, Derived[T]]()
|
157
|
+
|
158
|
+
@overload
|
159
|
+
def __get__(self, instance: None, owner: type[I]) -> Self: ...
|
160
|
+
@overload
|
161
|
+
def __get__(self, instance: I, owner: type[I]) -> Derived[T]: ...
|
162
|
+
|
163
|
+
def __get__(self, instance: I | None, owner):
|
164
|
+
if instance is None:
|
165
|
+
return self
|
166
|
+
if func := self.map.get(instance):
|
167
|
+
return func
|
168
|
+
|
169
|
+
self.map[instance] = func = Derived(self.method.__get__(instance, owner), self.check_equality)
|
170
|
+
return func
|
reactivity/hmr/api.py
CHANGED
reactivity/hmr/core.py
CHANGED
@@ -5,14 +5,17 @@ from functools import cached_property
|
|
5
5
|
from importlib.abc import Loader, MetaPathFinder
|
6
6
|
from importlib.machinery import ModuleSpec
|
7
7
|
from importlib.util import spec_from_loader
|
8
|
-
from inspect import currentframe
|
8
|
+
from inspect import currentframe, ismethod
|
9
9
|
from pathlib import Path
|
10
10
|
from site import getsitepackages
|
11
11
|
from types import ModuleType, TracebackType
|
12
12
|
from typing import Any, Self
|
13
13
|
from weakref import WeakValueDictionary
|
14
14
|
|
15
|
-
from .. import Reactive, batch
|
15
|
+
from .. import Reactive, batch
|
16
|
+
from ..functional import create_effect
|
17
|
+
from ..helpers import DerivedMethod
|
18
|
+
from ..primitives import BaseDerived, Derived, Signal
|
16
19
|
from .hooks import call_post_reload_hooks, call_pre_reload_hooks
|
17
20
|
|
18
21
|
|
@@ -29,10 +32,31 @@ def is_called_in_this_file() -> bool:
|
|
29
32
|
return frame.f_globals.get("__file__") == __file__
|
30
33
|
|
31
34
|
|
35
|
+
class Name(Signal, BaseDerived):
|
36
|
+
def __init__(self, initial_value):
|
37
|
+
super().__init__(initial_value)
|
38
|
+
|
39
|
+
|
32
40
|
class NamespaceProxy(Reactive[str, Any]):
|
33
|
-
def __init__(self, initial: MutableMapping, check_equality=True):
|
41
|
+
def __init__(self, initial: MutableMapping, module: "ReactiveModule", check_equality=True):
|
34
42
|
super().__init__(initial, check_equality)
|
35
43
|
self._original = initial
|
44
|
+
self.module = module
|
45
|
+
|
46
|
+
def _null(self):
|
47
|
+
self.module.load.subscribers.add(signal := Name(self.UNSET))
|
48
|
+
signal.dependencies.add(self.module.load)
|
49
|
+
return signal
|
50
|
+
|
51
|
+
def __getitem__(self, key: str):
|
52
|
+
try:
|
53
|
+
return super().__getitem__(key)
|
54
|
+
finally:
|
55
|
+
signal = self._signals[key]
|
56
|
+
if self.module.load in signal.subscribers:
|
57
|
+
# a module's loader shouldn't subscribe its variables
|
58
|
+
signal.subscribers.remove(self.module.load)
|
59
|
+
self.module.load.dependencies.remove(signal)
|
36
60
|
|
37
61
|
def __setitem__(self, key, value):
|
38
62
|
self._original[key] = value
|
@@ -53,7 +77,7 @@ class ReactiveModule(ModuleType):
|
|
53
77
|
self.__is_initialized = True
|
54
78
|
|
55
79
|
self.__namespace = namespace
|
56
|
-
self.__namespace_proxy = NamespaceProxy(namespace)
|
80
|
+
self.__namespace_proxy = NamespaceProxy(namespace, self)
|
57
81
|
self.__file = file
|
58
82
|
|
59
83
|
self.instances[file.resolve()] = self
|
@@ -64,7 +88,7 @@ class ReactiveModule(ModuleType):
|
|
64
88
|
return self.__file
|
65
89
|
raise AttributeError("file")
|
66
90
|
|
67
|
-
@
|
91
|
+
@DerivedMethod
|
68
92
|
def __load(self):
|
69
93
|
try:
|
70
94
|
code = compile(self.__file.read_text("utf-8"), str(self.__file), "exec", dont_inherit=True)
|
@@ -72,6 +96,13 @@ class ReactiveModule(ModuleType):
|
|
72
96
|
sys.excepthook(type(e), e, e.__traceback__)
|
73
97
|
else:
|
74
98
|
exec(code, self.__namespace, self.__namespace_proxy)
|
99
|
+
finally:
|
100
|
+
for dep in list((load := self.__load).dependencies):
|
101
|
+
assert ismethod(load.fn) # for type narrowing
|
102
|
+
if isinstance(dep, Derived) and ismethod(dep.fn) and isinstance(dep.fn.__self__, ReactiveModule) and dep.fn.__func__ is load.fn.__func__:
|
103
|
+
# unsubscribe it because we want invalidation to be fine-grained
|
104
|
+
dep.subscribers.remove(load)
|
105
|
+
load.dependencies.remove(dep)
|
75
106
|
|
76
107
|
@property
|
77
108
|
def load(self):
|
@@ -202,14 +233,9 @@ class BaseReloader:
|
|
202
233
|
namespace = {"__file__": self.entry, "__name__": "__main__"}
|
203
234
|
return ReactiveModule(Path(self.entry), namespace, "__main__")
|
204
235
|
|
205
|
-
@memoized_method
|
206
236
|
def run_entry_file(self):
|
207
|
-
|
208
|
-
|
209
|
-
self.entry_module.load.invalidate()
|
210
|
-
self.entry_module.load()
|
211
|
-
|
212
|
-
call_post_reload_hooks()
|
237
|
+
with self.error_filter:
|
238
|
+
self.entry_module.load()
|
213
239
|
|
214
240
|
@property
|
215
241
|
def watch_filter(self):
|
@@ -224,22 +250,23 @@ class BaseReloader:
|
|
224
250
|
return
|
225
251
|
|
226
252
|
path2module = get_path_module_map()
|
253
|
+
staled_modules: set[ReactiveModule] = set()
|
254
|
+
|
255
|
+
call_pre_reload_hooks()
|
227
256
|
|
228
257
|
with batch():
|
229
258
|
for type, file in events:
|
230
259
|
if type is not Change.deleted:
|
231
260
|
path = Path(file).resolve()
|
232
|
-
if
|
233
|
-
|
234
|
-
elif module := path2module.get(path):
|
235
|
-
module.load.invalidate()
|
261
|
+
if module := path2module.get(path):
|
262
|
+
staled_modules.add(module)
|
236
263
|
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
264
|
+
for module in staled_modules:
|
265
|
+
with self.error_filter:
|
266
|
+
module.load.invalidate()
|
267
|
+
module.load() # because `module.load` is not pulled by anyone
|
268
|
+
|
269
|
+
call_post_reload_hooks()
|
243
270
|
|
244
271
|
|
245
272
|
class _SimpleEvent:
|
@@ -270,11 +297,10 @@ class SyncReloader(BaseReloader):
|
|
270
297
|
del self._stop_event
|
271
298
|
|
272
299
|
def keep_watching_until_interrupt(self):
|
273
|
-
|
274
|
-
|
275
|
-
|
300
|
+
call_pre_reload_hooks()
|
301
|
+
with suppress(KeyboardInterrupt), create_effect(self.run_entry_file):
|
302
|
+
call_post_reload_hooks()
|
276
303
|
self.start_watching()
|
277
|
-
self.run_entry_file.dispose()
|
278
304
|
|
279
305
|
|
280
306
|
class AsyncReloader(BaseReloader):
|
@@ -296,11 +322,10 @@ class AsyncReloader(BaseReloader):
|
|
296
322
|
del self._stop_event
|
297
323
|
|
298
324
|
async def keep_watching_until_interrupt(self):
|
299
|
-
|
300
|
-
|
301
|
-
|
325
|
+
call_pre_reload_hooks()
|
326
|
+
with suppress(KeyboardInterrupt), create_effect(self.run_entry_file):
|
327
|
+
call_post_reload_hooks()
|
302
328
|
await self.start_watching()
|
303
|
-
self.run_entry_file.dispose()
|
304
329
|
|
305
330
|
|
306
331
|
def cli():
|
@@ -314,4 +339,4 @@ def cli():
|
|
314
339
|
SyncReloader(entry).keep_watching_until_interrupt()
|
315
340
|
|
316
341
|
|
317
|
-
__version__ = "0.
|
342
|
+
__version__ = "0.4.0.1"
|
reactivity/hmr/utils.py
CHANGED
reactivity/primitives.py
CHANGED
@@ -156,3 +156,60 @@ _batches: list[Batch] = []
|
|
156
156
|
|
157
157
|
def schedule_callbacks(callbacks: Iterable[BaseComputation]):
|
158
158
|
_batches[-1].callbacks.update(callbacks)
|
159
|
+
|
160
|
+
|
161
|
+
class BaseDerived[T](Subscribable, BaseComputation[T]):
|
162
|
+
def __init__(self):
|
163
|
+
super().__init__()
|
164
|
+
self.dirty = True
|
165
|
+
|
166
|
+
def _sync_dirty_deps(self):
|
167
|
+
for dep in self.dependencies:
|
168
|
+
if isinstance(dep, BaseDerived) and dep.dirty:
|
169
|
+
dep()
|
170
|
+
|
171
|
+
|
172
|
+
class Derived[T](BaseDerived[T]):
|
173
|
+
UNSET: T = object() # type: ignore
|
174
|
+
|
175
|
+
def __init__(self, fn: Callable[[], T], check_equality=True):
|
176
|
+
super().__init__()
|
177
|
+
self.fn = fn
|
178
|
+
self._check_equality = check_equality
|
179
|
+
self._value = self.UNSET
|
180
|
+
|
181
|
+
def recompute(self):
|
182
|
+
self._before()
|
183
|
+
try:
|
184
|
+
value = self.fn()
|
185
|
+
self.dirty = False
|
186
|
+
if self._check_equality:
|
187
|
+
if value == self._value:
|
188
|
+
return
|
189
|
+
elif self._value is self.UNSET: # do not notify on first set
|
190
|
+
self._value = value
|
191
|
+
return
|
192
|
+
self._value = value
|
193
|
+
self.notify()
|
194
|
+
finally:
|
195
|
+
self._after()
|
196
|
+
|
197
|
+
def __call__(self):
|
198
|
+
self.track()
|
199
|
+
self._sync_dirty_deps()
|
200
|
+
if self.dirty:
|
201
|
+
self.recompute()
|
202
|
+
|
203
|
+
return self._value
|
204
|
+
|
205
|
+
def trigger(self):
|
206
|
+
self.dirty = True
|
207
|
+
if _pulled(self):
|
208
|
+
self()
|
209
|
+
|
210
|
+
def invalidate(self):
|
211
|
+
self.trigger()
|
212
|
+
|
213
|
+
|
214
|
+
def _pulled(sub: Subscribable):
|
215
|
+
return any(not isinstance(s, BaseDerived) or _pulled(s) for s in sub.subscribers)
|
hmr-0.3.3.4.dist-info/RECORD
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
hmr-0.3.3.4.dist-info/METADATA,sha256=qq-JxvntRdTdZHN71E35GRMR2khQ6o7hMDQtWAewoMY,260
|
2
|
-
hmr-0.3.3.4.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
|
3
|
-
hmr-0.3.3.4.dist-info/entry_points.txt,sha256=g_T0uJ43WgsdG14kkkdaBQuIL0HO-m1qvtjXMP6d060,59
|
4
|
-
reactivity/__init__.py,sha256=pX-RUzkezCC1x4eOWGxNhXbwrbvBLP_3pQuZr9eZz1Y,300
|
5
|
-
reactivity/functional.py,sha256=U06vshcVhZ0sb218gcmHtEhfgTNAGtQ7zyvPz2w5qKM,1292
|
6
|
-
reactivity/helpers.py,sha256=7gwsIKKrjEahSz9G9oR4s1LdYXQTCIMO0k4UGXGla9Y,3714
|
7
|
-
reactivity/hmr/__init__.py,sha256=S5ZIHqCRpevdzWuhS0aCua_S8F0LkK0YNg6IgeTScFQ,177
|
8
|
-
reactivity/hmr/api.py,sha256=-0-6Tn0AVkaDs7_qrCCd9TXxRTPDMDB08-UYfepTdec,1644
|
9
|
-
reactivity/hmr/core.py,sha256=xwpJB-kJdNqhCA6g5WtVYl2gAKeL1-oK3gebnqGOI_4,10358
|
10
|
-
reactivity/hmr/hooks.py,sha256=-yLr5ktiyqPb1nDbHsgv6-c_ZkziBjNqCU-0PCfXGYU,592
|
11
|
-
reactivity/hmr/utils.py,sha256=zM7X5I8ywOguOt20uE55INWOGen3FuJZDAvgouu7lI8,1550
|
12
|
-
reactivity/primitives.py,sha256=DR2waJbzhVKOioHXMliE4FIsxQUq7DZA0umPrlvchA4,4217
|
13
|
-
hmr-0.3.3.4.dist-info/RECORD,,
|
File without changes
|