Comments (12)
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
from textual.
See the following advice about threaded workers https://textual.textualize.io/guide/workers/#posting-messages
I suspect fixing that will resolve your issue.
from textual.
Yep, that fixed the problem! Sorry for the noise.
from textual.
Don't forget to star the repository!
Follow @textualizeio for Textual updates.
from textual.
Updated the code to send a Message, but I still get race conditions when navigating around the table (rarely) or holding Ctrl+R
for a couple of seconds (more often). The end result is an empty Markdown widget and refresh doesn't help anymore:
![CleanShot 2024-04-13 at 18 18 45@2x](https://private-user-images.githubusercontent.com/370942/322201693-603b555f-84a0-48dc-8adc-c56a2e54aceb.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTMyMzE3NTMsIm5iZiI6MTcxMzIzMTQ1MywicGF0aCI6Ii8zNzA5NDIvMzIyMjAxNjkzLTYwM2I1NTVmLTg0YTAtNDhkYy04YWRjLWM1NmEyZTU0YWNlYi5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwNDE2JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDQxNlQwMTM3MzNaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1kZGJlZTZjZmJiMmM5NmM3OTE4ZDNiOTZmYjAyZDY4MzI2MmU1Y2VkMTNiNjZiYmUwOTFhNjFlM2YwMTI5MWU0JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.PS4gvxmweTJ4tBtH2H3omwyl6TosGqLLRb5JwxbxiX4)
import json
from textual import work
from textual.app import App, ComposeResult
from textual.message import Message
from textual.screen import Screen
from textual.widgets import Header, Footer, DataTable, Markdown
from textual.binding import Binding
import time, datetime
class AccountsListScreen(Screen[None]):
BINDINGS = [
("ctrl+r", "refresh", "Refresh"),
]
CSS = """
Markdown {
margin: 0 0 0 0;
padding: 1 1 0 1;
border: solid $panel-lighten-3;
}
"""
account: str | None = None
accounts: dict[str, dict] = {}
def compose(self) -> ComposeResult:
yield Header()
yield DataTable(cursor_type="row")
yield Markdown(id="detail")
yield Footer()
def on_mount(self) -> None:
self.sub_title = "Accounts"
table = self.query_one(DataTable)
table.add_columns("Name", "Instance", "Status", "Created at", "Updated at")
self.refresh_accounts()
def action_refresh(self) -> None:
self.refresh_accounts()
def on_data_table_row_highlighted(self, row: DataTable.RowSelected) -> None:
self.account = row.row_key.value
detail = self.query_one("#detail")
if self.account:
acc = self.accounts[self.account].copy()
acc["created_at"] = acc["created_at"].strftime('%Y-%m-%d %H:%M:%S')
acc["updated_at"] = acc["updated_at"].strftime('%Y-%m-%d %H:%M:%S')
MD = f"""
* Name: {acc["name"]}
* Instance: {acc["instance"]}
* Status: {acc["status"]}
* Created at: {acc["created_at"]}
* Updated at: {acc["updated_at"]}
### Params
```json
{json.dumps(acc, indent=2)}
```
""".strip()
detail.update(MD)
detail.border_title = self.account
else:
detail.update("")
detail.border_title = ""
class DBReturn(Message):
def __init__(self, accounts) -> None:
self.accounts = accounts
super().__init__()
#@work(exclusive=True)
def refresh_accounts(self) -> None:
table = self.query_one(DataTable)
# if table.loading:
# return
table.loading = True
self.get_accounts()
@work(exclusive=True, thread=True)
def get_accounts(self) -> None:
time.sleep(1) # synchronous DB call
accounts = [
{
"name": f'Test Account {i}',
"instance": "default",
"status": "active",
"created_at": datetime.datetime.now(),
"updated_at": datetime.datetime.now(),
}
for i in range(10)
]
# self.app.call_from_thread(self.on_accounts_list_screen_dbreturn, accounts)
self.post_message(self.DBReturn(accounts))
# @work(exclusive=True)
def on_accounts_list_screen_dbreturn(self, accounts):
table = self.query_one(DataTable)
table.clear()
self.account = None
self.accounts = {}
for acc in accounts.accounts:
table.add_row(
acc["name"],
acc["instance"],
acc["status"],
acc["created_at"].strftime('%Y-%m-%d %H:%M:%S'),
acc["updated_at"].strftime('%Y-%m-%d %H:%M:%S'),
key=acc["name"],
)
self.accounts[acc["name"]] = acc
self.account = self.account or acc["name"]
table.border_title = f"{len(accounts.accounts)} accounts"
table.loading = False
table.focus()
class BackofficeApp(App):
MODES = {
"accounts": AccountsListScreen,
}
BINDINGS = [
("q", "quit()", "Quit"),
Binding("f12", "take_screenshot()", "Take screenshot", show=False),
]
CSS = """
DataTable {
border: solid $accent;
}
"""
def on_mount(self) -> None:
self.title = "Title"
self.switch_mode("accounts")
def action_take_screenshot(self) -> None:
filename = self.save_screenshot()
self.notify(f"Saved in {filename}", title="Screenshot saved")
app = BackofficeApp()
if __name__ == "__main__":
app.run()
from textual.
That's quite a bit of code to wade through, but I don't understand why you have so many workers and async handlers?
from textual.
I tried with only one worker (the threaded one), but then wrapped the remaining two callbacks into async ones to see if that helps (it didn't). I just updated the code example so it no longer has the extra workers.
UPD: also fixed the formatting issues
from textual.
Threaded workers cannot be forced to cancel (a limit of threads). Which means that you need to manually check if the worker is cancelled.
I've made a couple of tweaks to your code, and I can't reproduce that issue now:
import json
from textual import work
from textual.app import App, ComposeResult
from textual.message import Message
from textual.screen import Screen
from textual.widgets import Header, Footer, DataTable, Markdown
from textual.binding import Binding
from textual.worker import get_current_worker
import time, datetime
class AccountsListScreen(Screen[None]):
BINDINGS = [
("ctrl+r", "refresh", "Refresh"),
]
CSS = """
Markdown {
margin: 0 0 0 0;
padding: 1 1 0 1;
border: solid $panel-lighten-3;
}
"""
account: str | None = None
accounts: dict[str, dict] = {}
def compose(self) -> ComposeResult:
yield Header()
yield DataTable(cursor_type="row")
yield Markdown(id="detail")
yield Footer()
def on_mount(self) -> None:
self.sub_title = "Accounts"
table = self.query_one(DataTable)
table.add_columns("Name", "Instance", "Status", "Created at", "Updated at")
self.refresh_accounts()
def action_refresh(self) -> None:
self.refresh_accounts()
def on_data_table_row_highlighted(self, row: DataTable.RowSelected) -> None:
self.account = row.row_key.value
detail = self.query_one("#detail")
if self.account:
acc = self.accounts[self.account].copy()
acc["created_at"] = acc["created_at"].strftime("%Y-%m-%d %H:%M:%S")
acc["updated_at"] = acc["updated_at"].strftime("%Y-%m-%d %H:%M:%S")
MD = f"""
* Name: {acc["name"]}
* Instance: {acc["instance"]}
* Status: {acc["status"]}
* Created at: {acc["created_at"]}
* Updated at: {acc["updated_at"]}
### Params
```json
{json.dumps(acc, indent=2)}
```
""".strip()
detail.update(MD)
detail.border_title = self.account
else:
detail.update("")
detail.border_title = ""
class DBReturn(Message):
def __init__(self, accounts) -> None:
self.accounts = accounts
super().__init__()
# @work(exclusive=True)
def refresh_accounts(self) -> None:
table = self.query_one(DataTable)
# if table.loading:
# return
table.loading = True
self.get_accounts()
@work(exclusive=True, thread=True)
def get_accounts(self) -> None:
time.sleep(1) # synchronous DB call
accounts = [
{
"name": f"Test Account {i}",
"instance": "default",
"status": "active",
"created_at": datetime.datetime.now(),
"updated_at": datetime.datetime.now(),
}
for i in range(10)
]
# self.app.call_from_thread(self.on_accounts_list_screen_dbreturn, accounts)
if not get_current_worker().is_cancelled:
self.post_message(self.DBReturn(accounts))
# @work(exclusive=True)
def on_accounts_list_screen_dbreturn(self, accounts):
table = self.query_one(DataTable)
table.clear()
self.account = None
self.accounts = {}
for acc in accounts.accounts:
table.add_row(
acc["name"],
acc["instance"],
acc["status"],
acc["created_at"].strftime("%Y-%m-%d %H:%M:%S"),
acc["updated_at"].strftime("%Y-%m-%d %H:%M:%S"),
key=acc["name"],
)
self.accounts[acc["name"]] = acc
self.account = self.account or acc["name"]
table.border_title = f"{len(accounts.accounts)} accounts"
table.loading = False
table.focus()
class BackofficeApp(App):
MODES = {
"accounts": AccountsListScreen,
}
BINDINGS = [
("q", "quit()", "Quit"),
Binding("f12", "take_screenshot()", "Take screenshot", show=False),
]
CSS = """
DataTable {
border: solid $accent;
}
"""
def on_mount(self) -> None:
self.title = "Title"
self.switch_mode("accounts")
def action_take_screenshot(self) -> None:
filename = self.save_screenshot()
self.notify(f"Saved in {filename}", title="Screenshot saved")
app = BackofficeApp()
if __name__ == "__main__":
app.run()
from textual.
I tried your version and the Ctrl+R
problem seems to be gone. Thanks!
But the Markdown widget is still get blanked out when I repeatedly press UP and DOWN keys:
Wrapping the on_data_table_row_highlighted
into an async worker doesn't help. Is something tricky required to prevent this as well?
from textual.
Here's a quick MRE for the issue described above when rapidly updating the Markdown
widget. Presumably the problem is that parsing the content is quite an expensive operation - is the best solution to await
the return from Markdown.update?
from textual import on
from textual.app import App, ComposeResult
from textual.widgets import DataTable, Markdown
EXAMPLE_MARKDOWN = """\
## Try continuously scrolling the table by holding the down key
This is an example of Textual's `Markdown` widget.
Markdown syntax and extensions are supported.
- Typography *emphasis*, **strong**, `inline code` etc.
- Headers
- Lists (bullet and ordered)
- Syntax highlighted code blocks
- Tables!
"""
class ExampleApp(App):
CSS = """
DataTable, Markdown {
height: 50%;
}
"""
def compose(self) -> ComposeResult:
yield DataTable(cursor_type="row")
yield Markdown()
def on_mount(self) -> None:
table = self.query_one(DataTable)
table.add_column("Column")
for i in range(100):
table.add_row(f"Row #{i}")
@on(DataTable.RowHighlighted)
def update_markdown(self, event: DataTable.RowHighlighted) -> None:
markdown = self.query_one(Markdown)
markdown.update(f"# Row #{event.cursor_row}\n{EXAMPLE_MARKDOWN}")
if __name__ == "__main__":
app = ExampleApp()
app.run()
from textual.
Related Issues (20)
- Grapheme clusters width miscalculation HOT 4
- Escape sequences after exiting app with `run(mouse=False)` HOT 4
- Could textual support React hooks? HOT 2
- Potential bug with bindings not appearing after 0.55.0 HOT 4
- Inline mode and the command palette don't play well together HOT 3
- Adding columns to empty `DataTable` won't appear? HOT 3
- Relative links in docs are not correct HOT 10
- Select constructor shouldn't send Changed message HOT 1
- Update SelectionToggled docs HOT 2
- `SelectionList.select` doesn't post `SelectionToggled` message HOT 1
- Interactive widgets in inline mode appear "laggy" as of 0.56.2 HOT 5
- Is inline mode currently supported on Windows? HOT 4
- KeyError on DataTable border title link click HOT 2
- `margin` CSS property leads to `textual.pilot.OutOfBounds` during testing. HOT 6
- [Bug] Describe failures for Integer validator HOT 2
- Disabling Bindings HOT 3
- Mouse movement control sequences leak to terminal at app shutdown HOT 19
- Redirecting stderr doesn't show the interface
- Cursor position incorrect HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from textual.