pulse-framework 0.1.46__py3-none-any.whl → 0.1.47__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 (71) hide show
  1. pulse/__init__.py +9 -23
  2. pulse/app.py +2 -24
  3. pulse/codegen/codegen.py +43 -88
  4. pulse/codegen/js.py +35 -5
  5. pulse/codegen/templates/route.py +341 -254
  6. pulse/form.py +1 -1
  7. pulse/helpers.py +40 -8
  8. pulse/hooks/core.py +2 -2
  9. pulse/hooks/effects.py +1 -1
  10. pulse/hooks/init.py +2 -1
  11. pulse/hooks/setup.py +1 -1
  12. pulse/hooks/stable.py +2 -2
  13. pulse/hooks/states.py +2 -2
  14. pulse/html/props.py +3 -2
  15. pulse/html/tags.py +135 -0
  16. pulse/html/tags.pyi +4 -0
  17. pulse/js/__init__.py +110 -0
  18. pulse/js/__init__.pyi +95 -0
  19. pulse/js/_types.py +297 -0
  20. pulse/js/array.py +253 -0
  21. pulse/js/console.py +47 -0
  22. pulse/js/date.py +113 -0
  23. pulse/js/document.py +138 -0
  24. pulse/js/error.py +139 -0
  25. pulse/js/json.py +62 -0
  26. pulse/js/map.py +84 -0
  27. pulse/js/math.py +66 -0
  28. pulse/js/navigator.py +76 -0
  29. pulse/js/number.py +54 -0
  30. pulse/js/object.py +173 -0
  31. pulse/js/promise.py +150 -0
  32. pulse/js/regexp.py +54 -0
  33. pulse/js/set.py +109 -0
  34. pulse/js/string.py +35 -0
  35. pulse/js/weakmap.py +50 -0
  36. pulse/js/weakset.py +45 -0
  37. pulse/js/window.py +199 -0
  38. pulse/messages.py +22 -3
  39. pulse/react_component.py +167 -14
  40. pulse/reactive_extensions.py +5 -5
  41. pulse/render_session.py +144 -34
  42. pulse/renderer.py +80 -115
  43. pulse/routing.py +1 -18
  44. pulse/transpiler/__init__.py +131 -0
  45. pulse/transpiler/builtins.py +731 -0
  46. pulse/transpiler/constants.py +110 -0
  47. pulse/transpiler/context.py +26 -0
  48. pulse/transpiler/errors.py +2 -0
  49. pulse/transpiler/function.py +250 -0
  50. pulse/transpiler/ids.py +16 -0
  51. pulse/transpiler/imports.py +409 -0
  52. pulse/transpiler/js_module.py +274 -0
  53. pulse/transpiler/modules/__init__.py +30 -0
  54. pulse/transpiler/modules/asyncio.py +38 -0
  55. pulse/transpiler/modules/json.py +20 -0
  56. pulse/transpiler/modules/math.py +320 -0
  57. pulse/transpiler/modules/re.py +466 -0
  58. pulse/transpiler/modules/tags.py +268 -0
  59. pulse/transpiler/modules/typing.py +59 -0
  60. pulse/transpiler/nodes.py +1216 -0
  61. pulse/transpiler/py_module.py +119 -0
  62. pulse/transpiler/transpiler.py +938 -0
  63. pulse/transpiler/utils.py +4 -0
  64. pulse/vdom.py +112 -6
  65. {pulse_framework-0.1.46.dist-info → pulse_framework-0.1.47.dist-info}/METADATA +1 -1
  66. pulse_framework-0.1.47.dist-info/RECORD +119 -0
  67. pulse/codegen/imports.py +0 -204
  68. pulse/css.py +0 -155
  69. pulse_framework-0.1.46.dist-info/RECORD +0 -80
  70. {pulse_framework-0.1.46.dist-info → pulse_framework-0.1.47.dist-info}/WHEEL +0 -0
  71. {pulse_framework-0.1.46.dist-info → pulse_framework-0.1.47.dist-info}/entry_points.txt +0 -0
pulse/hooks/core.py CHANGED
@@ -88,7 +88,7 @@ DEFAULT_HOOK_KEY = object()
88
88
 
89
89
 
90
90
  class HookNamespace(Generic[T]):
91
- __slots__: tuple[str, ...] = ("hook", "states")
91
+ __slots__ = ("hook", "states") # pyright: ignore[reportUnannotatedClassAttribute]
92
92
  hook: Hook[T]
93
93
 
94
94
  def __init__(self, hook: Hook[T]) -> None:
