pulse-framework 0.1.55__py3-none-any.whl → 0.1.56__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 +5 -6
- pulse/app.py +144 -57
- pulse/channel.py +139 -7
- pulse/cli/cmd.py +16 -2
- pulse/codegen/codegen.py +43 -12
- pulse/component.py +104 -0
- pulse/components/for_.py +30 -4
- pulse/components/if_.py +28 -5
- pulse/components/react_router.py +61 -3
- pulse/context.py +39 -5
- pulse/cookies.py +108 -4
- pulse/decorators.py +193 -24
- pulse/env.py +56 -2
- pulse/form.py +198 -5
- pulse/helpers.py +7 -1
- pulse/hooks/core.py +135 -5
- pulse/hooks/effects.py +61 -77
- pulse/hooks/init.py +60 -1
- pulse/hooks/runtime.py +241 -0
- pulse/hooks/setup.py +77 -0
- pulse/hooks/stable.py +58 -1
- pulse/hooks/state.py +107 -20
- pulse/js/__init__.py +40 -24
- pulse/js/array.py +9 -6
- pulse/js/console.py +15 -12
- pulse/js/date.py +9 -6
- pulse/js/document.py +5 -2
- pulse/js/error.py +7 -4
- pulse/js/json.py +9 -6
- pulse/js/map.py +8 -5
- pulse/js/math.py +9 -6
- pulse/js/navigator.py +5 -2
- pulse/js/number.py +9 -6
- pulse/js/obj.py +16 -13
- pulse/js/object.py +9 -6
- pulse/js/promise.py +19 -13
- pulse/js/pulse.py +28 -25
- pulse/js/react.py +94 -55
- pulse/js/regexp.py +7 -4
- pulse/js/set.py +8 -5
- pulse/js/string.py +9 -6
- pulse/js/weakmap.py +8 -5
- pulse/js/weakset.py +8 -5
- pulse/js/window.py +6 -3
- pulse/messages.py +5 -0
- pulse/middleware.py +147 -76
- pulse/plugin.py +76 -5
- pulse/queries/client.py +186 -39
- pulse/queries/common.py +52 -3
- pulse/queries/infinite_query.py +154 -2
- pulse/queries/mutation.py +127 -7
- pulse/queries/query.py +112 -11
- pulse/react_component.py +66 -3
- pulse/reactive.py +314 -30
- pulse/reactive_extensions.py +106 -26
- pulse/render_session.py +304 -173
- pulse/request.py +46 -11
- pulse/routing.py +140 -4
- pulse/serializer.py +71 -0
- pulse/state.py +177 -9
- pulse/test_helpers.py +15 -0
- pulse/transpiler/__init__.py +0 -3
- pulse/transpiler/py_module.py +1 -7
- pulse/user_session.py +119 -18
- {pulse_framework-0.1.55.dist-info → pulse_framework-0.1.56.dist-info}/METADATA +5 -5
- pulse_framework-0.1.56.dist-info/RECORD +127 -0
- pulse/transpiler/react_component.py +0 -44
- pulse_framework-0.1.55.dist-info/RECORD +0 -127
- {pulse_framework-0.1.55.dist-info → pulse_framework-0.1.56.dist-info}/WHEEL +0 -0
- {pulse_framework-0.1.55.dist-info → pulse_framework-0.1.56.dist-info}/entry_points.txt +0 -0
pulse/queries/mutation.py
CHANGED
|
@@ -22,9 +22,28 @@ P = ParamSpec("P")
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class MutationResult(Generic[T, P]):
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
"""Result object for mutations providing reactive state and execution.
|
|
26
|
+
|
|
27
|
+
MutationResult wraps an async mutation function and provides reactive
|
|
28
|
+
access to its execution state. It is callable to execute the mutation.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
data: The last successful mutation result, or None.
|
|
32
|
+
is_running: Whether the mutation is currently executing.
|
|
33
|
+
error: The last error encountered, or None.
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
# Access mutation state
|
|
39
|
+
if state.update_name.is_running:
|
|
40
|
+
show_loading()
|
|
41
|
+
if state.update_name.error:
|
|
42
|
+
show_error(state.update_name.error)
|
|
43
|
+
|
|
44
|
+
# Execute mutation
|
|
45
|
+
result = await state.update_name("New Name")
|
|
46
|
+
```
|
|
28
47
|
"""
|
|
29
48
|
|
|
30
49
|
_data: Signal[T | None]
|
|
@@ -40,6 +59,13 @@ class MutationResult(Generic[T, P]):
|
|
|
40
59
|
on_success: Callable[[T], Any] | None = None,
|
|
41
60
|
on_error: Callable[[Exception], Any] | None = None,
|
|
42
61
|
):
|
|
62
|
+
"""Initialize the mutation result.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
fn: The bound async function to execute.
|
|
66
|
+
on_success: Optional callback invoked on successful completion.
|
|
67
|
+
on_error: Optional callback invoked on error.
|
|
68
|
+
"""
|
|
43
69
|
self._data = Signal(None, name="mutation.data")
|
|
44
70
|
self._is_running = Signal(False, name="mutation.is_running")
|
|
45
71
|
self._error = Signal(None, name="mutation.error")
|
|
@@ -49,14 +75,17 @@ class MutationResult(Generic[T, P]):
|
|
|
49
75
|
|
|
50
76
|
@property
|
|
51
77
|
def data(self) -> T | None:
|
|
78
|
+
"""The last successful mutation result, or None if never completed."""
|
|
52
79
|
return self._data()
|
|
53
80
|
|
|
54
81
|
@property
|
|
55
82
|
def is_running(self) -> bool:
|
|
83
|
+
"""Whether the mutation is currently executing."""
|
|
56
84
|
return self._is_running()
|
|
57
85
|
|
|
58
86
|
@property
|
|
59
87
|
def error(self) -> Exception | None:
|
|
88
|
+
"""The last error encountered, or None if no error."""
|
|
60
89
|
return self._error()
|
|
61
90
|
|
|
62
91
|
async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
|
|
@@ -78,6 +107,37 @@ class MutationResult(Generic[T, P]):
|
|
|
78
107
|
|
|
79
108
|
|
|
80
109
|
class MutationProperty(Generic[T, TState, P], InitializableProperty):
|
|
110
|
+
"""Descriptor for state-bound mutations created by the @mutation decorator.
|
|
111
|
+
|
|
112
|
+
MutationProperty is the return type of the ``@mutation`` decorator. It acts
|
|
113
|
+
as a descriptor that creates and manages MutationResult instances for each
|
|
114
|
+
State object.
|
|
115
|
+
|
|
116
|
+
When accessed on a State instance, returns a MutationResult that can be
|
|
117
|
+
called to execute the mutation and provides reactive state properties.
|
|
118
|
+
|
|
119
|
+
Supports additional decorators for customization:
|
|
120
|
+
- ``@mutation_prop.on_success``: Handle successful mutation.
|
|
121
|
+
- ``@mutation_prop.on_error``: Handle mutation errors.
|
|
122
|
+
|
|
123
|
+
Example:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
class UserState(ps.State):
|
|
127
|
+
@ps.mutation
|
|
128
|
+
async def update_name(self, name: str) -> User:
|
|
129
|
+
return await api.update_user(name=name)
|
|
130
|
+
|
|
131
|
+
@update_name.on_success
|
|
132
|
+
def _on_success(self, data: User):
|
|
133
|
+
self.user.invalidate() # Refresh user query
|
|
134
|
+
|
|
135
|
+
@update_name.on_error
|
|
136
|
+
def _on_error(self, error: Exception):
|
|
137
|
+
logger.error(f"Update failed: {error}")
|
|
138
|
+
```
|
|
139
|
+
"""
|
|
140
|
+
|
|
81
141
|
_on_success_fn: Callable[[TState, T], Any] | None
|
|
82
142
|
_on_error_fn: Callable[[TState, Exception], Any] | None
|
|
83
143
|
name: str
|
|
@@ -90,13 +150,28 @@ class MutationProperty(Generic[T, TState, P], InitializableProperty):
|
|
|
90
150
|
on_success: OnSuccessFn[TState, T] | None = None,
|
|
91
151
|
on_error: OnErrorFn[TState] | None = None,
|
|
92
152
|
):
|
|
153
|
+
"""Initialize the mutation property.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
name: The method name.
|
|
157
|
+
fn: The async method to wrap.
|
|
158
|
+
on_success: Optional success callback.
|
|
159
|
+
on_error: Optional error callback.
|
|
160
|
+
"""
|
|
93
161
|
self.name = name
|
|
94
162
|
self.fn = fn
|
|
95
163
|
self._on_success_fn = on_success # pyright: ignore[reportAttributeAccessIssue]
|
|
96
164
|
self._on_error_fn = on_error # pyright: ignore[reportAttributeAccessIssue]
|
|
97
165
|
|
|
98
|
-
|
|
99
|
-
|
|
166
|
+
def on_success(self, fn: OnSuccessFn[TState, T]) -> OnSuccessFn[TState, T]:
|
|
167
|
+
"""Decorator to attach an on-success handler (sync or async).
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
fn: Callback receiving (self, data) on successful mutation.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
The callback function unchanged.
|
|
174
|
+
"""
|
|
100
175
|
if self._on_success_fn is not None:
|
|
101
176
|
raise RuntimeError(
|
|
102
177
|
f"Duplicate on_success() decorator for mutation '{self.name}'. Only one is allowed."
|
|
@@ -104,8 +179,15 @@ class MutationProperty(Generic[T, TState, P], InitializableProperty):
|
|
|
104
179
|
self._on_success_fn = fn # pyright: ignore[reportAttributeAccessIssue]
|
|
105
180
|
return fn
|
|
106
181
|
|
|
107
|
-
|
|
108
|
-
|
|
182
|
+
def on_error(self, fn: OnErrorFn[TState]) -> OnErrorFn[TState]:
|
|
183
|
+
"""Decorator to attach an on-error handler (sync or async).
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
fn: Callback receiving (self, error) on mutation failure.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
The callback function unchanged.
|
|
190
|
+
"""
|
|
109
191
|
if self._on_error_fn is not None:
|
|
110
192
|
raise RuntimeError(
|
|
111
193
|
f"Duplicate on_error() decorator for mutation '{self.name}'. Only one is allowed."
|
|
@@ -160,7 +242,45 @@ def mutation(
|
|
|
160
242
|
|
|
161
243
|
def mutation(
|
|
162
244
|
fn: Callable[Concatenate[TState, P], Awaitable[T]] | None = None,
|
|
245
|
+
) -> (
|
|
246
|
+
MutationProperty[T, TState, P]
|
|
247
|
+
| Callable[
|
|
248
|
+
[Callable[Concatenate[TState, P], Awaitable[T]]],
|
|
249
|
+
MutationProperty[T, TState, P],
|
|
250
|
+
]
|
|
163
251
|
):
|
|
252
|
+
"""Decorator for async mutations (write operations) on State methods.
|
|
253
|
+
|
|
254
|
+
Creates a mutation wrapper that tracks execution state and provides
|
|
255
|
+
callbacks for success/error handling. Unlike queries, mutations are
|
|
256
|
+
not cached and must be explicitly called.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
fn: The async method to decorate (when used without parentheses).
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
MutationProperty that creates MutationResult instances when accessed.
|
|
263
|
+
|
|
264
|
+
Example:
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
class UserState(ps.State):
|
|
268
|
+
@ps.mutation
|
|
269
|
+
async def update_name(self, name: str) -> User:
|
|
270
|
+
return await api.update_user(name=name)
|
|
271
|
+
|
|
272
|
+
@update_name.on_success
|
|
273
|
+
def _on_success(self, data: User):
|
|
274
|
+
self.user.invalidate()
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Calling the mutation:
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
result = await state.update_name("New Name")
|
|
281
|
+
```
|
|
282
|
+
"""
|
|
283
|
+
|
|
164
284
|
def decorator(func: Callable[Concatenate[TState, P], Awaitable[T]], /):
|
|
165
285
|
sig = inspect.signature(func)
|
|
166
286
|
params = list(sig.parameters.values())
|
pulse/queries/query.py
CHANGED
|
@@ -48,6 +48,20 @@ RETRY_DELAY_DEFAULT = 2.0 if not is_pytest() else 0.01
|
|
|
48
48
|
|
|
49
49
|
@dataclass(slots=True)
|
|
50
50
|
class QueryConfig(Generic[T]):
|
|
51
|
+
"""Configuration options for a query.
|
|
52
|
+
|
|
53
|
+
Stores immutable configuration that controls query behavior including
|
|
54
|
+
retry logic, caching, and lifecycle callbacks.
|
|
55
|
+
|
|
56
|
+
Attributes:
|
|
57
|
+
retries: Number of retry attempts on failure (default 3).
|
|
58
|
+
retry_delay: Delay in seconds between retry attempts (default 2.0).
|
|
59
|
+
initial_data: Initial data value or factory function.
|
|
60
|
+
initial_data_updated_at: Timestamp for initial data staleness calculation.
|
|
61
|
+
gc_time: Seconds to keep unused query in cache before garbage collection.
|
|
62
|
+
on_dispose: Callback invoked when query is disposed.
|
|
63
|
+
"""
|
|
64
|
+
|
|
51
65
|
retries: int
|
|
52
66
|
retry_delay: float
|
|
53
67
|
initial_data: T | Callable[[], T] | None
|
|
@@ -57,9 +71,20 @@ class QueryConfig(Generic[T]):
|
|
|
57
71
|
|
|
58
72
|
|
|
59
73
|
class QueryState(Generic[T]):
|
|
60
|
-
"""
|
|
61
|
-
|
|
74
|
+
"""Container for query state signals and manipulation methods.
|
|
75
|
+
|
|
76
|
+
Manages reactive signals for query data, status, errors, and retry state.
|
|
62
77
|
Used by both KeyedQuery and UnkeyedQuery via composition.
|
|
78
|
+
|
|
79
|
+
Attributes:
|
|
80
|
+
cfg: Query configuration options.
|
|
81
|
+
data: Signal containing the fetched data or None.
|
|
82
|
+
error: Signal containing the last error or None.
|
|
83
|
+
last_updated: Signal with timestamp of last successful update.
|
|
84
|
+
status: Signal with current QueryStatus ("loading", "success", "error").
|
|
85
|
+
is_fetching: Signal indicating if a fetch is in progress.
|
|
86
|
+
retries: Signal with current retry attempt count.
|
|
87
|
+
retry_reason: Signal with exception from last failed retry.
|
|
63
88
|
"""
|
|
64
89
|
|
|
65
90
|
cfg: QueryConfig[T]
|
|
@@ -900,17 +925,38 @@ class KeyedQueryResult(Generic[T], Disposable):
|
|
|
900
925
|
|
|
901
926
|
|
|
902
927
|
class QueryProperty(Generic[T, TState], InitializableProperty):
|
|
903
|
-
"""
|
|
904
|
-
|
|
928
|
+
"""Descriptor for state-bound queries created by the @query decorator.
|
|
929
|
+
|
|
930
|
+
QueryProperty is the return type of the ``@query`` decorator. It acts as a
|
|
931
|
+
descriptor that creates and manages query instances for each State object.
|
|
932
|
+
|
|
933
|
+
When accessed on a State instance, returns a QueryResult with reactive
|
|
934
|
+
properties (data, status, error) and methods (refetch, invalidate, etc.).
|
|
935
|
+
|
|
936
|
+
Supports additional decorators for customization:
|
|
937
|
+
- ``@query_prop.key``: Define dynamic query key for sharing.
|
|
938
|
+
- ``@query_prop.initial_data``: Provide initial/placeholder data.
|
|
939
|
+
- ``@query_prop.on_success``: Handle successful fetch.
|
|
940
|
+
- ``@query_prop.on_error``: Handle fetch errors.
|
|
905
941
|
|
|
906
|
-
|
|
907
|
-
class S(ps.State):
|
|
908
|
-
@ps.query()
|
|
909
|
-
async def user(self) -> User: ...
|
|
942
|
+
Example:
|
|
910
943
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
944
|
+
```python
|
|
945
|
+
class UserState(ps.State):
|
|
946
|
+
user_id: str = ""
|
|
947
|
+
|
|
948
|
+
@ps.query
|
|
949
|
+
async def user(self) -> User:
|
|
950
|
+
return await api.get_user(self.user_id)
|
|
951
|
+
|
|
952
|
+
@user.key
|
|
953
|
+
def _user_key(self):
|
|
954
|
+
return ("user", self.user_id)
|
|
955
|
+
|
|
956
|
+
@user.on_success
|
|
957
|
+
def _on_user_loaded(self, data: User):
|
|
958
|
+
print(f"Loaded user: {data.name}")
|
|
959
|
+
```
|
|
914
960
|
"""
|
|
915
961
|
|
|
916
962
|
name: str
|
|
@@ -1183,7 +1229,62 @@ def query(
|
|
|
1183
1229
|
enabled: bool = True,
|
|
1184
1230
|
fetch_on_mount: bool = True,
|
|
1185
1231
|
key: QueryKey | None = None,
|
|
1232
|
+
) -> (
|
|
1233
|
+
QueryProperty[T, TState]
|
|
1234
|
+
| Callable[[Callable[[TState], Awaitable[T]]], QueryProperty[T, TState]]
|
|
1186
1235
|
):
|
|
1236
|
+
"""Decorator for async data fetching on State methods.
|
|
1237
|
+
|
|
1238
|
+
Creates a reactive query that automatically fetches data, handles loading
|
|
1239
|
+
states, retries on failure, and caches results. Queries can be shared
|
|
1240
|
+
across components using keys.
|
|
1241
|
+
|
|
1242
|
+
Args:
|
|
1243
|
+
fn: The async method to decorate (when used without parentheses).
|
|
1244
|
+
stale_time: Seconds before data is considered stale (default 0.0).
|
|
1245
|
+
gc_time: Seconds to keep unused query in cache (default 300.0, None to disable).
|
|
1246
|
+
refetch_interval: Auto-refetch interval in seconds (default None, disabled).
|
|
1247
|
+
keep_previous_data: Keep previous data while refetching (default False).
|
|
1248
|
+
retries: Number of retry attempts on failure (default 3).
|
|
1249
|
+
retry_delay: Delay between retries in seconds (default 2.0).
|
|
1250
|
+
initial_data_updated_at: Timestamp for initial data staleness calculation.
|
|
1251
|
+
enabled: Whether query is enabled (default True).
|
|
1252
|
+
fetch_on_mount: Fetch when component mounts (default True).
|
|
1253
|
+
key: Static query key for sharing across instances.
|
|
1254
|
+
|
|
1255
|
+
Returns:
|
|
1256
|
+
QueryProperty that creates QueryResult instances when accessed.
|
|
1257
|
+
|
|
1258
|
+
Example:
|
|
1259
|
+
|
|
1260
|
+
Basic usage:
|
|
1261
|
+
|
|
1262
|
+
```python
|
|
1263
|
+
class UserState(ps.State):
|
|
1264
|
+
user_id: str = ""
|
|
1265
|
+
|
|
1266
|
+
@ps.query
|
|
1267
|
+
async def user(self) -> User:
|
|
1268
|
+
return await api.get_user(self.user_id)
|
|
1269
|
+
```
|
|
1270
|
+
|
|
1271
|
+
With options:
|
|
1272
|
+
|
|
1273
|
+
```python
|
|
1274
|
+
@ps.query(stale_time=60, refetch_interval=300)
|
|
1275
|
+
async def user(self) -> User:
|
|
1276
|
+
return await api.get_user(self.user_id)
|
|
1277
|
+
```
|
|
1278
|
+
|
|
1279
|
+
Keyed query (shared across instances):
|
|
1280
|
+
|
|
1281
|
+
```python
|
|
1282
|
+
@ps.query(key=("users", "current"))
|
|
1283
|
+
async def current_user(self) -> User:
|
|
1284
|
+
return await api.get_current_user()
|
|
1285
|
+
```
|
|
1286
|
+
"""
|
|
1287
|
+
|
|
1187
1288
|
def decorator(
|
|
1188
1289
|
func: Callable[[TState], Awaitable[T]], /
|
|
1189
1290
|
) -> QueryProperty[T, TState]:
|
pulse/react_component.py
CHANGED
|
@@ -1,5 +1,68 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""React component helpers for Python API."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from typing import Any, ParamSpec, overload
|
|
7
|
+
|
|
8
|
+
from pulse.transpiler.imports import Import
|
|
9
|
+
from pulse.transpiler.nodes import Element, Expr, Jsx, Node
|
|
10
|
+
|
|
11
|
+
P = ParamSpec("P")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def default_signature(
|
|
15
|
+
*children: Node, key: str | None = None, **props: Any
|
|
16
|
+
) -> Element: ...
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ReactComponent(Jsx):
|
|
20
|
+
"""JSX wrapper for React components with runtime call support."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, expr: Expr) -> None:
|
|
23
|
+
if not isinstance(expr, Expr):
|
|
24
|
+
raise TypeError("ReactComponent expects an Expr")
|
|
25
|
+
if isinstance(expr, Jsx):
|
|
26
|
+
expr = expr.expr
|
|
27
|
+
super().__init__(expr)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@overload
|
|
31
|
+
def react_component(
|
|
32
|
+
expr_or_name: Expr,
|
|
33
|
+
) -> Callable[[Callable[P, Any]], Callable[P, Element]]: ...
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@overload
|
|
37
|
+
def react_component(
|
|
38
|
+
expr_or_name: str, src: str, *, lazy: bool = False
|
|
39
|
+
) -> Callable[[Callable[P, Any]], Callable[P, Element]]: ...
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def react_component(
|
|
43
|
+
expr_or_name: Expr | str,
|
|
44
|
+
src: str | None = None,
|
|
45
|
+
*,
|
|
46
|
+
lazy: bool = False,
|
|
47
|
+
) -> Callable[[Callable[P, Any]], Callable[P, Element]]:
|
|
48
|
+
"""Decorator for typed React component bindings."""
|
|
49
|
+
if isinstance(expr_or_name, Expr):
|
|
50
|
+
if src is not None:
|
|
51
|
+
raise TypeError("react_component expects (expr) or (name, src)")
|
|
52
|
+
if lazy:
|
|
53
|
+
raise TypeError("react_component lazy only supported with (name, src)")
|
|
54
|
+
component = ReactComponent(expr_or_name)
|
|
55
|
+
elif isinstance(expr_or_name, str):
|
|
56
|
+
if src is None:
|
|
57
|
+
raise TypeError("react_component expects (name, src)")
|
|
58
|
+
component = ReactComponent(Import(expr_or_name, src, lazy=lazy))
|
|
59
|
+
else:
|
|
60
|
+
raise TypeError("react_component expects an Expr or (name, src)")
|
|
61
|
+
|
|
62
|
+
def decorator(fn: Callable[P, Any]) -> Callable[P, Element]:
|
|
63
|
+
return component.as_(fn)
|
|
64
|
+
|
|
65
|
+
return decorator
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
__all__ = ["ReactComponent", "react_component", "default_signature"]
|