pulse-framework 0.1.61__py3-none-any.whl → 0.1.63__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 CHANGED
@@ -1410,6 +1410,7 @@ from pulse.render_session import run_js as run_js
1410
1410
 
1411
1411
  # Request
1412
1412
  from pulse.request import PulseRequest as PulseRequest
1413
+ from pulse.requirements import require as require
1413
1414
  from pulse.routing import Layout as Layout
1414
1415
  from pulse.routing import Route as Route
1415
1416
  from pulse.routing import RouteInfo as RouteInfo
pulse/app.py CHANGED
@@ -147,7 +147,7 @@ class App:
147
147
  and lifecycle hooks.
148
148
  cookie: Session cookie configuration.
149
149
  session_store: Session storage backend. Defaults to CookieSessionStore.
150
- server_address: Public server URL. Required in production.
150
+ server_address: Public server URL. Used only in ci/prod.
151
151
  dev_server_address: Development server URL. Defaults to
152
152
  "http://localhost:8000".
153
153
  internal_server_address: Internal URL for server-side loader fetches.
@@ -243,8 +243,8 @@ class App:
243
243
  self.env = envvars.pulse_env
244
244
  self.mode = mode
245
245
  self.status = AppStatus.created
246
- # Persist the server address for use by sessions (API calls, etc.)
247
- self.server_address = server_address
246
+ # Persist the server address for use by sessions (API calls, etc.) in ci/prod.
247
+ self.server_address = server_address if self.env in ("ci", "prod") else None
248
248
  # Development server address (used in dev mode)
249
249
  self.dev_server_address = dev_server_address
250
250
  # Optional internal address used by server-side loader fetches
pulse/cli/dependencies.py CHANGED
@@ -11,11 +11,10 @@ from pulse.cli.packages import (
11
11
  is_workspace_spec,
12
12
  load_package_json,
13
13
  parse_dependency_spec,
14
- parse_install_spec,
15
14
  resolve_versions,
16
15
  spec_satisfies,
17
16
  )
18
- from pulse.transpiler.imports import get_registered_imports
17
+ from pulse.requirements import get_requirements
19
18
 
20
19
 
21
20
  def convert_pep440_to_semver(python_version: str) -> str:
@@ -98,20 +97,12 @@ def get_required_dependencies(
98
97
  "pulse-ui-client": [pulse_version],
99
98
  }
100
99
 
101
- # New transpiler v2 imports
102
- for imp in get_registered_imports():
103
- if imp.src:
104
- try:
105
- spec = parse_install_spec(imp.src)
106
- except ValueError as exc:
107
- # We might want to be more lenient here or at least log it,
108
- # but following existing pattern of raising DependencyError
109
- raise DependencyError(str(exc)) from None
110
- if spec:
111
- name_only, ver = parse_dependency_spec(spec)
112
- constraints.setdefault(name_only, []).append(ver)
113
- if imp.version:
114
- constraints.setdefault(name_only, []).append(imp.version)
100
+ for src, version in get_requirements():
101
+ name_only, ver_in_src = parse_dependency_spec(src)
102
+ if ver_in_src:
103
+ constraints.setdefault(name_only, []).append(ver_in_src)
104
+ if version:
105
+ constraints.setdefault(name_only, []).append(version)
115
106
 
116
107
  try:
117
108
  resolved = resolve_versions(constraints)
pulse/component.py CHANGED
@@ -112,7 +112,7 @@ class Component(Generic[P]):
112
112
  flattened = flatten_children(
113
113
  args, # pyright: ignore[reportArgumentType]
114
114
  parent_name=f"<{self.name}>",
115
- warn_stacklevel=4,
115
+ warn_stacklevel=None,
116
116
  )
117
117
  args = tuple(flattened) # pyright: ignore[reportAssignmentType]
118
118
 
pulse/js/obj.py CHANGED
@@ -30,7 +30,6 @@ from typing import TYPE_CHECKING, override
30
30
 
31
31
  from pulse.transpiler.errors import TranspileError
32
32
  from pulse.transpiler.nodes import Expr, Object, Spread, spread_dict
33
- from pulse.transpiler.vdom import VDOMNode
34
33
 
35
34
  # TYPE_CHECKING avoids import cycle: Transpiler -> nodes -> Expr -> obj -> Transpiler
36
35
  if TYPE_CHECKING:
@@ -53,7 +52,7 @@ class ObjTransformer(Expr):
53
52
  raise TypeError("obj cannot be emitted directly - must be called")
54
53
 
55
54
  @override
56
- def render(self) -> VDOMNode:
55
+ def render(self):
57
56
  raise TypeError("obj cannot be rendered - must be called")
58
57
 
59
58
  @override
pulse/js/react.py CHANGED
@@ -32,7 +32,6 @@ from pulse.transpiler.nodes import Call as _Call
32
32
  from pulse.transpiler.nodes import Expr as _Expr
33
33
  from pulse.transpiler.nodes import Jsx as _Jsx
34
34
  from pulse.transpiler.nodes import Node as _PulseNode
35
- from pulse.transpiler.vdom import VDOMNode as _VDOMNode
36
35
 
37
36
  if _TYPE_CHECKING:
38
37
  from pulse.transpiler.transpiler import Transpiler as _Transpiler
@@ -432,7 +431,7 @@ class _LazyComponentFactory(_Expr):
432
431
  self._import.emit(out)
433
432
 
434
433
  @_override
435
- def render(self) -> _VDOMNode:
434
+ def render(self):
436
435
  raise TypeError("lazy cannot be rendered to VDOM")
437
436
 
438
437
  @_override
pulse/renderer.py CHANGED
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import inspect
4
4
  from collections.abc import Callable, Iterable
5
5
  from dataclasses import dataclass
6
+ from types import NoneType
6
7
  from typing import Any, NamedTuple, TypeAlias, cast
7
8
 
8
9
  from pulse.helpers import values_equal
