Widget and (potentially) internal cache #4602
-
This is going to be a tad hard to explain, but here goes. I have an app that is a relatively simple CRUD app on top of a SQLite DB. It has the following tree:
The edit widget contains the form where I can edit a record from the DB. It raises an event with the payload that the user edited. That event is handled by the Now, the data class is instantiated in the I could be missing something and being super stupid, but to me this looks like there is some caching going on in the As an another question, the pattern of keeping the data class on Hope I'm making some sense. |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 2 replies
-
Without any code I'm afraid it is difficult to understand exactly the issue you're describing. But where you mention that...
and
...this sounds similar to a problem I encountered when first trying to get to grips with Textual. Reactives will only detect changes when you set a new value, not when you mutate an existing value. from textual.app import App, ComposeResult
from textual.reactive import reactive
from textual.widgets import Footer, Label
class ExampleApp(App):
BINDINGS = [("space", "add_next_number", "Add next number")]
numbers: reactive[list[int]] = reactive([1, 2, 3])
def compose(self) -> ComposeResult:
yield Label()
yield Footer()
def watch_numbers(self) -> None:
label = self.query_one(Label)
label.update(str(self.numbers))
def action_add_next_number(self) -> None:
next_number = self.numbers[-1] + 1
# This won't invoke the watcher
self.numbers.append(next_number)
# This will invoke the watcher
# new_numbers = [*self.numbers, next_number]
# self.numbers = new_numbers
self.notify(f"{next_number} appended to self.numbers")
if __name__ == "__main__":
app = ExampleApp()
app.run() |
Beta Was this translation helpful? Give feedback.
-
Yeah, I'm not making a lot of sense without the code, apologies for that. Below is the simplest repro I could find that is almost the same as what I am doing and is exhibiting the same results. As you can see, I am assigning to the reactive property in the listing widget directly. from logging import Logger
from typing import Type
from textual import on
from textual.app import App
from textual.driver import Driver
from textual.widgets import Static, Button, ContentSwitcher
from textual.containers import Vertical, Horizontal, Grid, VerticalScroll
from textual.message import Message
from textual.reactive import reactive
from librarian.cursed.common import Configuration
from librarian.manipulation import Manipulator
from librarian.retrieval import Retriever
class EditTest(Static):
class ItemHasChanged(Message):
def __init__(self) -> None:
super().__init__()
def compose(self):
yield VerticalScroll(
Button("Go to List", id="list_nav")
)
@on(Button.Pressed, "#list_nav")
def away(self):
self.post_message(self.ItemHasChanged())
class ListTest(Static):
class Navigate(Message):
def __init__(self) -> None:
super().__init__()
items = reactive([], always_update=True)
def compose(self):
yield Vertical(
Static("Something or another"),
Button("Go to Edit", id="edit_nav")
)
@on(Button.Pressed, "#edit_nav")
def away(self, event):
self.post_message(self.Navigate())
def watch_items(self, old_val, new_val):
if new_val:
message = ""
for item in new_val:
message += f"{item.title}\n"
self.query_one(Static).update(message)
class TestApp(App):
def __init__(self, retriever: Retriever, maniulator: Manipulator, driver_class: type[Driver] | None = None, css_path = None, watch_css: bool = False):
self.retriever = retriever
self.manipulator = maniulator
super().__init__(driver_class, css_path, watch_css)
def on_ready(self):
results = self.retriever.get_all()
self.query_one(ListTest).items = results
def compose(self):
yield VerticalScroll(
ContentSwitcher(
ListTest(id="list_widget"),
EditTest(id="edit_widget"),
initial="list_widget"
)
)
def on_edit_test_item_has_changed(self, event):
result = self.manipulator.upsert_orm({
"title": "Some test thing",
}, id=1, path="somepath")
self.notify(result.message)
if result.is_success:
self.query_one(ListTest).items = self.retriever.get_all()
self.query_one(ContentSwitcher).current = "list_widget"
def on_list_test_navigate(self, event: ListTest.Navigate):
self.query_one(ContentSwitcher).current = "edit_widget"
if __name__ == "__main__":
app = TestApp(
retriever=Retriever(catalog='./data/catalog.db', log=Logger("blah")),
maniulator=Manipulator(catalog='./data/catalog.db', log=Logger("blah"))
)
app.run() If I change the line in the handler for the As for the retriever, it is just a simple class and the |
Beta Was this translation helpful? Give feedback.
At first glance this wouldn't seem to be a Textual issue at all. What you seem to be saying here is that if you make repeat calls to the
get_all
method of an instance ofRetriever
you get the same result; but if you use a fresh instance ofRetriever
every time, you get different results.In the first instance I think I'd be checking the workings of that in isolation. As presented here this seems to be down to the workings of that library/class, not obviously something to do with Textual.