Skip to content
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

MountError: Can't mount widget(s) before <widget> is mounted with widgets=() during dynamic refresh #4570

Closed
mzebrak opened this issue May 29, 2024 · 10 comments · Fixed by #4575

Comments

@mzebrak
Copy link

mzebrak commented May 29, 2024

I know about the recent changes related to MountError in version 0.63.3 which is:

Attempting to mount on a non-mounted widget now raises a MountError #4547

However, it seems to me that something that worked before (and looks like it should still work) is not working with them.

I mean dynamically adding/removing widgets based on some changes occurring in a watched reactive.

We have observed this problem in our more extensive application, where we can reproduce it every time (I think because more things are going on - in the background data is being fetched from API), but the problematic code itself is quite simple to understand, so I will include it:

real app code looks like this
class CartItem(Static):
    """Holds the cart item info."""

    def __init__(self, index: int, operation: Operation) -> None:
        self._operation = operation
        super().__init__(f"{index}. {humanize_operation_name(operation)}")


class CartItemsContainer(VerticalScroll):
    """A container that holds the cart items."""


class CartOverview(Widget):
    def __init__(self) -> None:
        super().__init__()
        self.__cart_items_container = CartItemsContainer()


    def compose(self) -> ComposeResult:
       #  <we've got some widgets yielded there>
        with self.__cart_items_container:
            yield from self.__create_cart_items()

    def on_mount(self) -> None:
        self.watch(self.app.world, "profile_data", callback=self.__sync_cart_items)

    def __sync_cart_items(self) -> None:
        with self.app.batch_update():
            self.__cart_items_container.query(CartItem).remove()
            new_cart_items = self.__create_cart_items()
            self.__cart_items_container.mount(*new_cart_items)

    def __create_cart_items(self) -> list[CartItem]:
        return [CartItem(index + 1, operation) for index, operation in enumerate(self.app.world.profile_data.cart)]

... so we watch for changes taking place in some reactive, and when it happens - we want to update the Cart, so we remove all existing items and create new ones.

There may be a better way to do this. If you have any suggestions, please don't hesitate. (but we don't want to self.refresh(recompose=True) as it has a visual effect because widgets above cart_items_container are also rebuilt).

But after the latest textual bump, when such a sync occurs, we observe this error:
(When self.watch is commented - no issue occurs. self.__cart_items_container.mount(*new_cart_items) is the problematic part not included in stacktrace.)

