Generate state delta from processed state instance (#2023)
This commit is contained in:
parent
6ea657a4fd
commit
1734ba0b6d
@ -963,14 +963,19 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
|
|||||||
Returns:
|
Returns:
|
||||||
The valid StateUpdate containing the events and final flag.
|
The valid StateUpdate containing the events and final flag.
|
||||||
"""
|
"""
|
||||||
|
# get the delta from the root of the state tree
|
||||||
|
state = self
|
||||||
|
while state.parent_state is not None:
|
||||||
|
state = state.parent_state
|
||||||
|
|
||||||
token = self.router.session.client_token
|
token = self.router.session.client_token
|
||||||
|
|
||||||
# Convert valid EventHandler and EventSpec into Event
|
# Convert valid EventHandler and EventSpec into Event
|
||||||
fixed_events = fix_events(self._check_valid(handler, events), token)
|
fixed_events = fix_events(self._check_valid(handler, events), token)
|
||||||
|
|
||||||
# Get the delta after processing the event.
|
# Get the delta after processing the event.
|
||||||
delta = self.get_delta()
|
delta = state.get_delta()
|
||||||
self._clean()
|
state._clean()
|
||||||
|
|
||||||
return StateUpdate(
|
return StateUpdate(
|
||||||
delta=delta,
|
delta=delta,
|
||||||
@ -1009,30 +1014,30 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
|
|||||||
# Handle async generators.
|
# Handle async generators.
|
||||||
if inspect.isasyncgen(events):
|
if inspect.isasyncgen(events):
|
||||||
async for event in events:
|
async for event in events:
|
||||||
yield self._as_state_update(handler, event, final=False)
|
yield state._as_state_update(handler, event, final=False)
|
||||||
yield self._as_state_update(handler, events=None, final=True)
|
yield state._as_state_update(handler, events=None, final=True)
|
||||||
|
|
||||||
# Handle regular generators.
|
# Handle regular generators.
|
||||||
elif inspect.isgenerator(events):
|
elif inspect.isgenerator(events):
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
yield self._as_state_update(handler, next(events), final=False)
|
yield state._as_state_update(handler, next(events), final=False)
|
||||||
except StopIteration as si:
|
except StopIteration as si:
|
||||||
# the "return" value of the generator is not available
|
# the "return" value of the generator is not available
|
||||||
# in the loop, we must catch StopIteration to access it
|
# in the loop, we must catch StopIteration to access it
|
||||||
if si.value is not None:
|
if si.value is not None:
|
||||||
yield self._as_state_update(handler, si.value, final=False)
|
yield state._as_state_update(handler, si.value, final=False)
|
||||||
yield self._as_state_update(handler, events=None, final=True)
|
yield state._as_state_update(handler, events=None, final=True)
|
||||||
|
|
||||||
# Handle regular event chains.
|
# Handle regular event chains.
|
||||||
else:
|
else:
|
||||||
yield self._as_state_update(handler, events, final=True)
|
yield state._as_state_update(handler, events, final=True)
|
||||||
|
|
||||||
# If an error occurs, throw a window alert.
|
# If an error occurs, throw a window alert.
|
||||||
except Exception:
|
except Exception:
|
||||||
error = traceback.format_exc()
|
error = traceback.format_exc()
|
||||||
print(error)
|
print(error)
|
||||||
yield self._as_state_update(
|
yield state._as_state_update(
|
||||||
handler,
|
handler,
|
||||||
window_alert("An error occurred. See logs for details."),
|
window_alert("An error occurred. See logs for details."),
|
||||||
final=True,
|
final=True,
|
||||||
@ -1360,12 +1365,19 @@ class StateProxy(wrapt.ObjectProxy):
|
|||||||
Raises:
|
Raises:
|
||||||
ImmutableStateError: If the state is not in mutable mode.
|
ImmutableStateError: If the state is not in mutable mode.
|
||||||
"""
|
"""
|
||||||
if not name.startswith("_self_") and not self._self_mutable:
|
if (
|
||||||
raise ImmutableStateError(
|
name.startswith("_self_") # wrapper attribute
|
||||||
"Background task StateProxy is immutable outside of a context "
|
or self._self_mutable # lock held
|
||||||
"manager. Use `async with self` to modify state."
|
# non-persisted state attribute
|
||||||
)
|
or name in self.__wrapped__.get_skip_vars()
|
||||||
super().__setattr__(name, value)
|
):
|
||||||
|
super().__setattr__(name, value)
|
||||||
|
return
|
||||||
|
|
||||||
|
raise ImmutableStateError(
|
||||||
|
"Background task StateProxy is immutable outside of a context "
|
||||||
|
"manager. Use `async with self` to modify state."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class StateUpdate(Base):
|
class StateUpdate(Base):
|
||||||
|
@ -1577,7 +1577,7 @@ def mock_app(monkeypatch, state_manager: StateManager) -> rx.App:
|
|||||||
|
|
||||||
setattr(app_module, CompileVars.APP, app)
|
setattr(app_module, CompileVars.APP, app)
|
||||||
app.state = TestState
|
app.state = TestState
|
||||||
app.state_manager = state_manager
|
app._state_manager = state_manager
|
||||||
app.event_namespace.emit = AsyncMock() # type: ignore
|
app.event_namespace.emit = AsyncMock() # type: ignore
|
||||||
monkeypatch.setattr(prerequisites, "get_app", lambda: app_module)
|
monkeypatch.setattr(prerequisites, "get_app", lambda: app_module)
|
||||||
return app
|
return app
|
||||||
@ -1663,6 +1663,15 @@ class BackgroundTaskState(State):
|
|||||||
order: List[str] = []
|
order: List[str] = []
|
||||||
dict_list: Dict[str, List[int]] = {"foo": [1, 2, 3]}
|
dict_list: Dict[str, List[int]] = {"foo": [1, 2, 3]}
|
||||||
|
|
||||||
|
@rx.var
|
||||||
|
def computed_order(self) -> List[str]:
|
||||||
|
"""Get the order as a computed var.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The value of 'order' var.
|
||||||
|
"""
|
||||||
|
return self.order
|
||||||
|
|
||||||
@rx.background
|
@rx.background
|
||||||
async def background_task(self):
|
async def background_task(self):
|
||||||
"""A background task that updates the state."""
|
"""A background task that updates the state."""
|
||||||
@ -1791,6 +1800,10 @@ async def test_background_task_no_block(mock_app: rx.App, token: str):
|
|||||||
"background_task:start",
|
"background_task:start",
|
||||||
"other",
|
"other",
|
||||||
],
|
],
|
||||||
|
"computed_order": [
|
||||||
|
"background_task:start",
|
||||||
|
"other",
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -1800,7 +1813,7 @@ async def test_background_task_no_block(mock_app: rx.App, token: str):
|
|||||||
await task
|
await task
|
||||||
assert not mock_app.background_tasks
|
assert not mock_app.background_tasks
|
||||||
|
|
||||||
assert (await mock_app.state_manager.get_state(token)).order == [
|
exp_order = [
|
||||||
"background_task:start",
|
"background_task:start",
|
||||||
"other",
|
"other",
|
||||||
"background_task:stop",
|
"background_task:stop",
|
||||||
@ -1808,6 +1821,50 @@ async def test_background_task_no_block(mock_app: rx.App, token: str):
|
|||||||
"private",
|
"private",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
assert (await mock_app.state_manager.get_state(token)).order == exp_order
|
||||||
|
|
||||||
|
assert mock_app.event_namespace is not None
|
||||||
|
emit_mock = mock_app.event_namespace.emit
|
||||||
|
|
||||||
|
assert json.loads(emit_mock.mock_calls[0].args[1]) == {
|
||||||
|
"delta": {
|
||||||
|
"background_task_state": {
|
||||||
|
"order": ["background_task:start"],
|
||||||
|
"computed_order": ["background_task:start"],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"events": [],
|
||||||
|
"final": True,
|
||||||
|
}
|
||||||
|
for call in emit_mock.mock_calls[1:5]:
|
||||||
|
assert json.loads(call.args[1]) == {
|
||||||
|
"delta": {
|
||||||
|
"background_task_state": {"computed_order": ["background_task:start"]}
|
||||||
|
},
|
||||||
|
"events": [],
|
||||||
|
"final": True,
|
||||||
|
}
|
||||||
|
assert json.loads(emit_mock.mock_calls[-2].args[1]) == {
|
||||||
|
"delta": {
|
||||||
|
"background_task_state": {
|
||||||
|
"order": exp_order,
|
||||||
|
"computed_order": exp_order,
|
||||||
|
"dict_list": {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"events": [],
|
||||||
|
"final": True,
|
||||||
|
}
|
||||||
|
assert json.loads(emit_mock.mock_calls[-1].args[1]) == {
|
||||||
|
"delta": {
|
||||||
|
"background_task_state": {
|
||||||
|
"computed_order": exp_order,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"events": [],
|
||||||
|
"final": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_background_task_reset(mock_app: rx.App, token: str):
|
async def test_background_task_reset(mock_app: rx.App, token: str):
|
||||||
|
Loading…
Reference in New Issue
Block a user