@@ -27,7 +28,6 @@ from pulse.transpiler.vdom import (
27
28
  UpdatePropsDelta,
28
29
  UpdatePropsOperation,
29
30
  VDOMElement,
30
- VDOMExpr,
31
31
  VDOMNode,
32
32
  VDOMOperation,
33
33
  VDOMPropValue,
@@ -442,17 +442,20 @@ class Renderer:
442
442
  # Expression + tag rendering
443
443
  # ------------------------------------------------------------------
444
444
 
445
- def render_tag(self, tag: str | Expr) -> str | VDOMExpr:
445
+ def render_tag(self, tag: str | Expr):
446
446
  if isinstance(tag, str):
447
447
  return tag
448
448
 
449
449
  return self.register_component_expr(tag)
450
450
 
451
- def register_component_expr(self, expr: Expr) -> str | VDOMExpr:
451
+ def register_component_expr(self, expr: Expr):
452
452
  ref = registry_ref(expr)
453
453
  if ref is not None:
454
454
  return f"{MOUNT_PREFIX}{ref['key']}"
455
- return expr.render()
455
+ tag = expr.render()
456
+ if isinstance(tag, (int, float, bool, NoneType)):
457
+ raise TypeError(f"Invalid element tag: {tag}")
458
+ return tag
456
459
 
457
460
  # ------------------------------------------------------------------
458
461
  # Unmount helper
pulse/requirements.py ADDED
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+
5
+ _REQUIREMENTS: list[tuple[str, str]] = []
6
+
7
+
8
+ def add_requirement(name: str, version: str) -> None:
9
+ if not name or not version:
10
+ return
11
+ _REQUIREMENTS.append((name, version))
12
+
13
+
14
+ def register_requirements(packages: Mapping[str, str]) -> None:
15
+ for name, version in packages.items():
16
+ if not name or not version:
17
+ continue
18
+ add_requirement(name, version)
19
+
20
+
21
+ def get_requirements() -> list[tuple[str, str]]:
22
+ return list(_REQUIREMENTS)
23
+
24
+
25
+ def clear_requirements() -> None:
26
+ _REQUIREMENTS.clear()
27
+
28
+
29
+ def require(packages: Mapping[str, str]) -> None:
30
+ """Register npm package version requirements for dependency syncing."""
31
+ if not isinstance(packages, Mapping):
32
+ raise TypeError("require expects a mapping of package names to versions")
33
+ if not packages:
34
+ return
35
+
36
+ normalized: dict[str, str] = {}
37
+ for name, version in packages.items():
38
+ if not isinstance(name, str) or not name.strip():
39
+ raise TypeError("require expects non-empty package names")
40
+ if not isinstance(version, str) or not version.strip():
41
+ raise TypeError(f"require expects a version string for {name!r}")
42
+ normalized[name.strip()] = version.strip()
43
+
44
+ register_requirements(normalized)
45
+
46
+
47
+ __all__ = ["require"]
@@ -13,6 +13,15 @@ from pathlib import Path
13
13
  from pulse.transpiler.emit_context import EmitContext
14
14
  from pulse.transpiler.id import next_id
15
15
 
16
+ _CSS_MODULE_EXTS = (
17
+ ".css",
18
+ ".scss",
19
+ ".sass",
20
+ ".less",
21
+ ".styl",
22
+ ".stylus",
23
+ )
24
+
16
25
 
17
26
  @dataclass(slots=True)
18
27
  class LocalAsset:
@@ -23,7 +32,13 @@ class LocalAsset:
23
32
 
24
33
  @property
25
34
  def asset_filename(self) -> str:
26
- """Filename in assets folder: stem_id.ext"""
35
+ """Filename in assets folder: stem_id.ext (preserve .module.*)."""
36
+ name = self.source_path.name
37
+ for ext in _CSS_MODULE_EXTS:
38
+ module_suffix = f".module{ext}"
39
+ if name.endswith(module_suffix):
40
+ base = name[: -len(module_suffix)]
41
+ return f"{base}_{self.id}{module_suffix}"
27
42
  return f"{self.source_path.stem}_{self.id}{self.source_path.suffix}"
28
43
 
29
44
  def import_path(self) -> str:
@@ -28,7 +28,6 @@ from pulse.transpiler.assets import LocalAsset, register_local_asset
28
28
  from pulse.transpiler.errors import TranspileError
29
29
  from pulse.transpiler.imports import is_local_path, resolve_local_path
30
30
  from pulse.transpiler.nodes import Expr, Member
31
- from pulse.transpiler.vdom import VDOMNode
32
31
 
33
32
  if TYPE_CHECKING:
34
33
  from pulse.transpiler.transpiler import Transpiler
@@ -56,7 +55,7 @@ class DynamicImport(Expr):
56
55
  out.append(f'import("{self.src}")')
57
56
 
58
57
  @override
59
- def render(self) -> VDOMNode:
58
+ def render(self):
60
59
  raise TypeError("DynamicImport cannot be rendered to VDOM")
61
60
 
62
61
  @override
@@ -84,7 +83,7 @@ class DynamicImportFn(Expr):
84
83
  )
85
84
 
86
85
  @override
87
- def render(self) -> VDOMNode:
86
+ def render(self):
88
87
  raise TypeError("import_ cannot be rendered to VDOM")
89
88
 
90
89
  @override
@@ -39,7 +39,7 @@ from pulse.transpiler.nodes import (
39
39
  to_js_identifier,
40
40
  )
41
41
  from pulse.transpiler.transpiler import Transpiler
42
- from pulse.transpiler.vdom import VDOMNode
42
+ from pulse.transpiler.vdom import VDOMExpr
43
43
 
44
44
  Args = TypeVarTuple("Args")
45
45
  P = ParamSpec("P")
@@ -114,7 +114,7 @@ class Constant(Expr):
114
114
  out.append(self.js_name)
115
115
 
116
116
  @override
