Allow mo.state setters in widget callbacks#8244
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| # callback triggered by a frontend message). Use a sentinel | ||
| # that won't match any real cell, so self-loop prevention | ||
| # is skipped. | ||
| setter_cell_id = CellId_t("__external__") |
There was a problem hiding this comment.
this actually might fix a few other errors we say when people called this in async background tasks
3f185eb to
0ec3241
Compare
0c216da to
3e21fb2
Compare
|
You may need to rebase against main (should fix failing ci too) |
3e21fb2 to
6cab17f
Compare
83b3fa0 to
0af59a6
Compare
38f0204 to
2c7a60c
Compare
There was a problem hiding this comment.
Pull request overview
Enables mo.state setters to be called from widget/async callbacks that execute outside normal cell execution by introducing an “external setter” sentinel cell id, and ensures model-message handling flushes pending state updates so dependent cells re-run (or are marked stale in lazy mode).
Changes:
- Update
Kernel.register_state_updateto allow state setters without an active execution context using aCellId_t("__external__")sentinel, and refactor cell selection into_find_cells_for_state. - Flush pending
state_updatesafter processing a model update message when no UIElement-driven run occurs. - Add regression tests covering external
set_stateand anywidget model-message/observe +mo.stateinteractions (including nested models and self-loop avoidance expectations).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
marimo/_runtime/runtime.py |
Allows external state updates via sentinel cell id; adds _find_cells_for_state; flushes pending state updates after model messages. |
tests/_runtime/test_state.py |
Adds tests for calling set_state outside cell execution and verifying downstream re-runs. |
tests/_plugins/ui/_impl/test_anywidget.py |
Adds regression tests ensuring model-message-triggered observe callbacks can safely call mo.state setters (including nested models). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
9f28561 to
22caa81
Compare
Replaces #8243 Widget observe callbacks triggered by frontend model updates happen outside cell execution, so there's no execution context. Previously `register_state_update` asserted one existed, crashing the setter. The context was only needed for self-loop prevention (don't re-run the cell that called the setter). We use a `"__external__"` sentinel cell ID that won't match any real cell — I believe this is safe since there is no "self" cell to loop back to. An alternative would be to install a real execution context by tracking which cell created each model, but that adds complexity for a case that's truly external. Also ensures `handle_receive_model_message` flushes pending state updates when no UIElement triggers a run through the normal path.
Adds a test where the same cell defines a state, reads it, and has an observe callback that calls the setter. Confirms the defining cell does not re-run — only downstream cells do.
State setters called outside cell execution (async tasks, widget callbacks) would queue an update but nothing would process it. The runner only flushes `state_updates` during `_run_cells`, which only runs when something explicitly triggers it. Now `register_state_update` enqueues an `ExecuteStaleCellsCommand` when called outside cell execution, mirroring what `mo.Thread` already does. This keeps the fix in one place rather than needing flush logic in every message handler.
22caa81 to
a6c46b1
Compare
|
🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.19.10-dev44 |
Replaces #8243
Widget observe callbacks triggered by frontend model updates happen outside cell execution, so there's no execution context. Previously
register_state_updateasserted one existed, crashing the setter.The context was only needed for self-loop prevention (don't re-run the cell that called the setter). We use a
"__external__"sentinel cell ID that won't match any real cell. I believe this is safe since there is no "self" cell to loop back to. An alternative would be to install a real execution context by tracking which cell created each model, but that adds complexity for a case that's truly external.Also ensures
handle_receive_model_messageflushes pending state updates when no UIElement triggers a run through the normal path.