pulse-framework 0.1.62__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 +1493 -0
- pulse/_examples.py +29 -0
- pulse/app.py +1086 -0
- pulse/channel.py +607 -0
- pulse/cli/__init__.py +0 -0
- pulse/cli/cmd.py +575 -0
- pulse/cli/dependencies.py +181 -0
- pulse/cli/folder_lock.py +134 -0
- pulse/cli/helpers.py +271 -0
- pulse/cli/logging.py +102 -0
- pulse/cli/models.py +35 -0
- pulse/cli/packages.py +262 -0
- pulse/cli/processes.py +292 -0
- pulse/cli/secrets.py +39 -0
- pulse/cli/uvicorn_log_config.py +87 -0
- pulse/code_analysis.py +38 -0
- pulse/codegen/__init__.py +0 -0
- pulse/codegen/codegen.py +359 -0
- pulse/codegen/templates/__init__.py +0 -0
- pulse/codegen/templates/layout.py +106 -0
- pulse/codegen/templates/route.py +345 -0
- pulse/codegen/templates/routes_ts.py +42 -0
- pulse/codegen/utils.py +20 -0
- pulse/component.py +237 -0
- pulse/components/__init__.py +0 -0
- pulse/components/for_.py +83 -0
- pulse/components/if_.py +86 -0
- pulse/components/react_router.py +94 -0
- pulse/context.py +108 -0
- pulse/cookies.py +322 -0
- pulse/decorators.py +344 -0
- pulse/dom/__init__.py +0 -0
- pulse/dom/elements.py +1024 -0
- pulse/dom/events.py +445 -0
- pulse/dom/props.py +1250 -0
- pulse/dom/svg.py +0 -0
- pulse/dom/tags.py +328 -0
- pulse/dom/tags.pyi +480 -0
- pulse/env.py +178 -0
- pulse/form.py +538 -0
- pulse/helpers.py +541 -0
- pulse/hooks/__init__.py +0 -0
- pulse/hooks/core.py +452 -0
- pulse/hooks/effects.py +88 -0
- pulse/hooks/init.py +668 -0
- pulse/hooks/runtime.py +464 -0
- pulse/hooks/setup.py +254 -0
- pulse/hooks/stable.py +138 -0
- pulse/hooks/state.py +192 -0
- pulse/js/__init__.py +125 -0
- pulse/js/__init__.pyi +115 -0
- pulse/js/_types.py +299 -0
- pulse/js/array.py +339 -0
- pulse/js/console.py +50 -0
- pulse/js/date.py +119 -0
- pulse/js/document.py +145 -0
- pulse/js/error.py +140 -0
- pulse/js/json.py +66 -0
- pulse/js/map.py +97 -0
- pulse/js/math.py +69 -0
- pulse/js/navigator.py +79 -0
- pulse/js/number.py +57 -0
- pulse/js/obj.py +81 -0
- pulse/js/object.py +172 -0
- pulse/js/promise.py +172 -0
- pulse/js/pulse.py +115 -0
- pulse/js/react.py +495 -0
- pulse/js/regexp.py +57 -0
- pulse/js/set.py +124 -0
- pulse/js/string.py +38 -0
- pulse/js/weakmap.py +53 -0
- pulse/js/weakset.py +48 -0
- pulse/js/window.py +205 -0
- pulse/messages.py +202 -0
- pulse/middleware.py +471 -0
- pulse/plugin.py +96 -0
- pulse/proxy.py +242 -0
- pulse/py.typed +0 -0
- pulse/queries/__init__.py +0 -0
- pulse/queries/client.py +609 -0
- pulse/queries/common.py +101 -0
- pulse/queries/effect.py +55 -0
- pulse/queries/infinite_query.py +1418 -0
- pulse/queries/mutation.py +295 -0
- pulse/queries/protocol.py +136 -0
- pulse/queries/query.py +1314 -0
- pulse/queries/store.py +120 -0
- pulse/react_component.py +88 -0
- pulse/reactive.py +1208 -0
- pulse/reactive_extensions.py +1172 -0
- pulse/render_session.py +768 -0
- pulse/renderer.py +584 -0
- pulse/request.py +205 -0
- pulse/routing.py +598 -0
- pulse/serializer.py +279 -0
- pulse/state.py +556 -0
- pulse/test_helpers.py +15 -0
- pulse/transpiler/__init__.py +111 -0
- pulse/transpiler/assets.py +81 -0
- pulse/transpiler/builtins.py +1029 -0
- pulse/transpiler/dynamic_import.py +130 -0
- pulse/transpiler/emit_context.py +49 -0
- pulse/transpiler/errors.py +96 -0
- pulse/transpiler/function.py +611 -0
- pulse/transpiler/id.py +18 -0
- pulse/transpiler/imports.py +341 -0
- pulse/transpiler/js_module.py +336 -0
- pulse/transpiler/modules/__init__.py +33 -0
- pulse/transpiler/modules/asyncio.py +57 -0
- pulse/transpiler/modules/json.py +24 -0
- pulse/transpiler/modules/math.py +265 -0
- pulse/transpiler/modules/pulse/__init__.py +5 -0
- pulse/transpiler/modules/pulse/tags.py +250 -0
- pulse/transpiler/modules/typing.py +63 -0
- pulse/transpiler/nodes.py +1987 -0
- pulse/transpiler/py_module.py +135 -0
- pulse/transpiler/transpiler.py +1100 -0
- pulse/transpiler/vdom.py +256 -0
- pulse/types/__init__.py +0 -0
- pulse/types/event_handler.py +50 -0
- pulse/user_session.py +386 -0
- pulse/version.py +69 -0
- pulse_framework-0.1.62.dist-info/METADATA +198 -0
- pulse_framework-0.1.62.dist-info/RECORD +126 -0
- pulse_framework-0.1.62.dist-info/WHEEL +4 -0
- pulse_framework-0.1.62.dist-info/entry_points.txt +3 -0
pulse/queries/store.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import datetime as dt
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from typing import Any, TypeVar, cast
|
|
4
|
+
|
|
5
|
+
from pulse.helpers import MISSING
|
|
6
|
+
from pulse.queries.common import QueryKey
|
|
7
|
+
from pulse.queries.infinite_query import InfiniteQuery, Page
|
|
8
|
+
from pulse.queries.query import RETRY_DELAY_DEFAULT, KeyedQuery
|
|
9
|
+
|
|
10
|
+
T = TypeVar("T")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class QueryStore:
|
|
14
|
+
"""
|
|
15
|
+
Store for query entries. Manages creation, retrieval, and disposal of queries.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self._entries: dict[QueryKey, KeyedQuery[Any] | InfiniteQuery[Any, Any]] = {}
|
|
20
|
+
|
|
21
|
+
def items(self):
|
|
22
|
+
"""Iterate over all (key, query) pairs in the store."""
|
|
23
|
+
return self._entries.items()
|
|
24
|
+
|
|
25
|
+
def get_any(self, key: QueryKey):
|
|
26
|
+
"""Get any query (regular or infinite) by key, or None if not found."""
|
|
27
|
+
return self._entries.get(key)
|
|
28
|
+
|
|
29
|
+
def ensure(
|
|
30
|
+
self,
|
|
31
|
+
key: QueryKey,
|
|
32
|
+
initial_data: T | None = MISSING,
|
|
33
|
+
initial_data_updated_at: float | dt.datetime | None = None,
|
|
34
|
+
gc_time: float = 300.0,
|
|
35
|
+
retries: int = 3,
|
|
36
|
+
retry_delay: float = RETRY_DELAY_DEFAULT,
|
|
37
|
+
) -> KeyedQuery[T]:
|
|
38
|
+
# Return existing entry if present
|
|
39
|
+
existing = self._entries.get(key)
|
|
40
|
+
if existing:
|
|
41
|
+
if isinstance(existing, InfiniteQuery):
|
|
42
|
+
raise TypeError(
|
|
43
|
+
"Query key is already used for an infinite query; cannot reuse for regular query"
|
|
44
|
+
)
|
|
45
|
+
return cast(KeyedQuery[T], existing)
|
|
46
|
+
|
|
47
|
+
def _on_dispose(e: KeyedQuery[Any]) -> None:
|
|
48
|
+
if e.key in self._entries and self._entries[e.key] is e:
|
|
49
|
+
del self._entries[e.key]
|
|
50
|
+
|
|
51
|
+
entry = KeyedQuery(
|
|
52
|
+
key,
|
|
53
|
+
initial_data=initial_data,
|
|
54
|
+
initial_data_updated_at=initial_data_updated_at,
|
|
55
|
+
gc_time=gc_time,
|
|
56
|
+
retries=retries,
|
|
57
|
+
retry_delay=retry_delay,
|
|
58
|
+
on_dispose=_on_dispose,
|
|
59
|
+
)
|
|
60
|
+
self._entries[key] = entry
|
|
61
|
+
return entry
|
|
62
|
+
|
|
63
|
+
def get(self, key: QueryKey) -> KeyedQuery[Any] | None:
|
|
64
|
+
"""
|
|
65
|
+
Get an existing regular query by key, or None if not found.
|
|
66
|
+
"""
|
|
67
|
+
existing = self._entries.get(key)
|
|
68
|
+
if existing and isinstance(existing, InfiniteQuery):
|
|
69
|
+
return None
|
|
70
|
+
return existing
|
|
71
|
+
|
|
72
|
+
def get_infinite(self, key: QueryKey) -> InfiniteQuery[Any, Any] | None:
|
|
73
|
+
"""
|
|
74
|
+
Get an existing infinite query by key, or None if not found.
|
|
75
|
+
"""
|
|
76
|
+
existing = self._entries.get(key)
|
|
77
|
+
if existing and isinstance(existing, InfiniteQuery):
|
|
78
|
+
return existing
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
def ensure_infinite(
|
|
82
|
+
self,
|
|
83
|
+
key: QueryKey,
|
|
84
|
+
*,
|
|
85
|
+
initial_page_param: Any,
|
|
86
|
+
get_next_page_param: Callable[[list[Page[Any, Any]]], Any | None],
|
|
87
|
+
get_previous_page_param: Callable[[list[Page[Any, Any]]], Any | None]
|
|
88
|
+
| None = None,
|
|
89
|
+
max_pages: int = 0,
|
|
90
|
+
initial_data_updated_at: float | dt.datetime | None = None,
|
|
91
|
+
gc_time: float = 300.0,
|
|
92
|
+
retries: int = 3,
|
|
93
|
+
retry_delay: float = RETRY_DELAY_DEFAULT,
|
|
94
|
+
) -> InfiniteQuery[Any, Any]:
|
|
95
|
+
existing = self._entries.get(key)
|
|
96
|
+
if existing:
|
|
97
|
+
if not isinstance(existing, InfiniteQuery):
|
|
98
|
+
raise TypeError(
|
|
99
|
+
"Query key is already used for a regular query; cannot reuse for infinite query"
|
|
100
|
+
)
|
|
101
|
+
return existing
|
|
102
|
+
|
|
103
|
+
def _on_dispose(e: InfiniteQuery[Any, Any]) -> None:
|
|
104
|
+
if e.key in self._entries and self._entries[e.key] is e:
|
|
105
|
+
del self._entries[e.key]
|
|
106
|
+
|
|
107
|
+
entry = InfiniteQuery(
|
|
108
|
+
key,
|
|
109
|
+
initial_page_param=initial_page_param,
|
|
110
|
+
get_next_page_param=get_next_page_param,
|
|
111
|
+
get_previous_page_param=get_previous_page_param,
|
|
112
|
+
max_pages=max_pages,
|
|
113
|
+
initial_data_updated_at=initial_data_updated_at,
|
|
114
|
+
gc_time=gc_time,
|
|
115
|
+
retries=retries,
|
|
116
|
+
retry_delay=retry_delay,
|
|
117
|
+
on_dispose=_on_dispose,
|
|
118
|
+
)
|
|
119
|
+
self._entries[key] = entry
|
|
120
|
+
return entry
|
pulse/react_component.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""React component helpers for Python API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from typing import Any, ParamSpec, overload
|
|
7
|
+
|
|
8
|
+
from pulse.js.react import lazy as react_lazy
|
|
9
|
+
from pulse.transpiler.imports import Import
|
|
10
|
+
from pulse.transpiler.nodes import Element, Expr, Jsx, Node
|
|
11
|
+
|
|
12
|
+
P = ParamSpec("P")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def default_signature(
|
|
16
|
+
*children: Node, key: str | None = None, **props: Any
|
|
17
|
+
) -> Element: ...
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ReactComponent(Jsx):
|
|
21
|
+
"""JSX wrapper for React components with runtime call support."""
|
|
22
|
+
|
|
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
|
|
35
|
+
if not isinstance(expr, Expr):
|
|
36
|
+
raise TypeError("ReactComponent expects an Expr or source string")
|
|
37
|
+
if isinstance(expr, Jsx):
|
|
38
|
+
expr = expr.expr
|
|
39
|
+
super().__init__(expr)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@overload
|
|
43
|
+
def react_component(
|
|
44
|
+
expr_or_name: Expr,
|
|
45
|
+
) -> Callable[[Callable[P, Any]], Callable[P, Element]]: ...
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@overload
|
|
49
|
+
def react_component(
|
|
50
|
+
expr_or_name: str,
|
|
51
|
+
src: str | None = None,
|
|
52
|
+
*,
|
|
53
|
+
lazy: bool = False,
|
|
54
|
+
) -> Callable[[Callable[P, Any]], Callable[P, Element]]: ...
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def react_component(
|
|
58
|
+
expr_or_name: Expr | str,
|
|
59
|
+
src: str | None = None,
|
|
60
|
+
*,
|
|
61
|
+
lazy: bool = False,
|
|
62
|
+
) -> Callable[[Callable[P, Any]], Callable[P, Element]]:
|
|
63
|
+
"""Decorator for typed React component bindings."""
|
|
64
|
+
if isinstance(expr_or_name, Expr):
|
|
65
|
+
if src is not None:
|
|
66
|
+
raise TypeError("react_component expects (expr) or (name, src)")
|
|
67
|
+
if lazy:
|
|
68
|
+
raise TypeError("react_component lazy only supported with string inputs")
|
|
69
|
+
component = ReactComponent(expr_or_name)
|
|
70
|
+
elif isinstance(expr_or_name, str):
|
|
71
|
+
if src is None:
|
|
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)
|
|
79
|
+
else:
|
|
80
|
+
raise TypeError("react_component expects an Expr or (name, src)")
|
|
81
|
+
|
|
82
|
+
def decorator(fn: Callable[P, Any]) -> Callable[P, Element]:
|
|
83
|
+
return component.as_(fn)
|
|
84
|
+
|
|
85
|
+
return decorator
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
__all__ = ["ReactComponent", "react_component", "default_signature"]
|