pulse-framework 0.1.62__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/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/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"]
@@ -14,7 +14,8 @@ 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
@@ -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
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,
@@ -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
 
@@ -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}>"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pulse-framework
3
- Version: 0.1.62
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
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
@@ -91,6 +91,7 @@ pulse/reactive_extensions.py,sha256=yQ1PpdAh4kMvll7R15T72FOg8NFdG_HGBsGc63dawYk,
91
91
  pulse/render_session.py,sha256=9gfwuBZRCWuQMN_nFuaAi__1UPN3I3C1mKWtAXyA3-A,21340
92
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
@@ -103,7 +104,7 @@ pulse/transpiler/emit_context.py,sha256=GyK6VdsBSTVIewQRhBagaV0hlqLTlPZ1i8EAZGi8
103
104
  pulse/transpiler/errors.py,sha256=LSBjLBnMglbl2D94p9JR4y-3jDefk6iHSlUVBaBOTu4,2823
104
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=RWw5HJXWwQKRCOoNCMtfBA_CQMBte5XfVTmYqXdQuaA,9702
107
+ pulse/transpiler/imports.py,sha256=gWLjRr9jakbUzBGDEepE2RI5Xn_UZwOD4TmlqjNIapM,10302
107
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
@@ -112,7 +113,7 @@ pulse/transpiler/modules/math.py,sha256=8gjvdYTMqtuOnXrvX_Lwuo0ywAdSl7cpss4TMk6m
112
113
  pulse/transpiler/modules/pulse/__init__.py,sha256=TfMsiiB53ZFlxdNl7jfCAiMZs-vSRUTxUmqzkLTj-po,91
113
114
  pulse/transpiler/modules/pulse/tags.py,sha256=FMN1mWMlnsXa2qO6VmXxUAhFn1uOfGoKPQOjH4ZPlRE,6218
114
115
  pulse/transpiler/modules/typing.py,sha256=J9QCkXE6zzwMjiprX2q1BtK-iKLIiS21sQ78JH4RSMc,1716
115
- pulse/transpiler/nodes.py,sha256=oiAoHZ-q7zmp3zenvVr1aDesyd3nPmC8Uxgm5KG7qeM,50474
116
+ pulse/transpiler/nodes.py,sha256=xJSUb0PfqyfvG85ZnUwZMzUs4JVeCJTw87biI2K2UC8,51742
116
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
@@ -120,7 +121,7 @@ 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.62.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
124
- pulse_framework-0.1.62.dist-info/entry_points.txt,sha256=i7aohd3QaPu5IcuGKKvsQQEiMYMe5HcF56QEsaLVO64,46
125
- pulse_framework-0.1.62.dist-info/METADATA,sha256=Vs73aVk7hGZuZcVSnMmuJC6BY-rMUpTXTtc6UkRsWN0,8300
126
- pulse_framework-0.1.62.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,,