-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathpython.ts
More file actions
134 lines (115 loc) · 4.53 KB
/
Copy pathpython.ts
File metadata and controls
134 lines (115 loc) · 4.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/**
* Python-side helpers for runtime toolbox install + introspection.
*
* The introspection logic itself comes from scripts/pathview_introspect.py,
* which is the single source of truth shared with the build-time extractor
* (scripts/extract.py). Here we only add the runtime-only glue:
*
* - install (micropip in Pyodide / pip in Flask)
* - inline-module loading (.py upload)
* - module drop on uninstall
* - thin wrappers exposing introspect_blocks / introspect_events / etc.
* to the JS evaluate() bridge.
*/
import INTROSPECT_PY from '../../../scripts/pathview_introspect.py?raw';
import INSTALL_PY from '../../../scripts/pathview_install.py?raw';
const RUNTIME_GLUE = `
import sys as _pv_sys
import importlib as _pv_importlib
import types as _pv_types
_PV_INLINE_PREFIX = "pathview_inline_"
def _pv_load_inline(module_name, code):
"""Exec a single-file Python module string into sys.modules."""
if not module_name.startswith(_PV_INLINE_PREFIX):
module_name = _PV_INLINE_PREFIX + module_name
mod = _pv_types.ModuleType(module_name)
mod.__file__ = "<inline:" + module_name + ">"
try:
exec(compile(code, mod.__file__, "exec"), mod.__dict__)
except Exception as e:
return {"ok": False, "error": str(e), "module": module_name}
_pv_sys.modules[module_name] = mod
return {"ok": True, "module": module_name}
def _pv_drop_module(import_path):
"""Drop a module + submodules from sys.modules."""
dropped = []
prefix = import_path + "."
for name in list(_pv_sys.modules.keys()):
if name == import_path or name.startswith(prefix):
try:
del _pv_sys.modules[name]
dropped.append(name)
except KeyError:
pass
return dropped
def pathview_introspect_blocks(import_path):
"""Walk the module and return all Block subclasses with metadata."""
try:
mod = _pv_importlib.import_module(import_path)
except Exception as e:
return {"ok": False, "error": str(e)}
blocks = []
for name in dir(mod):
if name.startswith("_"):
continue
obj = getattr(mod, name)
if not is_block(obj):
continue
if obj.__module__ != mod.__name__ and not obj.__module__.startswith(mod.__name__ + "."):
continue
try:
blocks.append(extract_block(obj))
except Exception as e:
blocks.append({"className": name, "error": str(e)})
return {"ok": True, "blocks": blocks}
def pathview_introspect_events(import_path):
"""Walk the events submodule and list event classes with their params."""
try:
mod = _pv_importlib.import_module(import_path)
except Exception as e:
return {"ok": False, "error": str(e)}
events = []
for name in dir(mod):
if name.startswith("_"):
continue
obj = getattr(mod, name)
if not is_event(obj):
continue
if obj.__module__ != mod.__name__ and not obj.__module__.startswith(mod.__name__ + "."):
continue
events.append(extract_event(obj))
return {"ok": True, "events": events}
def pathview_uninstall(import_path):
"""Drop a module + submodules from sys.modules."""
return {"ok": True, "dropped": _pv_drop_module(import_path)}
def _pv_module_version(import_path):
"""Best-effort version lookup for an imported module.
Tries module.__version__ first, falls back to importlib.metadata,
returns None if nothing works."""
try:
mod = _pv_importlib.import_module(import_path)
except Exception:
return None
v = getattr(mod, "__version__", None)
if isinstance(v, str) and v:
return v
try:
import importlib.metadata as _pv_md
except Exception:
return None
# Walk up the dotted path so submodule imports still resolve to their
# owning distribution (e.g. pathsim_chem.blocks → pathsim-chem).
parts = import_path.split(".")
for i in range(len(parts), 0, -1):
candidate = parts[0] if i == 1 else ".".join(parts[:i])
for name in (candidate, candidate.replace("_", "-"), candidate.replace("-", "_")):
try:
return _pv_md.version(name)
except Exception:
continue
return None
_pv_helpers_loaded = True
`;
export const TOOLBOX_PYTHON_HELPERS = INTROSPECT_PY + INSTALL_PY + RUNTIME_GLUE;
/** Sentinel expression used to check whether helpers are already loaded in the REPL. */
export const TOOLBOX_HELPERS_SENTINEL = `'_pv_helpers_loaded' in dir()`;