real app traceback
╭─────────────────────────────────────────────────────────────────────────────────────────── Traceback (most recent call last) ───────────────────────────────────────────────────────────────────────────────────────────╮
│ /home/mzebrak/.pyenv/versions/clive/lib/python3.10/site-packages/textual/widget.py:3708 in _compose                                                                                                                     │
│                                                                                                                                                                                                                         │
│   3705 │   │   │   self.app._handle_exception(error)                                            ╭─────── locals ───────╮                                                                                                │
│   3706 │   │   else:                                                                            │    self = CartItem() │                                                                                                │
│   3707 │   │   │   self._extend_compose(widgets)                                                │ widgets = []         │                                                                                                │
│ ❱ 3708 │   │   │   await self.mount_composed_widgets(widgets)                                   ╰──────────────────────╯                                                                                                │
│   3709 │                                                                                                                                                                                                                │
│   3710async def mount_composed_widgets(self, widgets: list[Widget]) -> None:                                                                                                                                       │
│   3711 │   │   """Called by Textual to mount widgets after compose.                                                                                                                                                     │
│                                                                                                                                                                                                                         │
│ /home/mzebrak/.pyenv/versions/clive/lib/python3.10/site-packages/textual/widget.py:3720 in mount_composed_widgets                                                                                                       │
│                                                                                                                                                                                                                         │
│   3717 │   │   Args:                                                                            ╭─────── locals ───────╮                                                                                                │
│   3718 │   │   │   widgets: A list of child widgets.                                            │    self = CartItem() │                                                                                                │
│   3719 │   │   """widgets = []         │                                                                                                │
│ ❱ 3720 │   │   await self.mount_all(widgets)                                                    ╰──────────────────────╯                                                                                                │
│   3721 │                                                                                                                                                                                                                │
│   3722def _extend_compose(self, widgets: list[Widget]) -> None:                                                                                                                                                    │
│   3723 │   │   """Hook to extend composed widgets.                                                                                                                                                                      │
│                                                                                                                                                                                                                         │
│ /home/mzebrak/.pyenv/versions/clive/lib/python3.10/site-packages/textual/widget.py:1006 in mount_all                                                                                                                    │
│                                                                                                                                                                                                                         │
│   1003 │   │   │   Only one of ``before`` or ``after`` can be provided. If both are             ╭─────── locals ───────╮                                                                                                │
│   1004 │   │   │   provided a ``MountError`` will be raised.                                    │   after = None       │                                                                                                │
│   1005 │   │   """before = None       │                                                                                                │
│ ❱ 1006 │   │   await_mount = self.mount(*widgets, before=before, after=after)                   │    self = CartItem() │                                                                                                │
│   1007 │   │   return await_mountwidgets = []         │                                                                                                │
│   1008 │                                                                                        ╰──────────────────────╯                                                                                                │
│   1009 │   @overload                                                                                                                                                                                                    │
│                                                                                                                                                                                                                         │
│ /home/mzebrak/.pyenv/versions/clive/lib/python3.10/site-packages/textual/widget.py:938 in mount                                                                                                                         │
│                                                                                                                                                                                                                         │
│    935 │   │   │   provided a ``MountError`` will be raised.                                    ╭─────── locals ───────╮                                                                                                │
│    936 │   │   """                                                                              │   after = None       │                                                                                                │
│    937 │   │   if not self.is_attached:                                                         │  before = None       │                                                                                                │
│ ❱  938 │   │   │   raise MountError(f"Can't mount widget(s) before {self!r} is mounted")        │    self = CartItem() │                                                                                                │
│    939 │   │   # Check for duplicate IDs in the incoming widgets                                │ widgets = ()         │                                                                                                │940 │   │   ids_to_mount = [widget.id for widget in widgets if widget.id is not None]        ╰──────────────────────╯                                                                                                │
│    941 │   │   unique_ids = set(ids_to_mount)                                                                                                                                                                           │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
MountError: Can't mount widget(s) before CartItem() is mounted

This error is weird because we can see widgets is empty in locals. And what I thought about is that some race condition might happen between this __sync_cart_items and compose which will result in "Can't mount widget(s) before CartItemsContainer() is mounted" (vs actual CartItem) but looks like that shouldn't happen because this watch is scheduled after initial compose (because in the on_mount method).


MRE:

I sometimes manage to reproduce it using this code, but it's not easy, it requires multiple runs (if you can't still call it, you can modify the parameters like labels_increment and set_interval time)

MRE
from __future__ import annotations

from textual import work
from textual.app import App, ComposeResult
from textual.containers import Vertical
from textual.reactive import var
from textual.screen import Screen
from textual.widgets import Label


class MyScreen(Screen):
    watch_me = var(0)

    @work()
    async def update_watch_me(self):
        self.watch_me += 1

    def __init__(self):
        super().__init__()
        self.labels_count = 0
        self.container = Vertical()
        self.set_interval(0.05, self.update_watch_me)

    def compose(self) -> ComposeResult:
        with self.container:
            yield from self._create_container_content()

    def on_mount(self):
        self.watch(self, "watch_me", callback=self.sync)

    def sync(self):
        with self.app.batch_update():
            self.container.query(Label).remove()
            new_items = self._create_container_content()
            self.container.mount(*new_items)

    def _create_container_content(self) -> list[Label]:
        labels_increment = 50
        label_numbers = range(self.labels_count, self.labels_count + labels_increment)
        self.labels_count += labels_increment
        return [Label(f'label {i}') for i in label_numbers]


class MountErrorApp(App):
    def on_mount(self):
        self.push_screen(MyScreen())


MountErrorApp().run()
MRE exception
─────────────────────────────────────────────────────────────────────────────────────────── Traceback (most recent call last) ───────────────────────────────────────────────────────────────────────────────────────────╮
│ /home/mzebrak/.pyenv/versions/clive/lib/python3.10/site-packages/textual/widget.py:3708 in _compose                                                                                                                     │
│                                                                                                                                                                                                                         │
│   3705 │   │   │   self.app._handle_exception(error)                                            ╭───── locals ──────╮                                                                                                   │
│   3706 │   │   else:                                                                            │    self = Label() │                                                                                                   │
│   3707 │   │   │   self._extend_compose(widgets)                                                │ widgets = []      │                                                                                                   │
│ ❱ 3708 │   │   │   await self.mount_composed_widgets(widgets)                                   ╰───────────────────╯                                                                                                   │
│   3709 │                                                                                                                                                                                                                │
│   3710async def mount_composed_widgets(self, widgets: list[Widget]) -> None:                                                                                                                                       │
│   3711 │   │   """Called by Textual to mount widgets after compose.                                                                                                                                                     │
│                                                                                                                                                                                                                         │
│ /home/mzebrak/.pyenv/versions/clive/lib/python3.10/site-packages/textual/widget.py:3720 in mount_composed_widgets                                                                                                       │
│                                                                                                                                                                                                                         │
│   3717 │   │   Args:                                                                            ╭───── locals ──────╮                                                                                                   │
│   3718 │   │   │   widgets: A list of child widgets.                                            │    self = Label() │                                                                                                   │
│   3719 │   │   """widgets = []      │                                                                                                   │
│ ❱ 3720 │   │   await self.mount_all(widgets)                                                    ╰───────────────────╯                                                                                                   │
│   3721 │                                                                                                                                                                                                                │
│   3722def _extend_compose(self, widgets: list[Widget]) -> None:                                                                                                                                                    │
│   3723 │   │   """Hook to extend composed widgets.                                                                                                                                                                      │
│                                                                                                                                                                                                                         │
│ /home/mzebrak/.pyenv/versions/clive/lib/python3.10/site-packages/textual/widget.py:1006 in mount_all                                                                                                                    │
│                                                                                                                                                                                                                         │
│   1003 │   │   │   Only one of ``before`` or ``after`` can be provided. If both are             ╭───── locals ──────╮                                                                                                   │
│   1004 │   │   │   provided a ``MountError`` will be raised.                                    │   after = None    │                                                                                                   │
│   1005 │   │   """before = None    │                                                                                                   │
│ ❱ 1006 │   │   await_mount = self.mount(*widgets, before=before, after=after)                   │    self = Label() │                                                                                                   │
│   1007 │   │   return await_mountwidgets = []      │                                                                                                   │
│   1008 │                                                                                        ╰───────────────────╯                                                                                                   │
│   1009 │   @overload                                                                                                                                                                                                    │
│                                                                                                                                                                                                                         │
│ /home/mzebrak/.pyenv/versions/clive/lib/python3.10/site-packages/textual/widget.py:938 in mount                                                                                                                         │
│                                                                                                                                                                                                                         │
│    935 │   │   │   provided a ``MountError`` will be raised.                                    ╭───── locals ──────╮                                                                                                   │
│    936 │   │   """                                                                              │   after = None    │                                                                                                   │
│    937 │   │   if not self.is_attached:                                                         │  before = None    │                                                                                                   │
│ ❱  938 │   │   │   raise MountError(f"Can't mount widget(s) before {self!r} is mounted")        │    self = Label() │                                                                                                   │
│    939 │   │   # Check for duplicate IDs in the incoming widgets                                │ widgets = ()      │                                                                                                   │940 │   │   ids_to_mount = [widget.id for widget in widgets if widget.id is not None]        ╰───────────────────╯                                                                                                   │
│    941 │   │   unique_ids = set(ids_to_mount)                                                                                                                                                                           │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
MountError: Can't mount widget(s) before Label() is mounted