@@ -265,7 +265,7 @@ HOOK_REGISTRY: HookRegistry = HookRegistry()
265
265
 
266
266
 
267
267
  class HooksAPI:
268
- __slots__: tuple[()] = ()
268
+ __slots__ = () # pyright: ignore[reportUnannotatedClassAttribute]
269
269
 
270
270
  State: type[HookState] = HookState
271
271
  Metadata: type[HookMetadata] = HookMetadata
pulse/hooks/effects.py CHANGED
@@ -6,7 +6,7 @@ from pulse.reactive import Effect, EffectFn, Untrack
6
6
 
7
7
 
8
8
  class EffectsHookState(HookState):
9
- __slots__: tuple[str, ...] = ("initialized", "effects", "key", "_called")
9
+ __slots__ = ("initialized", "effects", "key", "_called") # pyright: ignore[reportUnannotatedClassAttribute]
10
10
  initialized: bool
11
11
  _called: bool
12
12
 
pulse/hooks/init.py CHANGED
@@ -9,6 +9,7 @@ import types
9
9
  from collections.abc import Callable, Sequence
10
10
  from typing import Any, Literal, cast, override
11
11
 
12
+ from pulse.helpers import getsourcecode
12
13
  from pulse.hooks.core import HookState, hooks
13
14
 
14
15
  # Storage keyed by (code object, lineno) of the `with ps.init()` call site.
@@ -419,7 +420,7 @@ class _InitCallChecker:
419
420
 
420
421
  def _get_source(func: Callable[..., Any]) -> str:
421
422
  try:
422
- return textwrap.dedent(inspect.getsource(func))
423
+ return textwrap.dedent(getsourcecode(func))
423
424
  except OSError as exc:
424
425
  src = getattr(func, "__source__", None)
425
426
  if src is None:
pulse/hooks/setup.py CHANGED
@@ -11,7 +11,7 @@ T = TypeVar("T")
11
11
 
12
12
 
13
13
  class SetupHookState(HookState):
