Skip to content

Commit 8a225f0

Browse files
committed
allow QueryResult.refetch to receive keep_previous_data
1 parent 0642b65 commit 8a225f0

File tree

3 files changed

+29
-26
lines changed

3 files changed

+29
-26
lines changed

examples/query.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class UserApi(ps.State):
1212
# Keyed query with keep_previous_data default True
1313
@ps.query(keep_previous_data=True)
1414
async def user(self):
15+
print("Running user query")
1516
await asyncio.sleep(0.3)
1617
# Simulate API; id 13 fails to demonstrate on_error
1718
if self.user_id == 13:
@@ -81,7 +82,7 @@ async def optimistic_rename():
8182
ps.div(
8283
ps.h2("Keyed user()", className="text-xl font-semibold mb-2"),
8384
ps.p(
84-
"Loading..." if s.user.is_loading else f"Data: {s.user.data}",
85+
"Loading..." if s.user.data is None else f"Data: {s.user.data}",
8586
className="mb-2",
8687
),
8788
ps.p(
@@ -90,7 +91,9 @@ async def optimistic_rename():
9091
),
9192
ps.div(
9293
ps.button(
93-
"Refetch", onClick=s.user.refetch, className="btn-primary mr-2"
94+
"Refetch",
95+
onClick=lambda: s.user.refetch(keep_previous_data=False),
96+
className="btn-primary mr-2",
9497
),
9598
ps.button(
9699
"Optimistic rename",

packages/pulse/python/src/pulse/query.py

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,21 @@ def __init__(
4444
self._keep_previous_data = keep_previous_data
4545

4646
@override
47-
def push_change(self):
47+
def push_change(self, keep_previous_data: bool | None = None):
4848
# Synchronously clear data before scheduling the effect to run.
4949
# This ensures renders see the loading state immediately, not stale data.
50-
self._result._set_loading(clear_data=not self._keep_previous_data) # pyright: ignore[reportPrivateUsage]
50+
if keep_previous_data is None:
51+
keep_previous_data = self._keep_previous_data
52+
self._result.set_loading(clear_data=not keep_previous_data)
5153
super().push_change()
5254

55+
def refetch(self, keep_previous_data: bool | None = None):
56+
if keep_previous_data is None:
57+
keep_previous_data = self._keep_previous_data
58+
self.cancel()
59+
self._result.set_loading(clear_data=not keep_previous_data)
60+
self.run()
61+
5362

5463
class QueryResult(Generic[T]):
5564
def __init__(self, initial_data: T | None = None):
@@ -63,7 +72,7 @@ def __init__(self, initial_data: T | None = None):
6372
# Tracks whether at least one load cycle completed (success or error)
6473
self._has_loaded: Signal[bool] = Signal(False, name="query.has_loaded")
6574
# Effect driving this query (attached by QueryProperty)
66-
self._effect: AsyncEffect | None = None
75+
self._effect: QueryAsyncEffect | None = None
6776

6877
@property
6978
def is_loading(self) -> bool:
@@ -89,39 +98,38 @@ def data(self) -> T | None:
8998
def has_loaded(self) -> bool:
9099
return self._has_loaded.read()
91100

92-
def attach_effect(self, effect: AsyncEffect) -> None:
101+
def attach_effect(self, effect: QueryAsyncEffect) -> None:
93102
self._effect = effect
94103

95-
def refetch(self) -> None:
104+
def refetch(self, keep_previous_data: bool | None = None) -> None:
96105
if self._effect is None:
97106
return
98-
self._effect.cancel()
99-
self._effect.run()
107+
self._effect.refetch(keep_previous_data=keep_previous_data)
100108

101109
def dispose(self) -> None:
102110
if self._effect is None:
103111
return
104112
self._effect.dispose()
105113

106114
# Internal setters used by the query machinery
107-
def _set_loading(self, *, clear_data: bool = False):
115+
def set_loading(self, *, clear_data: bool = False):
108116
# print("[QueryResult] set loading=True")
109117
self._is_loading.write(True)
110118
self._is_error.write(False)
111119
self._error.write(None)
112120
if clear_data:
113121
# If there was an explicit initial value, reset to it; otherwise clear
114-
self._data.write(self._initial_data)
122+
self._data.write(None)
115123

116-
def _set_success(self, data: T):
124+
def set_success(self, data: T):
117125
# print(f"[QueryResult] set success data={data!r}")
118126
self._data.write(data)
119127
self._is_loading.write(False)
120128
self._is_error.write(False)
121129
self._error.write(None)
122130
self._has_loaded.write(True)
123131

124-
def _set_error(self, err: Exception):
132+
def set_error(self, err: Exception):
125133
# print(f"[QueryResult] set error err={err!r}")
126134
self._error.write(err)
127135
self._is_loading.write(False)
@@ -140,16 +148,6 @@ def set_initial_data(self, data: T):
140148
self._initial_data = data
141149
self._data.write(data)
142150

143-
# Public helpers mirroring internal transitions
144-
def set_loading(self, *, clear_data: bool = False) -> None:
145-
self._set_loading(clear_data=clear_data)
146-
147-
def set_success(self, data: T) -> None:
148-
self._set_success(data)
149-
150-
def set_error(self, err: Exception) -> None:
151-
self._set_error(err)
152-
153151

154152
OnSuccessFn = Callable[[TState], Any] | Callable[[TState, T], Any]
155153
OnErrorFn = Callable[[TState], Any] | Callable[[TState, Exception], Any]
@@ -300,9 +298,6 @@ async def run_effect():
300298
return None
301299
inflight_key = key
302300

303-
# Set loading immediately; optionally clear previous data
304-
result.set_loading(clear_data=not self._keep_previous_data)
305-
306301
try:
307302
data = await bound_fetch()
308303
except asyncio.CancelledError:

packages/pulse/python/src/pulse/reactive.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,11 @@ def _task_name(self) -> str:
457457
base = self.name or "effect"
458458
return f"effect:{base}"
459459

460+
@override
461+
def push_change(self):
462+
self.cancel()
463+
super().push_change()
464+
460465
@override
461466
def _copy_kwargs(self):
462467
kwargs = super()._copy_kwargs()

0 commit comments

Comments
 (0)