The strangest thing is that the error does not concern the container stored in init (which should be mounted anyway), but the elements that are for sure mounted (elements inside that container). This is strange because this error sounds as if it is not true - this situation does not occur at first glance.

Copy link

We found the following entries in the FAQ which you may find helpful:

Feel free to close this issue if you found an answer in the FAQ. Otherwise, please give us a little time to review.

This is an automated reply, generated by FAQtory

@learnbyexample
Copy link
Contributor

Yeah, many of my apps are failing due to this error. Here's a MVP of my issue:

from textual.app import App
from textual.widgets import Button
from textual.containers import VerticalScroll, Vertical

class TestApp(App):

    def __init__(self):
        super().__init__()
        self.container = VerticalScroll()

    def compose(self):
        yield self.container

    def on_mount(self):
        self.dark = False
        self.chk_mount()

    def chk_mount(self):
        foo = Vertical()
        foo.mount(Button('Click me'))
        self.container.mount(foo)

if __name__ == "__main__":
    app = TestApp()
    app.run()

In my real usecase, the widgets in the chk_mount() function are removed and constructed again to move from one question to another.

@darrenburns
Copy link
Member

darrenburns commented May 30, 2024

@learnbyexample Mounting is for putting a widget into the DOM, not for nesting not-yet-mounted widgets inside each other. To nest widgets you just pass them as children, and then mount the tree of widgets from the root:

    def chk_mount(self):
        self.container.mount(Vertical(Button('Click me')))

