* log

* tests

* snapshot tests

* change to richlog

* keep raw lines

* disable highlighting by default

* simplify

* superfluous test

* optimization

* update cell length

* add refresh

* write method

* version bump

* doc fix link

* makes lines private

* docstring

* relax dev dependancy

* remove superfluous code [skip ci]

* added FAQ [skipci]

* fix code in faq [skipci]

* fix typo

* max lines fix
This commit is contained in:
Will McGugan
2023-08-03 10:11:17 +01:00
committed by GitHub
parent b045306c69
commit 879c985296
48 changed files with 3302 additions and 2287 deletions

View File

@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [0.32.0] - 2023-08-03
### Added
- Added widgets.Log
- Added Widget.is_vertical_scroll_end, Widget.is_horizontal_scroll_end, Widget.is_vertical_scrollbar_grabbed, Widget.is_horizontal_scrollbar_grabbed
### Changed
- Breaking change: Renamed TextLog to RichLog
## [0.31.0] - 2023-08-01
### Added

28
FAQ.md
View File

@@ -7,8 +7,9 @@
- [How can I select and copy text in a Textual app?](#how-can-i-select-and-copy-text-in-a-textual-app)
- [How can I set a translucent app background?](#how-can-i-set-a-translucent-app-background)
- [How do I center a widget in a screen?](#how-do-i-center-a-widget-in-a-screen)
- [How do I fixed WorkerDeclarationError?](#how-do-i-fixed-workerdeclarationerror)
- [How do I fix WorkerDeclarationError?](#how-do-i-fix-workerdeclarationerror)
- [How do I pass arguments to an app?](#how-do-i-pass-arguments-to-an-app)
- [No widget called TextLog](#no-widget-called-textlog)
- [Why do some key combinations never make it to my app?](#why-do-some-key-combinations-never-make-it-to-my-app)
- [Why doesn't Textual look good on macOS?](#why-doesn't-textual-look-good-on-macos)
- [Why doesn't Textual support ANSI themes?](#why-doesn't-textual-support-ansi-themes)
@@ -145,8 +146,8 @@ if __name__ == "__main__":
ButtonApp().run()
```
<a name="how-do-i-fixed-workerdeclarationerror"></a>
## How do I fixed WorkerDeclarationError?
<a name="how-do-i-fix-workerdeclarationerror"></a>
## How do I fix WorkerDeclarationError?
Textual version 0.31.0 requires that you set `thread=True` on the `@work` decorator if you want to run a threaded worker.
@@ -158,15 +159,15 @@ def run_in_background():
...
```
If you *don't* want a worker, you should make your work function `async`:
If you *don't* want a threaded worker, you should make your work function `async`:
```python
@work():
@work()
async def run_in_background():
...
```
This change was made because it was easy to accidentally created a threaded worker, which may produce unexpected results.
This change was made because it was too easy to accidentally create a threaded worker, which may produce unexpected results.
<a name="how-do-i-pass-arguments-to-an-app"></a>
## How do I pass arguments to an app?
@@ -195,13 +196,26 @@ Then the app can be run, passing in various arguments; for example:
# Running with default arguments.
Greetings().run()
# Running with a keyword arguyment.
# Running with a keyword argument.
Greetings(to_greet="davep").run()
# Running with both positional arguments.
Greetings("Well hello", "there").run()
```
<a name="no-widget-called-textlog"></a>
## No widget called TextLog
The `TextLog` widget was renamed to `RichLog` in Textual 0.32.0.
You will need to replace all references to `TextLog` in your code, with `RichLog`.
Most IDEs will have a search and replace function which will help you do this.
Here's how you should import RichLog:
```python
from textual.widgets import RichLog
```
<a name="why-do-some-key-combinations-never-make-it-to-my-app"></a>
## Why do some key combinations never make it to my app?

View File

@@ -1,16 +1,16 @@
from textual.app import App, ComposeResult
from textual.widgets import TextLog
from textual import events
from textual.app import App, ComposeResult
from textual.widgets import RichLog
class InputApp(App):
"""App to display key events."""
def compose(self) -> ComposeResult:
yield TextLog()
yield RichLog()
def on_key(self, event: events.Key) -> None:
self.query_one(TextLog).write(event)
self.query_one(RichLog).write(event)
if __name__ == "__main__":

View File

@@ -1,16 +1,16 @@
from textual.app import App, ComposeResult
from textual.widgets import TextLog
from textual import events
from textual.app import App, ComposeResult
from textual.widgets import RichLog
class InputApp(App):
"""App to display key events."""
def compose(self) -> ComposeResult:
yield TextLog()
yield RichLog()
def on_key(self, event: events.Key) -> None:
self.query_one(TextLog).write(event)
self.query_one(RichLog).write(event)
def key_space(self) -> None:
self.bell()

View File

@@ -1,9 +1,9 @@
from textual.app import App, ComposeResult
from textual.widgets import TextLog
from textual import events
from textual.app import App, ComposeResult
from textual.widgets import RichLog
class KeyLogger(TextLog):
class KeyLogger(RichLog):
def on_key(self, event: events.Key) -> None:
self.write(event)

View File

@@ -1,7 +1,7 @@
from textual import events
from textual.app import App, ComposeResult
from textual.containers import Container
from textual.widgets import Static, TextLog
from textual.widgets import RichLog, Static
class PlayArea(Container):
@@ -9,7 +9,7 @@ class PlayArea(Container):
self.capture_mouse()
def on_mouse_move(self, event: events.MouseMove) -> None:
self.screen.query_one(TextLog).write(event)
self.screen.query_one(RichLog).write(event)
self.query_one(Ball).offset = event.offset - (8, 2)
@@ -21,7 +21,7 @@ class MouseApp(App):
CSS_PATH = "mouse01.css"
def compose(self) -> ComposeResult:
yield TextLog()
yield RichLog()
yield PlayArea(Ball("Textual"))

View File

@@ -1,7 +1,7 @@
from textual.app import App, ComposeResult
from textual.containers import Horizontal
from textual.reactive import reactive
from textual.widgets import Button, TextLog
from textual.widgets import Button, RichLog
class ValidateApp(App):
@@ -23,14 +23,14 @@ class ValidateApp(App):
Button("-1", id="minus", variant="error"),
id="buttons",
)
yield TextLog(highlight=True)
yield RichLog(highlight=True)
def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "plus":
self.count += 1
else:
self.count -= 1
self.query_one(TextLog).write(f"{self.count=}")
self.query_one(RichLog).write(f"{self.count=}")
if __name__ == "__main__":

View File

@@ -0,0 +1,28 @@
from textual.app import App, ComposeResult
from textual.widgets import Log
TEXT = """I must not fear.
Fear is the mind-killer.
Fear is the little-death that brings total obliteration.
I will face my fear.
I will permit it to pass over me and through me.
And when it has gone past, I will turn the inner eye to see its path.
Where the fear has gone there will be nothing. Only I will remain."""
class LogApp(App):
"""An app with a simple log."""
def compose(self) -> ComposeResult:
yield Log()
def on_ready(self) -> None:
log = self.query_one(Log)
log.write_line("Hello, World!")
for _ in range(10):
log.write_line(TEXT)
if __name__ == "__main__":
app = LogApp()
app.run()

View File

@@ -1,13 +1,12 @@
import csv
import io
from rich.table import Table
from rich.syntax import Syntax
from rich.table import Table
from textual.app import App, ComposeResult
from textual import events
from textual.widgets import TextLog
from textual.app import App, ComposeResult
from textual.widgets import RichLog
CSV = """lane,swimmer,country,time
4,Joseph Schooling,Singapore,50.39
@@ -37,13 +36,13 @@ def loop_first_last(values: Iterable[T]) -> Iterable[tuple[bool, bool, T]]:
'''
class TextLogApp(App):
class RichLogApp(App):
def compose(self) -> ComposeResult:
yield TextLog(highlight=True, markup=True)
yield RichLog(highlight=True, markup=True)
def on_ready(self) -> None:
"""Called when the DOM is ready."""
text_log = self.query_one(TextLog)
text_log = self.query_one(RichLog)
text_log.write(Syntax(CODE, "python", indent_guides=True))
@@ -57,10 +56,10 @@ class TextLogApp(App):
def on_key(self, event: events.Key) -> None:
"""Write Key events to log."""
text_log = self.query_one(TextLog)
text_log = self.query_one(RichLog)
text_log.write(event)
if __name__ == "__main__":
app = TextLogApp()
app = RichLogApp()
app.run()

View File

@@ -23,7 +23,7 @@ The most fundamental way to receive input is via [Key][textual.events.Key] event
```{.textual path="docs/examples/guide/input/key01.py", press="T,e,x,t,u,a,l,!"}
```
When you press a key, the app will receive the event and write it to a [TextLog](../widgets/text_log.md) widget. Try pressing a few keys to see what happens.
When you press a key, the app will receive the event and write it to a [RichLog](../widgets/rich_log.md) widget. Try pressing a few keys to see what happens.
!!! tip
@@ -105,7 +105,7 @@ The following example shows how focus works in practice.
```{.textual path="docs/examples/guide/input/key03.py", press="H,e,l,l,o,tab,W,o,r,l,d,!"}
```
The app splits the screen in to quarters, with a `TextLog` widget in each quarter. If you click any of the text logs, you should see that it is highlighted to show that the widget has focus. Key events will be sent to the focused widget only.
The app splits the screen in to quarters, with a `RichLog` widget in each quarter. If you click any of the text logs, you should see that it is highlighted to show that the widget has focus. Key events will be sent to the focused widget only.
!!! tip

View File

@@ -434,7 +434,7 @@ You should find that if you move the mouse over the widget now, it will highligh
The following builtin widgets use the Line API. If you are building advanced widgets, it may be worth looking through the code for inspiration!
- [DataTable](https://github.com/Textualize/textual/blob/main/src/textual/widgets/_data_table.py)
- [TextLog](https://github.com/Textualize/textual/blob/main/src/textual/widgets/_text_log.py)
- [RichLog](https://github.com/Textualize/textual/blob/main/src/textual/widgets/_rich_log.py)
- [Tree](https://github.com/Textualize/textual/blob/main/src/textual/widgets/_tree.py)
## Compound widgets

View File

@@ -118,6 +118,15 @@ Display an animation while data is loading.
```{.textual path="docs/examples/widgets/loading_indicator.py"}
```
## Log
Display and update lines of text (such as from a file).
[Log reference](./widgets/log.md){ .md-button .md-button--primary }
```{.textual path="docs/examples/widgets/log.py"}
```
## MarkdownViewer
Display and interact with a Markdown document (adds a table of contents and browser-like navigation to Markdown).
@@ -198,6 +207,15 @@ A collection of radio buttons, that enforces uniqueness.
```{.textual path="docs/examples/widgets/radio_set.py"}
```
## RichLog
Display and update text in a scrolling panel.
[RichLog reference](./widgets/rich_log.md){ .md-button .md-button--primary }
```{.textual path="docs/examples/widgets/rich_log.py" press="H,i"}
```
## Select
Select from a number of possible options.
@@ -260,14 +278,6 @@ A Combination of Tabs and ContentSwitcher to navigate static content.
```{.textual path="docs/examples/widgets/tabbed_content.py" press="j"}
```
## TextLog
Display and update text in a scrolling panel.
[TextLog reference](./widgets/text_log.md){ .md-button .md-button--primary }
```{.textual path="docs/examples/widgets/text_log.py" press="H,i"}
```
## Tree

51
docs/widgets/log.md Normal file
View File

@@ -0,0 +1,51 @@
# Log
!!! tip "Added in version 0.32.0"
A Log widget displays lines of text which may be appended to in realtime.
Call [Log.write_line][textual.widgets.Log.write_line] to write a line at a time, or [Log.write_lines][textual.widgets.Log.write_lines] to write multiple lines at once. Call [Log.clear][textual.widgets.Log.clear] to clear the Log widget.
!!! tip
See also [RichLog](../widgets/rich_log.md) which can write more than just text, and supports a number of advanced features.
- [X] Focusable
- [ ] Container
## Example
The example below shows how to write text to a `Log` widget:
=== "Output"
```{.textual path="docs/examples/widgets/log.py"}
```
=== "log.py"
```python
--8<-- "docs/examples/widgets/log.py"
```
## Reactive Attributes
| Name | Type | Default | Description |
| ------------- | ------ | ------- | ------------------------------------------------------------ |
| `max_lines` | `int` | `None` | Maximum number of lines in the log or `None` for no maximum. |
| `auto_scroll` | `bool` | `False` | Scroll to end of log when new lines are added. |
## Messages
This widget sends no messages.
---
::: textual.widgets.Log
options:
heading_level: 2

View File

@@ -1,25 +1,29 @@
# TextLog
# RichLog
A TextLog is a widget which displays scrollable content that may be appended to in realtime.
A RichLog is a widget which displays scrollable content that may be appended to in realtime.
Call [TextLog.write][textual.widgets.TextLog.write] with a string or [Rich Renderable](https://rich.readthedocs.io/en/latest/protocol.html) to write content to the end of the TextLog. Call [TextLog.clear][textual.widgets.TextLog.clear] to clear the content.
Call [RichLog.write][textual.widgets.RichLog.write] with a string or [Rich Renderable](https://rich.readthedocs.io/en/latest/protocol.html) to write content to the end of the RichLog. Call [RichLog.clear][textual.widgets.RichLog.clear] to clear the content.
!!! tip
See also [Log](../widgets/log.md) which is an alternative to `RichLog` but specialized for simple text.
- [X] Focusable
- [ ] Container
## Example
The example below shows an application showing a `TextLog` with different kinds of data logged.
The example below shows an application showing a `RichLog` with different kinds of data logged.
=== "Output"
```{.textual path="docs/examples/widgets/text_log.py" press="H,i"}
```{.textual path="docs/examples/widgets/rich_log.py" press="H,i"}
```
=== "text_log.py"
=== "rich_log.py"
```python
--8<-- "docs/examples/widgets/text_log.py"
--8<-- "docs/examples/widgets/rich_log.py"
```
@@ -42,6 +46,6 @@ This widget sends no messages.
---
::: textual.widgets.TextLog
::: textual.widgets.RichLog
options:
heading_level: 2

View File

@@ -1,203 +1,204 @@
nav:
- Introduction:
- "index.md"
- "getting_started.md"
- "help.md"
- "tutorial.md"
- Guide:
- "guide/index.md"
- "guide/devtools.md"
- "guide/app.md"
- "guide/styles.md"
- "guide/CSS.md"
- "guide/design.md"
- "guide/queries.md"
- "guide/layout.md"
- "guide/events.md"
- "guide/input.md"
- "guide/actions.md"
- "guide/reactivity.md"
- "guide/widgets.md"
- "guide/animation.md"
- "guide/screens.md"
- "guide/workers.md"
- "widget_gallery.md"
- Reference:
- "reference/index.md"
- CSS Types:
- "css_types/index.md"
- "css_types/border.md"
- "css_types/color.md"
- "css_types/horizontal.md"
- "css_types/integer.md"
- "css_types/name.md"
- "css_types/number.md"
- "css_types/overflow.md"
- "css_types/percentage.md"
- "css_types/scalar.md"
- "css_types/text_align.md"
- "css_types/text_style.md"
- "css_types/vertical.md"
- Events:
- "events/index.md"
- "events/blur.md"
- "events/descendant_blur.md"
- "events/descendant_focus.md"
- "events/enter.md"
- "events/focus.md"
- "events/hide.md"
- "events/key.md"
- "events/leave.md"
- "events/load.md"
- "events/mount.md"
- "events/mouse_capture.md"
- "events/click.md"
- "events/mouse_down.md"
- "events/mouse_move.md"
- "events/mouse_release.md"
- "events/mouse_scroll_down.md"
- "events/mouse_scroll_up.md"
- "events/mouse_up.md"
- "events/paste.md"
- "events/resize.md"
- "events/screen_resume.md"
- "events/screen_suspend.md"
- "events/show.md"
- Styles:
- "styles/align.md"
- "styles/background.md"
- "styles/border.md"
- "styles/border_subtitle_align.md"
- "styles/border_subtitle_background.md"
- "styles/border_subtitle_color.md"
- "styles/border_subtitle_style.md"
- "styles/border_title_align.md"
- "styles/border_title_background.md"
- "styles/border_title_color.md"
- "styles/border_title_style.md"
- "styles/box_sizing.md"
- "styles/color.md"
- "styles/content_align.md"
- "styles/display.md"
- "styles/dock.md"
- "styles/index.md"
- Grid:
- "styles/grid/index.md"
- "styles/grid/column_span.md"
- "styles/grid/grid_columns.md"
- "styles/grid/grid_gutter.md"
- "styles/grid/grid_rows.md"
- "styles/grid/grid_size.md"
- "styles/grid/row_span.md"
- "styles/height.md"
- "styles/layer.md"
- "styles/layers.md"
- "styles/layout.md"
- Links:
- "styles/links/index.md"
- "styles/links/link_background.md"
- "styles/links/link_color.md"
- "styles/links/link_hover_background.md"
- "styles/links/link_hover_color.md"
- "styles/links/link_hover_style.md"
- "styles/links/link_style.md"
- "styles/margin.md"
- "styles/max_height.md"
- "styles/max_width.md"
- "styles/min_height.md"
- "styles/min_width.md"
- "styles/offset.md"
- "styles/opacity.md"
- "styles/outline.md"
- "styles/overflow.md"
- "styles/padding.md"
- Scrollbar colors:
- "styles/scrollbar_colors/index.md"
- "styles/scrollbar_colors/scrollbar_background.md"
- "styles/scrollbar_colors/scrollbar_background_active.md"
- "styles/scrollbar_colors/scrollbar_background_hover.md"
- "styles/scrollbar_colors/scrollbar_color.md"
- "styles/scrollbar_colors/scrollbar_color_active.md"
- "styles/scrollbar_colors/scrollbar_color_hover.md"
- "styles/scrollbar_colors/scrollbar_corner_color.md"
- "styles/scrollbar_gutter.md"
- "styles/scrollbar_size.md"
- "styles/text_align.md"
- "styles/text_opacity.md"
- "styles/text_style.md"
- "styles/tint.md"
- "styles/visibility.md"
- "styles/width.md"
- Widgets:
- "widgets/button.md"
- "widgets/checkbox.md"
- "widgets/content_switcher.md"
- "widgets/data_table.md"
- "widgets/directory_tree.md"
- "widgets/footer.md"
- "widgets/header.md"
- "widgets/index.md"
- "widgets/input.md"
- "widgets/label.md"
- "widgets/list_item.md"
- "widgets/list_view.md"
- "widgets/loading_indicator.md"
- "widgets/markdown_viewer.md"
- "widgets/markdown.md"
- "widgets/option_list.md"
- "widgets/placeholder.md"
- "widgets/pretty.md"
- "widgets/progress_bar.md"
- "widgets/radiobutton.md"
- "widgets/radioset.md"
- "widgets/select.md"
- "widgets/selection_list.md"
- "widgets/sparkline.md"
- "widgets/static.md"
- "widgets/switch.md"
- "widgets/tabbed_content.md"
- "widgets/tabs.md"
- "widgets/text_log.md"
- "widgets/tree.md"
- API:
- "api/index.md"
- "api/app.md"
- "api/await_remove.md"
- "api/binding.md"
- "api/color.md"
- "api/containers.md"
- "api/coordinate.md"
- "api/dom_node.md"
- "api/events.md"
- "api/errors.md"
- "api/filter.md"
- "api/geometry.md"
- "api/logger.md"
- "api/logging.md"
- "api/map_geometry.md"
- "api/message_pump.md"
- "api/message.md"
- "api/on.md"
- "api/pilot.md"
- "api/query.md"
- "api/reactive.md"
- "api/screen.md"
- "api/scrollbar.md"
- "api/scroll_view.md"
- "api/strip.md"
- "api/suggester.md"
- "api/timer.md"
- "api/types.md"
- "api/validation.md"
- "api/walk.md"
- "api/widget.md"
- "api/work.md"
- "api/worker.md"
- "api/worker_manager.md"
- "How To":
- "how-to/index.md"
- "how-to/center-things.md"
- "how-to/design-a-layout.md"
- "roadmap.md"
- "Blog":
- blog/index.md
- Introduction:
- "index.md"
- "getting_started.md"
- "help.md"
- "tutorial.md"
- Guide:
- "guide/index.md"
- "guide/devtools.md"
- "guide/app.md"
- "guide/styles.md"
- "guide/CSS.md"
- "guide/design.md"
- "guide/queries.md"
- "guide/layout.md"
- "guide/events.md"
- "guide/input.md"
- "guide/actions.md"
- "guide/reactivity.md"
- "guide/widgets.md"
- "guide/animation.md"
- "guide/screens.md"
- "guide/workers.md"
- "widget_gallery.md"
- Reference:
- "reference/index.md"
- CSS Types:
- "css_types/index.md"
- "css_types/border.md"
- "css_types/color.md"
- "css_types/horizontal.md"
- "css_types/integer.md"
- "css_types/name.md"
- "css_types/number.md"
- "css_types/overflow.md"
- "css_types/percentage.md"
- "css_types/scalar.md"
- "css_types/text_align.md"
- "css_types/text_style.md"
- "css_types/vertical.md"
- Events:
- "events/index.md"
- "events/blur.md"
- "events/descendant_blur.md"
- "events/descendant_focus.md"
- "events/enter.md"
- "events/focus.md"
- "events/hide.md"
- "events/key.md"
- "events/leave.md"
- "events/load.md"
- "events/mount.md"
- "events/mouse_capture.md"
- "events/click.md"
- "events/mouse_down.md"
- "events/mouse_move.md"
- "events/mouse_release.md"
- "events/mouse_scroll_down.md"
- "events/mouse_scroll_up.md"
- "events/mouse_up.md"
- "events/paste.md"
- "events/resize.md"
- "events/screen_resume.md"
- "events/screen_suspend.md"
- "events/show.md"
- Styles:
- "styles/align.md"
- "styles/background.md"
- "styles/border.md"
- "styles/border_subtitle_align.md"
- "styles/border_subtitle_background.md"
- "styles/border_subtitle_color.md"
- "styles/border_subtitle_style.md"
- "styles/border_title_align.md"
- "styles/border_title_background.md"
- "styles/border_title_color.md"
- "styles/border_title_style.md"
- "styles/box_sizing.md"
- "styles/color.md"
- "styles/content_align.md"
- "styles/display.md"
- "styles/dock.md"
- "styles/index.md"
- Grid:
- "styles/grid/index.md"
- "styles/grid/column_span.md"
- "styles/grid/grid_columns.md"
- "styles/grid/grid_gutter.md"
- "styles/grid/grid_rows.md"
- "styles/grid/grid_size.md"
- "styles/grid/row_span.md"
- "styles/height.md"
- "styles/layer.md"
- "styles/layers.md"
- "styles/layout.md"
- Links:
- "styles/links/index.md"
- "styles/links/link_background.md"
- "styles/links/link_color.md"
- "styles/links/link_hover_background.md"
- "styles/links/link_hover_color.md"
- "styles/links/link_hover_style.md"
- "styles/links/link_style.md"
- "styles/margin.md"
- "styles/max_height.md"
- "styles/max_width.md"
- "styles/min_height.md"
- "styles/min_width.md"
- "styles/offset.md"
- "styles/opacity.md"
- "styles/outline.md"
- "styles/overflow.md"
- "styles/padding.md"
- Scrollbar colors:
- "styles/scrollbar_colors/index.md"
- "styles/scrollbar_colors/scrollbar_background.md"
- "styles/scrollbar_colors/scrollbar_background_active.md"
- "styles/scrollbar_colors/scrollbar_background_hover.md"
- "styles/scrollbar_colors/scrollbar_color.md"
- "styles/scrollbar_colors/scrollbar_color_active.md"
- "styles/scrollbar_colors/scrollbar_color_hover.md"
- "styles/scrollbar_colors/scrollbar_corner_color.md"
- "styles/scrollbar_gutter.md"
- "styles/scrollbar_size.md"
- "styles/text_align.md"
- "styles/text_opacity.md"
- "styles/text_style.md"
- "styles/tint.md"
- "styles/visibility.md"
- "styles/width.md"
- Widgets:
- "widgets/button.md"
- "widgets/checkbox.md"
- "widgets/content_switcher.md"
- "widgets/data_table.md"
- "widgets/directory_tree.md"
- "widgets/footer.md"
- "widgets/header.md"
- "widgets/index.md"
- "widgets/input.md"
- "widgets/label.md"
- "widgets/list_item.md"
- "widgets/list_view.md"
- "widgets/loading_indicator.md"
- "widgets/log.md"
- "widgets/markdown_viewer.md"
- "widgets/markdown.md"
- "widgets/option_list.md"
- "widgets/placeholder.md"
- "widgets/pretty.md"
- "widgets/progress_bar.md"
- "widgets/radiobutton.md"
- "widgets/radioset.md"
- "widgets/rich_log.md"
- "widgets/select.md"
- "widgets/selection_list.md"
- "widgets/sparkline.md"
- "widgets/static.md"
- "widgets/switch.md"
- "widgets/tabbed_content.md"
- "widgets/tabs.md"
- "widgets/tree.md"
- API:
- "api/index.md"
- "api/app.md"
- "api/await_remove.md"
- "api/binding.md"
- "api/color.md"
- "api/containers.md"
- "api/coordinate.md"
- "api/dom_node.md"
- "api/events.md"
- "api/errors.md"
- "api/filter.md"
- "api/geometry.md"
- "api/logger.md"
- "api/logging.md"
- "api/map_geometry.md"
- "api/message_pump.md"
- "api/message.md"
- "api/on.md"
- "api/pilot.md"
- "api/query.md"
- "api/reactive.md"
- "api/screen.md"
- "api/scrollbar.md"
- "api/scroll_view.md"
- "api/strip.md"
- "api/suggester.md"
- "api/timer.md"
- "api/types.md"
- "api/validation.md"
- "api/walk.md"
- "api/widget.md"
- "api/work.md"
- "api/worker.md"
- "api/worker_manager.md"
- "How To":
- "how-to/index.md"
- "how-to/center-things.md"
- "how-to/design-a-layout.md"
- "roadmap.md"
- "Blog":
- blog/index.md

356
poetry.lock generated
View File

@@ -2,99 +2,99 @@
[[package]]
name = "aiohttp"
version = "3.8.4"
version = "3.8.5"
description = "Async http client/server framework (asyncio)"
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
{file = "aiohttp-3.8.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5ce45967538fb747370308d3145aa68a074bdecb4f3a300869590f725ced69c1"},
{file = "aiohttp-3.8.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b744c33b6f14ca26b7544e8d8aadff6b765a80ad6164fb1a430bbadd593dfb1a"},
{file = "aiohttp-3.8.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a45865451439eb320784918617ba54b7a377e3501fb70402ab84d38c2cd891b"},
{file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a86d42d7cba1cec432d47ab13b6637bee393a10f664c425ea7b305d1301ca1a3"},
{file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee3c36df21b5714d49fc4580247947aa64bcbe2939d1b77b4c8dcb8f6c9faecc"},
{file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:176a64b24c0935869d5bbc4c96e82f89f643bcdf08ec947701b9dbb3c956b7dd"},
{file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c844fd628851c0bc309f3c801b3a3d58ce430b2ce5b359cd918a5a76d0b20cb5"},
{file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5393fb786a9e23e4799fec788e7e735de18052f83682ce2dfcabaf1c00c2c08e"},
{file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e4b09863aae0dc965c3ef36500d891a3ff495a2ea9ae9171e4519963c12ceefd"},
{file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:adfbc22e87365a6e564c804c58fc44ff7727deea782d175c33602737b7feadb6"},
{file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:147ae376f14b55f4f3c2b118b95be50a369b89b38a971e80a17c3fd623f280c9"},
{file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:eafb3e874816ebe2a92f5e155f17260034c8c341dad1df25672fb710627c6949"},
{file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6cc15d58053c76eacac5fa9152d7d84b8d67b3fde92709195cb984cfb3475ea"},
{file = "aiohttp-3.8.4-cp310-cp310-win32.whl", hash = "sha256:59f029a5f6e2d679296db7bee982bb3d20c088e52a2977e3175faf31d6fb75d1"},
{file = "aiohttp-3.8.4-cp310-cp310-win_amd64.whl", hash = "sha256:fe7ba4a51f33ab275515f66b0a236bcde4fb5561498fe8f898d4e549b2e4509f"},
{file = "aiohttp-3.8.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d8ef1a630519a26d6760bc695842579cb09e373c5f227a21b67dc3eb16cfea4"},
{file = "aiohttp-3.8.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b3f2e06a512e94722886c0827bee9807c86a9f698fac6b3aee841fab49bbfb4"},
{file = "aiohttp-3.8.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a80464982d41b1fbfe3154e440ba4904b71c1a53e9cd584098cd41efdb188ef"},
{file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b631e26df63e52f7cce0cce6507b7a7f1bc9b0c501fcde69742130b32e8782f"},
{file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f43255086fe25e36fd5ed8f2ee47477408a73ef00e804cb2b5cba4bf2ac7f5e"},
{file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d347a172f866cd1d93126d9b239fcbe682acb39b48ee0873c73c933dd23bd0f"},
{file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3fec6a4cb5551721cdd70473eb009d90935b4063acc5f40905d40ecfea23e05"},
{file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80a37fe8f7c1e6ce8f2d9c411676e4bc633a8462844e38f46156d07a7d401654"},
{file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d1e6a862b76f34395a985b3cd39a0d949ca80a70b6ebdea37d3ab39ceea6698a"},
{file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cd468460eefef601ece4428d3cf4562459157c0f6523db89365202c31b6daebb"},
{file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:618c901dd3aad4ace71dfa0f5e82e88b46ef57e3239fc7027773cb6d4ed53531"},
{file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:652b1bff4f15f6287550b4670546a2947f2a4575b6c6dff7760eafb22eacbf0b"},
{file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80575ba9377c5171407a06d0196b2310b679dc752d02a1fcaa2bc20b235dbf24"},
{file = "aiohttp-3.8.4-cp311-cp311-win32.whl", hash = "sha256:bbcf1a76cf6f6dacf2c7f4d2ebd411438c275faa1dc0c68e46eb84eebd05dd7d"},
{file = "aiohttp-3.8.4-cp311-cp311-win_amd64.whl", hash = "sha256:6e74dd54f7239fcffe07913ff8b964e28b712f09846e20de78676ce2a3dc0bfc"},
{file = "aiohttp-3.8.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:880e15bb6dad90549b43f796b391cfffd7af373f4646784795e20d92606b7a51"},
{file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb96fa6b56bb536c42d6a4a87dfca570ff8e52de2d63cabebfd6fb67049c34b6"},
{file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a6cadebe132e90cefa77e45f2d2f1a4b2ce5c6b1bfc1656c1ddafcfe4ba8131"},
{file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f352b62b45dff37b55ddd7b9c0c8672c4dd2eb9c0f9c11d395075a84e2c40f75"},
{file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ab43061a0c81198d88f39aaf90dae9a7744620978f7ef3e3708339b8ed2ef01"},
{file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9cb1565a7ad52e096a6988e2ee0397f72fe056dadf75d17fa6b5aebaea05622"},
{file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:1b3ea7edd2d24538959c1c1abf97c744d879d4e541d38305f9bd7d9b10c9ec41"},
{file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:7c7837fe8037e96b6dd5cfcf47263c1620a9d332a87ec06a6ca4564e56bd0f36"},
{file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:3b90467ebc3d9fa5b0f9b6489dfb2c304a1db7b9946fa92aa76a831b9d587e99"},
{file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:cab9401de3ea52b4b4c6971db5fb5c999bd4260898af972bf23de1c6b5dd9d71"},
{file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d1f9282c5f2b5e241034a009779e7b2a1aa045f667ff521e7948ea9b56e0c5ff"},
{file = "aiohttp-3.8.4-cp36-cp36m-win32.whl", hash = "sha256:5e14f25765a578a0a634d5f0cd1e2c3f53964553a00347998dfdf96b8137f777"},
{file = "aiohttp-3.8.4-cp36-cp36m-win_amd64.whl", hash = "sha256:4c745b109057e7e5f1848c689ee4fb3a016c8d4d92da52b312f8a509f83aa05e"},
{file = "aiohttp-3.8.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:aede4df4eeb926c8fa70de46c340a1bc2c6079e1c40ccf7b0eae1313ffd33519"},
{file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ddaae3f3d32fc2cb4c53fab020b69a05c8ab1f02e0e59665c6f7a0d3a5be54f"},
{file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4eb3b82ca349cf6fadcdc7abcc8b3a50ab74a62e9113ab7a8ebc268aad35bb9"},
{file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bcb89336efa095ea21b30f9e686763f2be4478f1b0a616969551982c4ee4c3b"},
{file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c08e8ed6fa3d477e501ec9db169bfac8140e830aa372d77e4a43084d8dd91ab"},
{file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c6cd05ea06daca6ad6a4ca3ba7fe7dc5b5de063ff4daec6170ec0f9979f6c332"},
{file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7a00a9ed8d6e725b55ef98b1b35c88013245f35f68b1b12c5cd4100dddac333"},
{file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:de04b491d0e5007ee1b63a309956eaed959a49f5bb4e84b26c8f5d49de140fa9"},
{file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:40653609b3bf50611356e6b6554e3a331f6879fa7116f3959b20e3528783e699"},
{file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dbf3a08a06b3f433013c143ebd72c15cac33d2914b8ea4bea7ac2c23578815d6"},
{file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854f422ac44af92bfe172d8e73229c270dc09b96535e8a548f99c84f82dde241"},
{file = "aiohttp-3.8.4-cp37-cp37m-win32.whl", hash = "sha256:aeb29c84bb53a84b1a81c6c09d24cf33bb8432cc5c39979021cc0f98c1292a1a"},
{file = "aiohttp-3.8.4-cp37-cp37m-win_amd64.whl", hash = "sha256:db3fc6120bce9f446d13b1b834ea5b15341ca9ff3f335e4a951a6ead31105480"},
{file = "aiohttp-3.8.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fabb87dd8850ef0f7fe2b366d44b77d7e6fa2ea87861ab3844da99291e81e60f"},
{file = "aiohttp-3.8.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:91f6d540163f90bbaef9387e65f18f73ffd7c79f5225ac3d3f61df7b0d01ad15"},
{file = "aiohttp-3.8.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d265f09a75a79a788237d7f9054f929ced2e69eb0bb79de3798c468d8a90f945"},
{file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d89efa095ca7d442a6d0cbc755f9e08190ba40069b235c9886a8763b03785da"},
{file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dac314662f4e2aa5009977b652d9b8db7121b46c38f2073bfeed9f4049732cd"},
{file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe11310ae1e4cd560035598c3f29d86cef39a83d244c7466f95c27ae04850f10"},
{file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ddb2a2026c3f6a68c3998a6c47ab6795e4127315d2e35a09997da21865757f8"},
{file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e75b89ac3bd27d2d043b234aa7b734c38ba1b0e43f07787130a0ecac1e12228a"},
{file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6e601588f2b502c93c30cd5a45bfc665faaf37bbe835b7cfd461753068232074"},
{file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a5d794d1ae64e7753e405ba58e08fcfa73e3fad93ef9b7e31112ef3c9a0efb52"},
{file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a1f4689c9a1462f3df0a1f7e797791cd6b124ddbee2b570d34e7f38ade0e2c71"},
{file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3032dcb1c35bc330134a5b8a5d4f68c1a87252dfc6e1262c65a7e30e62298275"},
{file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8189c56eb0ddbb95bfadb8f60ea1b22fcfa659396ea36f6adcc521213cd7b44d"},
{file = "aiohttp-3.8.4-cp38-cp38-win32.whl", hash = "sha256:33587f26dcee66efb2fff3c177547bd0449ab7edf1b73a7f5dea1e38609a0c54"},
{file = "aiohttp-3.8.4-cp38-cp38-win_amd64.whl", hash = "sha256:e595432ac259af2d4630008bf638873d69346372d38255774c0e286951e8b79f"},
{file = "aiohttp-3.8.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5a7bdf9e57126dc345b683c3632e8ba317c31d2a41acd5800c10640387d193ed"},
{file = "aiohttp-3.8.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:22f6eab15b6db242499a16de87939a342f5a950ad0abaf1532038e2ce7d31567"},
{file = "aiohttp-3.8.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7235604476a76ef249bd64cb8274ed24ccf6995c4a8b51a237005ee7a57e8643"},
{file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea9eb976ffdd79d0e893869cfe179a8f60f152d42cb64622fca418cd9b18dc2a"},
{file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92c0cea74a2a81c4c76b62ea1cac163ecb20fb3ba3a75c909b9fa71b4ad493cf"},
{file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:493f5bc2f8307286b7799c6d899d388bbaa7dfa6c4caf4f97ef7521b9cb13719"},
{file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a63f03189a6fa7c900226e3ef5ba4d3bd047e18f445e69adbd65af433add5a2"},
{file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10c8cefcff98fd9168cdd86c4da8b84baaa90bf2da2269c6161984e6737bf23e"},
{file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bca5f24726e2919de94f047739d0a4fc01372801a3672708260546aa2601bf57"},
{file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:03baa76b730e4e15a45f81dfe29a8d910314143414e528737f8589ec60cf7391"},
{file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:8c29c77cc57e40f84acef9bfb904373a4e89a4e8b74e71aa8075c021ec9078c2"},
{file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:03543dcf98a6619254b409be2d22b51f21ec66272be4ebda7b04e6412e4b2e14"},
{file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17b79c2963db82086229012cff93ea55196ed31f6493bb1ccd2c62f1724324e4"},
{file = "aiohttp-3.8.4-cp39-cp39-win32.whl", hash = "sha256:34ce9f93a4a68d1272d26030655dd1b58ff727b3ed2a33d80ec433561b03d67a"},
{file = "aiohttp-3.8.4-cp39-cp39-win_amd64.whl", hash = "sha256:41a86a69bb63bb2fc3dc9ad5ea9f10f1c9c8e282b471931be0268ddd09430b04"},
{file = "aiohttp-3.8.4.tar.gz", hash = "sha256:bf2e1a9162c1e441bf805a1fd166e249d574ca04e03b34f97e2928769e91ab5c"},
{file = "aiohttp-3.8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a94159871304770da4dd371f4291b20cac04e8c94f11bdea1c3478e557fbe0d8"},
{file = "aiohttp-3.8.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:13bf85afc99ce6f9ee3567b04501f18f9f8dbbb2ea11ed1a2e079670403a7c84"},
{file = "aiohttp-3.8.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ce2ac5708501afc4847221a521f7e4b245abf5178cf5ddae9d5b3856ddb2f3a"},
{file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96943e5dcc37a6529d18766597c491798b7eb7a61d48878611298afc1fca946c"},
{file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ad5c3c4590bb3cc28b4382f031f3783f25ec223557124c68754a2231d989e2b"},
{file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c413c633d0512df4dc7fd2373ec06cc6a815b7b6d6c2f208ada7e9e93a5061d"},
{file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df72ac063b97837a80d80dec8d54c241af059cc9bb42c4de68bd5b61ceb37caa"},
{file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c48c5c0271149cfe467c0ff8eb941279fd6e3f65c9a388c984e0e6cf57538e14"},
{file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:368a42363c4d70ab52c2c6420a57f190ed3dfaca6a1b19afda8165ee16416a82"},
{file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7607ec3ce4993464368505888af5beb446845a014bc676d349efec0e05085905"},
{file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0d21c684808288a98914e5aaf2a7c6a3179d4df11d249799c32d1808e79503b5"},
{file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:312fcfbacc7880a8da0ae8b6abc6cc7d752e9caa0051a53d217a650b25e9a691"},
{file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad093e823df03bb3fd37e7dec9d4670c34f9e24aeace76808fc20a507cace825"},
{file = "aiohttp-3.8.5-cp310-cp310-win32.whl", hash = "sha256:33279701c04351a2914e1100b62b2a7fdb9a25995c4a104259f9a5ead7ed4802"},
{file = "aiohttp-3.8.5-cp310-cp310-win_amd64.whl", hash = "sha256:6e4a280e4b975a2e7745573e3fc9c9ba0d1194a3738ce1cbaa80626cc9b4f4df"},
{file = "aiohttp-3.8.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae871a964e1987a943d83d6709d20ec6103ca1eaf52f7e0d36ee1b5bebb8b9b9"},
{file = "aiohttp-3.8.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:461908b2578955045efde733719d62f2b649c404189a09a632d245b445c9c975"},
{file = "aiohttp-3.8.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72a860c215e26192379f57cae5ab12b168b75db8271f111019509a1196dfc780"},
{file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc14be025665dba6202b6a71cfcdb53210cc498e50068bc088076624471f8bb9"},
{file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8af740fc2711ad85f1a5c034a435782fbd5b5f8314c9a3ef071424a8158d7f6b"},
{file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:841cd8233cbd2111a0ef0a522ce016357c5e3aff8a8ce92bcfa14cef890d698f"},
{file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed1c46fb119f1b59304b5ec89f834f07124cd23ae5b74288e364477641060ff"},
{file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84f8ae3e09a34f35c18fa57f015cc394bd1389bce02503fb30c394d04ee6b938"},
{file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62360cb771707cb70a6fd114b9871d20d7dd2163a0feafe43fd115cfe4fe845e"},
{file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:23fb25a9f0a1ca1f24c0a371523546366bb642397c94ab45ad3aedf2941cec6a"},
{file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0ba0d15164eae3d878260d4c4df859bbdc6466e9e6689c344a13334f988bb53"},
{file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5d20003b635fc6ae3f96d7260281dfaf1894fc3aa24d1888a9b2628e97c241e5"},
{file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0175d745d9e85c40dcc51c8f88c74bfbaef9e7afeeeb9d03c37977270303064c"},
{file = "aiohttp-3.8.5-cp311-cp311-win32.whl", hash = "sha256:2e1b1e51b0774408f091d268648e3d57f7260c1682e7d3a63cb00d22d71bb945"},
{file = "aiohttp-3.8.5-cp311-cp311-win_amd64.whl", hash = "sha256:043d2299f6dfdc92f0ac5e995dfc56668e1587cea7f9aa9d8a78a1b6554e5755"},
{file = "aiohttp-3.8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cae533195e8122584ec87531d6df000ad07737eaa3c81209e85c928854d2195c"},
{file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f21e83f355643c345177a5d1d8079f9f28b5133bcd154193b799d380331d5d3"},
{file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a75ef35f2df54ad55dbf4b73fe1da96f370e51b10c91f08b19603c64004acc"},
{file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e2e9839e14dd5308ee773c97115f1e0a1cb1d75cbeeee9f33824fa5144c7634"},
{file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44e65da1de4403d0576473e2344828ef9c4c6244d65cf4b75549bb46d40b8dd"},
{file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d847e4cde6ecc19125ccbc9bfac4a7ab37c234dd88fbb3c5c524e8e14da543"},
{file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:c7a815258e5895d8900aec4454f38dca9aed71085f227537208057853f9d13f2"},
{file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:8b929b9bd7cd7c3939f8bcfffa92fae7480bd1aa425279d51a89327d600c704d"},
{file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5db3a5b833764280ed7618393832e0853e40f3d3e9aa128ac0ba0f8278d08649"},
{file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:a0215ce6041d501f3155dc219712bc41252d0ab76474615b9700d63d4d9292af"},
{file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:fd1ed388ea7fbed22c4968dd64bab0198de60750a25fe8c0c9d4bef5abe13824"},
{file = "aiohttp-3.8.5-cp36-cp36m-win32.whl", hash = "sha256:6e6783bcc45f397fdebc118d772103d751b54cddf5b60fbcc958382d7dd64f3e"},
{file = "aiohttp-3.8.5-cp36-cp36m-win_amd64.whl", hash = "sha256:b5411d82cddd212644cf9360879eb5080f0d5f7d809d03262c50dad02f01421a"},
{file = "aiohttp-3.8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:01d4c0c874aa4ddfb8098e85d10b5e875a70adc63db91f1ae65a4b04d3344cda"},
{file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5980a746d547a6ba173fd5ee85ce9077e72d118758db05d229044b469d9029a"},
{file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a482e6da906d5e6e653be079b29bc173a48e381600161c9932d89dfae5942ef"},
{file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80bd372b8d0715c66c974cf57fe363621a02f359f1ec81cba97366948c7fc873"},
{file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1161b345c0a444ebcf46bf0a740ba5dcf50612fd3d0528883fdc0eff578006a"},
{file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd56db019015b6acfaaf92e1ac40eb8434847d9bf88b4be4efe5bfd260aee692"},
{file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:153c2549f6c004d2754cc60603d4668899c9895b8a89397444a9c4efa282aaf4"},
{file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4a01951fabc4ce26ab791da5f3f24dca6d9a6f24121746eb19756416ff2d881b"},
{file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bfb9162dcf01f615462b995a516ba03e769de0789de1cadc0f916265c257e5d8"},
{file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7dde0009408969a43b04c16cbbe252c4f5ef4574ac226bc8815cd7342d2028b6"},
{file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4149d34c32f9638f38f544b3977a4c24052042affa895352d3636fa8bffd030a"},
{file = "aiohttp-3.8.5-cp37-cp37m-win32.whl", hash = "sha256:68c5a82c8779bdfc6367c967a4a1b2aa52cd3595388bf5961a62158ee8a59e22"},
{file = "aiohttp-3.8.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2cf57fb50be5f52bda004b8893e63b48530ed9f0d6c96c84620dc92fe3cd9b9d"},
{file = "aiohttp-3.8.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:eca4bf3734c541dc4f374ad6010a68ff6c6748f00451707f39857f429ca36ced"},
{file = "aiohttp-3.8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1274477e4c71ce8cfe6c1ec2f806d57c015ebf84d83373676036e256bc55d690"},
{file = "aiohttp-3.8.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:28c543e54710d6158fc6f439296c7865b29e0b616629767e685a7185fab4a6b9"},
{file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:910bec0c49637d213f5d9877105d26e0c4a4de2f8b1b29405ff37e9fc0ad52b8"},
{file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5443910d662db951b2e58eb70b0fbe6b6e2ae613477129a5805d0b66c54b6cb7"},
{file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e460be6978fc24e3df83193dc0cc4de46c9909ed92dd47d349a452ef49325b7"},
{file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb1558def481d84f03b45888473fc5a1f35747b5f334ef4e7a571bc0dfcb11f8"},
{file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34dd0c107799dcbbf7d48b53be761a013c0adf5571bf50c4ecad5643fe9cfcd0"},
{file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aa1990247f02a54185dc0dff92a6904521172a22664c863a03ff64c42f9b5410"},
{file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0e584a10f204a617d71d359fe383406305a4b595b333721fa50b867b4a0a1548"},
{file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a3cf433f127efa43fee6b90ea4c6edf6c4a17109d1d037d1a52abec84d8f2e42"},
{file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:c11f5b099adafb18e65c2c997d57108b5bbeaa9eeee64a84302c0978b1ec948b"},
{file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:84de26ddf621d7ac4c975dbea4c945860e08cccde492269db4e1538a6a6f3c35"},
{file = "aiohttp-3.8.5-cp38-cp38-win32.whl", hash = "sha256:ab88bafedc57dd0aab55fa728ea10c1911f7e4d8b43e1d838a1739f33712921c"},
{file = "aiohttp-3.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:5798a9aad1879f626589f3df0f8b79b3608a92e9beab10e5fda02c8a2c60db2e"},
{file = "aiohttp-3.8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a6ce61195c6a19c785df04e71a4537e29eaa2c50fe745b732aa937c0c77169f3"},
{file = "aiohttp-3.8.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:773dd01706d4db536335fcfae6ea2440a70ceb03dd3e7378f3e815b03c97ab51"},
{file = "aiohttp-3.8.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f83a552443a526ea38d064588613aca983d0ee0038801bc93c0c916428310c28"},
{file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f7372f7341fcc16f57b2caded43e81ddd18df53320b6f9f042acad41f8e049a"},
{file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea353162f249c8097ea63c2169dd1aa55de1e8fecbe63412a9bc50816e87b761"},
{file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d47ae48db0b2dcf70bc8a3bc72b3de86e2a590fc299fdbbb15af320d2659de"},
{file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d827176898a2b0b09694fbd1088c7a31836d1a505c243811c87ae53a3f6273c1"},
{file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3562b06567c06439d8b447037bb655ef69786c590b1de86c7ab81efe1c9c15d8"},
{file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4e874cbf8caf8959d2adf572a78bba17cb0e9d7e51bb83d86a3697b686a0ab4d"},
{file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6809a00deaf3810e38c628e9a33271892f815b853605a936e2e9e5129762356c"},
{file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:33776e945d89b29251b33a7e7d006ce86447b2cfd66db5e5ded4e5cd0340585c"},
{file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eaeed7abfb5d64c539e2db173f63631455f1196c37d9d8d873fc316470dfbacd"},
{file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e91d635961bec2d8f19dfeb41a539eb94bd073f075ca6dae6c8dc0ee89ad6f91"},
{file = "aiohttp-3.8.5-cp39-cp39-win32.whl", hash = "sha256:00ad4b6f185ec67f3e6562e8a1d2b69660be43070bd0ef6fcec5211154c7df67"},
{file = "aiohttp-3.8.5-cp39-cp39-win_amd64.whl", hash = "sha256:c0a9034379a37ae42dea7ac1e048352d96286626251862e448933c0f59cbd79c"},
{file = "aiohttp-3.8.5.tar.gz", hash = "sha256:b9552ec52cc147dbf1944ac7ac98af7602e51ea2dcd076ed194ca3c0d1c7d0bc"},
]
[package.dependencies]
@@ -263,14 +263,14 @@ files = [
[[package]]
name = "certifi"
version = "2023.5.7"
version = "2023.7.22"
description = "Python package for providing Mozilla's CA Bundle."
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
{file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"},
{file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"},
{file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"},
{file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"},
]
[[package]]
@@ -372,14 +372,14 @@ files = [
[[package]]
name = "click"
version = "8.1.5"
version = "8.1.6"
description = "Composable command line interface toolkit"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "click-8.1.5-py3-none-any.whl", hash = "sha256:e576aa487d679441d7d30abb87e1b43d24fc53bffb8758443b1a9e1cee504548"},
{file = "click-8.1.5.tar.gz", hash = "sha256:4be4b1af8d665c6d942909916d31a213a106800c47d0eeba73d34da3cbc11367"},
{file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"},
{file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"},
]
[package.dependencies]
@@ -836,20 +836,21 @@ test = ["coverage", "pytest", "pytest-cov"]
[[package]]
name = "markdown"
version = "3.3.7"
description = "Python implementation of Markdown."
version = "3.4.4"
description = "Python implementation of John Gruber's Markdown."
category = "dev"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.7"
files = [
{file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"},
{file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"},
{file = "Markdown-3.4.4-py3-none-any.whl", hash = "sha256:a4c1b65c0957b4bd9e7d86ddc7b3c9868fb9670660f6f99f6d1bca8954d5a941"},
{file = "Markdown-3.4.4.tar.gz", hash = "sha256:225c6123522495d4119a90b3a3ba31a1e87a70369e03f14799ea9c0d7183a3d6"},
]
[package.dependencies]
importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
[package.extras]
docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.0)", "mkdocs-nature (>=0.4)"]
testing = ["coverage", "pyyaml"]
[[package]]
@@ -986,14 +987,14 @@ files = [
[[package]]
name = "mkdocs"
version = "1.4.3"
version = "1.5.2"
description = "Project documentation with Markdown."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "mkdocs-1.4.3-py3-none-any.whl", hash = "sha256:6ee46d309bda331aac915cd24aab882c179a933bd9e77b80ce7d2eaaa3f689dd"},
{file = "mkdocs-1.4.3.tar.gz", hash = "sha256:5955093bbd4dd2e9403c5afaf57324ad8b04f16886512a3ee6ef828956481c57"},
{file = "mkdocs-1.5.2-py3-none-any.whl", hash = "sha256:60a62538519c2e96fe8426654a67ee177350451616118a41596ae7c876bb7eac"},
{file = "mkdocs-1.5.2.tar.gz", hash = "sha256:70d0da09c26cff288852471be03c23f0f521fc15cf16ac89c7a3bfb9ae8d24f9"},
]
[package.dependencies]
@@ -1002,9 +1003,12 @@ colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""}
ghp-import = ">=1.0"
importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""}
jinja2 = ">=2.11.1"
markdown = ">=3.2.1,<3.4"
markdown = ">=3.2.1"
markupsafe = ">=2.0.1"
mergedeep = ">=1.3.4"
packaging = ">=20.5"
pathspec = ">=0.11.1"
platformdirs = ">=2.2.0"
pyyaml = ">=5.1"
pyyaml-env-tag = ">=0.1"
typing-extensions = {version = ">=3.10", markers = "python_version < \"3.8\""}
@@ -1012,7 +1016,7 @@ watchdog = ">=2.0"
[package.extras]
i18n = ["babel (>=2.9.0)"]
min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"]
min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pathspec (==0.11.1)", "platformdirs (==2.2.0)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"]
[[package]]
name = "mkdocs-autorefs"
@@ -1046,21 +1050,21 @@ mkdocs = "*"
[[package]]
name = "mkdocs-material"
version = "9.1.18"
version = "9.1.21"
description = "Documentation that simply works"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "mkdocs_material-9.1.18-py3-none-any.whl", hash = "sha256:5bcf8fb79ac2f253c0ffe93fa181cba87718c6438f459dc4180ac7418cc9a450"},
{file = "mkdocs_material-9.1.18.tar.gz", hash = "sha256:981dd39979723d4cda7cfc77bbbe5e54922d5761a7af23fb8ba9edb52f114b13"},
{file = "mkdocs_material-9.1.21-py3-none-any.whl", hash = "sha256:58bb2f11ef240632e176d6f0f7d1cff06be1d11c696a5a1b553b808b4280ed47"},
{file = "mkdocs_material-9.1.21.tar.gz", hash = "sha256:71940cdfca84ab296b6362889c25395b1621273fb16c93deda257adb7ff44ec8"},
]
[package.dependencies]
colorama = ">=0.4"
jinja2 = ">=3.0"
markdown = ">=3.2"
mkdocs = ">=1.4.2"
mkdocs = ">=1.5.0"
mkdocs-material-extensions = ">=1.1"
pygments = ">=2.14"
pymdown-extensions = ">=9.9.1"
@@ -1389,34 +1393,34 @@ files = [
[[package]]
name = "pathspec"
version = "0.11.1"
version = "0.11.2"
description = "Utility library for gitignore style pattern matching of file paths."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"},
{file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"},
{file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"},
{file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"},
]
[[package]]
name = "platformdirs"
version = "3.9.1"
version = "3.10.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "platformdirs-3.9.1-py3-none-any.whl", hash = "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"},
{file = "platformdirs-3.9.1.tar.gz", hash = "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421"},
{file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"},
{file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"},
]
[package.dependencies]
typing-extensions = {version = ">=4.6.3", markers = "python_version < \"3.8\""}
typing-extensions = {version = ">=4.7.1", markers = "python_version < \"3.8\""}
[package.extras]
docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"]
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"]
[[package]]
name = "pluggy"
@@ -1620,52 +1624,52 @@ files = [
[[package]]
name = "pyyaml"
version = "6.0"
version = "6.0.1"
description = "YAML parser and emitter for Python"
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
{file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
{file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
{file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"},
{file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"},
{file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"},
{file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"},
{file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"},
{file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"},
{file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"},
{file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
{file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
{file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
{file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
{file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
{file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
{file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
{file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
{file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
{file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
{file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
{file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
{file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"},
{file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
{file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
{file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
{file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
{file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
{file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
{file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"},
{file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"},
{file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"},
{file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"},
{file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"},
{file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"},
{file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"},
{file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"},
{file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"},
{file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
{file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
{file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
{file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
{file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
{file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
{file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
]
[[package]]
@@ -1823,14 +1827,14 @@ idna2008 = ["idna"]
[[package]]
name = "rich"
version = "13.4.2"
version = "13.5.2"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
category = "main"
optional = false
python-versions = ">=3.7.0"
files = [
{file = "rich-13.4.2-py3-none-any.whl", hash = "sha256:8f87bc7ee54675732fa66a05ebfe489e27264caeeff3728c945d25971b6485ec"},
{file = "rich-13.4.2.tar.gz", hash = "sha256:d653d6bccede5844304c605d5aac802c7cf9621efd700b46c7ec2b51ea914898"},
{file = "rich-13.5.2-py3-none-any.whl", hash = "sha256:146a90b3b6b47cac4a73c12866a499e9817426423f57c5a66949c086191a8808"},
{file = "rich-13.5.2.tar.gz", hash = "sha256:fb9d6c0a0f643c99eed3875b5377a184132ba9be4d61516a55273d3554d75a39"},
]
[package.dependencies]
@@ -1912,21 +1916,21 @@ pytest = ">=5.1.0,<8.0.0"
[[package]]
name = "textual-dev"
version = "1.0.1"
version = "1.1.0"
description = "Development tools for working with Textual"
category = "dev"
optional = false
python-versions = ">=3.7,<4.0"
files = [
{file = "textual_dev-1.0.1-py3-none-any.whl", hash = "sha256:419fc426c120f04f89ab0cb1aa88f7873dd7cdb9c21618e709175c8eaff6b566"},
{file = "textual_dev-1.0.1.tar.gz", hash = "sha256:9f4c40655cbb56af7ee92805ef14fa24ae98ff8b0ae778c59de7222f1caa7281"},
{file = "textual_dev-1.1.0-py3-none-any.whl", hash = "sha256:c57320636098e31fa5d5c29fc3bc60829bb420da3c76bfed24db6eacf178dbc6"},
{file = "textual_dev-1.1.0.tar.gz", hash = "sha256:e2f8ce4e1c18a16b80282f3257cd2feb49a7ede289a78908c9063ce071bb77ce"},
]
[package.dependencies]
aiohttp = ">=3.8.1"
click = ">=8.1.2"
msgpack = ">=1.0.3"
textual = ">=0.29.0"
textual = ">=0.32.0"
typing-extensions = ">=4.4.0,<5.0.0"
[[package]]
@@ -2124,14 +2128,14 @@ test = ["coverage", "pytest", "pytest-cov"]
[[package]]
name = "urllib3"
version = "2.0.3"
version = "2.0.4"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "urllib3-2.0.3-py3-none-any.whl", hash = "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1"},
{file = "urllib3-2.0.3.tar.gz", hash = "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825"},
{file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"},
{file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"},
]
[package.extras]
@@ -2142,25 +2146,25 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "virtualenv"
version = "20.24.0"
version = "20.24.2"
description = "Virtual Python Environment builder"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "virtualenv-20.24.0-py3-none-any.whl", hash = "sha256:18d1b37fc75cc2670625702d76849a91ebd383768b4e91382a8d51be3246049e"},
{file = "virtualenv-20.24.0.tar.gz", hash = "sha256:e2a7cef9da880d693b933db7654367754f14e20650dc60e8ee7385571f8593a3"},
{file = "virtualenv-20.24.2-py3-none-any.whl", hash = "sha256:43a3052be36080548bdee0b42919c88072037d50d56c28bd3f853cbe92b953ff"},
{file = "virtualenv-20.24.2.tar.gz", hash = "sha256:fd8a78f46f6b99a67b7ec5cf73f92357891a7b3a40fd97637c27f854aae3b9e0"},
]
[package.dependencies]
distlib = ">=0.3.6,<1"
filelock = ">=3.12,<4"
distlib = ">=0.3.7,<1"
filelock = ">=3.12.2,<4"
importlib-metadata = {version = ">=6.6", markers = "python_version < \"3.8\""}
platformdirs = ">=3.5.1,<4"
platformdirs = ">=3.9.1,<4"
[package.extras]
docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.3.1)", "pytest-env (>=0.8.1)", "pytest-freezer (>=0.4.6)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=67.8)", "time-machine (>=2.9)"]
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
[[package]]
name = "watchdog"
@@ -2310,4 +2314,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
[metadata]
lock-version = "2.0"
python-versions = "^3.7"
content-hash = "652f05ae0b8df5f39af720c34bfb222e514e1b17afbf0eb10738946651c293e1"
content-hash = "5ac8aef69083d16bc38af16f22cc94ad14b8b70b5cff61e0c7d462c1d1a8a42c"

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "textual"
version = "0.31.0"
version = "0.32.0"
homepage = "https://github.com/Textualize/textual"
description = "Modern Text User Interface framework"
authors = ["Will McGugan <will@textualize.io>"]
@@ -57,9 +57,9 @@ time-machine = "^2.6.0"
mkdocs-rss-plugin = "^1.5.0"
httpx = "^0.23.1"
types-setuptools = "^67.2.0.1"
textual-dev = ">=1.0.0"
textual-dev = "^1.1.0"
pytest-asyncio = "*"
pytest-textual-snapshot = ">=0.1.0"
pytest-textual-snapshot = "0.2.0"
[tool.black]
includes = "src"

View File

@@ -0,0 +1,16 @@
---
title: "No widget called TextLog"
alt_titles:
- "ImportError with TextLog"
- "TextLog does not exist"
---
The `TextLog` widget was renamed to `RichLog` in Textual 0.32.0.
You will need to replace all references to `TextLog` in your code, with `RichLog`.
Most IDEs will have a search and replace function which will help you do this.
Here's how you should import RichLog:
```python
from textual.widgets import RichLog
```

View File

@@ -30,7 +30,7 @@ Then the app can be run, passing in various arguments; for example:
# Running with default arguments.
Greetings().run()
# Running with a keyword arguyment.
# Running with a keyword argument.
Greetings(to_greet="davep").run()
# Running with both positional arguments.

View File

@@ -0,0 +1,26 @@
---
title: "How do I fix WorkerDeclarationError?"
alt_titles:
- "Thread=True on Worker"
- "Problem with threaded workers"
---
Textual version 0.31.0 requires that you set `thread=True` on the `@work` decorator if you want to run a threaded worker.
If you want a threaded worker, you would declare it in the following way:
```python
@work(thread=True)
def run_in_background():
...
```
If you *don't* want a threaded worker, you should make your work function `async`:
```python
@work()
async def run_in_background():
...
```
This change was made because it was too easy to accidentally create a threaded worker, which may produce unexpected results.

View File

@@ -177,6 +177,21 @@ class LRUCache(Generic[CacheKey, CacheValue]):
def __contains__(self, key: CacheKey) -> bool:
return key in self._cache
def discard(self, key: CacheKey) -> None:
"""Discard item in cache from key.
Args:
key: Cache key.
"""
link = self._cache.get(key)
if link is None:
return
# Remove link from list
link[0][1] = link[1] # type: ignore[index]
link[1][0] = link[0] # type: ignore[index]
# Remove link from cache
del self._cache[key]
class FIFOCache(Generic[CacheKey, CacheValue]):
"""A simple cache that discards the first added key when full (First In First Out).

View File

@@ -0,0 +1,23 @@
from __future__ import annotations
import re
# Pre-compile the regular expression and store it in a global constant
LINE_AND_ENDING_PATTERN = re.compile(r"(.*?)(\r\n|\r|\n|$)", re.S)
def line_split(input_string: str) -> list[tuple[str, str]]:
r"""
Splits an arbitrary string into a list of tuples, where each tuple contains a line of text and its line ending.
Args:
input_string (str): The string to split.
Returns:
list[tuple[str, str]]: A list of tuples, where each tuple contains a line of text and its line ending.
Example:
split_string_to_lines_and_endings("Hello\r\nWorld\nThis is a test\rLast line")
>>> [('Hello', '\r\n'), ('World', '\n'), ('This is a test', '\r'), ('Last line', '')]
"""
return LINE_AND_ENDING_PATTERN.findall(input_string)[:-1] if input_string else []

View File

@@ -131,7 +131,7 @@ Screen>Container {
overflow: hidden;
}
TextLog {
RichLog {
background: $surface;
color: $text;
height: 50vh;
@@ -144,11 +144,11 @@ TextLog {
}
TextLog:focus {
RichLog:focus {
offset: 0 0 !important;
}
TextLog.-hidden {
RichLog.-hidden {
offset-y: 100%;
}

View File

@@ -22,9 +22,9 @@ from textual.widgets import (
Footer,
Header,
Input,
RichLog,
Static,
Switch,
TextLog,
)
from_markup = Text.from_markup
@@ -284,21 +284,21 @@ class DemoApp(App[None]):
("ctrl+b", "toggle_sidebar", "Sidebar"),
("ctrl+t", "app.toggle_dark", "Toggle Dark mode"),
("ctrl+s", "app.screenshot()", "Screenshot"),
("f1", "app.toggle_class('TextLog', '-hidden')", "Notes"),
("f1", "app.toggle_class('RichLog', '-hidden')", "Notes"),
Binding("ctrl+c,ctrl+q", "app.quit", "Quit", show=True),
]
show_sidebar = reactive(False)
def add_note(self, renderable: RenderableType) -> None:
self.query_one(TextLog).write(renderable)
self.query_one(RichLog).write(renderable)
def compose(self) -> ComposeResult:
example_css = Path(self.css_path[0]).read_text()
yield Container(
Sidebar(classes="-hidden"),
Header(show_clock=False),
TextLog(classes="-hidden", wrap=False, highlight=True, markup=True),
RichLog(classes="-hidden", wrap=False, highlight=True, markup=True),
Body(
QuickAccess(
LocationLink("TOP", ".location-top"),

View File

@@ -7,8 +7,9 @@ from __future__ import annotations
from rich.console import RenderableType
from ._animator import EasingFunction
from ._types import CallbackType
from .containers import ScrollableContainer
from .geometry import Size
from .geometry import Region, Size
class ScrollView(ScrollableContainer):
@@ -142,3 +143,16 @@ class ScrollView(ScrollableContainer):
force=force,
on_complete=on_complete,
)
def refresh_lines(self, y_start: int, line_count: int = 1) -> None:
"""Refresh one or more lines.
Args:
y_start: First line to refresh.
line_count: Total number of lines to refresh.
"""
width = self.size.width
scroll_x, scroll_y = self.scroll_offset
refresh_region = Region(scroll_x, y_start - scroll_y, width, line_count)
self.refresh(refresh_region)

View File

@@ -69,6 +69,8 @@ class Strip:
"_style_cache",
"_filter_cache",
"_render_cache",
"_line_length_cache",
"_crop_extend_cache",
"_link_ids",
]
@@ -81,6 +83,14 @@ class Strip:
self._crop_cache: FIFOCache[tuple[int, int], Strip] = FIFOCache(16)
self._style_cache: FIFOCache[Style, Strip] = FIFOCache(16)
self._filter_cache: FIFOCache[tuple[LineFilter, Color], Strip] = FIFOCache(4)
self._line_length_cache: FIFOCache[
tuple[int, Style | None],
Strip,
] = FIFOCache(4)
self._crop_extend_cache: FIFOCache[
tuple[int, int, Style | None],
Strip,
] = FIFOCache(4)
self._render_cache: str | None = None
self._link_ids: set[str] | None = None
@@ -222,6 +232,11 @@ class Strip:
A new strip with the supplied cell length.
"""
cache_key = (cell_length, style)
cached_strip = self._line_length_cache.get(cache_key)
if cached_strip is not None:
return cached_strip
new_line: list[Segment]
line = self._segments
current_cell_length = self.cell_length
@@ -233,6 +248,7 @@ class Strip:
new_line = line + [
_Segment(" " * (cell_length - current_cell_length), style)
]
strip = Strip(new_line, cell_length)
elif current_cell_length > cell_length:
# Cell length is shorter so we need to truncate.
@@ -249,11 +265,14 @@ class Strip:
text = set_cell_size(text, cell_length - line_length)
append(_Segment(text, segment_style))
break
strip = Strip(new_line, cell_length)
else:
# Strip is already the required cell length, so return self.
return self
strip = self
return Strip(new_line, cell_length)
self._line_length_cache[cache_key] = strip
return strip
def simplify(self) -> Strip:
"""Simplify the segments (join segments with same style)
@@ -312,6 +331,25 @@ class Strip:
]
return Strip(segments, self._cell_length)
def crop_extend(self, start: int, end: int, style: Style | None) -> Strip:
"""Crop between two points, extending the length if required.
Args:
start: Start offset of crop.
end: End offset of crop.
style: Style of additional padding.
Returns:
New cropped Strip.
"""
cache_key = (start, end, style)
cached_result = self._crop_extend_cache.get(cache_key)
if cached_result is not None:
return cached_result
strip = self.extend_cell_length(end).crop(start, end)
self._crop_extend_cache[cache_key] = strip
return strip
def crop(self, start: int, end: int | None = None) -> Strip:
"""Crop a strip between two cell positions.

View File

@@ -1146,6 +1146,26 @@ class Widget(DOMNode):
- (self.container_size.height - self.scrollbar_size_horizontal),
)
@property
def is_vertical_scroll_end(self) -> bool:
"""Is the vertical scroll position at the maximum?"""
return self.scroll_offset.y == self.max_scroll_y
@property
def is_horizontal_scroll_end(self) -> bool:
"""Is the horizontal scroll position at the maximum?"""
return self.scroll_offset.x == self.max_scroll_x
@property
def is_vertical_scrollbar_grabbed(self) -> bool:
"""Is the user dragging the vertical scrollbar?"""
return bool(self._vertical_scrollbar and self._vertical_scrollbar.grabbed)
@property
def is_horizontal_scrollbar_grabbed(self) -> bool:
"""Is the user dragging the vertical scrollbar?"""
return bool(self._horizontal_scrollbar and self._horizontal_scrollbar.grabbed)
@property
def scrollbar_corner(self) -> ScrollBarCorner:
"""The scrollbar corner.

View File

@@ -22,6 +22,7 @@ if typing.TYPE_CHECKING:
from ._list_item import ListItem
from ._list_view import ListView
from ._loading_indicator import LoadingIndicator
from ._log import Log
from ._markdown import Markdown, MarkdownViewer
from ._option_list import OptionList
from ._placeholder import Placeholder
@@ -29,6 +30,7 @@ if typing.TYPE_CHECKING:
from ._progress_bar import ProgressBar
from ._radio_button import RadioButton
from ._radio_set import RadioSet
from ._rich_log import RichLog
from ._select import Select
from ._selection_list import SelectionList
from ._sparkline import Sparkline
@@ -36,7 +38,6 @@ if typing.TYPE_CHECKING:
from ._switch import Switch
from ._tabbed_content import TabbedContent, TabPane
from ._tabs import Tab, Tabs
from ._text_log import TextLog
from ._tooltip import Tooltip
from ._tree import Tree
from ._welcome import Welcome
@@ -55,6 +56,7 @@ __all__ = [
"ListItem",
"ListView",
"LoadingIndicator",
"Log",
"Markdown",
"MarkdownViewer",
"OptionList",
@@ -72,7 +74,7 @@ __all__ = [
"TabbedContent",
"TabPane",
"Tabs",
"TextLog",
"RichLog",
"Tooltip",
"Tree",
"Welcome",

View File

@@ -11,6 +11,7 @@ from ._label import Label as Label
from ._list_item import ListItem as ListItem
from ._list_view import ListView as ListView
from ._loading_indicator import LoadingIndicator as LoadingIndicator
from ._log import Log as Log
from ._markdown import Markdown as Markdown
from ._markdown import MarkdownViewer as MarkdownViewer
from ._option_list import OptionList as OptionList
@@ -19,6 +20,7 @@ from ._pretty import Pretty as Pretty
from ._progress_bar import ProgressBar as ProgressBar
from ._radio_button import RadioButton as RadioButton
from ._radio_set import RadioSet as RadioSet
from ._rich_log import RichLog as RichLog
from ._select import Select as Select
from ._selection_list import SelectionList as SelectionList
from ._sparkline import Sparkline as Sparkline
@@ -28,7 +30,6 @@ from ._tabbed_content import TabbedContent as TabbedContent
from ._tabbed_content import TabPane as TabPane
from ._tabs import Tab as Tab
from ._tabs import Tabs as Tabs
from ._text_log import TextLog as TextLog
from ._tooltip import Tooltip as Tooltip
from ._tree import Tree as Tree
from ._welcome import Welcome as Welcome

309
src/textual/widgets/_log.py Normal file
View File

@@ -0,0 +1,309 @@
from __future__ import annotations
import re
from typing import TYPE_CHECKING, Iterable, Optional, Sequence
from rich.cells import cell_len
from rich.highlighter import ReprHighlighter
from rich.segment import Segment
from rich.style import Style
from rich.text import Text
from .. import work
from .._cache import LRUCache
from .._line_split import line_split
from ..geometry import Size
from ..reactive import var
from ..scroll_view import ScrollView
from ..strip import Strip
if TYPE_CHECKING:
from typing_extensions import Self
_sub_escape = re.compile("[\u0000-\u0014]").sub
class Log(ScrollView, can_focus=True):
"""A widget to log text."""
DEFAULT_CSS = """
Log {
background: $surface;
color: $text;
overflow: scroll;
}
"""
max_lines: var[int | None] = var[Optional[int]](None)
"""Maximum number of lines to show"""
auto_scroll: var[bool] = var(True)
"""Automatically scroll to new lines."""
def __init__(
self,
highlight: bool = False,
max_lines: int | None = None,
auto_scroll: bool = True,
name: str | None = None,
id: str | None = None,
classes: str | None = None,
disabled: bool = False,
) -> None:
"""Create a Log widget.
Args:
highlight: Enable highlighting.
max_lines: Maximum number of lines to display.
auto_scroll: Scroll to end on new lines.
name: The name of the text log.
id: The ID of the text log in the DOM.
classes: The CSS classes of the text log.
disabled: Whether the text log is disabled or not.
"""
self.highlight = highlight
"""Enable highlighting."""
self.max_lines = max_lines
self.auto_scroll = auto_scroll
self._lines: list[str] = []
self._width = 0
self._updates = 0
self._render_line_cache: LRUCache[int, Strip] = LRUCache(1024)
self.highlighter = ReprHighlighter()
"""The Rich Highlighter object to use, if `highlight=True`"""
super().__init__(name=name, id=id, classes=classes, disabled=disabled)
@property
def lines(self) -> Sequence[str]:
"""The raw lines in the TextLog.
Note that this attribute is read only.
Changing the lines will not update the Log's contents.
"""
return self._lines
def notify_style_update(self) -> None:
"""Called by Textual when styles update."""
self._render_line_cache.clear()
def _update_maximum_width(self, updates: int, size: int) -> None:
"""Update the virtual size width.
Args:
updates: A counter of updates.
size: Maximum size of new lines.
"""
if updates == self._updates:
self._width = max(size, self._width)
self.virtual_size = Size(self._width, self.line_count)
@property
def line_count(self) -> int:
"""Number of lines of content."""
if self._lines:
return len(self._lines) - (self._lines[-1] == "")
return 0
@classmethod
def _process_line(cls, line: str) -> str:
"""Process a line before it is rendered to remove control codes.
Args:
line: A string.
Returns:
New string with no control codes.
"""
return _sub_escape("<EFBFBD>", line.expandtabs())
@work(thread=True)
def _update_size(self, updates: int, lines: list[str]) -> None:
"""A thread worker to update the width in the background.
Args:
updates: The update index at the time of invocation.
lines: Lines that were added.
"""
if lines:
_process_line = self._process_line
max_length = max(cell_len(_process_line(line)) for line in lines)
self.app.call_from_thread(self._update_maximum_width, updates, max_length)
def _prune_max_lines(self) -> None:
"""Prune lines if there are more than the maximum."""
if self.max_lines is None:
return
remove_lines = len(self._lines) - self.max_lines
if remove_lines > 0:
_cache = self._render_line_cache
# We've removed some lines, which means the y values in the cache are out of sync
# Calculated a new dict of cache values
updated_cache = {
y - remove_lines: _cache[y] for y in _cache.keys() if y > remove_lines
}
# Clear the cache
_cache.clear()
# Update the cache with previously calculated values
for y, line in updated_cache.items():
_cache[y] = line
del self._lines[:remove_lines]
def write(
self,
data: str,
scroll_end: bool | None = None,
) -> Self:
"""Write to the log.
Args:
data: Data to write.
scroll_end: Scroll to the end after writing, or `None` to use `self.auto_scroll`.
Returns:
The `Log` instance.
"""
if data:
if not self._lines:
self._lines.append("")
for line, ending in line_split(data):
self._lines[-1] += line
self._width = max(
self._width, cell_len(self._process_line(self._lines[-1]))
)
self.refresh_lines(len(self._lines) - 1)
if ending:
self._lines.append("")
self.virtual_size = Size(self._width, self.line_count)
if self.max_lines is not None and len(self._lines) > self.max_lines:
self._prune_max_lines()
auto_scroll = self.auto_scroll if scroll_end is None else scroll_end
if auto_scroll and not self.is_vertical_scrollbar_grabbed:
self.scroll_end(animate=False)
return self
def write_line(self, line: str) -> Self:
"""Write content on a new line.
Args:
line: String to write to the log.
Returns:
The `Log` instance.
"""
self.write_lines([line])
return self
def write_lines(
self,
lines: Iterable[str],
scroll_end: bool | None = None,
) -> Self:
"""Write an iterable of lines.
Args:
lines: An iterable of strings to write.
scroll_end: Scroll to the end after writing, or `None` to use `self.auto_scroll`.
Returns:
The `Log` instance.
"""
auto_scroll = self.auto_scroll if scroll_end is None else scroll_end
new_lines = []
for line in lines:
new_lines.extend(line.splitlines())
start_line = len(self._lines)
self._lines.extend(new_lines)
if self.max_lines is not None and len(self._lines) > self.max_lines:
self._prune_max_lines()
self.virtual_size = Size(self._width, len(self._lines))
self._update_size(self._updates, new_lines)
self.refresh_lines(start_line, len(new_lines))
if auto_scroll and not self.is_vertical_scrollbar_grabbed:
self.scroll_end(animate=False)
else:
self.refresh()
return self
def clear(self) -> Self:
"""Clear the Log.
Returns:
The `Log` instance.
"""
self._lines.clear()
self._width = 0
self._render_line_cache.clear()
self._updates += 1
self.virtual_size = Size(0, 0)
return self
def render_line(self, y: int) -> Strip:
"""Render a line of content.
Args:
y: Y Coordinate of line.
Returns:
A rendered line.
"""
scroll_x, scroll_y = self.scroll_offset
strip = self._render_line(scroll_y + y, scroll_x, self.size.width)
return strip
def _render_line(self, y: int, scroll_x: int, width: int) -> Strip:
"""Render a line in to a cropped strip.
Args:
y: Y offset of line.
scroll_x: Current horizontal scroll.
width: Width of the widget.
Returns:
A Strip suitable for rendering.
"""
rich_style = self.rich_style
if y >= len(self._lines):
return Strip.blank(width, rich_style)
line = self._render_line_strip(y, rich_style)
assert line._cell_length is not None
line = line.crop_extend(scroll_x, scroll_x + width, rich_style)
return line
def _render_line_strip(self, y: int, rich_style: Style) -> Strip:
"""Render a line in to a Strip.
Args:
y: Y offset of line.
rich_style: Rich style of line.
Returns:
An uncropped Strip.
"""
if y in self._render_line_cache:
return self._render_line_cache[y]
_line = self._process_line(self._lines[y])
if self.highlight:
line_text = self.highlighter(Text(_line, style=rich_style, no_wrap=True))
line = Strip(line_text.render(self.app.console), cell_len(_line))
else:
line = Strip([Segment(_line, rich_style)], cell_len(_line))
self._render_line_cache[y] = line
return line
def refresh_lines(self, y_start: int, line_count: int = 1) -> None:
"""Refresh one or more lines.
Args:
y_start: First line to refresh.
line_count: Total number of lines to refresh.
"""
for y in range(y_start, y_start + line_count):
self._render_line_cache.discard(y)
super().refresh_lines(y_start, line_count=line_count)

View File

@@ -22,11 +22,11 @@ if TYPE_CHECKING:
from typing_extensions import Self
class TextLog(ScrollView, can_focus=True):
class RichLog(ScrollView, can_focus=True):
"""A widget for logging text."""
DEFAULT_CSS = """
TextLog{
RichLog{
background: $surface;
color: $text;
overflow-y: scroll;
@@ -54,7 +54,7 @@ class TextLog(ScrollView, can_focus=True):
classes: str | None = None,
disabled: bool = False,
) -> None:
"""Create a TextLog widget.
"""Create a RichLog widget.
Args:
max_lines: Maximum number of lines in the log or `None` for no maximum.
@@ -137,7 +137,7 @@ class TextLog(ScrollView, can_focus=True):
scroll_end: Enable automatic scroll to end, or `None` to use `self.auto_scroll`.
Returns:
The `TextLog` instance.
The `RichLog` instance.
"""
auto_scroll = self.auto_scroll if scroll_end is None else scroll_end
@@ -192,7 +192,7 @@ class TextLog(ScrollView, can_focus=True):
"""Clear the text log.
Returns:
The `TextLog` instance.
The `RichLog` instance.
"""
self.lines.clear()
self._line_cache.clear()
@@ -215,7 +215,7 @@ class TextLog(ScrollView, can_focus=True):
crop: Region within visible area to.
Returns:
A list of list of segments
A list of list of segments.
"""
lines = self._styles_cache.render_widget(self, crop)
return lines
@@ -228,11 +228,7 @@ class TextLog(ScrollView, can_focus=True):
if key in self._line_cache:
return self._line_cache[key]
line = (
self.lines[y]
.adjust_cell_length(max(self.max_width, width), self.rich_style)
.crop(scroll_x, scroll_x + width)
)
line = self.lines[y].crop_extend(scroll_x, scroll_x + width, self.rich_style)
self._line_cache[key] = line
return line

File diff suppressed because one or more lines are too long

View File

@@ -1,10 +1,10 @@
from textual.app import App, ComposeResult
from textual import events
from textual.widgets import TextLog
from textual.widgets import RichLog
class PrintLogger(TextLog):
"""A TextLog which captures printed text."""
class PrintLogger(RichLog):
"""A RichLog which captures printed text."""
def on_print(self, event: events.Print) -> None:
self.write(event.text)
@@ -15,10 +15,10 @@ class CaptureApp(App):
yield PrintLogger()
def on_mount(self) -> None:
self.query_one(TextLog).write("TextLog")
self.query_one(TextLog).begin_capture_print()
self.query_one(RichLog).write("RichLog")
self.query_one(RichLog).begin_capture_print()
print("This will be captured!")
self.query_one(TextLog).end_capture_print()
self.query_one(RichLog).end_capture_print()
print("This will *not* be captured")

View File

@@ -3,7 +3,7 @@ from rich.text import Text
from textual.app import App, ComposeResult
from textual.containers import VerticalScroll
from textual.widget import Widget
from textual.widgets import TextLog
from textual.widgets import RichLog
class MyWidget(Widget):
@@ -22,7 +22,7 @@ class ScrollViewApp(App):
align: center middle;
}
TextLog {
RichLog {
width:13;
height:10;
}
@@ -41,11 +41,11 @@ class ScrollViewApp(App):
"""
def compose(self) -> ComposeResult:
yield TextLog()
yield RichLog()
yield VerticalScroll(MyWidget())
def on_ready(self) -> None:
self.query_one(TextLog).write("\n".join(f"{n} 0123456789" for n in range(20)))
self.query_one(RichLog).write("\n".join(f"{n} 0123456789" for n in range(20)))
self.query_one(VerticalScroll).scroll_end(animate=False)

View File

@@ -0,0 +1,22 @@
from textual.app import App, ComposeResult
from textual.widgets import Log
from textual.containers import Horizontal
class LogApp(App):
def compose(self) -> ComposeResult:
yield Log()
def on_ready(self) -> None:
log = self.query_one(Log)
log.write("Hello,")
log.write(" World")
log.write("!\nWhat's up?")
log.write("")
log.write("\n")
log.write("FOO")
if __name__ == "__main__":
app = LogApp()
app.run()

View File

@@ -0,0 +1,38 @@
from textual.app import App, ComposeResult
from textual.widgets import Log
from textual.containers import Horizontal
TEXT = (
"""I must not fear.
Fear is the mind-killer.
Fear is the little-death that brings total obliteration.
I will face my fear.
I will permit it to pass over me and through me.
And when it has gone past, I will turn the inner eye to see its path.
Where the fear has gone there will be nothing. Only I will remain.
"""
* 20
)
class LogApp(App):
def compose(self) -> ComposeResult:
with Horizontal():
yield Log(id="log1", auto_scroll=False)
yield Log(id="log2", auto_scroll=True)
yield Log(id="log3")
yield Log(id="log4", max_lines=6)
def on_ready(self) -> None:
self.query_one("#log1", Log).write_line(TEXT)
self.query_one("#log2", Log).write_line(TEXT)
self.query_one("#log3", Log).write_line(TEXT)
self.query_one("#log4", Log).write_line(TEXT)
self.query_one("#log3", Log).clear()
self.query_one("#log3", Log).write_line("Hello, World")
if __name__ == "__main__":
app = LogApp()
app.run()

View File

@@ -1,20 +1,20 @@
from textual.app import App
from textual.widgets import TextLog
from textual.widgets import RichLog
class TextLogLines(App):
class RichLogLines(App):
count = 0
def compose(self):
yield TextLog(max_lines=3)
yield RichLog(max_lines=3)
async def on_key(self):
self.count += 1
log_widget = self.query_one(TextLog)
log_widget = self.query_one(RichLog)
log_widget.write(f"Key press #{self.count}")
app = TextLogLines()
app = RichLogLines()
if __name__ == "__main__":
app.run()

View File

@@ -1,11 +1,11 @@
from textual.app import App, ComposeResult
from textual.widgets import TextLog
from textual.widgets import RichLog
from textual.containers import Horizontal
class TextLogScrollApp(App):
class RichLogScrollApp(App):
CSS = """
TextLog{
RichLog{
width: 1fr;
height: 10;
}
@@ -14,22 +14,22 @@ class TextLogScrollApp(App):
def compose(self) -> ComposeResult:
with Horizontal():
# Don't scroll on write
yield TextLog(id="textlog1", auto_scroll=False)
yield RichLog(id="richlog1", auto_scroll=False)
# Scroll on write
yield TextLog(id="textlog2", auto_scroll=True)
yield RichLog(id="richlog2", auto_scroll=True)
# Scroll on write, but disabled on write()
yield TextLog(id="textlog3", auto_scroll=True)
yield RichLog(id="richlog3", auto_scroll=True)
def on_ready(self) -> None:
lines = [f"Line {n}" for n in range(20)]
for line in lines:
self.query_one("#textlog1", TextLog).write(line)
self.query_one("#richlog1", RichLog).write(line)
for line in lines:
self.query_one("#textlog2", TextLog).write(line)
self.query_one("#richlog2", RichLog).write(line)
for line in lines:
self.query_one("#textlog3", TextLog).write(line, scroll_end=False)
self.query_one("#richlog3", RichLog).write(line, scroll_end=False)
if __name__ == "__main__":
app = TextLogScrollApp()
app = RichLogScrollApp()
app.run()

View File

@@ -1,18 +1,18 @@
from textual.app import App, ComposeResult
from textual.widgets import TextLog
from textual.widgets import RichLog
class TextLogApp(App):
class RichLogApp(App):
def compose(self) -> ComposeResult:
yield TextLog()
yield RichLog()
def on_mount(self) -> None:
tl = self.query_one(TextLog)
tl = self.query_one(RichLog)
tl.write("Hello")
tl.write("")
tl.write("World")
if __name__ == "__main__":
app = TextLogApp()
app = RichLogApp()
app.run()

View File

@@ -162,8 +162,16 @@ def test_list_view(snap_compare):
)
def test_textlog_max_lines(snap_compare):
assert snap_compare("snapshot_apps/textlog_max_lines.py", press=[*"abcde"])
def test_richlog_max_lines(snap_compare):
assert snap_compare("snapshot_apps/richlog_max_lines.py", press=[*"abcde"])
def test_log_write_lines(snap_compare):
assert snap_compare("snapshot_apps/log_write_lines.py")
def test_log_write(snap_compare):
assert snap_compare("snapshot_apps/log_write.py")
def test_fr_units(snap_compare):
@@ -421,8 +429,8 @@ def test_table_markup(snap_compare):
assert snap_compare(SNAPSHOT_APPS_DIR / "table_markup.py")
def test_textlog_scroll(snap_compare):
assert snap_compare(SNAPSHOT_APPS_DIR / "textlog_scroll.py")
def test_richlog_scroll(snap_compare):
assert snap_compare(SNAPSHOT_APPS_DIR / "richlog_scroll.py")
def test_tabs_invalidate(snap_compare):

View File

@@ -240,3 +240,22 @@ def test_fifo_cache_hits():
assert cache.misses == 2
assert str(cache) == "<FIFOCache maxsize=4 hits=3 misses=2>"
def test_discard():
cache = LRUCache(maxsize=3)
cache.set("key1", "value1")
cache.set("key2", "value2")
cache.set("key3", "value3")
assert len(cache) == 3
assert cache.get("key1") == "value1"
cache.discard("key1")
assert len(cache) == 2
assert cache.get("key1") is None
cache.discard("key4") # key that does not exist
assert len(cache) == 2 # size should not change

View File

@@ -3,7 +3,7 @@ from threading import Thread
import pytest
from textual.app import App, ComposeResult
from textual.widgets import TextLog
from textual.widgets import RichLog
def test_call_from_thread_app_not_running():
@@ -27,7 +27,7 @@ def test_call_from_thread():
def run(self) -> None:
def write_stuff(text: str) -> None:
"""Write stuff to a widget."""
self.app.query_one(TextLog).write(text)
self.app.query_one(RichLog).write(text)
self.app.call_from_thread(write_stuff, "Hello")
# Exit the app with a code we can assert
@@ -37,7 +37,7 @@ def test_call_from_thread():
"""Trivial app with a single widget."""
def compose(self) -> ComposeResult:
yield TextLog()
yield RichLog()
def on_ready(self) -> None:
"""Launch a thread which will modify the app."""

View File

@@ -18,9 +18,9 @@ from textual.widgets import (
OptionList,
RadioButton,
RadioSet,
RichLog,
Select,
Switch,
TextLog,
Tree,
)
@@ -37,7 +37,7 @@ class DisableApp(App[None]):
Input(),
ListView(),
Switch(),
TextLog(),
RichLog(),
Tree("Test"),
Markdown(),
MarkdownViewer(),

26
tests/test_line_split.py Normal file
View File

@@ -0,0 +1,26 @@
from textual._line_split import line_split
def test_split_string_to_lines_and_endings():
# Test with different line endings
assert line_split("Hello\r\nWorld\n") == [("Hello", "\r\n"), ("World", "\n")]
assert line_split("Hello\rWorld\r\n") == [("Hello", "\r"), ("World", "\r\n")]
assert line_split("Hello\nWorld\r") == [("Hello", "\n"), ("World", "\r")]
# Test with no line endings
assert line_split("Hello World") == [("Hello World", "")]
# Test with empty string
assert line_split("") == []
# Test with multiple lines having same line endings
assert line_split("Hello\nWorld\nHow\nAre\nYou\n") == [
("Hello", "\n"),
("World", "\n"),
("How", "\n"),
("Are", "\n"),
("You", "\n"),
]
# Test with a single character
assert line_split("a") == [("a", "")]

8
tests/test_log.py Normal file
View File

@@ -0,0 +1,8 @@
from textual.widgets import Log
def test_process_line():
log = Log()
assert log._process_line("foo") == "foo"
assert log._process_line("foo\t") == "foo "
assert log._process_line("\0foo") == "<EFBFBD>foo"

View File

@@ -108,14 +108,14 @@ URL](https://example.com)\
assert isinstance(paragraph, MD.MarkdownParagraph)
assert paragraph._text.plain == "My site has this URL"
expected_spans = [
Span(0, 8, Style()),
Span(8, 11, Style(meta={"@click": "link('https://example.com')"})),
Span(11, 12, Style(meta={"@click": "link('https://example.com')"})),
Span(12, 16, Style(meta={"@click": "link('https://example.com')"})),
Span(16, 17, Style(meta={"@click": "link('https://example.com')"})),
Span(17, 20, Style(meta={"@click": "link('https://example.com')"})),
]
assert paragraph._text.spans == expected_spans
assert paragraph._text.spans == expected_spans
async def test_load_non_existing_file() -> None:

View File

@@ -11,7 +11,7 @@ from textual.app import (
UnknownModeError,
)
from textual.screen import ModalScreen, Screen
from textual.widgets import Footer, Header, Label, TextLog
from textual.widgets import Footer, Header, Label, RichLog
FRUITS = cycle("apple mango strawberry banana peach pear melon watermelon".split())
@@ -61,7 +61,7 @@ class FruitModal(ModalScreen[str], ScreenBindingsMixin):
class FruitsScreen(ScreenBindingsMixin):
def compose(self) -> ComposeResult:
yield TextLog()
yield RichLog()
@pytest.fixture

View File

@@ -1,11 +1,11 @@
from rich.text import Text
from textual.widgets import TextLog
from textual.widgets import RichLog
def test_make_renderable_expand_tabs():
# Regression test for https://github.com/Textualize/textual/issues/3007
text_log = TextLog()
text_log = RichLog()
renderable = text_log._make_renderable("\tfoo")
assert isinstance(renderable, Text)
assert renderable.plain == " foo"