hmr 0.6.2.1__py3-none-any.whl → 0.6.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hmr
3
- Version: 0.6.2.1
3
+ Version: 0.6.3
4
4
  Summary: Hot Module Reload for Python
5
5
  Keywords: reactive-programming,reload,signals,hmr
6
6
  Classifier: Development Status :: 5 - Production/Stable
@@ -0,0 +1,19 @@
1
+ hmr-0.6.3.dist-info/METADATA,sha256=2O8NQUxGt0pxJvSVLtCrkf2v3xLdmJqZ5fUJ2ix_bnM,2305
2
+ hmr-0.6.3.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
3
+ hmr-0.6.3.dist-info/entry_points.txt,sha256=g_T0uJ43WgsdG14kkkdaBQuIL0HO-m1qvtjXMP6d060,59
4
+ reactivity/__init__.py,sha256=pX-RUzkezCC1x4eOWGxNhXbwrbvBLP_3pQuZr9eZz1Y,300
5
+ reactivity/context.py,sha256=hgf3U4ihtGSVZ8mGvXcjKcO1U5i7lwX-LadDraAjdNg,1402
6
+ reactivity/functional.py,sha256=jJLEq88w5ag2cXuD1T118uEjnEg37_mqvWmFkb47KVY,1300
7
+ reactivity/helpers.py,sha256=jaKhJD4mh0vIwWDeg0SJGvrcwnJD_zOS9KX0opT9uS4,6000
8
+ reactivity/hmr/__init__.py,sha256=S5ZIHqCRpevdzWuhS0aCua_S8F0LkK0YNg6IgeTScFQ,177
9
+ reactivity/hmr/__main__.py,sha256=uIcyjR5gMFIXH_3hS0B3SD00RirVf7GIct-uItx675o,64
10
+ reactivity/hmr/_experimental.py,sha256=dB9Cfo2NnoC0GAMfdLZ_dV_LDVy8bCU-vzhTjyhYjS8,662
11
+ reactivity/hmr/api.py,sha256=ypS_LyzMH1JbN5cSwamAJ0HEcHMOY7BOIINuJmWJwo8,2283
12
+ reactivity/hmr/core.py,sha256=NSP6IW3sim6oRCaBAzSQfdOQTX7IIC7Nbnl84B8Wh5w,12252
13
+ reactivity/hmr/exec_hack/__init__.py,sha256=Ai-DY4Oqh6r4Avq0ohg5J61hGT-GORiXEzjG2zNXkNM,184
14
+ reactivity/hmr/exec_hack/transform.py,sha256=IzzeahV32TVLsfBi6UEkpO7VlS-PNUuIbLBpgh5Hhj0,1762
15
+ reactivity/hmr/hooks.py,sha256=jIFpe4CNxfaS9RcR4OIodx_sOZlnJ_IA1T1RtHPXhwU,945
16
+ reactivity/hmr/proxy.py,sha256=Jp1ecm088H_s_V6NaKrLKLKX1Gm6wB7ilkocoWUsN7I,573
17
+ reactivity/hmr/utils.py,sha256=TUhCy2DyPajN25U3MWF3roXOvUK_XLy7wFjs11zvyQw,2107
18
+ reactivity/primitives.py,sha256=O7hjFpduQyAZSQ35l5rg2O9j24WkNa5U7JfSMIVZAxw,6928
19
+ hmr-0.6.3.dist-info/RECORD,,
@@ -0,0 +1,23 @@
1
+ from collections.abc import Callable
2
+
3
+ from reactivity.context import Context
4
+
5
+ from ..primitives import BaseDerived, _equal
6
+
7
+
8
+ class Dirty[T](BaseDerived[T]):
9
+ UNSET = object()
10
+
11
+ def __init__(self, fn: Callable[[], T], *, context: Context | None = None):
12
+ super().__init__(context=context)
13
+ self.fn = fn
14
+ self.value = __class__.UNSET
15
+
16
+ def trigger(self):
17
+ self.track()
18
+ with self._enter():
19
+ value = self.fn()
20
+ if not _equal(value, self.value):
21
+ self.value = value
22
+ self.notify() # BUG: b -> a <- (b, c) will cause Dirty a to re-evaluate by c
23
+ return value
reactivity/hmr/core.py CHANGED
@@ -13,8 +13,9 @@ from typing import Self
13
13
  from weakref import WeakValueDictionary
14
14
 
15
15
  from ..context import Context, new_context
16
- from ..helpers import DerivedMethod
16
+ from ..helpers import DerivedMethod, DerivedProperty
17
17
  from ..primitives import BaseDerived, Derived, Signal
18
+ from ._experimental import Dirty
18
19
  from .hooks import call_post_reload_hooks, call_pre_reload_hooks
19
20
  from .proxy import Proxy
20
21
 
@@ -114,7 +115,7 @@ class ReactiveModule(ModuleType):
114
115
 
115
116
  def __getattr__(self, name: str):
116
117
  try:
117
- return self.__namespace_proxy[name]
118
+ return self.__namespace_proxy[name] if name != "__path__" else self.__namespace[name]
118
119
  except KeyError as e:
119
120
  if name != "__path__" and (getattr := self.__namespace_proxy.get("__getattr__")):
120
121
  return getattr(name)
@@ -143,6 +144,11 @@ class ReactiveModuleLoader(Loader):
143
144
  module.load()
144
145
 
145
146
 
147
+ @Dirty
148
+ def sys_path(): # TODO: Path(".") may change too
149
+ return sys.path
150
+
151
+
146
152
  class ReactiveModuleFinder(MetaPathFinder):
147
153
  def __init__(self, includes: Iterable[str] = ".", excludes: Iterable[str] = ()):
148
154
  super().__init__()
@@ -152,12 +158,18 @@ class ReactiveModuleFinder(MetaPathFinder):
152
158
  def _accept(self, path: Path):
153
159
  return path.is_file() and not is_relative_to_any(path, self.excludes) and is_relative_to_any(path, self.includes)
154
160
 
161
+ @DerivedProperty
162
+ def search_paths(self):
163
+ # FIXME: Handle case where `includes` contains file paths, not just directories
164
+ # Currently we assume `includes` never specify individual files
165
+ # And we assume `includes` and `excludes` never change
166
+ return [path for path in (Path(p).resolve() for p in sys_path()) if not is_relative_to_any(path, self.excludes) and is_relative_to_any(path, self.includes)]
167
+
155
168
  def find_spec(self, fullname: str, paths: Sequence[str] | None, _=None):
156
169
  if fullname in sys.modules:
157
170
  return None
158
171
 
159
- for p in sys.path:
160
- directory = Path(p).resolve()
172
+ for directory in self.search_paths:
161
173
  if directory.is_file():
162
174
  continue
163
175
 
@@ -219,7 +231,7 @@ class BaseReloader:
219
231
  self.includes = includes
220
232
  self.excludes = excludes
221
233
  patch_meta_path(includes, excludes)
222
- self.error_filter = ErrorFilter(*(str(i) for i in Path(__file__, "../..").resolve().glob("**/*.py")))
234
+ self.error_filter = ErrorFilter(*map(str, Path(__file__, "../..").resolve().glob("**/*.py")))
223
235
 
224
236
  @cached_property
225
237
  def entry_module(self):
@@ -335,4 +347,4 @@ def cli():
335
347
  reloader.keep_watching_until_interrupt()
336
348
 
337
349
 
338
- __version__ = "0.6.2.1"
350
+ __version__ = "0.6.3"
@@ -0,0 +1,7 @@
1
+ import ast
2
+
3
+ from .transform import ClassTransformer
4
+
5
+
6
+ def fix_class_name_resolution(mod: ast.Module) -> ast.Module:
7
+ return ast.fix_missing_locations(ClassTransformer().visit(mod))
@@ -0,0 +1,61 @@
1
+ import ast
2
+ from typing import override
3
+
4
+
5
+ class ClassTransformer(ast.NodeTransformer):
6
+ @override
7
+ def visit_ClassDef(self, node: ast.ClassDef):
8
+ node.body = [
9
+ name_lookup_function,
10
+ *map(ClassBodyTransformer().visit, node.body),
11
+ ast.Delete(targets=[ast.Name(id="__name_lookup", ctx=ast.Del())]),
12
+ ]
13
+ return node
14
+
15
+
16
+ class ClassBodyTransformer(ast.NodeTransformer):
17
+ @override
18
+ def visit_Name(self, node: ast.Name):
19
+ if isinstance(node.ctx, ast.Load) and node.id != "__name_lookup":
20
+ return build_name_lookup(node.id)
21
+ return node
22
+
23
+ @override
24
+ def visit_FunctionDef(self, node: ast.FunctionDef):
25
+ node.decorator_list = [self.visit(d) for d in node.decorator_list]
26
+ self.visit(node.args)
27
+ if node.returns:
28
+ node.returns = self.visit(node.returns)
29
+ return node
30
+
31
+ visit_AsyncFunctionDef = visit_FunctionDef # type: ignore # noqa: N815
32
+
33
+ @override
34
+ def visit_Lambda(self, node: ast.Lambda):
35
+ self.visit(node.args)
36
+ return node
37
+
38
+
39
+ def build_name_lookup(name: str) -> ast.Call:
40
+ return ast.Call(func=ast.Name(id="__name_lookup", ctx=ast.Load()), args=[ast.Constant(value=name)], keywords=[])
41
+
42
+
43
+ name_lookup_source = """
44
+
45
+ def __name_lookup(name):
46
+ from collections import ChainMap
47
+ from inspect import currentframe
48
+ f = currentframe().f_back
49
+ c = ChainMap(f.f_locals, f.f_globals, f.f_builtins)
50
+ f = f.f_back
51
+ while f is not None and f.f_code.co_name != "<module>":
52
+ c.maps.insert(1, f.f_locals)
53
+ f = f.f_back
54
+ m = object()
55
+ if (v := c.get(name, m)) is not m:
56
+ return v
57
+ raise NameError(name)
58
+
59
+ """
60
+
61
+ name_lookup_function = ast.parse(name_lookup_source).body[0]
reactivity/hmr/utils.py CHANGED
@@ -1,3 +1,4 @@
1
+ from ast import parse
1
2
  from collections import UserDict
