pulse-framework 0.1.51__py3-none-any.whl → 0.1.53__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/__init__.py +542 -562
- pulse/_examples.py +29 -0
- pulse/app.py +0 -14
- pulse/cli/cmd.py +96 -80
- pulse/cli/dependencies.py +10 -41
- pulse/cli/folder_lock.py +3 -3
- pulse/cli/helpers.py +40 -67
- pulse/cli/logging.py +102 -0
- pulse/cli/packages.py +16 -0
- pulse/cli/processes.py +40 -23
- pulse/codegen/codegen.py +70 -35
- pulse/codegen/js.py +2 -4
- pulse/codegen/templates/route.py +94 -146
- pulse/component.py +115 -0
- pulse/components/for_.py +1 -1
- pulse/components/if_.py +1 -1
- pulse/components/react_router.py +16 -22
- pulse/{html → dom}/events.py +1 -1
- pulse/{html → dom}/props.py +6 -6
- pulse/{html → dom}/tags.py +11 -11
- pulse/dom/tags.pyi +480 -0
- pulse/form.py +7 -6
- pulse/hooks/init.py +1 -13
- pulse/js/__init__.py +37 -41
- pulse/js/__init__.pyi +22 -2
- pulse/js/_types.py +5 -3
- pulse/js/array.py +121 -38
- pulse/js/console.py +9 -9
- pulse/js/date.py +22 -19
- pulse/js/document.py +8 -4
- pulse/js/error.py +12 -14
- pulse/js/json.py +4 -3
- pulse/js/map.py +17 -7
- pulse/js/math.py +2 -2
- pulse/js/navigator.py +4 -4
- pulse/js/number.py +8 -8
- pulse/js/object.py +9 -13
- pulse/js/promise.py +25 -9
- pulse/js/regexp.py +6 -6
- pulse/js/set.py +20 -8
- pulse/js/string.py +7 -7
- pulse/js/weakmap.py +6 -6
- pulse/js/weakset.py +6 -6
- pulse/js/window.py +17 -14
- pulse/messages.py +1 -4
- pulse/react_component.py +3 -1001
- pulse/render_session.py +74 -66
- pulse/renderer.py +311 -238
- pulse/routing.py +1 -10
- pulse/transpiler/__init__.py +84 -114
- pulse/transpiler/builtins.py +661 -343
- pulse/transpiler/errors.py +78 -2
- pulse/transpiler/function.py +463 -133
- pulse/transpiler/id.py +18 -0
- pulse/transpiler/imports.py +230 -325
- pulse/transpiler/js_module.py +218 -209
- pulse/transpiler/modules/__init__.py +16 -13
- pulse/transpiler/modules/asyncio.py +45 -26
- pulse/transpiler/modules/json.py +12 -8
- pulse/transpiler/modules/math.py +161 -216
- pulse/transpiler/modules/pulse/__init__.py +5 -0
- pulse/transpiler/modules/pulse/tags.py +231 -0
- pulse/transpiler/modules/typing.py +33 -28
- pulse/transpiler/nodes.py +1607 -923
- pulse/transpiler/py_module.py +118 -95
- pulse/transpiler/react_component.py +51 -0
- pulse/transpiler/transpiler.py +593 -437
- pulse/transpiler/vdom.py +255 -0
- {pulse_framework-0.1.51.dist-info → pulse_framework-0.1.53.dist-info}/METADATA +1 -1
- pulse_framework-0.1.53.dist-info/RECORD +120 -0
- pulse/html/tags.pyi +0 -470
- pulse/transpiler/constants.py +0 -110
- pulse/transpiler/context.py +0 -26
- pulse/transpiler/ids.py +0 -16
- pulse/transpiler/modules/re.py +0 -466
- pulse/transpiler/modules/tags.py +0 -268
- pulse/transpiler/utils.py +0 -4
- pulse/vdom.py +0 -599
- pulse_framework-0.1.51.dist-info/RECORD +0 -119
- /pulse/{html → dom}/__init__.py +0 -0
- /pulse/{html → dom}/elements.py +0 -0
- /pulse/{html → dom}/svg.py +0 -0
- {pulse_framework-0.1.51.dist-info → pulse_framework-0.1.53.dist-info}/WHEEL +0 -0
- {pulse_framework-0.1.51.dist-info → pulse_framework-0.1.53.dist-info}/entry_points.txt +0 -0
pulse/transpiler/py_module.py
CHANGED
|
@@ -1,119 +1,142 @@
|
|
|
1
|
-
"""Python module transpilation system for
|
|
1
|
+
"""Python module transpilation system for transpiler.
|
|
2
2
|
|
|
3
3
|
Provides infrastructure for mapping Python modules (like `math`) to JavaScript equivalents.
|
|
4
|
-
For direct JavaScript module bindings, use the pulse.js.* module system instead.
|
|
5
4
|
"""
|
|
6
5
|
|
|
7
6
|
from __future__ import annotations
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
from
|
|
8
|
+
import ast
|
|
9
|
+
from collections.abc import Callable, Iterable
|
|
11
10
|
from types import ModuleType
|
|
12
|
-
from typing import Any,
|
|
11
|
+
from typing import TYPE_CHECKING, Any, ClassVar, cast, override
|
|
13
12
|
|
|
14
|
-
from pulse.transpiler.
|
|
15
|
-
from pulse.transpiler.
|
|
13
|
+
from pulse.transpiler.nodes import Expr, Primitive, Transformer
|
|
14
|
+
from pulse.transpiler.vdom import VDOMNode
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
# but will be normalized to only JSExpr before storage
|
|
20
|
-
PyModuleTranspiler: TypeAlias = dict[str, JSExpr]
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from pulse.transpiler.transpiler import Transpiler
|
|
21
18
|
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"""JSExpr for a Python module imported as a whole (e.g., `import math`).
|
|
20
|
+
class PyModule(Expr):
|
|
21
|
+
"""Expr for a Python module imported as a whole (e.g., `import math`).
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
Subclasses can define transpiler mappings as class attributes:
|
|
24
|
+
- Expr attributes are used directly
|
|
25
|
+
- Callable attributes are wrapped in Transformer
|
|
26
|
+
- Primitives are converted via Expr.of()
|
|
27
|
+
|
|
28
|
+
The transpiler dict is built automatically via __init_subclass__.
|
|
29
29
|
"""
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
__slots__: tuple[str, str] = ("transpiler", "name")
|
|
32
|
+
|
|
33
|
+
# Class-level transpiler template, built by __init_subclass__
|
|
34
|
+
_transpiler: ClassVar[dict[str, Expr]] = {}
|
|
35
|
+
|
|
36
|
+
transpiler: dict[str, Expr]
|
|
37
|
+
name: str
|
|
38
|
+
|
|
39
|
+
def __init__(self, transpiler: dict[str, Expr] | None = None, name: str = ""):
|
|
40
|
+
self.transpiler = transpiler if transpiler is not None else {}
|
|
41
|
+
self.name = name
|
|
42
|
+
|
|
43
|
+
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
44
|
+
super().__init_subclass__(**kwargs)
|
|
45
|
+
cls._transpiler = {}
|
|
46
|
+
for attr_name in dir(cls):
|
|
47
|
+
if attr_name.startswith("_"):
|
|
48
|
+
continue
|
|
49
|
+
attr = getattr(cls, attr_name)
|
|
50
|
+
if isinstance(attr, Expr):
|
|
51
|
+
cls._transpiler[attr_name] = attr
|
|
52
|
+
elif callable(attr):
|
|
53
|
+
cls._transpiler[attr_name] = Transformer(
|
|
54
|
+
cast(Callable[..., Expr], attr), name=attr_name
|
|
55
|
+
)
|
|
56
|
+
elif isinstance(attr, (bool, int, float, str)) or attr is None:
|
|
57
|
+
cls._transpiler[attr_name] = Expr.of(attr)
|
|
32
58
|
|
|
33
59
|
@override
|
|
34
|
-
def emit(self) ->
|
|
35
|
-
|
|
60
|
+
def emit(self, out: list[str]) -> None:
|
|
61
|
+
label = self.name or "PyModule"
|
|
62
|
+
raise TypeError(f"{label} cannot be emitted directly")
|
|
36
63
|
|
|
37
64
|
@override
|
|
38
|
-
def
|
|
39
|
-
|
|
65
|
+
def render(self) -> VDOMNode:
|
|
66
|
+
label = self.name or "PyModule"
|
|
67
|
+
raise TypeError(f"{label} cannot be rendered directly")
|
|
40
68
|
|
|
41
69
|
@override
|
|
42
|
-
def
|
|
43
|
-
|
|
70
|
+
def transpile_call(
|
|
71
|
+
self,
|
|
72
|
+
args: list[ast.expr],
|
|
73
|
+
kwargs: dict[str, ast.expr],
|
|
74
|
+
ctx: Transpiler,
|
|
75
|
+
) -> Expr:
|
|
76
|
+
label = self.name or "PyModule"
|
|
77
|
+
raise TypeError(f"{label} cannot be called directly")
|
|
44
78
|
|
|
45
79
|
@override
|
|
46
|
-
def
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
raise
|
|
50
|
-
|
|
51
|
-
return value
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
class PyModule:
|
|
55
|
-
"""Base class for Python module transpilation mappings.
|
|
56
|
-
|
|
57
|
-
Subclasses define static methods and class attributes that map Python module
|
|
58
|
-
functions and constants to their JavaScript equivalents.
|
|
59
|
-
|
|
60
|
-
Example:
|
|
61
|
-
class PyMath(PyModule):
|
|
62
|
-
# Constants - JSExpr values
|
|
63
|
-
pi = JSMember(JSIdentifier("Math"), "PI")
|
|
64
|
-
|
|
65
|
-
# Functions - return JSExpr
|
|
66
|
-
@staticmethod
|
|
67
|
-
def floor(x: JSExpr) -> JSExpr:
|
|
68
|
-
return JSMemberCall(JSIdentifier("Math"), "floor", [x])
|
|
69
|
-
"""
|
|
70
|
-
|
|
80
|
+
def transpile_getattr(self, attr: str, ctx: Transpiler) -> Expr:
|
|
81
|
+
if attr not in self.transpiler:
|
|
82
|
+
label = self.name or "Module"
|
|
83
|
+
raise TypeError(f"{label} has no attribute '{attr}'")
|
|
84
|
+
return self.transpiler[attr]
|
|
71
85
|
|
|
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
|
-
|
|
86
|
+
@override
|
|
87
|
+
def transpile_subscript(self, key: ast.expr, ctx: Transpiler) -> Expr:
|
|
88
|
+
label = self.name or "PyModule"
|
|
89
|
+
raise TypeError(f"{label} cannot be subscripted")
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def _build_transpiler(items: Iterable[tuple[str, Any]]) -> dict[str, Expr]:
|
|
93
|
+
"""Build transpiler dict from name/value pairs."""
|
|
94
|
+
result: dict[str, Expr] = {}
|
|
95
|
+
for attr_name, attr in items:
|
|
96
|
+
if isinstance(attr, Expr):
|
|
97
|
+
result[attr_name] = attr
|
|
98
|
+
elif callable(attr):
|
|
99
|
+
result[attr_name] = Transformer(
|
|
100
|
+
cast(Callable[..., Expr], attr), name=attr_name
|
|
101
|
+
)
|
|
102
|
+
elif isinstance(attr, (bool, int, float, str)) or attr is None:
|
|
103
|
+
result[attr_name] = Expr.of(attr)
|
|
104
|
+
return result
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def register( # pyright: ignore[reportIncompatibleMethodOverride, reportImplicitOverride]
|
|
108
|
+
module: ModuleType,
|
|
109
|
+
transpilation: type[PyModule]
|
|
110
|
+
| dict[str, Expr | Primitive | Callable[..., Expr]],
|
|
111
|
+
) -> None:
|
|
112
|
+
"""Register a Python module for transpilation.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
module: The Python module to register (e.g., `math`)
|
|
116
|
+
transpilation: Either a PyModule subclass or a dict mapping attribute names to:
|
|
117
|
+
- Expr: used directly
|
|
118
|
+
- Primitive (bool, int, float, str, None): converted via Expr.of()
|
|
119
|
+
- Callable[..., Expr]: wrapped in Transformer
|
|
120
|
+
"""
|
|
121
|
+
# Get transpiler dict - use pre-built _transpiler for PyModule subclasses
|
|
122
|
+
if isinstance(transpilation, dict):
|
|
123
|
+
transpiler_dict = PyModule._build_transpiler(transpilation.items())
|
|
124
|
+
elif hasattr(transpilation, "_transpiler"):
|
|
125
|
+
transpiler_dict = transpilation._transpiler
|
|
108
126
|
else:
|
|
109
|
-
#
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
127
|
+
# Legacy: class namespace without PyModule inheritance
|
|
128
|
+
items = (
|
|
129
|
+
(name, getattr(transpilation, name))
|
|
130
|
+
for name in dir(transpilation)
|
|
131
|
+
if not name.startswith("_")
|
|
132
|
+
)
|
|
133
|
+
transpiler_dict = PyModule._build_transpiler(items)
|
|
134
|
+
|
|
135
|
+
# Register individual values for lookup by id
|
|
136
|
+
for attr_name, expr in transpiler_dict.items():
|
|
137
|
+
module_value = getattr(module, attr_name, None)
|
|
138
|
+
if module_value is not None:
|
|
139
|
+
Expr.register(module_value, expr)
|
|
140
|
+
|
|
141
|
+
# Register the module object itself
|
|
142
|
+
Expr.register(module, PyModule(transpiler_dict, name=module.__name__))
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""React component integration for transpiler.
|
|
2
|
+
|
|
3
|
+
In v2, client React components are represented as Expr nodes (typically Import or Member).
|
|
4
|
+
The @react_component decorator wraps an expression in Jsx(expr) to provide:
|
|
5
|
+
|
|
6
|
+
- JSX call semantics via Jsx (transpile to Element nodes)
|
|
7
|
+
- Type-safe Python call signature via .as_(fn)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from collections.abc import Callable
|
|
13
|
+
from typing import Any, ParamSpec
|
|
14
|
+
|
|
15
|
+
from pulse.transpiler.nodes import Element, Expr, Jsx, Node
|
|
16
|
+
|
|
17
|
+
P = ParamSpec("P")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def default_signature(
|
|
21
|
+
*children: Node, key: str | None = None, **props: Any
|
|
22
|
+
) -> Element: ...
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def react_component(
|
|
26
|
+
expr: Expr,
|
|
27
|
+
*,
|
|
28
|
+
lazy: bool = False,
|
|
29
|
+
):
|
|
30
|
+
"""Decorator that uses the decorated function solely as a typed signature.
|
|
31
|
+
|
|
32
|
+
Returns a Jsx(expr) that preserves the function's type signature for type
|
|
33
|
+
checkers and produces Element nodes when called in transpiled code.
|
|
34
|
+
|
|
35
|
+
Note: lazy=True is stored but not yet wired into codegen.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def decorator(fn: Callable[P, Any]) -> Callable[P, Element]:
|
|
39
|
+
if not isinstance(expr, Expr):
|
|
40
|
+
raise TypeError("react_component expects an Expr")
|
|
41
|
+
|
|
42
|
+
# Wrap expr: Jsx provides Element generation
|
|
43
|
+
jsx_wrapper = expr if isinstance(expr, Jsx) else Jsx(expr)
|
|
44
|
+
|
|
45
|
+
# Note: lazy flag is not currently wired into codegen
|
|
46
|
+
# Could store it via a separate side-registry if needed in future
|
|
47
|
+
_ = lazy # Suppress unused variable warning
|
|
48
|
+
|
|
49
|
+
return jsx_wrapper
|
|
50
|
+
|
|
51
|
+
return decorator
|