pulse-framework 0.1.58__tar.gz → 0.1.59__tar.gz
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_framework-0.1.58 → pulse_framework-0.1.59}/PKG-INFO +1 -1
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/pyproject.toml +1 -1
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/app.py +0 -2
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/cli/cmd.py +7 -2
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/codegen/codegen.py +21 -8
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/components/react_router.py +2 -4
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/react.py +1 -1
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/react_component.py +23 -13
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/render_session.py +0 -1
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/dynamic_import.py +1 -1
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/imports.py +27 -20
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/js_module.py +3 -2
- pulse_framework-0.1.58/src/pulse/codegen/js.py +0 -74
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/README.md +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/__init__.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/_examples.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/channel.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/cli/__init__.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/cli/dependencies.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/cli/folder_lock.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/cli/helpers.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/cli/logging.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/cli/models.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/cli/packages.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/cli/processes.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/cli/secrets.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/cli/uvicorn_log_config.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/code_analysis.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/codegen/__init__.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/codegen/templates/__init__.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/codegen/templates/layout.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/codegen/templates/route.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/codegen/templates/routes_ts.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/codegen/utils.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/component.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/components/__init__.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/components/for_.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/components/if_.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/context.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/cookies.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/decorators.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/dom/__init__.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/dom/elements.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/dom/events.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/dom/props.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/dom/svg.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/dom/tags.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/dom/tags.pyi +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/env.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/form.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/helpers.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/hooks/__init__.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/hooks/core.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/hooks/effects.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/hooks/init.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/hooks/runtime.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/hooks/setup.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/hooks/stable.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/hooks/state.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/__init__.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/__init__.pyi +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/_types.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/array.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/console.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/date.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/document.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/error.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/json.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/map.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/math.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/navigator.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/number.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/obj.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/object.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/promise.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/pulse.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/regexp.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/set.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/string.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/weakmap.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/weakset.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/js/window.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/messages.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/middleware.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/plugin.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/proxy.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/py.typed +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/queries/__init__.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/queries/client.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/queries/common.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/queries/effect.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/queries/infinite_query.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/queries/mutation.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/queries/protocol.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/queries/query.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/queries/store.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/reactive.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/reactive_extensions.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/renderer.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/request.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/routing.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/serializer.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/state.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/test_helpers.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/__init__.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/assets.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/builtins.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/emit_context.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/errors.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/function.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/id.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/modules/__init__.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/modules/asyncio.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/modules/json.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/modules/math.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/modules/pulse/__init__.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/modules/pulse/tags.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/modules/typing.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/nodes.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/py_module.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/transpiler.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/vdom.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/types/__init__.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/types/event_handler.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/user_session.py +0 -0
- {pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/version.py +0 -0
|
@@ -823,8 +823,6 @@ class App:
|
|
|
823
823
|
async def _handle_pulse_message(
|
|
824
824
|
self, render: RenderSession, session: UserSession, msg: ClientPulseMessage
|
|
825
825
|
) -> None:
|
|
826
|
-
print(f"[MSG] {msg}")
|
|
827
|
-
|
|
828
826
|
async def _next() -> Ok[None]:
|
|
829
827
|
if msg["type"] == "attach":
|
|
830
828
|
render.attach(msg["path"], msg["routeInfo"])
|
|
@@ -398,6 +398,7 @@ def build_uvicorn_command(
|
|
|
398
398
|
on_ready: Callable[[], None] | None = None,
|
|
399
399
|
plain: bool = False,
|
|
400
400
|
) -> CommandSpec:
|
|
401
|
+
cwd = app_ctx.server_cwd or app_ctx.app_dir or Path.cwd()
|
|
401
402
|
app_import = f"{app_ctx.module_name}:{app_ctx.app_var}.asgi_factory"
|
|
402
403
|
args: list[str] = [
|
|
403
404
|
sys.executable,
|
|
@@ -418,6 +419,12 @@ def build_uvicorn_command(
|
|
|
418
419
|
args.extend(["--reload-dir", str(app_dir)])
|
|
419
420
|
if web_root.exists():
|
|
420
421
|
args.extend(["--reload-dir", str(web_root)])
|
|
422
|
+
pulse_dir = str(app_ctx.app.codegen.cfg.pulse_dir)
|
|
423
|
+
pulse_app_dir = web_root / "app" / pulse_dir
|
|
424
|
+
rel_path = Path(os.path.relpath(pulse_app_dir, cwd))
|
|
425
|
+
if not rel_path.is_absolute():
|
|
426
|
+
args.extend(["--reload-exclude", str(rel_path)])
|
|
427
|
+
args.extend(["--reload-exclude", str(rel_path / "**")])
|
|
421
428
|
|
|
422
429
|
if app_ctx.app.env == "prod":
|
|
423
430
|
args.extend(production_flags())
|
|
@@ -450,8 +457,6 @@ def build_uvicorn_command(
|
|
|
450
457
|
if dev_secret:
|
|
451
458
|
command_env[ENV_PULSE_SECRET] = dev_secret
|
|
452
459
|
|
|
453
|
-
cwd = app_ctx.server_cwd or app_ctx.app_dir or Path.cwd()
|
|
454
|
-
|
|
455
460
|
# Apply custom log config to filter noisy requests (dev/ci only)
|
|
456
461
|
if app_ctx.app.env != "prod" and not verbose:
|
|
457
462
|
import json
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
import shutil
|
|
3
2
|
from collections.abc import Sequence
|
|
4
3
|
from dataclasses import dataclass
|
|
5
4
|
from pathlib import Path
|
|
@@ -107,20 +106,26 @@ class CodegenConfig:
|
|
|
107
106
|
return self.web_root / "app" / self.pulse_dir
|
|
108
107
|
|
|
109
108
|
|
|
110
|
-
def write_file_if_changed(path: Path, content: str) -> Path:
|
|
109
|
+
def write_file_if_changed(path: Path, content: str | bytes) -> Path:
|
|
111
110
|
"""Write content to file only if it has changed."""
|
|
112
111
|
if path.exists():
|
|
113
112
|
try:
|
|
114
|
-
|
|
113
|
+
if isinstance(content, bytes):
|
|
114
|
+
current_content = path.read_bytes()
|
|
115
|
+
else:
|
|
116
|
+
current_content = path.read_text()
|
|
115
117
|
if current_content == content:
|
|
116
118
|
return path # Skip writing, content is the same
|
|
117
|
-
except Exception:
|
|
118
|
-
logging.warning(
|
|
119
|
+
except Exception as exc:
|
|
120
|
+
logging.warning("Can't read file %s: %s", path.absolute(), exc)
|
|
119
121
|
# If we can't read the file for any reason, just write it
|
|
120
122
|
pass
|
|
121
123
|
|
|
122
124
|
path.parent.mkdir(exist_ok=True, parents=True)
|
|
123
|
-
|
|
125
|
+
if isinstance(content, bytes):
|
|
126
|
+
path.write_bytes(content)
|
|
127
|
+
else:
|
|
128
|
+
path.write_text(content)
|
|
124
129
|
return path
|
|
125
130
|
|
|
126
131
|
|
|
@@ -202,9 +207,17 @@ class Codegen:
|
|
|
202
207
|
|
|
203
208
|
# Copy file if source exists
|
|
204
209
|
if asset.source_path.exists():
|
|
205
|
-
shutil.copy2(asset.source_path, dest_path)
|
|
206
210
|
self._copied_files.add(dest_path)
|
|
207
|
-
|
|
211
|
+
try:
|
|
212
|
+
content = asset.source_path.read_bytes()
|
|
213
|
+
except OSError as exc:
|
|
214
|
+
logger.warning(
|
|
215
|
+
"Can't read asset %s: %s",
|
|
216
|
+
asset.source_path,
|
|
217
|
+
exc,
|
|
218
|
+
)
|
|
219
|
+
continue
|
|
220
|
+
write_file_if_changed(dest_path, content)
|
|
208
221
|
|
|
209
222
|
def generate_layout_tsx(
|
|
210
223
|
self,
|
|
@@ -19,8 +19,7 @@ class LinkPath(TypedDict):
|
|
|
19
19
|
hash: str
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
@react_component(Import("Link", "react-router"))
|
|
22
|
+
@react_component(Import("Link", "react-router", version="^7"))
|
|
24
23
|
def Link(
|
|
25
24
|
*children: Node,
|
|
26
25
|
key: str | None = None,
|
|
@@ -69,8 +68,7 @@ def Link(
|
|
|
69
68
|
...
|
|
70
69
|
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
@react_component(Import("Outlet", "react-router"))
|
|
71
|
+
@react_component(Import("Outlet", "react-router", version="^7"))
|
|
74
72
|
def Outlet(key: str | None = None) -> None:
|
|
75
73
|
"""Renders the matched child route's element.
|
|
76
74
|
|
|
@@ -384,7 +384,7 @@ class _LazyComponentFactory(_Expr):
|
|
|
384
384
|
|
|
385
385
|
```python
|
|
386
386
|
# At definition time (Python executes this)
|
|
387
|
-
LazyChart = lazy(Import("
|
|
387
|
+
LazyChart = lazy(Import("./Chart", lazy=True))
|
|
388
388
|
|
|
389
389
|
# As reference in transpiled code
|
|
390
390
|
@javascript
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from collections.abc import Callable
|
|
6
6
|
from typing import Any, ParamSpec, overload
|
|
7
7
|
|
|
8
|
+
from pulse.js.react import lazy as react_lazy
|
|
8
9
|
from pulse.transpiler.imports import Import
|
|
9
10
|
from pulse.transpiler.nodes import Element, Expr, Jsx, Node
|
|
10
11
|
|
|
@@ -19,9 +20,20 @@ def default_signature(
|
|
|
19
20
|
class ReactComponent(Jsx):
|
|
20
21
|
"""JSX wrapper for React components with runtime call support."""
|
|
21
22
|
|
|
22
|
-
def __init__(self,
|
|
23
|
+
def __init__(self, expr_or_src: Expr | str, *, lazy: bool = False) -> None:
|
|
24
|
+
if isinstance(expr_or_src, str):
|
|
25
|
+
if lazy:
|
|
26
|
+
expr: Expr = react_lazy(Import(expr_or_src, lazy=True))
|
|
27
|
+
else:
|
|
28
|
+
expr = Import(expr_or_src)
|
|
29
|
+
else:
|
|
30
|
+
if lazy:
|
|
31
|
+
raise TypeError(
|
|
32
|
+
"ReactComponent lazy only supported with a source string"
|
|
33
|
+
)
|
|
34
|
+
expr = expr_or_src
|
|
23
35
|
if not isinstance(expr, Expr):
|
|
24
|
-
raise TypeError("ReactComponent expects an Expr")
|
|
36
|
+
raise TypeError("ReactComponent expects an Expr or source string")
|
|
25
37
|
if isinstance(expr, Jsx):
|
|
26
38
|
expr = expr.expr
|
|
27
39
|
super().__init__(expr)
|
|
@@ -36,10 +48,9 @@ def react_component(
|
|
|
36
48
|
@overload
|
|
37
49
|
def react_component(
|
|
38
50
|
expr_or_name: str,
|
|
39
|
-
src: str,
|
|
51
|
+
src: str | None = None,
|
|
40
52
|
*,
|
|
41
53
|
lazy: bool = False,
|
|
42
|
-
is_default: bool = False,
|
|
43
54
|
) -> Callable[[Callable[P, Any]], Callable[P, Element]]: ...
|
|
44
55
|
|
|
45
56
|
|
|
@@ -48,24 +59,23 @@ def react_component(
|
|
|
48
59
|
src: str | None = None,
|
|
49
60
|
*,
|
|
50
61
|
lazy: bool = False,
|
|
51
|
-
is_default: bool = False,
|
|
52
62
|
) -> Callable[[Callable[P, Any]], Callable[P, Element]]:
|
|
53
63
|
"""Decorator for typed React component bindings."""
|
|
54
64
|
if isinstance(expr_or_name, Expr):
|
|
55
65
|
if src is not None:
|
|
56
66
|
raise TypeError("react_component expects (expr) or (name, src)")
|
|
57
67
|
if lazy:
|
|
58
|
-
raise TypeError("react_component lazy only supported with
|
|
59
|
-
if is_default:
|
|
60
|
-
raise TypeError(
|
|
61
|
-
"react_component is_default only supported with (name, src)"
|
|
62
|
-
)
|
|
68
|
+
raise TypeError("react_component lazy only supported with string inputs")
|
|
63
69
|
component = ReactComponent(expr_or_name)
|
|
64
70
|
elif isinstance(expr_or_name, str):
|
|
65
71
|
if src is None:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
component = ReactComponent(expr_or_name, lazy=lazy)
|
|
73
|
+
else:
|
|
74
|
+
imp = Import(expr_or_name, src, lazy=lazy)
|
|
75
|
+
if lazy:
|
|
76
|
+
component = ReactComponent(react_lazy(imp))
|
|
77
|
+
else:
|
|
78
|
+
component = ReactComponent(imp)
|
|
69
79
|
else:
|
|
70
80
|
raise TypeError("react_component expects an Expr or (name, src)")
|
|
71
81
|
|
|
@@ -457,7 +457,6 @@ class RenderSession:
|
|
|
457
457
|
def detach(self, path: str, *, timeout: float | None = None):
|
|
458
458
|
"""Client no longer wants updates. Queue briefly, then dispose."""
|
|
459
459
|
path = ensure_absolute_path(path)
|
|
460
|
-
print(f"Detaching '{path}'")
|
|
461
460
|
mount = self.route_mounts.get(path)
|
|
462
461
|
if not mount:
|
|
463
462
|
return
|
|
@@ -11,7 +11,7 @@ For lazy-loaded React components, use Import(lazy=True) with React.lazy:
|
|
|
11
11
|
from pulse.js.react import React, lazy
|
|
12
12
|
|
|
13
13
|
# Low-level: Import(lazy=True) creates a factory, wrap with React.lazy
|
|
14
|
-
factory = Import("
|
|
14
|
+
factory = Import("./Chart", lazy=True)
|
|
15
15
|
LazyChart = Jsx(React.lazy(factory))
|
|
16
16
|
|
|
17
17
|
# High-level: lazy() helper combines both
|
|
@@ -154,14 +154,13 @@ class Import(Expr):
|
|
|
154
154
|
useState = Import("useState", "react")
|
|
155
155
|
|
|
156
156
|
# Default import: import React from "react"
|
|
157
|
-
React = Import("
|
|
157
|
+
React = Import("react")
|
|
158
158
|
|
|
159
|
-
# Namespace import: import * as
|
|
160
|
-
|
|
159
|
+
# Namespace import: import * as React from "react"
|
|
160
|
+
React = Import("*", "react")
|
|
161
161
|
|
|
162
162
|
# Side-effect import: import "./styles.css"
|
|
163
|
-
Import("
|
|
164
|
-
Import("./styles.css")
|
|
163
|
+
Import("./styles.css", side_effect=True)
|
|
165
164
|
|
|
166
165
|
# Type-only import: import type { Props } from "./types"
|
|
167
166
|
Props = Import("Props", "./types", is_type=True)
|
|
@@ -171,12 +170,12 @@ class Import(Expr):
|
|
|
171
170
|
# Button("Click me", disabled=True) -> <Button_1 disabled={true}>Click me</Button_1>
|
|
172
171
|
|
|
173
172
|
# Local file imports (relative or absolute paths)
|
|
174
|
-
Import("
|
|
175
|
-
utils = Import("
|
|
176
|
-
config = Import("
|
|
173
|
+
Import("./styles.css", side_effect=True) # Local CSS
|
|
174
|
+
utils = Import("*", "./utils") # Local JS namespace (resolves extension)
|
|
175
|
+
config = Import("/absolute/path/config") # Absolute path default import
|
|
177
176
|
|
|
178
177
|
# Lazy import (generates factory for code-splitting)
|
|
179
|
-
Chart = Import("
|
|
178
|
+
Chart = Import("./Chart", lazy=True)
|
|
180
179
|
# Generates: const Chart_1 = () => import("./Chart")
|
|
181
180
|
"""
|
|
182
181
|
|
|
@@ -197,7 +196,7 @@ class Import(Expr):
|
|
|
197
196
|
name: str,
|
|
198
197
|
src: str | None = None,
|
|
199
198
|
*,
|
|
200
|
-
|
|
199
|
+
side_effect: bool = False,
|
|
201
200
|
is_type: bool = False,
|
|
202
201
|
lazy: bool = False,
|
|
203
202
|
version: str | None = None,
|
|
@@ -205,13 +204,25 @@ class Import(Expr):
|
|
|
205
204
|
_caller_depth: int = 2,
|
|
206
205
|
) -> None:
|
|
207
206
|
if src is None:
|
|
208
|
-
if
|
|
209
|
-
raise TypeError(
|
|
210
|
-
"Import single-argument form is reserved for side effect imports"
|
|
211
|
-
)
|
|
207
|
+
if name == "*":
|
|
208
|
+
raise TypeError("Import('*') requires a source")
|
|
212
209
|
src = name
|
|
213
|
-
|
|
214
|
-
|
|
210
|
+
if side_effect:
|
|
211
|
+
name = ""
|
|
212
|
+
kind: ImportKind = "side_effect"
|
|
213
|
+
else:
|
|
214
|
+
kind = "default"
|
|
215
|
+
else:
|
|
216
|
+
if side_effect:
|
|
217
|
+
raise TypeError("side_effect imports cannot specify a name")
|
|
218
|
+
if name == "*":
|
|
219
|
+
name = src
|
|
220
|
+
kind = "namespace"
|
|
221
|
+
else:
|
|
222
|
+
if not name:
|
|
223
|
+
raise TypeError("Import(name, src) requires a non-empty name")
|
|
224
|
+
kind = "named"
|
|
225
|
+
|
|
215
226
|
# Validate: lazy imports cannot be type-only
|
|
216
227
|
if lazy and is_type:
|
|
217
228
|
raise TranspileError("Import cannot be both lazy and type-only")
|
|
@@ -229,10 +240,6 @@ class Import(Expr):
|
|
|
229
240
|
asset = register_local_asset(resolved)
|
|
230
241
|
import_src = str(resolved)
|
|
231
242
|
|
|
232
|
-
# Default kind to "named" if not specified
|
|
233
|
-
if kind is None:
|
|
234
|
-
kind = "named"
|
|
235
|
-
|
|
236
243
|
self.name = name
|
|
237
244
|
self.src = import_src
|
|
238
245
|
self.kind = kind
|
|
@@ -160,8 +160,9 @@ class JsModule(Expr):
|
|
|
160
160
|
if self.src is None:
|
|
161
161
|
return Identifier(self.name)
|
|
162
162
|
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
if self.kind == "default":
|
|
164
|
+
return Import(self.src)
|
|
165
|
+
return Import("*", self.src)
|
|
165
166
|
|
|
166
167
|
def get_value(self, name: str) -> Member | Class | Jsx | Identifier | Import:
|
|
167
168
|
"""Get a member of this module as an expression.
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
# Placeholders for the WIP JS compilation feature
|
|
2
|
-
# NOTE: This module is deprecated. Use pulse.transpiler instead.
|
|
3
|
-
|
|
4
|
-
from collections.abc import Callable
|
|
5
|
-
from typing import Generic, TypeVar, TypeVarTuple
|
|
6
|
-
|
|
7
|
-
from pulse.transpiler.imports import Import
|
|
8
|
-
|
|
9
|
-
Args = TypeVarTuple("Args")
|
|
10
|
-
R = TypeVar("R")
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class JsFunction(Generic[*Args, R]):
|
|
14
|
-
"A transpiled JS function (deprecated - use pulse.transpiler.function.JsFunction)"
|
|
15
|
-
|
|
16
|
-
name: str
|
|
17
|
-
hint: Callable[[*Args], R]
|
|
18
|
-
|
|
19
|
-
def __init__(
|
|
20
|
-
self,
|
|
21
|
-
name: str,
|
|
22
|
-
hint: Callable[[*Args], R],
|
|
23
|
-
) -> None:
|
|
24
|
-
self.name = name
|
|
25
|
-
self.hint = hint
|
|
26
|
-
|
|
27
|
-
def __call__(self, *args: *Args) -> R: ...
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class ExternalJsFunction(Generic[*Args, R]):
|
|
31
|
-
"An imported JS function (deprecated - use pulse.transpiler.imports.Import)"
|
|
32
|
-
|
|
33
|
-
import_: Import
|
|
34
|
-
hint: Callable[[*Args], R]
|
|
35
|
-
_prop: str | None
|
|
36
|
-
|
|
37
|
-
def __init__(
|
|
38
|
-
self,
|
|
39
|
-
name: str,
|
|
40
|
-
src: str,
|
|
41
|
-
*,
|
|
42
|
-
prop: str | None = None,
|
|
43
|
-
is_default: bool,
|
|
44
|
-
hint: Callable[[*Args], R],
|
|
45
|
-
) -> None:
|
|
46
|
-
kind = "default" if is_default else "named"
|
|
47
|
-
self.import_ = Import(name, src, kind=kind)
|
|
48
|
-
self._prop = prop
|
|
49
|
-
self.hint = hint
|
|
50
|
-
|
|
51
|
-
@property
|
|
52
|
-
def name(self) -> str:
|
|
53
|
-
return self.import_.name
|
|
54
|
-
|
|
55
|
-
@property
|
|
56
|
-
def src(self) -> str:
|
|
57
|
-
return self.import_.src
|
|
58
|
-
|
|
59
|
-
@property
|
|
60
|
-
def is_default(self) -> bool:
|
|
61
|
-
return self.import_.is_default
|
|
62
|
-
|
|
63
|
-
@property
|
|
64
|
-
def prop(self) -> str | None:
|
|
65
|
-
return self._prop
|
|
66
|
-
|
|
67
|
-
@property
|
|
68
|
-
def expr(self) -> str:
|
|
69
|
-
base = self.import_.js_name
|
|
70
|
-
if self._prop:
|
|
71
|
-
return f"{base}.{self._prop}"
|
|
72
|
-
return base
|
|
73
|
-
|
|
74
|
-
def __call__(self, *args: *Args) -> R: ...
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/modules/pulse/__init__.py
RENAMED
|
File without changes
|
{pulse_framework-0.1.58 → pulse_framework-0.1.59}/src/pulse/transpiler/modules/pulse/tags.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|