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.
Files changed (84) hide show
  1. pulse/__init__.py +542 -562
  2. pulse/_examples.py +29 -0
  3. pulse/app.py +0 -14
  4. pulse/cli/cmd.py +96 -80
  5. pulse/cli/dependencies.py +10 -41
  6. pulse/cli/folder_lock.py +3 -3
  7. pulse/cli/helpers.py +40 -67
  8. pulse/cli/logging.py +102 -0
  9. pulse/cli/packages.py +16 -0
  10. pulse/cli/processes.py +40 -23
  11. pulse/codegen/codegen.py +70 -35
  12. pulse/codegen/js.py +2 -4
  13. pulse/codegen/templates/route.py +94 -146
  14. pulse/component.py +115 -0
  15. pulse/components/for_.py +1 -1
  16. pulse/components/if_.py +1 -1
  17. pulse/components/react_router.py +16 -22
  18. pulse/{html → dom}/events.py +1 -1
  19. pulse/{html → dom}/props.py +6 -6
  20. pulse/{html → dom}/tags.py +11 -11
  21. pulse/dom/tags.pyi +480 -0
  22. pulse/form.py +7 -6
  23. pulse/hooks/init.py +1 -13
  24. pulse/js/__init__.py +37 -41
  25. pulse/js/__init__.pyi +22 -2
  26. pulse/js/_types.py +5 -3
  27. pulse/js/array.py +121 -38
  28. pulse/js/console.py +9 -9
  29. pulse/js/date.py +22 -19
  30. pulse/js/document.py +8 -4
  31. pulse/js/error.py +12 -14
  32. pulse/js/json.py +4 -3
  33. pulse/js/map.py +17 -7
  34. pulse/js/math.py +2 -2
  35. pulse/js/navigator.py +4 -4
  36. pulse/js/number.py +8 -8
  37. pulse/js/object.py +9 -13
  38. pulse/js/promise.py +25 -9
  39. pulse/js/regexp.py +6 -6
  40. pulse/js/set.py +20 -8
  41. pulse/js/string.py +7 -7
  42. pulse/js/weakmap.py +6 -6
  43. pulse/js/weakset.py +6 -6
  44. pulse/js/window.py +17 -14
  45. pulse/messages.py +1 -4
  46. pulse/react_component.py +3 -1001
  47. pulse/render_session.py +74 -66
  48. pulse/renderer.py +311 -238
  49. pulse/routing.py +1 -10
  50. pulse/transpiler/__init__.py +84 -114
  51. pulse/transpiler/builtins.py +661 -343
  52. pulse/transpiler/errors.py +78 -2
  53. pulse/transpiler/function.py +463 -133
  54. pulse/transpiler/id.py +18 -0
  55. pulse/transpiler/imports.py +230 -325
  56. pulse/transpiler/js_module.py +218 -209
  57. pulse/transpiler/modules/__init__.py +16 -13
  58. pulse/transpiler/modules/asyncio.py +45 -26
  59. pulse/transpiler/modules/json.py +12 -8
  60. pulse/transpiler/modules/math.py +161 -216
  61. pulse/transpiler/modules/pulse/__init__.py +5 -0
  62. pulse/transpiler/modules/pulse/tags.py +231 -0
  63. pulse/transpiler/modules/typing.py +33 -28
  64. pulse/transpiler/nodes.py +1607 -923
  65. pulse/transpiler/py_module.py +118 -95
  66. pulse/transpiler/react_component.py +51 -0
  67. pulse/transpiler/transpiler.py +593 -437
  68. pulse/transpiler/vdom.py +255 -0
  69. {pulse_framework-0.1.51.dist-info → pulse_framework-0.1.53.dist-info}/METADATA +1 -1
  70. pulse_framework-0.1.53.dist-info/RECORD +120 -0
  71. pulse/html/tags.pyi +0 -470
  72. pulse/transpiler/constants.py +0 -110
  73. pulse/transpiler/context.py +0 -26
  74. pulse/transpiler/ids.py +0 -16
  75. pulse/transpiler/modules/re.py +0 -466
  76. pulse/transpiler/modules/tags.py +0 -268
  77. pulse/transpiler/utils.py +0 -4
  78. pulse/vdom.py +0 -599
  79. pulse_framework-0.1.51.dist-info/RECORD +0 -119
  80. /pulse/{html → dom}/__init__.py +0 -0
  81. /pulse/{html → dom}/elements.py +0 -0
  82. /pulse/{html → dom}/svg.py +0 -0
  83. {pulse_framework-0.1.51.dist-info → pulse_framework-0.1.53.dist-info}/WHEEL +0 -0
  84. {pulse_framework-0.1.51.dist-info → pulse_framework-0.1.53.dist-info}/entry_points.txt +0 -0
