-
Notifications
You must be signed in to change notification settings - Fork 780
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
self.watch
ignores reactive updates when callback is async
#3036
Comments
self.watch
ignores reactive updatesself.watch
ignores reactive updates when callback is async
This looks vaguely related to an issue I found when implementing the progress bar where I also couldn't watch changes on a reactive outside of the progress bar widget. Sadly, I can't find an open issue about that; maybe I didn't file any. I'll take a look at this. |
Alright, I took a look and what I found is that arguably V1 is working as expected and V2 is the buggy one. V1 "doesn't work" because you created a reactive attribute in a widget ( E.g., if you add CSS to the Modified examplefrom textual.app import App, ComposeResult
from textual.binding import Binding
from textual.reactive import var
from textual.widget import Widget
from textual.widgets import Footer, Placeholder
class ReactiveHolder(Widget):
### NEW: Added `DEFAULT_CSS` here:
DEFAULT_CSS = """ReactiveHolder { height: 0; width: 0; }"""
trigger = var(False)
class WatchApp(App):
BINDINGS = [
Binding("f2", "update", "Update"),
]
counter = 0
def __init__(self):
super().__init__()
self.reactive_holder = ReactiveHolder()
def compose(self) -> ComposeResult:
### NEW: Yielded the reactive holder here:
yield self.reactive_holder
yield Footer()
def on_mount(self) -> None:
self.watch(self.reactive_holder, "trigger", self.__callback)
def action_update(self) -> None:
self.__class__.counter += 1
self.reactive_holder.trigger = not self.reactive_holder.trigger
async def __callback(self) -> None:
await self.query(Placeholder).remove()
await self.mount(Placeholder(f"Callback called {self.counter} times"))
if __name__ == "__main__":
WatchApp().run() But if that's the case, then why does V2 work? That's because, in V2, the callback is a synchronous function and thus Textual calls it right away when the reactive attribute is changed, whereas in V1 it had to schedule the callback (because it is asynchronous). These are just my findings for the moment. |
There's an argument to be made in favour of V1 working, too. Maybe this will be "better", for some definition of "better". |
The async reactive callbacks are now scheduled on the message pump of the watcher of the reactive instead of on the owner of the reactive attribute. Related issues: #3036.
Don't forget to star the repository! Follow @textualizeio for Textual updates. |
Issue description
Consider such a snippet:
While running it - after pressing F2 nothing happens, when Placeholder should be mounted. This is because
self.watch
cannot call a callback if it is:Because either:
removing the
async
andawait
keywords makes this code to run just fine.also placing the reactive in
App
makes this run just fineTextual Diagnostics
Versions
Python
Operating System
167942497222.04~4a8cde1 SMP PREEMPT_DYNAMIC Tue MTerminal
Rich Console options
The text was updated successfully, but these errors were encountered: