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
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from collections.abc import Awaitable, Callable
|
|
3
|
+
from typing import (
|
|
4
|
+
Any,
|
|
5
|
+
Concatenate,
|
|
6
|
+
Generic,
|
|
7
|
+
ParamSpec,
|
|
8
|
+
TypeVar,
|
|
9
|
+
overload,
|
|
10
|
+
override,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from pulse.helpers import call_flexible, maybe_await
|
|
14
|
+
from pulse.queries.common import OnErrorFn, OnSuccessFn, bind_state
|
|
15
|
+
from pulse.reactive import Signal
|
|
16
|
+
from pulse.state import InitializableProperty, State
|
|
17
|
+
|
|
18
|
+
T = TypeVar("T")
|
|
19
|
+
TState = TypeVar("TState", bound=State)
|
|
20
|
+
R = TypeVar("R")
|
|
21
|
+
P = ParamSpec("P")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MutationResult(Generic[T, P]):
|
|
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
|
+
```
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
_data: Signal[T | None]
|
|
50
|
+
_is_running: Signal[bool]
|
|
51
|
+
_error: Signal[Exception | None]
|
|
52
|
+
_fn: Callable[P, Awaitable[T]]
|
|
53
|
+
_on_success: Callable[[T], Any] | None
|
|
54
|
+
_on_error: Callable[[Exception], Any] | None
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
fn: Callable[P, Awaitable[T]],
|
|
59
|
+
on_success: Callable[[T], Any] | None = None,
|
|
60
|
+
on_error: Callable[[Exception], Any] | None = None,
|
|
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
|
+
"""
|
|
69
|
+
self._data = Signal(None, name="mutation.data")
|
|
70
|
+
self._is_running = Signal(False, name="mutation.is_running")
|
|
71
|
+
self._error = Signal(None, name="mutation.error")
|
|
72
|
+
self._fn = fn
|
|
73
|
+
self._on_success = on_success
|
|
74
|
+
self._on_error = on_error
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def data(self) -> T | None:
|
|
78
|
+
"""The last successful mutation result, or None if never completed."""
|
|
79
|
+
return self._data()
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def is_running(self) -> bool:
|
|
83
|
+
"""Whether the mutation is currently executing."""
|
|
84
|
+
return self._is_running()
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def error(self) -> Exception | None:
|
|
88
|
+
"""The last error encountered, or None if no error."""
|
|
89
|
+
return self._error()
|
|
90
|
+
|
|
91
|
+
async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
|
|
92
|
+
self._is_running.write(True)
|
|
93
|
+
self._error.write(None)
|
|
94
|
+
try:
|
|
95
|
+
mutation_result = await self._fn(*args, **kwargs)
|
|
96
|
+
self._data.write(mutation_result)
|
|
97
|
+
if self._on_success:
|
|
98
|
+
await maybe_await(call_flexible(self._on_success, mutation_result))
|
|
99
|
+
return mutation_result
|
|
100
|
+
except Exception as e:
|
|
101
|
+
self._error.write(e)
|
|
102
|
+
if self._on_error:
|
|
103
|
+
await maybe_await(call_flexible(self._on_error, e))
|
|
104
|
+
raise e
|
|
105
|
+
finally:
|
|
106
|
+
self._is_running.write(False)
|
|
107
|
+
|
|
108
|
+
|
|
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
|
+
|
|
141
|
+
_on_success_fn: Callable[[TState, T], Any] | None
|
|
142
|
+
_on_error_fn: Callable[[TState, Exception], Any] | None
|
|
143
|
+
name: str
|
|
144
|
+
fn: Callable[Concatenate[TState, P], Awaitable[T]]
|
|
145
|
+
|
|
146
|
+
def __init__(
|
|
147
|
+
self,
|
|
148
|
+
name: str,
|
|
149
|
+
fn: Callable[Concatenate[TState, P], Awaitable[T]],
|
|
150
|
+
on_success: OnSuccessFn[TState, T] | None = None,
|
|
151
|
+
on_error: OnErrorFn[TState] | None = None,
|
|
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
|
+
"""
|
|
161
|
+
self.name = name
|
|
162
|
+
self.fn = fn
|
|
163
|
+
self._on_success_fn = on_success # pyright: ignore[reportAttributeAccessIssue]
|
|
164
|
+
self._on_error_fn = on_error # pyright: ignore[reportAttributeAccessIssue]
|
|
165
|
+
|
|
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
|
+
"""
|
|
175
|
+
if self._on_success_fn is not None:
|
|
176
|
+
raise RuntimeError(
|
|
177
|
+
f"Duplicate on_success() decorator for mutation '{self.name}'. Only one is allowed."
|
|
178
|
+
)
|
|
179
|
+
self._on_success_fn = fn # pyright: ignore[reportAttributeAccessIssue]
|
|
180
|
+
return fn
|
|
181
|
+
|
|
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
|
+
"""
|
|
191
|
+
if self._on_error_fn is not None:
|
|
192
|
+
raise RuntimeError(
|
|
193
|
+
f"Duplicate on_error() decorator for mutation '{self.name}'. Only one is allowed."
|
|
194
|
+
)
|
|
195
|
+
self._on_error_fn = fn # pyright: ignore[reportAttributeAccessIssue]
|
|
196
|
+
return fn
|
|
197
|
+
|
|
198
|
+
def __get__(self, obj: Any, objtype: Any = None) -> MutationResult[T, P]:
|
|
199
|
+
if obj is None:
|
|
200
|
+
return self # pyright: ignore[reportReturnType]
|
|
201
|
+
|
|
202
|
+
# Cache the result on the instance
|
|
203
|
+
cache_key = f"__mutation_{self.name}"
|
|
204
|
+
if not hasattr(obj, cache_key):
|
|
205
|
+
# Bind methods to state
|
|
206
|
+
bound_fn = bind_state(obj, self.fn)
|
|
207
|
+
bound_on_success = (
|
|
208
|
+
bind_state(obj, self._on_success_fn) if self._on_success_fn else None
|
|
209
|
+
)
|
|
210
|
+
bound_on_error = (
|
|
211
|
+
bind_state(obj, self._on_error_fn) if self._on_error_fn else None
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
result = MutationResult[T, P](
|
|
215
|
+
fn=bound_fn,
|
|
216
|
+
on_success=bound_on_success,
|
|
217
|
+
on_error=bound_on_error,
|
|
218
|
+
)
|
|
219
|
+
setattr(obj, cache_key, result)
|
|
220
|
+
|
|
221
|
+
return getattr(obj, cache_key)
|
|
222
|
+
|
|
223
|
+
@override
|
|
224
|
+
def initialize(self, state: State, name: str) -> MutationResult[T, P]:
|
|
225
|
+
# For compatibility with InitializableProperty, but mutations don't need special initialization
|
|
226
|
+
return self.__get__(state, state.__class__)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@overload
|
|
230
|
+
def mutation(
|
|
231
|
+
fn: Callable[Concatenate[TState, P], Awaitable[T]],
|
|
232
|
+
) -> MutationProperty[T, TState, P]: ...
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@overload
|
|
236
|
+
def mutation(
|
|
237
|
+
fn: None = None,
|
|
238
|
+
) -> Callable[
|
|
239
|
+
[Callable[Concatenate[TState, P], Awaitable[T]]], MutationProperty[T, TState, P]
|
|
240
|
+
]: ...
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def mutation(
|
|
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
|
+
]
|
|
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
|
+
|
|
284
|
+
def decorator(func: Callable[Concatenate[TState, P], Awaitable[T]], /):
|
|
285
|
+
sig = inspect.signature(func)
|
|
286
|
+
params = list(sig.parameters.values())
|
|
287
|
+
|
|
288
|
+
if len(params) == 0 or params[0].name != "self":
|
|
289
|
+
raise TypeError("@mutation method must have 'self' as first argument")
|
|
290
|
+
|
|
291
|
+
return MutationProperty(func.__name__, func)
|
|
292
|
+
|
|
293
|
+
if fn:
|
|
294
|
+
return decorator(fn)
|
|
295
|
+
return decorator
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import datetime as dt
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from typing import Protocol, TypeVar, runtime_checkable
|
|
4
|
+
|
|
5
|
+
from pulse.queries.common import ActionResult, QueryStatus
|
|
6
|
+
|
|
7
|
+
T = TypeVar("T")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@runtime_checkable
|
|
11
|
+
class QueryResult(Protocol[T]):
|
|
12
|
+
"""
|
|
13
|
+
Unified query result interface for both keyed and unkeyed queries.
|
|
14
|
+
|
|
15
|
+
This protocol defines the public API that all query results expose,
|
|
16
|
+
regardless of whether they use keyed (cached/shared) or unkeyed
|
|
17
|
+
(dependency-tracked) execution strategies.
|
|
18
|
+
|
|
19
|
+
Keyed queries use a session-wide cache and explicit key functions to
|
|
20
|
+
determine when to refetch. Unkeyed queries automatically track reactive
|
|
21
|
+
dependencies and refetch when those dependencies change.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
# Status properties
|
|
25
|
+
@property
|
|
26
|
+
def status(self) -> QueryStatus:
|
|
27
|
+
"""Current query status: 'loading', 'success', or 'error'."""
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def is_loading(self) -> bool:
|
|
32
|
+
"""True if the query has not yet completed its initial fetch."""
|
|
33
|
+
...
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def is_success(self) -> bool:
|
|
37
|
+
"""True if the query completed successfully."""
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def is_error(self) -> bool:
|
|
42
|
+
"""True if the query completed with an error."""
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def is_fetching(self) -> bool:
|
|
47
|
+
"""True if a fetch is currently in progress (including refetches)."""
|
|
48
|
+
...
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def is_scheduled(self) -> bool:
|
|
52
|
+
"""True if a fetch is scheduled or currently running."""
|
|
53
|
+
...
|
|
54
|
+
|
|
55
|
+
# Data properties
|
|
56
|
+
@property
|
|
57
|
+
def data(self) -> T | None:
|
|
58
|
+
"""The query result data, or None if not yet available."""
|
|
59
|
+
...
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def error(self) -> Exception | None:
|
|
63
|
+
"""The error from the last fetch, or None if no error."""
|
|
64
|
+
...
|
|
65
|
+
|
|
66
|
+
# Query operations
|
|
67
|
+
def is_stale(self) -> bool:
|
|
68
|
+
"""Check if the query data is stale based on stale_time."""
|
|
69
|
+
...
|
|
70
|
+
|
|
71
|
+
async def refetch(self, cancel_refetch: bool = True) -> ActionResult[T]:
|
|
72
|
+
"""
|
|
73
|
+
Refetch the query data.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
cancel_refetch: If True (default), cancels any in-flight request
|
|
77
|
+
before starting a new one. If False, deduplicates requests.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
ActionResult containing either the data or an error.
|
|
81
|
+
"""
|
|
82
|
+
...
|
|
83
|
+
|
|
84
|
+
async def wait(self) -> ActionResult[T]:
|
|
85
|
+
"""
|
|
86
|
+
Wait for the current fetch to complete.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
ActionResult containing either the data or an error.
|
|
90
|
+
"""
|
|
91
|
+
...
|
|
92
|
+
|
|
93
|
+
def invalidate(self) -> None:
|
|
94
|
+
"""Mark the query as stale and trigger a refetch if observed."""
|
|
95
|
+
...
|
|
96
|
+
|
|
97
|
+
# Data manipulation
|
|
98
|
+
def set_data(self, data: T | Callable[[T | None], T]) -> None:
|
|
99
|
+
"""
|
|
100
|
+
Optimistically set data without changing loading/error state.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
data: The new data value, or a function that receives the current
|
|
104
|
+
data and returns the new data.
|
|
105
|
+
"""
|
|
106
|
+
...
|
|
107
|
+
|
|
108
|
+
def set_initial_data(
|
|
109
|
+
self,
|
|
110
|
+
data: T | Callable[[], T],
|
|
111
|
+
*,
|
|
112
|
+
updated_at: float | dt.datetime | None = None,
|
|
113
|
+
) -> None:
|
|
114
|
+
"""
|
|
115
|
+
Set data as if it were provided as initial_data.
|
|
116
|
+
|
|
117
|
+
Only takes effect if the query is still in 'loading' state.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
data: The initial data value, or a function that returns it.
|
|
121
|
+
updated_at: Optional timestamp to seed staleness calculations.
|
|
122
|
+
"""
|
|
123
|
+
...
|
|
124
|
+
|
|
125
|
+
def set_error(self, error: Exception) -> None:
|
|
126
|
+
"""Set error state on the query."""
|
|
127
|
+
...
|
|
128
|
+
|
|
129
|
+
# Enable/disable
|
|
130
|
+
def enable(self) -> None:
|
|
131
|
+
"""Enable the query, allowing it to fetch."""
|
|
132
|
+
...
|
|
133
|
+
|
|
134
|
+
def disable(self) -> None:
|
|
135
|
+
"""Disable the query, preventing it from fetching."""
|
|
136
|
+
...
|