14
- __slots__: tuple[str, ...] = (
14
+ __slots__ = ( # pyright: ignore[reportUnannotatedClassAttribute]
15
15
  "value",
16
16
  "initialized",
17
17
  "args",
pulse/hooks/stable.py CHANGED
@@ -8,7 +8,7 @@ TCallable = TypeVar("TCallable", bound=Callable[..., Any])
8
8
 
9
9
 
10
10
  class StableEntry:
11
- __slots__: tuple[str, ...] = ("value", "wrapper")
11
+ __slots__ = ("value", "wrapper") # pyright: ignore[reportUnannotatedClassAttribute]
12
12
  value: Any
13
13
  wrapper: Callable[..., Any]
14
14
 
@@ -25,7 +25,7 @@ class StableEntry:
25
25
 
26
26
 
27
27
  class StableRegistry(HookState):
28
- __slots__: tuple[str, ...] = ("entries",)
28
+ __slots__ = ("entries",) # pyright: ignore[reportUnannotatedClassAttribute]
29
29
 
30
30
  def __init__(self) -> None:
31
31
  super().__init__()
pulse/hooks/states.py CHANGED
@@ -18,7 +18,7 @@ S10 = TypeVar("S10", bound=State)
18
18
 
19
19
 
20
20
  class StateNamespace:
21
- __slots__: tuple[str, ...] = ("states", "key", "called")
21
+ __slots__ = ("states", "key", "called") # pyright: ignore[reportUnannotatedClassAttribute]
22
22
  states: tuple[State, ...]
23
23
  key: str | None
24
24
  called: bool
@@ -84,7 +84,7 @@ class StateNamespace:
84
84
 
85
85
 
86
86
  class StatesHookState(HookState):
87
- __slots__: tuple[str, ...] = ("namespaces",)
87
+ __slots__ = ("namespaces",) # pyright: ignore[reportUnannotatedClassAttribute]
88
88
  namespaces: dict[str | None, StateNamespace]
89
89
 
90
90
  def __init__(self) -> None:
pulse/html/props.py CHANGED
@@ -2,7 +2,6 @@
2
2
  # NOT the same thing as the properties in `elements.py` (but very similar)
3
3
  from typing import Any, Literal, TypedDict
4
4
 
5
- from pulse.css import CssReference
6
5
  from pulse.helpers import CSSProperties
7
6
  from pulse.html.elements import (
8
7
  GenericHTMLElement,
@@ -69,10 +68,12 @@ from pulse.html.events import (
69
68
  TElement,
70
69
  TextAreaDOMEvents,
71
70
  )
71
+ from pulse.transpiler.nodes import JSExpr
72
72
 
73
73
  Booleanish = Literal[True, False, "true", "false"]
74
74
  CrossOrigin = Literal["anonymous", "use-credentials", ""] | None
75
- ClassName = str | CssReference
75
+ # ClassName can be a string or any JSExpr (e.g., JSMember from CssModule.classname)
76
+ ClassName = str | JSExpr
76
77
 
77
78
 
78
79
  class BaseHTMLProps(TypedDict, total=False):
pulse/html/tags.py CHANGED
@@ -191,3 +191,138 @@ clipPath = define_tag("clipPath")
191
191
  mask = define_tag("mask")
192
192
  pattern = define_tag("pattern")
193
193
  use = define_tag("use")
194
+
195
+ # Lists of tag names/default props (referenced by JS transpiler builtins)
196
+ TAGS = [
197
+ ("a", None),
198
+ ("abbr", None),
199
+ ("address", None),
200
+ ("article", None),
201
+ ("aside", None),
202
+ ("audio", None),
203
+ ("b", None),
204
+ ("bdi", None),
205
+ ("bdo", None),
206
+ ("blockquote", None),
207
+ ("body", None),
208
+ ("button", None),
209
+ ("canvas", None),
210
+ ("caption", None),
211
+ ("cite", None),
212
+ ("code", None),
213
+ ("colgroup", None),
214
+ ("data", None),
215
+ ("datalist", None),
216
+ ("dd", None),
217
+ ("del", None),
218
+ ("details", None),
219
+ ("dfn", None),
220
+ ("dialog", None),
221
+ ("div", None),
222
+ ("dl", None),
223
+ ("dt", None),
224
+ ("em", None),
225
+ ("fieldset", None),
226
+ ("figcaption", None),
227
+ ("figure", None),
228
+ ("footer", None),
229
+ ("form", {"method": "POST"}),
230
+ ("h1", None),
231
+ ("h2", None),
232
+ ("h3", None),
233
+ ("h4", None),
234
+ ("h5", None),
235
+ ("h6", None),
236
+ ("head", None),
237
+ ("header", None),
238
+ ("hgroup", None),
239
+ ("html", None),
240
+ ("i", None),
241
+ ("iframe", None),
242
+ ("ins", None),
243
+ ("kbd", None),
244
+ ("label", None),
245
+ ("legend", None),
246
+ ("li", None),
247
+ ("main", None),
248
+ ("map", None),
249
+ ("mark", None),
250
+ ("menu", None),
251
+ ("meter", None),
252
+ ("nav", None),
253
+ ("noscript", None),
254
+ ("object", None),
255
+ ("ol", None),
256
+ ("optgroup", None),
257
+ ("option", None),
258
+ ("output", None),
259
+ ("p", None),
260
+ ("picture", None),
261
+ ("pre", None),
262
+ ("progress", None),
263
+ ("q", None),
264
+ ("rp", None),
265
+ ("rt", None),
266
+ ("ruby", None),
267
+ ("s", None),
268
+ ("samp", None),
269
+ ("script", {"type": "text/javascript"}),
270
+ ("section", None),
271
+ ("select", None),
272
+ ("small", None),
273
+ ("span", None),
274
+ ("strong", None),
275
+ ("style", {"type": "text/css"}),
276
+ ("sub", None),
277
+ ("summary", None),
278
+ ("sup", None),
279
+ ("table", None),
280
+ ("tbody", None),
281
+ ("td", None),
282
+ ("template", None),
283
+ ("textarea", None),
284
+ ("tfoot", None),
285
+ ("th", None),
286
+ ("thead", None),
287
+ ("time", None),
288
+ ("title", None),
289
+ ("tr", None),
290
+ ("u", None),
291
+ ("ul", None),
292
+ ("var", None),
293
+ ("video", None),
294
+ # SVG tags
295
+ ("svg", None),
296
+ ("circle", None),
297
+ ("ellipse", None),
298
+ ("g", None),
299
+ ("line", None),
300
+ ("path", None),
301
+ ("polygon", None),
302
+ ("polyline", None),
303
+ ("rect", None),
304
+ ("text", None),
305
+ ("tspan", None),
306
+ ("defs", None),
307
+ ("clipPath", None),
308
+ ("mask", None),
309
+ ("pattern", None),
310
+ ("use", None),
311
+ ]
312
+
313
+ SELF_CLOSING_TAGS = [
314
+ ("area", None),
315
+ ("base", None),
316
+ ("br", None),
317
+ ("col", None),
318
+ ("embed", None),
319
+ ("hr", None),
320
+ ("img", None),
321
+ ("input", None),
322
+ ("link", None),
323
+ ("meta", None),
324
+ ("param", None),
325
+ ("source", None),
326
+ ("track", None),
327
+ ("wbr", None),
328
+ ]
pulse/html/tags.pyi CHANGED
@@ -464,3 +464,7 @@ def use(
464
464
  key: str | None = None,
465
465
  **props: Unpack[HTMLSVGProps[GenericHTMLElement]],
466
466
  ) -> Node: ...
467
+
468
+ # Lists exported for JS transpiler
469
+ TAGS: list[tuple[str, dict[str, Any] | None]]
470
+ SELF_CLOSING_TAGS: list[tuple[str, dict[str, Any] | None]]
pulse/js/__init__.py ADDED
@@ -0,0 +1,110 @@
1
+ """JavaScript module bindings for use in @javascript decorated functions.
2
+
3
+ Usage:
4
+ # For builtins (no import needed in JS):
5
+ import pulse.js.math as Math
6
+ Math.PI # -> Math.PI
7
+
8
+ from pulse.js.math import floor
9
+ floor(x) # -> Math.floor(x)
10
+
11
+ # Direct import of builtins from pulse.js:
12
+ from pulse.js import Set, Number, Array, Math, Date, Promise
13
+ Set([1, 2, 3]) # -> new Set([1, 2, 3])
14
+ Number.isFinite(42) # -> Number.isFinite(42)
15
+
16
+ # Statement functions:
17
+ from pulse.js import throw
18
+ throw(Error("message")) # -> throw Error("message");
19
+
20
+ # For external modules:
21
+ from pulse.js.lodash import chunk
22
+ chunk(arr, 2) # -> import { chunk } from "lodash"; chunk(arr, 2)
23
+
24
+ import pulse.js.lodash as _
25
+ _.debounce(fn, 100) # -> import _ from "lodash"; _.debounce(fn, 100)
26
+ """
27
+
28
+ import importlib as _importlib
29
+ from typing import Any as _Any
30
+ from typing import NoReturn as _NoReturn
31
+
32
+ from pulse.transpiler.nodes import (
33
+ JSIdentifier as _JSIdentifier,
34
+ )
35
+ from pulse.transpiler.nodes import (
36
+ JSStmtExpr as _JSStmtExpr,
37
+ )
38
+ from pulse.transpiler.nodes import (
39
+ JSThrow as _JSThrow,
40
+ )
41
+ from pulse.transpiler.nodes import (
42
+ JSUndefined as _JSUndefined,
43
+ )
44
+ from pulse.transpiler.nodes import (
45
+ js_transformer as _js_transformer,
46
+ )
47
+
48
+ # Namespace modules that resolve to JSIdentifier
49
+ _MODULE_EXPORTS_IDENTIFIER: dict[str, str] = {
50
+ "JSON": "pulse.js.json",
51
+ "Math": "pulse.js.math",
52
+ "console": "pulse.js.console",
53
+ "window": "pulse.js.window",
54
+ "document": "pulse.js.document",
55
+ "navigator": "pulse.js.navigator",
56
+ }
57
+
58
+ # Regular modules that resolve via getattr
59
+ _MODULE_EXPORTS_ATTRIBUTE: dict[str, str] = {
60
+ "Array": "pulse.js.array",
61
+ "Date": "pulse.js.date",
62
+ "Error": "pulse.js.error",
63
+ "Map": "pulse.js.map",
64
+ "Object": "pulse.js.object",
65
+ "Promise": "pulse.js.promise",
66
+ "RegExp": "pulse.js.regexp",
67
+ "Set": "pulse.js.set",
68
+ "String": "pulse.js.string",
69
+ "WeakMap": "pulse.js.weakmap",
70
+ "WeakSet": "pulse.js.weakset",
71
+ "Number": "pulse.js.number",
72
+ }
73
+
74
+
75
+ # Statement-like functions (not classes/objects, but callable transformers)
76
+ @_js_transformer("throw")
77
+ def throw(x: _Any) -> _NoReturn:
78
+ # x is typed as _Any for users, but at transpile time it's JSExpr
79
+ return _JSStmtExpr(_JSThrow(x), name="throw") # pyright: ignore[reportGeneralTypeIssues]
80
+
81
+
82
+ # JS primitive values
83
+ undefined = _JSUndefined()
84
+
85
+
86
+ # Cache for exported values
87
+ _export_cache: dict[str, _Any] = {}
88
+
89
+
90
+ def __getattr__(name: str) -> _Any:
91
+ """Lazily import and return JS builtin modules.
92
+
93
+ Allows: from pulse.js import Set, Number, Array, etc.
94
+ """
95
+ # Return cached export if already imported
96
+ if name in _export_cache:
97
+ return _export_cache[name]
98
+
99
+ # Check which dict contains the name
100
+ if name in _MODULE_EXPORTS_IDENTIFIER:
101
+ module = _importlib.import_module(_MODULE_EXPORTS_IDENTIFIER[name])
102
+ export = _JSIdentifier(name)
103
+ elif name in _MODULE_EXPORTS_ATTRIBUTE:
104
+ module = _importlib.import_module(_MODULE_EXPORTS_ATTRIBUTE[name])
105
+ export = getattr(module, name)
106
+ else:
107
+ raise AttributeError(f"module 'pulse.js' has no attribute '{name}'")
108
+
109
+ _export_cache[name] = export
110
+ return export
pulse/js/__init__.pyi ADDED
@@ -0,0 +1,95 @@
1
+ """Type stubs for pulse.js module exports.
2
+
3
+ This file provides type hints for direct imports from pulse.js:
4
+ from pulse.js import Set, Number, Array, Math, Date, Promise, etc.
5
+ """
6
+
7
+ from typing import Any as _Any
8
+ from typing import NoReturn as _NoReturn
9
+
10
+ import pulse.js.console
11
+ import pulse.js.document
12
+ import pulse.js.json
13
+ import pulse.js.math
14
+ import pulse.js.navigator
15
+ import pulse.js.window
16
+
17
+ # Re-export type definitions for use in user code
18
+ from pulse.js._types import (
19
+ Clipboard as Clipboard,
20
+ )
21
+ from pulse.js._types import (
22
+ ClipboardItem as ClipboardItem,
23
+ )
24
+ from pulse.js._types import (
25
+ CSSStyleDeclaration as CSSStyleDeclaration,
26
+ )
27
+ from pulse.js._types import (
28
+ Element as Element,
29
+ )
30
+ from pulse.js._types import (
31
+ Event as Event,
32
+ )
33
+ from pulse.js._types import (
34
+ HTMLCollection as HTMLCollection,
35
+ )
36
+ from pulse.js._types import (
37
+ HTMLElement as HTMLElement,
38
+ )
39
+ from pulse.js._types import (
40
+ JSIterable as JSIterable,
41
+ )
42
+ from pulse.js._types import (
43
+ JSIterator as JSIterator,
44
+ )
45
+ from pulse.js._types import (
46
+ JSIteratorResult as JSIteratorResult,
47
+ )
48
+ from pulse.js._types import (
49
+ NodeList as NodeList,
50
+ )
51
+ from pulse.js._types import (
52
+ Range as Range,
53
+ )
54
+ from pulse.js._types import (
55
+ Selection as Selection,
56
+ )
57
+
58
+ # Re-export classes with proper generic types
59
+ from pulse.js.array import Array as Array
60
+ from pulse.js.date import Date as Date
61
+ from pulse.js.error import Error as Error
62
+ from pulse.js.error import EvalError as EvalError
63
+ from pulse.js.error import RangeError as RangeError
64
+ from pulse.js.error import ReferenceError as ReferenceError
65
+ from pulse.js.error import SyntaxError as SyntaxError
66
+ from pulse.js.error import TypeError as TypeError
67
+ from pulse.js.error import URIError as URIError
68
+ from pulse.js.map import Map as Map
69
+ from pulse.js.number import Number as Number
70
+ from pulse.js.object import Object as Object
71
+ from pulse.js.object import PropertyDescriptor as PropertyDescriptor
72
+ from pulse.js.promise import Promise as Promise
73
+ from pulse.js.promise import PromiseWithResolvers as PromiseWithResolvers
74
+ from pulse.js.regexp import RegExp as RegExp
75
+ from pulse.js.set import Set as Set
76
+ from pulse.js.string import String as String
77
+ from pulse.js.weakmap import WeakMap as WeakMap
78
+ from pulse.js.weakset import WeakSet as WeakSet
79
+ from pulse.transpiler.nodes import JSUndefined
80
+
81
+ # Re-export namespace modules
82
+ console = pulse.js.console
83
+ document = pulse.js.document
84
+ JSON = pulse.js.json
85
+ Math = pulse.js.math
86
+ navigator = pulse.js.navigator
87
+ window = pulse.js.window
88
+
89
+ # Statement-like functions
90
+ def throw(x: _Any) -> _NoReturn:
91
+ """Throw a JavaScript error."""
92
+ ...
93
+
94
+ # Primitive values
95
+ undefined: JSUndefined