textual icon indicating copy to clipboard operation
textual copied to clipboard

Cannot find "active_app"

Open davidbrochart opened this issue 1 year ago • 7 comments

While running a Textual app with its run_async() method, I get this message in the Textual console:

Task exception was never retrieved
future: <Task finished name='set_timer#1' coro=<Timer._run_timer() done, defined at
/home/david/git/textual/src/textual/timer.py:137> exception=LookupError(<ContextVar
name='active_app' at 0x7f64d1c924d0>)>
Traceback (most recent call last):
  File "/home/david/git/textual/src/textual/timer.py", line 140, in _run_timer
    await self._run()
  File "/home/david/git/textual/src/textual/timer.py", line 169, in _run
    await self._tick(next_timer=next_timer, count=count)
  File "/home/david/git/textual/src/textual/timer.py", line 176, in _tick
    app = active_app.get()
LookupError: <ContextVar name='active_app' at 0x7f64d1c924d0>

Sometimes it makes the app crash, sometimes not. I don't have a reproducible example because it happens in a bigger application, but do you know what could cause this?

# Textual Diagnostics

## Versions

| Name    | Value  |
|---------|--------|
| Textual | 1.0.0  |
| Rich    | 13.9.4 |

## Python

| Name           | Value                                                                     |
|----------------|---------------------------------------------------------------------------|
| Version        | 3.13.1                                                                    |
| Implementation | CPython                                                                   |
| Compiler       | GCC 13.3.0                                                                |
| Executable     | /home/david/.local/share/hatch/env/virtual/jpterm/N1y3coPp/dev/bin/python |

## Operating System

| Name    | Value                                                |
|---------|------------------------------------------------------|
| System  | Linux                                                |
| Release | 6.1.0-27-amd64                                       |
| Version | #1 SMP PREEMPT_DYNAMIC Debian 6.1.115-1 (2024-11-01) |

## Terminal

| Name                 | Value           |
|----------------------|-----------------|
| Terminal Application | vscode (1.96.1) |
| TERM                 | xterm-256color  |
| COLORTERM            | truecolor       |
| FORCE_COLOR          | *Not set*       |
| NO_COLOR             | *Not set*       |

## Rich Console options

| Name           | Value               |
|----------------|---------------------|
| size           | width=95, height=45 |
| legacy_windows | False               |
| min_width      | 1                   |
| max_width      | 95                  |
| is_terminal    | True                |
| encoding       | utf-8               |
| max_height     | 45                  |
| justify        | None                |
| overflow       | None                |
| no_wrap        | False               |
| highlight      | None                |
| markup         | None                |
| height         | None                |

If you don't have the textual command on your path, you may have forgotten to install the textual-dev package.

Feel free to add screenshots and / or videos. These can be very helpful!

davidbrochart avatar Dec 21 '24 15:12 davidbrochart

Thank you for your issue. Give us a little time to review it.

PS. You might want to check the FAQ if you haven't done so already.

This is an automated reply, generated by FAQtory

github-actions[bot] avatar Dec 21 '24 15:12 github-actions[bot]

This snippet triggers the error:

import asyncio
from textual.app import App, ComposeResult
from textual.containers import Container
from textual.widgets import MarkdownViewer

EXAMPLE_MARKDOWN = """\
# Markdown Viewer

This is an example of Textual's `MarkdownViewer` widget.


## Features

Markdown syntax and extensions are supported.

- Typography *emphasis*, **strong**, `inline code` etc.
- Headers
- Lists (bullet and ordered)
- Syntax highlighted code blocks
- Tables!

## Tables

Tables are displayed in a DataTable widget.

| Name            | Type   | Default | Description                        |
| --------------- | ------ | ------- | ---------------------------------- |
| `show_header`   | `bool` | `True`  | Show the table header              |
| `fixed_rows`    | `int`  | `0`     | Number of fixed rows               |
| `fixed_columns` | `int`  | `0`     | Number of fixed columns            |
| `zebra_stripes` | `bool` | `False` | Display alternating colors on rows |
| `header_height` | `int`  | `1`     | Height of header row               |
| `show_cursor`   | `bool` | `True`  | Show a cell cursor                 |
"""

class MyApp(App):
    def compose(self) -> ComposeResult:
        self.container = Container()
        yield self.container

async def task(app: MyApp):
    await asyncio.sleep(1)
    app.container.mount(MarkdownViewer(EXAMPLE_MARKDOWN))

async def main():
    app = MyApp()
    t0 = asyncio.create_task(task(app))
    t1 = asyncio.create_task(app.run_async())
    await asyncio.Future()

if __name__ == "__main__":
    asyncio.run(main())

davidbrochart avatar Dec 21 '24 17:12 davidbrochart

Note that it works fine if I replace MarkdownViewer(EXAMPLE_MARKDOWN) with Header() for instance.

davidbrochart avatar Dec 21 '24 21:12 davidbrochart

One workaround is to:

from textual._context import active_app

And to set the active app before mounting the widget:

active_app.set(app)
app.container.mount(MarkdownViewer(EXAMPLE_MARKDOWN))

But I'm not sure if there's a better solution?

davidbrochart avatar Dec 21 '24 21:12 davidbrochart

I wouldn't expect the MRE to work. It looks like it would call mount before the app is running. The earliest that should work is when the App handles the Ready event. If you try to do anything with the DOM prior to that, then the results may be undefined.

Not sure if that explains your original issue. Could you be creating a timer before the app is running?

willmcgugan avatar Dec 22 '24 15:12 willmcgugan

The mount is called after the app is running, but in a different stack, so contextvars are unrelated. That's why setting the active_app before the mount solves the issue.

davidbrochart avatar Dec 22 '24 16:12 davidbrochart

I am running into this problem when calling await app.recompose(), the workaround @davidbrochart mentioned (active_app.set(app)) works.

For context, I am running into it here https://github.com/basnijholt/tuitorial/pull/18

basnijholt avatar Jan 03 '25 21:01 basnijholt