@@ -1,119 +1,142 @@
1
- """Python module transpilation system for javascript_v2.
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
- from collections.abc import Callable
10
- from dataclasses import dataclass
8
+ import ast
9
+ from collections.abc import Callable, Iterable
11
10
  from types import ModuleType
12
- from typing import Any, TypeAlias, cast, override
11
+ from typing import TYPE_CHECKING, Any, ClassVar, cast, override
13
12
 
14
- from pulse.transpiler.errors import JSCompilationError
15
- from pulse.transpiler.nodes import JSExpr, JSTransformer
13
+ from pulse.transpiler.nodes import Expr, Primitive, Transformer
14
+ from pulse.transpiler.vdom import VDOMNode
16
15
 
17
- # Type alias for module transpilers - either a PyModule class or a dict
18
- # The dict can contain JSExpr or Callable[..., JSExpr] during construction,
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
- @dataclass
24
- class PyModuleExpr(JSExpr):
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
- Holds a transpiler dict mapping attribute names to JSExpr.
28
- Attribute access looks up the attr in the dict and returns the result.
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
- transpiler: dict[str, JSExpr]
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) -> str:
35
- raise JSCompilationError("PyModuleExpr cannot be emitted directly")
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 emit_call(self, args: list[Any], kwargs: dict[str, Any]) -> JSExpr:
39
- raise JSCompilationError("PyModuleExpr cannot be called directly")
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 emit_subscript(self, indices: list[Any]) -> JSExpr:
43
- raise JSCompilationError("PyModuleExpr cannot be subscripted")
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 emit_getattr(self, attr: str) -> JSExpr:
47
- value = self.transpiler.get(attr)
48
- if value is None:
49
- raise JSCompilationError(f"Module has no attribute '{attr}'")
50
- # transpiler always contains JSExpr (wrapping happens in register_module)
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
- PY_MODULES: dict[ModuleType, PyModuleTranspiler] = {}
73
-
74
-
75
- def register_module(
76
- module: ModuleType,
77
- transpilation: type[PyModule] | dict[str, JSExpr | Callable[..., JSExpr]],
78
- ) -> None:
79
- """Register a Python module for transpilation.
80
-
81
- Args:
82
- module: The Python module to register (e.g., `math`, `pulse.html.tags`)
83
- transpilation: Either a PyModule subclass or a dict mapping attribute names
84
- to JSExpr (for constants) or Callable[..., JSExpr] (for functions).
85
- Callables will be wrapped in JSTransformer during registration.
86
- """
87
- # Convert PyModule class to dict if needed (wraps callables)
88
- transpiler_dict: PyModuleTranspiler = {}
89
-
90
- # Get items to iterate over - either from dict or PyModule class
91
- if isinstance(transpilation, dict):
92
- items = transpilation.items()
93
- else:
94
- # Convert PyModule class to (name, attr) pairs
95
- items = (
96
- (attr_name, getattr(transpilation, attr_name, None))
97
- for attr_name in dir(transpilation)
98
- if not attr_name.startswith("_")
99
- )
100
-
101
- # Normalize: wrap callables in JSTransformer and register via JSExpr.register
102
- for attr_name, attr in items:
103
- if isinstance(attr, JSExpr):
104
- pass
105
- elif callable(attr):
106
- # Wrap callables in JSTransformer so result always contains JSExpr
107
- attr = JSTransformer(cast(Callable[..., JSExpr], attr))
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
- # Skip non-JSExpr, non-callable values
110
- continue
111
-
112
- transpiler_dict[attr_name] = attr
113
- # Register the module attribute value for lookup by id
114
- module_value = getattr(module, attr_name, None)
115
- if module_value is not None:
116
- JSExpr.register(module_value, attr)
117
-
118
- # Store as dict (now normalized to only JSExpr)
119
- PY_MODULES[module] = transpiler_dict
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