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

A watch on a reactive that is declared init=False fires watch method on init #3878

Closed
davep opened this issue Dec 15, 2023 · 2 comments · Fixed by #4030
Closed

A watch on a reactive that is declared init=False fires watch method on init #3878

davep opened this issue Dec 15, 2023 · 2 comments · Fixed by #4030
Assignees
Labels
bug Something isn't working Task

Comments

@davep
Copy link
Contributor

davep commented Dec 15, 2023

Stemming from a question on Discord; consider this code:

from textual.app import App, ComposeResult
from textual.reactive import var
from textual.widgets import Label, Log

class SomeWidget(Label):

    test_1: var[int] = var(0)
    test_2: var[int] = var(0, init=False)

    def watch_test_1(self, was: int, into: int) -> None:
        self.screen.query_one(Log).write_line(f"test_1 {was} -> {into}")

    def watch_test_2(self, was: int, into: int) -> None:
        self.screen.query_one(Log).write_line(f"test_2 {was} -> {into}")

class InitOverrideApp(App[None]):

    def compose(self) -> ComposeResult:
        yield SomeWidget()
        yield Log()

if __name__ == "__main__":
    InitOverrideApp().run()

when run, it correctly logs that test_1 changed, and correctly doesn't log that test_2 changed. Now, if we add a watch on test_2:

from textual.app import App, ComposeResult
from textual.reactive import var
from textual.widgets import Label, Log

class SomeWidget(Label):

    test_1: var[int] = var(0)
    test_2: var[int] = var(0, init=False)

    def watch_test_1(self, was: int, into: int) -> None:
        self.screen.query_one(Log).write_line(f"test_1 {was} -> {into}")

    def watch_test_2(self, was: int, into: int) -> None:
        self.screen.query_one(Log).write_line(f"test_2 {was} -> {into}")

class InitOverrideApp(App[None]):

    def compose(self) -> ComposeResult:
        yield SomeWidget()
        yield Log()

    def on_mount(self) -> None:
        def gndn() -> None:
            return
        self.watch(self.query_one(SomeWidget), "test_2", gndn)

if __name__ == "__main__":
    InitOverrideApp().run()

this appears to override the init=False declared for test_2, and causes SomeWidget.watch_test_2 to fire. This can be worked around by making the watch setup call like this:

        self.watch(self.query_one(SomeWidget), "test_2", gndn, init=False)

but it seems like an unintended consequence that the watch method (SomeWidget.watch_test_2) gets called without that.

@davep davep added bug Something isn't working Task labels Dec 15, 2023
davep added a commit to davep/textual-sandbox that referenced this issue Dec 15, 2023
@willmcgugan
Copy link
Collaborator

The explicit self.watch should only call the method given, and not any reactive watchers.

@rodrigogiraoserrao rodrigogiraoserrao self-assigned this Jan 15, 2024
rodrigogiraoserrao added a commit that referenced this issue Jan 16, 2024
When programmatically creating a watcher to a reactive attribute, init only the new watcher instead of triggering all watchers.

Related issue: #3878
Copy link

github-actions bot commented Feb 8, 2024

Don't forget to star the repository!

Follow @textualizeio for Textual updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working Task
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants