Textualize / textual

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.
https://textual.textualize.io/
MIT License
25.05k stars 766 forks source link

`AssertionError` when updating `ListView` in a worker #4958

Closed kwevin closed 4 days ago

kwevin commented 2 weeks ago

Setting loading=True and updating ListView inside of a worker with an empty list will cause it to raise an AssertionError with LoadingIndicator

class MyApp(App[None]):
    def compose(self) -> ComposeResult:
        yield ListView()

    @work
    async def populate_table(self) -> None:
        list_view = self.query_exactly_one(ListView)
        await list_view.clear()
        await list_view.extend([])
        list_view.loading = False

    def on_mount(self) -> None:
        list_view = self.query_exactly_one(ListView)
        list_view.loading = True
        self.populate_table()

app = MyApp()
app.run()

Textual Diagnostics

Versions

Name Value
Textual 0.79.0
Rich 13.7.1

Python

Name Value
Version 3.11.1
Implementation CPython
Compiler MSC v.1934 64 bit (AMD64)
Executable C:\Users\Kevin\AppData\Local\pypoetry\Cache\virtualenvs\listentui-qruDwX8r-py3.11\Scripts\python.exe

Operating System

Name Value
System Windows
Release 10
Version 10.0.22621

Terminal

Name Value
Terminal Application Windows Terminal
TERM Not set
COLORTERM Not set
FORCE_COLOR Not set
NO_COLOR Not set

Rich Console options

Name Value
size width=120, height=30
legacy_windows False
min_width 1
max_width 120
is_terminal True
encoding utf-8
max_height 30
justify None
overflow None
no_wrap False
highlight None
markup None
height None
github-actions[bot] commented 2 weeks ago

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

darrenburns commented 4 days ago

Yeah, setting loading mounts a child widget which kind of interferes with ListView. Unfortunately the loading attribute is a bit flaky in general at the moment, and this issue is making me think we might need to explore other mechanisms for implementing it (perhaps outwith the DOM, not interfering with children etc).

Anyway, you can still make it work in this case: You want to switch off "loading" which will unmount the LoadingIndicator as a child of ListView. We also need to await this removal to ensure it completes before we try to modify the children of the ListView using extend:

from textual._work_decorator import work
from textual.app import App, ComposeResult
from textual.widgets import ListView

class MyApp(App[None]):
    def compose(self) -> ComposeResult:
        yield ListView()

    def on_mount(self) -> None:
        list_view = self.query_exactly_one(ListView)
        list_view.loading = True
        self.populate_table()

    @work
    async def populate_table(self) -> None:
        list_view = self.query_exactly_one(ListView)
        await list_view.set_loading(False)
        await list_view.clear()
        await list_view.extend([])

app = MyApp()
app.run()

Closing this for now as I've validated the above works, but feel free to let us know if you hit any other problems.

github-actions[bot] commented 4 days ago

Don't forget to star the repository!

Follow @textualizeio for Textual updates.