117
- def render(self) -> VDOMNode:
117
+ def render(self) -> VDOMExpr:
118
118
  """Render as a registry reference."""
119
119
  return {"t": "ref", "key": self.id}
120
120
 
@@ -226,7 +226,7 @@ class JsFunction(Expr, Generic[*Args, R]):
226
226
  out.append(self.js_name)
227
227
 
228
228
  @override
229
- def render(self) -> VDOMNode:
229
+ def render(self) -> VDOMExpr:
230
230
  """Render as a registry reference."""
231
231
  return {"t": "ref", "key": self.id}
232
232
 
@@ -314,7 +314,7 @@ class JsxFunction(Expr, Generic[P, R]):
314
314
  out.append(self.js_name)
315
315
 
316
316
  @override
317
- def render(self) -> VDOMNode:
317
+ def render(self) -> VDOMExpr:
318
318
  """Render as a registry reference."""
319
319
  return {"t": "ref", "key": self.id}
320
320
 
@@ -14,12 +14,13 @@ from typing import (
14
14
  )
15
15
  from typing import Literal as Lit
16
16
 
17
- from pulse.cli.packages import pick_more_specific
17
+ from pulse.cli.packages import parse_dependency_spec, pick_more_specific
18
+ from pulse.requirements import add_requirement, clear_requirements
18
19
  from pulse.transpiler.assets import LocalAsset, register_local_asset
19
20
  from pulse.transpiler.errors import TranspileError
20
21
  from pulse.transpiler.id import next_id
21
22
  from pulse.transpiler.nodes import Call, Expr, to_js_identifier
22
- from pulse.transpiler.vdom import VDOMNode
23
+ from pulse.transpiler.vdom import VDOMExpr
23
24
 
24
25
  _P = ParamSpec("_P")
25
26
  _R = TypeVar("_R")
@@ -131,6 +132,14 @@ _ImportKey: TypeAlias = tuple[str, str, str, bool]
131
132
  _IMPORT_REGISTRY: dict[_ImportKey, "Import"] = {}
132
133
 
133
134
 
135
+ def _is_alias_path(path: str) -> bool:
136
+ return path.startswith("@/") or path.startswith("~/")
137
+
138
+
139
+ def _is_url(path: str) -> bool:
140
+ return path.startswith("http://") or path.startswith("https://")
141
+
142
+
134
143
  def get_registered_imports() -> list["Import"]:
135
144
  """Get all registered imports."""
136
145
  return list(_IMPORT_REGISTRY.values())
@@ -139,6 +148,7 @@ def get_registered_imports() -> list["Import"]:
139
148
  def clear_import_registry() -> None:
140
149
  """Clear the import registry."""
141
150
  _IMPORT_REGISTRY.clear()
151
+ clear_requirements()
142
152
 
143
153
 
144
154
  @dataclass(slots=True, init=False)
@@ -240,6 +250,17 @@ class Import(Expr):
240
250
  asset = register_local_asset(resolved)
241
251
  import_src = str(resolved)
242
252
 
253
+ if (
254
+ not is_local_path(import_src)
255
+ and not _is_alias_path(import_src)
256
+ and not _is_url(import_src)
257
+ ):
258
+ name_only, ver_in_src = parse_dependency_spec(import_src)
259
+ if ver_in_src:
260
+ add_requirement(name_only, ver_in_src)
261
+ if version:
262
+ add_requirement(name_only, version)
263
+
243
264
  self.name = name
244
265
  self.src = import_src
245
266
  self.kind = kind
@@ -320,7 +341,7 @@ class Import(Expr):
320
341
  out.append(self.js_name)
321
342
 
322
343
  @override
323
- def render(self) -> VDOMNode:
344
+ def render(self) -> VDOMExpr:
324
345
  """Render as a registry reference."""
325
346
  return {"t": "ref", "key": self.id}
326
347
 
@@ -23,7 +23,6 @@ from pulse.transpiler.nodes import (
23
23
  New,
24
24
  )
25
25
  from pulse.transpiler.transpiler import Transpiler
26
- from pulse.transpiler.vdom import VDOMNode
27
26
 
28
27
  _MODULE_DUNDERS = frozenset(
29
28
  {
@@ -58,7 +57,7 @@ class Class(Expr):
58
57
  self.ctor.emit(out)
59
58
 
60
59
  @override
61
- def render(self) -> VDOMNode:
60
+ def render(self):
62
61
  return self.ctor.render()
63
62
 
64
63
  @override
@@ -110,7 +109,7 @@ class JsModule(Expr):
110
109
  raise TypeError(f"{label} cannot be emitted directly - access an attribute")
111
110
 
112
111
  @override
113
- def render(self) -> VDOMNode:
112
+ def render(self):
114
113
  label = self.py_name or self.name or "JsModule"
115
114
  raise TypeError(f"{label} cannot be rendered directly - access an attribute")
116
115
 
@@ -28,7 +28,6 @@ from pulse.transpiler.nodes import (
28
28
  )
29
29
  from pulse.transpiler.py_module import PyModule
30
30
  from pulse.transpiler.transpiler import Transpiler
31
- from pulse.transpiler.vdom import VDOMNode
32
31
 
33
32
 
34
33
  @dataclass(slots=True, frozen=True)
@@ -46,7 +45,7 @@ class TagExpr(Expr):
46
45
  out.append(f'"{self.tag}"')
47
46
 
48
47
  @override
49
- def render(self) -> VDOMNode:
48
+ def render(self):
50
49
  return self.tag
51
50
 
52
51
  @override
@@ -9,7 +9,6 @@ from typing import final, override
9
9
  from pulse.transpiler.nodes import Expr
10
10
  from pulse.transpiler.py_module import PyModule
11
11
  from pulse.transpiler.transpiler import Transpiler
12
- from pulse.transpiler.vdom import VDOMNode
13
12
 
14
13
 
15
14
  @dataclass(slots=True)
@@ -30,7 +29,7 @@ class TypeHint(Expr):
30
29
  )
31
30
 
32
31
  @override
33
- def render(self) -> VDOMNode:
32
+ def render(self):
34
33
  raise TypeError(
35
34
  f"Type hint '{self.name}' cannot be rendered. "
36
35
  + "It should only be used with typing.cast() or similar."
pulse/transpiler/nodes.py CHANGED
@@ -7,7 +7,7 @@ import warnings
7
7
  from abc import ABC, abstractmethod
8
8
  from collections.abc import Callable, Iterable, Sequence
9
9
  from dataclasses import dataclass, field
10
- from inspect import isfunction, signature
10
+ from inspect import currentframe, isfunction, signature
11
11
  from typing import (
12
12
  TYPE_CHECKING,
13
13
  Any,
@@ -23,7 +23,7 @@ from typing import Literal as Lit
23
23
 
24
24
  from pulse.env import env
25
25
  from pulse.transpiler.errors import TranspileError
26
- from pulse.transpiler.vdom import VDOMNode
26
+ from pulse.transpiler.vdom import VDOMExpr, VDOMNode, VDOMPrimitive
27
27
 
28
28
  if TYPE_CHECKING:
29
29
  from pulse.transpiler.transpiler import Transpiler
@@ -132,7 +132,7 @@ class Expr(ABC):
132
132
  # -------------------------------------------------------------------------
133
133
 
134
134
  @abstractmethod
135
- def render(self) -> VDOMNode:
135
+ def render(self) -> VDOMPrimitive | VDOMExpr:
136
136
  """Serialize this expression node for client-side rendering.
137
137
 
138
138
  Returns a VDOMNode (primitive or dict) that can be JSON-serialized and
@@ -334,7 +334,7 @@ class ExprWrapper(Expr):
334
334
  self.expr.emit(out)
335
335
 
336
336
  @override
337
- def render(self) -> VDOMNode:
337
+ def render(self) -> VDOMPrimitive | VDOMExpr:
338
338
  return self.expr.render()
339
339
 
340
340
  @override
@@ -500,7 +500,7 @@ class Value(Expr):
500
500
  _emit_value(self.value, out)
501
501
 
502
502
  @override
503
- def render(self) -> VDOMNode:
503
+ def render(self) -> VDOMExpr:
504
504
  raise TypeError(
505
505
  "Value cannot be rendered as VDOMExpr; unwrap with .value instead"
506
506
  )
@@ -540,15 +540,13 @@ class Element(Expr):
540
540
  self.children = None
541
541
  else:
542
542
  if isinstance(tag, str):
543
- parent_name = tag[2:] if tag.startswith("$$") else tag
543
+ parent_name: str | Expr = tag[2:] if tag.startswith("$$") else tag
544
544
  else:
545
- tag_out: list[str] = []
546
- tag.emit(tag_out)
547
- parent_name = "".join(tag_out)
545
+ parent_name = tag
548
546
  self.children = flatten_children(
549
547
  children,
550
548
  parent_name=parent_name,
551
- warn_stacklevel=5,
549
+ warn_stacklevel=None,
552
550
  )
553
551
  self.key = key
554
552
 
@@ -733,7 +731,7 @@ class Element(Expr):
733
731
  return result
734
732
 
735
733
  @override
736
- def render(self) -> VDOMNode:
734
+ def render(self):
737
735
  """Element rendering is handled by Renderer.render_node(), not render().
738
736
 
739
737
  This method validates render-time constraints and raises TypeError
@@ -787,7 +785,7 @@ class PulseNode:
787
785
  flat = flatten_children(
788
786
  children_arg,
789
787
  parent_name=parent_name,
790
- warn_stacklevel=5,
788
+ warn_stacklevel=None,
791
789
  )
792
790
  return PulseNode(
793
791
  fn=self.fn,
@@ -804,8 +802,8 @@ class PulseNode:
804
802
  def flatten_children(
805
803
  children: Sequence[Node | Iterable[Node]],
806
804
  *,
807
- parent_name: str,
808
- warn_stacklevel: int = 5,
805
+ parent_name: str | Expr,
806
+ warn_stacklevel: int | None = None,
809
807
  ) -> list[Node]:
810
808
  if env.pulse_env == "dev":
811
809
  return _flatten_children_dev(
@@ -835,13 +833,14 @@ def _flatten_children_prod(children: Sequence[Node | Iterable[Node]]) -> list[No
835
833
  def _flatten_children_dev(
836
834
  children: Sequence[Node | Iterable[Node]],
837
835
  *,
838
- parent_name: str,
839
- warn_stacklevel: int = 5,
836
+ parent_name: str | Expr,
837
+ warn_stacklevel: int | None = None,
840
838
  ) -> list[Node]:
841
839
  flat: list[Node] = []
842
840
  seen_keys: set[str] = set()
843
841
 
844
842
  def visit(item: Node | Iterable[Node]) -> None:
843
+ nonlocal warn_stacklevel
845
844
  if isinstance(item, dict):
846
845
  raise TypeError("Dict is not a valid child")
847
846
  if isinstance(item, Iterable) and not isinstance(item, str):
@@ -853,6 +852,32 @@ def _flatten_children_dev(
853
852
  missing_key = True
854
853
  visit(sub) # type: ignore[arg-type]
855
854
  if missing_key:
855
+ if warn_stacklevel is None:
856
+ stacklevel = 1
857
+ frame = currentframe()
858
+ if frame is not None:
859
+ frame = frame.f_back
860
+ internal_prefixes = (
861
+ "pulse",
862
+ "pulse_mantine",
863
+ "pulse_ag_grid",
864
+ "pulse_recharts",
865
+ "pulse_lucide",
866
+ "pulse_msal",
867
+ "pulse_aws",
868
+ )
869
+ while frame is not None:
870
+ module = frame.f_globals.get("__name__", "")
871
+ if module and not any(
872
+ module == prefix or module.startswith(f"{prefix}.")
873
+ for prefix in internal_prefixes
874
+ ):
875
+ break
876
+ stacklevel += 1
877
+ frame = frame.f_back
878
+ if frame is not None:
879
+ stacklevel += 1
880
+ warn_stacklevel = stacklevel
856
881
  clean_name = clean_element_name(parent_name)
857
882
  warnings.warn(
858
883
  (
@@ -888,7 +913,25 @@ def _flatten_children_dev(
888
913
  return flat
889
914
 
890
915
 
891
- def clean_element_name(parent_name: str) -> str:
916
+ def clean_element_name(parent_name: str | Expr) -> str:
917
+ def expr_name(expr: Expr) -> str:
918
+ while isinstance(expr, ExprWrapper):
919
+ expr = expr.expr
920
+ if isinstance(expr, Member):
921
+ base = expr_name(expr.obj)
922
+ return f"{base}.{expr.prop}" if base else expr.prop
923
+ if isinstance(expr, Identifier):
924
+ return expr.name
925
+ if expr.__class__.__name__ == "Import":
926
+ name = getattr(expr, "name", None)
927
+ if isinstance(name, str) and name:
928
+ return name
929
+ out: list[str] = []
930
+ expr.emit(out)
931
+ return "".join(out)
932
+
933
+ if isinstance(parent_name, Expr):
934
+ parent_name = expr_name(parent_name)
892
935
  if parent_name.startswith("<") and parent_name.endswith(">"):
893
936
  return parent_name
894
937
  return f"<{parent_name}>"
@@ -916,7 +959,7 @@ class Identifier(Expr):
916
959
  out.append(self.name)
917
960
 
918
961
  @override
919
- def render(self) -> VDOMNode:
962
+ def render(self) -> VDOMExpr:
920
963
  return {"t": "id", "name": self.name}
921
964
 
922
965
 
@@ -940,7 +983,7 @@ class Literal(Expr):
940
983
  out.append(str(self.value))
941
984
 
942
985
  @override
943
- def render(self) -> VDOMNode:
986
+ def render(self) -> VDOMPrimitive:
944
987
  return self.value
945
988
 
946
989
 
@@ -958,7 +1001,7 @@ class Undefined(Expr):
958
1001
  out.append("undefined")
959
1002
 
960
1003
  @override
961
- def render(self) -> VDOMNode:
1004
+ def render(self) -> VDOMExpr:
962
1005
  return {"t": "undef"}
963
1006
 
964
1007
 
@@ -982,7 +1025,7 @@ class Array(Expr):
982
1025
  out.append("]")
983
1026
 
984
1027
  @override
985
- def render(self) -> VDOMNode:
1028
+ def render(self) -> VDOMExpr:
986
1029
  return {"t": "array", "items": [e.render() for e in self.elements]}
987
1030
 
988
1031
 
@@ -1014,7 +1057,7 @@ class Object(Expr):
1014
1057
  out.append("}")
1015
1058
 
1016
1059
  @override
1017
- def render(self) -> VDOMNode:
1060
+ def render(self) -> VDOMExpr:
1018
1061
  rendered_props: dict[str, VDOMNode] = {}
1019
1062
  for prop in self.props:
1020
1063
  if isinstance(prop, Spread):
@@ -1038,7 +1081,7 @@ class Member(Expr):
1038
1081
  out.append(self.prop)
1039
1082
 
1040
1083
  @override
1041
- def render(self) -> VDOMNode:
1084
+ def render(self) -> VDOMExpr:
1042
1085
  return {"t": "member", "obj": self.obj.render(), "prop": self.prop}
1043
1086
 
1044
1087
 
@@ -1057,7 +1100,7 @@ class Subscript(Expr):
1057
1100
  out.append("]")
1058
1101
 
1059
1102
  @override
1060
- def render(self) -> VDOMNode:
1103
+ def render(self) -> VDOMExpr:
1061
1104
  return {"t": "sub", "obj": self.obj.render(), "key": self.key.render()}
1062
1105
 
1063
1106
 
@@ -1079,7 +1122,7 @@ class Call(Expr):
1079
1122
  out.append(")")
1080
1123
 
1081
1124
  @override
1082
- def render(self) -> VDOMNode:
1125
+ def render(self) -> VDOMExpr:
1083
1126
  return {
1084
1127
  "t": "call",
1085
1128
  "callee": self.callee.render(),
@@ -1110,7 +1153,7 @@ class Unary(Expr):
1110
1153
  _emit_paren(self.operand, self.op, "unary", out)
1111
1154
 
1112
1155
  @override
1113
- def render(self) -> VDOMNode:
1156
+ def render(self) -> VDOMExpr:
1114
1157
  if self.op == "await":
1115
1158
  raise TypeError("await is not supported in VDOM expressions")
1116
1159
  return {"t": "unary", "op": self.op, "arg": self.operand.render()}
@@ -1148,7 +1191,7 @@ class Binary(Expr):
1148
1191
  _emit_paren(self.right, self.op, "right", out)
1149
1192
 
1150
1193
  @override
1151
- def render(self) -> VDOMNode:
1194
+ def render(self) -> VDOMExpr:
1152
1195
  return {
1153
1196
  "t": "binary",
1154
1197
  "op": self.op,
@@ -1178,7 +1221,7 @@ class Ternary(Expr):
1178
1221
  self.else_.emit(out)
1179
1222
 
1180
1223
  @override
1181
- def render(self) -> VDOMNode:
1224
+ def render(self) -> VDOMExpr:
1182
1225
  return {
1183
1226
  "t": "ternary",
1184
1227
  "cond": self.cond.render(),
@@ -1224,7 +1267,7 @@ class Arrow(Expr):
1224
1267
  out.append("}")
1225
1268
 
1226
1269
  @override
1227
- def render(self) -> VDOMNode:
1270
+ def render(self) -> VDOMExpr:
1228
1271
  if not isinstance(self.body, Expr):
1229
1272
  raise TypeError("Arrow with statement body cannot be rendered as VDOMExpr")
1230
1273
  return {"t": "arrow", "params": list(self.params), "body": self.body.render()}
@@ -1253,7 +1296,7 @@ class Template(Expr):
1253
1296
  out.append("`")
1254
1297
 
1255
1298
  @override
1256
- def render(self) -> VDOMNode:
1299
+ def render(self) -> VDOMExpr:
1257
1300
  rendered_parts: list[str | VDOMNode] = []
1258
1301
  for p in self.parts:
1259
1302
  if isinstance(p, str):
@@ -1275,7 +1318,7 @@ class Spread(Expr):
1275
1318
  self.expr.emit(out)
1276
1319
 
1277
1320
  @override
1278
- def render(self) -> VDOMNode:
1321
+ def render(self) -> VDOMExpr:
1279
1322
  raise TypeError("Spread cannot be rendered as VDOMExpr directly")
1280
1323
 
1281
1324
 
@@ -1313,7 +1356,7 @@ class New(Expr):
1313
1356
  out.append(")")
1314
1357
 
1315
1358
  @override
1316
- def render(self) -> VDOMNode:
1359
+ def render(self) -> VDOMExpr:
1317
1360
  return {
1318
1361
  "t": "new",
1319
1362
  "ctor": self.ctor.render(),
@@ -1377,7 +1420,7 @@ class Transformer(Expr, Generic[_F]):
1377
1420
  raise TypeError(f"{label} cannot be subscripted")
1378
1421
 
1379
1422
  @override
1380
- def render(self) -> VDOMNode:
1423
+ def render(self) -> VDOMExpr:
1381
1424
  label = self.name or "Transformer"
1382
1425
  raise TypeError(f"{label} cannot be rendered - must be called")
1383
1426
 
@@ -1677,7 +1720,7 @@ class Function(Expr):
1677
1720
  out.append("}")
1678
1721
 
1679
1722
  @override
1680
- def render(self) -> VDOMNode:
1723
+ def render(self) -> VDOMExpr:
1681
1724
  raise TypeError("Function cannot be rendered as VDOMExpr")
1682
1725
 
1683
1726
 
@@ -11,7 +11,6 @@ from types import ModuleType
11
11
  from typing import TYPE_CHECKING, Any, ClassVar, cast, override
12
12
 
13
13
  from pulse.transpiler.nodes import Expr, Primitive, Transformer
14
- from pulse.transpiler.vdom import VDOMNode
15
14
 
16
15
  if TYPE_CHECKING:
17
16
  from pulse.transpiler.transpiler import Transpiler
@@ -62,7 +61,7 @@ class PyModule(Expr):
62
61
  raise TypeError(f"{label} cannot be emitted directly")
63
62
 
64
63
  @override
65
- def render(self) -> VDOMNode:
64
+ def render(self):
66
65
  label = self.name or "PyModule"
67
66
  raise TypeError(f"{label} cannot be rendered directly")
68
67
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pulse-framework
3
- Version: 0.1.61
3
+ Version: 0.1.63
4
4
  Summary: Pulse - Full-stack framework for building real-time React applications in Python
5
5
  Requires-Dist: websockets>=12.0
6
6
  Requires-Dist: fastapi>=0.128.0
@@ -1,10 +1,10 @@
1
- pulse/__init__.py,sha256=_7WUYtxNI78XXdnVu0CFTxRInlCEaP1tGEf-WXMU48I,31993
1
+ pulse/__init__.py,sha256=hf1FFA4EDRxFNiPQoKwQfNHtnCf4UT7STqzgbjJ7dgI,32043
2
2
  pulse/_examples.py,sha256=dFuhD2EVXsbvAeexoG57s4VuN4gWLaTMOEMNYvlPm9A,561
3
- pulse/app.py,sha256=JqxDN1pR-DU_VScF7JXivJpWoHAfsmWdiNOWLoSd_rI,35095
3
+ pulse/app.py,sha256=KnP6U8uHgfBbFMguDcVk0KgjakY1UC1NJk2rS5l6Sas,35145
4
4
  pulse/channel.py,sha256=sQrDLh3k9Z8CyJQkEHzKu4h-yR4XSTgAA3OCQax3Ciw,15766
5
5
  pulse/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  pulse/cli/cmd.py,sha256=zh3Ah6c16cNg3o_v_If_S58Qe8rvxNe5M2VrTkwvDU8,15957
7
- pulse/cli/dependencies.py,sha256=ezFw-7YWnJcvB1k25b0nB_OaOaP-EKtleNtBE2oJUUk,4603
7
+ pulse/cli/dependencies.py,sha256=qU-rF7QyP0Rl1Fl0YKQubrGNBzj84BAbH1uUT3ehxik,4283
8
8
  pulse/cli/folder_lock.py,sha256=-AKld2iM91G0uHB3F5ARD0QAjOw0TmsYYGaFgy_V350,3477
9
9
  pulse/cli/helpers.py,sha256=XXRRXeGFgeq-jbp0QGFFVq_aGg_Kp7_AkYsTK8LfSdg,7810
10
10
  pulse/cli/logging.py,sha256=3uuB1dqI-lHJkodNUURN6UMWdKF5UQ9spNG-hBG7bA4,2516
@@ -21,7 +21,7 @@ pulse/codegen/templates/layout.py,sha256=nmWPQcO9SRXc3mCCVLCmykreSF96TqQfdDY7dvU
21
21
  pulse/codegen/templates/route.py,sha256=UjBrb3e_8tMkd1OjBjEsnYmK6PCQqOYZBWDuU59FcrI,9234
22
22
  pulse/codegen/templates/routes_ts.py,sha256=nPgKCvU0gzue2k6KlOL1TJgrBqqRLmyy7K_qKAI8zAE,1129
23
23
  pulse/codegen/utils.py,sha256=QoXcV-h-DLLmq_t03hDNUePS0fNnofUQLoR-TXzDFCY,539
24
- pulse/component.py,sha256=TVgV0dgNDf2Smt2xsJVD0-Wejsnqm14n-NxBzsdObjc,6227
24
+ pulse/component.py,sha256=mY4ZTX7XKXGXAiVwTec1YR3_HOJf6uTdZcxCT_WX5Gs,6230
25
25
  pulse/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  pulse/components/for_.py,sha256=lrt1JHegf4OkBbL9nrMOy7zxmbuD8Kn11x32ZGS72lY,2390
27
27
  pulse/components/if_.py,sha256=5IOq3R70B-JdI-fvDNYDyAaSEtO8L5OaiqHp-jUn-Kw,2153
@@ -60,11 +60,11 @@ pulse/js/map.py,sha256=bhw75CUMIearH4JACCs9zAffdzfla3Rae7SKGcCLGoc,2243
60
60
  pulse/js/math.py,sha256=OBbMlgoa6ZHLDgmXGNKMj5wYrvroV5ICIx-VsSE5_II,1757
61
61
  pulse/js/navigator.py,sha256=2QWSr9xyyBfgd76P472qmayXQRYXIEo-b8zLzvfhHfg,1517
62
62
  pulse/js/number.py,sha256=fX2M6hZ5ry2FPsYaHhGlqgO6reBEXw7C-gtu0-8_Zyw,1262
63
- pulse/js/obj.py,sha256=75NlJ7gsaMAXyYGXEeG5E4QKuKCy3ls9mDexodwxPyw,2085
63
+ pulse/js/obj.py,sha256=8JG9OZZ1CNqAFoMTdYtxWhTmb6zs1BqxC-nLT7KYMF8,2030
64
64
  pulse/js/object.py,sha256=95WvnGWgB-PL-D7l12UgdxNy_fxO5sJXool3Rx5ahUQ,4433
65
65
  pulse/js/promise.py,sha256=vBXcL-U9BuZN-q1jbYhyzQaOL2niDPw4LsD7q7Y_yco,4670
66
66
  pulse/js/pulse.py,sha256=m-LgqwhYygVBj7GzjeO-uo8fK5ThyVe7c3QvOJt_vc0,2962
67
- pulse/js/react.py,sha256=YGbWJcvYIrF__UW4xW1v5HcaIdSSUuDRJWOPK5DQKGw,12126
67
+ pulse/js/react.py,sha256=eRMrgM8RsoAIn2lcHDoUYas3l4tImLOW51dwmw9AxQU,12057
68
68
  pulse/js/regexp.py,sha256=qO-3nmt7uGN7V_bwimPCN-2RSsPfE6YiY7G1MjoP3YY,1055
69
69
  pulse/js/set.py,sha256=omG3g-25GRHxgoKISSB4x-M8UDFlaXtFV9cSIpd5uB0,3017
70
70
  pulse/js/string.py,sha256=VsvDF_ve8R9QIiBdDotLP2KpCKwmpEfGgRQWckOCmHk,813
@@ -89,38 +89,39 @@ pulse/react_component.py,sha256=8RLg4Bi7IcjqbnbEnp4hJpy8t1UsE7mG0UR1Q655LDk,2332
89
89
  pulse/reactive.py,sha256=FxxpH7NBtQr7G89iCVN7y1EG21f23GcRi1M-XIxcRQA,31280
90
90
  pulse/reactive_extensions.py,sha256=yQ1PpdAh4kMvll7R15T72FOg8NFdG_HGBsGc63dawYk,33754
91
91
  pulse/render_session.py,sha256=9gfwuBZRCWuQMN_nFuaAi__1UPN3I3C1mKWtAXyA3-A,21340
92
- pulse/renderer.py,sha256=vXVd63Ox7f3of9_n6bN66KE96SvOYoc-PqzlRc09xvo,15907
92
+ pulse/renderer.py,sha256=fjSsUvCqV12jyN7Y5XspKUfjQJJzKX-Chha5oF5PrAk,16001
93
93
  pulse/request.py,sha256=N0oFOLiGxpbgSgxznjvu64lG3YyOcZPKC8JFyKx6X7w,6023
94
+ pulse/requirements.py,sha256=nMnE25Uu-TUuQd88jW7m2xwus6fD-HvXxQ9UNb7OOGc,1254
94
95
  pulse/routing.py,sha256=LzTITvGgaLI1w7qTDZjFwoBcWAb4O8Dz7AmXeTNYrFU,16903
95
96
  pulse/serializer.py,sha256=HmQZgxQiaCx2SL2XwmEQLd_xsk_P8XfLtGciLLLOxx0,7616
96
97
  pulse/state.py,sha256=VMphVpYNU1CyHMMg1_kNJO3cfqLXJPAuq9gr9RYyUAw,15922
97
98
  pulse/test_helpers.py,sha256=4iO5Ymy3SMvSjh-UaAaSdqm1I_SAJMNjdY2iYVro5f8,436
98
99
  pulse/transpiler/__init__.py,sha256=wDDnzqxgHpp_OLtcgyrJEg2jVoTnFIe3SSSTOsMDW8w,4700
99
- pulse/transpiler/assets.py,sha256=FHielogI5NrFwst5H94E49YWYFX7Tp1rwJHCcowT3P0,1974
100
+ pulse/transpiler/assets.py,sha256=digd5hKYPEgLOzMtDBHULX3Adj1sfngdvnx3quQmgPY,2299
100
101
  pulse/transpiler/builtins.py,sha256=QZrow7XJ2wxGMAE-mgZmaUD03egOnXCbikOg8yMx9vQ,30807
101
- pulse/transpiler/dynamic_import.py,sha256=JECOyJMfusKYms7uwyHIqv4QabDnnPMweMqSXhp2m-4,3439
102
+ pulse/transpiler/dynamic_import.py,sha256=1AmBl6agGSoTZBp_94seXH733fewLOULUix9BOBPtKI,3372
102
103
  pulse/transpiler/emit_context.py,sha256=GyK6VdsBSTVIewQRhBagaV0hlqLTlPZ1i8EAZGi8SaY,1321
103
104
  pulse/transpiler/errors.py,sha256=LSBjLBnMglbl2D94p9JR4y-3jDefk6iHSlUVBaBOTu4,2823
104
- pulse/transpiler/function.py,sha256=Pf5eoyzKHpn3d2EvCCYtARMnn_ixpAl7IZmY1fhmDIk,17036
105
+ pulse/transpiler/function.py,sha256=a871LZFergCmjs1vr-XlOx4eU1FQKAuYxSLJej-LHHc,17036
105
106
  pulse/transpiler/id.py,sha256=CdgA1NndBpZjv0Hp4XiYbKn7wi-x4zWsFSjEiViKxVk,434
106
- pulse/transpiler/imports.py,sha256=BW2y6Ftvn21hUl-A40gnW6toBAZ0tc4gsnyEXroyDFU,9702
107
- pulse/transpiler/js_module.py,sha256=81aiJmf-BW8MGEgFggYrZ3m1FmOHShaxpR0Y5VVFQmg,11160
107
+ pulse/transpiler/imports.py,sha256=gWLjRr9jakbUzBGDEepE2RI5Xn_UZwOD4TmlqjNIapM,10302
108
+ pulse/transpiler/js_module.py,sha256=OcIgmrfiA6Hh6aukzgkyX63KsVSHdLzx5ezdKiJFUaQ,11093
108
109
  pulse/transpiler/modules/__init__.py,sha256=JGi3CuZoF4sug4dNhQg3MFhpEQqnXec4xRJM2cHNP3c,1184
109
110
  pulse/transpiler/modules/asyncio.py,sha256=kWMuFU2vZbqutCM_EXJMvy5SdlB66XiT0czs8lELj_o,1584
110
111
  pulse/transpiler/modules/json.py,sha256=Zxe8dsaQ0Eoq3yHUiJeKEx6ibiN36HCT61ScFkLFCeY,676
111
112
  pulse/transpiler/modules/math.py,sha256=8gjvdYTMqtuOnXrvX_Lwuo0ywAdSl7cpss4TMk6mQtQ,7044
112
113
  pulse/transpiler/modules/pulse/__init__.py,sha256=TfMsiiB53ZFlxdNl7jfCAiMZs-vSRUTxUmqzkLTj-po,91
113
- pulse/transpiler/modules/pulse/tags.py,sha256=YFodKrhmt4DH7QtjTBdVvK5J6Qq2ntfhzb50tP-PTac,6273
114
- pulse/transpiler/modules/typing.py,sha256=hMGff6gBscFJE2GPV9xUFNfLuff2QCMIbMJUs3f24BY,1771
115
- pulse/transpiler/nodes.py,sha256=BI6maJdQ3KXyVHB-rf4H8ffp3nWpLZ2K6ZNUXzkj2vc,50424
116
- pulse/transpiler/py_module.py,sha256=YYSeroGInkXsoy39Y34Oxj1mrwDQnbSFVEgWru7vqG0,4465
114
+ pulse/transpiler/modules/pulse/tags.py,sha256=FMN1mWMlnsXa2qO6VmXxUAhFn1uOfGoKPQOjH4ZPlRE,6218
115
+ pulse/transpiler/modules/typing.py,sha256=J9QCkXE6zzwMjiprX2q1BtK-iKLIiS21sQ78JH4RSMc,1716
116
+ pulse/transpiler/nodes.py,sha256=xJSUb0PfqyfvG85ZnUwZMzUs4JVeCJTw87biI2K2UC8,51742
117
+ pulse/transpiler/py_module.py,sha256=um4BYLrbs01bpgv2LEBHTbhXXh8Bs174c3ygv5tHHOg,4410
117
118
  pulse/transpiler/transpiler.py,sha256=28diEp1yZTs3RsUEJZZdCv1DfzgO9WyOGI-xSHe7y_4,32562
118
119
  pulse/transpiler/vdom.py,sha256=Ie36iHa2bkUbui5iMClbMSFDGlKaNxI98Ux0JLPCGT4,6399
119
120
  pulse/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
120
121
  pulse/types/event_handler.py,sha256=psQCydj-WEtBcFU5JU4mDwvyzkW8V2O0g_VFRU2EOHI,1618
121
122
  pulse/user_session.py,sha256=nsnsMgqq2xGJZLpbHRMHUHcLrElMP8WcA4gjGMrcoBk,10208
122
123
  pulse/version.py,sha256=711vaM1jVIQPgkisGgKZqwmw019qZIsc_QTae75K2pg,1895
123
- pulse_framework-0.1.61.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
124
- pulse_framework-0.1.61.dist-info/entry_points.txt,sha256=i7aohd3QaPu5IcuGKKvsQQEiMYMe5HcF56QEsaLVO64,46
125
- pulse_framework-0.1.61.dist-info/METADATA,sha256=_rF3PxyuZJ5lKy4-pEX8LP9dZrstrw46DMxMypRNgUg,8300
126
- pulse_framework-0.1.61.dist-info/RECORD,,
124
+ pulse_framework-0.1.63.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
125
+ pulse_framework-0.1.63.dist-info/entry_points.txt,sha256=i7aohd3QaPu5IcuGKKvsQQEiMYMe5HcF56QEsaLVO64,46
126
+ pulse_framework-0.1.63.dist-info/METADATA,sha256=Owdkmpwk6VaYMBTca7Xx5Fo2r2VS6lEf9ja-HB8BwtM,8300
127
+ pulse_framework-0.1.63.dist-info/RECORD,,