@darrenburns
Copy link
Member

In the MRE in the original post, it seems like Label.mount([]) is running (with an empty children list, since Label is not a container. This seems like a Textual bug to me.

I suspect await self.mount_composed_widgets(widgets) should only be getting called here if widgets is non-empty (i.e. only try to mount children if there are children).

If there are new children, then the new check that gets triggered doesn't make sense (since we're not actually trying to mount any children - Label has none).

@learnbyexample
Copy link
Contributor

learnbyexample commented May 30, 2024

@darrenburns I'll try it out tomorrow and let you know.

The Vertical in this self.container.mount(Vertical(Button('Click me'))) snippet just has a single widget. How would you add multiple widgets/containers to this Vertical without using mount? In my actual use, this Vertical has in turn multiple containers that are dynamically created and then widgets mounted to them.

Edit: Okay, I think you mean Vertical(wgt1, wgt2, wgt3) right? I'll have to handle nesting the same way (for example, container(w1, w2) instead of wgt1)?

@darrenburns
Copy link
Member

darrenburns commented May 30, 2024

@learnbyexample You can nest like this to build up the tree of widgets however you like:

stuff = Vertical(
    Button("top button"),
    Button("middle button"),
    Button("bottom button"),
    Horizontal(
        Label("foo"),
        Label("bar"),
        Vertical(
            Label("one"),
            Label("two"),
            Label("three"),
        ),
    ),
)

Then just mount like this: container.mount(stuff). You only need to mount the root container (stuff). You don't need to call mount on all of the buttons, labels, etc. there.

@willmcgugan
Copy link
Collaborator

@mzebrak If you call remove and mount in a single method they will run concurrently, which can result in things not happening in the order you expect -- which might result in flicker.

The solution would be to make the method async and await mount and remove. This will also make batch_update work (in your example it had no effect).

If you do that it should work, however it still leaves a traceback on exit, which I am looking in to now.

@learnbyexample
Copy link
Contributor

@darrenburns @willmcgugan Thanks, I got it working for my use case. I had to collect the dynamically determined widgets in a list and then pass it to a container (along with other containers and widgets).

Copy link

github-actions bot commented Jun 2, 2024

Don't forget to star the repository!

Follow @textualizeio for Textual updates.

@mzebrak
Copy link
Author

mzebrak commented Jun 3, 2024

@willmcgugan Sorry for the late response, but I was away from the computer.

@mzebrak If you call remove and mount in a single method they will run concurrently, which can result in things not happening in the order you expect -- which might result in flicker.

That sounds reasonable, but I didn't mean it flickers when I do:

    def sync(self):
        with self.app.batch_update():
            self.container.query(Label).remove()
            new_items = self._create_container_content()
            self.container.mount(*new_items)

instead, I see a flicker when I do:

    def sync(self):
        self.refresh(recompose=True)

Another interesting thing is that we never observed an issue with things not happening in the order you expect in the mentioned code, because if that was the case, it would be possible to mount new widgets first and then remove them all - we have never observed this.

The solution would be to make the method async and await mount and remove. This will also make batch_update work (in your example it had no effect).

