The lean application framework for Python. Build sophisticated user interfaces with a simple Python API. Run your apps in the terminal and a web browser.
from __future__ import annotations
from typing import cast
from textual import on
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.screen import Screen
from textual.widgets import Button, Footer, Header, Label
class FirstScreen(Screen):
def compose(self) -> ComposeResult:
yield Header()
yield Label("this is the first screen")
yield Footer()
class SecondScreen(Screen):
BINDINGS = [
Binding("a", "finish", "Here it works"), # key binding works just fine
]
def compose(self) -> ComposeResult:
yield Header()
yield Label("This is the second screen")
yield Button("Here it doesn't work")
yield Footer()
async def action_finish(self) -> None:
await self._finish()
@on(Button.Pressed)
async def finish(self) -> None:
# Running in worker causes app closure with no error
# can't run directly also because of a deadlock https://github.com/Textualize/textual/issues/5008
self.run_worker(self._finish())
async def _finish(self) -> None:
app = cast(MyApp, self.app)
await app.action_pop_until_first_screen()
self.app.mount(Label("done")) # assume something needs to be done after awaiting previous action
class MyApp(App):
BINDINGS = [
Binding("d", "push_second_screen", "Push second screen"),
]
async def on_mount(self) -> None:
await self.push_screen(FirstScreen())
async def action_pop_until_first_screen(self) -> None:
while not isinstance(self.screen, FirstScreen):
await self.pop_screen()
async def action_push_second_screen(self) -> None:
await self.push_screen(SecondScreen())
MyApp().run()
After adding a wrapper to log cancellation inspired by https://stackoverflow.com/a/71356489 and some more logging looks like it comes from await_prune of AwaitRemove (log from mentioned issue and its MRE, but I hope helps a bit):
Found the casue of this issue. Replacing self.run_worker with self.app.run_worker seems to work. Still it is weird it just crashed with NO message in the first case.
This was observed in: https://github.com/Textualize/textual/issues/5008 but I think it's a separate issue.
MRE:
I investigated a bit and found out that when the app closure happens, there is
asyncio.CancelledError
being raised from this line: https://github.com/Textualize/textual/blob/main/src/textual/worker.py#L339After adding a wrapper to log cancellation inspired by https://stackoverflow.com/a/71356489 and some more logging looks like it comes from await_prune of AwaitRemove (log from mentioned issue and its MRE, but I hope helps a bit):
further narrowing shows it comes from await gather(*tasks) and we can see there is <Task cancelled name='message pump SecondScreen()', ...> there