2
3
  from collections.abc import Callable
3
4
  from functools import wraps
@@ -7,6 +8,7 @@ from types import FunctionType
7
8
 
8
9
  from ..helpers import Memoized
9
10
  from .core import HMR_CONTEXT, NamespaceProxy, ReactiveModule
11
+ from .exec_hack import fix_class_name_resolution
10
12
  from .hooks import post_reload, pre_reload
11
13
 
12
14
  memos: dict[str, Callable] = {}
@@ -27,6 +29,9 @@ def clear_memos():
27
29
  del memos[func]
28
30
 
29
31
 
32
+ _cache_decorator_phase = False
33
+
34
+
30
35
  def cache_across_reloads[T](func: Callable[[], T]) -> Callable[[], T]:
31
36
  file = getsourcefile(func)
32
37
  assert file is not None
@@ -41,6 +46,12 @@ def cache_across_reloads[T](func: Callable[[], T]) -> Callable[[], T]:
41
46
 
42
47
  proxy: NamespaceProxy = module._ReactiveModule__namespace_proxy # type: ignore # noqa: SLF001
43
48
 
49
+ global _cache_decorator_phase
50
+ _cache_decorator_phase = not _cache_decorator_phase
51
+ if _cache_decorator_phase: # this function will be called twice: once transforming ast and once re-executing the patched source
52
+ exec(compile(fix_class_name_resolution(parse(source)), file, "exec"), DictProxy(proxy))
53
+ return proxy[func.__name__]
54
+
44
55
  func = FunctionType(func.__code__, DictProxy(proxy), func.__name__, func.__defaults__, func.__closure__)
45
56
 
46
57
  functions[source] = func
reactivity/primitives.py CHANGED
@@ -15,8 +15,8 @@ def _equal(a, b):
15
15
  comparison_result = a == b
16
16
  if comparison_result:
17
17
  return True
18
- except ValueError as e:
19
- if ".all()" in str(e) and hasattr(comparison_result, "all"): # array-like instances
18
+ except (ValueError, RuntimeError) as e:
19
+ if "is ambiguous" in str(e) and hasattr(comparison_result, "all"): # array-like instances
20
20
  comparison_result = comparison_result.all()
21
21
  else:
22
22
  return False
@@ -1,16 +0,0 @@
1
- hmr-0.6.2.1.dist-info/METADATA,sha256=yJ9-D7wUAoMBWOXkKKsCIsuk_0VL1-s6SH7QJPFKs08,2307
2
- hmr-0.6.2.1.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
3
- hmr-0.6.2.1.dist-info/entry_points.txt,sha256=g_T0uJ43WgsdG14kkkdaBQuIL0HO-m1qvtjXMP6d060,59
4
- reactivity/__init__.py,sha256=pX-RUzkezCC1x4eOWGxNhXbwrbvBLP_3pQuZr9eZz1Y,300
5
- reactivity/context.py,sha256=hgf3U4ihtGSVZ8mGvXcjKcO1U5i7lwX-LadDraAjdNg,1402
6
- reactivity/functional.py,sha256=jJLEq88w5ag2cXuD1T118uEjnEg37_mqvWmFkb47KVY,1300
7
- reactivity/helpers.py,sha256=jaKhJD4mh0vIwWDeg0SJGvrcwnJD_zOS9KX0opT9uS4,6000
8
- reactivity/hmr/__init__.py,sha256=S5ZIHqCRpevdzWuhS0aCua_S8F0LkK0YNg6IgeTScFQ,177
9
- reactivity/hmr/__main__.py,sha256=uIcyjR5gMFIXH_3hS0B3SD00RirVf7GIct-uItx675o,64
10
- reactivity/hmr/api.py,sha256=ypS_LyzMH1JbN5cSwamAJ0HEcHMOY7BOIINuJmWJwo8,2283
11
- reactivity/hmr/core.py,sha256=k-7YgiIzzkNpoBc6ljsTX8asL2x4UMVr3H9TxUaFHCY,11670
12
- reactivity/hmr/hooks.py,sha256=jIFpe4CNxfaS9RcR4OIodx_sOZlnJ_IA1T1RtHPXhwU,945
13
- reactivity/hmr/proxy.py,sha256=Jp1ecm088H_s_V6NaKrLKLKX1Gm6wB7ilkocoWUsN7I,573
14
- reactivity/hmr/utils.py,sha256=yeLsJynaAchCKOZWSRaeyRujb7jOdFyKPCWhzWgyRYw,1647
15
- reactivity/primitives.py,sha256=_I6mG1qPBH6Vsktlf3-0mXpRO6L1hFhbfRtNMrpVxS8,6906
16
- hmr-0.6.2.1.dist-info/RECORD,,
File without changes