Unfortunately doing it your suggested way has another issue - everything freezes (which I've been talking initlaly in #3214), but I think the same thing happens in the MRE under.

When spamming on the button in such an MRE makes the entire app to hang (hard to reproduce here again.. but in real app occurs very often):

MRE with async remove and mount which freezes app on pop_screen
from __future__ import annotations

from textual import on, work
from textual.app import App, ComposeResult
from textual.containers import VerticalScroll
from textual.reactive import var
from textual.screen import Screen
from textual.widgets import Button,  Label


class ScreenToPop(Screen):
    def compose(self) -> ComposeResult:
        yield Button("press me to update and pop_screen", id="button")

    @on(Button.Pressed)
    def action_update(self):
        self.app.watch_me = 0
        self.app.pop_screen()

class MyScreen(Screen):
    def __init__(self):
        super().__init__()
        self.labels_count = 0
        self.container = VerticalScroll()

    def compose(self) -> ComposeResult:
        yield Button("press me to push screen again")
        with self.container:
            yield from self._create_container_content()

    def on_mount(self):
        self.watch(self.app, "watch_me", callback=self.sync)

    async def sync(self):
        with self.app.batch_update():
            await self.container.query(Label).remove()
            new_items = self._create_container_content()
            await self.container.mount(*new_items)

    @on(Button.Pressed)
    def push_screen_again(self):
        self.app.push_screen(ScreenToPop())

    def _create_container_content(self) -> list[Label]:
        labels_increment = 40
        label_numbers = range(self.labels_count, self.labels_count + labels_increment)
        self.labels_count += labels_increment
        return [Label(f'label {i}') for i in label_numbers]


class MountErrorApp(App):
    watch_me = var(0)

    def on_mount(self):
        self.set_interval(0.1, self.update_watch_me)
        self.push_screen(MyScreen())
        self.push_screen(ScreenToPop())

    @work()
    async def update_watch_me(self):
        self.watch_me += 1


MountErrorApp().run()
Video
Screencast.from.06-03-2024.08.25.36.AM.webm

But we already observed a workaround for this freeze which is:

    def on_mount(self):
        def delegate_work() -> None:
            self.run_worker(self.sync())
        
        self.watch(self.app, "watch_me", callback=delegate_work)

but still, doing it like this:

MRE with freeze workaround
from __future__ import annotations

from textual import on, work
from textual.app import App, ComposeResult
from textual.containers import VerticalScroll
from textual.reactive import var
from textual.screen import Screen
from textual.widgets import Button,  Label


class ScreenToPop(Screen):
    def compose(self) -> ComposeResult:
        yield Button("press me to update and pop_screen", id="button")

    @on(Button.Pressed)
    def action_update(self):
        self.app.watch_me = 0
        self.app.pop_screen()

class MyScreen(Screen):
    def __init__(self):
        super().__init__()
        self.labels_count = 0
        self.container = VerticalScroll()

    def compose(self) -> ComposeResult:
        yield Button("press me to push screen again")
        with self.container:
            yield from self._create_container_content()

    def on_mount(self):
        def delegate_work() -> None:
            self.run_worker(self.sync())

        self.watch(self.app, "watch_me", callback=delegate_work)

    async def sync(self):
        with self.app.batch_update():
            await self.container.query(Label).remove()
            new_items = self._create_container_content()
            await self.container.mount(*new_items)

    @on(Button.Pressed)
    def push_screen_again(self):
        self.app.push_screen(ScreenToPop())

    def _create_container_content(self) -> list[Label]:
        labels_increment = 40
        label_numbers = range(self.labels_count, self.labels_count + labels_increment)
        self.labels_count += labels_increment
        return [Label(f'label {i}') for i in label_numbers]


class MountErrorApp(App):
    watch_me = var(0)

    def on_mount(self):
        self.set_interval(0.1, self.update_watch_me)
        self.push_screen(MyScreen())
        self.push_screen(ScreenToPop())

    @work()
    async def update_watch_me(self):
        self.watch_me += 1


MountErrorApp().run()

throws an error:

Traceback for freeze workaround - but still MountError
╭─────────────────────────────────────────────────────────────────────────────────────────── Traceback (most recent call last) ───────────────────────────────────────────────────────────────────────────────────────────╮
│ /home/mzebrak/.pyenv/versions/clive/lib/python3.10/site-packages/textual/worker.py:365 in _run                                                                                                                          │
│                                                                                                                                                                                                                         │
│   362 │   │   self.state = WorkerState.RUNNING                                                                                                                                                                          │
│   363 │   │   app.log.worker(self)                                                                                                                                                                                      │
│   364 │   │   try:                                                                                                                                                                                                      │
│ ❱ 365 │   │   │   self._result = await self.run()                                                                                                                                                                       │
│   366 │   │   except asyncio.CancelledError as error:                                                                                                                                                                   │
│   367 │   │   │   self.state = WorkerState.CANCELLED                                                                                                                                                                    │
│   368 │   │   │   self._error = error                                                                                                                                                                                   │
│                                                                                                                                                                                                                         │
│ ╭───────────────────────────────────────────────────────────── locals ─────────────────────────────────────────────────────────────╮                                                                                    │
│ │           app = MountErrorApp(title='MountErrorApp', classes={'-dark-mode'})                                                     │                                                                                    │
│ │         error = MountError("Can't mount widget(s) before VerticalScroll() is mounted")                                           │                                                                                    │
│ │          self = <Worker ERROR name='sync' description='<coroutine object MyScreen.sync at 0x7f653fe1a9d0>'>                      │                                                                                    │
│ │     Traceback = <class 'rich.traceback.Traceback'>                                                                               │                                                                                    │
│ │ worker_failed = WorkerFailed('Worker raised exception: MountError("Can\'t mount widget(s) before VerticalScroll() is mounted")') │                                                                                    │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯                                                                                    │
│                                                                                                                                                                                                                         │
│ /home/mzebrak/.pyenv/versions/clive/lib/python3.10/site-packages/textual/worker.py:349 in run                                                                                                                           │
│                                                                                                                                                                                                                         │
│   346 │   │   Returns:                                                                         ╭────────────────────────────────────────────── locals ──────────────────────────────────────────────╮                   │
│   347 │   │   │   Return value of the work.                                                    │ self = <Worker ERROR name='sync' description='<coroutine object MyScreen.sync at 0x7f653fe1a9d0>'> │                   │
│   348 │   │   """                                                                              ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯                   │
│ ❱ 349 │   │   return await (                                                                                                                                                                                            │
│   350 │   │   │   self._run_threaded() if self._thread_worker else self._run_async()                                                                                                                                    │
│   351 │   │   )                                                                                                                                                                                                         │
│   352                                                                                                                                                                                                                   │
│                                                                                                                                                                                                                         │
│ /home/mzebrak/.pyenv/versions/clive/lib/python3.10/site-packages/textual/worker.py:336 in _run_async                                                                                                                    │
│                                                                                                                                                                                                                         │
│   333 │   │   ):                                                                               ╭────────────────────────────────────────────── locals ──────────────────────────────────────────────╮                   │
│   334 │   │   │   return await self._work()                                                    │ self = <Worker ERROR name='sync' description='<coroutine object MyScreen.sync at 0x7f653fe1a9d0>'> │                   │
│   335 │   │   elif inspect.isawaitable(self._work):                                            ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯                   │
│ ❱ 336 │   │   │   return await self._work                                                                                                                                                                               │
│   337 │   │   elif callable(self._work):                                                                                                                                                                                │
│   338 │   │   │   raise WorkerError("Request to run a non-async function as an async worker")                                                                                                                           │
│   339 │   │   raise WorkerError("Unsupported attempt to run an async worker")                                                                                                                                           │
│                                                                                                                                                                                                                         │
│ /home/mzebrak/1workspace/clive/textual_mount_bug.py:41 in sync                                                                                                                                                          │
│                                                                                                                                                                                                                         │
│   38 │   │   with self.app.batch_update():                                                    ╭──────────────────────────────────────────────────── locals ─────────────────────────────────────────────────────╮       │
│   39 │   │   │   await self.container.query(Label).remove()                                   │ new_items = [Label(), Label(), Label(), Label(), Label(), Label(), Label(), Label(), Label(), Label(), ... +30] │       │
│   40 │   │   │   new_items = self._create_container_content()                                 │      self = MyScreen()                                                                                          │       │
│ ❱ 41 │   │   │   await self.container.mount(*new_items)                                       ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯       │
│   42 │                                                                                                                                                                                                                  │
│   43 │   @on(Button.Pressed)                                                                                                                                                                                            │
│   44 │   def push_screen_again(self):                                                                                                                                                                                   │
│                                                                                                                                                                                                                         │
│ /home/mzebrak/.pyenv/versions/clive/lib/python3.10/site-packages/textual/widget.py:938 in mount                                                                                                                         │
│                                                                                                                                                                                                                         │
│    935 │   │   │   provided a ``MountError`` will be raised.                                    ╭─────────────────────────────────────────────────── locals ────────────────────────────────────────────────────╮       │
│    936 │   │   """after = None                                                                                                │       │
│    937 │   │   if not self.is_attached:                                                         │  before = None                                                                                                │       │
│ ❱  938 │   │   │   raise MountError(f"Can't mount widget(s) before {self!r} is mounted")        │    self = VerticalScroll()                                                                                    │       │
│    939 │   │   # Check for duplicate IDs in the incoming widgets                                │ widgets = (Label(), Label(), Label(), Label(), Label(), Label(), Label(), Label(), Label(), Label(), ... +30) │       │940 │   │   ids_to_mount = [widget.id for widget in widgets if widget.id is not None]        ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────╯       │
│    941 │   │   unique_ids = set(ids_to_mount)                                                                                                                                                                           │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
MountError: Can't mount widget(s) before VerticalScroll() is mounted

I would be grateful if someone would look at this problem a little longer. Another problem was also noticed here, which resulted in the premature closing of this issue. I still don't see a solution for dynamically updating the screen with no MountError or freeze :/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants