From 0723c8b23e7a5011565698a8391dae6c3c31da83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 18 Jan 2023 18:25:00 +0000 Subject: [PATCH 01/93] Fix #1607 for generic/integer properties. --- src/textual/css/_style_properties.py | 17 ++++++++++++++--- src/textual/css/styles.py | 8 ++++---- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/textual/css/_style_properties.py b/src/textual/css/_style_properties.py index bfc9f90cb..81ab274a4 100644 --- a/src/textual/css/_style_properties.py +++ b/src/textual/css/_style_properties.py @@ -60,9 +60,20 @@ PropertySetType = TypeVar("PropertySetType") class GenericProperty(Generic[PropertyGetType, PropertySetType]): - def __init__(self, default: PropertyGetType, layout: bool = False) -> None: + """Generic property to be inherited by other style properties. + + Args: + default: The default value (or a factory thereof) of the property. + layout: Whether to refresh the node layout on value change. + children: Whether to refresh the node children on value change. + """ + + def __init__( + self, default: PropertyGetType, layout: bool = False, children: bool = False + ) -> None: self.default = default self.layout = layout + self.children = children def validate_value(self, value: object) -> PropertyGetType: """Validate the setter value. @@ -88,11 +99,11 @@ class GenericProperty(Generic[PropertyGetType, PropertySetType]): _rich_traceback_omit = True if value is None: obj.clear_rule(self.name) - obj.refresh(layout=self.layout) + obj.refresh(layout=self.layout, children=self.children) return new_value = self.validate_value(value) if obj.set_rule(self.name, new_value): - obj.refresh(layout=self.layout) + obj.refresh(layout=self.layout, children=self.children) class IntegerProperty(GenericProperty[int, int]): diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index c04b24a48..a9903da99 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -280,10 +280,10 @@ class StylesBase(ABC): grid_rows = ScalarListProperty() grid_columns = ScalarListProperty() - grid_size_columns = IntegerProperty(default=1, layout=True) - grid_size_rows = IntegerProperty(default=0, layout=True) - grid_gutter_horizontal = IntegerProperty(default=0, layout=True) - grid_gutter_vertical = IntegerProperty(default=0, layout=True) + grid_size_columns = IntegerProperty(default=1, layout=True, children=True) + grid_size_rows = IntegerProperty(default=0, layout=True, children=True) + grid_gutter_horizontal = IntegerProperty(default=0, layout=True, children=True) + grid_gutter_vertical = IntegerProperty(default=0, layout=True, children=True) row_span = IntegerProperty(default=1, layout=True) column_span = IntegerProperty(default=1, layout=True) From e6c23f5d0f8276d3a043c951016908c21cde4068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 18 Jan 2023 18:33:57 +0000 Subject: [PATCH 02/93] Fix #1607 for ScalarListProperty. --- src/textual/css/_style_properties.py | 15 ++++++++++++--- src/textual/css/styles.py | 4 ++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/textual/css/_style_properties.py b/src/textual/css/_style_properties.py index 81ab274a4..3805ffb18 100644 --- a/src/textual/css/_style_properties.py +++ b/src/textual/css/_style_properties.py @@ -60,7 +60,7 @@ PropertySetType = TypeVar("PropertySetType") class GenericProperty(Generic[PropertyGetType, PropertySetType]): - """Generic property to be inherited by other style properties. + """Descriptor that abstracts away common machinery for other style descriptors. Args: default: The default value (or a factory thereof) of the property. @@ -213,6 +213,15 @@ class ScalarProperty: class ScalarListProperty: + """Descriptor for lists of scalars. + + Args: + children: Whether to refresh the node children on value change. + """ + + def __init__(self, children: bool = False) -> None: + self.children = children + def __set_name__(self, owner: Styles, name: str) -> None: self.name = name @@ -226,7 +235,7 @@ class ScalarListProperty: ) -> None: if value is None: obj.clear_rule(self.name) - obj.refresh(layout=True) + obj.refresh(layout=True, children=self.children) return parse_values: Iterable[str | float] if isinstance(value, str): @@ -245,7 +254,7 @@ class ScalarListProperty: else parse_value ) if obj.set_rule(self.name, tuple(scalars)): - obj.refresh(layout=True) + obj.refresh(layout=True, children=self.children) class BoxProperty: diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index a9903da99..feb39adcc 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -277,8 +277,8 @@ class StylesBase(ABC): content_align_vertical = StringEnumProperty(VALID_ALIGN_VERTICAL, "top") content_align = AlignProperty() - grid_rows = ScalarListProperty() - grid_columns = ScalarListProperty() + grid_rows = ScalarListProperty(children=True) + grid_columns = ScalarListProperty(children=True) grid_size_columns = IntegerProperty(default=1, layout=True, children=True) grid_size_rows = IntegerProperty(default=0, layout=True, children=True) From 6c5022a78ed9770062ec36810514d3b79e1ef768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 18 Jan 2023 18:48:21 +0000 Subject: [PATCH 03/93] Fix #1607 for some StringEnumProperty. This commit fixes the issue for scrollbar_gutter, align, align_horizontal, and align_vertical, but it doesn't solve the issue for overflow_x/y, and I still don't know why... [skip ci] --- src/textual/css/_style_properties.py | 19 ++++++++++++++++--- src/textual/css/styles.py | 12 +++++++++--- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/textual/css/_style_properties.py b/src/textual/css/_style_properties.py index 3805ffb18..2d05b3b50 100644 --- a/src/textual/css/_style_properties.py +++ b/src/textual/css/_style_properties.py @@ -699,12 +699,25 @@ class OffsetProperty: class StringEnumProperty: """Descriptor for getting and setting string properties and ensuring that the set value belongs in the set of valid values. + + Args: + valid_values: The set of valid values that the descriptor can take. + default: The default value (or a factory thereof) of the property. + layout: Whether to refresh the node layout on value change. + children: Whether to refresh the node children on value change. """ - def __init__(self, valid_values: set[str], default: str, layout=False) -> None: + def __init__( + self, + valid_values: set[str], + default: str, + layout: bool = False, + children: bool = False, + ) -> None: self._valid_values = valid_values self._default = default self._layout = layout + self._children = children def __set_name__(self, owner: StylesBase, name: str) -> None: self.name = name @@ -734,7 +747,7 @@ class StringEnumProperty: _rich_traceback_omit = True if value is None: if obj.clear_rule(self.name): - obj.refresh(layout=self._layout) + obj.refresh(layout=self._layout, children=self._children) else: if value not in self._valid_values: raise StyleValueError( @@ -746,7 +759,7 @@ class StringEnumProperty: ), ) if obj.set_rule(self.name, value): - obj.refresh(layout=self._layout) + obj.refresh(layout=self._layout, children=self._children) class NameProperty: diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index feb39adcc..6f94b1877 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -264,13 +264,19 @@ class StylesBase(ABC): scrollbar_background_hover = ColorProperty("#444444") scrollbar_background_active = ColorProperty("black") - scrollbar_gutter = StringEnumProperty(VALID_SCROLLBAR_GUTTER, "auto") + scrollbar_gutter = StringEnumProperty( + VALID_SCROLLBAR_GUTTER, "auto", layout=True, children=True + ) scrollbar_size_vertical = IntegerProperty(default=1, layout=True) scrollbar_size_horizontal = IntegerProperty(default=1, layout=True) - align_horizontal = StringEnumProperty(VALID_ALIGN_HORIZONTAL, "left") - align_vertical = StringEnumProperty(VALID_ALIGN_VERTICAL, "top") + align_horizontal = StringEnumProperty( + VALID_ALIGN_HORIZONTAL, "left", layout=True, children=True + ) + align_vertical = StringEnumProperty( + VALID_ALIGN_VERTICAL, "top", layout=True, children=True + ) align = AlignProperty() content_align_horizontal = StringEnumProperty(VALID_ALIGN_HORIZONTAL, "left") From 19780db0bb50b86371cbf8ec09ed5e2ab50132f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 19 Jan 2023 15:29:09 +0000 Subject: [PATCH 04/93] Setting overflow refreshes layout. [skip-ci] --- src/textual/css/styles.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index 6f94b1877..83e1bcd30 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -246,8 +246,12 @@ class StylesBase(ABC): dock = DockProperty() - overflow_x = StringEnumProperty(VALID_OVERFLOW, "hidden") - overflow_y = StringEnumProperty(VALID_OVERFLOW, "hidden") + overflow_x = StringEnumProperty( + VALID_OVERFLOW, "hidden", layout=True, children=True + ) + overflow_y = StringEnumProperty( + VALID_OVERFLOW, "hidden", layout=True, children=True + ) layer = NameProperty() layers = NameListProperty() From ef4bd43c9bf0014ca626ba4951f986ff2a9ebc8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Fri, 20 Jan 2023 11:33:30 +0000 Subject: [PATCH 05/93] Add regression test. --- tests/css/test_programmatic_style_changes.py | 64 ++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 tests/css/test_programmatic_style_changes.py diff --git a/tests/css/test_programmatic_style_changes.py b/tests/css/test_programmatic_style_changes.py new file mode 100644 index 000000000..3abc15ba1 --- /dev/null +++ b/tests/css/test_programmatic_style_changes.py @@ -0,0 +1,64 @@ +import pytest + +from textual.app import App +from textual.containers import Grid +from textual.screen import Screen +from textual.widgets import Label + + +updates = 0 + + +class _Label(Label): + """Label widget that keeps track of its own updates.""" + + def refresh(self, *args, **kwargs): + global updates + updates += 1 + return super().refresh(*args, **kwargs) + + +@pytest.mark.parametrize( + "style, value", + [ + ("grid_size_rows", 2), + ("grid_size_columns", 2), + ("grid_gutter_vertical", 2), + ("grid_gutter_horizontal", 1), + ("grid_rows", "1fr 3fr"), + ("grid_columns", "1fr 3fr"), + ("overflow_x", "scroll"), + ("overflow_y", "scroll"), + ("scrollbar_gutter", "stable"), + ("align_horizontal", "right"), + ("align_vertical", "bottom"), + ("align", ("right", "bottom")), + ], +) +def test_programmatic_style_change_refreshes_children_layout(style: str, value): + """Regression test for #1607 https://github.com/Textualize/textual/issues/1607 + + Some programmatic style changes to a widget were not updating the layout of the + children widgets, which seemed to be happening when the style change did not affect + the size of the widget but did affect the layout of the children. + """ + + global updates + + app = App() + app.DEFAULT_CSS = "Grid { grid-size: 1 1; }" + app._set_active() + app.push_screen(Screen()) + + grid = Grid( + _Label("one"), + _Label("two"), + _Label("three"), + _Label("four"), + ) + app.screen._add_children(grid) + + update_count = updates + setattr(grid.styles, style, value) + print(updates, update_count) + assert updates > update_count From c7f1771bbf91a36e6b8aa84c3dbe29df09912191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Fri, 20 Jan 2023 11:35:14 +0000 Subject: [PATCH 06/93] Update changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3ffc3dbe..8794ceda5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Fixed `textual diagnose` crash on older supported Python versions. https://github.com/Textualize/textual/issues/1622 +- Fixed programmatic style changes not refreshing children layouts when parent widget did not change size https://github.com/Textualize/textual/issues/1607 ### Changed From 3305659132ccdbcd1e4fb894025b391156bb07e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 23 Jan 2023 17:41:19 +0000 Subject: [PATCH 07/93] Fix silly mistake when resolving merge conflict. --- src/textual/css/_style_properties.py | 4 ++-- src/textual/css/styles.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/textual/css/_style_properties.py b/src/textual/css/_style_properties.py index e91282af3..10c8f9f8a 100644 --- a/src/textual/css/_style_properties.py +++ b/src/textual/css/_style_properties.py @@ -220,9 +220,9 @@ class ScalarListProperty: percent_unit: The dimension to which percentage scalars will be relative to. """ - def __init__(self, children: bool = False, percent_unit: Unit) -> None: - self.children = children + def __init__(self, percent_unit: Unit, children: bool = False) -> None: self.percent_unit = percent_unit + self.children = children def __set_name__(self, owner: Styles, name: str) -> None: self.name = name diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index 29cacf7cb..4a3691bcc 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -287,8 +287,8 @@ class StylesBase(ABC): content_align_vertical = StringEnumProperty(VALID_ALIGN_VERTICAL, "top") content_align = AlignProperty() - grid_rows = ScalarListProperty(children=True, percent_unit=Unit.HEIGHT) - grid_columns = ScalarListProperty(children=True, percent_unit=Unit.WIDTH) + grid_rows = ScalarListProperty(percent_unit=Unit.HEIGHT, children=True) + grid_columns = ScalarListProperty(percent_unit=Unit.WIDTH, children=True) grid_size_columns = IntegerProperty(default=1, layout=True, children=True) grid_size_rows = IntegerProperty(default=0, layout=True, children=True) From 3f2ecd1ad863e0fa6f243fb8365726289595d26a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 23 Jan 2023 18:11:33 +0000 Subject: [PATCH 08/93] Update changelog. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e4f19ef8..2cac4c421 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Fixed stuck screen https://github.com/Textualize/textual/issues/1632 +- Fixed programmatic style changes not refreshing children layouts when parent widget did not change size https://github.com/Textualize/textual/issues/1607 - Fixed relative units in `grid-rows` and `grid-columns` being computed with respect to the wrong dimension https://github.com/Textualize/textual/issues/1406 ## [0.10.1] - 2023-01-20 @@ -21,7 +22,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Fixed `textual diagnose` crash on older supported Python versions. https://github.com/Textualize/textual/issues/1622 -- Fixed programmatic style changes not refreshing children layouts when parent widget did not change size https://github.com/Textualize/textual/issues/1607 ### Changed From fcba6ecaad1f123d67d6db75c917c82c9369f855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 25 Jan 2023 11:41:05 +0000 Subject: [PATCH 09/93] Omit default values for attributes. We copy the mkdocstrings template for attributes so that we can exclude the (default) value for attributes (and class variables) from the documentation as that is generally useless. --- .../_templates/python/material/attribute.html | 67 +++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 68 insertions(+) create mode 100644 docs/_templates/python/material/attribute.html diff --git a/docs/_templates/python/material/attribute.html b/docs/_templates/python/material/attribute.html new file mode 100644 index 000000000..b4f6bcaf8 --- /dev/null +++ b/docs/_templates/python/material/attribute.html @@ -0,0 +1,67 @@ +{{ log.debug("Rendering " + attribute.path) }} + +
+{% with html_id = attribute.path %} + + {% if root %} + {% set show_full_path = config.show_root_full_path %} + {% set root_members = True %} + {% elif root_members %} + {% set show_full_path = config.show_root_members_full_path or config.show_object_full_path %} + {% set root_members = False %} + {% else %} + {% set show_full_path = config.show_object_full_path %} + {% endif %} + + {% if not root or config.show_root_heading %} + + {% filter heading(heading_level, + role="data" if attribute.parent.kind.value == "module" else "attr", + id=html_id, + class="doc doc-heading", + toc_label=attribute.name) %} + + {% if config.separate_signature %} + {% if show_full_path %}{{ attribute.path }}{% else %}{{ attribute.name }}{% endif %} + {% else %} + {% filter highlight(language="python", inline=True) %} + {% if show_full_path %}{{ attribute.path }}{% else %}{{ attribute.name }}{% endif %} + {% if attribute.annotation %}: {{ attribute.annotation }}{% endif %} + {% endfilter %} + {% endif %} + + {% with labels = attribute.labels %} + {% include "labels.html" with context %} + {% endwith %} + + {% endfilter %} + + {% if config.separate_signature %} + {% filter highlight(language="python", inline=False) %} + {% filter format_code(config.line_length) %} + {% if show_full_path %}{{ attribute.path }}{% else %}{{ attribute.name }}{% endif %} + {% if attribute.annotation %}: {{ attribute.annotation|safe }}{% endif %} + {% endfilter %} + {% endfilter %} + {% endif %} + + {% else %} + {% if config.show_root_toc_entry %} + {% filter heading(heading_level, + role="data" if attribute.parent.kind.value == "module" else "attr", + id=html_id, + toc_label=attribute.path if config.show_root_full_path else attribute.name, + hidden=True) %} + {% endfilter %} + {% endif %} + {% set heading_level = heading_level - 1 %} + {% endif %} + +
+ {% with docstring_sections = attribute.docstring.parsed %} + {% include "docstring.html" with context %} + {% endwith %} +
+ +{% endwith %} +
diff --git a/mkdocs.yml b/mkdocs.yml index 5c29f469c..021c95400 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -251,6 +251,7 @@ plugins: - search: - autorefs: - mkdocstrings: + custom_templates: docs/_templates default_handler: python handlers: python: From 9cb32d2b660551de7d3916b81b34a02bb7f219ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 25 Jan 2023 11:41:33 +0000 Subject: [PATCH 10/93] Fix alphabetical order. --- mkdocs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 021c95400..081bdff37 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -128,17 +128,17 @@ nav: - "widgets/button.md" - "widgets/checkbox.md" - "widgets/data_table.md" - - "widgets/text_log.md" - "widgets/directory_tree.md" - "widgets/footer.md" - "widgets/header.md" - "widgets/index.md" - "widgets/input.md" - "widgets/label.md" - - "widgets/list_view.md" - "widgets/list_item.md" + - "widgets/list_view.md" - "widgets/placeholder.md" - "widgets/static.md" + - "widgets/text_log.md" - "widgets/tree.md" - API: - "api/index.md" From f3999bbe474cd1835cc97cff69d4ce80531c9390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 25 Jan 2023 13:03:10 +0000 Subject: [PATCH 11/93] Add docs to bindings and component classes. --- src/textual/widgets/_checkbox.py | 17 +++++++++++++---- src/textual/widgets/_data_table.py | 1 + 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/textual/widgets/_checkbox.py b/src/textual/widgets/_checkbox.py index 55a656206..f1b90fe61 100644 --- a/src/textual/widgets/_checkbox.py +++ b/src/textual/widgets/_checkbox.py @@ -13,8 +13,12 @@ from ..scrollbar import ScrollBarRender class Checkbox(Widget, can_focus=True): - """A checkbox widget. Represents a boolean value. Can be toggled by clicking - on it or by pressing the enter key or space bar while it has focus. + """A checkbox widget that represents a boolean value. + + Can be toggled by clicking on it or through its [bindings][textual.widgets.Checkbox.BINDINGS]. + + The checkbox widget also contains [component classes][textual.widgets.Checkbox.COMPONENT_CLASSES] + that enable more customization. """ DEFAULT_CSS = """ @@ -49,9 +53,14 @@ class Checkbox(Widget, can_focus=True): } """ - BINDINGS = [ - Binding("enter,space", "toggle", "toggle", show=False), + BINDINGS: list[Binding] = [ + Binding("enter,space", "toggle", "Toggle the checkbox status.", show=False), ] + """ + | Key(s) | Action | Description | + | :- | :- | :- | + | enter,space | [toggle][textual.widgets.Checkbox.toggle] | Toggle the checkbox status. | + """ COMPONENT_CLASSES: ClassVar[set[str]] = { "checkbox--switch", diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index f5b33dec2..0eb1f750f 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -144,6 +144,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): "datatable--highlight", "datatable--cursor", } + """This is now documented.""" BINDINGS = [ Binding("enter", "select_cursor", "Select", show=False), From bf02c7b5d3de131022090e524e3545a9dfe0d27e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 25 Jan 2023 14:30:06 +0000 Subject: [PATCH 12/93] Don't inherit component classes explicitly. --- src/textual/widgets/_directory_tree.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index 2ca41badd..68bc48306 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -32,13 +32,6 @@ class DirectoryTree(Tree[DirEntry]): """ COMPONENT_CLASSES: ClassVar[set[str]] = { - "tree--label", - "tree--guides", - "tree--guides-hover", - "tree--guides-selected", - "tree--cursor", - "tree--highlight", - "tree--highlight-line", "directory-tree--folder", "directory-tree--file", "directory-tree--extension", From 66b158d7e426012f57cdfc1f8d50718c7f05f64f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 25 Jan 2023 14:31:49 +0000 Subject: [PATCH 13/93] Remove dead import. --- src/textual/widgets/_footer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/textual/widgets/_footer.py b/src/textual/widgets/_footer.py index 24e6826c0..dd8af2e9b 100644 --- a/src/textual/widgets/_footer.py +++ b/src/textual/widgets/_footer.py @@ -7,7 +7,6 @@ from rich.console import RenderableType from rich.text import Text from .. import events -from ..keys import _get_key_display from ..reactive import Reactive, watch from ..widget import Widget From fd0d1feb798c3ec47fc77571ef878cb6f5dd761c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 25 Jan 2023 14:47:53 +0000 Subject: [PATCH 14/93] Add helper tool to generate widget docs. References: #1662, #1543 --- tools/widget_documentation.py | 81 +++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 tools/widget_documentation.py diff --git a/tools/widget_documentation.py b/tools/widget_documentation.py new file mode 100644 index 000000000..d58c8c019 --- /dev/null +++ b/tools/widget_documentation.py @@ -0,0 +1,81 @@ +""" +Helper script to help document all widgets. + +This goes through the widgets listed in textual.widgets and prints the scaffolding +for the tables that are used to document the classvars BINDINGS and COMPONENT_CLASSES. +""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +import textual.widgets + +if TYPE_CHECKING: + from textual.binding import Binding + + +def print_bindings(widget: str, bindings: list[Binding]) -> None: + """Print a table summarising the bindings. + + The table contains columns for the key(s) that trigger the binding, + the method that it calls (and tries to link it to the widget itself), + and the description of the binding. + """ + if bindings: + print("BINDINGS") + print('"""') + print("| Key(s) | Action | Description |") + print("| :- | :- | :- |") + + for binding in bindings: + print( + f"| {binding.key} | [{binding.action}][textual.widgets.{widget}.{binding.action}] | {binding.description} |" + ) + + if bindings: + print('"""') + + +def print_component_classes(classes: set[str]) -> None: + """Print a table to document these component classes. + + The table contains two columns, one with the component class name and another + for the description of what the component class is for. + The second column is always empty. + """ + if classes: + print("COMPONENT_CLASSES") + print('"""') + print("| Class | Description |") + print("| :- | :- |") + + for cls in sorted(classes): + print(f"| `{cls}` | XXX |") + + if classes: + print('"""') + + +def main() -> None: + """Main entrypoint. + + Iterates over all widgets and prints docs tables. + """ + + widgets: list[str] = textual.widgets.__all__ + + for widget in widgets: + w = getattr(textual.widgets, widget) + bindings: list[Binding] = w.__dict__.get("BINDINGS", []) + component_classes: set[str] = getattr(w, "COMPONENT_CLASSES", set()) + + if bindings or component_classes: + print(widget) + print() + print_bindings(widget, bindings) + print_component_classes(component_classes) + print() + + +if __name__ == "__main__": + main() From 2ea501ea9b1cc8949dde738eda04428a78a03f96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 25 Jan 2023 14:49:37 +0000 Subject: [PATCH 15/93] Document bindings and component classes. --- src/textual/widgets/_checkbox.py | 34 ++++++++------ src/textual/widgets/_data_table.py | 64 +++++++++++++++++--------- src/textual/widgets/_directory_tree.py | 10 ++++ src/textual/widgets/_footer.py | 23 ++++++--- src/textual/widgets/_input.py | 44 ++++++++++++------ src/textual/widgets/_list_view.py | 12 ++++- src/textual/widgets/_tree.py | 42 ++++++++++++----- 7 files changed, 159 insertions(+), 70 deletions(-) diff --git a/src/textual/widgets/_checkbox.py b/src/textual/widgets/_checkbox.py index f1b90fe61..a2b5f1eea 100644 --- a/src/textual/widgets/_checkbox.py +++ b/src/textual/widgets/_checkbox.py @@ -4,7 +4,7 @@ from typing import ClassVar from rich.console import RenderableType -from ..binding import Binding +from ..binding import Binding, BindingType from ..geometry import Size from ..message import Message from ..reactive import reactive @@ -21,8 +21,25 @@ class Checkbox(Widget, can_focus=True): that enable more customization. """ - DEFAULT_CSS = """ + BINDINGS: ClassVar[list[BindingType]] = [ + Binding("enter,space", "toggle", "Toggle", show=False), + ] + """ + | Key(s) | Action | Description | + | :- | :- | :- | + | enter,space | [toggle][textual.widgets.Checkbox.toggle] | Toggle | + """ + COMPONENT_CLASSES: ClassVar[set[str]] = { + "checkbox--switch", + } + """ + | Class | Description | + | :- | :- | + | `checkbox--switch` | Targets the switch of the checkbox. | + """ + + DEFAULT_CSS = """ Checkbox { border: tall transparent; background: $panel; @@ -53,19 +70,6 @@ class Checkbox(Widget, can_focus=True): } """ - BINDINGS: list[Binding] = [ - Binding("enter,space", "toggle", "Toggle the checkbox status.", show=False), - ] - """ - | Key(s) | Action | Description | - | :- | :- | :- | - | enter,space | [toggle][textual.widgets.Checkbox.toggle] | Toggle the checkbox status. | - """ - - COMPONENT_CLASSES: ClassVar[set[str]] = { - "checkbox--switch", - } - def __init__( self, value: bool = False, diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index 0eb1f750f..2e9e19eb4 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -17,7 +17,7 @@ from .._cache import LRUCache from .._segment_tools import line_crop from .._types import SegmentLines from .._typing import Literal -from ..binding import Binding +from ..binding import Binding, BindingType from ..coordinate import Coordinate from ..geometry import Region, Size, Spacing, clamp from ..message import Message @@ -84,6 +84,48 @@ class Row: class DataTable(ScrollView, Generic[CellType], can_focus=True): + """A tabular widget that contains data.""" + + BINDINGS: ClassVar[list[BindingType]] = [ + Binding("enter", "select_cursor", "Select", show=False), + Binding("up", "cursor_up", "Cursor Up", show=False), + Binding("down", "cursor_down", "Cursor Down", show=False), + Binding("right", "cursor_right", "Cursor Right", show=False), + Binding("left", "cursor_left", "Cursor Left", show=False), + ] + """ + | Key(s) | Action | Description | + | :- | :- | :- | + | enter | [select_cursor][textual.widgets.DataTable.select_cursor] | Select cells under the cursor. | + | up | [cursor_up][textual.widgets.DataTable.cursor_up] | Move the cursor up. | + | down | [cursor_down][textual.widgets.DataTable.cursor_down] | Move the cursor down. | + | right | [cursor_right][textual.widgets.DataTable.cursor_right] | Move the cursor right. | + | left | [cursor_left][textual.widgets.DataTable.cursor_left] | Move the cursor left. | + """ + + COMPONENT_CLASSES: ClassVar[set[str]] = { + "datatable--header", + "datatable--cursor-fixed", + "datatable--highlight-fixed", + "datatable--fixed", + "datatable--odd-row", + "datatable--even-row", + "datatable--highlight", + "datatable--cursor", + } + """ + | Class | Description | + | :- | :- | + | `datatable--cursor` | Target the cursor. | + | `datatable--cursor-fixed` | Target fixed columns or header under the cursor. | + | `datatable--even-row` | Target even rows (row indices start at 0). | + | `datatable--fixed` | Target fixed columns or header. | + | `datatable--header` | Target the header of the data table. | + | `datatable--highlight` | Target the highlighted cell(s). | + | `datatable--highlight-fixed` | Target highlighted and fixed columns or header. | + | `datatable--odd-row` | Target odd rows (row indices start at 0). | + """ + DEFAULT_CSS = """ App.-dark DataTable { background:; @@ -134,26 +176,6 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): } """ - COMPONENT_CLASSES: ClassVar[set[str]] = { - "datatable--header", - "datatable--cursor-fixed", - "datatable--highlight-fixed", - "datatable--fixed", - "datatable--odd-row", - "datatable--even-row", - "datatable--highlight", - "datatable--cursor", - } - """This is now documented.""" - - BINDINGS = [ - Binding("enter", "select_cursor", "Select", show=False), - Binding("up", "cursor_up", "Cursor Up", show=False), - Binding("down", "cursor_down", "Cursor Down", show=False), - Binding("right", "cursor_right", "Cursor Right", show=False), - Binding("left", "cursor_left", "Cursor Left", show=False), - ] - show_header = Reactive(True) fixed_rows = Reactive(0) fixed_columns = Reactive(0) diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index 68bc48306..9d6cbdc83 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -37,6 +37,16 @@ class DirectoryTree(Tree[DirEntry]): "directory-tree--extension", "directory-tree--hidden", } + """ + | Class | Description | + | :- | :- | + | `directory-tree--extension` | Target the extension of a file name. | + | `directory-tree--file` | Target files in the directory structure. | + | `directory-tree--folder` | Target folders in the directory structure. | + | `directory-tree--hidden` | Target hidden items in the directory structure. | + + See also the [component classes for `Tree`][textual.widgets.Tree.COMPONENT_CLASSES]. + """ DEFAULT_CSS = """ DirectoryTree > .directory-tree--folder { diff --git a/src/textual/widgets/_footer.py b/src/textual/widgets/_footer.py index dd8af2e9b..4f7801b94 100644 --- a/src/textual/widgets/_footer.py +++ b/src/textual/widgets/_footer.py @@ -1,6 +1,7 @@ from __future__ import annotations from collections import defaultdict +from typing import ClassVar import rich.repr from rich.console import RenderableType @@ -15,6 +16,21 @@ from ..widget import Widget class Footer(Widget): """A simple footer widget which docks itself to the bottom of the parent container.""" + COMPONENT_CLASSES: ClassVar[set[str]] = { + "footer--description", + "footer--key", + "footer--highlight", + "footer--highlight-key", + } + """ + | Class | Description | + | :- | :- | + | `footer--description` | Targets the descriptions of the key bindings. | + | `footer--highlight` | Targets the highlighted key binding. | + | `footer--highlight-key` | Targets the key portion of the highlighted key binding. | + | `footer--key` | Targets the key portions of the key bindings. | + """ + DEFAULT_CSS = """ Footer { background: $accent; @@ -37,13 +53,6 @@ class Footer(Widget): } """ - COMPONENT_CLASSES = { - "footer--description", - "footer--key", - "footer--highlight", - "footer--highlight-key", - } - def __init__(self) -> None: super().__init__() self._key_text: Text | None = None diff --git a/src/textual/widgets/_input.py b/src/textual/widgets/_input.py index 497b7c671..fdfbefe35 100644 --- a/src/textual/widgets/_input.py +++ b/src/textual/widgets/_input.py @@ -1,4 +1,5 @@ from __future__ import annotations +from typing import ClassVar from rich.cells import cell_len, get_character_cell_size from rich.console import Console, ConsoleOptions, RenderableType, RenderResult @@ -8,7 +9,7 @@ from rich.text import Text from .. import events from .._segment_tools import line_crop -from ..binding import Binding +from ..binding import Binding, BindingType from ..geometry import Size from ..message import Message from ..reactive import reactive @@ -53,6 +54,35 @@ class _InputRenderable: class Input(Widget, can_focus=True): """A text input widget.""" + BINDINGS: ClassVar[list[BindingType]] = [ + Binding("left", "cursor_left", "cursor left", show=False), + Binding("right", "cursor_right", "cursor right", show=False), + Binding("backspace", "delete_left", "delete left", show=False), + Binding("home", "home", "home", show=False), + Binding("end", "end", "end", show=False), + Binding("ctrl+d", "delete_right", "delete right", show=False), + Binding("enter", "submit", "submit", show=False), + ] + """ + | Key(s) | Action | Description | + | :- | :- | :- | + | left | [cursor_left][textual.widgets.Input.cursor_left] | Move the cursor left. | + | right | [cursor_right][textual.widgets.Input.cursor_right] | Move the cursor right. | + | backspace | [delete_left][textual.widgets.Input.delete_left] | Delete the character to the left of the cursor. | + | home | [home][textual.widgets.Input.home] | Go to the beginning of the input. | + | end | [end][textual.widgets.Input.end] | Go to the end of the input. | + | ctrl+d | [delete_right][textual.widgets.Input.delete_right] | Delete the character to the right of the cursor. | + | enter | [submit][textual.widgets.Input.submit] | Submit the current value of the input. | + """ + + COMPONENT_CLASSES: ClassVar[set[str]] = {"input--cursor", "input--placeholder"} + """ + | Class | Description | + | :- | :- | + | `input--cursor` | Target the cursor. | + | `input--placeholder` | Target the placeholder text (when it exists). | + """ + DEFAULT_CSS = """ Input { background: $boost; @@ -79,18 +109,6 @@ class Input(Widget, can_focus=True): } """ - BINDINGS = [ - Binding("left", "cursor_left", "cursor left", show=False), - Binding("right", "cursor_right", "cursor right", show=False), - Binding("backspace", "delete_left", "delete left", show=False), - Binding("home", "home", "home", show=False), - Binding("end", "end", "end", show=False), - Binding("ctrl+d", "delete_right", "delete right", show=False), - Binding("enter", "submit", "submit", show=False), - ] - - COMPONENT_CLASSES = {"input--cursor", "input--placeholder"} - cursor_blink = reactive(True) value = reactive("", layout=True, init=False) input_scroll_offset = reactive(0) diff --git a/src/textual/widgets/_list_view.py b/src/textual/widgets/_list_view.py index 3cf65d076..c5067de64 100644 --- a/src/textual/widgets/_list_view.py +++ b/src/textual/widgets/_list_view.py @@ -1,8 +1,9 @@ from __future__ import annotations +from typing import ClassVar from textual import events from textual.await_remove import AwaitRemove -from textual.binding import Binding +from textual.binding import Binding, BindingType from textual.containers import Vertical from textual.geometry import clamp from textual.message import Message @@ -19,11 +20,18 @@ class ListView(Vertical, can_focus=True, can_focus_children=False): index: The index in the list that's currently highlighted. """ - BINDINGS = [ + BINDINGS: ClassVar[list[BindingType]] = [ Binding("enter", "select_cursor", "Select", show=False), Binding("up", "cursor_up", "Cursor Up", show=False), Binding("down", "cursor_down", "Cursor Down", show=False), ] + """ + | Key(s) | Action | Description | + | :- | :- | :- | + | enter | [select_cursor][textual.widgets.ListView.select_cursor] | Select the current item. | + | up | [cursor_up][textual.widgets.ListView.cursor_up] | Move the cursor up. | + | down | [cursor_down][textual.widgets.ListView.cursor_down] | Move the cursor down. | + """ index = reactive(0, always_update=True) diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index fc930f945..5bca333b9 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -14,7 +14,7 @@ from .._segment_tools import line_pad from .._types import MessageTarget from .._typing import TypeAlias from .._immutable_sequence_view import ImmutableSequenceView -from ..binding import Binding +from ..binding import Binding, BindingType from ..geometry import Region, Size, clamp from ..message import Message from ..reactive import reactive, var @@ -242,11 +242,39 @@ class TreeNode(Generic[TreeDataType]): class Tree(Generic[TreeDataType], ScrollView, can_focus=True): - BINDINGS = [ + BINDINGS: ClassVar[list[BindingType]] = [ Binding("enter", "select_cursor", "Select", show=False), Binding("up", "cursor_up", "Cursor Up", show=False), Binding("down", "cursor_down", "Cursor Down", show=False), ] + """ + | Key(s) | Action | Description | + | :- | :- | :- | + | enter | [select_cursor][textual.widgets.Tree.select_cursor] | Select the current item. | + | up | [cursor_up][textual.widgets.Tree.cursor_up] | Move the cursor up. | + | down | [cursor_down][textual.widgets.Tree.cursor_down] | Move the cursor down. | + """ + + COMPONENT_CLASSES: ClassVar[set[str]] = { + "tree--label", + "tree--guides", + "tree--guides-hover", + "tree--guides-selected", + "tree--cursor", + "tree--highlight", + "tree--highlight-line", + } + """ + | Class | Description | + | :- | :- | + | `tree--cursor` | Targets the cursor. | + | `tree--guides` | Targets the indentation guides. | + | `tree--guides-hover` | Targets the indentation guides under the cursor. | + | `tree--guides-selected` | Targets the indentation guides that are selected. | + | `tree--highlight` | Targets the highlighted items. | + | `tree--highlight-line` | Targets the lines under the cursor. | + | `tree--label` | Targets the (text) labels of the items. | + """ DEFAULT_CSS = """ Tree { @@ -286,16 +314,6 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): """ - COMPONENT_CLASSES: ClassVar[set[str]] = { - "tree--label", - "tree--guides", - "tree--guides-hover", - "tree--guides-selected", - "tree--cursor", - "tree--highlight", - "tree--highlight-line", - } - show_root = reactive(True) """bool: Show the root of the tree.""" hover_line = var(-1) From b63e2366e16c98266d4af6104724733b78694d5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 25 Jan 2023 15:00:45 +0000 Subject: [PATCH 16/93] Remove 'action' column from bindings. --- src/textual/widgets/_checkbox.py | 6 +++--- src/textual/widgets/_data_table.py | 14 +++++++------- src/textual/widgets/_input.py | 18 +++++++++--------- src/textual/widgets/_list_view.py | 10 +++++----- src/textual/widgets/_tree.py | 10 +++++----- tools/widget_documentation.py | 8 +++----- 6 files changed, 32 insertions(+), 34 deletions(-) diff --git a/src/textual/widgets/_checkbox.py b/src/textual/widgets/_checkbox.py index a2b5f1eea..ee2a1c9ef 100644 --- a/src/textual/widgets/_checkbox.py +++ b/src/textual/widgets/_checkbox.py @@ -25,9 +25,9 @@ class Checkbox(Widget, can_focus=True): Binding("enter,space", "toggle", "Toggle", show=False), ] """ - | Key(s) | Action | Description | - | :- | :- | :- | - | enter,space | [toggle][textual.widgets.Checkbox.toggle] | Toggle | + | Key(s) | Description | + | :- | :- | + | enter,space | Toggle the checkbox status. | """ COMPONENT_CLASSES: ClassVar[set[str]] = { diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index 2e9e19eb4..2c2febc6d 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -94,13 +94,13 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): Binding("left", "cursor_left", "Cursor Left", show=False), ] """ - | Key(s) | Action | Description | - | :- | :- | :- | - | enter | [select_cursor][textual.widgets.DataTable.select_cursor] | Select cells under the cursor. | - | up | [cursor_up][textual.widgets.DataTable.cursor_up] | Move the cursor up. | - | down | [cursor_down][textual.widgets.DataTable.cursor_down] | Move the cursor down. | - | right | [cursor_right][textual.widgets.DataTable.cursor_right] | Move the cursor right. | - | left | [cursor_left][textual.widgets.DataTable.cursor_left] | Move the cursor left. | + | Key(s) | Description | + | :- | :- | + | enter | Select cells under the cursor. | + | up | Move the cursor up. | + | down | Move the cursor down. | + | right | Move the cursor right. | + | left | Move the cursor left. | """ COMPONENT_CLASSES: ClassVar[set[str]] = { diff --git a/src/textual/widgets/_input.py b/src/textual/widgets/_input.py index fdfbefe35..52e48d6ca 100644 --- a/src/textual/widgets/_input.py +++ b/src/textual/widgets/_input.py @@ -64,15 +64,15 @@ class Input(Widget, can_focus=True): Binding("enter", "submit", "submit", show=False), ] """ - | Key(s) | Action | Description | - | :- | :- | :- | - | left | [cursor_left][textual.widgets.Input.cursor_left] | Move the cursor left. | - | right | [cursor_right][textual.widgets.Input.cursor_right] | Move the cursor right. | - | backspace | [delete_left][textual.widgets.Input.delete_left] | Delete the character to the left of the cursor. | - | home | [home][textual.widgets.Input.home] | Go to the beginning of the input. | - | end | [end][textual.widgets.Input.end] | Go to the end of the input. | - | ctrl+d | [delete_right][textual.widgets.Input.delete_right] | Delete the character to the right of the cursor. | - | enter | [submit][textual.widgets.Input.submit] | Submit the current value of the input. | + | Key(s) | Description | + | :- | :- | + | left | Move the cursor left. | + | right | Move the cursor right. | + | backspace | Delete the character to the left of the cursor. | + | home | Go to the beginning of the input. | + | end | Go to the end of the input. | + | ctrl+d | Delete the character to the right of the cursor. | + | enter | Submit the current value of the input. | """ COMPONENT_CLASSES: ClassVar[set[str]] = {"input--cursor", "input--placeholder"} diff --git a/src/textual/widgets/_list_view.py b/src/textual/widgets/_list_view.py index c5067de64..f05622616 100644 --- a/src/textual/widgets/_list_view.py +++ b/src/textual/widgets/_list_view.py @@ -26,11 +26,11 @@ class ListView(Vertical, can_focus=True, can_focus_children=False): Binding("down", "cursor_down", "Cursor Down", show=False), ] """ - | Key(s) | Action | Description | - | :- | :- | :- | - | enter | [select_cursor][textual.widgets.ListView.select_cursor] | Select the current item. | - | up | [cursor_up][textual.widgets.ListView.cursor_up] | Move the cursor up. | - | down | [cursor_down][textual.widgets.ListView.cursor_down] | Move the cursor down. | + | Key(s) | Description | + | :- | :- | + | enter | Select the current item. | + | up | Move the cursor up. | + | down | Move the cursor down. | """ index = reactive(0, always_update=True) diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index 5bca333b9..54cdda39c 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -248,11 +248,11 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): Binding("down", "cursor_down", "Cursor Down", show=False), ] """ - | Key(s) | Action | Description | - | :- | :- | :- | - | enter | [select_cursor][textual.widgets.Tree.select_cursor] | Select the current item. | - | up | [cursor_up][textual.widgets.Tree.cursor_up] | Move the cursor up. | - | down | [cursor_down][textual.widgets.Tree.cursor_down] | Move the cursor down. | + | Key(s) | Description | + | :- | :- | + | enter | Select the current item. | + | up | Move the cursor up. | + | down | Move the cursor down. | """ COMPONENT_CLASSES: ClassVar[set[str]] = { diff --git a/tools/widget_documentation.py b/tools/widget_documentation.py index d58c8c019..04f3de86b 100644 --- a/tools/widget_documentation.py +++ b/tools/widget_documentation.py @@ -24,13 +24,11 @@ def print_bindings(widget: str, bindings: list[Binding]) -> None: if bindings: print("BINDINGS") print('"""') - print("| Key(s) | Action | Description |") - print("| :- | :- | :- |") + print("| Key(s) | Description |") + print("| :- | :- |") for binding in bindings: - print( - f"| {binding.key} | [{binding.action}][textual.widgets.{widget}.{binding.action}] | {binding.description} |" - ) + print(f"| {binding.key} | {binding.description} |") if bindings: print('"""') From ef779d71dda997b617dcd2043e54e8d8c45211fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 25 Jan 2023 15:19:16 +0000 Subject: [PATCH 17/93] Add bindings and component classes to reference pages. --- docs/widgets/checkbox.md | 20 ++++++++++++++++++-- docs/widgets/data_table.md | 18 ++++++++++++++++++ docs/widgets/directory_tree.md | 8 ++++++++ docs/widgets/footer.md | 9 +++++++++ docs/widgets/input.md | 17 +++++++++++++++++ docs/widgets/list_view.md | 8 ++++++++ docs/widgets/tree.md | 15 +++++++++++++++ 7 files changed, 93 insertions(+), 2 deletions(-) diff --git a/docs/widgets/checkbox.md b/docs/widgets/checkbox.md index 6a8acc612..a8ed28054 100644 --- a/docs/widgets/checkbox.md +++ b/docs/widgets/checkbox.md @@ -46,11 +46,27 @@ The `Checkbox.Changed` message is sent when the checkbox is toggled. | --------- | ------ | ------------------------------ | | `value` | `bool` | The new value of the checkbox. | +## Bindings + +The checkbox widget defines directly the following bindings: + +::: textual.widgets.Checkbox.BINDINGS + options: + show_root_heading: false + show_root_toc_entry: false + +## Component Classes + +The checkbox widget provides the following component classes: + +::: textual.widgets.Checkbox.COMPONENT_CLASSES + options: + show_root_heading: false + show_root_toc_entry: false + ## Additional Notes - To remove the spacing around a checkbox, set `border: none;` and `padding: 0;`. -- The `.checkbox--switch` component class can be used to change the color and background of the switch. -- When focused, the ++enter++ or ++space++ keys can be used to toggle the checkbox. ## See Also diff --git a/docs/widgets/data_table.md b/docs/widgets/data_table.md index a593dc4c2..9dd918d9d 100644 --- a/docs/widgets/data_table.md +++ b/docs/widgets/data_table.md @@ -48,6 +48,24 @@ The example below populates a table with CSV data. ### ::: textual.widgets.DataTable.ColumnSelected +## Bindings + +The data table widget defines directly the following bindings: + +::: textual.widgets.DataTable.BINDINGS + options: + show_root_heading: false + show_root_toc_entry: false + +## Component Classes + +The data table widget provides the following component classes: + +::: textual.widgets.DataTable.COMPONENT_CLASSES + options: + show_root_heading: false + show_root_toc_entry: false + ## See Also * [DataTable][textual.widgets.DataTable] code reference diff --git a/docs/widgets/directory_tree.md b/docs/widgets/directory_tree.md index 27e0af9ab..5af71d64f 100644 --- a/docs/widgets/directory_tree.md +++ b/docs/widgets/directory_tree.md @@ -36,6 +36,14 @@ The `DirectoryTree.FileSelected` message is sent when the user selects a file in | `show_guides` | `bool` | `True` | Show guide lines between levels. | | `guide_depth` | `int` | `4` | Amount of indentation between parent and child. | +## Component Classes + +The directory tree widget provides the following component classes: + +::: textual.widgets.DirectoryTree.COMPONENT_CLASSES + options: + show_root_heading: false + show_root_toc_entry: false ## See Also diff --git a/docs/widgets/footer.md b/docs/widgets/footer.md index e45b147db..d500ba66b 100644 --- a/docs/widgets/footer.md +++ b/docs/widgets/footer.md @@ -32,6 +32,15 @@ widget. Notice how the `Footer` automatically displays the keybinding. This widget sends no messages. +## Component Classes + +The footer widget provides the following component classes: + +::: textual.widgets.Footer.COMPONENT_CLASSES + options: + show_root_heading: false + show_root_toc_entry: false + ## Additional Notes * You can prevent keybindings from appearing in the footer by setting the `show` argument of the `Binding` to `False`. diff --git a/docs/widgets/input.md b/docs/widgets/input.md index 586fd1ca5..c5a7cc724 100644 --- a/docs/widgets/input.md +++ b/docs/widgets/input.md @@ -57,6 +57,23 @@ The `Input.Submitted` message is sent when you press ++enter++ with the text fie | --------- | ----- | -------------------------------- | | `value` | `str` | The new value in the text input. | +## Bindings + +The input widget defines directly the following bindings: + +::: textual.widgets.Input.BINDINGS + options: + show_root_heading: false + show_root_toc_entry: false + +## Component Classes + +The input widget provides the following component classes: + +::: textual.widgets.Input.COMPONENT_CLASSES + options: + show_root_heading: false + show_root_toc_entry: false ## Additional Notes diff --git a/docs/widgets/list_view.md b/docs/widgets/list_view.md index fe7a8c563..a348eedbd 100644 --- a/docs/widgets/list_view.md +++ b/docs/widgets/list_view.md @@ -63,6 +63,14 @@ or by clicking on it. | --------- | ---------- | --------------------------- | | `item` | `ListItem` | The item that was selected. | +## Bindings + +The list view widget defines directly the following bindings: + +::: textual.widgets.ListView.BINDINGS + options: + show_root_heading: false + show_root_toc_entry: false ## See Also diff --git a/docs/widgets/tree.md b/docs/widgets/tree.md index 801d993f0..ce476bd16 100644 --- a/docs/widgets/tree.md +++ b/docs/widgets/tree.md @@ -71,8 +71,23 @@ The `Tree.NodeCollapsed` message is sent when the user expands a node in the tre | --------- | ------------------------------------ | --------------- | | `node` | [TreeNode][textual.widgets.TreeNode] | Collapsed node. | +## Bindings +The tree widget defines directly the following bindings: +::: textual.widgets.Tree.BINDINGS + options: + show_root_heading: false + show_root_toc_entry: false + +## Component Classes + +The tree widget provides the following component classes: + +::: textual.widgets.Tree.COMPONENT_CLASSES + options: + show_root_heading: false + show_root_toc_entry: false ## See Also From 4405f2da2a6aced4a720b00c31b1a80466d126fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 26 Jan 2023 10:47:53 +0000 Subject: [PATCH 18/93] Add plugin to ignore docs templates. --- mkdocs.yml | 4 +- poetry.lock | 2232 ++++++++++++++++++++++++------------------------ pyproject.toml | 1 + 3 files changed, 1127 insertions(+), 1110 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 081bdff37..c1ecd43e2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -263,9 +263,11 @@ plugins: - "!^_" - "^__init__$" - "!^can_replace$" - watch: - src/textual +- exclude: + glob: + - "**/_template.md" extra_css: diff --git a/poetry.lock b/poetry.lock index 885b92cd0..97eee26de 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,5 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + [[package]] name = "aiohttp" version = "3.8.3" @@ -5,1033 +7,7 @@ description = "Async http client/server framework (asyncio)" category = "main" optional = false python-versions = ">=3.6" - -[package.dependencies] -aiosignal = ">=1.1.2" -async-timeout = ">=4.0.0a3,<5.0" -asynctest = {version = "0.13.0", markers = "python_version < \"3.8\""} -attrs = ">=17.3.0" -charset-normalizer = ">=2.0,<3.0" -frozenlist = ">=1.1.1" -multidict = ">=4.5,<7.0" -typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} -yarl = ">=1.0,<2.0" - -[package.extras] -speedups = ["Brotli", "aiodns", "cchardet"] - -[[package]] -name = "aiosignal" -version = "1.3.1" -description = "aiosignal: a list of registered asynchronous callbacks" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -frozenlist = ">=1.1.0" - -[[package]] -name = "anyio" -version = "3.6.2" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "dev" -optional = false -python-versions = ">=3.6.2" - -[package.dependencies] -idna = ">=2.8" -sniffio = ">=1.1" -typing-extensions = {version = "*", markers = "python_version < \"3.8\""} - -[package.extras] -doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] -trio = ["trio (>=0.16,<0.22)"] - -[[package]] -name = "async-timeout" -version = "4.0.2" -description = "Timeout context manager for asyncio programs" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""} - -[[package]] -name = "asynctest" -version = "0.13.0" -description = "Enhance the standard unittest package with features for testing asyncio libraries" -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "attrs" -version = "22.2.0" -description = "Classes Without Boilerplate" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] -tests = ["attrs[tests-no-zope]", "zope.interface"] -tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] - -[[package]] -name = "black" -version = "22.8.0" -description = "The uncompromising code formatter." -category = "dev" -optional = false -python-versions = ">=3.6.2" - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} -typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "cached-property" -version = "1.5.2" -description = "A decorator for caching properties in classes." -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "certifi" -version = "2022.12.7" -description = "Python package for providing Mozilla's CA Bundle." -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "cfgv" -version = "3.3.1" -description = "Validate configuration and produce human readable error messages." -category = "dev" -optional = false -python-versions = ">=3.6.1" - -[[package]] -name = "charset-normalizer" -version = "2.1.1" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" -optional = false -python-versions = ">=3.6.0" - -[package.extras] -unicode-backport = ["unicodedata2"] - -[[package]] -name = "click" -version = "8.1.3" -description = "Composable command line interface toolkit" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" - -[[package]] -name = "colored" -version = "1.4.4" -description = "Simple library for color and formatting to terminal" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "coverage" -version = "7.0.5" -description = "Code coverage measurement for Python" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -toml = ["tomli"] - -[[package]] -name = "distlib" -version = "0.3.6" -description = "Distribution utilities" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "exceptiongroup" -version = "1.1.0" -description = "Backport of PEP 654 (exception groups)" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "filelock" -version = "3.9.0" -description = "A platform independent file lock." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo (>=2022.12.7)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] -testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] - -[[package]] -name = "frozenlist" -version = "1.3.3" -description = "A list-like structure which implements collections.abc.MutableSequence" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "ghp-import" -version = "2.1.0" -description = "Copy your docs directly to the gh-pages branch." -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -python-dateutil = ">=2.8.1" - -[package.extras] -dev = ["flake8", "markdown", "twine", "wheel"] - -[[package]] -name = "gitdb" -version = "4.0.10" -description = "Git Object Database" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -smmap = ">=3.0.1,<6" - -[[package]] -name = "gitpython" -version = "3.1.30" -description = "GitPython is a python library used to interact with Git repositories" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -gitdb = ">=4.0.1,<5" -typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\""} - -[[package]] -name = "griffe" -version = "0.25.4" -description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -cached-property = {version = "*", markers = "python_version < \"3.8\""} -colorama = ">=0.4" - -[package.extras] -async = ["aiofiles (>=0.7,<1.0)"] - -[[package]] -name = "h11" -version = "0.14.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -typing-extensions = {version = "*", markers = "python_version < \"3.8\""} - -[[package]] -name = "httpcore" -version = "0.16.3" -description = "A minimal low-level HTTP client." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -anyio = ">=3.0,<5.0" -certifi = "*" -h11 = ">=0.13,<0.15" -sniffio = ">=1.0.0,<2.0.0" - -[package.extras] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] - -[[package]] -name = "httpx" -version = "0.23.3" -description = "The next generation HTTP client." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -certifi = "*" -httpcore = ">=0.15.0,<0.17.0" -rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} -sniffio = "*" - -[package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] - -[[package]] -name = "identify" -version = "2.5.13" -description = "File identification library for Python" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -license = ["ukkonen"] - -[[package]] -name = "idna" -version = "3.4" -description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "importlib-metadata" -version = "4.13.0" -description = "Read metadata from Python packages" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -perf = ["ipython"] -testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "jinja2" -version = "3.0.3" -description = "A very fast and expressive template engine." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "markdown" -version = "3.3.7" -description = "Python implementation of Markdown." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} - -[package.extras] -testing = ["coverage", "pyyaml"] - -[[package]] -name = "markdown-it-py" -version = "2.1.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -mdurl = ">=0.1,<1.0" -typing_extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark (>=3.2,<4.0)"] -code-style = ["pre-commit (==2.6)"] -compare = ["commonmark (>=0.9.1,<0.10.0)", "markdown (>=3.3.6,<3.4.0)", "mistletoe (>=0.8.1,<0.9.0)", "mistune (>=2.0.2,<2.1.0)", "panflute (>=2.1.3,<2.2.0)"] -linkify = ["linkify-it-py (>=1.0,<2.0)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "markupsafe" -version = "2.1.2" -description = "Safely add untrusted strings to HTML/XML markup." -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "mergedeep" -version = "1.3.4" -description = "A deep merge function for 🐍." -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "mkdocs" -version = "1.4.2" -description = "Project documentation with Markdown." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -click = ">=7.0" -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" -mergedeep = ">=1.3.4" -packaging = ">=20.5" -pyyaml = ">=5.1" -pyyaml-env-tag = ">=0.1" -typing-extensions = {version = ">=3.10", markers = "python_version < \"3.8\""} -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)"] - -[[package]] -name = "mkdocs-autorefs" -version = "0.4.1" -description = "Automatically link across pages in MkDocs." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -Markdown = ">=3.3" -mkdocs = ">=1.1" - -[[package]] -name = "mkdocs-material" -version = "8.5.11" -description = "Documentation that simply works" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -jinja2 = ">=3.0.2" -markdown = ">=3.2" -mkdocs = ">=1.4.0" -mkdocs-material-extensions = ">=1.1" -pygments = ">=2.12" -pymdown-extensions = ">=9.4" -requests = ">=2.26" - -[[package]] -name = "mkdocs-material-extensions" -version = "1.1.1" -description = "Extension pack for Python Markdown and MkDocs Material." -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "mkdocs-rss-plugin" -version = "1.5.0" -description = "MkDocs plugin which generates a static RSS feed using git log and page.meta." -category = "dev" -optional = false -python-versions = ">=3.7, <4" - -[package.dependencies] -GitPython = ">=3.1,<3.2" -mkdocs = ">=1.1,<2" -pytz = {version = ">=2022.0.0,<2023.0.0", markers = "python_version < \"3.9\""} -tzdata = {version = ">=2022.0.0,<2023.0.0", markers = "python_version >= \"3.9\" and sys_platform == \"win32\""} - -[package.extras] -dev = ["black", "feedparser (>=6.0,<6.1)", "flake8 (>=4,<5.1)", "pre-commit (>=2.10,<2.21)", "pytest-cov (>=4.0.0,<4.1.0)", "validator-collection (>=1.5,<1.6)"] -doc = ["mkdocs-bootswatch (>=1,<2)", "mkdocs-minify-plugin (>=0.5.0,<0.6.0)", "pygments (>=2.5,<3)", "pymdown-extensions (>=7,<10)"] - -[[package]] -name = "mkdocstrings" -version = "0.19.1" -description = "Automatic documentation from sources, for MkDocs." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -Jinja2 = ">=2.11.1" -Markdown = ">=3.3" -MarkupSafe = ">=1.1" -mkdocs = ">=1.2" -mkdocs-autorefs = ">=0.3.1" -mkdocstrings-python = {version = ">=0.5.2", optional = true, markers = "extra == \"python\""} -pymdown-extensions = ">=6.3" - -[package.extras] -crystal = ["mkdocstrings-crystal (>=0.3.4)"] -python = ["mkdocstrings-python (>=0.5.2)"] -python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] - -[[package]] -name = "mkdocstrings-python" -version = "0.8.3" -description = "A Python handler for mkdocstrings." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -griffe = ">=0.24" -mkdocstrings = ">=0.19" - -[[package]] -name = "msgpack" -version = "1.0.4" -description = "MessagePack serializer" -category = "main" -optional = true -python-versions = "*" - -[[package]] -name = "multidict" -version = "6.0.4" -description = "multidict implementation" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "mypy" -version = "0.990" -description = "Optional static typing for Python" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -mypy-extensions = ">=0.4.3" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} -typing-extensions = ">=3.10" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -python2 = ["typed-ast (>=1.4.0,<2)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "nanoid" -version = "2.0.0" -description = "A tiny, secure, URL-friendly, unique string ID generator for Python" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "nodeenv" -version = "1.7.0" -description = "Node.js virtual environment builder" -category = "dev" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" - -[package.dependencies] -setuptools = "*" - -[[package]] -name = "packaging" -version = "23.0" -description = "Core utilities for Python packages" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "pathspec" -version = "0.10.3" -description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "platformdirs" -version = "2.6.2" -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" - -[package.dependencies] -typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""} - -[package.extras] -docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] - -[[package]] -name = "pluggy" -version = "1.0.0" -description = "plugin and hook calling mechanisms for python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "pre-commit" -version = "2.21.0" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -virtualenv = ">=20.10.0" - -[[package]] -name = "pygments" -version = "2.14.0" -description = "Pygments is a syntax highlighting package written in Python." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -plugins = ["importlib-metadata"] - -[[package]] -name = "pymdown-extensions" -version = "9.9.2" -description = "Extension pack for Python Markdown." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -markdown = ">=3.2" - -[[package]] -name = "pytest" -version = "7.2.1" -description = "pytest: simple powerful testing with Python" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -attrs = ">=19.2.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] - -[[package]] -name = "pytest-aiohttp" -version = "1.0.4" -description = "Pytest plugin for aiohttp support" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -aiohttp = ">=3.8.1" -pytest = ">=6.1.0" -pytest-asyncio = ">=0.17.2" - -[package.extras] -testing = ["coverage (==6.2)", "mypy (==0.931)"] - -[[package]] -name = "pytest-asyncio" -version = "0.20.3" -description = "Pytest support for asyncio" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -pytest = ">=6.1.0" -typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""} - -[package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] -testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] - -[[package]] -name = "pytest-cov" -version = "2.12.1" -description = "Pytest plugin for measuring coverage." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.dependencies] -coverage = ">=5.2.1" -pytest = ">=4.6" -toml = "*" - -[package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] - -[[package]] -name = "python-dateutil" -version = "2.8.2" -description = "Extensions to the standard Python datetime module" -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "pytz" -version = "2022.7.1" -description = "World timezone definitions, modern and historical" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "pyyaml" -version = "6.0" -description = "YAML parser and emitter for Python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "pyyaml-env-tag" -version = "0.1" -description = "A custom YAML tag for referencing environment variables in YAML files. " -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -pyyaml = "*" - -[[package]] -name = "requests" -version = "2.28.2" -description = "Python HTTP for Humans." -category = "dev" -optional = false -python-versions = ">=3.7, <4" - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "rfc3986" -version = "1.5.0" -description = "Validating URI References per RFC 3986" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} - -[package.extras] -idna2008 = ["idna"] - -[[package]] -name = "rich" -version = "13.2.0" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "main" -optional = false -python-versions = ">=3.7.0" - -[package.dependencies] -markdown-it-py = ">=2.1.0,<3.0.0" -pygments = ">=2.6.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] - -[[package]] -name = "setuptools" -version = "66.1.1" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "smmap" -version = "5.0.0" -description = "A pure Python implementation of a sliding window memory map manager" -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "sniffio" -version = "1.3.0" -description = "Sniff out which async library your code is running under" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "syrupy" -version = "3.0.6" -description = "Pytest Snapshot Test Utility" -category = "dev" -optional = false -python-versions = ">=3.7,<4" - -[package.dependencies] -colored = ">=1.3.92,<2.0.0" -pytest = ">=5.1.0,<8.0.0" - -[[package]] -name = "time-machine" -version = "2.9.0" -description = "Travel through time in your tests." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -python-dateutil = "*" - -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "typed-ast" -version = "1.5.4" -description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "typing-extensions" -version = "4.4.0" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "tzdata" -version = "2022.7" -description = "Provider of IANA time zone data" -category = "dev" -optional = false -python-versions = ">=2" - -[[package]] -name = "urllib3" -version = "1.26.14" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "virtualenv" -version = "20.17.1" -description = "Virtual Python Environment builder" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -distlib = ">=0.3.6,<1" -filelock = ">=3.4.1,<4" -importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.8\""} -platformdirs = ">=2.4,<3" - -[package.extras] -docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"] -testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] - -[[package]] -name = "watchdog" -version = "2.2.1" -description = "Filesystem events monitoring" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -watchmedo = ["PyYAML (>=3.10)"] - -[[package]] -name = "yarl" -version = "1.8.2" -description = "Yet another URL library" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" -typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} - -[[package]] -name = "zipp" -version = "3.11.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - -[extras] -dev = ["aiohttp", "click", "msgpack"] - -[metadata] -lock-version = "1.1" -python-versions = "^3.7" -content-hash = "0e3bcf48b37c16096a3c2b2f7d3f548494f9a22ebdee2e2c5d8ac74b80ab344e" - -[metadata.files] -aiohttp = [ +files = [ {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ba71c9b4dcbb16212f334126cc3d8beb6af377f6703d9dc2d9fb3874fd667ee9"}, {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d24b8bb40d5c61ef2d9b6a8f4528c2f17f1c5d2d31fed62ec860f6006142e83e"}, {file = "aiohttp-3.8.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f88df3a83cf9df566f171adba39d5bd52814ac0b94778d2448652fc77f9eb491"}, @@ -1120,27 +96,112 @@ aiohttp = [ {file = "aiohttp-3.8.3-cp39-cp39-win_amd64.whl", hash = "sha256:88e5be56c231981428f4f506c68b6a46fa25c4123a2e86d156c58a8369d31ab7"}, {file = "aiohttp-3.8.3.tar.gz", hash = "sha256:3828fb41b7203176b82fe5d699e0d845435f2374750a44b480ea6b930f6be269"}, ] -aiosignal = [ + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = ">=4.0.0a3,<5.0" +asynctest = {version = "0.13.0", markers = "python_version < \"3.8\""} +attrs = ">=17.3.0" +charset-normalizer = ">=2.0,<3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns", "cchardet"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, ] -anyio = [ + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "anyio" +version = "3.6.2" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "dev" +optional = false +python-versions = ">=3.6.2" +files = [ {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, ] -async-timeout = [ + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] +trio = ["trio (>=0.16,<0.22)"] + +[[package]] +name = "async-timeout" +version = "4.0.2" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, ] -asynctest = [ + +[package.dependencies] +typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""} + +[[package]] +name = "asynctest" +version = "0.13.0" +description = "Enhance the standard unittest package with features for testing asyncio libraries" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ {file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"}, {file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"}, ] -attrs = [ + +[[package]] +name = "attrs" +version = "22.2.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, ] -black = [ + +[package.extras] +cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] +tests = ["attrs[tests-no-zope]", "zope.interface"] +tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] + +[[package]] +name = "black" +version = "22.8.0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6.2" +files = [ {file = "black-22.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce957f1d6b78a8a231b18e0dd2d94a33d2ba738cd88a7fe64f53f659eea49fdd"}, {file = "black-22.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5107ea36b2b61917956d018bd25129baf9ad1125e39324a9b18248d362156a27"}, {file = "black-22.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8166b7bfe5dcb56d325385bd1d1e0f635f24aae14b3ae437102dedc0c186747"}, @@ -1165,34 +226,120 @@ black = [ {file = "black-22.8.0-py3-none-any.whl", hash = "sha256:d2c21d439b2baf7aa80d6dd4e3659259be64c6f49dfd0f32091063db0e006db4"}, {file = "black-22.8.0.tar.gz", hash = "sha256:792f7eb540ba9a17e8656538701d3eb1afcb134e3b45b71f20b25c77a8db7e6e"}, ] -cached-property = [ + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} +typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "cached-property" +version = "1.5.2" +description = "A decorator for caching properties in classes." +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, ] -certifi = [ + +[[package]] +name = "certifi" +version = "2022.12.7" +description = "Python package for providing Mozilla's CA Bundle." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, ] -cfgv = [ + +[[package]] +name = "cfgv" +version = "3.3.1" +description = "Validate configuration and produce human readable error messages." +category = "dev" +optional = false +python-versions = ">=3.6.1" +files = [ {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, ] -charset-normalizer = [ + +[[package]] +name = "charset-normalizer" +version = "2.1.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.6.0" +files = [ {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, ] -click = [ + +[package.extras] +unicode-backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, ] -colorama = [ + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -colored = [ + +[[package]] +name = "colored" +version = "1.4.4" +description = "Simple library for color and formatting to terminal" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "colored-1.4.4.tar.gz", hash = "sha256:04ff4d4dd514274fe3b99a21bb52fb96f2688c01e93fba7bef37221e7cb56ce0"}, ] -coverage = [ + +[[package]] +name = "coverage" +version = "7.0.5" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "coverage-7.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2a7f23bbaeb2a87f90f607730b45564076d870f1fb07b9318d0c21f36871932b"}, {file = "coverage-7.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c18d47f314b950dbf24a41787ced1474e01ca816011925976d90a88b27c22b89"}, {file = "coverage-7.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef14d75d86f104f03dea66c13188487151760ef25dd6b2dbd541885185f05f40"}, @@ -1245,19 +392,61 @@ coverage = [ {file = "coverage-7.0.5-pp37.pp38.pp39-none-any.whl", hash = "sha256:b9727ac4f5cf2cbf87880a63870b5b9730a8ae3a4a360241a0fdaa2f71240ff0"}, {file = "coverage-7.0.5.tar.gz", hash = "sha256:051afcbd6d2ac39298d62d340f94dbb6a1f31de06dfaf6fcef7b759dd3860c45"}, ] -distlib = [ + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "distlib" +version = "0.3.6" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, ] -exceptiongroup = [ + +[[package]] +name = "exceptiongroup" +version = "1.1.0" +description = "Backport of PEP 654 (exception groups)" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, ] -filelock = [ + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "filelock" +version = "3.9.0" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "filelock-3.9.0-py3-none-any.whl", hash = "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d"}, {file = "filelock-3.9.0.tar.gz", hash = "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de"}, ] -frozenlist = [ + +[package.extras] +docs = ["furo (>=2022.12.7)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] +testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "frozenlist" +version = "1.3.3" +description = "A list-like structure which implements collections.abc.MutableSequence" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff8bf625fe85e119553b5383ba0fb6aa3d0ec2ae980295aaefa552374926b3f4"}, {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dfbac4c2dfcc082fcf8d942d1e49b6aa0766c19d3358bd86e2000bf0fa4a9cf0"}, {file = "frozenlist-1.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b1c63e8d377d039ac769cd0926558bb7068a1f7abb0f003e3717ee003ad85530"}, @@ -1333,63 +522,266 @@ frozenlist = [ {file = "frozenlist-1.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfe33efc9cb900a4c46f91a5ceba26d6df370ffddd9ca386eb1d4f0ad97b9ea9"}, {file = "frozenlist-1.3.3.tar.gz", hash = "sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a"}, ] -ghp-import = [ + +[[package]] +name = "ghp-import" +version = "2.1.0" +description = "Copy your docs directly to the gh-pages branch." +category = "main" +optional = false +python-versions = "*" +files = [ {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, ] -gitdb = [ + +[package.dependencies] +python-dateutil = ">=2.8.1" + +[package.extras] +dev = ["flake8", "markdown", "twine", "wheel"] + +[[package]] +name = "gitdb" +version = "4.0.10" +description = "Git Object Database" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "gitdb-4.0.10-py3-none-any.whl", hash = "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"}, {file = "gitdb-4.0.10.tar.gz", hash = "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a"}, ] -gitpython = [ + +[package.dependencies] +smmap = ">=3.0.1,<6" + +[[package]] +name = "gitpython" +version = "3.1.30" +description = "GitPython is a python library used to interact with Git repositories" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "GitPython-3.1.30-py3-none-any.whl", hash = "sha256:cd455b0000615c60e286208ba540271af9fe531fa6a87cc590a7298785ab2882"}, {file = "GitPython-3.1.30.tar.gz", hash = "sha256:769c2d83e13f5d938b7688479da374c4e3d49f71549aaf462b646db9602ea6f8"}, ] -griffe = [ + +[package.dependencies] +gitdb = ">=4.0.1,<5" +typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\""} + +[[package]] +name = "griffe" +version = "0.25.4" +description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "griffe-0.25.4-py3-none-any.whl", hash = "sha256:919f935a358b31074d16e324e26b041883c60a8cf10504655e394afc3a7caad8"}, {file = "griffe-0.25.4.tar.gz", hash = "sha256:f190edf8ef58d43c856d2d6761ec324a043ff60deb8c14359263571e8b91fe68"}, ] -h11 = [ + +[package.dependencies] +cached-property = {version = "*", markers = "python_version < \"3.8\""} +colorama = ">=0.4" + +[package.extras] +async = ["aiofiles (>=0.7,<1.0)"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] -httpcore = [ + +[package.dependencies] +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "httpcore" +version = "0.16.3" +description = "A minimal low-level HTTP client." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"}, {file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"}, ] -httpx = [ + +[package.dependencies] +anyio = ">=3.0,<5.0" +certifi = "*" +h11 = ">=0.13,<0.15" +sniffio = ">=1.0.0,<2.0.0" + +[package.extras] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] + +[[package]] +name = "httpx" +version = "0.23.3" +description = "The next generation HTTP client." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"}, {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"}, ] -identify = [ + +[package.dependencies] +certifi = "*" +httpcore = ">=0.15.0,<0.17.0" +rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] + +[[package]] +name = "identify" +version = "2.5.13" +description = "File identification library for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "identify-2.5.13-py2.py3-none-any.whl", hash = "sha256:8aa48ce56e38c28b6faa9f261075dea0a942dfbb42b341b4e711896cbb40f3f7"}, {file = "identify-2.5.13.tar.gz", hash = "sha256:abb546bca6f470228785338a01b539de8a85bbf46491250ae03363956d8ebb10"}, ] -idna = [ + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] -importlib-metadata = [ + +[[package]] +name = "importlib-metadata" +version = "4.13.0" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "importlib_metadata-4.13.0-py3-none-any.whl", hash = "sha256:8a8a81bcf996e74fee46f0d16bd3eaa382a7eb20fd82445c3ad11f4090334116"}, {file = "importlib_metadata-4.13.0.tar.gz", hash = "sha256:dd0173e8f150d6815e098fd354f6414b0f079af4644ddfe90c71e2fc6174346d"}, ] -iniconfig = [ + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +perf = ["ipython"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] -jinja2 = [ + +[[package]] +name = "jinja2" +version = "3.0.3" +description = "A very fast and expressive template engine." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, ] -markdown = [ + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markdown" +version = "3.3.7" +description = "Python implementation of Markdown." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"}, {file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"}, ] -markdown-it-py = [ + +[package.dependencies] +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} + +[package.extras] +testing = ["coverage", "pyyaml"] + +[[package]] +name = "markdown-it-py" +version = "2.1.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "markdown-it-py-2.1.0.tar.gz", hash = "sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da"}, {file = "markdown_it_py-2.1.0-py3-none-any.whl", hash = "sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27"}, ] -markupsafe = [ + +[package.dependencies] +mdurl = ">=0.1,<1.0" +typing_extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark (>=3.2,<4.0)"] +code-style = ["pre-commit (==2.6)"] +compare = ["commonmark (>=0.9.1,<0.10.0)", "markdown (>=3.3.6,<3.4.0)", "mistletoe (>=0.8.1,<0.9.0)", "mistune (>=2.0.2,<2.1.0)", "panflute (>=2.1.3,<2.2.0)"] +linkify = ["linkify-it-py (>=1.0,<2.0)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "markupsafe" +version = "2.1.2" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, @@ -1441,43 +833,196 @@ markupsafe = [ {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, ] -mdurl = [ + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] -mergedeep = [ + +[[package]] +name = "mergedeep" +version = "1.3.4" +description = "A deep merge function for 🐍." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, ] -mkdocs = [ + +[[package]] +name = "mkdocs" +version = "1.4.2" +description = "Project documentation with Markdown." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "mkdocs-1.4.2-py3-none-any.whl", hash = "sha256:c8856a832c1e56702577023cd64cc5f84948280c1c0fcc6af4cd39006ea6aa8c"}, {file = "mkdocs-1.4.2.tar.gz", hash = "sha256:8947af423a6d0facf41ea1195b8e1e8c85ad94ac95ae307fe11232e0424b11c5"}, ] -mkdocs-autorefs = [ + +[package.dependencies] +click = ">=7.0" +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" +mergedeep = ">=1.3.4" +packaging = ">=20.5" +pyyaml = ">=5.1" +pyyaml-env-tag = ">=0.1" +typing-extensions = {version = ">=3.10", markers = "python_version < \"3.8\""} +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)"] + +[[package]] +name = "mkdocs-autorefs" +version = "0.4.1" +description = "Automatically link across pages in MkDocs." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"}, {file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"}, ] -mkdocs-material = [ + +[package.dependencies] +Markdown = ">=3.3" +mkdocs = ">=1.1" + +[[package]] +name = "mkdocs-exclude" +version = "1.0.2" +description = "A mkdocs plugin that lets you exclude files or trees." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "mkdocs-exclude-1.0.2.tar.gz", hash = "sha256:ba6fab3c80ddbe3fd31d3e579861fd3124513708271180a5f81846da8c7e2a51"}, +] + +[package.dependencies] +mkdocs = "*" + +[[package]] +name = "mkdocs-material" +version = "8.5.11" +description = "Documentation that simply works" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "mkdocs_material-8.5.11-py3-none-any.whl", hash = "sha256:c907b4b052240a5778074a30a78f31a1f8ff82d7012356dc26898b97559f082e"}, {file = "mkdocs_material-8.5.11.tar.gz", hash = "sha256:b0ea0513fd8cab323e8a825d6692ea07fa83e917bb5db042e523afecc7064ab7"}, ] -mkdocs-material-extensions = [ + +[package.dependencies] +jinja2 = ">=3.0.2" +markdown = ">=3.2" +mkdocs = ">=1.4.0" +mkdocs-material-extensions = ">=1.1" +pygments = ">=2.12" +pymdown-extensions = ">=9.4" +requests = ">=2.26" + +[[package]] +name = "mkdocs-material-extensions" +version = "1.1.1" +description = "Extension pack for Python Markdown and MkDocs Material." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "mkdocs_material_extensions-1.1.1-py3-none-any.whl", hash = "sha256:e41d9f38e4798b6617ad98ca8f7f1157b1e4385ac1459ca1e4ea219b556df945"}, {file = "mkdocs_material_extensions-1.1.1.tar.gz", hash = "sha256:9c003da71e2cc2493d910237448c672e00cefc800d3d6ae93d2fc69979e3bd93"}, ] -mkdocs-rss-plugin = [ + +[[package]] +name = "mkdocs-rss-plugin" +version = "1.5.0" +description = "MkDocs plugin which generates a static RSS feed using git log and page.meta." +category = "dev" +optional = false +python-versions = ">=3.7, <4" +files = [ {file = "mkdocs-rss-plugin-1.5.0.tar.gz", hash = "sha256:4178b3830dcbad9b53b12459e315b1aad6b37d1e7e5c56c686866a10f99878a4"}, {file = "mkdocs_rss_plugin-1.5.0-py2.py3-none-any.whl", hash = "sha256:2ab14c20bf6b7983acbe50181e7e4a0778731d9c2d5c38107ca7047a7abd2165"}, ] -mkdocstrings = [ + +[package.dependencies] +GitPython = ">=3.1,<3.2" +mkdocs = ">=1.1,<2" +pytz = {version = ">=2022.0.0,<2023.0.0", markers = "python_version < \"3.9\""} +tzdata = {version = ">=2022.0.0,<2023.0.0", markers = "python_version >= \"3.9\" and sys_platform == \"win32\""} + +[package.extras] +dev = ["black", "feedparser (>=6.0,<6.1)", "flake8 (>=4,<5.1)", "pre-commit (>=2.10,<2.21)", "pytest-cov (>=4.0.0,<4.1.0)", "validator-collection (>=1.5,<1.6)"] +doc = ["mkdocs-bootswatch (>=1,<2)", "mkdocs-minify-plugin (>=0.5.0,<0.6.0)", "pygments (>=2.5,<3)", "pymdown-extensions (>=7,<10)"] + +[[package]] +name = "mkdocstrings" +version = "0.19.1" +description = "Automatic documentation from sources, for MkDocs." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "mkdocstrings-0.19.1-py3-none-any.whl", hash = "sha256:32a38d88f67f65b264184ea71290f9332db750d189dea4200cbbe408d304c261"}, {file = "mkdocstrings-0.19.1.tar.gz", hash = "sha256:d1037cacb4b522c1e8c164ed5d00d724a82e49dcee0af80db8fb67b384faeef9"}, ] -mkdocstrings-python = [ + +[package.dependencies] +Jinja2 = ">=2.11.1" +Markdown = ">=3.3" +MarkupSafe = ">=1.1" +mkdocs = ">=1.2" +mkdocs-autorefs = ">=0.3.1" +mkdocstrings-python = {version = ">=0.5.2", optional = true, markers = "extra == \"python\""} +pymdown-extensions = ">=6.3" + +[package.extras] +crystal = ["mkdocstrings-crystal (>=0.3.4)"] +python = ["mkdocstrings-python (>=0.5.2)"] +python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] + +[[package]] +name = "mkdocstrings-python" +version = "0.8.3" +description = "A Python handler for mkdocstrings." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "mkdocstrings-python-0.8.3.tar.gz", hash = "sha256:9ae473f6dc599339b09eee17e4d2b05d6ac0ec29860f3fc9b7512d940fc61adf"}, {file = "mkdocstrings_python-0.8.3-py3-none-any.whl", hash = "sha256:4e6e1cd6f37a785de0946ced6eb846eb2f5d891ac1cc2c7b832943d3529087a7"}, ] -msgpack = [ + +[package.dependencies] +griffe = ">=0.24" +mkdocstrings = ">=0.19" + +[[package]] +name = "msgpack" +version = "1.0.4" +description = "MessagePack serializer" +category = "main" +optional = true +python-versions = "*" +files = [ {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250"}, {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:112b0f93202d7c0fef0b7810d465fde23c746a2d482e1e2de2aafd2ce1492c88"}, {file = "msgpack-1.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:002b5c72b6cd9b4bafd790f364b8480e859b4712e91f43014fe01e4f957b8467"}, @@ -1531,7 +1076,15 @@ msgpack = [ {file = "msgpack-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:4d5834a2a48965a349da1c5a79760d94a1a0172fbb5ab6b5b33cbf8447e109ce"}, {file = "msgpack-1.0.4.tar.gz", hash = "sha256:f5d869c18f030202eb412f08b28d2afeea553d6613aee89e200d7aca7ef01f5f"}, ] -multidict = [ + +[[package]] +name = "multidict" +version = "6.0.4" +description = "multidict implementation" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, @@ -1607,7 +1160,15 @@ multidict = [ {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, ] -mypy = [ + +[[package]] +name = "mypy" +version = "0.990" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "mypy-0.990-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aaf1be63e0207d7d17be942dcf9a6b641745581fe6c64df9a38deb562a7dbafa"}, {file = "mypy-0.990-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d555aa7f44cecb7ea3c0ac69d58b1a5afb92caa017285a8e9c4efbf0518b61b4"}, {file = "mypy-0.990-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f694d6d09a460b117dccb6857dda269188e3437c880d7b60fa0014fa872d1e9"}, @@ -1639,71 +1200,290 @@ mypy = [ {file = "mypy-0.990-py3-none-any.whl", hash = "sha256:8f1940325a8ed460ba03d19ab83742260fa9534804c317224e5d4e5aa588e2d6"}, {file = "mypy-0.990.tar.gz", hash = "sha256:72382cb609142dba3f04140d016c94b4092bc7b4d98ca718740dc989e5271b8d"}, ] -mypy-extensions = [ + +[package.dependencies] +mypy-extensions = ">=0.4.3" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} +typing-extensions = ">=3.10" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] -nanoid = [ + +[[package]] +name = "nanoid" +version = "2.0.0" +description = "A tiny, secure, URL-friendly, unique string ID generator for Python" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "nanoid-2.0.0-py3-none-any.whl", hash = "sha256:90aefa650e328cffb0893bbd4c236cfd44c48bc1f2d0b525ecc53c3187b653bb"}, {file = "nanoid-2.0.0.tar.gz", hash = "sha256:5a80cad5e9c6e9ae3a41fa2fb34ae189f7cb420b2a5d8f82bd9d23466e4efa68"}, ] -nodeenv = [ + +[[package]] +name = "nodeenv" +version = "1.7.0" +description = "Node.js virtual environment builder" +category = "dev" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, ] -packaging = [ + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "packaging" +version = "23.0" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, ] -pathspec = [ + +[[package]] +name = "pathspec" +version = "0.10.3" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "pathspec-0.10.3-py3-none-any.whl", hash = "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6"}, {file = "pathspec-0.10.3.tar.gz", hash = "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"}, ] -platformdirs = [ + +[[package]] +name = "platformdirs" +version = "2.6.2" +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-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, ] -pluggy = [ + +[package.dependencies] +typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""} + +[package.extras] +docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] -pre-commit = [ + +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pre-commit" +version = "2.21.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, ] -pygments = [ + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "pygments" +version = "2.14.0" +description = "Pygments is a syntax highlighting package written in Python." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, ] -pymdown-extensions = [ + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pymdown-extensions" +version = "9.9.2" +description = "Extension pack for Python Markdown." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "pymdown_extensions-9.9.2-py3-none-any.whl", hash = "sha256:c3d804eb4a42b85bafb5f36436342a5ad38df03878bb24db8855a4aa8b08b765"}, {file = "pymdown_extensions-9.9.2.tar.gz", hash = "sha256:ebb33069bafcb64d5f5988043331d4ea4929325dc678a6bcf247ddfcf96499f8"}, ] -pytest = [ + +[package.dependencies] +markdown = ">=3.2" + +[[package]] +name = "pytest" +version = "7.2.1" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "pytest-7.2.1-py3-none-any.whl", hash = "sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5"}, {file = "pytest-7.2.1.tar.gz", hash = "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"}, ] -pytest-aiohttp = [ + +[package.dependencies] +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "pytest-aiohttp" +version = "1.0.4" +description = "Pytest plugin for aiohttp support" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "pytest-aiohttp-1.0.4.tar.gz", hash = "sha256:39ff3a0d15484c01d1436cbedad575c6eafbf0f57cdf76fb94994c97b5b8c5a4"}, {file = "pytest_aiohttp-1.0.4-py3-none-any.whl", hash = "sha256:1d2dc3a304c2be1fd496c0c2fb6b31ab60cd9fc33984f761f951f8ea1eb4ca95"}, ] -pytest-asyncio = [ + +[package.dependencies] +aiohttp = ">=3.8.1" +pytest = ">=6.1.0" +pytest-asyncio = ">=0.17.2" + +[package.extras] +testing = ["coverage (==6.2)", "mypy (==0.931)"] + +[[package]] +name = "pytest-asyncio" +version = "0.20.3" +description = "Pytest support for asyncio" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "pytest-asyncio-0.20.3.tar.gz", hash = "sha256:83cbf01169ce3e8eb71c6c278ccb0574d1a7a3bb8eaaf5e50e0ad342afb33b36"}, {file = "pytest_asyncio-0.20.3-py3-none-any.whl", hash = "sha256:f129998b209d04fcc65c96fc85c11e5316738358909a8399e93be553d7656442"}, ] -pytest-cov = [ + +[package.dependencies] +pytest = ">=6.1.0" +typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""} + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] + +[[package]] +name = "pytest-cov" +version = "2.12.1" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, ] -python-dateutil = [ + +[package.dependencies] +coverage = ">=5.2.1" +pytest = ">=4.6" +toml = "*" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] -pytz = [ + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2022.7.1" +description = "World timezone definitions, modern and historical" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "pytz-2022.7.1-py2.py3-none-any.whl", hash = "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"}, {file = "pytz-2022.7.1.tar.gz", hash = "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0"}, ] -pyyaml = [ + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "main" +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"}, @@ -1745,43 +1525,159 @@ pyyaml = [ {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] -pyyaml-env-tag = [ + +[[package]] +name = "pyyaml-env-tag" +version = "0.1" +description = "A custom YAML tag for referencing environment variables in YAML files. " +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, ] -requests = [ + +[package.dependencies] +pyyaml = "*" + +[[package]] +name = "requests" +version = "2.28.2" +description = "Python HTTP for Humans." +category = "dev" +optional = false +python-versions = ">=3.7, <4" +files = [ {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, ] -rfc3986 = [ + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rfc3986" +version = "1.5.0" +description = "Validating URI References per RFC 3986" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, ] -rich = [ + +[package.dependencies] +idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} + +[package.extras] +idna2008 = ["idna"] + +[[package]] +name = "rich" +version = "13.2.0" +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.2.0-py3-none-any.whl", hash = "sha256:7c963f0d03819221e9ac561e1bc866e3f95a02248c1234daa48954e6d381c003"}, {file = "rich-13.2.0.tar.gz", hash = "sha256:f1a00cdd3eebf999a15d85ec498bfe0b1a77efe9b34f645768a54132ef444ac5"}, ] -setuptools = [ + +[package.dependencies] +markdown-it-py = ">=2.1.0,<3.0.0" +pygments = ">=2.6.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] + +[[package]] +name = "setuptools" +version = "66.1.1" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "setuptools-66.1.1-py3-none-any.whl", hash = "sha256:6f590d76b713d5de4e49fe4fbca24474469f53c83632d5d0fd056f7ff7e8112b"}, {file = "setuptools-66.1.1.tar.gz", hash = "sha256:ac4008d396bc9cd983ea483cb7139c0240a07bbc74ffb6232fceffedc6cf03a8"}, ] -six = [ + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -smmap = [ + +[[package]] +name = "smmap" +version = "5.0.0" +description = "A pure Python implementation of a sliding window memory map manager" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, ] -sniffio = [ + +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, ] -syrupy = [ + +[[package]] +name = "syrupy" +version = "3.0.6" +description = "Pytest Snapshot Test Utility" +category = "dev" +optional = false +python-versions = ">=3.7,<4" +files = [ {file = "syrupy-3.0.6-py3-none-any.whl", hash = "sha256:9c18e22264026b34239bcc87ab7cc8d893eb17236ea7dae634217ea4f22a848d"}, {file = "syrupy-3.0.6.tar.gz", hash = "sha256:583aa5ca691305c27902c3e29a1ce9da50ff9ab5f184c54b1dc124a16e4a6cf4"}, ] -time-machine = [ + +[package.dependencies] +colored = ">=1.3.92,<2.0.0" +pytest = ">=5.1.0,<8.0.0" + +[[package]] +name = "time-machine" +version = "2.9.0" +description = "Travel through time in your tests." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "time-machine-2.9.0.tar.gz", hash = "sha256:60222d43f6e93a926adc36ed37a54bc8e4d0d8d1c4d449096afcfe85086129c2"}, {file = "time_machine-2.9.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fd72c0b2e7443fff6e4481991742b72c17f73735e5fdd176406ca48df187a5c9"}, {file = "time_machine-2.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5657e0e6077cf15b37f0d8cf78e868113bbb3ecccc60064c40fe52d8166ca8b1"}, @@ -1836,15 +1732,42 @@ time-machine = [ {file = "time_machine-2.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:cc6bf01211b5ea40f633d5502c5aa495b415ebaff66e041820997dae70a508e1"}, {file = "time_machine-2.9.0-cp39-cp39-win_arm64.whl", hash = "sha256:3ce445775fcf7cb4040cfdba4b7c4888e7fd98bbcccfe1dc3fa8a798ed1f1d24"}, ] -toml = [ + +[package.dependencies] +python-dateutil = "*" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] -tomli = [ + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -typed-ast = [ + +[[package]] +name = "typed-ast" +version = "1.5.4" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, @@ -1870,23 +1793,78 @@ typed-ast = [ {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] -typing-extensions = [ + +[[package]] +name = "typing-extensions" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] -tzdata = [ + +[[package]] +name = "tzdata" +version = "2022.7" +description = "Provider of IANA time zone data" +category = "dev" +optional = false +python-versions = ">=2" +files = [ {file = "tzdata-2022.7-py2.py3-none-any.whl", hash = "sha256:2b88858b0e3120792a3c0635c23daf36a7d7eeeca657c323da299d2094402a0d"}, {file = "tzdata-2022.7.tar.gz", hash = "sha256:fe5f866eddd8b96e9fcba978f8e503c909b19ea7efda11e52e39494bad3a7bfa"}, ] -urllib3 = [ + +[[package]] +name = "urllib3" +version = "1.26.14" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, ] -virtualenv = [ + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "virtualenv" +version = "20.17.1" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "virtualenv-20.17.1-py3-none-any.whl", hash = "sha256:ce3b1684d6e1a20a3e5ed36795a97dfc6af29bc3970ca8dab93e11ac6094b3c4"}, {file = "virtualenv-20.17.1.tar.gz", hash = "sha256:f8b927684efc6f1cc206c9db297a570ab9ad0e51c16fa9e45487d36d1905c058"}, ] -watchdog = [ + +[package.dependencies] +distlib = ">=0.3.6,<1" +filelock = ">=3.4.1,<4" +importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.8\""} +platformdirs = ">=2.4,<3" + +[package.extras] +docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"] +testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "watchdog" +version = "2.2.1" +description = "Filesystem events monitoring" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "watchdog-2.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a09483249d25cbdb4c268e020cb861c51baab2d1affd9a6affc68ffe6a231260"}, {file = "watchdog-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5100eae58133355d3ca6c1083a33b81355c4f452afa474c2633bd2fbbba398b3"}, {file = "watchdog-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e618a4863726bc7a3c64f95c218437f3349fb9d909eb9ea3a1ed3b567417c661"}, @@ -1916,7 +1894,18 @@ watchdog = [ {file = "watchdog-2.2.1-py3-none-win_ia64.whl", hash = "sha256:195ab1d9d611a4c1e5311cbf42273bc541e18ea8c32712f2fb703cfc6ff006f9"}, {file = "watchdog-2.2.1.tar.gz", hash = "sha256:cdcc23c9528601a8a293eb4369cbd14f6b4f34f07ae8769421252e9c22718b6f"}, ] -yarl = [ + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[[package]] +name = "yarl" +version = "1.8.2" +description = "Yet another URL library" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "yarl-1.8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bb81f753c815f6b8e2ddd2eef3c855cf7da193b82396ac013c661aaa6cc6b0a5"}, {file = "yarl-1.8.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:47d49ac96156f0928f002e2424299b2c91d9db73e08c4cd6742923a086f1c863"}, {file = "yarl-1.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3fc056e35fa6fba63248d93ff6e672c096f95f7836938241ebc8260e062832fe"}, @@ -1992,7 +1981,32 @@ yarl = [ {file = "yarl-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:6604711362f2dbf7160df21c416f81fac0de6dbcf0b5445a2ef25478ecc4c778"}, {file = "yarl-1.8.2.tar.gz", hash = "sha256:49d43402c6e3013ad0978602bf6bf5328535c48d192304b91b97a3c6790b1562"}, ] -zipp = [ + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" +typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} + +[[package]] +name = "zipp" +version = "3.11.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, ] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[extras] +dev = ["aiohttp", "click", "msgpack"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.7" +content-hash = "2c9e7a727933ce76d9a330ef89b64dcd374777a416543163d7af5e679c4d4ce0" diff --git a/pyproject.toml b/pyproject.toml index 99ee64632..8b106de82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ aiohttp = { version = ">=3.8.1", optional = true } click = {version = ">=8.1.2", optional = true} msgpack = { version = ">=1.0.3", optional = true } nanoid = ">=2.0.0" +mkdocs-exclude = "^1.0.2" [tool.poetry.extras] dev = ["aiohttp", "click", "msgpack"] From 210e39488f4e01e68fa8dcefbcb0d75579058cfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 26 Jan 2023 10:56:32 +0000 Subject: [PATCH 19/93] Add template for widget reference. --- docs/widgets/_template.md | 64 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 docs/widgets/_template.md diff --git a/docs/widgets/_template.md b/docs/widgets/_template.md new file mode 100644 index 000000000..1e8c6f972 --- /dev/null +++ b/docs/widgets/_template.md @@ -0,0 +1,64 @@ +# Widget + +Widget description. + +- [ ] Focusable +- [ ] Container + + +## Example + +Example app showing the widget: + +=== "Output" + + ```{.textual path="docs/examples/widgets/checkbox.py"} + ``` + +=== "checkbox.py" + + ```python + --8<-- "docs/examples/widgets/checkbox.py" + ``` + +=== "checkbox.css" + + ```sass + --8<-- "docs/examples/widgets/checkbox.css" + ``` + + +## Reactive attributes + + +## Bindings + +The WIDGET widget defines directly the following bindings: + +::: textual.widgets.WIDGET.BINDINGS + options: + show_root_heading: false + show_root_toc_entry: false + + +## Component classes + +The WIDGET widget provides the following component classes: + +::: textual.widget.WIDGET.COMPONENT_CLASSES + options: + show_root_heading: false + show_root_toc_entry: false + + +## Additional notes + +- Did you know this? +- Another pro tip. + + +## See also + +- [WIDGET](../api/WIDGET.md) code reference. +- Another related API. +- Something else useful. From d75845e52ac25b77459bf61ac26774a532dfe596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 26 Jan 2023 11:19:44 +0000 Subject: [PATCH 20/93] Move reactives and messages to top of widget source. --- src/textual/widgets/_button.py | 18 +-- src/textual/widgets/_checkbox.py | 38 ++--- src/textual/widgets/_data_table.py | 238 ++++++++++++++--------------- src/textual/widgets/_footer.py | 4 +- src/textual/widgets/_header.py | 4 +- src/textual/widgets/_input.py | 52 +++---- src/textual/widgets/_label.py | 1 - src/textual/widgets/_list_item.py | 11 +- 8 files changed, 183 insertions(+), 183 deletions(-) diff --git a/src/textual/widgets/_button.py b/src/textual/widgets/_button.py index 90da10c5d..81b54783f 100644 --- a/src/textual/widgets/_button.py +++ b/src/textual/widgets/_button.py @@ -150,6 +150,15 @@ class Button(Static, can_focus=True): ACTIVE_EFFECT_DURATION = 0.3 """When buttons are clicked they get the `-active` class for this duration (in seconds)""" + label: Reactive[RenderableType] = Reactive("") + """The text label that appears within the button.""" + + variant = Reactive.init("default") + """The variant name for the button.""" + + disabled = Reactive(False) + """The disabled state of the button; `True` if disabled, `False` if not.""" + class Pressed(Message, bubble=True): """Event sent when a `Button` is pressed. @@ -194,15 +203,6 @@ class Button(Static, can_focus=True): self.variant = self.validate_variant(variant) - label: Reactive[RenderableType] = Reactive("") - """The text label that appears within the button.""" - - variant = Reactive.init("default") - """The variant name for the button.""" - - disabled = Reactive(False) - """The disabled state of the button; `True` if disabled, `False` if not.""" - def __rich_repr__(self) -> rich.repr.Result: yield from super().__rich_repr__() yield "variant", self.variant, "default" diff --git a/src/textual/widgets/_checkbox.py b/src/textual/widgets/_checkbox.py index ee2a1c9ef..428b27a1e 100644 --- a/src/textual/widgets/_checkbox.py +++ b/src/textual/widgets/_checkbox.py @@ -70,6 +70,25 @@ class Checkbox(Widget, can_focus=True): } """ + value = reactive(False, init=False) + """The value of the checkbox; `True` for on and `False` for off.""" + + slider_pos = reactive(0.0) + """The position of the slider.""" + + class Changed(Message, bubble=True): + """Checkbox was toggled. + + Attributes: + value: The value that the checkbox was changed to. + input: The `Checkbox` widget that was changed. + """ + + def __init__(self, sender: Checkbox, value: bool) -> None: + super().__init__(sender) + self.value: bool = value + self.input: Checkbox = sender + def __init__( self, value: bool = False, @@ -94,12 +113,6 @@ class Checkbox(Widget, can_focus=True): self._reactive_value = value self._should_animate = animate - value = reactive(False, init=False) - """The value of the checkbox; `True` for on and `False` for off.""" - - slider_pos = reactive(0.0) - """The position of the slider.""" - def watch_value(self, value: bool) -> None: target_slider_pos = 1.0 if value else 0.0 if self._should_animate: @@ -137,16 +150,3 @@ class Checkbox(Widget, can_focus=True): """Toggle the checkbox value. As a result of the value changing, a Checkbox.Changed message will be emitted.""" self.value = not self.value - - class Changed(Message, bubble=True): - """Checkbox was toggled. - - Attributes: - value: The value that the checkbox was changed to. - input: The `Checkbox` widget that was changed. - """ - - def __init__(self, sender: Checkbox, value: bool) -> None: - super().__init__(sender) - self.value: bool = value - self.input: Checkbox = sender diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index 2c2febc6d..04823df11 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -189,6 +189,125 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): ) hover_cell: Reactive[Coordinate] = Reactive(Coordinate(0, 0), repaint=False) + class CellHighlighted(Message, bubble=True): + """Emitted when the cursor moves to highlight a new cell. + It's only relevant when the `cursor_type` is `"cell"`. + It's also emitted when the cell cursor is re-enabled (by setting `show_cursor=True`), + and when the cursor type is changed to `"cell"`. Can be handled using + `on_data_table_cell_highlighted` in a subclass of `DataTable` or in a parent + widget in the DOM. + + Attributes: + value: The value in the highlighted cell. + coordinate: The coordinate of the highlighted cell. + """ + + def __init__( + self, sender: DataTable, value: CellType, coordinate: Coordinate + ) -> None: + self.value: CellType = value + self.coordinate: Coordinate = coordinate + super().__init__(sender) + + def __rich_repr__(self) -> rich.repr.Result: + yield "sender", self.sender + yield "value", self.value + yield "coordinate", self.coordinate + + class CellSelected(Message, bubble=True): + """Emitted by the `DataTable` widget when a cell is selected. + It's only relevant when the `cursor_type` is `"cell"`. Can be handled using + `on_data_table_cell_selected` in a subclass of `DataTable` or in a parent + widget in the DOM. + + Attributes: + value: The value in the cell that was selected. + coordinate: The coordinate of the cell that was selected. + """ + + def __init__( + self, sender: DataTable, value: CellType, coordinate: Coordinate + ) -> None: + self.value: CellType = value + self.coordinate: Coordinate = coordinate + super().__init__(sender) + + def __rich_repr__(self) -> rich.repr.Result: + yield "sender", self.sender + yield "value", self.value + yield "coordinate", self.coordinate + + class RowHighlighted(Message, bubble=True): + """Emitted when a row is highlighted. This message is only emitted when the + `cursor_type` is set to `"row"`. Can be handled using `on_data_table_row_highlighted` + in a subclass of `DataTable` or in a parent widget in the DOM. + + Attributes: + cursor_row: The y-coordinate of the cursor that highlighted the row. + """ + + def __init__(self, sender: DataTable, cursor_row: int) -> None: + self.cursor_row: int = cursor_row + super().__init__(sender) + + def __rich_repr__(self) -> rich.repr.Result: + yield "sender", self.sender + yield "cursor_row", self.cursor_row + + class RowSelected(Message, bubble=True): + """Emitted when a row is selected. This message is only emitted when the + `cursor_type` is set to `"row"`. Can be handled using + `on_data_table_row_selected` in a subclass of `DataTable` or in a parent + widget in the DOM. + + Attributes: + cursor_row: The y-coordinate of the cursor that made the selection. + """ + + def __init__(self, sender: DataTable, cursor_row: int) -> None: + self.cursor_row: int = cursor_row + super().__init__(sender) + + def __rich_repr__(self) -> rich.repr.Result: + yield "sender", self.sender + yield "cursor_row", self.cursor_row + + class ColumnHighlighted(Message, bubble=True): + """Emitted when a column is highlighted. This message is only emitted when the + `cursor_type` is set to `"column"`. Can be handled using + `on_data_table_column_highlighted` in a subclass of `DataTable` or in a parent + widget in the DOM. + + Attributes: + cursor_column: The x-coordinate of the column that was highlighted. + """ + + def __init__(self, sender: DataTable, cursor_column: int) -> None: + self.cursor_column: int = cursor_column + super().__init__(sender) + + def __rich_repr__(self) -> rich.repr.Result: + yield "sender", self.sender + yield "cursor_column", self.cursor_column + + class ColumnSelected(Message, bubble=True): + """Emitted when a column is selected. This message is only emitted when the + `cursor_type` is set to `"column"`. Can be handled using + `on_data_table_column_selected` in a subclass of `DataTable` or in a parent + widget in the DOM. + + Attributes: + cursor_column: The x-coordinate of the column that was selected. + """ + + def __init__(self, sender: DataTable, cursor_column: int) -> None: + self.cursor_column: int = cursor_column + super().__init__(sender) + + def __rich_repr__(self) -> rich.repr.Result: + yield "sender", self.sender + yield "cursor_column", self.cursor_column + def __init__( self, *, @@ -989,122 +1108,3 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): elif cursor_type == "column": _, column = cursor_cell self.emit_no_wait(DataTable.ColumnSelected(self, column)) - - class CellHighlighted(Message, bubble=True): - """Emitted when the cursor moves to highlight a new cell. - It's only relevant when the `cursor_type` is `"cell"`. - It's also emitted when the cell cursor is re-enabled (by setting `show_cursor=True`), - and when the cursor type is changed to `"cell"`. Can be handled using - `on_data_table_cell_highlighted` in a subclass of `DataTable` or in a parent - widget in the DOM. - - Attributes: - value: The value in the highlighted cell. - coordinate: The coordinate of the highlighted cell. - """ - - def __init__( - self, sender: DataTable, value: CellType, coordinate: Coordinate - ) -> None: - self.value: CellType = value - self.coordinate: Coordinate = coordinate - super().__init__(sender) - - def __rich_repr__(self) -> rich.repr.Result: - yield "sender", self.sender - yield "value", self.value - yield "coordinate", self.coordinate - - class CellSelected(Message, bubble=True): - """Emitted by the `DataTable` widget when a cell is selected. - It's only relevant when the `cursor_type` is `"cell"`. Can be handled using - `on_data_table_cell_selected` in a subclass of `DataTable` or in a parent - widget in the DOM. - - Attributes: - value: The value in the cell that was selected. - coordinate: The coordinate of the cell that was selected. - """ - - def __init__( - self, sender: DataTable, value: CellType, coordinate: Coordinate - ) -> None: - self.value: CellType = value - self.coordinate: Coordinate = coordinate - super().__init__(sender) - - def __rich_repr__(self) -> rich.repr.Result: - yield "sender", self.sender - yield "value", self.value - yield "coordinate", self.coordinate - - class RowHighlighted(Message, bubble=True): - """Emitted when a row is highlighted. This message is only emitted when the - `cursor_type` is set to `"row"`. Can be handled using `on_data_table_row_highlighted` - in a subclass of `DataTable` or in a parent widget in the DOM. - - Attributes: - cursor_row: The y-coordinate of the cursor that highlighted the row. - """ - - def __init__(self, sender: DataTable, cursor_row: int) -> None: - self.cursor_row: int = cursor_row - super().__init__(sender) - - def __rich_repr__(self) -> rich.repr.Result: - yield "sender", self.sender - yield "cursor_row", self.cursor_row - - class RowSelected(Message, bubble=True): - """Emitted when a row is selected. This message is only emitted when the - `cursor_type` is set to `"row"`. Can be handled using - `on_data_table_row_selected` in a subclass of `DataTable` or in a parent - widget in the DOM. - - Attributes: - cursor_row: The y-coordinate of the cursor that made the selection. - """ - - def __init__(self, sender: DataTable, cursor_row: int) -> None: - self.cursor_row: int = cursor_row - super().__init__(sender) - - def __rich_repr__(self) -> rich.repr.Result: - yield "sender", self.sender - yield "cursor_row", self.cursor_row - - class ColumnHighlighted(Message, bubble=True): - """Emitted when a column is highlighted. This message is only emitted when the - `cursor_type` is set to `"column"`. Can be handled using - `on_data_table_column_highlighted` in a subclass of `DataTable` or in a parent - widget in the DOM. - - Attributes: - cursor_column: The x-coordinate of the column that was highlighted. - """ - - def __init__(self, sender: DataTable, cursor_column: int) -> None: - self.cursor_column: int = cursor_column - super().__init__(sender) - - def __rich_repr__(self) -> rich.repr.Result: - yield "sender", self.sender - yield "cursor_column", self.cursor_column - - class ColumnSelected(Message, bubble=True): - """Emitted when a column is selected. This message is only emitted when the - `cursor_type` is set to `"column"`. Can be handled using - `on_data_table_column_selected` in a subclass of `DataTable` or in a parent - widget in the DOM. - - Attributes: - cursor_column: The x-coordinate of the column that was selected. - """ - - def __init__(self, sender: DataTable, cursor_column: int) -> None: - self.cursor_column: int = cursor_column - super().__init__(sender) - - def __rich_repr__(self) -> rich.repr.Result: - yield "sender", self.sender - yield "cursor_column", self.cursor_column diff --git a/src/textual/widgets/_footer.py b/src/textual/widgets/_footer.py index 4f7801b94..0b1c9a808 100644 --- a/src/textual/widgets/_footer.py +++ b/src/textual/widgets/_footer.py @@ -53,13 +53,13 @@ class Footer(Widget): } """ + highlight_key: Reactive[str | None] = Reactive(None) + def __init__(self) -> None: super().__init__() self._key_text: Text | None = None self.auto_links = False - highlight_key: Reactive[str | None] = Reactive(None) - async def watch_highlight_key(self, value) -> None: """If highlight key changes we need to regenerate the text.""" self._key_text = None diff --git a/src/textual/widgets/_header.py b/src/textual/widgets/_header.py index 8f107ba44..b2df426cc 100644 --- a/src/textual/widgets/_header.py +++ b/src/textual/widgets/_header.py @@ -100,10 +100,10 @@ class Header(Widget): } """ - tall = Reactive(False) - DEFAULT_CLASSES = "" + tall = Reactive(False) + def __init__( self, show_clock: bool = False, diff --git a/src/textual/widgets/_input.py b/src/textual/widgets/_input.py index 52e48d6ca..2015f7277 100644 --- a/src/textual/widgets/_input.py +++ b/src/textual/widgets/_input.py @@ -121,6 +121,32 @@ class Input(Widget, can_focus=True): password = reactive(False) max_size: reactive[int | None] = reactive(None) + class Changed(Message, bubble=True): + """Value was changed. + + Attributes: + value: The value that the input was changed to. + input: The `Input` widget that was changed. + """ + + def __init__(self, sender: Input, value: str) -> None: + super().__init__(sender) + self.value: str = value + self.input: Input = sender + + class Submitted(Message, bubble=True): + """Sent when the enter key is pressed within an `Input`. + + Attributes: + value: The value of the `Input` being submitted.. + input: The `Input` widget that is being submitted. + """ + + def __init__(self, sender: Input, value: str) -> None: + super().__init__(sender) + self.value: str = value + self.input: Input = sender + def __init__( self, value: str | None = None, @@ -346,29 +372,3 @@ class Input(Widget, can_focus=True): async def action_submit(self) -> None: await self.emit(self.Submitted(self, self.value)) - - class Changed(Message, bubble=True): - """Value was changed. - - Attributes: - value: The value that the input was changed to. - input: The `Input` widget that was changed. - """ - - def __init__(self, sender: Input, value: str) -> None: - super().__init__(sender) - self.value: str = value - self.input: Input = sender - - class Submitted(Message, bubble=True): - """Sent when the enter key is pressed within an `Input`. - - Attributes: - value: The value of the `Input` being submitted.. - input: The `Input` widget that is being submitted. - """ - - def __init__(self, sender: Input, value: str) -> None: - super().__init__(sender) - self.value: str = value - self.input: Input = sender diff --git a/src/textual/widgets/_label.py b/src/textual/widgets/_label.py index 344c37013..d890d333b 100644 --- a/src/textual/widgets/_label.py +++ b/src/textual/widgets/_label.py @@ -12,4 +12,3 @@ class Label(Static): height: auto; } """ - """str: The default styling of a `Label`.""" diff --git a/src/textual/widgets/_list_item.py b/src/textual/widgets/_list_item.py index 1100621c6..f1af8c416 100644 --- a/src/textual/widgets/_list_item.py +++ b/src/textual/widgets/_list_item.py @@ -25,15 +25,16 @@ class ListItem(Widget, can_focus=False): height: auto; } """ + highlighted = reactive(False) + class _ChildClicked(Message): + """For informing with the parent ListView that we were clicked""" + + pass + def on_click(self, event: events.Click) -> None: self.emit_no_wait(self._ChildClicked(self)) def watch_highlighted(self, value: bool) -> None: self.set_class(value, "--highlight") - - class _ChildClicked(Message): - """For informing with the parent ListView that we were clicked""" - - pass From fb4f804dff007e6d1cd586e9042d6b9f8837b173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 26 Jan 2023 11:53:37 +0000 Subject: [PATCH 21/93] Improve widget message docstrings. --- src/textual/widgets/__init__.py | 2 +- src/textual/widgets/_button.py | 3 ++ src/textual/widgets/_checkbox.py | 5 +- src/textual/widgets/_directory_tree.py | 11 ++++- src/textual/widgets/_input.py | 12 +++-- src/textual/widgets/_list_view.py | 51 +++++++++++--------- src/textual/widgets/_tree.py | 64 +++++++++++++++----------- 7 files changed, 94 insertions(+), 54 deletions(-) diff --git a/src/textual/widgets/__init__.py b/src/textual/widgets/__init__.py index 4628cbe92..f26679d33 100644 --- a/src/textual/widgets/__init__.py +++ b/src/textual/widgets/__init__.py @@ -4,7 +4,7 @@ import typing from ..case import camel_to_snake -# ⚠️For any new built-in Widget we create, not only do we have to import them here and add them to `__all__`, +# For any new built-in Widget we create, not only do we have to import them here and add them to `__all__`, # but also to the `__init__.pyi` file in this same folder - otherwise text editors and type checkers won't # be able to "see" them. if typing.TYPE_CHECKING: diff --git a/src/textual/widgets/_button.py b/src/textual/widgets/_button.py index 81b54783f..164ca38ef 100644 --- a/src/textual/widgets/_button.py +++ b/src/textual/widgets/_button.py @@ -162,6 +162,9 @@ class Button(Static, can_focus=True): class Pressed(Message, bubble=True): """Event sent when a `Button` is pressed. + Can be handled using `on_button_pressed` in a subclass of `Button` or + in a parent widget in the DOM. + Attributes: button: The button that was pressed. """ diff --git a/src/textual/widgets/_checkbox.py b/src/textual/widgets/_checkbox.py index 428b27a1e..305858faf 100644 --- a/src/textual/widgets/_checkbox.py +++ b/src/textual/widgets/_checkbox.py @@ -77,7 +77,10 @@ class Checkbox(Widget, can_focus=True): """The position of the slider.""" class Changed(Message, bubble=True): - """Checkbox was toggled. + """Emitted when the status of the checkbox changes. + + Can be handled using `on_checkbox_changed` in a subclass of `Checkbox` + or in a parent widget in the DOM. Attributes: value: The value that the checkbox was changed to. diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index 9d6cbdc83..0be163d67 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -67,8 +67,17 @@ class DirectoryTree(Tree[DirEntry]): """ class FileSelected(Message, bubble=True): + """Emitted when a file is selected. + + Can be handled using `on_directory_tree_file_selected` in a subclass of + `DirectoryTree` or in a parent widget in the DOM. + + Attributes: + path: The path of the file that was selected. + """ + def __init__(self, sender: MessageTarget, path: str) -> None: - self.path = path + self.path: str = path super().__init__(sender) def __init__( diff --git a/src/textual/widgets/_input.py b/src/textual/widgets/_input.py index 2015f7277..912fe67f2 100644 --- a/src/textual/widgets/_input.py +++ b/src/textual/widgets/_input.py @@ -122,7 +122,10 @@ class Input(Widget, can_focus=True): max_size: reactive[int | None] = reactive(None) class Changed(Message, bubble=True): - """Value was changed. + """Emitted when the value changes. + + Can be handled using `on_input_changed` in a subclass of `Input` or in a parent + widget in the DOM. Attributes: value: The value that the input was changed to. @@ -135,10 +138,13 @@ class Input(Widget, can_focus=True): self.input: Input = sender class Submitted(Message, bubble=True): - """Sent when the enter key is pressed within an `Input`. + """Emitted when the enter key is pressed within an `Input`. + + Can be handled using `on_input_submitted` in a subclass of `Input` or in a + parent widget in the DOM. Attributes: - value: The value of the `Input` being submitted.. + value: The value of the `Input` being submitted. input: The `Input` widget that is being submitted. """ diff --git a/src/textual/widgets/_list_view.py b/src/textual/widgets/_list_view.py index f05622616..87675ec47 100644 --- a/src/textual/widgets/_list_view.py +++ b/src/textual/widgets/_list_view.py @@ -35,6 +35,35 @@ class ListView(Vertical, can_focus=True, can_focus_children=False): index = reactive(0, always_update=True) + class Highlighted(Message, bubble=True): + """Emitted when the highlighted item changes. + + Highlighted item is controlled using up/down keys. + Can be handled using `on_list_view_highlighted` in a subclass of `ListView` + or in a parent widget in the DOM. + + Attributes: + item: The highlighted item, if there is one highlighted. + """ + + def __init__(self, sender: ListView, item: ListItem | None) -> None: + super().__init__(sender) + self.item: ListItem | None = item + + class Selected(Message, bubble=True): + """Emitted when a list item is selected, e.g. when you press the enter key on it. + + Can be handled using `on_list_view_selected` in a subclass of `ListView` or in + a parent widget in the DOM. + + Attributes: + item: The selected item. + """ + + def __init__(self, sender: ListView, item: ListItem) -> None: + super().__init__(sender) + self.item: ListItem = item + def __init__( self, *children: ListItem, @@ -147,25 +176,3 @@ class ListView(Vertical, can_focus=True, can_focus_children=False): def __len__(self): return len(self.children) - - class Highlighted(Message, bubble=True): - """Emitted when the highlighted item changes. Highlighted item is controlled using up/down keys. - - Attributes: - item: The highlighted item, if there is one highlighted. - """ - - def __init__(self, sender: ListView, item: ListItem | None) -> None: - super().__init__(sender) - self.item: ListItem | None = item - - class Selected(Message, bubble=True): - """Emitted when a list item is selected, e.g. when you press the enter key on it - - Attributes: - item: The selected item. - """ - - def __init__(self, sender: ListView, item: ListItem) -> None: - super().__init__(sender) - self.item: ListItem = item diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index 54cdda39c..bcd8c0aa7 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -348,35 +348,12 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): ), } - class NodeSelected(Generic[EventTreeDataType], Message, bubble=True): - """Event sent when a node is selected. - - Attributes: - node: The node that was selected. - """ - - def __init__( - self, sender: MessageTarget, node: TreeNode[EventTreeDataType] - ) -> None: - self.node: TreeNode[EventTreeDataType] = node - super().__init__(sender) - - class NodeExpanded(Generic[EventTreeDataType], Message, bubble=True): - """Event sent when a node is expanded. - - Attributes: - node: The node that was expanded. - """ - - def __init__( - self, sender: MessageTarget, node: TreeNode[EventTreeDataType] - ) -> None: - self.node: TreeNode[EventTreeDataType] = node - super().__init__(sender) - class NodeCollapsed(Generic[EventTreeDataType], Message, bubble=True): """Event sent when a node is collapsed. + Can be handled using `on_tree_node_collapsed` in a subclass of `Tree` or in a + parent node in the DOM. + Attributes: node: The node that was collapsed. """ @@ -387,9 +364,28 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): self.node: TreeNode[EventTreeDataType] = node super().__init__(sender) + class NodeExpanded(Generic[EventTreeDataType], Message, bubble=True): + """Event sent when a node is expanded. + + Can be handled using `on_tree_node_expanded` in a subclass of `Tree` or in a + parent node in the DOM. + + Attributes: + node: The node that was expanded. + """ + + def __init__( + self, sender: MessageTarget, node: TreeNode[EventTreeDataType] + ) -> None: + self.node: TreeNode[EventTreeDataType] = node + super().__init__(sender) + class NodeHighlighted(Generic[EventTreeDataType], Message, bubble=True): """Event sent when a node is highlighted. + Can be handled using `on_tree_node_highlighted` in a subclass of `Tree` or in a + parent node in the DOM. + Attributes: node: The node that was highlighted. """ @@ -400,6 +396,22 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): self.node: TreeNode[EventTreeDataType] = node super().__init__(sender) + class NodeSelected(Generic[EventTreeDataType], Message, bubble=True): + """Event sent when a node is selected. + + Can be handled using `on_tree_node_selected` in a subclass of `Tree` or in a + parent node in the DOM. + + Attributes: + node: The node that was selected. + """ + + def __init__( + self, sender: MessageTarget, node: TreeNode[EventTreeDataType] + ) -> None: + self.node: TreeNode[EventTreeDataType] = node + super().__init__(sender) + def __init__( self, label: TextType, From b37517c40054889ad7bd14a2272db2ddd0b414e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 26 Jan 2023 11:53:51 +0000 Subject: [PATCH 22/93] Add all messages to widget references. --- docs/widgets/button.md | 10 +-------- docs/widgets/checkbox.md | 18 ++++------------- docs/widgets/directory_tree.md | 12 +---------- docs/widgets/input.md | 25 ++--------------------- docs/widgets/list_item.md | 7 ------- docs/widgets/list_view.md | 28 ++----------------------- docs/widgets/tree.md | 37 ++++------------------------------ 7 files changed, 14 insertions(+), 123 deletions(-) diff --git a/docs/widgets/button.md b/docs/widgets/button.md index ff7436d3b..d5b07ec96 100644 --- a/docs/widgets/button.md +++ b/docs/widgets/button.md @@ -39,15 +39,7 @@ Clicking any of the non-disabled buttons in the example app below will result th ## Messages -### Pressed - -The `Button.Pressed` message is sent when the button is pressed. - -- [x] Bubbles - -#### Attributes - -_No other attributes_ +### ::: textual.widgets.Button.Pressed ## Additional Notes diff --git a/docs/widgets/checkbox.md b/docs/widgets/checkbox.md index a8ed28054..a5a247ef9 100644 --- a/docs/widgets/checkbox.md +++ b/docs/widgets/checkbox.md @@ -32,20 +32,6 @@ The example below shows checkboxes in various states. | ------- | ------ | ------- | ---------------------------------- | | `value` | `bool` | `False` | The default value of the checkbox. | -## Messages - -### Pressed - -The `Checkbox.Changed` message is sent when the checkbox is toggled. - -- [x] Bubbles - -#### Attributes - -| attribute | type | purpose | -| --------- | ------ | ------------------------------ | -| `value` | `bool` | The new value of the checkbox. | - ## Bindings The checkbox widget defines directly the following bindings: @@ -64,6 +50,10 @@ The checkbox widget provides the following component classes: show_root_heading: false show_root_toc_entry: false +## Messages + +### ::: textual.widgets.Checkbox.Changed + ## Additional Notes - To remove the spacing around a checkbox, set `border: none;` and `padding: 0;`. diff --git a/docs/widgets/directory_tree.md b/docs/widgets/directory_tree.md index 5af71d64f..a43ae5beb 100644 --- a/docs/widgets/directory_tree.md +++ b/docs/widgets/directory_tree.md @@ -16,17 +16,7 @@ The example below creates a simple tree to navigate the current working director ## Messages -### FileSelected - -The `DirectoryTree.FileSelected` message is sent when the user selects a file in the tree - -- [x] Bubbles - -#### Attributes - -| attribute | type | purpose | -| --------- | ----- | ----------------- | -| `path` | `str` | Path of the file. | +### ::: textual.widgets.DirectoryTree.FileSelected ## Reactive Attributes diff --git a/docs/widgets/input.md b/docs/widgets/input.md index c5a7cc724..ee6183cf8 100644 --- a/docs/widgets/input.md +++ b/docs/widgets/input.md @@ -32,30 +32,9 @@ The example below shows how you might create a simple form using two `Input` wid ## Messages -### Changed +### ::: textual.widgets.Input.Changed -The `Input.Changed` message is sent when the value in the text input changes. - -- [x] Bubbles - -#### Attributes - -| attribute | type | purpose | -| --------- | ----- | -------------------------------- | -| `value` | `str` | The new value in the text input. | - - -### Submitted - -The `Input.Submitted` message is sent when you press ++enter++ with the text field submitted. - -- [x] Bubbles - -#### Attributes - -| attribute | type | purpose | -| --------- | ----- | -------------------------------- | -| `value` | `str` | The new value in the text input. | +### ::: textual.widgets.Input.Submitted ## Bindings diff --git a/docs/widgets/list_item.md b/docs/widgets/list_item.md index 5a9ed4435..698214159 100644 --- a/docs/widgets/list_item.md +++ b/docs/widgets/list_item.md @@ -27,13 +27,6 @@ of multiple `ListItem`s. The arrow keys can be used to navigate the list. |---------------|--------|---------|--------------------------------------| | `highlighted` | `bool` | `False` | True if this ListItem is highlighted | -## Messages - -### Selected - -The `ListItem.Selected` message is sent when the item is selected. - - - [x] Bubbles #### Attributes diff --git a/docs/widgets/list_view.md b/docs/widgets/list_view.md index a348eedbd..8bca015df 100644 --- a/docs/widgets/list_view.md +++ b/docs/widgets/list_view.md @@ -35,33 +35,9 @@ The example below shows an app with a simple `ListView`. ## Messages -### Highlighted +### ::: textual.widgets.ListView.Highlighted -The `ListView.Highlighted` message is emitted when the highlight changes. -This happens when you use the arrow keys on your keyboard and when you -click on a list item. - -- [x] Bubbles - -#### Attributes - -| attribute | type | purpose | -| --------- | ---------- | ------------------------------ | -| `item` | `ListItem` | The item that was highlighted. | - -### Selected - -The `ListView.Selected` message is emitted when a list item is selected. -You can select a list item by pressing ++enter++ while it is highlighted, -or by clicking on it. - -- [x] Bubbles - -#### Attributes - -| attribute | type | purpose | -| --------- | ---------- | --------------------------- | -| `item` | `ListItem` | The item that was selected. | +### ::: textual.widgets.ListView.Selected ## Bindings diff --git a/docs/widgets/tree.md b/docs/widgets/tree.md index ce476bd16..d35a7648d 100644 --- a/docs/widgets/tree.md +++ b/docs/widgets/tree.md @@ -32,44 +32,15 @@ A each tree widget has a "root" attribute which is an instance of a [TreeNode][t | `show_guides` | `bool` | `True` | Show guide lines between levels. | | `guide_depth` | `int` | `4` | Amount of indentation between parent and child. | - - ## Messages -### NodeSelected +### ::: textual.widgets.Tree.NodeCollapsed -The `Tree.NodeSelected` message is sent when the user selects a tree node. +### ::: textual.widgets.Tree.NodeExpanded +### ::: textual.widgets.Tree.NodeHighlighted -#### Attributes - -| attribute | type | purpose | -| --------- | ------------------------------------ | -------------- | -| `node` | [TreeNode][textual.widgets.TreeNode] | Selected node. | - - -### NodeExpanded - -The `Tree.NodeExpanded` message is sent when the user expands a node in the tree. - -#### Attributes - -| attribute | type | purpose | -| --------- | ------------------------------------ | -------------- | -| `node` | [TreeNode][textual.widgets.TreeNode] | Expanded node. | - - -### NodeCollapsed - - -The `Tree.NodeCollapsed` message is sent when the user expands a node in the tree. - - -#### Attributes - -| attribute | type | purpose | -| --------- | ------------------------------------ | --------------- | -| `node` | [TreeNode][textual.widgets.TreeNode] | Collapsed node. | +### ::: textual.widgets.Tree.NodeSelected ## Bindings From 9c83517fd3948924f1292e93b05666aa6fc0a461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 26 Jan 2023 13:57:15 +0000 Subject: [PATCH 23/93] Update poetry.lock. --- poetry.lock | 988 ++++------------------------------------------------ 1 file changed, 59 insertions(+), 929 deletions(-) diff --git a/poetry.lock b/poetry.lock index d9d7e8ef2..7cd75899a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -340,57 +340,57 @@ category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "coverage-7.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2a7f23bbaeb2a87f90f607730b45564076d870f1fb07b9318d0c21f36871932b"}, - {file = "coverage-7.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c18d47f314b950dbf24a41787ced1474e01ca816011925976d90a88b27c22b89"}, - {file = "coverage-7.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef14d75d86f104f03dea66c13188487151760ef25dd6b2dbd541885185f05f40"}, - {file = "coverage-7.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66e50680e888840c0995f2ad766e726ce71ca682e3c5f4eee82272c7671d38a2"}, - {file = "coverage-7.0.5-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9fed35ca8c6e946e877893bbac022e8563b94404a605af1d1e6accc7eb73289"}, - {file = "coverage-7.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d8d04e755934195bdc1db45ba9e040b8d20d046d04d6d77e71b3b34a8cc002d0"}, - {file = "coverage-7.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e109f1c9a3ece676597831874126555997c48f62bddbcace6ed17be3e372de8"}, - {file = "coverage-7.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0a1890fca2962c4f1ad16551d660b46ea77291fba2cc21c024cd527b9d9c8809"}, - {file = "coverage-7.0.5-cp310-cp310-win32.whl", hash = "sha256:be9fcf32c010da0ba40bf4ee01889d6c737658f4ddff160bd7eb9cac8f094b21"}, - {file = "coverage-7.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:cbfcba14a3225b055a28b3199c3d81cd0ab37d2353ffd7f6fd64844cebab31ad"}, - {file = "coverage-7.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:30b5fec1d34cc932c1bc04017b538ce16bf84e239378b8f75220478645d11fca"}, - {file = "coverage-7.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1caed2367b32cc80a2b7f58a9f46658218a19c6cfe5bc234021966dc3daa01f0"}, - {file = "coverage-7.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d254666d29540a72d17cc0175746cfb03d5123db33e67d1020e42dae611dc196"}, - {file = "coverage-7.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19245c249aa711d954623d94f23cc94c0fd65865661f20b7781210cb97c471c0"}, - {file = "coverage-7.0.5-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b05ed4b35bf6ee790832f68932baf1f00caa32283d66cc4d455c9e9d115aafc"}, - {file = "coverage-7.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:29de916ba1099ba2aab76aca101580006adfac5646de9b7c010a0f13867cba45"}, - {file = "coverage-7.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e057e74e53db78122a3979f908973e171909a58ac20df05c33998d52e6d35757"}, - {file = "coverage-7.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:411d4ff9d041be08fdfc02adf62e89c735b9468f6d8f6427f8a14b6bb0a85095"}, - {file = "coverage-7.0.5-cp311-cp311-win32.whl", hash = "sha256:52ab14b9e09ce052237dfe12d6892dd39b0401690856bcfe75d5baba4bfe2831"}, - {file = "coverage-7.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:1f66862d3a41674ebd8d1a7b6f5387fe5ce353f8719040a986551a545d7d83ea"}, - {file = "coverage-7.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b69522b168a6b64edf0c33ba53eac491c0a8f5cc94fa4337f9c6f4c8f2f5296c"}, - {file = "coverage-7.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436e103950d05b7d7f55e39beeb4d5be298ca3e119e0589c0227e6d0b01ee8c7"}, - {file = "coverage-7.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c56bec53d6e3154eaff6ea941226e7bd7cc0d99f9b3756c2520fc7a94e6d96"}, - {file = "coverage-7.0.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a38362528a9115a4e276e65eeabf67dcfaf57698e17ae388599568a78dcb029"}, - {file = "coverage-7.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f67472c09a0c7486e27f3275f617c964d25e35727af952869dd496b9b5b7f6a3"}, - {file = "coverage-7.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:220e3fa77d14c8a507b2d951e463b57a1f7810a6443a26f9b7591ef39047b1b2"}, - {file = "coverage-7.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ecb0f73954892f98611e183f50acdc9e21a4653f294dfbe079da73c6378a6f47"}, - {file = "coverage-7.0.5-cp37-cp37m-win32.whl", hash = "sha256:d8f3e2e0a1d6777e58e834fd5a04657f66affa615dae61dd67c35d1568c38882"}, - {file = "coverage-7.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9e662e6fc4f513b79da5d10a23edd2b87685815b337b1a30cd11307a6679148d"}, - {file = "coverage-7.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:790e4433962c9f454e213b21b0fd4b42310ade9c077e8edcb5113db0818450cb"}, - {file = "coverage-7.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49640bda9bda35b057b0e65b7c43ba706fa2335c9a9896652aebe0fa399e80e6"}, - {file = "coverage-7.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d66187792bfe56f8c18ba986a0e4ae44856b1c645336bd2c776e3386da91e1dd"}, - {file = "coverage-7.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:276f4cd0001cd83b00817c8db76730938b1ee40f4993b6a905f40a7278103b3a"}, - {file = "coverage-7.0.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95304068686545aa368b35dfda1cdfbbdbe2f6fe43de4a2e9baa8ebd71be46e2"}, - {file = "coverage-7.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:17e01dd8666c445025c29684d4aabf5a90dc6ef1ab25328aa52bedaa95b65ad7"}, - {file = "coverage-7.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea76dbcad0b7b0deb265d8c36e0801abcddf6cc1395940a24e3595288b405ca0"}, - {file = "coverage-7.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:50a6adc2be8edd7ee67d1abc3cd20678987c7b9d79cd265de55941e3d0d56499"}, - {file = "coverage-7.0.5-cp38-cp38-win32.whl", hash = "sha256:e4ce984133b888cc3a46867c8b4372c7dee9cee300335e2925e197bcd45b9e16"}, - {file = "coverage-7.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:4a950f83fd3f9bca23b77442f3a2b2ea4ac900944d8af9993743774c4fdc57af"}, - {file = "coverage-7.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c2155943896ac78b9b0fd910fb381186d0c345911f5333ee46ac44c8f0e43ab"}, - {file = "coverage-7.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:54f7e9705e14b2c9f6abdeb127c390f679f6dbe64ba732788d3015f7f76ef637"}, - {file = "coverage-7.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ee30375b409d9a7ea0f30c50645d436b6f5dfee254edffd27e45a980ad2c7f4"}, - {file = "coverage-7.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b78729038abea6a5df0d2708dce21e82073463b2d79d10884d7d591e0f385ded"}, - {file = "coverage-7.0.5-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13250b1f0bd023e0c9f11838bdeb60214dd5b6aaf8e8d2f110c7e232a1bff83b"}, - {file = "coverage-7.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c407b1950b2d2ffa091f4e225ca19a66a9bd81222f27c56bd12658fc5ca1209"}, - {file = "coverage-7.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c76a3075e96b9c9ff00df8b5f7f560f5634dffd1658bafb79eb2682867e94f78"}, - {file = "coverage-7.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f26648e1b3b03b6022b48a9b910d0ae209e2d51f50441db5dce5b530fad6d9b1"}, - {file = "coverage-7.0.5-cp39-cp39-win32.whl", hash = "sha256:ba3027deb7abf02859aca49c865ece538aee56dcb4871b4cced23ba4d5088904"}, - {file = "coverage-7.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:949844af60ee96a376aac1ded2a27e134b8c8d35cc006a52903fc06c24a3296f"}, - {file = "coverage-7.0.5-pp37.pp38.pp39-none-any.whl", hash = "sha256:b9727ac4f5cf2cbf87880a63870b5b9730a8ae3a4a360241a0fdaa2f71240ff0"}, - {file = "coverage-7.0.5.tar.gz", hash = "sha256:051afcbd6d2ac39298d62d340f94dbb6a1f31de06dfaf6fcef7b759dd3860c45"}, + {file = "coverage-7.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3b946bbcd5a8231383450b195cfb58cb01cbe7f8949f5758566b881df4b33baf"}, + {file = "coverage-7.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec8e767f13be637d056f7e07e61d089e555f719b387a7070154ad80a0ff31801"}, + {file = "coverage-7.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a5a5879a939cb84959d86869132b00176197ca561c664fc21478c1eee60d75"}, + {file = "coverage-7.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b643cb30821e7570c0aaf54feaf0bfb630b79059f85741843e9dc23f33aaca2c"}, + {file = "coverage-7.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32df215215f3af2c1617a55dbdfb403b772d463d54d219985ac7cd3bf124cada"}, + {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:33d1ae9d4079e05ac4cc1ef9e20c648f5afabf1a92adfaf2ccf509c50b85717f"}, + {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:29571503c37f2ef2138a306d23e7270687c0efb9cab4bd8038d609b5c2393a3a"}, + {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:63ffd21aa133ff48c4dff7adcc46b7ec8b565491bfc371212122dd999812ea1c"}, + {file = "coverage-7.1.0-cp310-cp310-win32.whl", hash = "sha256:4b14d5e09c656de5038a3f9bfe5228f53439282abcab87317c9f7f1acb280352"}, + {file = "coverage-7.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:8361be1c2c073919500b6601220a6f2f98ea0b6d2fec5014c1d9cfa23dd07038"}, + {file = "coverage-7.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:da9b41d4539eefd408c46725fb76ecba3a50a3367cafb7dea5f250d0653c1040"}, + {file = "coverage-7.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5b15ed7644ae4bee0ecf74fee95808dcc34ba6ace87e8dfbf5cb0dc20eab45a"}, + {file = "coverage-7.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d12d076582507ea460ea2a89a8c85cb558f83406c8a41dd641d7be9a32e1274f"}, + {file = "coverage-7.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2617759031dae1bf183c16cef8fcfb3de7617f394c813fa5e8e46e9b82d4222"}, + {file = "coverage-7.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4e4881fa9e9667afcc742f0c244d9364d197490fbc91d12ac3b5de0bf2df146"}, + {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9d58885215094ab4a86a6aef044e42994a2bd76a446dc59b352622655ba6621b"}, + {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ffeeb38ee4a80a30a6877c5c4c359e5498eec095878f1581453202bfacc8fbc2"}, + {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3baf5f126f30781b5e93dbefcc8271cb2491647f8283f20ac54d12161dff080e"}, + {file = "coverage-7.1.0-cp311-cp311-win32.whl", hash = "sha256:ded59300d6330be27bc6cf0b74b89ada58069ced87c48eaf9344e5e84b0072f7"}, + {file = "coverage-7.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:6a43c7823cd7427b4ed763aa7fb63901ca8288591323b58c9cd6ec31ad910f3c"}, + {file = "coverage-7.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a726d742816cb3a8973c8c9a97539c734b3a309345236cd533c4883dda05b8d"}, + {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc7c85a150501286f8b56bd8ed3aa4093f4b88fb68c0843d21ff9656f0009d6a"}, + {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5b4198d85a3755d27e64c52f8c95d6333119e49fd001ae5798dac872c95e0f8"}, + {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddb726cb861c3117a553f940372a495fe1078249ff5f8a5478c0576c7be12050"}, + {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:51b236e764840a6df0661b67e50697aaa0e7d4124ca95e5058fa3d7cbc240b7c"}, + {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7ee5c9bb51695f80878faaa5598040dd6c9e172ddcf490382e8aedb8ec3fec8d"}, + {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c31b75ae466c053a98bf26843563b3b3517b8f37da4d47b1c582fdc703112bc3"}, + {file = "coverage-7.1.0-cp37-cp37m-win32.whl", hash = "sha256:3b155caf3760408d1cb903b21e6a97ad4e2bdad43cbc265e3ce0afb8e0057e73"}, + {file = "coverage-7.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2a60d6513781e87047c3e630b33b4d1e89f39836dac6e069ffee28c4786715f5"}, + {file = "coverage-7.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2cba5c6db29ce991029b5e4ac51eb36774458f0a3b8d3137241b32d1bb91f06"}, + {file = "coverage-7.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beeb129cacea34490ffd4d6153af70509aa3cda20fdda2ea1a2be870dfec8d52"}, + {file = "coverage-7.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c45948f613d5d18c9ec5eaa203ce06a653334cf1bd47c783a12d0dd4fd9c851"}, + {file = "coverage-7.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef382417db92ba23dfb5864a3fc9be27ea4894e86620d342a116b243ade5d35d"}, + {file = "coverage-7.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c7c0d0827e853315c9bbd43c1162c006dd808dbbe297db7ae66cd17b07830f0"}, + {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e5cdbb5cafcedea04924568d990e20ce7f1945a1dd54b560f879ee2d57226912"}, + {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9817733f0d3ea91bea80de0f79ef971ae94f81ca52f9b66500c6a2fea8e4b4f8"}, + {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:218fe982371ac7387304153ecd51205f14e9d731b34fb0568181abaf7b443ba0"}, + {file = "coverage-7.1.0-cp38-cp38-win32.whl", hash = "sha256:04481245ef966fbd24ae9b9e537ce899ae584d521dfbe78f89cad003c38ca2ab"}, + {file = "coverage-7.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:8ae125d1134bf236acba8b83e74c603d1b30e207266121e76484562bc816344c"}, + {file = "coverage-7.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2bf1d5f2084c3932b56b962a683074a3692bce7cabd3aa023c987a2a8e7612f6"}, + {file = "coverage-7.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:98b85dd86514d889a2e3dd22ab3c18c9d0019e696478391d86708b805f4ea0fa"}, + {file = "coverage-7.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38da2db80cc505a611938d8624801158e409928b136c8916cd2e203970dde4dc"}, + {file = "coverage-7.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3164d31078fa9efe406e198aecd2a02d32a62fecbdef74f76dad6a46c7e48311"}, + {file = "coverage-7.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db61a79c07331e88b9a9974815c075fbd812bc9dbc4dc44b366b5368a2936063"}, + {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ccb092c9ede70b2517a57382a601619d20981f56f440eae7e4d7eaafd1d1d09"}, + {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:33ff26d0f6cc3ca8de13d14fde1ff8efe1456b53e3f0273e63cc8b3c84a063d8"}, + {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d47dd659a4ee952e90dc56c97d78132573dc5c7b09d61b416a9deef4ebe01a0c"}, + {file = "coverage-7.1.0-cp39-cp39-win32.whl", hash = "sha256:d248cd4a92065a4d4543b8331660121b31c4148dd00a691bfb7a5cdc7483cfa4"}, + {file = "coverage-7.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7ed681b0f8e8bcbbffa58ba26fcf5dbc8f79e7997595bf071ed5430d8c08d6f3"}, + {file = "coverage-7.1.0-pp37.pp38.pp39-none-any.whl", hash = "sha256:755e89e32376c850f826c425ece2c35a4fc266c081490eb0a841e7c1cb0d3bda"}, + {file = "coverage-7.1.0.tar.gz", hash = "sha256:10188fe543560ec4874f974b5305cd1a8bdcfa885ee00ea3a03733464c4ca265"}, ] [package.extras] @@ -660,8 +660,8 @@ category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "identify-2.5.13-py2.py3-none-any.whl", hash = "sha256:8aa48ce56e38c28b6faa9f261075dea0a942dfbb42b341b4e711896cbb40f3f7"}, - {file = "identify-2.5.13.tar.gz", hash = "sha256:abb546bca6f470228785338a01b539de8a85bbf46491250ae03363956d8ebb10"}, + {file = "identify-2.5.15-py2.py3-none-any.whl", hash = "sha256:1f4b36c5f50f3f950864b2a047308743f064eaa6f6645da5e5c780d1c7125487"}, + {file = "identify-2.5.15.tar.gz", hash = "sha256:c22aa206f47cc40486ecf585d27ad5f40adbfc494a3fa41dc3ed0499a23b123f"}, ] [package.extras] @@ -981,8 +981,8 @@ category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "mkdocstrings-0.19.1-py3-none-any.whl", hash = "sha256:32a38d88f67f65b264184ea71290f9332db750d189dea4200cbbe408d304c261"}, - {file = "mkdocstrings-0.19.1.tar.gz", hash = "sha256:d1037cacb4b522c1e8c164ed5d00d724a82e49dcee0af80db8fb67b384faeef9"}, + {file = "mkdocstrings-0.20.0-py3-none-any.whl", hash = "sha256:f17fc2c4f760ec302b069075ef9e31045aa6372ca91d2f35ded3adba8e25a472"}, + {file = "mkdocstrings-0.20.0.tar.gz", hash = "sha256:c757f4f646d4f939491d6bc9256bfe33e36c5f8026392f49eaa351d241c838e5"}, ] [package.dependencies] @@ -1272,8 +1272,8 @@ category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pathspec-0.10.3-py3-none-any.whl", hash = "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6"}, - {file = "pathspec-0.10.3.tar.gz", hash = "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"}, + {file = "pathspec-0.11.0-py3-none-any.whl", hash = "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229"}, + {file = "pathspec-0.11.0.tar.gz", hash = "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"}, ] [[package]] @@ -1677,877 +1677,7 @@ description = "Travel through time in your tests." category = "dev" optional = false python-versions = ">=3.7" - -[package.dependencies] -python-dateutil = "*" - -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "typed-ast" -version = "1.5.4" -description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "typing-extensions" -version = "4.4.0" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "tzdata" -version = "2022.7" -description = "Provider of IANA time zone data" -category = "dev" -optional = false -python-versions = ">=2" - -[[package]] -name = "urllib3" -version = "1.26.14" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "virtualenv" -version = "20.17.1" -description = "Virtual Python Environment builder" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -distlib = ">=0.3.6,<1" -filelock = ">=3.4.1,<4" -importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.8\""} -platformdirs = ">=2.4,<3" - -[package.extras] -docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"] -testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] - -[[package]] -name = "watchdog" -version = "2.2.1" -description = "Filesystem events monitoring" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -watchmedo = ["PyYAML (>=3.10)"] - -[[package]] -name = "yarl" -version = "1.8.2" -description = "Yet another URL library" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" -typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} - -[[package]] -name = "zipp" -version = "3.11.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - -[extras] -dev = ["aiohttp", "click", "msgpack"] - -[metadata] -lock-version = "1.1" -python-versions = "^3.7" -content-hash = "0e3bcf48b37c16096a3c2b2f7d3f548494f9a22ebdee2e2c5d8ac74b80ab344e" - -[metadata.files] -aiohttp = [ - {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ba71c9b4dcbb16212f334126cc3d8beb6af377f6703d9dc2d9fb3874fd667ee9"}, - {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d24b8bb40d5c61ef2d9b6a8f4528c2f17f1c5d2d31fed62ec860f6006142e83e"}, - {file = "aiohttp-3.8.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f88df3a83cf9df566f171adba39d5bd52814ac0b94778d2448652fc77f9eb491"}, - {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97decbb3372d4b69e4d4c8117f44632551c692bb1361b356a02b97b69e18a62"}, - {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:309aa21c1d54b8ef0723181d430347d7452daaff93e8e2363db8e75c72c2fb2d"}, - {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad5383a67514e8e76906a06741febd9126fc7c7ff0f599d6fcce3e82b80d026f"}, - {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20acae4f268317bb975671e375493dbdbc67cddb5f6c71eebdb85b34444ac46b"}, - {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05a3c31c6d7cd08c149e50dc7aa2568317f5844acd745621983380597f027a18"}, - {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d6f76310355e9fae637c3162936e9504b4767d5c52ca268331e2756e54fd4ca5"}, - {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:256deb4b29fe5e47893fa32e1de2d73c3afe7407738bd3c63829874661d4822d"}, - {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5c59fcd80b9049b49acd29bd3598cada4afc8d8d69bd4160cd613246912535d7"}, - {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:059a91e88f2c00fe40aed9031b3606c3f311414f86a90d696dd982e7aec48142"}, - {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2feebbb6074cdbd1ac276dbd737b40e890a1361b3cc30b74ac2f5e24aab41f7b"}, - {file = "aiohttp-3.8.3-cp310-cp310-win32.whl", hash = "sha256:5bf651afd22d5f0c4be16cf39d0482ea494f5c88f03e75e5fef3a85177fecdeb"}, - {file = "aiohttp-3.8.3-cp310-cp310-win_amd64.whl", hash = "sha256:653acc3880459f82a65e27bd6526e47ddf19e643457d36a2250b85b41a564715"}, - {file = "aiohttp-3.8.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:86fc24e58ecb32aee09f864cb11bb91bc4c1086615001647dbfc4dc8c32f4008"}, - {file = "aiohttp-3.8.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75e14eac916f024305db517e00a9252714fce0abcb10ad327fb6dcdc0d060f1d"}, - {file = "aiohttp-3.8.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d1fde0f44029e02d02d3993ad55ce93ead9bb9b15c6b7ccd580f90bd7e3de476"}, - {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ab94426ddb1ecc6a0b601d832d5d9d421820989b8caa929114811369673235c"}, - {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89d2e02167fa95172c017732ed7725bc8523c598757f08d13c5acca308e1a061"}, - {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02f9a2c72fc95d59b881cf38a4b2be9381b9527f9d328771e90f72ac76f31ad8"}, - {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c7149272fb5834fc186328e2c1fa01dda3e1fa940ce18fded6d412e8f2cf76d"}, - {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:512bd5ab136b8dc0ffe3fdf2dfb0c4b4f49c8577f6cae55dca862cd37a4564e2"}, - {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7018ecc5fe97027214556afbc7c502fbd718d0740e87eb1217b17efd05b3d276"}, - {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:88c70ed9da9963d5496d38320160e8eb7e5f1886f9290475a881db12f351ab5d"}, - {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:da22885266bbfb3f78218dc40205fed2671909fbd0720aedba39b4515c038091"}, - {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:e65bc19919c910127c06759a63747ebe14f386cda573d95bcc62b427ca1afc73"}, - {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:08c78317e950e0762c2983f4dd58dc5e6c9ff75c8a0efeae299d363d439c8e34"}, - {file = "aiohttp-3.8.3-cp311-cp311-win32.whl", hash = "sha256:45d88b016c849d74ebc6f2b6e8bc17cabf26e7e40c0661ddd8fae4c00f015697"}, - {file = "aiohttp-3.8.3-cp311-cp311-win_amd64.whl", hash = "sha256:96372fc29471646b9b106ee918c8eeb4cca423fcbf9a34daa1b93767a88a2290"}, - {file = "aiohttp-3.8.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c971bf3786b5fad82ce5ad570dc6ee420f5b12527157929e830f51c55dc8af77"}, - {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff25f48fc8e623d95eca0670b8cc1469a83783c924a602e0fbd47363bb54aaca"}, - {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e381581b37db1db7597b62a2e6b8b57c3deec95d93b6d6407c5b61ddc98aca6d"}, - {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db19d60d846283ee275d0416e2a23493f4e6b6028825b51290ac05afc87a6f97"}, - {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25892c92bee6d9449ffac82c2fe257f3a6f297792cdb18ad784737d61e7a9a85"}, - {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:398701865e7a9565d49189f6c90868efaca21be65c725fc87fc305906be915da"}, - {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4a4fbc769ea9b6bd97f4ad0b430a6807f92f0e5eb020f1e42ece59f3ecfc4585"}, - {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:b29bfd650ed8e148f9c515474a6ef0ba1090b7a8faeee26b74a8ff3b33617502"}, - {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:1e56b9cafcd6531bab5d9b2e890bb4937f4165109fe98e2b98ef0dcfcb06ee9d"}, - {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ec40170327d4a404b0d91855d41bfe1fe4b699222b2b93e3d833a27330a87a6d"}, - {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2df5f139233060578d8c2c975128fb231a89ca0a462b35d4b5fcf7c501ebdbe1"}, - {file = "aiohttp-3.8.3-cp36-cp36m-win32.whl", hash = "sha256:f973157ffeab5459eefe7b97a804987876dd0a55570b8fa56b4e1954bf11329b"}, - {file = "aiohttp-3.8.3-cp36-cp36m-win_amd64.whl", hash = "sha256:437399385f2abcd634865705bdc180c8314124b98299d54fe1d4c8990f2f9494"}, - {file = "aiohttp-3.8.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:09e28f572b21642128ef31f4e8372adb6888846f32fecb288c8b0457597ba61a"}, - {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f3553510abdbec67c043ca85727396ceed1272eef029b050677046d3387be8d"}, - {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e168a7560b7c61342ae0412997b069753f27ac4862ec7867eff74f0fe4ea2ad9"}, - {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db4c979b0b3e0fa7e9e69ecd11b2b3174c6963cebadeecfb7ad24532ffcdd11a"}, - {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e164e0a98e92d06da343d17d4e9c4da4654f4a4588a20d6c73548a29f176abe2"}, - {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8a78079d9a39ca9ca99a8b0ac2fdc0c4d25fc80c8a8a82e5c8211509c523363"}, - {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:21b30885a63c3f4ff5b77a5d6caf008b037cb521a5f33eab445dc566f6d092cc"}, - {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4b0f30372cef3fdc262f33d06e7b411cd59058ce9174ef159ad938c4a34a89da"}, - {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8135fa153a20d82ffb64f70a1b5c2738684afa197839b34cc3e3c72fa88d302c"}, - {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:ad61a9639792fd790523ba072c0555cd6be5a0baf03a49a5dd8cfcf20d56df48"}, - {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:978b046ca728073070e9abc074b6299ebf3501e8dee5e26efacb13cec2b2dea0"}, - {file = "aiohttp-3.8.3-cp37-cp37m-win32.whl", hash = "sha256:0d2c6d8c6872df4a6ec37d2ede71eff62395b9e337b4e18efd2177de883a5033"}, - {file = "aiohttp-3.8.3-cp37-cp37m-win_amd64.whl", hash = "sha256:21d69797eb951f155026651f7e9362877334508d39c2fc37bd04ff55b2007091"}, - {file = "aiohttp-3.8.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ca9af5f8f5812d475c5259393f52d712f6d5f0d7fdad9acdb1107dd9e3cb7eb"}, - {file = "aiohttp-3.8.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d90043c1882067f1bd26196d5d2db9aa6d268def3293ed5fb317e13c9413ea4"}, - {file = "aiohttp-3.8.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d737fc67b9a970f3234754974531dc9afeea11c70791dcb7db53b0cf81b79784"}, - {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf909ea0a3fc9596e40d55d8000702a85e27fd578ff41a5500f68f20fd32e6c"}, - {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5835f258ca9f7c455493a57ee707b76d2d9634d84d5d7f62e77be984ea80b849"}, - {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da37dcfbf4b7f45d80ee386a5f81122501ec75672f475da34784196690762f4b"}, - {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87f44875f2804bc0511a69ce44a9595d5944837a62caecc8490bbdb0e18b1342"}, - {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:527b3b87b24844ea7865284aabfab08eb0faf599b385b03c2aa91fc6edd6e4b6"}, - {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5ba88df9aa5e2f806650fcbeedbe4f6e8736e92fc0e73b0400538fd25a4dd96"}, - {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e7b8813be97cab8cb52b1375f41f8e6804f6507fe4660152e8ca5c48f0436017"}, - {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:2dea10edfa1a54098703cb7acaa665c07b4e7568472a47f4e64e6319d3821ccf"}, - {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:713d22cd9643ba9025d33c4af43943c7a1eb8547729228de18d3e02e278472b6"}, - {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2d252771fc85e0cf8da0b823157962d70639e63cb9b578b1dec9868dd1f4f937"}, - {file = "aiohttp-3.8.3-cp38-cp38-win32.whl", hash = "sha256:66bd5f950344fb2b3dbdd421aaa4e84f4411a1a13fca3aeb2bcbe667f80c9f76"}, - {file = "aiohttp-3.8.3-cp38-cp38-win_amd64.whl", hash = "sha256:84b14f36e85295fe69c6b9789b51a0903b774046d5f7df538176516c3e422446"}, - {file = "aiohttp-3.8.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16c121ba0b1ec2b44b73e3a8a171c4f999b33929cd2397124a8c7fcfc8cd9e06"}, - {file = "aiohttp-3.8.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8d6aaa4e7155afaf994d7924eb290abbe81a6905b303d8cb61310a2aba1c68ba"}, - {file = "aiohttp-3.8.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43046a319664a04b146f81b40e1545d4c8ac7b7dd04c47e40bf09f65f2437346"}, - {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599418aaaf88a6d02a8c515e656f6faf3d10618d3dd95866eb4436520096c84b"}, - {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92a2964319d359f494f16011e23434f6f8ef0434acd3cf154a6b7bec511e2fb7"}, - {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73a4131962e6d91109bca6536416aa067cf6c4efb871975df734f8d2fd821b37"}, - {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598adde339d2cf7d67beaccda3f2ce7c57b3b412702f29c946708f69cf8222aa"}, - {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75880ed07be39beff1881d81e4a907cafb802f306efd6d2d15f2b3c69935f6fb"}, - {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0239da9fbafd9ff82fd67c16704a7d1bccf0d107a300e790587ad05547681c8"}, - {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4e3a23ec214e95c9fe85a58470b660efe6534b83e6cbe38b3ed52b053d7cb6ad"}, - {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:47841407cc89a4b80b0c52276f3cc8138bbbfba4b179ee3acbd7d77ae33f7ac4"}, - {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:54d107c89a3ebcd13228278d68f1436d3f33f2dd2af5415e3feaeb1156e1a62c"}, - {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c37c5cce780349d4d51739ae682dec63573847a2a8dcb44381b174c3d9c8d403"}, - {file = "aiohttp-3.8.3-cp39-cp39-win32.whl", hash = "sha256:f178d2aadf0166be4df834c4953da2d7eef24719e8aec9a65289483eeea9d618"}, - {file = "aiohttp-3.8.3-cp39-cp39-win_amd64.whl", hash = "sha256:88e5be56c231981428f4f506c68b6a46fa25c4123a2e86d156c58a8369d31ab7"}, - {file = "aiohttp-3.8.3.tar.gz", hash = "sha256:3828fb41b7203176b82fe5d699e0d845435f2374750a44b480ea6b930f6be269"}, -] -aiosignal = [ - {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, - {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, -] -anyio = [ - {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, - {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, -] -async-timeout = [ - {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, - {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, -] -asynctest = [ - {file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"}, - {file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"}, -] -attrs = [ - {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, - {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, -] -black = [ - {file = "black-22.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce957f1d6b78a8a231b18e0dd2d94a33d2ba738cd88a7fe64f53f659eea49fdd"}, - {file = "black-22.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5107ea36b2b61917956d018bd25129baf9ad1125e39324a9b18248d362156a27"}, - {file = "black-22.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8166b7bfe5dcb56d325385bd1d1e0f635f24aae14b3ae437102dedc0c186747"}, - {file = "black-22.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd82842bb272297503cbec1a2600b6bfb338dae017186f8f215c8958f8acf869"}, - {file = "black-22.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d839150f61d09e7217f52917259831fe2b689f5c8e5e32611736351b89bb2a90"}, - {file = "black-22.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a05da0430bd5ced89176db098567973be52ce175a55677436a271102d7eaa3fe"}, - {file = "black-22.8.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a098a69a02596e1f2a58a2a1c8d5a05d5a74461af552b371e82f9fa4ada8342"}, - {file = "black-22.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5594efbdc35426e35a7defa1ea1a1cb97c7dbd34c0e49af7fb593a36bd45edab"}, - {file = "black-22.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a983526af1bea1e4cf6768e649990f28ee4f4137266921c2c3cee8116ae42ec3"}, - {file = "black-22.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b2c25f8dea5e8444bdc6788a2f543e1fb01494e144480bc17f806178378005e"}, - {file = "black-22.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:78dd85caaab7c3153054756b9fe8c611efa63d9e7aecfa33e533060cb14b6d16"}, - {file = "black-22.8.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:cea1b2542d4e2c02c332e83150e41e3ca80dc0fb8de20df3c5e98e242156222c"}, - {file = "black-22.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5b879eb439094751185d1cfdca43023bc6786bd3c60372462b6f051efa6281a5"}, - {file = "black-22.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a12e4e1353819af41df998b02c6742643cfef58282915f781d0e4dd7a200411"}, - {file = "black-22.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3a73f66b6d5ba7288cd5d6dad9b4c9b43f4e8a4b789a94bf5abfb878c663eb3"}, - {file = "black-22.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:e981e20ec152dfb3e77418fb616077937378b322d7b26aa1ff87717fb18b4875"}, - {file = "black-22.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8ce13ffed7e66dda0da3e0b2eb1bdfc83f5812f66e09aca2b0978593ed636b6c"}, - {file = "black-22.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:32a4b17f644fc288c6ee2bafdf5e3b045f4eff84693ac069d87b1a347d861497"}, - {file = "black-22.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ad827325a3a634bae88ae7747db1a395d5ee02cf05d9aa7a9bd77dfb10e940c"}, - {file = "black-22.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53198e28a1fb865e9fe97f88220da2e44df6da82b18833b588b1883b16bb5d41"}, - {file = "black-22.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:bc4d4123830a2d190e9cc42a2e43570f82ace35c3aeb26a512a2102bce5af7ec"}, - {file = "black-22.8.0-py3-none-any.whl", hash = "sha256:d2c21d439b2baf7aa80d6dd4e3659259be64c6f49dfd0f32091063db0e006db4"}, - {file = "black-22.8.0.tar.gz", hash = "sha256:792f7eb540ba9a17e8656538701d3eb1afcb134e3b45b71f20b25c77a8db7e6e"}, -] -cached-property = [ - {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, - {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, -] -certifi = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, -] -cfgv = [ - {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, - {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, -] -charset-normalizer = [ - {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, - {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, -] -click = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, -] -colorama = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -colored = [ - {file = "colored-1.4.4.tar.gz", hash = "sha256:04ff4d4dd514274fe3b99a21bb52fb96f2688c01e93fba7bef37221e7cb56ce0"}, -] -coverage = [ - {file = "coverage-7.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2a7f23bbaeb2a87f90f607730b45564076d870f1fb07b9318d0c21f36871932b"}, - {file = "coverage-7.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c18d47f314b950dbf24a41787ced1474e01ca816011925976d90a88b27c22b89"}, - {file = "coverage-7.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef14d75d86f104f03dea66c13188487151760ef25dd6b2dbd541885185f05f40"}, - {file = "coverage-7.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66e50680e888840c0995f2ad766e726ce71ca682e3c5f4eee82272c7671d38a2"}, - {file = "coverage-7.0.5-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9fed35ca8c6e946e877893bbac022e8563b94404a605af1d1e6accc7eb73289"}, - {file = "coverage-7.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d8d04e755934195bdc1db45ba9e040b8d20d046d04d6d77e71b3b34a8cc002d0"}, - {file = "coverage-7.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e109f1c9a3ece676597831874126555997c48f62bddbcace6ed17be3e372de8"}, - {file = "coverage-7.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0a1890fca2962c4f1ad16551d660b46ea77291fba2cc21c024cd527b9d9c8809"}, - {file = "coverage-7.0.5-cp310-cp310-win32.whl", hash = "sha256:be9fcf32c010da0ba40bf4ee01889d6c737658f4ddff160bd7eb9cac8f094b21"}, - {file = "coverage-7.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:cbfcba14a3225b055a28b3199c3d81cd0ab37d2353ffd7f6fd64844cebab31ad"}, - {file = "coverage-7.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:30b5fec1d34cc932c1bc04017b538ce16bf84e239378b8f75220478645d11fca"}, - {file = "coverage-7.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1caed2367b32cc80a2b7f58a9f46658218a19c6cfe5bc234021966dc3daa01f0"}, - {file = "coverage-7.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d254666d29540a72d17cc0175746cfb03d5123db33e67d1020e42dae611dc196"}, - {file = "coverage-7.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19245c249aa711d954623d94f23cc94c0fd65865661f20b7781210cb97c471c0"}, - {file = "coverage-7.0.5-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b05ed4b35bf6ee790832f68932baf1f00caa32283d66cc4d455c9e9d115aafc"}, - {file = "coverage-7.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:29de916ba1099ba2aab76aca101580006adfac5646de9b7c010a0f13867cba45"}, - {file = "coverage-7.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e057e74e53db78122a3979f908973e171909a58ac20df05c33998d52e6d35757"}, - {file = "coverage-7.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:411d4ff9d041be08fdfc02adf62e89c735b9468f6d8f6427f8a14b6bb0a85095"}, - {file = "coverage-7.0.5-cp311-cp311-win32.whl", hash = "sha256:52ab14b9e09ce052237dfe12d6892dd39b0401690856bcfe75d5baba4bfe2831"}, - {file = "coverage-7.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:1f66862d3a41674ebd8d1a7b6f5387fe5ce353f8719040a986551a545d7d83ea"}, - {file = "coverage-7.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b69522b168a6b64edf0c33ba53eac491c0a8f5cc94fa4337f9c6f4c8f2f5296c"}, - {file = "coverage-7.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436e103950d05b7d7f55e39beeb4d5be298ca3e119e0589c0227e6d0b01ee8c7"}, - {file = "coverage-7.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c56bec53d6e3154eaff6ea941226e7bd7cc0d99f9b3756c2520fc7a94e6d96"}, - {file = "coverage-7.0.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a38362528a9115a4e276e65eeabf67dcfaf57698e17ae388599568a78dcb029"}, - {file = "coverage-7.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f67472c09a0c7486e27f3275f617c964d25e35727af952869dd496b9b5b7f6a3"}, - {file = "coverage-7.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:220e3fa77d14c8a507b2d951e463b57a1f7810a6443a26f9b7591ef39047b1b2"}, - {file = "coverage-7.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ecb0f73954892f98611e183f50acdc9e21a4653f294dfbe079da73c6378a6f47"}, - {file = "coverage-7.0.5-cp37-cp37m-win32.whl", hash = "sha256:d8f3e2e0a1d6777e58e834fd5a04657f66affa615dae61dd67c35d1568c38882"}, - {file = "coverage-7.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9e662e6fc4f513b79da5d10a23edd2b87685815b337b1a30cd11307a6679148d"}, - {file = "coverage-7.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:790e4433962c9f454e213b21b0fd4b42310ade9c077e8edcb5113db0818450cb"}, - {file = "coverage-7.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49640bda9bda35b057b0e65b7c43ba706fa2335c9a9896652aebe0fa399e80e6"}, - {file = "coverage-7.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d66187792bfe56f8c18ba986a0e4ae44856b1c645336bd2c776e3386da91e1dd"}, - {file = "coverage-7.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:276f4cd0001cd83b00817c8db76730938b1ee40f4993b6a905f40a7278103b3a"}, - {file = "coverage-7.0.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95304068686545aa368b35dfda1cdfbbdbe2f6fe43de4a2e9baa8ebd71be46e2"}, - {file = "coverage-7.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:17e01dd8666c445025c29684d4aabf5a90dc6ef1ab25328aa52bedaa95b65ad7"}, - {file = "coverage-7.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea76dbcad0b7b0deb265d8c36e0801abcddf6cc1395940a24e3595288b405ca0"}, - {file = "coverage-7.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:50a6adc2be8edd7ee67d1abc3cd20678987c7b9d79cd265de55941e3d0d56499"}, - {file = "coverage-7.0.5-cp38-cp38-win32.whl", hash = "sha256:e4ce984133b888cc3a46867c8b4372c7dee9cee300335e2925e197bcd45b9e16"}, - {file = "coverage-7.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:4a950f83fd3f9bca23b77442f3a2b2ea4ac900944d8af9993743774c4fdc57af"}, - {file = "coverage-7.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c2155943896ac78b9b0fd910fb381186d0c345911f5333ee46ac44c8f0e43ab"}, - {file = "coverage-7.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:54f7e9705e14b2c9f6abdeb127c390f679f6dbe64ba732788d3015f7f76ef637"}, - {file = "coverage-7.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ee30375b409d9a7ea0f30c50645d436b6f5dfee254edffd27e45a980ad2c7f4"}, - {file = "coverage-7.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b78729038abea6a5df0d2708dce21e82073463b2d79d10884d7d591e0f385ded"}, - {file = "coverage-7.0.5-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13250b1f0bd023e0c9f11838bdeb60214dd5b6aaf8e8d2f110c7e232a1bff83b"}, - {file = "coverage-7.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c407b1950b2d2ffa091f4e225ca19a66a9bd81222f27c56bd12658fc5ca1209"}, - {file = "coverage-7.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c76a3075e96b9c9ff00df8b5f7f560f5634dffd1658bafb79eb2682867e94f78"}, - {file = "coverage-7.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f26648e1b3b03b6022b48a9b910d0ae209e2d51f50441db5dce5b530fad6d9b1"}, - {file = "coverage-7.0.5-cp39-cp39-win32.whl", hash = "sha256:ba3027deb7abf02859aca49c865ece538aee56dcb4871b4cced23ba4d5088904"}, - {file = "coverage-7.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:949844af60ee96a376aac1ded2a27e134b8c8d35cc006a52903fc06c24a3296f"}, - {file = "coverage-7.0.5-pp37.pp38.pp39-none-any.whl", hash = "sha256:b9727ac4f5cf2cbf87880a63870b5b9730a8ae3a4a360241a0fdaa2f71240ff0"}, - {file = "coverage-7.0.5.tar.gz", hash = "sha256:051afcbd6d2ac39298d62d340f94dbb6a1f31de06dfaf6fcef7b759dd3860c45"}, -] -distlib = [ - {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, - {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, -] -exceptiongroup = [ - {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, - {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, -] -filelock = [ - {file = "filelock-3.9.0-py3-none-any.whl", hash = "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d"}, - {file = "filelock-3.9.0.tar.gz", hash = "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de"}, -] -frozenlist = [ - {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff8bf625fe85e119553b5383ba0fb6aa3d0ec2ae980295aaefa552374926b3f4"}, - {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dfbac4c2dfcc082fcf8d942d1e49b6aa0766c19d3358bd86e2000bf0fa4a9cf0"}, - {file = "frozenlist-1.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b1c63e8d377d039ac769cd0926558bb7068a1f7abb0f003e3717ee003ad85530"}, - {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fdfc24dcfce5b48109867c13b4cb15e4660e7bd7661741a391f821f23dfdca7"}, - {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c926450857408e42f0bbc295e84395722ce74bae69a3b2aa2a65fe22cb14b99"}, - {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1841e200fdafc3d51f974d9d377c079a0694a8f06de2e67b48150328d66d5483"}, - {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f470c92737afa7d4c3aacc001e335062d582053d4dbe73cda126f2d7031068dd"}, - {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:783263a4eaad7c49983fe4b2e7b53fa9770c136c270d2d4bbb6d2192bf4d9caf"}, - {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:924620eef691990dfb56dc4709f280f40baee568c794b5c1885800c3ecc69816"}, - {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae4dc05c465a08a866b7a1baf360747078b362e6a6dbeb0c57f234db0ef88ae0"}, - {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bed331fe18f58d844d39ceb398b77d6ac0b010d571cba8267c2e7165806b00ce"}, - {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:02c9ac843e3390826a265e331105efeab489ffaf4dd86384595ee8ce6d35ae7f"}, - {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9545a33965d0d377b0bc823dcabf26980e77f1b6a7caa368a365a9497fb09420"}, - {file = "frozenlist-1.3.3-cp310-cp310-win32.whl", hash = "sha256:d5cd3ab21acbdb414bb6c31958d7b06b85eeb40f66463c264a9b343a4e238642"}, - {file = "frozenlist-1.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:b756072364347cb6aa5b60f9bc18e94b2f79632de3b0190253ad770c5df17db1"}, - {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b4395e2f8d83fbe0c627b2b696acce67868793d7d9750e90e39592b3626691b7"}, - {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14143ae966a6229350021384870458e4777d1eae4c28d1a7aa47f24d030e6678"}, - {file = "frozenlist-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d8860749e813a6f65bad8285a0520607c9500caa23fea6ee407e63debcdbef6"}, - {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23d16d9f477bb55b6154654e0e74557040575d9d19fe78a161bd33d7d76808e8"}, - {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb82dbba47a8318e75f679690190c10a5e1f447fbf9df41cbc4c3afd726d88cb"}, - {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9309869032abb23d196cb4e4db574232abe8b8be1339026f489eeb34a4acfd91"}, - {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a97b4fe50b5890d36300820abd305694cb865ddb7885049587a5678215782a6b"}, - {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c188512b43542b1e91cadc3c6c915a82a5eb95929134faf7fd109f14f9892ce4"}, - {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:303e04d422e9b911a09ad499b0368dc551e8c3cd15293c99160c7f1f07b59a48"}, - {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0771aed7f596c7d73444c847a1c16288937ef988dc04fb9f7be4b2aa91db609d"}, - {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:66080ec69883597e4d026f2f71a231a1ee9887835902dbe6b6467d5a89216cf6"}, - {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:41fe21dc74ad3a779c3d73a2786bdf622ea81234bdd4faf90b8b03cad0c2c0b4"}, - {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f20380df709d91525e4bee04746ba612a4df0972c1b8f8e1e8af997e678c7b81"}, - {file = "frozenlist-1.3.3-cp311-cp311-win32.whl", hash = "sha256:f30f1928162e189091cf4d9da2eac617bfe78ef907a761614ff577ef4edfb3c8"}, - {file = "frozenlist-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:a6394d7dadd3cfe3f4b3b186e54d5d8504d44f2d58dcc89d693698e8b7132b32"}, - {file = "frozenlist-1.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8df3de3a9ab8325f94f646609a66cbeeede263910c5c0de0101079ad541af332"}, - {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0693c609e9742c66ba4870bcee1ad5ff35462d5ffec18710b4ac89337ff16e27"}, - {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd4210baef299717db0a600d7a3cac81d46ef0e007f88c9335db79f8979c0d3d"}, - {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:394c9c242113bfb4b9aa36e2b80a05ffa163a30691c7b5a29eba82e937895d5e"}, - {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6327eb8e419f7d9c38f333cde41b9ae348bec26d840927332f17e887a8dcb70d"}, - {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e24900aa13212e75e5b366cb9065e78bbf3893d4baab6052d1aca10d46d944c"}, - {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3843f84a6c465a36559161e6c59dce2f2ac10943040c2fd021cfb70d58c4ad56"}, - {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:84610c1502b2461255b4c9b7d5e9c48052601a8957cd0aea6ec7a7a1e1fb9420"}, - {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c21b9aa40e08e4f63a2f92ff3748e6b6c84d717d033c7b3438dd3123ee18f70e"}, - {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:efce6ae830831ab6a22b9b4091d411698145cb9b8fc869e1397ccf4b4b6455cb"}, - {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:40de71985e9042ca00b7953c4f41eabc3dc514a2d1ff534027f091bc74416401"}, - {file = "frozenlist-1.3.3-cp37-cp37m-win32.whl", hash = "sha256:180c00c66bde6146a860cbb81b54ee0df350d2daf13ca85b275123bbf85de18a"}, - {file = "frozenlist-1.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9bbbcedd75acdfecf2159663b87f1bb5cfc80e7cd99f7ddd9d66eb98b14a8411"}, - {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:034a5c08d36649591be1cbb10e09da9f531034acfe29275fc5454a3b101ce41a"}, - {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba64dc2b3b7b158c6660d49cdb1d872d1d0bf4e42043ad8d5006099479a194e5"}, - {file = "frozenlist-1.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:47df36a9fe24054b950bbc2db630d508cca3aa27ed0566c0baf661225e52c18e"}, - {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:008a054b75d77c995ea26629ab3a0c0d7281341f2fa7e1e85fa6153ae29ae99c"}, - {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:841ea19b43d438a80b4de62ac6ab21cfe6827bb8a9dc62b896acc88eaf9cecba"}, - {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e235688f42b36be2b6b06fc37ac2126a73b75fb8d6bc66dd632aa35286238703"}, - {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca713d4af15bae6e5d79b15c10c8522859a9a89d3b361a50b817c98c2fb402a2"}, - {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ac5995f2b408017b0be26d4a1d7c61bce106ff3d9e3324374d66b5964325448"}, - {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4ae8135b11652b08a8baf07631d3ebfe65a4c87909dbef5fa0cdde440444ee4"}, - {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ea42116ceb6bb16dbb7d526e242cb6747b08b7710d9782aa3d6732bd8d27649"}, - {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:810860bb4bdce7557bc0febb84bbd88198b9dbc2022d8eebe5b3590b2ad6c842"}, - {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ee78feb9d293c323b59a6f2dd441b63339a30edf35abcb51187d2fc26e696d13"}, - {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0af2e7c87d35b38732e810befb9d797a99279cbb85374d42ea61c1e9d23094b3"}, - {file = "frozenlist-1.3.3-cp38-cp38-win32.whl", hash = "sha256:899c5e1928eec13fd6f6d8dc51be23f0d09c5281e40d9cf4273d188d9feeaf9b"}, - {file = "frozenlist-1.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:7f44e24fa70f6fbc74aeec3e971f60a14dde85da364aa87f15d1be94ae75aeef"}, - {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2b07ae0c1edaa0a36339ec6cce700f51b14a3fc6545fdd32930d2c83917332cf"}, - {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ebb86518203e12e96af765ee89034a1dbb0c3c65052d1b0c19bbbd6af8a145e1"}, - {file = "frozenlist-1.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5cf820485f1b4c91e0417ea0afd41ce5cf5965011b3c22c400f6d144296ccbc0"}, - {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c11e43016b9024240212d2a65043b70ed8dfd3b52678a1271972702d990ac6d"}, - {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8fa3c6e3305aa1146b59a09b32b2e04074945ffcfb2f0931836d103a2c38f936"}, - {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:352bd4c8c72d508778cf05ab491f6ef36149f4d0cb3c56b1b4302852255d05d5"}, - {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65a5e4d3aa679610ac6e3569e865425b23b372277f89b5ef06cf2cdaf1ebf22b"}, - {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e2c1185858d7e10ff045c496bbf90ae752c28b365fef2c09cf0fa309291669"}, - {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f163d2fd041c630fed01bc48d28c3ed4a3b003c00acd396900e11ee5316b56bb"}, - {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:05cdb16d09a0832eedf770cb7bd1fe57d8cf4eaf5aced29c4e41e3f20b30a784"}, - {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:8bae29d60768bfa8fb92244b74502b18fae55a80eac13c88eb0b496d4268fd2d"}, - {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eedab4c310c0299961ac285591acd53dc6723a1ebd90a57207c71f6e0c2153ab"}, - {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3bbdf44855ed8f0fbcd102ef05ec3012d6a4fd7c7562403f76ce6a52aeffb2b1"}, - {file = "frozenlist-1.3.3-cp39-cp39-win32.whl", hash = "sha256:efa568b885bca461f7c7b9e032655c0c143d305bf01c30caf6db2854a4532b38"}, - {file = "frozenlist-1.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfe33efc9cb900a4c46f91a5ceba26d6df370ffddd9ca386eb1d4f0ad97b9ea9"}, - {file = "frozenlist-1.3.3.tar.gz", hash = "sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a"}, -] -ghp-import = [ - {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, - {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, -] -gitdb = [ - {file = "gitdb-4.0.10-py3-none-any.whl", hash = "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"}, - {file = "gitdb-4.0.10.tar.gz", hash = "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a"}, -] -gitpython = [ - {file = "GitPython-3.1.30-py3-none-any.whl", hash = "sha256:cd455b0000615c60e286208ba540271af9fe531fa6a87cc590a7298785ab2882"}, - {file = "GitPython-3.1.30.tar.gz", hash = "sha256:769c2d83e13f5d938b7688479da374c4e3d49f71549aaf462b646db9602ea6f8"}, -] -griffe = [ - {file = "griffe-0.25.4-py3-none-any.whl", hash = "sha256:919f935a358b31074d16e324e26b041883c60a8cf10504655e394afc3a7caad8"}, - {file = "griffe-0.25.4.tar.gz", hash = "sha256:f190edf8ef58d43c856d2d6761ec324a043ff60deb8c14359263571e8b91fe68"}, -] -h11 = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] -httpcore = [ - {file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"}, - {file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"}, -] -httpx = [ - {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"}, - {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"}, -] -identify = [ - {file = "identify-2.5.13-py2.py3-none-any.whl", hash = "sha256:8aa48ce56e38c28b6faa9f261075dea0a942dfbb42b341b4e711896cbb40f3f7"}, - {file = "identify-2.5.13.tar.gz", hash = "sha256:abb546bca6f470228785338a01b539de8a85bbf46491250ae03363956d8ebb10"}, -] -idna = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, -] -importlib-metadata = [ - {file = "importlib_metadata-4.13.0-py3-none-any.whl", hash = "sha256:8a8a81bcf996e74fee46f0d16bd3eaa382a7eb20fd82445c3ad11f4090334116"}, - {file = "importlib_metadata-4.13.0.tar.gz", hash = "sha256:dd0173e8f150d6815e098fd354f6414b0f079af4644ddfe90c71e2fc6174346d"}, -] -iniconfig = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] -jinja2 = [ - {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, - {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, -] -markdown = [ - {file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"}, - {file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"}, -] -markdown-it-py = [ - {file = "markdown-it-py-2.1.0.tar.gz", hash = "sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da"}, - {file = "markdown_it_py-2.1.0-py3-none-any.whl", hash = "sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27"}, -] -markupsafe = [ - {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, - {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, -] -mdurl = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] -mergedeep = [ - {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, - {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, -] -mkdocs = [ - {file = "mkdocs-1.4.2-py3-none-any.whl", hash = "sha256:c8856a832c1e56702577023cd64cc5f84948280c1c0fcc6af4cd39006ea6aa8c"}, - {file = "mkdocs-1.4.2.tar.gz", hash = "sha256:8947af423a6d0facf41ea1195b8e1e8c85ad94ac95ae307fe11232e0424b11c5"}, -] -mkdocs-autorefs = [ - {file = "mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"}, - {file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"}, -] -mkdocs-material = [ - {file = "mkdocs_material-8.5.11-py3-none-any.whl", hash = "sha256:c907b4b052240a5778074a30a78f31a1f8ff82d7012356dc26898b97559f082e"}, - {file = "mkdocs_material-8.5.11.tar.gz", hash = "sha256:b0ea0513fd8cab323e8a825d6692ea07fa83e917bb5db042e523afecc7064ab7"}, -] -mkdocs-material-extensions = [ - {file = "mkdocs_material_extensions-1.1.1-py3-none-any.whl", hash = "sha256:e41d9f38e4798b6617ad98ca8f7f1157b1e4385ac1459ca1e4ea219b556df945"}, - {file = "mkdocs_material_extensions-1.1.1.tar.gz", hash = "sha256:9c003da71e2cc2493d910237448c672e00cefc800d3d6ae93d2fc69979e3bd93"}, -] -mkdocs-rss-plugin = [ - {file = "mkdocs-rss-plugin-1.5.0.tar.gz", hash = "sha256:4178b3830dcbad9b53b12459e315b1aad6b37d1e7e5c56c686866a10f99878a4"}, - {file = "mkdocs_rss_plugin-1.5.0-py2.py3-none-any.whl", hash = "sha256:2ab14c20bf6b7983acbe50181e7e4a0778731d9c2d5c38107ca7047a7abd2165"}, -] -mkdocstrings = [ - {file = "mkdocstrings-0.19.1-py3-none-any.whl", hash = "sha256:32a38d88f67f65b264184ea71290f9332db750d189dea4200cbbe408d304c261"}, - {file = "mkdocstrings-0.19.1.tar.gz", hash = "sha256:d1037cacb4b522c1e8c164ed5d00d724a82e49dcee0af80db8fb67b384faeef9"}, -] -mkdocstrings-python = [ - {file = "mkdocstrings-python-0.8.3.tar.gz", hash = "sha256:9ae473f6dc599339b09eee17e4d2b05d6ac0ec29860f3fc9b7512d940fc61adf"}, - {file = "mkdocstrings_python-0.8.3-py3-none-any.whl", hash = "sha256:4e6e1cd6f37a785de0946ced6eb846eb2f5d891ac1cc2c7b832943d3529087a7"}, -] -msgpack = [ - {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250"}, - {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:112b0f93202d7c0fef0b7810d465fde23c746a2d482e1e2de2aafd2ce1492c88"}, - {file = "msgpack-1.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:002b5c72b6cd9b4bafd790f364b8480e859b4712e91f43014fe01e4f957b8467"}, - {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35bc0faa494b0f1d851fd29129b2575b2e26d41d177caacd4206d81502d4c6a6"}, - {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4733359808c56d5d7756628736061c432ded018e7a1dff2d35a02439043321aa"}, - {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb514ad14edf07a1dbe63761fd30f89ae79b42625731e1ccf5e1f1092950eaa6"}, - {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c23080fdeec4716aede32b4e0ef7e213c7b1093eede9ee010949f2a418ced6ba"}, - {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:49565b0e3d7896d9ea71d9095df15b7f75a035c49be733051c34762ca95bbf7e"}, - {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aca0f1644d6b5a73eb3e74d4d64d5d8c6c3d577e753a04c9e9c87d07692c58db"}, - {file = "msgpack-1.0.4-cp310-cp310-win32.whl", hash = "sha256:0dfe3947db5fb9ce52aaea6ca28112a170db9eae75adf9339a1aec434dc954ef"}, - {file = "msgpack-1.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dea20515f660aa6b7e964433b1808d098dcfcabbebeaaad240d11f909298075"}, - {file = "msgpack-1.0.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e83f80a7fec1a62cf4e6c9a660e39c7f878f603737a0cdac8c13131d11d97f52"}, - {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c11a48cf5e59026ad7cb0dc29e29a01b5a66a3e333dc11c04f7e991fc5510a9"}, - {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1276e8f34e139aeff1c77a3cefb295598b504ac5314d32c8c3d54d24fadb94c9"}, - {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c9566f2c39ccced0a38d37c26cc3570983b97833c365a6044edef3574a00c08"}, - {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fcb8a47f43acc113e24e910399376f7277cf8508b27e5b88499f053de6b115a8"}, - {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:76ee788122de3a68a02ed6f3a16bbcd97bc7c2e39bd4d94be2f1821e7c4a64e6"}, - {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:0a68d3ac0104e2d3510de90a1091720157c319ceeb90d74f7b5295a6bee51bae"}, - {file = "msgpack-1.0.4-cp36-cp36m-win32.whl", hash = "sha256:85f279d88d8e833ec015650fd15ae5eddce0791e1e8a59165318f371158efec6"}, - {file = "msgpack-1.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:c1683841cd4fa45ac427c18854c3ec3cd9b681694caf5bff04edb9387602d661"}, - {file = "msgpack-1.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a75dfb03f8b06f4ab093dafe3ddcc2d633259e6c3f74bb1b01996f5d8aa5868c"}, - {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9667bdfdf523c40d2511f0e98a6c9d3603be6b371ae9a238b7ef2dc4e7a427b0"}, - {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11184bc7e56fd74c00ead4f9cc9a3091d62ecb96e97653add7a879a14b003227"}, - {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac5bd7901487c4a1dd51a8c58f2632b15d838d07ceedaa5e4c080f7190925bff"}, - {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1e91d641d2bfe91ba4c52039adc5bccf27c335356055825c7f88742c8bb900dd"}, - {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2a2df1b55a78eb5f5b7d2a4bb221cd8363913830145fad05374a80bf0877cb1e"}, - {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:545e3cf0cf74f3e48b470f68ed19551ae6f9722814ea969305794645da091236"}, - {file = "msgpack-1.0.4-cp37-cp37m-win32.whl", hash = "sha256:2cc5ca2712ac0003bcb625c96368fd08a0f86bbc1a5578802512d87bc592fe44"}, - {file = "msgpack-1.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:eba96145051ccec0ec86611fe9cf693ce55f2a3ce89c06ed307de0e085730ec1"}, - {file = "msgpack-1.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7760f85956c415578c17edb39eed99f9181a48375b0d4a94076d84148cf67b2d"}, - {file = "msgpack-1.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:449e57cc1ff18d3b444eb554e44613cffcccb32805d16726a5494038c3b93dab"}, - {file = "msgpack-1.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d603de2b8d2ea3f3bcb2efe286849aa7a81531abc52d8454da12f46235092bcb"}, - {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f5d88c99f64c456413d74a975bd605a9b0526293218a3b77220a2c15458ba9"}, - {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916c78f33602ecf0509cc40379271ba0f9ab572b066bd4bdafd7434dee4bc6e"}, - {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81fc7ba725464651190b196f3cd848e8553d4d510114a954681fd0b9c479d7e1"}, - {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5b5b962221fa2c5d3a7f8133f9abffc114fe218eb4365e40f17732ade576c8e"}, - {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:77ccd2af37f3db0ea59fb280fa2165bf1b096510ba9fe0cc2bf8fa92a22fdb43"}, - {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b17be2478b622939e39b816e0aa8242611cc8d3583d1cd8ec31b249f04623243"}, - {file = "msgpack-1.0.4-cp38-cp38-win32.whl", hash = "sha256:2bb8cdf50dd623392fa75525cce44a65a12a00c98e1e37bf0fb08ddce2ff60d2"}, - {file = "msgpack-1.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:26b8feaca40a90cbe031b03d82b2898bf560027160d3eae1423f4a67654ec5d6"}, - {file = "msgpack-1.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:462497af5fd4e0edbb1559c352ad84f6c577ffbbb708566a0abaaa84acd9f3ae"}, - {file = "msgpack-1.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2999623886c5c02deefe156e8f869c3b0aaeba14bfc50aa2486a0415178fce55"}, - {file = "msgpack-1.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f0029245c51fd9473dc1aede1160b0a29f4a912e6b1dd353fa6d317085b219da"}, - {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed6f7b854a823ea44cf94919ba3f727e230da29feb4a99711433f25800cf747f"}, - {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df96d6eaf45ceca04b3f3b4b111b86b33785683d682c655063ef8057d61fd92"}, - {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a4192b1ab40f8dca3f2877b70e63799d95c62c068c84dc028b40a6cb03ccd0f"}, - {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e3590f9fb9f7fbc36df366267870e77269c03172d086fa76bb4eba8b2b46624"}, - {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1576bd97527a93c44fa856770197dec00d223b0b9f36ef03f65bac60197cedf8"}, - {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:63e29d6e8c9ca22b21846234913c3466b7e4ee6e422f205a2988083de3b08cae"}, - {file = "msgpack-1.0.4-cp39-cp39-win32.whl", hash = "sha256:fb62ea4b62bfcb0b380d5680f9a4b3f9a2d166d9394e9bbd9666c0ee09a3645c"}, - {file = "msgpack-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:4d5834a2a48965a349da1c5a79760d94a1a0172fbb5ab6b5b33cbf8447e109ce"}, - {file = "msgpack-1.0.4.tar.gz", hash = "sha256:f5d869c18f030202eb412f08b28d2afeea553d6613aee89e200d7aca7ef01f5f"}, -] -multidict = [ - {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, - {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, - {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, - {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, - {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, - {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, - {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, - {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, - {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, - {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, - {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, - {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, - {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, - {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, - {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, -] -mypy = [ - {file = "mypy-0.990-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aaf1be63e0207d7d17be942dcf9a6b641745581fe6c64df9a38deb562a7dbafa"}, - {file = "mypy-0.990-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d555aa7f44cecb7ea3c0ac69d58b1a5afb92caa017285a8e9c4efbf0518b61b4"}, - {file = "mypy-0.990-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f694d6d09a460b117dccb6857dda269188e3437c880d7b60fa0014fa872d1e9"}, - {file = "mypy-0.990-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:269f0dfb6463b8780333310ff4b5134425157ef0d2b1d614015adaf6d6a7eabd"}, - {file = "mypy-0.990-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8798c8ed83aa809f053abff08664bdca056038f5a02af3660de00b7290b64c47"}, - {file = "mypy-0.990-cp310-cp310-win_amd64.whl", hash = "sha256:47a9955214615108c3480a500cfda8513a0b1cd3c09a1ed42764ca0dd7b931dd"}, - {file = "mypy-0.990-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4a8a6c10f4c63fbf6ad6c03eba22c9331b3946a4cec97f008e9ffb4d3b31e8e2"}, - {file = "mypy-0.990-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cd2dd3730ba894ec2a2082cc703fbf3e95a08479f7be84912e3131fc68809d46"}, - {file = "mypy-0.990-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7da0005e47975287a92b43276e460ac1831af3d23032c34e67d003388a0ce8d0"}, - {file = "mypy-0.990-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:262c543ef24deb10470a3c1c254bb986714e2b6b1a67d66daf836a548a9f316c"}, - {file = "mypy-0.990-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3ff201a0c6d3ea029d73b1648943387d75aa052491365b101f6edd5570d018ea"}, - {file = "mypy-0.990-cp311-cp311-win_amd64.whl", hash = "sha256:1767830da2d1afa4e62b684647af0ff79b401f004d7fa08bc5b0ce2d45bcd5ec"}, - {file = "mypy-0.990-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6826d9c4d85bbf6d68cb279b561de6a4d8d778ca8e9ab2d00ee768ab501a9852"}, - {file = "mypy-0.990-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46897755f944176fbc504178422a5a2875bbf3f7436727374724842c0987b5af"}, - {file = "mypy-0.990-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0680389c34284287fe00e82fc8bccdea9aff318f7e7d55b90d967a13a9606013"}, - {file = "mypy-0.990-cp37-cp37m-win_amd64.whl", hash = "sha256:b08541a06eed35b543ae1a6b301590eb61826a1eb099417676ddc5a42aa151c5"}, - {file = "mypy-0.990-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:be88d665e76b452c26fb2bdc3d54555c01226fba062b004ede780b190a50f9db"}, - {file = "mypy-0.990-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b8f4a8213b1fd4b751e26b59ae0e0c12896568d7e805861035c7a15ed6dc9eb"}, - {file = "mypy-0.990-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2b6f85c2ad378e3224e017904a051b26660087b3b76490d533b7344f1546d3ff"}, - {file = "mypy-0.990-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ee5f99817ee70254e7eb5cf97c1b11dda29c6893d846c8b07bce449184e9466"}, - {file = "mypy-0.990-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49082382f571c3186ce9ea0bd627cb1345d4da8d44a8377870f4442401f0a706"}, - {file = "mypy-0.990-cp38-cp38-win_amd64.whl", hash = "sha256:aba38e3dd66bdbafbbfe9c6e79637841928ea4c79b32e334099463c17b0d90ef"}, - {file = "mypy-0.990-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9d851c09b981a65d9d283a8ccb5b1d0b698e580493416a10942ef1a04b19fd37"}, - {file = "mypy-0.990-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d847dd23540e2912d9667602271e5ebf25e5788e7da46da5ffd98e7872616e8e"}, - {file = "mypy-0.990-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc6019808580565040cd2a561b593d7c3c646badd7e580e07d875eb1bf35c695"}, - {file = "mypy-0.990-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a3150d409609a775c8cb65dbe305c4edd7fe576c22ea79d77d1454acd9aeda8"}, - {file = "mypy-0.990-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3227f14fe943524f5794679156488f18bf8d34bfecd4623cf76bc55958d229c5"}, - {file = "mypy-0.990-cp39-cp39-win_amd64.whl", hash = "sha256:c76c769c46a1e6062a84837badcb2a7b0cdb153d68601a61f60739c37d41cc74"}, - {file = "mypy-0.990-py3-none-any.whl", hash = "sha256:8f1940325a8ed460ba03d19ab83742260fa9534804c317224e5d4e5aa588e2d6"}, - {file = "mypy-0.990.tar.gz", hash = "sha256:72382cb609142dba3f04140d016c94b4092bc7b4d98ca718740dc989e5271b8d"}, -] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] -nanoid = [ - {file = "nanoid-2.0.0-py3-none-any.whl", hash = "sha256:90aefa650e328cffb0893bbd4c236cfd44c48bc1f2d0b525ecc53c3187b653bb"}, - {file = "nanoid-2.0.0.tar.gz", hash = "sha256:5a80cad5e9c6e9ae3a41fa2fb34ae189f7cb420b2a5d8f82bd9d23466e4efa68"}, -] -nodeenv = [ - {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, - {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, -] -packaging = [ - {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, - {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, -] -pathspec = [ - {file = "pathspec-0.10.3-py3-none-any.whl", hash = "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6"}, - {file = "pathspec-0.10.3.tar.gz", hash = "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"}, -] -platformdirs = [ - {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, - {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] -pre-commit = [ - {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, - {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, -] -pygments = [ - {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, - {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, -] -pymdown-extensions = [ - {file = "pymdown_extensions-9.9.2-py3-none-any.whl", hash = "sha256:c3d804eb4a42b85bafb5f36436342a5ad38df03878bb24db8855a4aa8b08b765"}, - {file = "pymdown_extensions-9.9.2.tar.gz", hash = "sha256:ebb33069bafcb64d5f5988043331d4ea4929325dc678a6bcf247ddfcf96499f8"}, -] -pytest = [ - {file = "pytest-7.2.1-py3-none-any.whl", hash = "sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5"}, - {file = "pytest-7.2.1.tar.gz", hash = "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"}, -] -pytest-aiohttp = [ - {file = "pytest-aiohttp-1.0.4.tar.gz", hash = "sha256:39ff3a0d15484c01d1436cbedad575c6eafbf0f57cdf76fb94994c97b5b8c5a4"}, - {file = "pytest_aiohttp-1.0.4-py3-none-any.whl", hash = "sha256:1d2dc3a304c2be1fd496c0c2fb6b31ab60cd9fc33984f761f951f8ea1eb4ca95"}, -] -pytest-asyncio = [ - {file = "pytest-asyncio-0.20.3.tar.gz", hash = "sha256:83cbf01169ce3e8eb71c6c278ccb0574d1a7a3bb8eaaf5e50e0ad342afb33b36"}, - {file = "pytest_asyncio-0.20.3-py3-none-any.whl", hash = "sha256:f129998b209d04fcc65c96fc85c11e5316738358909a8399e93be553d7656442"}, -] -pytest-cov = [ - {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, - {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, -] -python-dateutil = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] -pytz = [ - {file = "pytz-2022.7.1-py2.py3-none-any.whl", hash = "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"}, - {file = "pytz-2022.7.1.tar.gz", hash = "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0"}, -] -pyyaml = [ - {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"}, -] -pyyaml-env-tag = [ - {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, - {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, -] -requests = [ - {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, - {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, -] -rfc3986 = [ - {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, - {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, -] -rich = [ - {file = "rich-13.2.0-py3-none-any.whl", hash = "sha256:7c963f0d03819221e9ac561e1bc866e3f95a02248c1234daa48954e6d381c003"}, - {file = "rich-13.2.0.tar.gz", hash = "sha256:f1a00cdd3eebf999a15d85ec498bfe0b1a77efe9b34f645768a54132ef444ac5"}, -] -setuptools = [ - {file = "setuptools-66.1.1-py3-none-any.whl", hash = "sha256:6f590d76b713d5de4e49fe4fbca24474469f53c83632d5d0fd056f7ff7e8112b"}, - {file = "setuptools-66.1.1.tar.gz", hash = "sha256:ac4008d396bc9cd983ea483cb7139c0240a07bbc74ffb6232fceffedc6cf03a8"}, -] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -smmap = [ - {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, - {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, -] -sniffio = [ - {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, - {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, -] -syrupy = [ - {file = "syrupy-3.0.6-py3-none-any.whl", hash = "sha256:9c18e22264026b34239bcc87ab7cc8d893eb17236ea7dae634217ea4f22a848d"}, - {file = "syrupy-3.0.6.tar.gz", hash = "sha256:583aa5ca691305c27902c3e29a1ce9da50ff9ab5f184c54b1dc124a16e4a6cf4"}, -] -time-machine = [ +files = [ {file = "time-machine-2.9.0.tar.gz", hash = "sha256:60222d43f6e93a926adc36ed37a54bc8e4d0d8d1c4d449096afcfe85086129c2"}, {file = "time_machine-2.9.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fd72c0b2e7443fff6e4481991742b72c17f73735e5fdd176406ca48df187a5c9"}, {file = "time_machine-2.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5657e0e6077cf15b37f0d8cf78e868113bbb3ecccc60064c40fe52d8166ca8b1"}, @@ -2879,4 +2009,4 @@ dev = ["aiohttp", "click", "msgpack"] [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "2c9e7a727933ce76d9a330ef89b64dcd374777a416543163d7af5e679c4d4ce0" +content-hash = "b70dc64a3c9e7a7b765252f5dd1a5de8ed6efacd0695cde32ff983b14ec55ca6" From c4dbde19948d1c0f162594871c1f5f7bf0a7e7de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 26 Jan 2023 16:14:24 +0000 Subject: [PATCH 24/93] Don't force layout on overflow change. Fix: #1628. --- src/textual/css/styles.py | 4 ++-- tests/css/test_programmatic_style_changes.py | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index cc9e99a6a..a7021a5b8 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -247,8 +247,8 @@ class StylesBase(ABC): dock = DockProperty() - overflow_x = OverflowProperty(VALID_OVERFLOW, "hidden", layout=True, children=True) - overflow_y = OverflowProperty(VALID_OVERFLOW, "hidden", layout=True, children=True) + overflow_x = OverflowProperty(VALID_OVERFLOW, "hidden") + overflow_y = OverflowProperty(VALID_OVERFLOW, "hidden") layer = NameProperty() layers = NameListProperty() diff --git a/tests/css/test_programmatic_style_changes.py b/tests/css/test_programmatic_style_changes.py index 3abc15ba1..b30f34ba7 100644 --- a/tests/css/test_programmatic_style_changes.py +++ b/tests/css/test_programmatic_style_changes.py @@ -27,8 +27,6 @@ class _Label(Label): ("grid_gutter_horizontal", 1), ("grid_rows", "1fr 3fr"), ("grid_columns", "1fr 3fr"), - ("overflow_x", "scroll"), - ("overflow_y", "scroll"), ("scrollbar_gutter", "stable"), ("align_horizontal", "right"), ("align_vertical", "bottom"), From b023d4e02e0a91c99847da4a6429c008ae3ec398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 30 Jan 2023 11:50:02 +0000 Subject: [PATCH 25/93] Improve tests. --- tests/css/test_programmatic_style_changes.py | 107 +++++++----- .../__snapshots__/test_snapshots.ambr | 155 ++++++++++++++++++ .../programmatic_scrollbar_gutter_change.py | 29 ++++ tests/snapshot_tests/test_snapshots.py | 10 ++ 4 files changed, 264 insertions(+), 37 deletions(-) create mode 100644 tests/snapshot_tests/snapshot_apps/programmatic_scrollbar_gutter_change.py diff --git a/tests/css/test_programmatic_style_changes.py b/tests/css/test_programmatic_style_changes.py index b30f34ba7..f15ecdd55 100644 --- a/tests/css/test_programmatic_style_changes.py +++ b/tests/css/test_programmatic_style_changes.py @@ -6,57 +6,90 @@ from textual.screen import Screen from textual.widgets import Label -updates = 0 - - -class _Label(Label): - """Label widget that keeps track of its own updates.""" - - def refresh(self, *args, **kwargs): - global updates - updates += 1 - return super().refresh(*args, **kwargs) - - @pytest.mark.parametrize( "style, value", [ - ("grid_size_rows", 2), - ("grid_size_columns", 2), - ("grid_gutter_vertical", 2), - ("grid_gutter_horizontal", 1), + ("grid_size_rows", 3), + ("grid_size_columns", 3), + ("grid_gutter_vertical", 4), + ("grid_gutter_horizontal", 4), ("grid_rows", "1fr 3fr"), ("grid_columns", "1fr 3fr"), - ("scrollbar_gutter", "stable"), - ("align_horizontal", "right"), - ("align_vertical", "bottom"), - ("align", ("right", "bottom")), ], ) -def test_programmatic_style_change_refreshes_children_layout(style: str, value): +async def test_programmatic_style_change_updates_children(style: str, value: object): """Regression test for #1607 https://github.com/Textualize/textual/issues/1607 Some programmatic style changes to a widget were not updating the layout of the children widgets, which seemed to be happening when the style change did not affect the size of the widget but did affect the layout of the children. + + This test, in particular, checks the attributes that _should_ affect the size of the + children widgets. """ - global updates + class MyApp(App[None]): + CSS = """ + Grid { grid-size: 2 2; } + Label { width: 100%; height: 100%; } + """ - app = App() - app.DEFAULT_CSS = "Grid { grid-size: 1 1; }" - app._set_active() - app.push_screen(Screen()) + def compose(self): + yield Grid( + Label("one"), + Label("two"), + Label("three"), + Label("four"), + ) - grid = Grid( - _Label("one"), - _Label("two"), - _Label("three"), - _Label("four"), - ) - app.screen._add_children(grid) + app = MyApp() - update_count = updates - setattr(grid.styles, style, value) - print(updates, update_count) - assert updates > update_count + async with app.run_test() as pilot: + sizes = [(lbl.size.width, lbl.size.height) for lbl in app.screen.query(Label)] + + setattr(app.query_one(Grid).styles, style, value) + await pilot.pause() + + assert sizes != [ + (lbl.size.width, lbl.size.height) for lbl in app.screen.query(Label) + ] + + +@pytest.mark.parametrize( + "style, value", + [ + ("align_horizontal", "right"), + ("align_vertical", "bottom"), + ("align", ("right", "bottom")), + ], +) +async def test_programmatic_align_change_updates_children_position( + style: str, value: str +): + """Regression test for #1607 for the align(_xxx) styles. + + See https://github.com/Textualize/textual/issues/1607. + """ + + class MyApp(App[None]): + CSS = "Grid { grid-size: 2 2; }" + + def compose(self): + yield Grid( + Label("one"), + Label("two"), + Label("three"), + Label("four"), + ) + + app = MyApp() + + async with app.run_test() as pilot: + offsets = [(lbl.region.x, lbl.region.y) for lbl in app.screen.query(Label)] + + setattr(app.query_one(Grid).styles, style, value) + await pilot.pause() + + assert offsets != [ + (lbl.region.x, lbl.region.y) for lbl in app.screen.query(Label) + ] diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr index 52b617982..27dfb81aa 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr +++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr @@ -13651,6 +13651,161 @@ ''' # --- +# name: test_programmatic_scrollbar_gutter_change + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ProgrammaticScrollbarGutterChange + + + + + + + + + + onetwo + + + + + + + + + + + + threefour + + + + + + + + + + + + + + + + ''' +# --- # name: test_textlog_max_lines ''' diff --git a/tests/snapshot_tests/snapshot_apps/programmatic_scrollbar_gutter_change.py b/tests/snapshot_tests/snapshot_apps/programmatic_scrollbar_gutter_change.py new file mode 100644 index 000000000..d1a7fba0d --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/programmatic_scrollbar_gutter_change.py @@ -0,0 +1,29 @@ +from textual.app import App +from textual.containers import Grid +from textual.widgets import Label + + +class ProgrammaticScrollbarGutterChange(App[None]): + CSS = """ + Grid { grid-size: 2 2; scrollbar-size: 5 5; } + Label { width: 100%; height: 100%; background: red; } + """ + + def compose(self): + yield Grid( + Label("one"), + Label("two"), + Label("three"), + Label("four"), + ) + + def on_key(self, event): + if event.key == "s": + self.query_one(Grid).styles.scrollbar_gutter = "stable" + + +app = ProgrammaticScrollbarGutterChange() + + +if __name__ == "__main__": + app().run() diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index 868bd91aa..7a6fb9324 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -179,6 +179,16 @@ def test_nested_auto_heights(snap_compare): assert snap_compare("snapshot_apps/nested_auto_heights.py", press=["1", "2", "_"]) +def test_programmatic_scrollbar_gutter_change(snap_compare): + """Regression test for #1607 https://github.com/Textualize/textual/issues/1607 + + See also tests/css/test_programmatic_style_changes.py for other related regression tests. + """ + assert snap_compare( + "snapshot_apps/programmatic_scrollbar_gutter_change.py", press=["s"] + ) + + # --- Other --- From 12cfec7be3a8100f22088464c42136cd4317112f Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Mon, 30 Jan 2023 16:51:45 +0000 Subject: [PATCH 26/93] When in password mode have word-oriented actions act on whole input The idea here is that a password field should give no hint as to what's within, length notwithstanding. To this end have the actions that (re)move based on word boundaries act as if a password field is always just one word. See #1692 (and previously #1676, prompted originally by #1310). --- src/textual/widgets/_input.py | 68 ++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/src/textual/widgets/_input.py b/src/textual/widgets/_input.py index b09b96310..64f0fd942 100644 --- a/src/textual/widgets/_input.py +++ b/src/textual/widgets/_input.py @@ -378,20 +378,32 @@ class Input(Widget, can_focus=True): def action_cursor_left_word(self) -> None: """Move the cursor left to the start of a word.""" - try: - *_, hit = re.finditer(self._WORD_START, self.value[: self.cursor_position]) - except ValueError: - self.cursor_position = 0 + if self.password: + # This is a password field so don't give any hints about word + # boundaries, even during movement. + self.action_home() else: - self.cursor_position = hit.start() + try: + *_, hit = re.finditer( + self._WORD_START, self.value[: self.cursor_position] + ) + except ValueError: + self.cursor_position = 0 + else: + self.cursor_position = hit.start() def action_cursor_right_word(self) -> None: """Move the cursor right to the start of a word.""" - hit = re.search(self._WORD_START, self.value[self.cursor_position :]) - if hit is None: - self.cursor_position = len(self.value) + if self.password: + # This is a password field so don't give any hints about word + # boundaries, even during movement. + self.action_end() else: - self.cursor_position += hit.start() + hit = re.search(self._WORD_START, self.value[self.cursor_position :]) + if hit is None: + self.cursor_position = len(self.value) + else: + self.cursor_position += hit.start() def action_delete_right(self) -> None: """Delete one character at the current cursor position.""" @@ -404,12 +416,19 @@ class Input(Widget, can_focus=True): def action_delete_right_word(self) -> None: """Delete the current character and all rightward to the start of the next word.""" - after = self.value[self.cursor_position :] - hit = re.search(self._WORD_START, after) - if hit is None: - self.value = self.value[: self.cursor_position] + if self.password: + # This is a password field so don't give any hints about word + # boundaries, even during deletion. + self.action_delete_right_all() else: - self.value = f"{self.value[: self.cursor_position]}{after[hit.end()-1 :]}" + after = self.value[self.cursor_position :] + hit = re.search(self._WORD_START, after) + if hit is None: + self.value = self.value[: self.cursor_position] + else: + self.value = ( + f"{self.value[: self.cursor_position]}{after[hit.end()-1 :]}" + ) def action_delete_right_all(self) -> None: """Delete the current character and all characters to the right of the cursor position.""" @@ -437,14 +456,21 @@ class Input(Widget, can_focus=True): """Delete leftward of the cursor position to the start of a word.""" if self.cursor_position <= 0: return - after = self.value[self.cursor_position :] - try: - *_, hit = re.finditer(self._WORD_START, self.value[: self.cursor_position]) - except ValueError: - self.cursor_position = 0 + if self.password: + # This is a password field so don't give any hints about word + # boundaries, even during deletion. + self.action_delete_left_all() else: - self.cursor_position = hit.start() - self.value = f"{self.value[: self.cursor_position]}{after}" + after = self.value[self.cursor_position :] + try: + *_, hit = re.finditer( + self._WORD_START, self.value[: self.cursor_position] + ) + except ValueError: + self.cursor_position = 0 + else: + self.cursor_position = hit.start() + self.value = f"{self.value[: self.cursor_position]}{after}" def action_delete_left_all(self) -> None: """Delete all characters to the left of the cursor position.""" From 1fc2c2951cc9e062ef34a4d690053aa9419d1bc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 30 Jan 2023 17:24:15 +0000 Subject: [PATCH 27/93] Update test_animation.py Make it less likely for test to fail. References: #1675 --- tests/test_animation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_animation.py b/tests/test_animation.py index 8aa1615a2..20805ae9f 100644 --- a/tests/test_animation.py +++ b/tests/test_animation.py @@ -153,8 +153,8 @@ async def test_schedule_reverse_animations() -> None: assert styles.background.rgb == (0, 0, 0) # Now, the actual test is to make sure we go back to black if scheduling both at once. - styles.animate("background", "white", delay=0.01, duration=0.01) - await pilot.pause(0.005) - styles.animate("background", "black", delay=0.01, duration=0.01) + styles.animate("background", "white", delay=0.05, duration=0.01) + await pilot.pause() + styles.animate("background", "black", delay=0.05, duration=0.01) await pilot.wait_for_scheduled_animations() assert styles.background.rgb == (0, 0, 0) From 4aa988347fcb982af6b1d505ef9c70235a458e66 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 30 Jan 2023 18:59:23 +0100 Subject: [PATCH 28/93] add tests --- .../__snapshots__/test_snapshots.ambr | 158 ++++++++++++++++++ .../snapshot_apps/label_widths.py | 45 +++++ tests/snapshot_tests/test_snapshots.py | 4 + 3 files changed, 207 insertions(+) create mode 100644 tests/snapshot_tests/snapshot_apps/label_widths.py diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr index 1883f87a2..90173650b 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr +++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr @@ -12380,6 +12380,164 @@ ''' # --- +# name: test_label_widths + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + LabelWrap + + + + + + + + + + + + + + + + Apple Banana Cherry Mango Fig Guava Pineapple:Dragon Unicorn Centaur Phoenix Ch + + + Apple Banana Cherry Mango Fig Guava Pineapple:Dragon Unicorn Centaur Phoenix  + Chimera Castle + + + ╭────────────────────────────────────────────────────────────────────────────╮ + Apple Banana Cherry Mango Fig Guava Pineapple:Dragon Unicorn Centaur  + Phoenix Chimera Castle + ╰────────────────────────────────────────────────────────────────────────────╯ + + + + + + + + + + + + ''' +# --- # name: test_layers ''' diff --git a/tests/snapshot_tests/snapshot_apps/label_widths.py b/tests/snapshot_tests/snapshot_apps/label_widths.py new file mode 100644 index 000000000..2cc49d2f9 --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/label_widths.py @@ -0,0 +1,45 @@ +from textual.app import App +from textual.widgets import Label, Static +from rich.panel import Panel + + +class LabelWrap(App): + CSS = """Screen { + align: center middle; + } + + #l_data { + border: blank; + background: lightgray; + } + + #s_data { + border: blank; + background: lightgreen; + } + + #p_data { + border: blank; + background: lightgray; + }""" + + def __init__(self): + super().__init__() + + self.data = ( + "Apple Banana Cherry Mango Fig Guava Pineapple:" + "Dragon Unicorn Centaur Phoenix Chimera Castle" + ) + + def compose(self): + yield Label(self.data, id="l_data") + yield Static(self.data, id="s_data") + yield Label(Panel(self.data), id="p_data") + + def on_mount(self): + self.dark = False + + +if __name__ == "__main__": + app = LabelWrap() + app.run() diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index 8aa2b6514..20bf6001d 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -193,3 +193,7 @@ def test_demo(snap_compare): press=["down", "down", "down"], terminal_size=(100, 30), ) + + +def test_label_widths(snap_compare): + assert snap_compare(SNAPSHOT_APPS_DIR / "label_widths.py") From 71a0a6676fcc6cacf824825cc0725c632bdfe45c Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 30 Jan 2023 19:02:35 +0100 Subject: [PATCH 29/93] docstring [skip ci] --- tests/snapshot_tests/test_snapshots.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index 20bf6001d..bdad3bf53 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -196,4 +196,5 @@ def test_demo(snap_compare): def test_label_widths(snap_compare): + """Test renderable widths are calculate correctly.""" assert snap_compare(SNAPSHOT_APPS_DIR / "label_widths.py") From 62cede54ef69eb9fa8c98f18d0c58c2209264f38 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 30 Jan 2023 19:24:16 +0100 Subject: [PATCH 30/93] docstring update --- src/textual/_wait.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/textual/_wait.py b/src/textual/_wait.py index 67a124954..f7ed7d3f3 100644 --- a/src/textual/_wait.py +++ b/src/textual/_wait.py @@ -10,11 +10,11 @@ async def wait_for_idle( ) -> None: """Wait until the process isn't working very hard. - This will compare wall clock time with process time, if the process time - is not advancing the same as wall clock time it means the process is in a - sleep state or waiting for input. + This will compare wall clock time with process time. If the process time + is not advancing at the same rate as wall clock time it means the process is + idle (i.e. sleeping or waiting for input). - When the process is idle it suggests that input has been processes and the state + When the process is idle it suggests that input has been processed and the state is predictable enough to test. Args: From 5fc16c6af0f7fd6f0fde69a123fd3f3c3d8c4401 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Mon, 30 Jan 2023 20:34:22 +0000 Subject: [PATCH 31/93] Add extra unit tests for password field movement Here we're just testing the exceptional situations. --- .../input/test_input_key_movement_actions.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/input/test_input_key_movement_actions.py b/tests/input/test_input_key_movement_actions.py index 2d0bc1338..a6cf13694 100644 --- a/tests/input/test_input_key_movement_actions.py +++ b/tests/input/test_input_key_movement_actions.py @@ -97,6 +97,16 @@ async def test_input_left_word_from_end() -> None: assert input.cursor_position == expected_at[input.id] +async def test_password_input_left_word_from_end() -> None: + """Going left one word from the end in a password field should land at home.""" + async with InputTester().run_test() as pilot: + for input in pilot.app.query(Input): + input.action_end() + input.password = True + input.action_cursor_left_word() + assert input.cursor_position == 0 + + async def test_input_right_word_from_home() -> None: """Going right one word from the start should land correctly..""" async with InputTester().run_test() as pilot: @@ -112,6 +122,15 @@ async def test_input_right_word_from_home() -> None: assert input.cursor_position == expected_at[input.id] +async def test_password_input_right_word_from_home() -> None: + """Going right one word from the start of a password input should go to the end.""" + async with InputTester().run_test() as pilot: + for input in pilot.app.query(Input): + input.password = True + input.action_cursor_right_word() + assert input.cursor_position == len(input.value) + + async def test_input_right_word_from_end() -> None: """Going right one word from the end should do nothing.""" async with InputTester().run_test() as pilot: From 928a289c0e3c55e47368473e4570d2ce7ea7e19b Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Mon, 30 Jan 2023 20:38:19 +0000 Subject: [PATCH 32/93] Add extra unit tests for password field deletion Here we're just testing the exceptional situations. --- .../test_input_key_modification_actions.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/input/test_input_key_modification_actions.py b/tests/input/test_input_key_modification_actions.py index 51b124d7d..68f7e50c8 100644 --- a/tests/input/test_input_key_modification_actions.py +++ b/tests/input/test_input_key_modification_actions.py @@ -66,6 +66,17 @@ async def test_delete_left_word_from_end() -> None: assert input.value == expected[input.id] +async def test_password_delete_left_word_from_end() -> None: + """Deleting word left from end of a password input should delete everything.""" + async with InputTester().run_test() as pilot: + for input in pilot.app.query(Input): + input.action_end() + input.password = True + input.action_delete_left_word() + assert input.cursor_position == 0 + assert input.value == "" + + async def test_delete_left_all_from_home() -> None: """Deleting all left from home should do nothing.""" async with InputTester().run_test() as pilot: @@ -119,6 +130,16 @@ async def test_delete_right_word_from_home() -> None: assert input.value == expected[input.id] +async def test_password_delete_right_word_from_home() -> None: + """Deleting word right from home of a password input should delete everything.""" + async with InputTester().run_test() as pilot: + for input in pilot.app.query(Input): + input.password = True + input.action_delete_right_word() + assert input.cursor_position == 0 + assert input.value == "" + + async def test_delete_right_word_from_end() -> None: """Deleting word right from end should not change the input's value.""" async with InputTester().run_test() as pilot: From dc02b22d7c8ff21c3eca2d2dd5f40fb8b1d97c96 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 31 Jan 2023 11:12:47 +0100 Subject: [PATCH 33/93] Fix paste and test --- src/textual/app.py | 4 +++- tests/test_paste.py | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 tests/test_paste.py diff --git a/src/textual/app.py b/src/textual/app.py index 46ac67903..6abf74839 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -1901,9 +1901,11 @@ class App(Generic[ReturnType], DOMNode): else: await self.screen._forward_event(event) - elif isinstance(event, events.Paste): + elif isinstance(event, events.Paste) and not event.is_forwarded: if self.focused is not None: await self.focused._forward_event(event) + else: + await self.screen._forward_event(event) else: await super().on_event(event) diff --git a/tests/test_paste.py b/tests/test_paste.py new file mode 100644 index 000000000..58c5da41d --- /dev/null +++ b/tests/test_paste.py @@ -0,0 +1,18 @@ +from textual.app import App +from textual import events + + +async def test_paste_app(): + paste_events = [] + + class PasteApp(App): + def on_paste(self, event): + paste_events.append(event) + + app = PasteApp() + async with app.run_test() as pilot: + await app.post_message(events.Paste(sender=app, text="Hello")) + await pilot.pause(0) + + assert len(paste_events) == 1 + assert paste_events[0].text == "Hello" From 6e7fd08890fc9a78fc2690a796c046539b3b46f1 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 31 Jan 2023 11:14:23 +0100 Subject: [PATCH 34/93] change log [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0bf73d17..c3e3694a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Fixed double-paste into `Input` https://github.com/Textualize/textual/issues/1657 - Added a workaround for an apparent Windows Terminal paste issue https://github.com/Textualize/textual/issues/1661 - Fixes issue with renderable width calculation https://github.com/Textualize/textual/issues/1685 +- Fixed issue with app not processing Paste event https://github.com/Textualize/textual/issues/1666 ## [0.10.1] - 2023-01-20 From 2ec01916d0670419e58a6ae55bd9b235daf448ff Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 31 Jan 2023 11:16:48 +0100 Subject: [PATCH 35/93] unused import --- src/textual/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/events.py b/src/textual/events.py index d2f7dfe49..9914c03d5 100644 --- a/src/textual/events.py +++ b/src/textual/events.py @@ -7,7 +7,7 @@ from rich.style import Style from ._types import MessageTarget from .geometry import Offset, Size -from .keys import _get_key_aliases, _get_key_display +from .keys import _get_key_aliases from .message import Message MouseEventT = TypeVar("MouseEventT", bound="MouseEvent") From 8bfe4e817013f459ef15094b9494f82d131c9d08 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 31 Jan 2023 10:36:07 +0000 Subject: [PATCH 36/93] Fix unintentional indent --- src/textual/widgets/_input.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/widgets/_input.py b/src/textual/widgets/_input.py index 64f0fd942..5af98629b 100644 --- a/src/textual/widgets/_input.py +++ b/src/textual/widgets/_input.py @@ -470,7 +470,7 @@ class Input(Widget, can_focus=True): self.cursor_position = 0 else: self.cursor_position = hit.start() - self.value = f"{self.value[: self.cursor_position]}{after}" + self.value = f"{self.value[: self.cursor_position]}{after}" def action_delete_left_all(self) -> None: """Delete all characters to the left of the cursor position.""" From be5a67e90360c810059530503bd29a8ec2c3a992 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 31 Jan 2023 12:13:25 +0100 Subject: [PATCH 37/93] fix auto width glitch --- src/textual/widgets/_input.py | 1 + .../__snapshots__/test_snapshots.ambr | 160 ++++++++++++++++++ tests/snapshot_tests/test_snapshots.py | 6 + 3 files changed, 167 insertions(+) diff --git a/src/textual/widgets/_input.py b/src/textual/widgets/_input.py index b09b96310..333b8e9fe 100644 --- a/src/textual/widgets/_input.py +++ b/src/textual/widgets/_input.py @@ -255,6 +255,7 @@ class Input(Widget, can_focus=True): return self._position_to_cell(len(self.value)) + 1 def render(self) -> RenderableType: + self.view_position = self.view_position if not self.value: placeholder = Text(self.placeholder, justify="left") placeholder.stylize(self.get_component_rich_style("input--placeholder")) diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr index 90173650b..d347bcc03 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr +++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr @@ -1,3 +1,163 @@ +# name: test_auto_width_input + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + InputWidthAutoApp + + + + + + + + + + InputWidthAutoApp + ▔▔▔▔▔▔▔▔▔▔ + Hello + ▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + + + + + + + + + + + + ''' +# --- # name: test_buttons_render ''' diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index bdad3bf53..a2a8447ba 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -198,3 +198,9 @@ def test_demo(snap_compare): def test_label_widths(snap_compare): """Test renderable widths are calculate correctly.""" assert snap_compare(SNAPSHOT_APPS_DIR / "label_widths.py") + + +def test_auto_width_input(snap_compare): + assert snap_compare( + SNAPSHOT_APPS_DIR / "auto_width_input.py", press=["tab", *"Hello"] + ) From 632cfc77b0a22d7477ce5f0acbb4860de65a1a73 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 31 Jan 2023 12:14:36 +0100 Subject: [PATCH 38/93] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3e3694a5..329aa89a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added a workaround for an apparent Windows Terminal paste issue https://github.com/Textualize/textual/issues/1661 - Fixes issue with renderable width calculation https://github.com/Textualize/textual/issues/1685 - Fixed issue with app not processing Paste event https://github.com/Textualize/textual/issues/1666 +- Fixed glitch with view position with auto width inputs https://github.com/Textualize/textual/tree/auto-width-inputs ## [0.10.1] - 2023-01-20 From bfa586fdcdd641f7d0e680db19e10af9bcbae327 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 31 Jan 2023 12:16:21 +0100 Subject: [PATCH 39/93] changelog url --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 329aa89a9..26d7a0dd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,7 +33,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added a workaround for an apparent Windows Terminal paste issue https://github.com/Textualize/textual/issues/1661 - Fixes issue with renderable width calculation https://github.com/Textualize/textual/issues/1685 - Fixed issue with app not processing Paste event https://github.com/Textualize/textual/issues/1666 -- Fixed glitch with view position with auto width inputs https://github.com/Textualize/textual/tree/auto-width-inputs +- Fixed glitch with view position with auto width inputs https://github.com/Textualize/textual/issues/1693 ## [0.10.1] - 2023-01-20 From 330db5fc80d9926f3030427f355c212bb7fff015 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 31 Jan 2023 12:43:07 +0100 Subject: [PATCH 40/93] snapshot --- .../snapshot_apps/auto_width_input.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/snapshot_tests/snapshot_apps/auto_width_input.py diff --git a/tests/snapshot_tests/snapshot_apps/auto_width_input.py b/tests/snapshot_tests/snapshot_apps/auto_width_input.py new file mode 100644 index 000000000..aafffaf8d --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/auto_width_input.py @@ -0,0 +1,26 @@ +from textual.app import App, ComposeResult +from textual.containers import Vertical +from textual.widgets import Header, Footer, Label, Input + + +class InputWidthAutoApp(App[None]): + + CSS = """ + Screen { + align: center middle; + } + Input.auto { + width: auto; + max-width: 100%; + } + + """ + + def compose(self) -> ComposeResult: + yield Header() + yield Input(placeholder="This has auto width", classes="auto") + yield Footer() + + +if __name__ == "__main__": + InputWidthAutoApp().run() From e446695684d52f8eae4d1ed678a0e14a86e03b4d Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 31 Jan 2023 12:53:32 +0100 Subject: [PATCH 41/93] snapshot fix --- tests/snapshot_tests/snapshot_apps/auto_width_input.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/snapshot_tests/snapshot_apps/auto_width_input.py b/tests/snapshot_tests/snapshot_apps/auto_width_input.py index aafffaf8d..35cc3b2ec 100644 --- a/tests/snapshot_tests/snapshot_apps/auto_width_input.py +++ b/tests/snapshot_tests/snapshot_apps/auto_width_input.py @@ -6,14 +6,10 @@ from textual.widgets import Header, Footer, Label, Input class InputWidthAutoApp(App[None]): CSS = """ - Screen { - align: center middle; - } Input.auto { width: auto; max-width: 100%; } - """ def compose(self) -> ComposeResult: From 5c17bc34828af23ce6c38c71f09de815a09a67a2 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 31 Jan 2023 12:36:22 +0000 Subject: [PATCH 42/93] Add a toggle action to Tree, along with a new binding See #1433. The idea here is that the user has an option of expanding/collapsing a non-leaf node without causing a selected event, or (with auto_expand turned off) cause a selected event without an expand/collapse event. As this will need a new binding, I've chosen the space bar as the key to toggle the expanded state. --- src/textual/widgets/_tree.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index 64e40ebe2..b3d85cf6a 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -284,6 +284,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): BINDINGS: ClassVar[list[BindingType]] = [ Binding("enter", "select_cursor", "Select", show=False), + Binding("space", "toggle_node", "Toggle", show=False), Binding("up", "cursor_up", "Cursor Up", show=False), Binding("down", "cursor_down", "Cursor Down", show=False), ] @@ -291,6 +292,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): | Key(s) | Description | | :- | :- | | enter | Select the current item. | + | space | Toggle the expand/collapsed space of the current item. | | up | Move the cursor up. | | down | Move the cursor down. | """ @@ -1004,6 +1006,14 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): self.cursor_line = self.last_line self.scroll_to_line(self.cursor_line) + def action_toggle_node(self) -> None: + try: + line = self._tree_lines[self.cursor_line] + except IndexError: + pass + else: + self._toggle_node(line.path[-1]) + def action_select_cursor(self) -> None: try: line = self._tree_lines[self.cursor_line] From 775165ec1224c68a1716411c129cd97e71d76f5f Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 31 Jan 2023 12:48:19 +0000 Subject: [PATCH 43/93] Add a docstring to action_toggle_node I want to add docstrings to the actions I'm adding as part of #1700, so with this in mind I'm going to add docstrings to all the actions. --- src/textual/widgets/_tree.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index b3d85cf6a..5a3e0939b 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -1015,6 +1015,13 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): self._toggle_node(line.path[-1]) def action_select_cursor(self) -> None: + """Cause a select event for the target node. + + Note: + If `auto_expand` is `True` use of this action on a non-leaf node + will cause both an expand/collapse event to occour, as well as a + selected event. + """ try: line = self._tree_lines[self.cursor_line] except IndexError: From ca224b76aba5788929ad754ed809d89af5d2c626 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 31 Jan 2023 12:50:12 +0000 Subject: [PATCH 44/93] Add a docstring to action_toggle_node --- src/textual/widgets/_tree.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index 5a3e0939b..6c4e4aa0a 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -1007,6 +1007,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): self.scroll_to_line(self.cursor_line) def action_toggle_node(self) -> None: + """Toggle the expanded state of the target node.""" try: line = self._tree_lines[self.cursor_line] except IndexError: From 93d4863e0b891394bd1c9a741485c6063266c880 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 31 Jan 2023 12:50:40 +0000 Subject: [PATCH 45/93] Add a docstring to action_cursor_up --- src/textual/widgets/_tree.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index 6c4e4aa0a..ff60da1f6 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -973,6 +973,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): self._invalidate() def action_cursor_up(self) -> None: + """Move the cursor up one node.""" if self.cursor_line == -1: self.cursor_line = self.last_line else: From 84fa94978e2efa081dbe3020323acdf4bb90c591 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 31 Jan 2023 12:51:04 +0000 Subject: [PATCH 46/93] Add a docstring to action_cursor_down --- src/textual/widgets/_tree.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index ff60da1f6..1c0465c6d 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -981,6 +981,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): self.scroll_to_line(self.cursor_line) def action_cursor_down(self) -> None: + """Move the cursor down one node.""" if self.cursor_line == -1: self.cursor_line = 0 else: From 17d84bc75be66c752cfad219df5781e59f58a140 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 31 Jan 2023 12:51:41 +0000 Subject: [PATCH 47/93] Add a docstring to action_page_down --- src/textual/widgets/_tree.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index 1c0465c6d..5fef9e912 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -989,6 +989,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): self.scroll_to_line(self.cursor_line) def action_page_down(self) -> None: + """Move the cursor down a page's-worth of nodes.""" if self.cursor_line == -1: self.cursor_line = 0 self.cursor_line += self.scrollable_content_region.height - 1 From b2aecfa847aea146a938cc904f9b9a727a9af7e0 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 31 Jan 2023 13:00:06 +0000 Subject: [PATCH 48/93] Add a docstring to action_page_up --- src/textual/widgets/_tree.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index 5fef9e912..4f3c731a0 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -996,6 +996,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): self.scroll_to_line(self.cursor_line) def action_page_up(self) -> None: + """Move the cursor up a page's-worth of nodes.""" if self.cursor_line == -1: self.cursor_line = self.last_line self.cursor_line -= self.scrollable_content_region.height - 1 From ca773f4350f0a3134ee56bfa9c5e84e25a74fa7e Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 31 Jan 2023 13:02:34 +0000 Subject: [PATCH 49/93] Add a docstring to action_scroll_home --- src/textual/widgets/_tree.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index 4f3c731a0..18464a018 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -1003,6 +1003,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): self.scroll_to_line(self.cursor_line) def action_scroll_home(self) -> None: + """Move the cursor to the top of the tree.""" self.cursor_line = 0 self.scroll_to_line(self.cursor_line) From 9e406f525e04c9b3f85f88a6ca33aeb3e205d74f Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 31 Jan 2023 13:03:23 +0000 Subject: [PATCH 50/93] Add a docstring to action_scroll_end --- src/textual/widgets/_tree.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index 18464a018..0d6ac4baf 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -1008,6 +1008,11 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): self.scroll_to_line(self.cursor_line) def action_scroll_end(self) -> None: + """Move the cursor to the bottom of the tree. + + Note: + Here button means vertically, not branch depth. + """ self.cursor_line = self.last_line self.scroll_to_line(self.cursor_line) From 606af8d3a28da1596d19f5ef188417ba7b0265a5 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 31 Jan 2023 13:12:03 +0000 Subject: [PATCH 51/93] Correct a typo in the docstring of action_scroll_end --- src/textual/widgets/_tree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index 0d6ac4baf..415cb9e4e 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -1011,7 +1011,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): """Move the cursor to the bottom of the tree. Note: - Here button means vertically, not branch depth. + Here bottom means vertically, not branch depth. """ self.cursor_line = self.last_line self.scroll_to_line(self.cursor_line) From 2086b713dbc5416f133955ff63e9b890109c6b0b Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 31 Jan 2023 13:17:58 +0000 Subject: [PATCH 52/93] Update the CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3e3694a5..1715cad83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added more keyboard actions and related bindings to `Input` https://github.com/Textualize/textual/pull/1676 - Added App.scroll_sensitivity_x and App.scroll_sensitivity_y to adjust how many lines the scroll wheel moves the scroll position https://github.com/Textualize/textual/issues/928 - Added Shift+scroll wheel and ctrl+scroll wheel to scroll horizontally +- Added `Tree.action_toggle_node` to toggle a node without selecting, and bound it to Space https://github.com/Textualize/textual/issues/1433 ### Changed From 6db5217f3e6fa9cb4780ee582daa6d4f18eb3a45 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 31 Jan 2023 13:32:46 +0000 Subject: [PATCH 53/93] Modify Tree message unit tests to take new action in to account See #1433. --- tests/tree/test_tree_messages.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tests/tree/test_tree_messages.py b/tests/tree/test_tree_messages.py index 67620d70e..df7442d31 100644 --- a/tests/tree/test_tree_messages.py +++ b/tests/tree/test_tree_messages.py @@ -48,23 +48,26 @@ async def test_tree_node_selected_message() -> None: assert pilot.app.messages == ["NodeExpanded", "NodeSelected"] +async def test_tree_node_selected_message_no_auto() -> None: + """Selecting a node should result in only a selected message being emitted.""" + async with TreeApp().run_test() as pilot: + pilot.app.query_one(MyTree).auto_expand = False + await pilot.press("enter") + assert pilot.app.messages == ["NodeSelected"] + + async def test_tree_node_expanded_message() -> None: """Expanding a node should result in an expanded message being emitted.""" async with TreeApp().run_test() as pilot: - await pilot.press("enter") - assert pilot.app.messages == ["NodeExpanded", "NodeSelected"] + await pilot.press("space") + assert pilot.app.messages == ["NodeExpanded"] async def test_tree_node_collapsed_message() -> None: """Collapsing a node should result in a collapsed message being emitted.""" async with TreeApp().run_test() as pilot: - await pilot.press("enter", "enter") - assert pilot.app.messages == [ - "NodeExpanded", - "NodeSelected", - "NodeCollapsed", - "NodeSelected", - ] + await pilot.press("space", "space") + assert pilot.app.messages == ["NodeExpanded", "NodeCollapsed"] async def test_tree_node_highlighted_message() -> None: From 1cc5b3799cb70e1cb75eb287c769fb4008df94c4 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 31 Jan 2023 14:45:28 +0000 Subject: [PATCH 54/93] Fix wording of entry in CHANGELOG to match lines around it --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3e3694a5..daf03ecc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Programmatically setting `overflow_x`/`overflow_y` refreshes the layout correctly https://github.com/Textualize/textual/issues/1616 - Fixed double-paste into `Input` https://github.com/Textualize/textual/issues/1657 - Added a workaround for an apparent Windows Terminal paste issue https://github.com/Textualize/textual/issues/1661 -- Fixes issue with renderable width calculation https://github.com/Textualize/textual/issues/1685 +- Fixed issue with renderable width calculation https://github.com/Textualize/textual/issues/1685 - Fixed issue with app not processing Paste event https://github.com/Textualize/textual/issues/1666 ## [0.10.1] - 2023-01-20 From c79f19c1c78e583bd4d881a83d28cca174b14f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 31 Jan 2023 15:07:30 +0000 Subject: [PATCH 55/93] Rename attribute. --- src/textual/css/_style_properties.py | 33 +++++++++++++++------------- src/textual/css/styles.py | 22 +++++++++++-------- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/src/textual/css/_style_properties.py b/src/textual/css/_style_properties.py index c06313e63..882be31cf 100644 --- a/src/textual/css/_style_properties.py +++ b/src/textual/css/_style_properties.py @@ -65,15 +65,18 @@ class GenericProperty(Generic[PropertyGetType, PropertySetType]): Args: default: The default value (or a factory thereof) of the property. layout: Whether to refresh the node layout on value change. - children: Whether to refresh the node children on value change. + refresh_children: Whether to refresh the node children on value change. """ def __init__( - self, default: PropertyGetType, layout: bool = False, children: bool = False + self, + default: PropertyGetType, + layout: bool = False, + refresh_children: bool = False, ) -> None: self.default = default self.layout = layout - self.children = children + self.refresh_children = refresh_children def validate_value(self, value: object) -> PropertyGetType: """Validate the setter value. @@ -99,11 +102,11 @@ class GenericProperty(Generic[PropertyGetType, PropertySetType]): _rich_traceback_omit = True if value is None: obj.clear_rule(self.name) - obj.refresh(layout=self.layout, children=self.children) + obj.refresh(layout=self.layout, children=self.refresh_children) return new_value = self.validate_value(value) if obj.set_rule(self.name, new_value): - obj.refresh(layout=self.layout, children=self.children) + obj.refresh(layout=self.layout, children=self.refresh_children) class IntegerProperty(GenericProperty[int, int]): @@ -216,13 +219,13 @@ class ScalarListProperty: """Descriptor for lists of scalars. Args: - children: Whether to refresh the node children on value change. percent_unit: The dimension to which percentage scalars will be relative to. + refresh_children: Whether to refresh the node children on value change. """ - def __init__(self, percent_unit: Unit, children: bool = False) -> None: + def __init__(self, percent_unit: Unit, refresh_children: bool = False) -> None: self.percent_unit = percent_unit - self.children = children + self.refresh_children = refresh_children def __set_name__(self, owner: Styles, name: str) -> None: self.name = name @@ -237,7 +240,7 @@ class ScalarListProperty: ) -> None: if value is None: obj.clear_rule(self.name) - obj.refresh(layout=True, children=self.children) + obj.refresh(layout=True, children=self.refresh_children) return parse_values: Iterable[str | float] if isinstance(value, str): @@ -256,7 +259,7 @@ class ScalarListProperty: else parse_value ) if obj.set_rule(self.name, tuple(scalars)): - obj.refresh(layout=True, children=self.children) + obj.refresh(layout=True, children=self.refresh_children) class BoxProperty: @@ -706,7 +709,7 @@ class StringEnumProperty: valid_values: The set of valid values that the descriptor can take. default: The default value (or a factory thereof) of the property. layout: Whether to refresh the node layout on value change. - children: Whether to refresh the node children on value change. + refresh_children: Whether to refresh the node children on value change. """ def __init__( @@ -714,12 +717,12 @@ class StringEnumProperty: valid_values: set[str], default: str, layout: bool = False, - children: bool = False, + refresh_children: bool = False, ) -> None: self._valid_values = valid_values self._default = default self._layout = layout - self._children = children + self._refresh_children = refresh_children def __set_name__(self, owner: StylesBase, name: str) -> None: self.name = name @@ -753,7 +756,7 @@ class StringEnumProperty: if value is None: if obj.clear_rule(self.name): self._before_refresh(obj, value) - obj.refresh(layout=self._layout, children=self._children) + obj.refresh(layout=self._layout, children=self._refresh_children) else: if value not in self._valid_values: raise StyleValueError( @@ -766,7 +769,7 @@ class StringEnumProperty: ) if obj.set_rule(self.name, value): self._before_refresh(obj, value) - obj.refresh(layout=self._layout, children=self._children) + obj.refresh(layout=self._layout, children=self._refresh_children) class OverflowProperty(StringEnumProperty): diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index a7021a5b8..0bbb66823 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -266,17 +266,17 @@ class StylesBase(ABC): scrollbar_background_active = ColorProperty("black") scrollbar_gutter = StringEnumProperty( - VALID_SCROLLBAR_GUTTER, "auto", layout=True, children=True + VALID_SCROLLBAR_GUTTER, "auto", layout=True, refresh_children=True ) scrollbar_size_vertical = IntegerProperty(default=1, layout=True) scrollbar_size_horizontal = IntegerProperty(default=1, layout=True) align_horizontal = StringEnumProperty( - VALID_ALIGN_HORIZONTAL, "left", layout=True, children=True + VALID_ALIGN_HORIZONTAL, "left", layout=True, refresh_children=True ) align_vertical = StringEnumProperty( - VALID_ALIGN_VERTICAL, "top", layout=True, children=True + VALID_ALIGN_VERTICAL, "top", layout=True, refresh_children=True ) align = AlignProperty() @@ -284,13 +284,17 @@ class StylesBase(ABC): content_align_vertical = StringEnumProperty(VALID_ALIGN_VERTICAL, "top") content_align = AlignProperty() - grid_rows = ScalarListProperty(percent_unit=Unit.HEIGHT, children=True) - grid_columns = ScalarListProperty(percent_unit=Unit.WIDTH, children=True) + grid_rows = ScalarListProperty(percent_unit=Unit.HEIGHT, refresh_children=True) + grid_columns = ScalarListProperty(percent_unit=Unit.WIDTH, refresh_children=True) - grid_size_columns = IntegerProperty(default=1, layout=True, children=True) - grid_size_rows = IntegerProperty(default=0, layout=True, children=True) - grid_gutter_horizontal = IntegerProperty(default=0, layout=True, children=True) - grid_gutter_vertical = IntegerProperty(default=0, layout=True, children=True) + grid_size_columns = IntegerProperty(default=1, layout=True, refresh_children=True) + grid_size_rows = IntegerProperty(default=0, layout=True, refresh_children=True) + grid_gutter_horizontal = IntegerProperty( + default=0, layout=True, refresh_children=True + ) + grid_gutter_vertical = IntegerProperty( + default=0, layout=True, refresh_children=True + ) row_span = IntegerProperty(default=1, layout=True) column_span = IntegerProperty(default=1, layout=True) From 6e1f18910a249b51540bae115fbcd19cd7e749ea Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 31 Jan 2023 15:51:31 +0000 Subject: [PATCH 56/93] Keep a visible, but darker, cursor in Tree when it doesn't have focus See #1471. It seems useful and important for a Tree to still have a cursor visible even when it doesn't have focus -- that ways someone can build a UI where the user can tab away from the tree to something that relates to the node, and still see which node is in play. --- src/textual/widgets/_tree.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index 415cb9e4e..2f3d073cf 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -341,11 +341,15 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): } Tree > .tree--cursor { - background: $secondary; + background: $secondary-darken-2; color: $text; text-style: bold; } + Tree:focus > .tree--cursor { + background: $secondary; + } + Tree > .tree--highlight { text-style: underline; } @@ -925,7 +929,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): label_style += self.get_component_rich_style( "tree--highlight", partial=True ) - if self.cursor_line == y and self.has_focus: + if self.cursor_line == y: label_style += self.get_component_rich_style( "tree--cursor", partial=False ) From f2e756b69539857d6fc731d9f64e193343281b0a Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 31 Jan 2023 15:54:27 +0000 Subject: [PATCH 57/93] Update the CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5168a2032..66033a6e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed - Breaking change: `TreeNode` can no longer be imported from `textual.widgets`; it is now available via `from textual.widgets.tree import TreeNode`. https://github.com/Textualize/textual/pull/1637 +- `Tree` now shows a (subdued) cursor for a highlighted node when focus has moved elsewhere https://github.com/Textualize/textual/issues/1471 ### Fixed From b0a29050cf5498a15525f1c2c9e1ad8e6738cb3c Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 31 Jan 2023 16:20:41 +0000 Subject: [PATCH 58/93] Update snapshots after change of unfocused tree cursor handling --- .../__snapshots__/test_snapshots.ambr | 113 +++++++++--------- 1 file changed, 57 insertions(+), 56 deletions(-) diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr index d347bcc03..7b569f3fe 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr +++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr @@ -14147,132 +14147,133 @@ font-weight: 700; } - .terminal-2744759648-matrix { + .terminal-1336653930-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2744759648-title { + .terminal-1336653930-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2744759648-r1 { fill: #e2e3e3 } - .terminal-2744759648-r2 { fill: #c5c8c6 } - .terminal-2744759648-r3 { fill: #008139 } + .terminal-1336653930-r1 { fill: #e2e3e3 } + .terminal-1336653930-r2 { fill: #1a1000;font-weight: bold } + .terminal-1336653930-r3 { fill: #c5c8c6 } + .terminal-1336653930-r4 { fill: #008139 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TreeApp + TreeApp - - - - ▼ Dune - └── ▼ Characters - ├── Paul - ├── Jessica - └── Chani - - - - - - - - - - - - - - - - - - + + + + ▼ Dune + └── ▼ Characters + ├── Paul + ├── Jessica + └── Chani + + + + + + + + + + + + + + + + + + From c14f6352447739a509d29f6915b95e555d1735fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 31 Jan 2023 17:31:06 +0000 Subject: [PATCH 59/93] Document setting app (sub)title. --- docs/examples/app/question_title01.py | 22 ++++++++++++++++ docs/examples/app/question_title02.py | 27 +++++++++++++++++++ docs/guide/app.md | 37 ++++++++++++++++++++++++++- 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 docs/examples/app/question_title01.py create mode 100644 docs/examples/app/question_title02.py diff --git a/docs/examples/app/question_title01.py b/docs/examples/app/question_title01.py new file mode 100644 index 000000000..e597a4746 --- /dev/null +++ b/docs/examples/app/question_title01.py @@ -0,0 +1,22 @@ +from textual.app import App, ComposeResult +from textual.widgets import Button, Header, Label + + +class MyApp(App[str]): + TITLE = "A Question App" + SUB_TITLE = "The most important question" + + def compose(self) -> ComposeResult: + yield Header() + yield Label("Do you love Textual?") + yield Button("Yes", id="yes", variant="primary") + yield Button("No", id="no", variant="error") + + def on_button_pressed(self, event: Button.Pressed) -> None: + self.exit(event.button.id) + + +if __name__ == "__main__": + app = MyApp() + reply = app.run() + print(reply) diff --git a/docs/examples/app/question_title02.py b/docs/examples/app/question_title02.py new file mode 100644 index 000000000..91ebf96ab --- /dev/null +++ b/docs/examples/app/question_title02.py @@ -0,0 +1,27 @@ +from textual.app import App, ComposeResult +from textual.events import Key +from textual.widgets import Button, Header, Label + + +class MyApp(App[str]): + TITLE = "A Question App" + SUB_TITLE = "The most important question" + + def compose(self) -> ComposeResult: + yield Header() + yield Label("Do you love Textual?") + yield Button("Yes", id="yes", variant="primary") + yield Button("No", id="no", variant="error") + + def on_button_pressed(self, event: Button.Pressed) -> None: + self.exit(event.button.id) + + def on_key(self, event: Key): + self.title = event.key + self.sub_title = f"You just pressed {event.key}!" + + +if __name__ == "__main__": + app = MyApp() + reply = app.run() + print(reply) diff --git a/docs/guide/app.md b/docs/guide/app.md index be8a3fa5a..a086b48c1 100644 --- a/docs/guide/app.md +++ b/docs/guide/app.md @@ -38,6 +38,7 @@ If you hit ++ctrl+c++ Textual will exit application mode and return you to the c A side effect of application mode is that you may no longer be able to select and copy text in the usual way. Terminals typically offer a way to bypass this limit with a key modifier. On iTerm you can select text if you hold the ++option++ key. See the documentation for your terminal software for how to select text in application mode. + ## Events Textual has an event system you can use to respond to key presses, mouse actions, and internal state changes. Event handlers are methods prefixed with `on_` followed by the name of the event. @@ -147,7 +148,39 @@ The addition of `[str]` tells mypy that `run()` is expected to return a string. Type annotations are entirely optional (but recommended) with Textual. -## CSS +## Customising your app + +### Setting the title and subtitle + +By default, the title of your application matches the name of the application class and the subtitle is empty. +However, you can override those defaults by setting the class attributes `TITLE` and `SUB_TITLE`, as shown below. +We also make use of the built-in widget `Header` to make the title and the subtitle visible in our app. + +```py title="question_title01.py" hl_lines="6-7 10" +--8<-- "docs/examples/app/question_title01.py" +``` + +The app title and subtitle are displayed at the top of the application in the header: + +```{.textual path="docs/examples/app/question_title01.py"} +``` + +On top of being able to define a default title and a default subtitle for a given application class, each _instance_ of your application class has two attributes `title` and `sub_title` that can be used to modify the title and subtitle of said instance application. + +For example, the application shown below changes its title and subtitle as soon as the application is instantiated. + +```py title="question_title02.py" hl_lines="19-21" +--8<-- "docs/examples/app/question_title02.py" +``` + +If you run the app shown above and if you press a key, the title and subtitle update accordingly. + +For example, if you press `t`, your application will look as shown below: + +```{.textual path="docs/examples/app/question_title02.py" press="t"} +``` + +### CSS Textual apps can reference [CSS](CSS.md) files which define how your app and widgets will look, while keeping your Python code free of display related code (which tends to be messy). @@ -170,6 +203,7 @@ When `"question02.py"` runs it will load `"question02.css"` and update the app a ```{.textual path="docs/examples/app/question02.py"} ``` + ### Classvar CSS While external CSS files are recommended for most applications, and enable some cool features like *live editing*, you can also specify the CSS directly within the Python code. @@ -182,6 +216,7 @@ Here's the question app with classvar CSS: --8<-- "docs/examples/app/question03.py" ``` + ## What's next In the following chapter we will learn more about how to apply styles to your widgets and app. From 828d8d2cf1688344a6c7481449b73542404f063a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 31 Jan 2023 17:31:36 +0000 Subject: [PATCH 60/93] Replace Static with Label. --- docs/examples/app/question01.py | 4 ++-- docs/examples/app/question02.py | 4 ++-- docs/examples/app/question03.py | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/examples/app/question01.py b/docs/examples/app/question01.py index e61fba393..04c4e8689 100644 --- a/docs/examples/app/question01.py +++ b/docs/examples/app/question01.py @@ -1,10 +1,10 @@ from textual.app import App, ComposeResult -from textual.widgets import Static, Button +from textual.widgets import Label, Button class QuestionApp(App[str]): def compose(self) -> ComposeResult: - yield Static("Do you love Textual?") + yield Label("Do you love Textual?") yield Button("Yes", id="yes", variant="primary") yield Button("No", id="no", variant="error") diff --git a/docs/examples/app/question02.py b/docs/examples/app/question02.py index 36b23722b..65eb79f65 100644 --- a/docs/examples/app/question02.py +++ b/docs/examples/app/question02.py @@ -1,12 +1,12 @@ from textual.app import App, ComposeResult -from textual.widgets import Static, Button +from textual.widgets import Label, Button class QuestionApp(App[str]): CSS_PATH = "question02.css" def compose(self) -> ComposeResult: - yield Static("Do you love Textual?", id="question") + yield Label("Do you love Textual?", id="question") yield Button("Yes", id="yes", variant="primary") yield Button("No", id="no", variant="error") diff --git a/docs/examples/app/question03.py b/docs/examples/app/question03.py index 777e5a9eb..6fc372ded 100644 --- a/docs/examples/app/question03.py +++ b/docs/examples/app/question03.py @@ -1,5 +1,5 @@ from textual.app import App, ComposeResult -from textual.widgets import Static, Button +from textual.widgets import Label, Button class QuestionApp(App[str]): @@ -7,8 +7,8 @@ class QuestionApp(App[str]): Screen { layout: grid; grid-size: 2; - grid-gutter: 2; - padding: 2; + grid-gutter: 2; + padding: 2; } #question { width: 100%; @@ -16,7 +16,7 @@ class QuestionApp(App[str]): column-span: 2; content-align: center bottom; text-style: bold; - } + } Button { width: 100%; @@ -24,7 +24,7 @@ class QuestionApp(App[str]): """ def compose(self) -> ComposeResult: - yield Static("Do you love Textual?", id="question") + yield Label("Do you love Textual?", id="question") yield Button("Yes", id="yes", variant="primary") yield Button("No", id="no", variant="error") From 521e90488ce184e1cef7af70c57be52894a42fdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 1 Feb 2023 11:08:13 +0000 Subject: [PATCH 61/93] Add context about (sub)title. --- docs/guide/app.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/guide/app.md b/docs/guide/app.md index a086b48c1..81e6180c8 100644 --- a/docs/guide/app.md +++ b/docs/guide/app.md @@ -150,11 +150,14 @@ The addition of `[str]` tells mypy that `run()` is expected to return a string. ## Customising your app -### Setting the title and subtitle +### Title and subtitle + +Textual applications have a `title` attribute that represents the name of your application and a `sub_title` attribute that gives additional context for it. +These attributes can be displayed to the user if your application has a widget `Header` and there are two ways in which you can customize them if you don't want to run with the default values. By default, the title of your application matches the name of the application class and the subtitle is empty. However, you can override those defaults by setting the class attributes `TITLE` and `SUB_TITLE`, as shown below. -We also make use of the built-in widget `Header` to make the title and the subtitle visible in our app. +Remember that you need the built-in widget `Header` to make the title and the subtitle visible in our app. ```py title="question_title01.py" hl_lines="6-7 10" --8<-- "docs/examples/app/question_title01.py" From d1790caff771b235f00bbbe025eaf504e308f8d6 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Wed, 1 Feb 2023 12:25:00 +0100 Subject: [PATCH 62/93] Fix typo --- docs/widgets/static.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/widgets/static.md b/docs/widgets/static.md index c8e41606f..9c01d6c1c 100644 --- a/docs/widgets/static.md +++ b/docs/widgets/static.md @@ -1,7 +1,7 @@ # Static A widget which displays static content. -Can be used for Rich renderables and can also for the base for other types of widgets. +Can be used for Rich renderables and can also be the base for other types of widgets. - [ ] Focusable - [ ] Container From 476eca7a6e40dab5dd4f702b8daf1c53a9cc4463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 1 Feb 2023 11:30:56 +0000 Subject: [PATCH 63/93] Shuffle sections around. --- docs/guide/app.md | 74 +++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/docs/guide/app.md b/docs/guide/app.md index 81e6180c8..d5dc9f76a 100644 --- a/docs/guide/app.md +++ b/docs/guide/app.md @@ -117,7 +117,7 @@ When you first run this you will get a blank screen. Press any key to add the we ```{.textual path="docs/examples/app/widgets02.py" press="a,a,a,down,down,down,down,down,down,_,_,_,_,_,_"} ``` -### Exiting +## Exiting An app will run until you call [App.exit()][textual.app.App.exit] which will exit application mode and the [run][textual.app.App.run] method will return. If this is the last line in your code you will return to the command prompt. @@ -134,7 +134,7 @@ Running this app will give you the following: Clicking either of those buttons will exit the app, and the `run()` method will return either `"yes"` or `"no"` depending on button clicked. -#### Return type +### Return type You may have noticed that we subclassed `App[str]` rather than the usual `App`. @@ -148,42 +148,8 @@ The addition of `[str]` tells mypy that `run()` is expected to return a string. Type annotations are entirely optional (but recommended) with Textual. -## Customising your app -### Title and subtitle - -Textual applications have a `title` attribute that represents the name of your application and a `sub_title` attribute that gives additional context for it. -These attributes can be displayed to the user if your application has a widget `Header` and there are two ways in which you can customize them if you don't want to run with the default values. - -By default, the title of your application matches the name of the application class and the subtitle is empty. -However, you can override those defaults by setting the class attributes `TITLE` and `SUB_TITLE`, as shown below. -Remember that you need the built-in widget `Header` to make the title and the subtitle visible in our app. - -```py title="question_title01.py" hl_lines="6-7 10" ---8<-- "docs/examples/app/question_title01.py" -``` - -The app title and subtitle are displayed at the top of the application in the header: - -```{.textual path="docs/examples/app/question_title01.py"} -``` - -On top of being able to define a default title and a default subtitle for a given application class, each _instance_ of your application class has two attributes `title` and `sub_title` that can be used to modify the title and subtitle of said instance application. - -For example, the application shown below changes its title and subtitle as soon as the application is instantiated. - -```py title="question_title02.py" hl_lines="19-21" ---8<-- "docs/examples/app/question_title02.py" -``` - -If you run the app shown above and if you press a key, the title and subtitle update accordingly. - -For example, if you press `t`, your application will look as shown below: - -```{.textual path="docs/examples/app/question_title02.py" press="t"} -``` - -### CSS +## CSS Textual apps can reference [CSS](CSS.md) files which define how your app and widgets will look, while keeping your Python code free of display related code (which tends to be messy). @@ -220,6 +186,40 @@ Here's the question app with classvar CSS: ``` +## Title and subtitle + +Textual applications have a `title` attribute that represents the name of your application and a `sub_title` attribute that gives additional context for it. +These attributes can be displayed to the user if your application has a widget `Header` and there are two ways in which you can customize them if you don't want to run with the default values. + +By default, the title of your application matches the name of the application class and the subtitle is empty. +However, you can override those defaults by setting the class attributes `TITLE` and `SUB_TITLE`, as shown below. +Remember that you need the built-in widget `Header` to make the title and the subtitle visible in our app. + +```py title="question_title01.py" hl_lines="6-7 10" +--8<-- "docs/examples/app/question_title01.py" +``` + +The app title and subtitle are displayed at the top of the application in the header: + +```{.textual path="docs/examples/app/question_title01.py"} +``` + +On top of being able to define a default title and a default subtitle for a given application class, each _instance_ of your application class has two attributes `title` and `sub_title` that can be used to modify the title and subtitle of said instance application. + +For example, the application shown below changes its title and subtitle as soon as the application is instantiated. + +```py title="question_title02.py" hl_lines="19-21" +--8<-- "docs/examples/app/question_title02.py" +``` + +If you run the app shown above and if you press a key, the title and subtitle update accordingly. + +For example, if you press `t`, your application will look as shown below: + +```{.textual path="docs/examples/app/question_title02.py" press="t"} +``` + + ## What's next In the following chapter we will learn more about how to apply styles to your widgets and app. From 7563d4cb7e9af13e8f25878c73a8ea549838a53e Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Wed, 1 Feb 2023 12:33:36 +0000 Subject: [PATCH 64/93] Allow setting a new label when performing a clear on a Tree See #1437 for background. While it would be ideal to allow for the complete emptying of a Tree, the root node is required (and it's part of the construction of a Tree). So, here, when clearing the Tree we optionally allow for a new label to be given. Ideally we'll also allow for fresh data to be provided too; but there's a wrinkle there in knowing the difference between the data being None, and no data being provided (so the current root's data being carried over). Following the method of defaulting used in __init__ would cause problems. As such, rather than roll all of this into one commit, this goes with the basic requirement and the solution for data will follow. Note this also starts some unit tests for the clearing of a Tree. --- src/textual/widgets/_tree.py | 11 ++++-- tests/tree/test_tree_clearing.py | 62 ++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 tests/tree/test_tree_clearing.py diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index 2f3d073cf..8f2304ae8 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -560,12 +560,17 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): label = self.render_label(node, NULL_STYLE, NULL_STYLE) return label.cell_len - def clear(self) -> None: - """Clear all nodes under root.""" + def clear(self, label: TextType | None = None) -> None: + """Clear all nodes under root. + + Args: + label: An optional new label for the root node. If not provided + the current root node's label will be used. + """ self._line_cache.clear() self._tree_lines_cached = None self._current_id = 0 - root_label = self.root._label + root_label = self.root._label if label is None else label root_data = self.root.data self.root = TreeNode( self, diff --git a/tests/tree/test_tree_clearing.py b/tests/tree/test_tree_clearing.py new file mode 100644 index 000000000..56a4515d6 --- /dev/null +++ b/tests/tree/test_tree_clearing.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +from textual.app import App, ComposeResult +from textual.widgets import Tree + + +class VerseBody: + pass + + +class VerseStar(VerseBody): + pass + + +class VersePlanet(VerseBody): + pass + + +class VerseMoon(VerseBody): + pass + + +class TestTree(Tree[VerseBody]): + pass + + +class TreeClearApp(App[None]): + """Tree clearing test app.""" + + def compose(self) -> ComposeResult: + yield TestTree("White Sun", data=VerseStar()) + + def on_mount(self) -> None: + tree = self.query_one(TestTree) + node = tree.root.add("Londinium", VersePlanet()) + node.add_leaf("Balkerne", VerseMoon()) + node.add_leaf("Colchester", VerseMoon()) + node = tree.root.add("Sihnon", VersePlanet()) + node.add_leaf("Airen", VerseMoon()) + node.add_leaf("Xiaojie", VerseMoon()) + + +async def test_tree_simple_clear() -> None: + """Clearing a tree should keep the old label and data.""" + async with TreeClearApp().run_test() as pilot: + tree = pilot.app.query_one(TestTree) + assert len(tree.root.children) > 1 + pilot.app.query_one(TestTree).clear() + assert len(tree.root.children) == 0 + assert str(tree.root.label) == "White Sun" + assert isinstance(tree.root.data, VerseStar) + + +async def test_tree_new_label_clear() -> None: + """Clearing a tree with a new label should use the new label and keep the old data.""" + async with TreeClearApp().run_test() as pilot: + tree = pilot.app.query_one(TestTree) + assert len(tree.root.children) > 1 + pilot.app.query_one(TestTree).clear("Jiangyin") + assert len(tree.root.children) == 0 + assert str(tree.root.label) == "Jiangyin" + assert isinstance(tree.root.data, VerseStar) From ff7f2488f8c9383880cca13cd38c04e45218f2af Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Wed, 1 Feb 2023 12:45:18 +0000 Subject: [PATCH 65/93] Remove Tree class name clash with how pytest works Just before doing the commit I decided to rename the test tree in the new unit test for clearing down a tree. Of course I managed to name it in such a way that it becomes special to pytest. This fixes that. --- tests/tree/test_tree_clearing.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/tree/test_tree_clearing.py b/tests/tree/test_tree_clearing.py index 56a4515d6..1a1efa449 100644 --- a/tests/tree/test_tree_clearing.py +++ b/tests/tree/test_tree_clearing.py @@ -20,7 +20,7 @@ class VerseMoon(VerseBody): pass -class TestTree(Tree[VerseBody]): +class VerseTree(Tree[VerseBody]): pass @@ -28,10 +28,10 @@ class TreeClearApp(App[None]): """Tree clearing test app.""" def compose(self) -> ComposeResult: - yield TestTree("White Sun", data=VerseStar()) + yield VerseTree("White Sun", data=VerseStar()) def on_mount(self) -> None: - tree = self.query_one(TestTree) + tree = self.query_one(VerseTree) node = tree.root.add("Londinium", VersePlanet()) node.add_leaf("Balkerne", VerseMoon()) node.add_leaf("Colchester", VerseMoon()) @@ -43,9 +43,9 @@ class TreeClearApp(App[None]): async def test_tree_simple_clear() -> None: """Clearing a tree should keep the old label and data.""" async with TreeClearApp().run_test() as pilot: - tree = pilot.app.query_one(TestTree) + tree = pilot.app.query_one(VerseTree) assert len(tree.root.children) > 1 - pilot.app.query_one(TestTree).clear() + pilot.app.query_one(VerseTree).clear() assert len(tree.root.children) == 0 assert str(tree.root.label) == "White Sun" assert isinstance(tree.root.data, VerseStar) @@ -54,9 +54,9 @@ async def test_tree_simple_clear() -> None: async def test_tree_new_label_clear() -> None: """Clearing a tree with a new label should use the new label and keep the old data.""" async with TreeClearApp().run_test() as pilot: - tree = pilot.app.query_one(TestTree) + tree = pilot.app.query_one(VerseTree) assert len(tree.root.children) > 1 - pilot.app.query_one(TestTree).clear("Jiangyin") + pilot.app.query_one(VerseTree).clear("Jiangyin") assert len(tree.root.children) == 0 assert str(tree.root.label) == "Jiangyin" assert isinstance(tree.root.data, VerseStar) From 78f49f0ab2d87ec58589bc0e1082885f6e7c05db Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Wed, 1 Feb 2023 13:44:53 +0000 Subject: [PATCH 66/93] Allow replacement of the root node data when clearing the tree --- src/textual/widgets/_tree.py | 6 ++++-- tests/tree/test_tree_clearing.py | 24 +++++++++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index 8f2304ae8..f299c0d99 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -560,18 +560,20 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): label = self.render_label(node, NULL_STYLE, NULL_STYLE) return label.cell_len - def clear(self, label: TextType | None = None) -> None: + def clear(self, label: TextType | None = None, data: TreeDataType = ...) -> None: """Clear all nodes under root. Args: label: An optional new label for the root node. If not provided the current root node's label will be used. + data: Optional new data for the root node. If not provided the + current root node's data will be used. """ self._line_cache.clear() self._tree_lines_cached = None self._current_id = 0 root_label = self.root._label if label is None else label - root_data = self.root.data + root_data = self.root.data if data is ... else data self.root = TreeNode( self, None, diff --git a/tests/tree/test_tree_clearing.py b/tests/tree/test_tree_clearing.py index 1a1efa449..9b7762bbf 100644 --- a/tests/tree/test_tree_clearing.py +++ b/tests/tree/test_tree_clearing.py @@ -56,7 +56,29 @@ async def test_tree_new_label_clear() -> None: async with TreeClearApp().run_test() as pilot: tree = pilot.app.query_one(VerseTree) assert len(tree.root.children) > 1 - pilot.app.query_one(VerseTree).clear("Jiangyin") + pilot.app.query_one(VerseTree).clear(label="Jiangyin") assert len(tree.root.children) == 0 assert str(tree.root.label) == "Jiangyin" assert isinstance(tree.root.data, VerseStar) + + +async def test_tree_new_data_clear() -> None: + """Clearing a tree with data should keep the old label and use the new data.""" + async with TreeClearApp().run_test() as pilot: + tree = pilot.app.query_one(VerseTree) + assert len(tree.root.children) > 1 + pilot.app.query_one(VerseTree).clear(data=VersePlanet()) + assert len(tree.root.children) == 0 + assert str(tree.root.label) == "White Sun" + assert isinstance(tree.root.data, VersePlanet) + + +async def test_tree_new_labal_and_data_clear() -> None: + """Clearing a tree with label and data should replace the label and data.""" + async with TreeClearApp().run_test() as pilot: + tree = pilot.app.query_one(VerseTree) + assert len(tree.root.children) > 1 + pilot.app.query_one(VerseTree).clear(label="Jiangyin", data=VersePlanet()) + assert len(tree.root.children) == 0 + assert str(tree.root.label) == "Jiangyin" + assert isinstance(tree.root.data, VersePlanet) From 8b4a833e269700b5514a0808ed9a9d6e26ead7c1 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Wed, 1 Feb 2023 13:47:48 +0000 Subject: [PATCH 67/93] Update the CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2abe8f5d7..4410a7fbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added App.scroll_sensitivity_x and App.scroll_sensitivity_y to adjust how many lines the scroll wheel moves the scroll position https://github.com/Textualize/textual/issues/928 - Added Shift+scroll wheel and ctrl+scroll wheel to scroll horizontally - Added `Tree.action_toggle_node` to toggle a node without selecting, and bound it to Space https://github.com/Textualize/textual/issues/1433 +- Added the ability to set new root node label and/or data when calling `Tree.clear` https://github.com/Textualize/textual/issues/1437 ### Changed From 2140aa52d26a340bf76d42f806ad896bda1d606f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 1 Feb 2023 14:53:20 +0000 Subject: [PATCH 68/93] Update docs/guide/app.md --- docs/guide/app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/app.md b/docs/guide/app.md index d5dc9f76a..96afc50cd 100644 --- a/docs/guide/app.md +++ b/docs/guide/app.md @@ -214,7 +214,7 @@ For example, the application shown below changes its title and subtitle as soon If you run the app shown above and if you press a key, the title and subtitle update accordingly. -For example, if you press `t`, your application will look as shown below: +For example, if you press ++t++, your application will look as shown below: ```{.textual path="docs/examples/app/question_title02.py" press="t"} ``` From d350374e597e84281436856c9eb5f0cdbe097ec2 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Wed, 1 Feb 2023 15:08:03 +0000 Subject: [PATCH 69/93] Revert Tree.clear and add Tree.reset After some internal discussion we've decided to keep `Tree.clear` as it was, and add a `Tree.reset`, which does a `Tree.clear` but resets the label and data of `Tree.root` to the values given, while mirroring how `Tree.__init__` takes those parameters. --- CHANGELOG.md | 2 +- src/textual/widgets/_tree.py | 26 +++++++++++++++----------- tests/tree/test_tree_clearing.py | 27 ++++++++------------------- 3 files changed, 24 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4410a7fbb..b9f38a6ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added App.scroll_sensitivity_x and App.scroll_sensitivity_y to adjust how many lines the scroll wheel moves the scroll position https://github.com/Textualize/textual/issues/928 - Added Shift+scroll wheel and ctrl+scroll wheel to scroll horizontally - Added `Tree.action_toggle_node` to toggle a node without selecting, and bound it to Space https://github.com/Textualize/textual/issues/1433 -- Added the ability to set new root node label and/or data when calling `Tree.clear` https://github.com/Textualize/textual/issues/1437 +- Added `Tree.reset` to fully reset a `Tree` https://github.com/Textualize/textual/issues/1437 ### Changed diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index f299c0d99..4145e9eb0 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -560,20 +560,13 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): label = self.render_label(node, NULL_STYLE, NULL_STYLE) return label.cell_len - def clear(self, label: TextType | None = None, data: TreeDataType = ...) -> None: - """Clear all nodes under root. - - Args: - label: An optional new label for the root node. If not provided - the current root node's label will be used. - data: Optional new data for the root node. If not provided the - current root node's data will be used. - """ + def clear(self) -> None: + """Clear all nodes under root.""" self._line_cache.clear() self._tree_lines_cached = None self._current_id = 0 - root_label = self.root._label if label is None else label - root_data = self.root.data if data is ... else data + root_label = self.root._label + root_data = self.root.data self.root = TreeNode( self, None, @@ -585,6 +578,17 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True): self._updates += 1 self.refresh() + def reset(self, label: TextType, data: TreeDataType | None = None) -> None: + """Clear the tree and reset the root node. + + Args: + label: The label for the root node. + data: Optional data for the root node. + """ + self.clear() + self.root.label = label + self.root.data = data + def select_node(self, node: TreeNode[TreeDataType] | None) -> None: """Move the cursor to the given node, or reset cursor. diff --git a/tests/tree/test_tree_clearing.py b/tests/tree/test_tree_clearing.py index 9b7762bbf..87543c4c8 100644 --- a/tests/tree/test_tree_clearing.py +++ b/tests/tree/test_tree_clearing.py @@ -41,7 +41,7 @@ class TreeClearApp(App[None]): async def test_tree_simple_clear() -> None: - """Clearing a tree should keep the old label and data.""" + """Clearing a tree should keep the old root label and data.""" async with TreeClearApp().run_test() as pilot: tree = pilot.app.query_one(VerseTree) assert len(tree.root.children) > 1 @@ -51,34 +51,23 @@ async def test_tree_simple_clear() -> None: assert isinstance(tree.root.data, VerseStar) -async def test_tree_new_label_clear() -> None: - """Clearing a tree with a new label should use the new label and keep the old data.""" +async def test_tree_reset_with_label() -> None: + """Resetting a tree with a new label should use the new label and set the data to None.""" async with TreeClearApp().run_test() as pilot: tree = pilot.app.query_one(VerseTree) assert len(tree.root.children) > 1 - pilot.app.query_one(VerseTree).clear(label="Jiangyin") + pilot.app.query_one(VerseTree).reset(label="Jiangyin") assert len(tree.root.children) == 0 assert str(tree.root.label) == "Jiangyin" - assert isinstance(tree.root.data, VerseStar) + assert tree.root.data is None -async def test_tree_new_data_clear() -> None: - """Clearing a tree with data should keep the old label and use the new data.""" +async def test_tree_reset_with_label_and_data() -> None: + """Resetting a tree with a label and data have that label and data used.""" async with TreeClearApp().run_test() as pilot: tree = pilot.app.query_one(VerseTree) assert len(tree.root.children) > 1 - pilot.app.query_one(VerseTree).clear(data=VersePlanet()) - assert len(tree.root.children) == 0 - assert str(tree.root.label) == "White Sun" - assert isinstance(tree.root.data, VersePlanet) - - -async def test_tree_new_labal_and_data_clear() -> None: - """Clearing a tree with label and data should replace the label and data.""" - async with TreeClearApp().run_test() as pilot: - tree = pilot.app.query_one(VerseTree) - assert len(tree.root.children) > 1 - pilot.app.query_one(VerseTree).clear(label="Jiangyin", data=VersePlanet()) + pilot.app.query_one(VerseTree).reset(label="Jiangyin", data=VersePlanet()) assert len(tree.root.children) == 0 assert str(tree.root.label) == "Jiangyin" assert isinstance(tree.root.data, VersePlanet) From 9e5814ed0f0ebdd6e14d4e46f84957b3b8dded6f Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 1 Feb 2023 16:22:14 +0100 Subject: [PATCH 70/93] Strip improvements and line api --- src/textual/_styles_cache.py | 5 +++-- src/textual/strip.py | 24 +++++++++++++++++++++--- src/textual/widgets/_text_log.py | 1 - 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/textual/_styles_cache.py b/src/textual/_styles_cache.py index a874b287b..dea21a3fb 100644 --- a/src/textual/_styles_cache.py +++ b/src/textual/_styles_cache.py @@ -10,7 +10,7 @@ from rich.style import Style from ._border import get_box, render_row from ._filter import LineFilter from ._opacity import _apply_opacity -from ._segment_tools import line_crop, line_pad, line_trim +from ._segment_tools import line_pad, line_trim from ._typing import TypeAlias from .color import Color from .geometry import Region, Size, Spacing @@ -22,7 +22,7 @@ if TYPE_CHECKING: from .css.styles import StylesBase from .widget import Widget -RenderLineCallback: TypeAlias = Callable[[int], List[Segment]] +RenderLineCallback: TypeAlias = Callable[[int], Strip] @lru_cache(1024 * 8) @@ -313,6 +313,7 @@ class StylesCache: content_y = y - gutter.top if content_y < content_height: line = render_content_line(y - gutter.top) + line = line.adjust_cell_length(content_width) else: line = [make_blank(content_width, inner)] if inner: diff --git a/src/textual/strip.py b/src/textual/strip.py index 95cf03038..c10a649a9 100644 --- a/src/textual/strip.py +++ b/src/textual/strip.py @@ -6,7 +6,7 @@ from typing import Iterable, Iterator import rich.repr from rich.cells import cell_len, set_cell_size from rich.segment import Segment -from rich.style import Style +from rich.style import Style, StyleType from ._cache import FIFOCache from ._filter import LineFilter @@ -49,7 +49,7 @@ class Strip: return "".join(segment.text for segment in self._segments) @classmethod - def blank(cls, cell_length: int, style: Style | None) -> Strip: + def blank(cls, cell_length: int, style: StyleType | None = None) -> Strip: """Create a blank strip. Args: @@ -59,7 +59,8 @@ class Strip: Returns: New strip. """ - return cls([Segment(" " * cell_length, style)], cell_length) + segment_style = Style.parse(style) if isinstance(style, str) else style + return cls([Segment(" " * cell_length, segment_style)], cell_length) @classmethod def from_lines( @@ -135,6 +136,23 @@ class Strip: self._segments == strip._segments and self.cell_length == strip.cell_length ) + def extend_cell_length(self, cell_length: int, style: Style | None = None) -> Strip: + """Extend the cell length if it is less than the given value. + + Args: + cell_length: Required minimum cell length. + style: Style for padding if the cell length is extended. + + Returns: + A new Strip. + """ + if self.cell_length < cell_length: + missing_space = cell_length - self.cell_length + segments = self._segments + [Segment(" " * missing_space, style)] + return Strip(segments, cell_length) + else: + return self + def adjust_cell_length(self, cell_length: int, style: Style | None = None) -> Strip: """Adjust the cell length, possibly truncating or extending. diff --git a/src/textual/widgets/_text_log.py b/src/textual/widgets/_text_log.py index 1c8ad17a0..3b6f931f4 100644 --- a/src/textual/widgets/_text_log.py +++ b/src/textual/widgets/_text_log.py @@ -14,7 +14,6 @@ from ..reactive import var from ..geometry import Size, Region from ..scroll_view import ScrollView from .._cache import LRUCache -from .._segment_tools import line_crop from ..strip import Strip From 0a5838d9643e80b1bd80232c97123f758a973513 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 1 Feb 2023 16:26:55 +0100 Subject: [PATCH 71/93] checker example --- docs/examples/guide/checker.py | 70 ++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 docs/examples/guide/checker.py diff --git a/docs/examples/guide/checker.py b/docs/examples/guide/checker.py new file mode 100644 index 000000000..27419bf96 --- /dev/null +++ b/docs/examples/guide/checker.py @@ -0,0 +1,70 @@ +from __future__ import annotations + +from textual.app import App, ComposeResult +from textual.geometry import Size +from textual.strip import Strip +from textual.scroll_view import ScrollView + +from rich.segment import Segment + + +class CheckerBoard(ScrollView): + COMPONENT_CLASSES = { + "checkerboard--white-square", + "checkerboard--black-square", + "checkerboard--void", + } + + DEFAULT_CSS = """ + CheckerBoard { + background: $primary; + } + CheckerBoard .checkerboard--void { + background: $background; + } + + CheckerBoard .checkerboard--white-square { + background: $foreground 70%; + } + CheckerBoard .checkerboard--black-square { + background: $primary; + } + """ + + def on_mount(self) -> None: + self.virtual_size = Size(64, 32) + + def render_line(self, y: int) -> Strip: + """Render a line of the widget. y is relative to the top of the widget.""" + + scroll_x, scroll_y = self.scroll_offset + y += scroll_y + row_index = y // 4 # four lines per row + + white = self.get_component_rich_style("checkerboard--white-square") + black = self.get_component_rich_style("checkerboard--black-square") + void = self.get_component_rich_style("checkerboard--void") + + if row_index >= 8: + return Strip.blank(self.size.width, void) + + is_odd = row_index % 2 + + segments = [ + Segment(" " * 8, black if (column + is_odd) % 2 else white) + for column in range(8) + ] + strip = Strip(segments, 8 * 8) + strip = strip.extend_cell_length(self.size.width, void) + strip = strip.crop(scroll_x, scroll_x + self.size.width) + return strip + + +class BoardApp(App): + def compose(self) -> ComposeResult: + yield CheckerBoard() + + +if __name__ == "__main__": + app = BoardApp() + app.run() From 806c80b8fe921e8cd85200c82c9947c20b41847c Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 1 Feb 2023 17:14:53 +0100 Subject: [PATCH 72/93] simplify --- docs/examples/guide/checker.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/examples/guide/checker.py b/docs/examples/guide/checker.py index 27419bf96..73093d180 100644 --- a/docs/examples/guide/checker.py +++ b/docs/examples/guide/checker.py @@ -12,17 +12,12 @@ class CheckerBoard(ScrollView): COMPONENT_CLASSES = { "checkerboard--white-square", "checkerboard--black-square", - "checkerboard--void", } DEFAULT_CSS = """ CheckerBoard { background: $primary; } - CheckerBoard .checkerboard--void { - background: $background; - } - CheckerBoard .checkerboard--white-square { background: $foreground 70%; } @@ -43,10 +38,9 @@ class CheckerBoard(ScrollView): white = self.get_component_rich_style("checkerboard--white-square") black = self.get_component_rich_style("checkerboard--black-square") - void = self.get_component_rich_style("checkerboard--void") if row_index >= 8: - return Strip.blank(self.size.width, void) + return Strip.blank(self.size.width) is_odd = row_index % 2 @@ -55,7 +49,6 @@ class CheckerBoard(ScrollView): for column in range(8) ] strip = Strip(segments, 8 * 8) - strip = strip.extend_cell_length(self.size.width, void) strip = strip.crop(scroll_x, scroll_x + self.size.width) return strip From 91630d6e040c4476e82f35e8d667dee667fc5b9f Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Wed, 1 Feb 2023 16:37:53 +0000 Subject: [PATCH 73/93] Make `textual keys` work on older supported Pythons --- src/textual/cli/previews/keys.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/textual/cli/previews/keys.py b/src/textual/cli/previews/keys.py index 91ac5575a..933fac807 100644 --- a/src/textual/cli/previews/keys.py +++ b/src/textual/cli/previews/keys.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from rich.panel import Panel from rich.text import Text From c04e387dfe0812da8e1576106cd6869454b6dc95 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 1 Feb 2023 17:38:19 +0100 Subject: [PATCH 74/93] title copy --- docs/guide/app.md | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/docs/guide/app.md b/docs/guide/app.md index 96afc50cd..82cec0bbe 100644 --- a/docs/guide/app.md +++ b/docs/guide/app.md @@ -188,37 +188,33 @@ Here's the question app with classvar CSS: ## Title and subtitle -Textual applications have a `title` attribute that represents the name of your application and a `sub_title` attribute that gives additional context for it. -These attributes can be displayed to the user if your application has a widget `Header` and there are two ways in which you can customize them if you don't want to run with the default values. - -By default, the title of your application matches the name of the application class and the subtitle is empty. -However, you can override those defaults by setting the class attributes `TITLE` and `SUB_TITLE`, as shown below. -Remember that you need the built-in widget `Header` to make the title and the subtitle visible in our app. +Textual apps have a `title` attribute which is typically the name of your application, and an optional `sub_title` attribute which adds additional context (such as the file your are working on). +By default, `title` will be set to they name of your App class, and `sub-title` is empty. +You can change these defaults by defining `TITLE` and `SUB_TITLE` class variables. Here's an example of that: ```py title="question_title01.py" hl_lines="6-7 10" --8<-- "docs/examples/app/question_title01.py" ``` -The app title and subtitle are displayed at the top of the application in the header: +Note the title and subtitle are displayed by the builtin [Header](./../widgets/header.md) widget at the top of the screen: ```{.textual path="docs/examples/app/question_title01.py"} ``` -On top of being able to define a default title and a default subtitle for a given application class, each _instance_ of your application class has two attributes `title` and `sub_title` that can be used to modify the title and subtitle of said instance application. - -For example, the application shown below changes its title and subtitle as soon as the application is instantiated. +You can also set the title attributes dynamically within a method of your app. The following example sets the title and subtitle in response to a key press: ```py title="question_title02.py" hl_lines="19-21" --8<-- "docs/examples/app/question_title02.py" ``` -If you run the app shown above and if you press a key, the title and subtitle update accordingly. - -For example, if you press ++t++, your application will look as shown below: +If you run this app and press the ++t++ key, you should see the header update accordingly: ```{.textual path="docs/examples/app/question_title02.py" press="t"} ``` +!!! tip + + Note that there is no need to explicitly refresh the screen when setting the title attributes. This is an example of [reactivity](./reactivity.md), which we will cover later in the guide. ## What's next From 259c1cc14e90fec5a0e9f11861358cede77c5b2b Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 1 Feb 2023 17:43:29 +0100 Subject: [PATCH 75/93] tip to info --- docs/guide/app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/app.md b/docs/guide/app.md index 82cec0bbe..9a3d0dad7 100644 --- a/docs/guide/app.md +++ b/docs/guide/app.md @@ -212,7 +212,7 @@ If you run this app and press the ++t++ key, you should see the header update ac ```{.textual path="docs/examples/app/question_title02.py" press="t"} ``` -!!! tip +!!! info Note that there is no need to explicitly refresh the screen when setting the title attributes. This is an example of [reactivity](./reactivity.md), which we will cover later in the guide. From 340f7c53bc5a163b68bf6312c8030f83f0039439 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 1 Feb 2023 17:49:05 +0100 Subject: [PATCH 76/93] Update docs/guide/app.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com> --- docs/guide/app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/app.md b/docs/guide/app.md index 9a3d0dad7..aea725fed 100644 --- a/docs/guide/app.md +++ b/docs/guide/app.md @@ -189,7 +189,7 @@ Here's the question app with classvar CSS: ## Title and subtitle Textual apps have a `title` attribute which is typically the name of your application, and an optional `sub_title` attribute which adds additional context (such as the file your are working on). -By default, `title` will be set to they name of your App class, and `sub-title` is empty. +By default, `title` will be set to the name of your App class, and `sub_title` is empty. You can change these defaults by defining `TITLE` and `SUB_TITLE` class variables. Here's an example of that: ```py title="question_title01.py" hl_lines="6-7 10" From 087654a246aa119eed4fdfeeed153840112c8de0 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 1 Feb 2023 17:49:33 +0100 Subject: [PATCH 77/93] Update docs/guide/app.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com> --- docs/guide/app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/app.md b/docs/guide/app.md index aea725fed..b11d02184 100644 --- a/docs/guide/app.md +++ b/docs/guide/app.md @@ -196,7 +196,7 @@ You can change these defaults by defining `TITLE` and `SUB_TITLE` class variable --8<-- "docs/examples/app/question_title01.py" ``` -Note the title and subtitle are displayed by the builtin [Header](./../widgets/header.md) widget at the top of the screen: +Note that the title and subtitle are displayed by the builtin [Header](./../widgets/header.md) widget at the top of the screen: ```{.textual path="docs/examples/app/question_title01.py"} ``` From 8ce76f4f899af0f62f0e6edcde2de6e3a69e1470 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 1 Feb 2023 18:00:52 +0100 Subject: [PATCH 78/93] fix title update --- docs/examples/app/question_title01.py | 3 ++- docs/examples/app/question_title02.py | 3 ++- docs/guide/app.md | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/examples/app/question_title01.py b/docs/examples/app/question_title01.py index e597a4746..55dc43599 100644 --- a/docs/examples/app/question_title01.py +++ b/docs/examples/app/question_title01.py @@ -3,12 +3,13 @@ from textual.widgets import Button, Header, Label class MyApp(App[str]): + CSS_PATH = "question02.css" TITLE = "A Question App" SUB_TITLE = "The most important question" def compose(self) -> ComposeResult: yield Header() - yield Label("Do you love Textual?") + yield Label("Do you love Textual?", id="question") yield Button("Yes", id="yes", variant="primary") yield Button("No", id="no", variant="error") diff --git a/docs/examples/app/question_title02.py b/docs/examples/app/question_title02.py index 91ebf96ab..c279d7e20 100644 --- a/docs/examples/app/question_title02.py +++ b/docs/examples/app/question_title02.py @@ -4,12 +4,13 @@ from textual.widgets import Button, Header, Label class MyApp(App[str]): + CSS_PATH = "question02.css" TITLE = "A Question App" SUB_TITLE = "The most important question" def compose(self) -> ComposeResult: yield Header() - yield Label("Do you love Textual?") + yield Label("Do you love Textual?", id="question") yield Button("Yes", id="yes", variant="primary") yield Button("No", id="no", variant="error") diff --git a/docs/guide/app.md b/docs/guide/app.md index b11d02184..eaa874ed3 100644 --- a/docs/guide/app.md +++ b/docs/guide/app.md @@ -192,7 +192,7 @@ Textual apps have a `title` attribute which is typically the name of your applic By default, `title` will be set to the name of your App class, and `sub_title` is empty. You can change these defaults by defining `TITLE` and `SUB_TITLE` class variables. Here's an example of that: -```py title="question_title01.py" hl_lines="6-7 10" +```py title="question_title01.py" hl_lines="7-8 11" --8<-- "docs/examples/app/question_title01.py" ``` @@ -203,7 +203,7 @@ Note that the title and subtitle are displayed by the builtin [Header](./../widg You can also set the title attributes dynamically within a method of your app. The following example sets the title and subtitle in response to a key press: -```py title="question_title02.py" hl_lines="19-21" +```py title="question_title02.py" hl_lines="20-22" --8<-- "docs/examples/app/question_title02.py" ``` From 244372205e350f1431a124f6ee8d69e82dee5f07 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Thu, 2 Feb 2023 13:51:45 +0000 Subject: [PATCH 79/93] Include the unit tests in the source distribution As requested in #631 and also #1349; this change should cause the tests (and also the examples from the docs directory which get pulled into the tests) to be pulled into the sdist. With this change I can: - `poetry build` - Grab and extract the resulting tarball - `poetry install --extras dev` inside the resulting directory - `poetry run pytest -vv tests` inside the resulting directory As I understand the request, I think this should provide everything necessary. NOTE: We have spoken about rolling the CLI tools into snapshot tests too, so if that happens we should remember to make sure that the pipeline outlined above still works fine. --- pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9582f1550..6d283c933 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,9 @@ classifiers = [ "Typing :: Typed", ] include = [ - "src/textual/py.typed" + "src/textual/py.typed", + { path = "docs/examples", format = "sdist" }, + { path = "tests", format = "sdist" } ] [tool.poetry.scripts] From 2ff278874b7b9081e0f513e1f845b302ded49eda Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 3 Feb 2023 11:23:14 +0100 Subject: [PATCH 80/93] docs examples and diagrams --- docs/api/strip.md | 1 + docs/examples/guide/widgets/checker01.py | 43 + docs/examples/guide/widgets/checker02.py | 63 + .../{checker.py => widgets/checker03.py} | 13 +- docs/guide/widgets.md | 76 +- docs/images/render_line.excalidraw.svg | 16 + docs/images/segment.excalidraw.svg | 16 + mkdocs.yml | 1 + poetry.lock | 2307 +++++++++-------- pyproject.toml | 2 +- src/textual/_animator.py | 1 - src/textual/_parser.py | 2 - src/textual/_sleep.py | 1 - src/textual/_xterm_parser.py | 2 - src/textual/cli/previews/colors.py | 2 - src/textual/css/_styles_builder.py | 1 - src/textual/css/parse.py | 1 - src/textual/css/query.py | 1 - src/textual/css/styles.py | 1 - src/textual/devtools/service.py | 1 - src/textual/drivers/linux_driver.py | 3 - src/textual/drivers/windows_driver.py | 1 - src/textual/layouts/horizontal.py | 1 - src/textual/layouts/vertical.py | 1 - src/textual/message_pump.py | 1 - src/textual/reactive.py | 1 - src/textual/scrollbar.py | 2 - src/textual/widgets/_directory_tree.py | 1 - src/textual/widgets/_input.py | 1 - src/textual/widgets/_static.py | 1 - src/textual/widgets/_text_log.py | 1 - src/textual/widgets/_tree.py | 1 - src/textual/widgets/_welcome.py | 5 +- 33 files changed, 1381 insertions(+), 1190 deletions(-) create mode 100644 docs/api/strip.md create mode 100644 docs/examples/guide/widgets/checker01.py create mode 100644 docs/examples/guide/widgets/checker02.py rename docs/examples/guide/{checker.py => widgets/checker03.py} (85%) create mode 100644 docs/images/render_line.excalidraw.svg create mode 100644 docs/images/segment.excalidraw.svg diff --git a/docs/api/strip.md b/docs/api/strip.md new file mode 100644 index 000000000..44051a14a --- /dev/null +++ b/docs/api/strip.md @@ -0,0 +1 @@ +::: textual.strip.Strip diff --git a/docs/examples/guide/widgets/checker01.py b/docs/examples/guide/widgets/checker01.py new file mode 100644 index 000000000..66b6a0963 --- /dev/null +++ b/docs/examples/guide/widgets/checker01.py @@ -0,0 +1,43 @@ +from rich.segment import Segment +from rich.style import Style + +from textual.app import App, ComposeResult +from textual.strip import Strip +from textual.widget import Widget + + +class CheckerBoard(Widget): + """Render an 8x8 checkerboard.""" + + def render_line(self, y: int) -> Strip: + """Render a line of the widget. y is relative to the top of the widget.""" + + row_index = y // 4 # A checkerboard square consists of 4 rows + + if row_index >= 8: # Generate blank lines when we reach the end + return Strip.blank(self.size.width) + + is_odd = row_index % 2 # Used to alternate the starting square on each row + + white = Style.parse("on white") # Get a style object for a white background + black = Style.parse("on black") # Get a style object for a black background + + # Generate a list of segments with alternating black and white space characters + segments = [ + Segment(" " * 8, black if (column + is_odd) % 2 else white) + for column in range(8) + ] + strip = Strip(segments, 8 * 8) + return strip + + +class BoardApp(App): + """A simple app to show our widget.""" + + def compose(self) -> ComposeResult: + yield CheckerBoard() + + +if __name__ == "__main__": + app = BoardApp() + app.run() diff --git a/docs/examples/guide/widgets/checker02.py b/docs/examples/guide/widgets/checker02.py new file mode 100644 index 000000000..48b674ea9 --- /dev/null +++ b/docs/examples/guide/widgets/checker02.py @@ -0,0 +1,63 @@ +from rich.segment import Segment +from rich.style import Style + +from textual.app import App, ComposeResult +from textual.geometry import Size +from textual.strip import Strip +from textual.widget import Widget + + +class CheckerBoard(Widget): + """Render an 8x8 checkerboard.""" + + COMPONENT_CLASSES = { + "checkerboard--white-square", + "checkerboard--black-square", + } + + DEFAULT_CSS = """ + CheckerBoard .checkerboard--white-square { + background: #A5BAC9; + } + CheckerBoard .checkerboard--black-square { + background: #004578; + } + """ + + def get_content_width(self, container: Size, viewport: Size) -> int: + return 64 + + def get_content_height(self, container: Size, viewport: Size, width: int) -> int: + return 32 + + def render_line(self, y: int) -> Strip: + """Render a line of the widget. y is relative to the top of the widget.""" + + row_index = y // 4 # four lines per row + + if row_index >= 8: + return Strip.blank(self.size.width) + + is_odd = row_index % 2 + + white = self.get_component_rich_style("checkerboard--white-square") + black = self.get_component_rich_style("checkerboard--black-square") + + segments = [ + Segment(" " * 8, black if (column + is_odd) % 2 else white) + for column in range(8) + ] + strip = Strip(segments, 8 * 8) + return strip + + +class BoardApp(App): + """A simple app to show our widget.""" + + def compose(self) -> ComposeResult: + yield CheckerBoard() + + +if __name__ == "__main__": + app = BoardApp() + app.run() diff --git a/docs/examples/guide/checker.py b/docs/examples/guide/widgets/checker03.py similarity index 85% rename from docs/examples/guide/checker.py rename to docs/examples/guide/widgets/checker03.py index 73093d180..ccdab8ebf 100644 --- a/docs/examples/guide/checker.py +++ b/docs/examples/guide/widgets/checker03.py @@ -15,17 +15,20 @@ class CheckerBoard(ScrollView): } DEFAULT_CSS = """ - CheckerBoard { - background: $primary; - } CheckerBoard .checkerboard--white-square { - background: $foreground 70%; + background: #A5BAC9; } CheckerBoard .checkerboard--black-square { - background: $primary; + background: #004578; } """ + def get_content_width(self, container: Size, viewport: Size) -> int: + return 64 + + def get_content_height(self, container: Size, viewport: Size, width: int) -> int: + return 32 + def on_mount(self) -> None: self.virtual_size = Size(64, 32) diff --git a/docs/guide/widgets.md b/docs/guide/widgets.md index d30e30c96..be7098232 100644 --- a/docs/guide/widgets.md +++ b/docs/guide/widgets.md @@ -200,4 +200,78 @@ TODO: Explanation of compound widgets ## Line API -TODO: Explanation of line API +Working with Rich renderables allows you to build sophisticated widgets with minimal effort, but there is a downside to widgets that return renderables. +When you resize a widget or update its state, Textual has to refresh the widget's content in its entirety, which may be expensive. +You are unlikely to notice this if the widget fits within the screen but large widgets that scroll may slow down your application. + +Textual offers an alternative API which reduces the amount of work Textual needs to do to refresh a widget, and makes it possible to update portions of a widget (as small as a single character). This is known as the *line API*. + +!!! info + + The [DataTable](./../widgets/data_table.md) widget uses the Line API, which can support thousands or even millions of rows without a reduction in render times. + +### Render Line method + +To build an widget with the line API, implement a `render_line` method rather than a `render` method. The `render_line` method takes a single integer argument `y` which is an offset from the top of the widget, and should return a [Strip][textual.strip.Strip] object which contains that line's content. +Textual will call this method as required to to get the content for every line. + +
+--8<-- "docs/images/render_line.excalidraw.svg" +
+ +Let's look at an example before we go in to the details. The following Textual app implements a widget with the line API that renders a checkerboard pattern. This might form the basis of a chess / checkers app. Here's the code: + +=== "checker01.py" + + ```python title="checker01.py" hl_lines="12-30" + --8<-- "docs/examples/guide/widgets/checker01.py" + ``` + +=== "Output" + + ```{.textual path="docs/examples/guide/widgets/checker01.py"} + ``` + + +The `render_line` method above calculates a `Strip` for every row of characters in the widget. Each strip contains alternating black and white space characters which form the squares in the checkerboard. You may have noticed that the checkerboard widget makes use of some objects we haven't covered before. Let's explore those. + +#### Segment and Style + +A [Segment](https://rich.readthedocs.io/en/latest/protocol.html#low-level-render) is a class borrowed from the [Rich](https://github.com/Textualize/rich) project. It is small object (actually a named tuple) which bundles text and a [Style](https://rich.readthedocs.io/en/latest/style.html) which tells Textual how the text should be displayed. + +Lets look at a simple segment which would produce the text "Hello, World!" in bold. + +```python +greeting = Segment("Hello, World!", Style(bold=True)) +``` + +This would create the following object: + +
+--8<-- "docs/images/segment.excalidraw.svg" +
+ +Both Rich and Textual work with segments to generate content. A Textual app is the result of processing hundreds, or perhaps thousands of segments. + +#### Strips + +A [Strip][textual.strip.Strip] is a container for a number of segments which define the content for a single *line* (or row) in the Widget. A Strip only requires a single segment, but will likely contain many more. + +You construct a strip with a list of segments. Here's now you might construct a strip that ultimately displays the text "Hello, World!", but with the second word in bold: + +```python +segments = [ + Segment("Hello, "), + Segment("World", Style(bold=Trip)), + Segment("!") +] +strip = Strip(segments) +``` + +The `Strip` constructor has a second optional constructor, which should be the length of the strip. In the code above, the length of the strip is 13, so we could have constructed it like this: + +```python +strip = Strip(segments, 13) +``` + +Note that the length parameter is _not_ the total number of characters in the string. It is the number of terminal "cells". Some characters (such as Asian language characters and certain emoji) take up the space of two Western alphabet characters. If you don't know in advance the number of cells your segments will occupy, it is best to leave the length parameter blank. diff --git a/docs/images/render_line.excalidraw.svg b/docs/images/render_line.excalidraw.svg new file mode 100644 index 000000000..0fe5edfa2 --- /dev/null +++ b/docs/images/render_line.excalidraw.svg @@ -0,0 +1,16 @@ + + + eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1cXGlT29hcdTAwMTL9nl9BMV/mVVxymtt9107V1CsggVx1MDAxMFx1MDAxMpZcdTAwMDAhydRcdTAwMTQlbNkoyFx1MDAwYrZYp/LfX1+HWPJcIi9gg/Mq/sCixbq6Oqf79HL174ulpeX0tlx1MDAxOS2/XFxajm5KYVx1MDAxMpdb4fXyXHUwMDFmfvtV1GrHjTrvws7/7cZlq9Q58ixNm+2Xf/5ZXHUwMDBiW+dR2kzCUlx1MDAxNFxcxe3LMGmnl+W4XHUwMDExlFx1MDAxYbU/4zSqtf/rf+6EteivZqNWTltBdpGVqFx1MDAxY6eN1vdrRUlUi+ppm7/9b/5/aenfzs/c6FpRKVxy69Uk6pzQ2ZVcctBcdTAwMDD1b91p1DuDNUaRVcaZ7lx1MDAwMXH7XHUwMDE1Xy6Nyry3wkOOsj1+07K+Ofp8eHXS3LFHt3fHn1x1MDAwZrU6KWN21UqcJFx1MDAwN+lt8n0mwtLZZSs3pnbaapxHx3E5Pfsxcbnt3fPaXHKehOysVuOyelaP2v7+obu10VxmS3F6y9uc6G78Plx1MDAwNy+Xsi03/N+KXHUwMDBlSElcco60sJZcdTAwMDCzK/vzQcuAJFx1MDAxOGVRXHUwMDE4YbTqXHUwMDFi13oj4SfB4/pccpxR4Wk2stOwdF7l4dXL3WPSVlhvN8NcdTAwMTY/r+y46/s7llZcdTAwMDYoJKDu7jqL4upZ6mdcdTAwMDNNoDU5LY0mcoQ2XHUwMDFiRtR5XHUwMDFjnVx1MDAxMaIlm92dv3hzq9xBxj/5XHSrl+8nrH6ZJNl4/Y7XOTRl51xcNsthen9cdTAwMTmrXHUwMDE136mTaDNcXCRx/bz/65JG6TxcdTAwMDNKZ+u3P1x1MDAxZVx1MDAwMFBcdTAwMTAgi1x1MDAxMOo0II9mXG6EJudvL07OWttm//VeYuBcXO3i7UZcdTAwMDFCS61Gu71yXHUwMDE2pqWzeaNcdTAwMTTEWJiSXGLIXGJGoWVcdTAwMWOCQtlcdTAwMDNTS1x1MDAwMVx1MDAxMfFUOKl4PnJT1lx1MDAwZlPR+TxcdTAwMWOmWqnASJRKXG5cclx1MDAwMEPAKo1cYlx1MDAxY1xiUspITyw0XHUwMDAzYJWkXHUwMDE5SFK7p1x1MDAwMKtcdTAwMDRcdTAwMGLOPlx0WG3OevRhXHUwMDE1XHUwMDE0gHDOkptcdTAwMTiselx1MDAxYtdLlZ3rXHUwMDEwV48/X8sqnCTtXCJz2lx1MDAwN7jngylcdTAwMDRsXHUwMDFhpLFCailcdTAwMTBcdTAwMDZgqlx1MDAxYym0qK11QPOEqVxmXHUwMDA0XHUwMDE4jU5bYLPoXHUwMDA2cYouMEYwX4xAI1x1MDAxOdODOCWpjVNcdTAwMDaexKiyXHUwMDAxZ/czK5xGSVx1MDAxMjfbw32+1kUoZSdI2mmaXHUwMDE4o/tblavt2/btu8b5fvpqq7Xd3jy7eFxiRuHpMOpRKEGxXHQzXHUwMDBlnKBejDpcbrRlMEhEIZxcdTAwMTbFXHUwMDFlf1x1MDAwMoz+Vlx0NWpcdTAwMWPEJ8hAXHRETVx1MDAwNvlcdTAwMTfbbDVcYlDAQHtcdTAwMTHGRlSglKhcdTAwMDa8PqNcdTAwMDadcvIp8GlJSmFmZkdH4NOKQk1cbkDEXCIsr1x0xiFcdTAwMTRL9epd8ql8UD9z7037IPz62Vx1MDAxNrn8RUGo5icvhLLOgkFgZ9mLUFx1MDAxNzAkrHYktGJcdTAwMDSbR1wi9FRcYj0vhEq2v1Yw1/6/XHUwMDEwalxuNSmwN0GSrOQnRujx3vnd3seztUR8NDtbu69cdTAwMGU+hSBcdTAwMTdcdTAwMWOhqFx1MDAwMmWV0lx1MDAxYVxmgGFcZuheiNrAXG6Bjkhaw/B4lKP/XHLwlEXtvCDK4yNhnTA/XHUwMDFmREeKUeegXGKkXHUwMDE2LT9cdTAwMWE1ReBUPtjcK5feXHUwMDFkXHUwMDFlfbXXl+9vtnVaufo4U4yWw/ZZVFx1MDAwMFLxIJCKgE2nZFx1MDAwNUhKWFx1MDAwZVVcdTAwMTimPSBcdTAwMDVNXHUwMDAxKFwiRFx1MDAwNrHkR6NcdTAwMWaFUsviqYI4xNn7iEhbdJKU4YB1aIRvOIBTqLS0SvDPXHUwMDFjlH9YUmUsx3dmXHUwMDFhmP5cdTAwMDBMXHUwMDA2XHUwMDE5eb/lWzF6u+dkZ3dh8OHi6vqotH1z/mXjjbn7dLu+d4U33Vx1MDAxYu7BZthqNa6Xu3u+3f81Wlx1MDAwMIOUs+JGXHUwMDFh3aTDaJFPXFz0225kuYfscyfnxWf68O44OXh9VHVrlcu1la33unT4sJTXXHUwMDEzWm/JXHUwMDAyg8VcdTAwMDOLXHUwMDBidD5Igj5eIFx1MDAwN/DGXHUwMDAy81x1MDAwNlBcdTAwMDL1x4+zXHUwMDBi0zCXx8i4oPuxzyzV3tnM0EY/XHUwMDE3yontP1x1MDAxYpspUJ6BqVFPXHUwMDBm4jt/XVx1MDAxND1bN8JanNz24KFcdTAwMDP/l52ZrkZpwNNfjlonfLXo99u/xH/yj6xcdTAwMWT5zf5s13P6alx1MDAxMlc9Y5aTqNJLpTQuhUl3d9poZntLPJyQv661Ve6/rUYrrsb1MDlcdTAwMWMztFx1MDAwN9FaymJak2GpLJCySVx1MDAxYpt6OSyJ7Z3j2Lb23Ea7XHUwMDE27tLm262fgNZcdTAwMTAwY31cdTAwMTJcdTAwMTmlQsh8yG1cdTAwMDczLJfY3Vx1MDAxOWFcYpx1OVx1MDAxMM6c1sPygoO0Zt2Filx1MDAxZF5mX+bJ6ovSOl1cdTAwMWVcXFx1MDAxY5TIJW+2w8r51sX705mxmvWFy+jzLKyGxWU1PJDVXG5HOGtgXHUwMDBmpmQutFx1MDAxY5v9t1W8S/ejq4+7a+vxp0q8sofXP1x1MDAwMatZPXJcdTAwMWOtOeBHpjj1hlrIpFx1MDAwNyeNXHUwMDE2IDjMcY9TsWO89ZBcdTAwMDBriLeWRGBYwj5ccq9cdTAwMWKlL2Hrc/XNUby3c0tcdTAwMDf753Sxvzs7XrOdnCYpO1x1MDAwN17j4vJcdTAwMWHH8Pr7hFx1MDAwZiE22Fx1MDAxMcxmLDsjlZ6c2aNcdTAwMTXbojJcdTAwMWJQXHUwMDA1TGpcdIZcdTAwMWShhlxcKb7zXHUwMDA1xlx1MDAwNEZIjkhAXHUwMDE4a3N7Z81rllxmgZIojbLaKeNy5eUuzW3AY+DwXHUwMDEzUbAlUjY3mlx1MDAxZlx1MDAxNWi+XHRkXHUwMDE5Nlxy61x1MDAwN1x1MDAwM1S83zJcIkCdi7hup2ErXYvr5bhe7Vx1MDAxZNh9h8XWXHUwMDA08Z6/57DJx+lAslx1MDAxZfDRPIFPUeVcdTAwMGWoNEqX/jZWXHUwMDA0XHUwMDFmozjgXHUwMDE1XHUwMDAyNJt1qaxcdTAwMWG4dybb+DGNzs10x6RcdTAwMDJcdTAwMGX70Fx0K1x1MDAxZIByomBQWlx1MDAxYbbfzjdcdTAwMTOgXCI3OKYkbKfrjVotTnnq91x1MDAxYXE97Z/izlxcrnrqn0XhgGHhe8rv67dcdTAwMTFN/429Rj/7aymjUOef7t///DH06GJk+89cdTAwMDCms697kf89tXWjnMXvM27KIbFHUZOnXHUwMDE4RuvWxbVtLrDagNOs4ZC07a2yoaCAxYzPXHUwMDBlk1VujilcdTAwMDbWiXwlQYJcdTAwMTWU0EbYIcZcclx1MDAwMmdRKKOUYjpKUplr7Vo3zSjik6cptc3cuqGRuVr07K3b6LC3z05cdTAwMTBJr0qN1Fxm9yyp1rU3NkDtbaAlXHUwMDBiqI2mgXufyLrFr5Tcfp2EXHUwMDE3sqbOpfy6cX1T2y1cdTAwMThcdTAwMTMqjdbDylk2pzJcdTAwMTNcdTAwMTC5QVx0ckBsXG6kNlxmwsFB/Vxc5q1cYtudvVx1MDAwM6ielX1j/qv+zT9cZlx1MDAxY1+RXHUwMDFmunNTWLjRXG5+cS1cdTAwMWNcdTAwMDb8XHUwMDAwlE/bO3anqle+oXZcdTAwMDFcIu9k22dcdTAwMTVcbphfXFxcdTAwMDYgXHUwMDAzXHUwMDAwa1x1MDAwNFlhnTFmSPaFrTGjwLJcdTAwMWRhU0yoXHUwMDA36lx1MDAwYmBcdTAwMWPfhbR2mqas2Vs4ma+tzt7CjU5cdTAwMDHkrIlcYvyQhEGwguWZyTXN5Fx1MDAxNJXivVx1MDAwNoxiU0PqoVx1MDAxNq7S/lK3Z+dX71x1MDAwZk/v1j7U6u06bSbDx6S0XHUwMDE3KlqTIYRcXMCQXHUwMDEznlx1MDAxNiSw3tFOkFwio39u+1ZcYmz/WVx1MDAxOcD0lPatsEQkXHUwMDBiXHUwMDFiUFhcdTAwMWQ7R9O0nH7Q5eZXiCv66HhXvq9cdTAwMWRUV6vh6oybokfVTlx1MDAxZtRcdTAwMTWtKFx1MDAxMFqwW1eCXHUwMDAzXHUwMDA2tH1cdTAwMDVcIoOBN3uoOTz1TnSOKSeX3XSWcspcdTAwMDbbNV6CXHThi+tTXHUwMDE4r4ennJrutLpcdTAwMGLXglx1MDAxZn1cZrXTt5eVzcPV5Tw2XHUwMDFmUVx1MDAwNtXeWT825Vx1MDAwNKZnazfllNnWXHUwMDFmKaeDtFx1MDAxNTd//7tcdTAwMWRV/a3/sdT9I1xign9cbjJPpudb5p15XHUwMDFhM8KRXHUwMDE0XHUwMDFm2SFBrjBcdTAwMDcl2WFcdTAwMWLjYPJcdTAwMTTUaJ26mFx1MDAxZFx1MDAxMiyZXHUwMDE1WuGYOuznkC1bX6tcdTAwMTmQXHQ0XGLtrFx1MDAwMsFm4HF9PHPukGDlyVx1MDAwMac09DhcdTAwMDXzyFx1MDAxNol5lpmc8vX6p2lcdTAwMWZcdTAwMDKhXG6TXHUwMDE44PhRXHUwMDAyu93Jm9lHi5xcdTAwMDWlh1xmkNhcdTAwMDc64dtshctJ447ClypcdTAwMDDBXHUwMDFhS1x1MDAxOO8rXHUwMDFm14Y5Z26AsnyE1e5Z24fmWapxXHUwMDFjc1x1MDAxMsCsuFGoXHJdcXMya1SGis7Vvca2XHUwMDBm7ey+Wa2JVbprn8qtV4d3ur3eXFx4cehcdTAwMDKWKOTDXGaSyvSFvVx1MDAwMlx1MDAwMo5cIrVi1iiL+nG0mIk2lKCcT2nPTFx1MDAxYY4y0IpcdTAwMWM9KID9Jd4m7/UpXlx1MDAxMmhcclqj5Vx1MDAxNFx1MDAxNNy6OUluV6rHuGJaXHUwMDFm4NPm2+al2lt4XG6awHJ879dcdTAwMDEqYuXWl1xclzaQzvpYXHUwMDE1XGa7apxjXHUwMDAz32RcdTAwMWN00oBcdTAwMTb6SVx1MDAxNqp8d1x1MDAwNL9cdTAwMDKo+XIwt6KyP0WCWrEnzFx1MDAxNSrGUVx1MDAxMOyO2Wx93Ph8udZ6Xd6/Wf2y594+qzZcdTAwMWNPQFx1MDAxOVxiTSiZX9pIYUxfr51cZlhxSak5ZNLWWFPsXHUwMDA1J1g3PkJcdTAwMWOCXHUwMDFltrJxsCWHdaomO11otKj5XHUwMDEx31xi/3QtOe+YY0ure1tLx51cdTAwMDaYRWjF6Vx1MDAxZtJIXHUwMDAyXHUwMDE3XHUwMDE3cUYsYpJoWcTRXHUwMDE0XHUwMDFkOKNcdTAwMWb5nFx1MDAxNys/iMLAUZNwQCT9anW/YLmHwlx1MDAxY3WRr4Zw0GVcdTAwMTSCUXOjMFx1MDAwNYTWknFIXHUwMDFjn1x0NaQlnny/vkFnObwgXHUwMDA0kZuke1x1MDAwN8tcdTAwMDGqtbNskJ9cdTAwMGJcdTAwMDMnrNKMdlxiS/mKiOis7idJfPdgncDBMlxyYGBJcEDCqoBcdTAwMDW6+1FcdTAwMDadskwzOpG/1FuI9kVvK3iiSFGuWNpcdTAwMWSTXHUwMDBifMOmlpLHbCzan7xcdTAwMGVdXGJh/1x1MDAxOVx1MDAwMG/2dS/yv1x1MDAxZpamyivQgcZ/we53mj7C6t715/2tVXVU0lx1MDAxOFx1MDAxZlbLpdKnXHUwMDBm5WdVXCJ6glx1MDAxY67TQlx1MDAxMlx1MDAwNz2ChYjvWO2VXCJWeKmiXGZHTSSketx69lx1MDAxMWkq3SnHOVx1MDAxZYhfpVx1MDAwNjCsV3hsnspJpoPLv0fj/yxN5ZdcdTAwMDbr2b07p7Bx3lx1MDAxNb81R1xu7H2pxThWbOxcXNXWV1x1MDAxMnXSbJ7WTva3dr/S6eYzh8hqrELnXHUwMDE4XHUwMDE4hUNk0Fx1MDAxYt/Q2Mdcbun7z7T2K2G1yq9cdTAwMWV6tlx1MDAxMiZTVEF+1c78XFw4oWK/RFOg8FeM/FBcdTAwMDdcdTAwMDXSXHUwMDE1UdGvsFx1MDAxNEZbOTlcdTAwMTe/bJ6+XHKbXHUwMDA3d28+XHUwMDFjmbXD+MvtdvV6ti9cdTAwMGKYh4cygpTtLDtcdTAwMDOHorebgJ1WYDlQXHUwMDE2llU2MiFcdTAwMTe6yFxiyFx1MDAxNlx1MDAwM5Sc/zrsUeyVOt/HOi9cdTAwMWZcIlxugauclkhcdTAwMDCT1//e1Y/dUSk+Xr9z9crhyWbra7PZWHhcdTAwMWZCXHUwMDAxaybDN0vsRYj6gFx1MDAwYqyspOr0wVwi+ddQPLtcdTAwMGbRvulVXHUwMDFhN7uVVyN9XGLz+bErmX/5kIl8yIhcdTAwMTfLsbBHSTDFi+VWReVqy1RcZiRXeq/0Pv566q6KSlx1MDAxZU/jQ8Yy0SdjgONKwzwzXHUwMDFj6vfmaqSSbNlccvnXfKAzrn9cXFx1MDAwYuVBWHBKwdHa8zpcdTAwMTBcdTAwMDdi7rVySYWoVWA0+ZU3XHUwMDEzg/a2vXF516Aj65JqUrYnXHUwMDFi+u7t60V3IJqDc0SD/MDJZ+dUXHUwMDFmbFn5MJJASHRkzLOX6Tg0XHUwMDE1pK1+klo5+1x1MDAwZmaanlx1MDAwMoS//Mf3XGZDZ06Xw2bzIOVcdTAwMTntmlx1MDAwN35Wcfl+WrLrLF/F0fXasFx1MDAxN791Pv5bO7z2XGaKOrbm24tv/1x1MDAwM0RNWtcifQ== + + + + widget.render_line(y=0)widget.render_line(y=1)widget.render_line(y=2)Strip([segment, segment, ...])Strip([segment, segment, ...])Strip([segment, segment, ...])Line API WidgetStrip([segment, segment, ...])Strip([segment, segment, ...])Strip([segment, segment, ...]) diff --git a/docs/images/segment.excalidraw.svg b/docs/images/segment.excalidraw.svg new file mode 100644 index 000000000..a1a521b85 --- /dev/null +++ b/docs/images/segment.excalidraw.svg @@ -0,0 +1,16 @@ + + + eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nN1aW1PbRlx1MDAxOH3nVzDuSztcdTAwMTM2e790ptOhJFx1MDAxNEIuUEJoaTuMsNa2gixcdTAwMTlJ5pJO/nu/XHUwMDE1RpIl7CBjUqd+XHUwMDAwe3el/Xb3nPNdpH/W1tc72c3Idn5cXO/Y665cdTAwMTdcdTAwMDZ+4l11nrn2S5ukQVx1MDAxY0FcdTAwMTfNf6fxOOnmI1x1MDAwN1k2Sn98/nzoJec2XHUwMDFihV7XossgXHUwMDFke2Gajf0gRt14+DzI7DD92f1961xy7U+jeOhnXHQqJ9mwfpDFye1cXDa0Q1x1MDAxYmUp3P1P+L2+/k/+t2JdYruZXHUwMDE39UObX5B3lVx1MDAwNirJ661v4yg3lkhFhGRcdTAwMWHLYkSQvoD5MutDd1x1MDAwZmy2ZY9r6vAjuvlhK+7ffPQvtlx1MDAwZcJNSo7CP8ppe0FcdTAwMThcdTAwMWVmN+HtVnjdwTipXHUwMDE4lWZJfG6PXHUwMDAzP1x1MDAxYrjZa+3Fdb6XXHUwMDBlwICiO4nH/UFkU7dcdTAwMDO4aI1HXjfIbtyNcNl6u1xy1XHX7pBcZkZSca41Zsatueh11zNKXHUwMDExx0ZLyZRcdTAwMTCMypphW3FcYmdcdTAwMDGGfYfzT2nZmdc974N5kV+MyVx1MDAxMi9KR15cdTAwMDInVo67miyZXHUwMDBiiqQglCqMXHKunszAXHUwMDA2/UHmjosgho1SynDMsJCiNMbmp0JcZnN2XHUwMDEyo4tcdTAwMWVnwmjXz1x1MDAxMfJ3ddtcIn+ybXeQKUHDJi2fy8W48S/rYKtcdTAwMDKugoN3e0dXxmNDtn/iXHUwMDFk7uyeblx1MDAxZqTvVLHgKXR6SVx1MDAxMl91ip7Pk2+loeOR791cIlx1MDAwZVx1MDAwMCk4XHUwMDExWlFDTdFcdTAwMWZcdTAwMDbROXRG4zAs2+LueVx00rXKStqxg1a2scZcdTAwMGVcdTAwMDBcdTAwMGLRilx1MDAxOf5gclxcnrw7vd5S9rV93dvjsYjt/lx0WYxcdTAwMWN0XHUwMDE2OdJcdTAwMTgk4n5ukFx1MDAwNblhkFwiXFxzXHUwMDAw1TQtXHUwMDE4QVhSMZNcclpSa7qLs4FiiaQhXHUwMDFjJuHGfVSTXHUwMDBlgiFtpm2744HEmlxuzivn80Q8mIdUXHUwMDAxu8SWhdTMXmf3gnQmRlx1MDAwNTVcZnSLPlxcwI9Aen57b7btx4R9XHUwMDE0Nt7ZXHUwMDBlg8GSXHUwMDA1fOlcdTAwMThlRFwirakmtFx1MDAwNlH+dFpNKtJbwJHyOlxmXHKmXHUwMDA0XHUwMDBiptqgcFxuXHUwMDFmrWT3evx7cPkq+nBp9/ax2Lu+Or5cdTAwMWPsLUl2OWfaXHUwMDE40lx1MDAwMswlauIoO1xmPtmc1FOt294wXGLzoyqac5SDgX91dmxcdTAwMTjGz9aP4yT0/+pUjyq1MHvtdu66zTDoO0Z0QtubpkpcdTAwMTZA8FR0Z/Go7O2CXHUwMDFkXHUwMDFl3C7Z9evriZOgXHUwMDFmRF74fpZNi3tcdTAwMTbGVL31jrWGXHUwMDEwIK3hXHUwMDBmZ+3u3ubbbSrVxYuD991cdTAwMWXTn3x/J5jB2lx1MDAxYfv+K79cIihGVIBCmqZjwVxiT7XWucsk73XVIzxcdTAwMGJhSNx5ldaeRTpBxZqV6/m/XHUwMDA1WMYwgkvFelwit0VmM4AoyjGRpEVwdfM2ftU/O9u/XHUwMDEx4uJ0g1x1MDAxZI9e71x1MDAxZmyuuuNcdTAwMTJMI6yVXHUwMDEyhlPnKNg0XHUwMDExuECSc8HInCjr0X5Mmyb4m35MSaG5Ulx1MDAxNU16Sj/2q/fJo3tcdTAwMDO6uSuOduTxsWTnLy+Wlj5cdTAwMTjNOG2B7sf5sVx1MDAxYz3fn8Wh/9P7ZGx/WFx1MDAwNT/WsGmxuLNidZ3AnEHKXHUwMDAwaWhcdTAwMTmafonAL0/UwcvRfnK+8bt9dbQpe1x1MDAxYofh6apcdTAwMTOYUYYoJNzwMVxuXHUwMDEy93K57nouOVx1MDAwMvpSXHUwMDE3PHHYjbp3XVwii+k9Low2XFyXkka6QK6N61rNaFRcdTAwMTBcdTAwMTBF9lQsJlNsdFx1MDAwNvZcdTAwMTNrsyDqo2kyVChcXIH616DwtEGL8Vx1MDAxN8/kL/gjXHR7rMpcdTAwMDFfou9V/8Ppm1x1MDAxNyPpXHUwMDFmvtl68UZEoThcdTAwMWOfrDp9hYJA0HBiZO6AWZ2+XG5RIDV0XHUwMDAwezWresVl87dCyTn8hWReSY1L2H+zTphcdTAwMTCBK1x1MDAxOfRXo2+aY2mV+Htr0VxcXHUwMDAy327vfUlkpVxmWvfAWlx1MDAwYlx1MDAwMVx1MDAxOU5cdTAwMGJcdTAwMGY8X69XlMJMXHUwMDBiJFx1MDAwNGR04Fx1MDAwZaRcdTAwMDF/PFx1MDAxZENzSZCmmFNtnthcdTAwMDVcdTAwMWKkQElcXKFEaqOwvqc8pDRSICXmLlx1MDAxY6hcdTAwMThzV7yXcFx1MDAwM6ZcdTAwMWVXu6eTlsVqlos71jTzkuyXIPJcdTAwMDHW04ZNnlHtPiDQy7ncXHUwMDFkOys3MMJcXFx1MDAxMVc9g3CSUWp4+WjG7Y03cqtFkuWlv8aqbeR/2Zr5XHUwMDA1z4o1YFxmwVx1MDAwNsPeMEyoNIRy1TCGMMjn8pJGw5rQS7OteDhcZjLY7v04iLL6tub7t+m4PrBeQ0BgNdW+uiiM3Fx1MDAxZKc1vfy2XpIm/1F8//vZvaNnYtl9Nlx1MDAxYTAub7dW/d9azqiaWclcdTAwMDYh4EJcYqVcdTAwMWUuZ/P914rKmWRcdTAwMWNcdTAwMTmqMVMu6IDMoaFmsFx1MDAwNdJhzEUkrGbX8tRcZuJcIsyNUkZcdTAwMGLIb4SsJP6lnCmEXHUwMDFkXHUwMDE1INOrXHUwMDE5M1EzJjXRXHUwMDFjyzZFg2XL2eLZ/lx1MDAwM+VsfuBblzNcdTAwMDaUYiBoXHUwMDEwVzqK8cq4W1x1MDAwNVx1MDAxMYgzdr+CPEjP5tfBpvWMXG7KNVx1MDAwMVx1MDAwNitOsKaGNawhXHUwMDAykVx1MDAxOer6LenZxmw45911JLdcdTAwMTS0WVx0lmGi3lokWFxmpuK0zZO5M9zt9a8hXHUwMDFhXHUwMDFkXHUwMDFmXnw8+qN38mv8epFcdTAwMWH/XCJitth7XHUwMDE1Tq5cdTAwMDTPdUxgXHUwMDAzwVlZpHA3oJQh8KJGXHUwMDEy6TA/O7tcdTAwMDLn7/H5YvZdr9drqph6UFWEQOKHtWStdGrxvOqJS/dSV6pQXyuvWqWMasFcXIpSQurNRfRcdTAwMDFbXHUwMDBiQCUtkqn5p7ySj+S4XHUwMDExiCqmlVCUS1x1MDAxMMKSkTldhUBaQSxuXHUwMDA0eHZiKq/GLI2wXHUwMDEwP3NcZiqtJOdcdTAwMWFCoEq4Vz6ZI65mg4VDPDZcdTAwMTJX9GzygFx1MDAwZbSVske+XHUwMDAw9aioQ2JcdTAwMTeutnlFqW3UMd9cdTAwMWJMuXnKMVx1MDAwM99GKTbCbUwz5lBIuUhAaYWpXHUwMDE0RuNcdTAwMDVjj/lv/9ViXHUwMDBmLMDlau5cdTAwMWVwSaFpwyhcdTAwMDKBMNPgrImGMFx1MDAxMoY0n5h+SyHIbGS7T1x1MDAwM9OzXCKQtclcdTAwMDRcdTAwMWRvNDrMXHUwMDAwdMVxXHUwMDAwylx1MDAwM39cIuDlKjuXgb365X76OVx1MDAwNq5N9tNcdJLNmfB57fO/MTl+mCJ9 + + + + "Hello, World"Style(bold=True)greeting.textgreeting.stylegreeting diff --git a/mkdocs.yml b/mkdocs.yml index 1748de127..bbb962580 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -168,6 +168,7 @@ nav: - "api/reactive.md" - "api/screen.md" - "api/static.md" + - "api/strip.md" - "api/text_log.md" - "api/timer.md" - "api/tree.md" diff --git a/poetry.lock b/poetry.lock index 7cd75899a..c1ee65e43 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,3 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. - [[package]] name = "aiohttp" version = "3.8.3" @@ -7,7 +5,1045 @@ description = "Async http client/server framework (asyncio)" category = "main" optional = false python-versions = ">=3.6" -files = [ + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = ">=4.0.0a3,<5.0" +asynctest = {version = "0.13.0", markers = "python_version < \"3.8\""} +attrs = ">=17.3.0" +charset-normalizer = ">=2.0,<3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns", "cchardet"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "anyio" +version = "3.6.2" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] +trio = ["trio (>=0.16,<0.22)"] + +[[package]] +name = "async-timeout" +version = "4.0.2" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""} + +[[package]] +name = "asynctest" +version = "0.13.0" +description = "Enhance the standard unittest package with features for testing asyncio libraries" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "attrs" +version = "22.2.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] +tests = ["attrs[tests-no-zope]", "zope.interface"] +tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] + +[[package]] +name = "black" +version = "23.1.0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "cached-property" +version = "1.5.2" +description = "A decorator for caching properties in classes." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "certifi" +version = "2022.12.7" +description = "Python package for providing Mozilla's CA Bundle." +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "cfgv" +version = "3.3.1" +description = "Validate configuration and produce human readable error messages." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "charset-normalizer" +version = "2.1.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.6.0" + +[package.extras] +unicode-backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" + +[[package]] +name = "colored" +version = "1.4.4" +description = "Simple library for color and formatting to terminal" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "coverage" +version = "7.1.0" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "distlib" +version = "0.3.6" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "exceptiongroup" +version = "1.1.0" +description = "Backport of PEP 654 (exception groups)" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "filelock" +version = "3.9.0" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2022.12.7)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] +testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "frozenlist" +version = "1.3.3" +description = "A list-like structure which implements collections.abc.MutableSequence" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "ghp-import" +version = "2.1.0" +description = "Copy your docs directly to the gh-pages branch." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +python-dateutil = ">=2.8.1" + +[package.extras] +dev = ["flake8", "markdown", "twine", "wheel"] + +[[package]] +name = "gitdb" +version = "4.0.10" +description = "Git Object Database" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +smmap = ">=3.0.1,<6" + +[[package]] +name = "gitpython" +version = "3.1.30" +description = "GitPython is a python library used to interact with Git repositories" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +gitdb = ">=4.0.1,<5" +typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\""} + +[[package]] +name = "griffe" +version = "0.25.4" +description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +cached-property = {version = "*", markers = "python_version < \"3.8\""} +colorama = ">=0.4" + +[package.extras] +async = ["aiofiles (>=0.7,<1.0)"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "httpcore" +version = "0.16.3" +description = "A minimal low-level HTTP client." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +anyio = ">=3.0,<5.0" +certifi = "*" +h11 = ">=0.13,<0.15" +sniffio = ">=1.0.0,<2.0.0" + +[package.extras] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] + +[[package]] +name = "httpx" +version = "0.23.3" +description = "The next generation HTTP client." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +certifi = "*" +httpcore = ">=0.15.0,<0.17.0" +rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] + +[[package]] +name = "identify" +version = "2.5.17" +description = "File identification library for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "importlib-metadata" +version = "4.13.0" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +perf = ["ipython"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "jinja2" +version = "3.0.3" +description = "A very fast and expressive template engine." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markdown" +version = "3.3.7" +description = "Python implementation of Markdown." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} + +[package.extras] +testing = ["coverage", "pyyaml"] + +[[package]] +name = "markdown-it-py" +version = "2.1.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +mdurl = ">=0.1,<1.0" +typing_extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark (>=3.2,<4.0)"] +code-style = ["pre-commit (==2.6)"] +compare = ["commonmark (>=0.9.1,<0.10.0)", "markdown (>=3.3.6,<3.4.0)", "mistletoe (>=0.8.1,<0.9.0)", "mistune (>=2.0.2,<2.1.0)", "panflute (>=2.1.3,<2.2.0)"] +linkify = ["linkify-it-py (>=1.0,<2.0)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "markupsafe" +version = "2.1.2" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "mergedeep" +version = "1.3.4" +description = "A deep merge function for 🐍." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "mkdocs" +version = "1.4.2" +description = "Project documentation with Markdown." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +click = ">=7.0" +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" +mergedeep = ">=1.3.4" +packaging = ">=20.5" +pyyaml = ">=5.1" +pyyaml-env-tag = ">=0.1" +typing-extensions = {version = ">=3.10", markers = "python_version < \"3.8\""} +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)"] + +[[package]] +name = "mkdocs-autorefs" +version = "0.4.1" +description = "Automatically link across pages in MkDocs." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +Markdown = ">=3.3" +mkdocs = ">=1.1" + +[[package]] +name = "mkdocs-exclude" +version = "1.0.2" +description = "A mkdocs plugin that lets you exclude files or trees." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +mkdocs = "*" + +[[package]] +name = "mkdocs-material" +version = "8.5.11" +description = "Documentation that simply works" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +jinja2 = ">=3.0.2" +markdown = ">=3.2" +mkdocs = ">=1.4.0" +mkdocs-material-extensions = ">=1.1" +pygments = ">=2.12" +pymdown-extensions = ">=9.4" +requests = ">=2.26" + +[[package]] +name = "mkdocs-material-extensions" +version = "1.1.1" +description = "Extension pack for Python Markdown and MkDocs Material." +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "mkdocs-rss-plugin" +version = "1.5.0" +description = "MkDocs plugin which generates a static RSS feed using git log and page.meta." +category = "dev" +optional = false +python-versions = ">=3.7, <4" + +[package.dependencies] +GitPython = ">=3.1,<3.2" +mkdocs = ">=1.1,<2" +pytz = {version = ">=2022.0.0,<2023.0.0", markers = "python_version < \"3.9\""} +tzdata = {version = ">=2022.0.0,<2023.0.0", markers = "python_version >= \"3.9\" and sys_platform == \"win32\""} + +[package.extras] +dev = ["black", "feedparser (>=6.0,<6.1)", "flake8 (>=4,<5.1)", "pre-commit (>=2.10,<2.21)", "pytest-cov (>=4.0.0,<4.1.0)", "validator-collection (>=1.5,<1.6)"] +doc = ["mkdocs-bootswatch (>=1,<2)", "mkdocs-minify-plugin (>=0.5.0,<0.6.0)", "pygments (>=2.5,<3)", "pymdown-extensions (>=7,<10)"] + +[[package]] +name = "mkdocstrings" +version = "0.20.0" +description = "Automatic documentation from sources, for MkDocs." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +Jinja2 = ">=2.11.1" +Markdown = ">=3.3" +MarkupSafe = ">=1.1" +mkdocs = ">=1.2" +mkdocs-autorefs = ">=0.3.1" +mkdocstrings-python = {version = ">=0.5.2", optional = true, markers = "extra == \"python\""} +pymdown-extensions = ">=6.3" + +[package.extras] +crystal = ["mkdocstrings-crystal (>=0.3.4)"] +python = ["mkdocstrings-python (>=0.5.2)"] +python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] + +[[package]] +name = "mkdocstrings-python" +version = "0.8.3" +description = "A Python handler for mkdocstrings." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +griffe = ">=0.24" +mkdocstrings = ">=0.19" + +[[package]] +name = "msgpack" +version = "1.0.4" +description = "MessagePack serializer" +category = "main" +optional = true +python-versions = "*" + +[[package]] +name = "multidict" +version = "6.0.4" +description = "multidict implementation" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "mypy" +version = "0.990" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +mypy-extensions = ">=0.4.3" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} +typing-extensions = ">=3.10" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "nanoid" +version = "2.0.0" +description = "A tiny, secure, URL-friendly, unique string ID generator for Python" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "nodeenv" +version = "1.7.0" +description = "Node.js virtual environment builder" +category = "dev" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "packaging" +version = "23.0" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "pathspec" +version = "0.11.0" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "platformdirs" +version = "2.6.2" +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" + +[package.dependencies] +typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""} + +[package.extras] +docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pre-commit" +version = "2.21.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "pygments" +version = "2.14.0" +description = "Pygments is a syntax highlighting package written in Python." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pymdown-extensions" +version = "9.9.2" +description = "Extension pack for Python Markdown." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +markdown = ">=3.2" + +[[package]] +name = "pytest" +version = "7.2.1" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "pytest-aiohttp" +version = "1.0.4" +description = "Pytest plugin for aiohttp support" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +aiohttp = ">=3.8.1" +pytest = ">=6.1.0" +pytest-asyncio = ">=0.17.2" + +[package.extras] +testing = ["coverage (==6.2)", "mypy (==0.931)"] + +[[package]] +name = "pytest-asyncio" +version = "0.20.3" +description = "Pytest support for asyncio" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +pytest = ">=6.1.0" +typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""} + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] + +[[package]] +name = "pytest-cov" +version = "2.12.1" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +coverage = ">=5.2.1" +pytest = ">=4.6" +toml = "*" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2022.7.1" +description = "World timezone definitions, modern and historical" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "pyyaml-env-tag" +version = "0.1" +description = "A custom YAML tag for referencing environment variables in YAML files. " +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyyaml = "*" + +[[package]] +name = "requests" +version = "2.28.2" +description = "Python HTTP for Humans." +category = "dev" +optional = false +python-versions = ">=3.7, <4" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rfc3986" +version = "1.5.0" +description = "Validating URI References per RFC 3986" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} + +[package.extras] +idna2008 = ["idna"] + +[[package]] +name = "rich" +version = "13.3.1" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "main" +optional = false +python-versions = ">=3.7.0" + +[package.dependencies] +markdown-it-py = ">=2.1.0,<3.0.0" +pygments = ">=2.14.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "setuptools" +version = "67.1.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "smmap" +version = "5.0.0" +description = "A pure Python implementation of a sliding window memory map manager" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "syrupy" +version = "3.0.6" +description = "Pytest Snapshot Test Utility" +category = "dev" +optional = false +python-versions = ">=3.7,<4" + +[package.dependencies] +colored = ">=1.3.92,<2.0.0" +pytest = ">=5.1.0,<8.0.0" + +[[package]] +name = "time-machine" +version = "2.9.0" +description = "Travel through time in your tests." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +python-dateutil = "*" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "typed-ast" +version = "1.5.4" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "typing-extensions" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "tzdata" +version = "2022.7" +description = "Provider of IANA time zone data" +category = "dev" +optional = false +python-versions = ">=2" + +[[package]] +name = "urllib3" +version = "1.26.14" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "virtualenv" +version = "20.17.1" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +distlib = ">=0.3.6,<1" +filelock = ">=3.4.1,<4" +importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.8\""} +platformdirs = ">=2.4,<3" + +[package.extras] +docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"] +testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "watchdog" +version = "2.2.1" +description = "Filesystem events monitoring" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[[package]] +name = "yarl" +version = "1.8.2" +description = "Yet another URL library" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" +typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} + +[[package]] +name = "zipp" +version = "3.12.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[extras] +dev = ["aiohttp", "click", "msgpack"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.7" +content-hash = "8b9c57d32f9db7d59bacc1e254e46bc5ae523e9e831494c205caf1b5fe7982e4" + +[metadata.files] +aiohttp = [ {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ba71c9b4dcbb16212f334126cc3d8beb6af377f6703d9dc2d9fb3874fd667ee9"}, {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d24b8bb40d5c61ef2d9b6a8f4528c2f17f1c5d2d31fed62ec860f6006142e83e"}, {file = "aiohttp-3.8.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f88df3a83cf9df566f171adba39d5bd52814ac0b94778d2448652fc77f9eb491"}, @@ -96,250 +1132,81 @@ files = [ {file = "aiohttp-3.8.3-cp39-cp39-win_amd64.whl", hash = "sha256:88e5be56c231981428f4f506c68b6a46fa25c4123a2e86d156c58a8369d31ab7"}, {file = "aiohttp-3.8.3.tar.gz", hash = "sha256:3828fb41b7203176b82fe5d699e0d845435f2374750a44b480ea6b930f6be269"}, ] - -[package.dependencies] -aiosignal = ">=1.1.2" -async-timeout = ">=4.0.0a3,<5.0" -asynctest = {version = "0.13.0", markers = "python_version < \"3.8\""} -attrs = ">=17.3.0" -charset-normalizer = ">=2.0,<3.0" -frozenlist = ">=1.1.1" -multidict = ">=4.5,<7.0" -typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} -yarl = ">=1.0,<2.0" - -[package.extras] -speedups = ["Brotli", "aiodns", "cchardet"] - -[[package]] -name = "aiosignal" -version = "1.3.1" -description = "aiosignal: a list of registered asynchronous callbacks" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ +aiosignal = [ {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, ] - -[package.dependencies] -frozenlist = ">=1.1.0" - -[[package]] -name = "anyio" -version = "3.6.2" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "dev" -optional = false -python-versions = ">=3.6.2" -files = [ +anyio = [ {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, ] - -[package.dependencies] -idna = ">=2.8" -sniffio = ">=1.1" -typing-extensions = {version = "*", markers = "python_version < \"3.8\""} - -[package.extras] -doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] -trio = ["trio (>=0.16,<0.22)"] - -[[package]] -name = "async-timeout" -version = "4.0.2" -description = "Timeout context manager for asyncio programs" -category = "main" -optional = false -python-versions = ">=3.6" -files = [ +async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, ] - -[package.dependencies] -typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""} - -[[package]] -name = "asynctest" -version = "0.13.0" -description = "Enhance the standard unittest package with features for testing asyncio libraries" -category = "main" -optional = false -python-versions = ">=3.5" -files = [ +asynctest = [ {file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"}, {file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"}, ] - -[[package]] -name = "attrs" -version = "22.2.0" -description = "Classes Without Boilerplate" -category = "main" -optional = false -python-versions = ">=3.6" -files = [ +attrs = [ {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, ] - -[package.extras] -cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] -tests = ["attrs[tests-no-zope]", "zope.interface"] -tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] - -[[package]] -name = "black" -version = "22.8.0" -description = "The uncompromising code formatter." -category = "dev" -optional = false -python-versions = ">=3.6.2" -files = [ - {file = "black-22.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce957f1d6b78a8a231b18e0dd2d94a33d2ba738cd88a7fe64f53f659eea49fdd"}, - {file = "black-22.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5107ea36b2b61917956d018bd25129baf9ad1125e39324a9b18248d362156a27"}, - {file = "black-22.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8166b7bfe5dcb56d325385bd1d1e0f635f24aae14b3ae437102dedc0c186747"}, - {file = "black-22.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd82842bb272297503cbec1a2600b6bfb338dae017186f8f215c8958f8acf869"}, - {file = "black-22.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d839150f61d09e7217f52917259831fe2b689f5c8e5e32611736351b89bb2a90"}, - {file = "black-22.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a05da0430bd5ced89176db098567973be52ce175a55677436a271102d7eaa3fe"}, - {file = "black-22.8.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a098a69a02596e1f2a58a2a1c8d5a05d5a74461af552b371e82f9fa4ada8342"}, - {file = "black-22.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5594efbdc35426e35a7defa1ea1a1cb97c7dbd34c0e49af7fb593a36bd45edab"}, - {file = "black-22.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a983526af1bea1e4cf6768e649990f28ee4f4137266921c2c3cee8116ae42ec3"}, - {file = "black-22.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b2c25f8dea5e8444bdc6788a2f543e1fb01494e144480bc17f806178378005e"}, - {file = "black-22.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:78dd85caaab7c3153054756b9fe8c611efa63d9e7aecfa33e533060cb14b6d16"}, - {file = "black-22.8.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:cea1b2542d4e2c02c332e83150e41e3ca80dc0fb8de20df3c5e98e242156222c"}, - {file = "black-22.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5b879eb439094751185d1cfdca43023bc6786bd3c60372462b6f051efa6281a5"}, - {file = "black-22.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a12e4e1353819af41df998b02c6742643cfef58282915f781d0e4dd7a200411"}, - {file = "black-22.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3a73f66b6d5ba7288cd5d6dad9b4c9b43f4e8a4b789a94bf5abfb878c663eb3"}, - {file = "black-22.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:e981e20ec152dfb3e77418fb616077937378b322d7b26aa1ff87717fb18b4875"}, - {file = "black-22.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8ce13ffed7e66dda0da3e0b2eb1bdfc83f5812f66e09aca2b0978593ed636b6c"}, - {file = "black-22.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:32a4b17f644fc288c6ee2bafdf5e3b045f4eff84693ac069d87b1a347d861497"}, - {file = "black-22.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ad827325a3a634bae88ae7747db1a395d5ee02cf05d9aa7a9bd77dfb10e940c"}, - {file = "black-22.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53198e28a1fb865e9fe97f88220da2e44df6da82b18833b588b1883b16bb5d41"}, - {file = "black-22.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:bc4d4123830a2d190e9cc42a2e43570f82ace35c3aeb26a512a2102bce5af7ec"}, - {file = "black-22.8.0-py3-none-any.whl", hash = "sha256:d2c21d439b2baf7aa80d6dd4e3659259be64c6f49dfd0f32091063db0e006db4"}, - {file = "black-22.8.0.tar.gz", hash = "sha256:792f7eb540ba9a17e8656538701d3eb1afcb134e3b45b71f20b25c77a8db7e6e"}, +black = [ + {file = "black-23.1.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:b6a92a41ee34b883b359998f0c8e6eb8e99803aa8bf3123bf2b2e6fec505a221"}, + {file = "black-23.1.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:57c18c5165c1dbe291d5306e53fb3988122890e57bd9b3dcb75f967f13411a26"}, + {file = "black-23.1.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:9880d7d419bb7e709b37e28deb5e68a49227713b623c72b2b931028ea65f619b"}, + {file = "black-23.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6663f91b6feca5d06f2ccd49a10f254f9298cc1f7f49c46e498a0771b507104"}, + {file = "black-23.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9afd3f493666a0cd8f8df9a0200c6359ac53940cbde049dcb1a7eb6ee2dd7074"}, + {file = "black-23.1.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:bfffba28dc52a58f04492181392ee380e95262af14ee01d4bc7bb1b1c6ca8d27"}, + {file = "black-23.1.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c1c476bc7b7d021321e7d93dc2cbd78ce103b84d5a4cf97ed535fbc0d6660648"}, + {file = "black-23.1.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:382998821f58e5c8238d3166c492139573325287820963d2f7de4d518bd76958"}, + {file = "black-23.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bf649fda611c8550ca9d7592b69f0637218c2369b7744694c5e4902873b2f3a"}, + {file = "black-23.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:121ca7f10b4a01fd99951234abdbd97728e1240be89fde18480ffac16503d481"}, + {file = "black-23.1.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:a8471939da5e824b891b25751955be52ee7f8a30a916d570a5ba8e0f2eb2ecad"}, + {file = "black-23.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8178318cb74f98bc571eef19068f6ab5613b3e59d4f47771582f04e175570ed8"}, + {file = "black-23.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a436e7881d33acaf2536c46a454bb964a50eff59b21b51c6ccf5a40601fbef24"}, + {file = "black-23.1.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:a59db0a2094d2259c554676403fa2fac3473ccf1354c1c63eccf7ae65aac8ab6"}, + {file = "black-23.1.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:0052dba51dec07ed029ed61b18183942043e00008ec65d5028814afaab9a22fd"}, + {file = "black-23.1.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:49f7b39e30f326a34b5c9a4213213a6b221d7ae9d58ec70df1c4a307cf2a1580"}, + {file = "black-23.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:162e37d49e93bd6eb6f1afc3e17a3d23a823042530c37c3c42eeeaf026f38468"}, + {file = "black-23.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b70eb40a78dfac24842458476135f9b99ab952dd3f2dab738c1881a9b38b753"}, + {file = "black-23.1.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:a29650759a6a0944e7cca036674655c2f0f63806ddecc45ed40b7b8aa314b651"}, + {file = "black-23.1.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:bb460c8561c8c1bec7824ecbc3ce085eb50005883a6203dcfb0122e95797ee06"}, + {file = "black-23.1.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c91dfc2c2a4e50df0026f88d2215e166616e0c80e86004d0003ece0488db2739"}, + {file = "black-23.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a951cc83ab535d248c89f300eccbd625e80ab880fbcfb5ac8afb5f01a258ac9"}, + {file = "black-23.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0680d4380db3719ebcfb2613f34e86c8e6d15ffeabcf8ec59355c5e7b85bb555"}, + {file = "black-23.1.0-py3-none-any.whl", hash = "sha256:7a0f701d314cfa0896b9001df70a530eb2472babb76086344e688829efd97d32"}, + {file = "black-23.1.0.tar.gz", hash = "sha256:b0bd97bea8903f5a2ba7219257a44e3f1f9d00073d6cc1add68f0beec69692ac"}, ] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} -typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "cached-property" -version = "1.5.2" -description = "A decorator for caching properties in classes." -category = "dev" -optional = false -python-versions = "*" -files = [ +cached-property = [ {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, ] - -[[package]] -name = "certifi" -version = "2022.12.7" -description = "Python package for providing Mozilla's CA Bundle." -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ +certifi = [ {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, ] - -[[package]] -name = "cfgv" -version = "3.3.1" -description = "Validate configuration and produce human readable error messages." -category = "dev" -optional = false -python-versions = ">=3.6.1" -files = [ +cfgv = [ {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, ] - -[[package]] -name = "charset-normalizer" -version = "2.1.1" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" -optional = false -python-versions = ">=3.6.0" -files = [ +charset-normalizer = [ {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, ] - -[package.extras] -unicode-backport = ["unicodedata2"] - -[[package]] -name = "click" -version = "8.1.3" -description = "Composable command line interface toolkit" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ +click = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, ] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ +colorama = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] - -[[package]] -name = "colored" -version = "1.4.4" -description = "Simple library for color and formatting to terminal" -category = "dev" -optional = false -python-versions = "*" -files = [ +colored = [ {file = "colored-1.4.4.tar.gz", hash = "sha256:04ff4d4dd514274fe3b99a21bb52fb96f2688c01e93fba7bef37221e7cb56ce0"}, ] - -[[package]] -name = "coverage" -version = "7.1.0" -description = "Code coverage measurement for Python" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +coverage = [ {file = "coverage-7.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3b946bbcd5a8231383450b195cfb58cb01cbe7f8949f5758566b881df4b33baf"}, {file = "coverage-7.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec8e767f13be637d056f7e07e61d089e555f719b387a7070154ad80a0ff31801"}, {file = "coverage-7.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a5a5879a939cb84959d86869132b00176197ca561c664fc21478c1eee60d75"}, @@ -392,61 +1259,19 @@ files = [ {file = "coverage-7.1.0-pp37.pp38.pp39-none-any.whl", hash = "sha256:755e89e32376c850f826c425ece2c35a4fc266c081490eb0a841e7c1cb0d3bda"}, {file = "coverage-7.1.0.tar.gz", hash = "sha256:10188fe543560ec4874f974b5305cd1a8bdcfa885ee00ea3a03733464c4ca265"}, ] - -[package.extras] -toml = ["tomli"] - -[[package]] -name = "distlib" -version = "0.3.6" -description = "Distribution utilities" -category = "dev" -optional = false -python-versions = "*" -files = [ +distlib = [ {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, ] - -[[package]] -name = "exceptiongroup" -version = "1.1.0" -description = "Backport of PEP 654 (exception groups)" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +exceptiongroup = [ {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, ] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "filelock" -version = "3.9.0" -description = "A platform independent file lock." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +filelock = [ {file = "filelock-3.9.0-py3-none-any.whl", hash = "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d"}, {file = "filelock-3.9.0.tar.gz", hash = "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de"}, ] - -[package.extras] -docs = ["furo (>=2022.12.7)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] -testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] - -[[package]] -name = "frozenlist" -version = "1.3.3" -description = "A list-like structure which implements collections.abc.MutableSequence" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ +frozenlist = [ {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff8bf625fe85e119553b5383ba0fb6aa3d0ec2ae980295aaefa552374926b3f4"}, {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dfbac4c2dfcc082fcf8d942d1e49b6aa0766c19d3358bd86e2000bf0fa4a9cf0"}, {file = "frozenlist-1.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b1c63e8d377d039ac769cd0926558bb7068a1f7abb0f003e3717ee003ad85530"}, @@ -522,266 +1347,63 @@ files = [ {file = "frozenlist-1.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfe33efc9cb900a4c46f91a5ceba26d6df370ffddd9ca386eb1d4f0ad97b9ea9"}, {file = "frozenlist-1.3.3.tar.gz", hash = "sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a"}, ] - -[[package]] -name = "ghp-import" -version = "2.1.0" -description = "Copy your docs directly to the gh-pages branch." -category = "main" -optional = false -python-versions = "*" -files = [ +ghp-import = [ {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, ] - -[package.dependencies] -python-dateutil = ">=2.8.1" - -[package.extras] -dev = ["flake8", "markdown", "twine", "wheel"] - -[[package]] -name = "gitdb" -version = "4.0.10" -description = "Git Object Database" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +gitdb = [ {file = "gitdb-4.0.10-py3-none-any.whl", hash = "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"}, {file = "gitdb-4.0.10.tar.gz", hash = "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a"}, ] - -[package.dependencies] -smmap = ">=3.0.1,<6" - -[[package]] -name = "gitpython" -version = "3.1.30" -description = "GitPython is a python library used to interact with Git repositories" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +gitpython = [ {file = "GitPython-3.1.30-py3-none-any.whl", hash = "sha256:cd455b0000615c60e286208ba540271af9fe531fa6a87cc590a7298785ab2882"}, {file = "GitPython-3.1.30.tar.gz", hash = "sha256:769c2d83e13f5d938b7688479da374c4e3d49f71549aaf462b646db9602ea6f8"}, ] - -[package.dependencies] -gitdb = ">=4.0.1,<5" -typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\""} - -[[package]] -name = "griffe" -version = "0.25.4" -description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +griffe = [ {file = "griffe-0.25.4-py3-none-any.whl", hash = "sha256:919f935a358b31074d16e324e26b041883c60a8cf10504655e394afc3a7caad8"}, {file = "griffe-0.25.4.tar.gz", hash = "sha256:f190edf8ef58d43c856d2d6761ec324a043ff60deb8c14359263571e8b91fe68"}, ] - -[package.dependencies] -cached-property = {version = "*", markers = "python_version < \"3.8\""} -colorama = ">=0.4" - -[package.extras] -async = ["aiofiles (>=0.7,<1.0)"] - -[[package]] -name = "h11" -version = "0.14.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +h11 = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] - -[package.dependencies] -typing-extensions = {version = "*", markers = "python_version < \"3.8\""} - -[[package]] -name = "httpcore" -version = "0.16.3" -description = "A minimal low-level HTTP client." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +httpcore = [ {file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"}, {file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"}, ] - -[package.dependencies] -anyio = ">=3.0,<5.0" -certifi = "*" -h11 = ">=0.13,<0.15" -sniffio = ">=1.0.0,<2.0.0" - -[package.extras] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] - -[[package]] -name = "httpx" -version = "0.23.3" -description = "The next generation HTTP client." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +httpx = [ {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"}, {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"}, ] - -[package.dependencies] -certifi = "*" -httpcore = ">=0.15.0,<0.17.0" -rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} -sniffio = "*" - -[package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] - -[[package]] -name = "identify" -version = "2.5.15" -description = "File identification library for Python" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "identify-2.5.15-py2.py3-none-any.whl", hash = "sha256:1f4b36c5f50f3f950864b2a047308743f064eaa6f6645da5e5c780d1c7125487"}, - {file = "identify-2.5.15.tar.gz", hash = "sha256:c22aa206f47cc40486ecf585d27ad5f40adbfc494a3fa41dc3ed0499a23b123f"}, +identify = [ + {file = "identify-2.5.17-py2.py3-none-any.whl", hash = "sha256:7d526dd1283555aafcc91539acc061d8f6f59adb0a7bba462735b0a318bff7ed"}, + {file = "identify-2.5.17.tar.gz", hash = "sha256:93cc61a861052de9d4c541a7acb7e3dcc9c11b398a2144f6e52ae5285f5f4f06"}, ] - -[package.extras] -license = ["ukkonen"] - -[[package]] -name = "idna" -version = "3.4" -description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" -optional = false -python-versions = ">=3.5" -files = [ +idna = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] - -[[package]] -name = "importlib-metadata" -version = "4.13.0" -description = "Read metadata from Python packages" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ +importlib-metadata = [ {file = "importlib_metadata-4.13.0-py3-none-any.whl", hash = "sha256:8a8a81bcf996e74fee46f0d16bd3eaa382a7eb20fd82445c3ad11f4090334116"}, {file = "importlib_metadata-4.13.0.tar.gz", hash = "sha256:dd0173e8f150d6815e098fd354f6414b0f079af4644ddfe90c71e2fc6174346d"}, ] - -[package.dependencies] -typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -perf = ["ipython"] -testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +iniconfig = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] - -[[package]] -name = "jinja2" -version = "3.0.3" -description = "A very fast and expressive template engine." -category = "main" -optional = false -python-versions = ">=3.6" -files = [ +jinja2 = [ {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, ] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "markdown" -version = "3.3.7" -description = "Python implementation of Markdown." -category = "main" -optional = false -python-versions = ">=3.6" -files = [ +markdown = [ {file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"}, {file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"}, ] - -[package.dependencies] -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} - -[package.extras] -testing = ["coverage", "pyyaml"] - -[[package]] -name = "markdown-it-py" -version = "2.1.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ +markdown-it-py = [ {file = "markdown-it-py-2.1.0.tar.gz", hash = "sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da"}, {file = "markdown_it_py-2.1.0-py3-none-any.whl", hash = "sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27"}, ] - -[package.dependencies] -mdurl = ">=0.1,<1.0" -typing_extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark (>=3.2,<4.0)"] -code-style = ["pre-commit (==2.6)"] -compare = ["commonmark (>=0.9.1,<0.10.0)", "markdown (>=3.3.6,<3.4.0)", "mistletoe (>=0.8.1,<0.9.0)", "mistune (>=2.0.2,<2.1.0)", "panflute (>=2.1.3,<2.2.0)"] -linkify = ["linkify-it-py (>=1.0,<2.0)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "markupsafe" -version = "2.1.2" -description = "Safely add untrusted strings to HTML/XML markup." -category = "main" -optional = false -python-versions = ">=3.7" -files = [ +markupsafe = [ {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, @@ -833,196 +1455,46 @@ files = [ {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, ] - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ +mdurl = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] - -[[package]] -name = "mergedeep" -version = "1.3.4" -description = "A deep merge function for 🐍." -category = "main" -optional = false -python-versions = ">=3.6" -files = [ +mergedeep = [ {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, ] - -[[package]] -name = "mkdocs" -version = "1.4.2" -description = "Project documentation with Markdown." -category = "main" -optional = false -python-versions = ">=3.7" -files = [ +mkdocs = [ {file = "mkdocs-1.4.2-py3-none-any.whl", hash = "sha256:c8856a832c1e56702577023cd64cc5f84948280c1c0fcc6af4cd39006ea6aa8c"}, {file = "mkdocs-1.4.2.tar.gz", hash = "sha256:8947af423a6d0facf41ea1195b8e1e8c85ad94ac95ae307fe11232e0424b11c5"}, ] - -[package.dependencies] -click = ">=7.0" -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" -mergedeep = ">=1.3.4" -packaging = ">=20.5" -pyyaml = ">=5.1" -pyyaml-env-tag = ">=0.1" -typing-extensions = {version = ">=3.10", markers = "python_version < \"3.8\""} -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)"] - -[[package]] -name = "mkdocs-autorefs" -version = "0.4.1" -description = "Automatically link across pages in MkDocs." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +mkdocs-autorefs = [ {file = "mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"}, {file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"}, ] - -[package.dependencies] -Markdown = ">=3.3" -mkdocs = ">=1.1" - -[[package]] -name = "mkdocs-exclude" -version = "1.0.2" -description = "A mkdocs plugin that lets you exclude files or trees." -category = "main" -optional = false -python-versions = "*" -files = [ +mkdocs-exclude = [ {file = "mkdocs-exclude-1.0.2.tar.gz", hash = "sha256:ba6fab3c80ddbe3fd31d3e579861fd3124513708271180a5f81846da8c7e2a51"}, ] - -[package.dependencies] -mkdocs = "*" - -[[package]] -name = "mkdocs-material" -version = "8.5.11" -description = "Documentation that simply works" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +mkdocs-material = [ {file = "mkdocs_material-8.5.11-py3-none-any.whl", hash = "sha256:c907b4b052240a5778074a30a78f31a1f8ff82d7012356dc26898b97559f082e"}, {file = "mkdocs_material-8.5.11.tar.gz", hash = "sha256:b0ea0513fd8cab323e8a825d6692ea07fa83e917bb5db042e523afecc7064ab7"}, ] - -[package.dependencies] -jinja2 = ">=3.0.2" -markdown = ">=3.2" -mkdocs = ">=1.4.0" -mkdocs-material-extensions = ">=1.1" -pygments = ">=2.12" -pymdown-extensions = ">=9.4" -requests = ">=2.26" - -[[package]] -name = "mkdocs-material-extensions" -version = "1.1.1" -description = "Extension pack for Python Markdown and MkDocs Material." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +mkdocs-material-extensions = [ {file = "mkdocs_material_extensions-1.1.1-py3-none-any.whl", hash = "sha256:e41d9f38e4798b6617ad98ca8f7f1157b1e4385ac1459ca1e4ea219b556df945"}, {file = "mkdocs_material_extensions-1.1.1.tar.gz", hash = "sha256:9c003da71e2cc2493d910237448c672e00cefc800d3d6ae93d2fc69979e3bd93"}, ] - -[[package]] -name = "mkdocs-rss-plugin" -version = "1.5.0" -description = "MkDocs plugin which generates a static RSS feed using git log and page.meta." -category = "dev" -optional = false -python-versions = ">=3.7, <4" -files = [ +mkdocs-rss-plugin = [ {file = "mkdocs-rss-plugin-1.5.0.tar.gz", hash = "sha256:4178b3830dcbad9b53b12459e315b1aad6b37d1e7e5c56c686866a10f99878a4"}, {file = "mkdocs_rss_plugin-1.5.0-py2.py3-none-any.whl", hash = "sha256:2ab14c20bf6b7983acbe50181e7e4a0778731d9c2d5c38107ca7047a7abd2165"}, ] - -[package.dependencies] -GitPython = ">=3.1,<3.2" -mkdocs = ">=1.1,<2" -pytz = {version = ">=2022.0.0,<2023.0.0", markers = "python_version < \"3.9\""} -tzdata = {version = ">=2022.0.0,<2023.0.0", markers = "python_version >= \"3.9\" and sys_platform == \"win32\""} - -[package.extras] -dev = ["black", "feedparser (>=6.0,<6.1)", "flake8 (>=4,<5.1)", "pre-commit (>=2.10,<2.21)", "pytest-cov (>=4.0.0,<4.1.0)", "validator-collection (>=1.5,<1.6)"] -doc = ["mkdocs-bootswatch (>=1,<2)", "mkdocs-minify-plugin (>=0.5.0,<0.6.0)", "pygments (>=2.5,<3)", "pymdown-extensions (>=7,<10)"] - -[[package]] -name = "mkdocstrings" -version = "0.20.0" -description = "Automatic documentation from sources, for MkDocs." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +mkdocstrings = [ {file = "mkdocstrings-0.20.0-py3-none-any.whl", hash = "sha256:f17fc2c4f760ec302b069075ef9e31045aa6372ca91d2f35ded3adba8e25a472"}, {file = "mkdocstrings-0.20.0.tar.gz", hash = "sha256:c757f4f646d4f939491d6bc9256bfe33e36c5f8026392f49eaa351d241c838e5"}, ] - -[package.dependencies] -Jinja2 = ">=2.11.1" -Markdown = ">=3.3" -MarkupSafe = ">=1.1" -mkdocs = ">=1.2" -mkdocs-autorefs = ">=0.3.1" -mkdocstrings-python = {version = ">=0.5.2", optional = true, markers = "extra == \"python\""} -pymdown-extensions = ">=6.3" - -[package.extras] -crystal = ["mkdocstrings-crystal (>=0.3.4)"] -python = ["mkdocstrings-python (>=0.5.2)"] -python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] - -[[package]] -name = "mkdocstrings-python" -version = "0.8.3" -description = "A Python handler for mkdocstrings." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +mkdocstrings-python = [ {file = "mkdocstrings-python-0.8.3.tar.gz", hash = "sha256:9ae473f6dc599339b09eee17e4d2b05d6ac0ec29860f3fc9b7512d940fc61adf"}, {file = "mkdocstrings_python-0.8.3-py3-none-any.whl", hash = "sha256:4e6e1cd6f37a785de0946ced6eb846eb2f5d891ac1cc2c7b832943d3529087a7"}, ] - -[package.dependencies] -griffe = ">=0.24" -mkdocstrings = ">=0.19" - -[[package]] -name = "msgpack" -version = "1.0.4" -description = "MessagePack serializer" -category = "main" -optional = true -python-versions = "*" -files = [ +msgpack = [ {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250"}, {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:112b0f93202d7c0fef0b7810d465fde23c746a2d482e1e2de2aafd2ce1492c88"}, {file = "msgpack-1.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:002b5c72b6cd9b4bafd790f364b8480e859b4712e91f43014fe01e4f957b8467"}, @@ -1076,15 +1548,7 @@ files = [ {file = "msgpack-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:4d5834a2a48965a349da1c5a79760d94a1a0172fbb5ab6b5b33cbf8447e109ce"}, {file = "msgpack-1.0.4.tar.gz", hash = "sha256:f5d869c18f030202eb412f08b28d2afeea553d6613aee89e200d7aca7ef01f5f"}, ] - -[[package]] -name = "multidict" -version = "6.0.4" -description = "multidict implementation" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ +multidict = [ {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, @@ -1160,15 +1624,7 @@ files = [ {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, ] - -[[package]] -name = "mypy" -version = "0.990" -description = "Optional static typing for Python" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +mypy = [ {file = "mypy-0.990-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aaf1be63e0207d7d17be942dcf9a6b641745581fe6c64df9a38deb562a7dbafa"}, {file = "mypy-0.990-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d555aa7f44cecb7ea3c0ac69d58b1a5afb92caa017285a8e9c4efbf0518b61b4"}, {file = "mypy-0.990-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f694d6d09a460b117dccb6857dda269188e3437c880d7b60fa0014fa872d1e9"}, @@ -1200,290 +1656,71 @@ files = [ {file = "mypy-0.990-py3-none-any.whl", hash = "sha256:8f1940325a8ed460ba03d19ab83742260fa9534804c317224e5d4e5aa588e2d6"}, {file = "mypy-0.990.tar.gz", hash = "sha256:72382cb609142dba3f04140d016c94b4092bc7b4d98ca718740dc989e5271b8d"}, ] - -[package.dependencies] -mypy-extensions = ">=0.4.3" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} -typing-extensions = ">=3.10" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -python2 = ["typed-ast (>=1.4.0,<2)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" -optional = false -python-versions = "*" -files = [ +mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] - -[[package]] -name = "nanoid" -version = "2.0.0" -description = "A tiny, secure, URL-friendly, unique string ID generator for Python" -category = "main" -optional = false -python-versions = "*" -files = [ +nanoid = [ {file = "nanoid-2.0.0-py3-none-any.whl", hash = "sha256:90aefa650e328cffb0893bbd4c236cfd44c48bc1f2d0b525ecc53c3187b653bb"}, {file = "nanoid-2.0.0.tar.gz", hash = "sha256:5a80cad5e9c6e9ae3a41fa2fb34ae189f7cb420b2a5d8f82bd9d23466e4efa68"}, ] - -[[package]] -name = "nodeenv" -version = "1.7.0" -description = "Node.js virtual environment builder" -category = "dev" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" -files = [ +nodeenv = [ {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, ] - -[package.dependencies] -setuptools = "*" - -[[package]] -name = "packaging" -version = "23.0" -description = "Core utilities for Python packages" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ +packaging = [ {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, ] - -[[package]] -name = "pathspec" -version = "0.11.0" -description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +pathspec = [ {file = "pathspec-0.11.0-py3-none-any.whl", hash = "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229"}, {file = "pathspec-0.11.0.tar.gz", hash = "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"}, ] - -[[package]] -name = "platformdirs" -version = "2.6.2" -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 = [ +platformdirs = [ {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, ] - -[package.dependencies] -typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""} - -[package.extras] -docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] - -[[package]] -name = "pluggy" -version = "1.0.0" -description = "plugin and hook calling mechanisms for python" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ +pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] - -[package.dependencies] -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "pre-commit" -version = "2.21.0" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +pre-commit = [ {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, ] - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -virtualenv = ">=20.10.0" - -[[package]] -name = "pygments" -version = "2.14.0" -description = "Pygments is a syntax highlighting package written in Python." -category = "main" -optional = false -python-versions = ">=3.6" -files = [ +pygments = [ {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, ] - -[package.extras] -plugins = ["importlib-metadata"] - -[[package]] -name = "pymdown-extensions" -version = "9.9.2" -description = "Extension pack for Python Markdown." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +pymdown-extensions = [ {file = "pymdown_extensions-9.9.2-py3-none-any.whl", hash = "sha256:c3d804eb4a42b85bafb5f36436342a5ad38df03878bb24db8855a4aa8b08b765"}, {file = "pymdown_extensions-9.9.2.tar.gz", hash = "sha256:ebb33069bafcb64d5f5988043331d4ea4929325dc678a6bcf247ddfcf96499f8"}, ] - -[package.dependencies] -markdown = ">=3.2" - -[[package]] -name = "pytest" -version = "7.2.1" -description = "pytest: simple powerful testing with Python" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +pytest = [ {file = "pytest-7.2.1-py3-none-any.whl", hash = "sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5"}, {file = "pytest-7.2.1.tar.gz", hash = "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"}, ] - -[package.dependencies] -attrs = ">=19.2.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] - -[[package]] -name = "pytest-aiohttp" -version = "1.0.4" -description = "Pytest plugin for aiohttp support" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +pytest-aiohttp = [ {file = "pytest-aiohttp-1.0.4.tar.gz", hash = "sha256:39ff3a0d15484c01d1436cbedad575c6eafbf0f57cdf76fb94994c97b5b8c5a4"}, {file = "pytest_aiohttp-1.0.4-py3-none-any.whl", hash = "sha256:1d2dc3a304c2be1fd496c0c2fb6b31ab60cd9fc33984f761f951f8ea1eb4ca95"}, ] - -[package.dependencies] -aiohttp = ">=3.8.1" -pytest = ">=6.1.0" -pytest-asyncio = ">=0.17.2" - -[package.extras] -testing = ["coverage (==6.2)", "mypy (==0.931)"] - -[[package]] -name = "pytest-asyncio" -version = "0.20.3" -description = "Pytest support for asyncio" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +pytest-asyncio = [ {file = "pytest-asyncio-0.20.3.tar.gz", hash = "sha256:83cbf01169ce3e8eb71c6c278ccb0574d1a7a3bb8eaaf5e50e0ad342afb33b36"}, {file = "pytest_asyncio-0.20.3-py3-none-any.whl", hash = "sha256:f129998b209d04fcc65c96fc85c11e5316738358909a8399e93be553d7656442"}, ] - -[package.dependencies] -pytest = ">=6.1.0" -typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""} - -[package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] -testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] - -[[package]] -name = "pytest-cov" -version = "2.12.1" -description = "Pytest plugin for measuring coverage." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ +pytest-cov = [ {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, ] - -[package.dependencies] -coverage = ">=5.2.1" -pytest = ">=4.6" -toml = "*" - -[package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] - -[[package]] -name = "python-dateutil" -version = "2.8.2" -description = "Extensions to the standard Python datetime module" -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ +python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "pytz" -version = "2022.7.1" -description = "World timezone definitions, modern and historical" -category = "dev" -optional = false -python-versions = "*" -files = [ +pytz = [ {file = "pytz-2022.7.1-py2.py3-none-any.whl", hash = "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"}, {file = "pytz-2022.7.1.tar.gz", hash = "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0"}, ] - -[[package]] -name = "pyyaml" -version = "6.0" -description = "YAML parser and emitter for Python" -category = "main" -optional = false -python-versions = ">=3.6" -files = [ +pyyaml = [ {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"}, @@ -1525,159 +1762,43 @@ files = [ {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] - -[[package]] -name = "pyyaml-env-tag" -version = "0.1" -description = "A custom YAML tag for referencing environment variables in YAML files. " -category = "main" -optional = false -python-versions = ">=3.6" -files = [ +pyyaml-env-tag = [ {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, ] - -[package.dependencies] -pyyaml = "*" - -[[package]] -name = "requests" -version = "2.28.2" -description = "Python HTTP for Humans." -category = "dev" -optional = false -python-versions = ">=3.7, <4" -files = [ +requests = [ {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, ] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "rfc3986" -version = "1.5.0" -description = "Validating URI References per RFC 3986" -category = "dev" -optional = false -python-versions = "*" -files = [ +rfc3986 = [ {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, ] - -[package.dependencies] -idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} - -[package.extras] -idna2008 = ["idna"] - -[[package]] -name = "rich" -version = "13.2.0" -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.2.0-py3-none-any.whl", hash = "sha256:7c963f0d03819221e9ac561e1bc866e3f95a02248c1234daa48954e6d381c003"}, - {file = "rich-13.2.0.tar.gz", hash = "sha256:f1a00cdd3eebf999a15d85ec498bfe0b1a77efe9b34f645768a54132ef444ac5"}, +rich = [ + {file = "rich-13.3.1-py3-none-any.whl", hash = "sha256:8aa57747f3fc3e977684f0176a88e789be314a99f99b43b75d1e9cb5dc6db9e9"}, + {file = "rich-13.3.1.tar.gz", hash = "sha256:125d96d20c92b946b983d0d392b84ff945461e5a06d3867e9f9e575f8697b67f"}, ] - -[package.dependencies] -markdown-it-py = ">=2.1.0,<3.0.0" -pygments = ">=2.6.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] - -[[package]] -name = "setuptools" -version = "66.1.1" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "setuptools-66.1.1-py3-none-any.whl", hash = "sha256:6f590d76b713d5de4e49fe4fbca24474469f53c83632d5d0fd056f7ff7e8112b"}, - {file = "setuptools-66.1.1.tar.gz", hash = "sha256:ac4008d396bc9cd983ea483cb7139c0240a07bbc74ffb6232fceffedc6cf03a8"}, +setuptools = [ + {file = "setuptools-67.1.0-py3-none-any.whl", hash = "sha256:a7687c12b444eaac951ea87a9627c4f904ac757e7abdc5aac32833234af90378"}, + {file = "setuptools-67.1.0.tar.gz", hash = "sha256:e261cdf010c11a41cb5cb5f1bf3338a7433832029f559a6a7614bd42a967c300"}, ] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ +six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] - -[[package]] -name = "smmap" -version = "5.0.0" -description = "A pure Python implementation of a sliding window memory map manager" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ +smmap = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, ] - -[[package]] -name = "sniffio" -version = "1.3.0" -description = "Sniff out which async library your code is running under" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +sniffio = [ {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, ] - -[[package]] -name = "syrupy" -version = "3.0.6" -description = "Pytest Snapshot Test Utility" -category = "dev" -optional = false -python-versions = ">=3.7,<4" -files = [ +syrupy = [ {file = "syrupy-3.0.6-py3-none-any.whl", hash = "sha256:9c18e22264026b34239bcc87ab7cc8d893eb17236ea7dae634217ea4f22a848d"}, {file = "syrupy-3.0.6.tar.gz", hash = "sha256:583aa5ca691305c27902c3e29a1ce9da50ff9ab5f184c54b1dc124a16e4a6cf4"}, ] - -[package.dependencies] -colored = ">=1.3.92,<2.0.0" -pytest = ">=5.1.0,<8.0.0" - -[[package]] -name = "time-machine" -version = "2.9.0" -description = "Travel through time in your tests." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +time-machine = [ {file = "time-machine-2.9.0.tar.gz", hash = "sha256:60222d43f6e93a926adc36ed37a54bc8e4d0d8d1c4d449096afcfe85086129c2"}, {file = "time_machine-2.9.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fd72c0b2e7443fff6e4481991742b72c17f73735e5fdd176406ca48df187a5c9"}, {file = "time_machine-2.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5657e0e6077cf15b37f0d8cf78e868113bbb3ecccc60064c40fe52d8166ca8b1"}, @@ -1732,42 +1853,15 @@ files = [ {file = "time_machine-2.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:cc6bf01211b5ea40f633d5502c5aa495b415ebaff66e041820997dae70a508e1"}, {file = "time_machine-2.9.0-cp39-cp39-win_arm64.whl", hash = "sha256:3ce445775fcf7cb4040cfdba4b7c4888e7fd98bbcccfe1dc3fa8a798ed1f1d24"}, ] - -[package.dependencies] -python-dateutil = "*" - -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ +toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ +tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] - -[[package]] -name = "typed-ast" -version = "1.5.4" -description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ +typed-ast = [ {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, @@ -1793,78 +1887,23 @@ files = [ {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] - -[[package]] -name = "typing-extensions" -version = "4.4.0" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ +typing-extensions = [ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] - -[[package]] -name = "tzdata" -version = "2022.7" -description = "Provider of IANA time zone data" -category = "dev" -optional = false -python-versions = ">=2" -files = [ +tzdata = [ {file = "tzdata-2022.7-py2.py3-none-any.whl", hash = "sha256:2b88858b0e3120792a3c0635c23daf36a7d7eeeca657c323da299d2094402a0d"}, {file = "tzdata-2022.7.tar.gz", hash = "sha256:fe5f866eddd8b96e9fcba978f8e503c909b19ea7efda11e52e39494bad3a7bfa"}, ] - -[[package]] -name = "urllib3" -version = "1.26.14" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -files = [ +urllib3 = [ {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, ] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "virtualenv" -version = "20.17.1" -description = "Virtual Python Environment builder" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ +virtualenv = [ {file = "virtualenv-20.17.1-py3-none-any.whl", hash = "sha256:ce3b1684d6e1a20a3e5ed36795a97dfc6af29bc3970ca8dab93e11ac6094b3c4"}, {file = "virtualenv-20.17.1.tar.gz", hash = "sha256:f8b927684efc6f1cc206c9db297a570ab9ad0e51c16fa9e45487d36d1905c058"}, ] - -[package.dependencies] -distlib = ">=0.3.6,<1" -filelock = ">=3.4.1,<4" -importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.8\""} -platformdirs = ">=2.4,<3" - -[package.extras] -docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"] -testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] - -[[package]] -name = "watchdog" -version = "2.2.1" -description = "Filesystem events monitoring" -category = "main" -optional = false -python-versions = ">=3.6" -files = [ +watchdog = [ {file = "watchdog-2.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a09483249d25cbdb4c268e020cb861c51baab2d1affd9a6affc68ffe6a231260"}, {file = "watchdog-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5100eae58133355d3ca6c1083a33b81355c4f452afa474c2633bd2fbbba398b3"}, {file = "watchdog-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e618a4863726bc7a3c64f95c218437f3349fb9d909eb9ea3a1ed3b567417c661"}, @@ -1894,18 +1933,7 @@ files = [ {file = "watchdog-2.2.1-py3-none-win_ia64.whl", hash = "sha256:195ab1d9d611a4c1e5311cbf42273bc541e18ea8c32712f2fb703cfc6ff006f9"}, {file = "watchdog-2.2.1.tar.gz", hash = "sha256:cdcc23c9528601a8a293eb4369cbd14f6b4f34f07ae8769421252e9c22718b6f"}, ] - -[package.extras] -watchmedo = ["PyYAML (>=3.10)"] - -[[package]] -name = "yarl" -version = "1.8.2" -description = "Yet another URL library" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ +yarl = [ {file = "yarl-1.8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bb81f753c815f6b8e2ddd2eef3c855cf7da193b82396ac013c661aaa6cc6b0a5"}, {file = "yarl-1.8.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:47d49ac96156f0928f002e2424299b2c91d9db73e08c4cd6742923a086f1c863"}, {file = "yarl-1.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3fc056e35fa6fba63248d93ff6e672c096f95f7836938241ebc8260e062832fe"}, @@ -1981,32 +2009,7 @@ files = [ {file = "yarl-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:6604711362f2dbf7160df21c416f81fac0de6dbcf0b5445a2ef25478ecc4c778"}, {file = "yarl-1.8.2.tar.gz", hash = "sha256:49d43402c6e3013ad0978602bf6bf5328535c48d192304b91b97a3c6790b1562"}, ] - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" -typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} - -[[package]] -name = "zipp" -version = "3.11.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, - {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, +zipp = [ + {file = "zipp-3.12.0-py3-none-any.whl", hash = "sha256:9eb0a4c5feab9b08871db0d672745b53450d7f26992fd1e4653aa43345e97b86"}, + {file = "zipp-3.12.0.tar.gz", hash = "sha256:73efd63936398aac78fd92b6f4865190119d6c91b531532e798977ea8dd402eb"}, ] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - -[extras] -dev = ["aiohttp", "click", "msgpack"] - -[metadata] -lock-version = "2.0" -python-versions = "^3.7" -content-hash = "b70dc64a3c9e7a7b765252f5dd1a5de8ed6efacd0695cde32ff983b14ec55ca6" diff --git a/pyproject.toml b/pyproject.toml index 9582f1550..bfd661b74 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ dev = ["aiohttp", "click", "msgpack"] [tool.poetry.group.dev.dependencies] pytest = "^7.1.3" -black = "^22.3.0,<22.10.0" # macos wheel issue on 22.10 +black = "^23.1.0" mypy = "^0.990" pytest-cov = "^2.12.1" mkdocs = "^1.3.0" diff --git a/src/textual/_animator.py b/src/textual/_animator.py index 330ea2dfd..6ace3dcff 100644 --- a/src/textual/_animator.py +++ b/src/textual/_animator.py @@ -322,7 +322,6 @@ class Animator: ) if animation is None: - if not isinstance(value, (int, float)) and not isinstance( value, Animatable ): diff --git a/src/textual/_parser.py b/src/textual/_parser.py index 0a725c85d..5163a6014 100644 --- a/src/textual/_parser.py +++ b/src/textual/_parser.py @@ -79,7 +79,6 @@ class Parser(Generic[T]): self._awaiting = next(self._gen) def feed(self, data: str) -> Iterable[T]: - if self._eof: raise ParseError("end of file reached") from None if not data: @@ -104,7 +103,6 @@ class Parser(Generic[T]): yield popleft() while pos < data_size or isinstance(self._awaiting, _PeekBuffer): - _awaiting = self._awaiting if isinstance(_awaiting, _Read1): self._awaiting = self._gen.send(data[pos : pos + 1]) diff --git a/src/textual/_sleep.py b/src/textual/_sleep.py index cb632e899..bb931e062 100644 --- a/src/textual/_sleep.py +++ b/src/textual/_sleep.py @@ -38,7 +38,6 @@ class Sleeper(Thread): async def check_sleeps() -> None: - sleeper = Sleeper() sleeper.start() diff --git a/src/textual/_xterm_parser.py b/src/textual/_xterm_parser.py index b97a1abd1..7111fca50 100644 --- a/src/textual/_xterm_parser.py +++ b/src/textual/_xterm_parser.py @@ -92,7 +92,6 @@ class XTermParser(Parser[events.Event]): return None def parse(self, on_token: TokenCallback) -> Generator[Awaitable, str, None]: - ESC = "\x1b" read1 = self.read1 sequence_to_key_events = self._sequence_to_key_events @@ -161,7 +160,6 @@ class XTermParser(Parser[events.Event]): # Look ahead through the suspected escape sequence for a match while True: - # If we run into another ESC at this point, then we've failed # to find a match, and should issue everything we've seen within # the suspected sequence as Key events instead. diff --git a/src/textual/cli/previews/colors.py b/src/textual/cli/previews/colors.py index e25c70d7f..3ae0eb669 100644 --- a/src/textual/cli/previews/colors.py +++ b/src/textual/cli/previews/colors.py @@ -30,7 +30,6 @@ class Content(Vertical): class ColorsView(Vertical): def compose(self) -> ComposeResult: - LEVELS = [ "darken-3", "darken-2", @@ -42,7 +41,6 @@ class ColorsView(Vertical): ] for color_name in ColorSystem.COLOR_NAMES: - items: list[Widget] = [Label(f'"{color_name}"')] for level in LEVELS: color = f"{color_name}-{level}" if level else color_name diff --git a/src/textual/css/_styles_builder.py b/src/textual/css/_styles_builder.py index 14f1e5404..ff66e9c84 100644 --- a/src/textual/css/_styles_builder.py +++ b/src/textual/css/_styles_builder.py @@ -434,7 +434,6 @@ class StylesBuilder: process_padding_left = _process_space_partial def _parse_border(self, name: str, tokens: list[Token]) -> BorderValue: - border_type: EdgeType = "solid" border_color = Color(0, 255, 0) diff --git a/src/textual/css/parse.py b/src/textual/css/parse.py index 7bfe1514c..1b98a56f6 100644 --- a/src/textual/css/parse.py +++ b/src/textual/css/parse.py @@ -36,7 +36,6 @@ SELECTOR_MAP: dict[str, tuple[SelectorType, Specificity3]] = { @lru_cache(maxsize=1024) def parse_selectors(css_selectors: str) -> tuple[SelectorSet, ...]: - if not css_selectors.strip(): return () diff --git a/src/textual/css/query.py b/src/textual/css/query.py index 3f00ee718..7e5c149cc 100644 --- a/src/textual/css/query.py +++ b/src/textual/css/query.py @@ -72,7 +72,6 @@ class DOMQuery(Generic[QueryType]): exclude: str | None = None, parent: DOMQuery | None = None, ) -> None: - self._node = node self._nodes: list[QueryType] | None = None self._filters: list[tuple[SelectorSet, ...]] = ( diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index 0bbb66823..5f7ff7235 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -327,7 +327,6 @@ class StylesBase(ABC): # Check we are animating a Scalar or Scalar offset if isinstance(start_value, (Scalar, ScalarOffset)): - # If destination is a number, we can convert that to a scalar if isinstance(value, (int, float)): value = Scalar(value, Unit.CELLS, Unit.CELLS) diff --git a/src/textual/devtools/service.py b/src/textual/devtools/service.py index 6e55456d1..cea646739 100644 --- a/src/textual/devtools/service.py +++ b/src/textual/devtools/service.py @@ -236,7 +236,6 @@ class ClientHandler: message = cast(WSMessage, message) if message.type in (WSMsgType.TEXT, WSMsgType.BINARY): - try: if isinstance(message.data, bytes): message = msgpack.unpackb(message.data) diff --git a/src/textual/drivers/linux_driver.py b/src/textual/drivers/linux_driver.py index 2d1b5e1a0..09260be6f 100644 --- a/src/textual/drivers/linux_driver.py +++ b/src/textual/drivers/linux_driver.py @@ -92,7 +92,6 @@ class LinuxDriver(Driver): self.console.file.flush() def start_application_mode(self): - loop = asyncio.get_running_loop() def send_size_event(): @@ -123,7 +122,6 @@ class LinuxDriver(Driver): except termios.error: pass else: - newattr[tty.LFLAG] = self._patch_lflag(newattr[tty.LFLAG]) newattr[tty.IFLAG] = self._patch_iflag(newattr[tty.IFLAG]) @@ -208,7 +206,6 @@ class LinuxDriver(Driver): pass # TODO: log def _run_input_thread(self, loop) -> None: - selector = selectors.DefaultSelector() selector.register(self.fileno, selectors.EVENT_READ) diff --git a/src/textual/drivers/windows_driver.py b/src/textual/drivers/windows_driver.py index 0899f65ef..26b155f06 100644 --- a/src/textual/drivers/windows_driver.py +++ b/src/textual/drivers/windows_driver.py @@ -58,7 +58,6 @@ class WindowsDriver(Driver): self.console.file.write("\x1b[?2004l") def start_application_mode(self) -> None: - loop = asyncio.get_running_loop() self._restore_console = win32.enable_application_mode() diff --git a/src/textual/layouts/horizontal.py b/src/textual/layouts/horizontal.py index ac643663e..2d6736b08 100644 --- a/src/textual/layouts/horizontal.py +++ b/src/textual/layouts/horizontal.py @@ -18,7 +18,6 @@ class HorizontalLayout(Layout): def arrange( self, parent: Widget, children: list[Widget], size: Size ) -> ArrangeResult: - placements: list[WidgetPlacement] = [] add_placement = placements.append x = max_height = Fraction(0) diff --git a/src/textual/layouts/vertical.py b/src/textual/layouts/vertical.py index ccdcd1be3..f26e531c0 100644 --- a/src/textual/layouts/vertical.py +++ b/src/textual/layouts/vertical.py @@ -19,7 +19,6 @@ class VerticalLayout(Layout): def arrange( self, parent: Widget, children: list[Widget], size: Size ) -> ArrangeResult: - placements: list[WidgetPlacement] = [] add_placement = placements.append parent_size = parent.outer_size diff --git a/src/textual/message_pump.py b/src/textual/message_pump.py index c469b381d..d79e0764c 100644 --- a/src/textual/message_pump.py +++ b/src/textual/message_pump.py @@ -398,7 +398,6 @@ class MessagePump(metaclass=MessagePumpMeta): self.app._handle_exception(error) break finally: - self._message_queue.task_done() current_time = time() diff --git a/src/textual/reactive.py b/src/textual/reactive.py index 048bb56e1..93139501d 100644 --- a/src/textual/reactive.py +++ b/src/textual/reactive.py @@ -170,7 +170,6 @@ class Reactive(Generic[ReactiveType]): getattr(obj, "__computes", []).clear() def __set_name__(self, owner: Type[MessageTarget], name: str) -> None: - # Check for compute method if hasattr(owner, f"compute_{name}"): # Compute methods are stored in a list called `__computes` diff --git a/src/textual/scrollbar.py b/src/textual/scrollbar.py index ec3cd4790..ad8a99e7e 100644 --- a/src/textual/scrollbar.py +++ b/src/textual/scrollbar.py @@ -92,7 +92,6 @@ class ScrollBarRender: back_color: Color = Color.parse("#555555"), bar_color: Color = Color.parse("bright_magenta"), ) -> Segments: - if vertical: bars = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", " "] else: @@ -190,7 +189,6 @@ class ScrollBarRender: @rich.repr.auto class ScrollBar(Widget): - renderer: ClassVar[Type[ScrollBarRender]] = ScrollBarRender """The class used for rendering scrollbars. This can be overriden and set to a ScrollBarRender-derived class diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index 0be163d67..40282e03f 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -88,7 +88,6 @@ class DirectoryTree(Tree[DirEntry]): id: str | None = None, classes: str | None = None, ) -> None: - self.path = path super().__init__( path, diff --git a/src/textual/widgets/_input.py b/src/textual/widgets/_input.py index ee47b3801..278ab3a88 100644 --- a/src/textual/widgets/_input.py +++ b/src/textual/widgets/_input.py @@ -28,7 +28,6 @@ class _InputRenderable: def __rich_console__( self, console: "Console", options: "ConsoleOptions" ) -> "RenderResult": - input = self.input result = input._value if input._cursor_at_end: diff --git a/src/textual/widgets/_static.py b/src/textual/widgets/_static.py index 7d2059b3d..a9698a954 100644 --- a/src/textual/widgets/_static.py +++ b/src/textual/widgets/_static.py @@ -57,7 +57,6 @@ class Static(Widget, inherit_bindings=False): id: str | None = None, classes: str | None = None, ) -> None: - super().__init__(name=name, id=id, classes=classes) self.expand = expand self.shrink = shrink diff --git a/src/textual/widgets/_text_log.py b/src/textual/widgets/_text_log.py index 3b6f931f4..1c116c0b4 100644 --- a/src/textual/widgets/_text_log.py +++ b/src/textual/widgets/_text_log.py @@ -159,7 +159,6 @@ class TextLog(ScrollView, can_focus=True): return lines def _render_line(self, y: int, scroll_x: int, width: int) -> Strip: - if y >= len(self.lines): return Strip.blank(width, self.rich_style) diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index 2f3d073cf..1eee0da0f 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -281,7 +281,6 @@ class TreeNode(Generic[TreeDataType]): class Tree(Generic[TreeDataType], ScrollView, can_focus=True): - BINDINGS: ClassVar[list[BindingType]] = [ Binding("enter", "select_cursor", "Select", show=False), Binding("space", "toggle_node", "Toggle", show=False), diff --git a/src/textual/widgets/_welcome.py b/src/textual/widgets/_welcome.py index 3c8a6d1be..0ad5032dc 100644 --- a/src/textual/widgets/_welcome.py +++ b/src/textual/widgets/_welcome.py @@ -24,11 +24,10 @@ Where the fear has gone there will be nothing. Only I will remain." class Welcome(Static): - DEFAULT_CSS = """ Welcome { width: 100%; - height: 100%; + height: 100%; background: $surface; } @@ -44,7 +43,7 @@ class Welcome(Static): Welcome #close { dock: bottom; - width: 100%; + width: 100%; } """ From a5808db8b86f36252565ceb8a400176124fc184e Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 3 Feb 2023 19:10:03 +0100 Subject: [PATCH 81/93] more docs and diagrams --- docs/api/scroll_view.md | 1 + docs/examples/guide/widgets/checker02.py | 6 -- docs/examples/guide/widgets/checker03.py | 26 +++---- docs/guide/widgets.md | 97 +++++++++++++++++++----- docs/images/render_line.excalidraw.svg | 6 +- docs/images/scroll_view.excalidraw.svg | 16 ++++ mkdocs.yml | 1 + 7 files changed, 111 insertions(+), 42 deletions(-) create mode 100644 docs/api/scroll_view.md create mode 100644 docs/images/scroll_view.excalidraw.svg diff --git a/docs/api/scroll_view.md b/docs/api/scroll_view.md new file mode 100644 index 000000000..461d9de6f --- /dev/null +++ b/docs/api/scroll_view.md @@ -0,0 +1 @@ +::: textual.scroll_view.ScrollView diff --git a/docs/examples/guide/widgets/checker02.py b/docs/examples/guide/widgets/checker02.py index 48b674ea9..f108de8c5 100644 --- a/docs/examples/guide/widgets/checker02.py +++ b/docs/examples/guide/widgets/checker02.py @@ -24,12 +24,6 @@ class CheckerBoard(Widget): } """ - def get_content_width(self, container: Size, viewport: Size) -> int: - return 64 - - def get_content_height(self, container: Size, viewport: Size, width: int) -> int: - return 32 - def render_line(self, y: int) -> Strip: """Render a line of the widget. y is relative to the top of the widget.""" diff --git a/docs/examples/guide/widgets/checker03.py b/docs/examples/guide/widgets/checker03.py index ccdab8ebf..03ca19381 100644 --- a/docs/examples/guide/widgets/checker03.py +++ b/docs/examples/guide/widgets/checker03.py @@ -23,42 +23,40 @@ class CheckerBoard(ScrollView): } """ - def get_content_width(self, container: Size, viewport: Size) -> int: - return 64 - - def get_content_height(self, container: Size, viewport: Size, width: int) -> int: - return 32 - - def on_mount(self) -> None: - self.virtual_size = Size(64, 32) + def __init__(self, board_size: int) -> None: + super().__init__() + self.board_size = board_size + # Each square is 4 rows and 8 columns + self.virtual_size = Size(board_size * 8, board_size * 4) def render_line(self, y: int) -> Strip: """Render a line of the widget. y is relative to the top of the widget.""" - scroll_x, scroll_y = self.scroll_offset - y += scroll_y + scroll_x, scroll_y = self.scroll_offset # The current scroll position + y += scroll_y # The line at the top of the widget is now `scroll_y`, not zero! row_index = y // 4 # four lines per row white = self.get_component_rich_style("checkerboard--white-square") black = self.get_component_rich_style("checkerboard--black-square") - if row_index >= 8: + if row_index >= self.board_size: return Strip.blank(self.size.width) is_odd = row_index % 2 segments = [ Segment(" " * 8, black if (column + is_odd) % 2 else white) - for column in range(8) + for column in range(self.board_size) ] - strip = Strip(segments, 8 * 8) + strip = Strip(segments, self.board_size * 8) + # Crop the strip so that is covers the visible area strip = strip.crop(scroll_x, scroll_x + self.size.width) return strip class BoardApp(App): def compose(self) -> ComposeResult: - yield CheckerBoard() + yield CheckerBoard(100) if __name__ == "__main__": diff --git a/docs/guide/widgets.md b/docs/guide/widgets.md index be7098232..80676284c 100644 --- a/docs/guide/widgets.md +++ b/docs/guide/widgets.md @@ -200,30 +200,28 @@ TODO: Explanation of compound widgets ## Line API -Working with Rich renderables allows you to build sophisticated widgets with minimal effort, but there is a downside to widgets that return renderables. -When you resize a widget or update its state, Textual has to refresh the widget's content in its entirety, which may be expensive. -You are unlikely to notice this if the widget fits within the screen but large widgets that scroll may slow down your application. +A downside of widgets that return Rich renderables is that Textual will redraw the entire widget when its state is updated or it changes size. +If a widget is large enough to require scrolling or updates frequently then this redrawing can make your app feel less responsive. +Textual offers an alternative API which reduces the amount of work required refresh a widget, and makes it possible to update portions of a widget (as small as a single character) without a full redraw. This is known as the *line API*. -Textual offers an alternative API which reduces the amount of work Textual needs to do to refresh a widget, and makes it possible to update portions of a widget (as small as a single character). This is known as the *line API*. +!!! note -!!! info - - The [DataTable](./../widgets/data_table.md) widget uses the Line API, which can support thousands or even millions of rows without a reduction in render times. + The Line API requires a little more work that typical Rich renderables, but can produce very power widgets such as the builtin [DataTable](./../widgets/data_table.md) which can handle thousands or even millions of rows. ### Render Line method -To build an widget with the line API, implement a `render_line` method rather than a `render` method. The `render_line` method takes a single integer argument `y` which is an offset from the top of the widget, and should return a [Strip][textual.strip.Strip] object which contains that line's content. -Textual will call this method as required to to get the content for every line. +To build a widget with the line API, implement a `render_line` method rather than a `render` method. The `render_line` method takes a single integer argument `y` which is an offset from the top of the widget, and should return a [Strip][textual.strip.Strip] object containing that line's content. +Textual will call this method as required to to get content for every row of characters in the widget.
--8<-- "docs/images/render_line.excalidraw.svg"
-Let's look at an example before we go in to the details. The following Textual app implements a widget with the line API that renders a checkerboard pattern. This might form the basis of a chess / checkers app. Here's the code: +Let's look at an example before we go in to the details. The following Textual app implements a widget with the line API that renders a checkerboard pattern. This might form the basis of a chess / checkers game. Here's the code: === "checker01.py" - ```python title="checker01.py" hl_lines="12-30" + ```python title="checker01.py" hl_lines="12-31" --8<-- "docs/examples/guide/widgets/checker01.py" ``` @@ -233,11 +231,13 @@ Let's look at an example before we go in to the details. The following Textual a ``` -The `render_line` method above calculates a `Strip` for every row of characters in the widget. Each strip contains alternating black and white space characters which form the squares in the checkerboard. You may have noticed that the checkerboard widget makes use of some objects we haven't covered before. Let's explore those. +The `render_line` method above calculates a `Strip` for every row of characters in the widget. Each strip contains alternating black and white space characters which form the squares in the checkerboard. + +You may have noticed that the checkerboard widget makes use of some objects we haven't covered before. Let's explore those. #### Segment and Style -A [Segment](https://rich.readthedocs.io/en/latest/protocol.html#low-level-render) is a class borrowed from the [Rich](https://github.com/Textualize/rich) project. It is small object (actually a named tuple) which bundles text and a [Style](https://rich.readthedocs.io/en/latest/style.html) which tells Textual how the text should be displayed. +A [Segment](https://rich.readthedocs.io/en/latest/protocol.html#low-level-render) is a class borrowed from the [Rich](https://github.com/Textualize/rich) project. It is small object (actually a named tuple) which bundles a string to be displayed and a [Style](https://rich.readthedocs.io/en/latest/style.html) which tells Textual how the text should look (color, bold, italic etc). Lets look at a simple segment which would produce the text "Hello, World!" in bold. @@ -251,27 +251,86 @@ This would create the following object: --8<-- "docs/images/segment.excalidraw.svg" -Both Rich and Textual work with segments to generate content. A Textual app is the result of processing hundreds, or perhaps thousands of segments. +Both Rich and Textual work with segments to generate content. When you run a Textual app you are seeing hundreds or perhaps thousands of segments combined together. #### Strips -A [Strip][textual.strip.Strip] is a container for a number of segments which define the content for a single *line* (or row) in the Widget. A Strip only requires a single segment, but will likely contain many more. +A [Strip][textual.strip.Strip] is a container for a number of segments covering a single *line* (or row) in the Widget. A Strip will at least one segment, but often many more. -You construct a strip with a list of segments. Here's now you might construct a strip that ultimately displays the text "Hello, World!", but with the second word in bold: +A `Strip` is constructed from a list of Segment objects. Here's now you might construct a strip that displays the text "Hello, World!", but with the second word in bold: ```python segments = [ Segment("Hello, "), - Segment("World", Style(bold=Trip)), + Segment("World", Style(bold=True)), Segment("!") ] strip = Strip(segments) ``` -The `Strip` constructor has a second optional constructor, which should be the length of the strip. In the code above, the length of the strip is 13, so we could have constructed it like this: +The first and third strip omit a style, which results in the widgets default style being used. The second segment has a style object which applies bold to the text "World". If this were part of a Strip it would produce the text: Hello, **World**! + +The `Strip` constructor has an optional second parameter, which should be the *cell length* of the strip. In the code above, the length of the strip is 13, so we could have constructed it like this: ```python strip = Strip(segments, 13) ``` -Note that the length parameter is _not_ the total number of characters in the string. It is the number of terminal "cells". Some characters (such as Asian language characters and certain emoji) take up the space of two Western alphabet characters. If you don't know in advance the number of cells your segments will occupy, it is best to leave the length parameter blank. +Note that the cell length parameter is _not_ the total number of characters in the string. It is the number of terminal "cells". Some characters (such as Asian language characters and certain emoji) take up the space of two Western alphabet characters. If you don't know in advance the number of cells your segments will occupy, it is best to leave the length parameter blank. + +### Component classes + +When applying styles to widgets we can use CSS to select the child widgets. Widgets rendered with the line API don't have children per-se, but we can still use CSS to apply styles to parts of our widget by defining *component classes*. Component classes are associated with a widget by defining a `COMPONENT_CLASSES` class variable which should be a set of strings containing CSS class names. + +In the checkerboard example above we hard-coded the color of the squares to "white" and "black". But what if we want to create a checkerboard with different colors? We can do this by defining two component classes, one for the "white" squares and one for the "dark" squares. This will allow us to change the colors with CSS. + +The following example replaces our hard-coded colors with component classes. + +=== "checker02.py" + + ```python title="checker02.py" hl_lines="13-15 18-25 37-38" + --8<-- "docs/examples/guide/widgets/checker02.py" + ``` + +=== "Output" + + ```{.textual path="docs/examples/guide/widgets/checker02.py"} + ``` + +The `COMPONENT_CLASSES` class variable above adds two class names: `checkerboard--white-square` and `checkerboard--black-square`. These are set in the `DEFAULT_CSS` but can modified int he apps `CSS` class variable or external CSS. + +!!! tip + + Component classes typically begin with the name of the widget followed by *two* hyphens. This is a convention to avoid potential name clashes. + +The `render_line` method calls [get_component_rich_style][textual.widget.Widget.get_component_rich_style] to get `Style` object from the CSS, which we apply to the segments to create a more colorful looking checkerboard. + +### Scrolling + +Line API widgets require a little more more work to handle scrolling. + +A Line API widget can be made to scroll by extending the [ScrollView][textual.scroll_view.ScrollView] class. We also need to manage the following details: + +1. The ScrollView requires a *virtual size* which is the size of the scrollable content, and should be set via the `virtual_size` property. If this is larger than the widget then Textual will add scrollbars. +2. We need to update the `render_line` method to compensate for the current position of the scrollbars. + +Lets add scrolling to our checkerboard example. A standard 8 x 8 board isn't really sufficient to demonstrate scrolling so we will also make the size of the board configurable, and set it to 100 x 100, for a total of 10,000 squares. + +=== "checker03.py" + + ```python title="checker03.py" hl_lines="26-30 35-36 52-53" + --8<-- "docs/examples/guide/widgets/checker03.py" + ``` + +=== "Output" + + ```{.textual path="docs/examples/guide/widgets/checker03.py"} + ``` + +The virtual size is set in the constructor to match the total size of the board, which will enable scrollbars (unless you have your terminal zoomed out very far). + +The `render_line` method gets the scroll offset via the `scroll_offset` property. This attribute is an [Offset][textual.geometry.Offset] that indicates the position of the scroll bars. It starts at `(0, 0)` but will change if you move any of the scrollbars. + +
+--8<-- "docs/images/scroll_view.excalidraw.svg" +
diff --git a/docs/images/render_line.excalidraw.svg b/docs/images/render_line.excalidraw.svg index 0fe5edfa2..591956da0 100644 --- a/docs/images/render_line.excalidraw.svg +++ b/docs/images/render_line.excalidraw.svg @@ -1,6 +1,6 @@ - + - eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1cXGlT29hcdTAwMTL9nl9BMV/mVVxymtt9107V1CsggVx1MDAxMFx1MDAxMpZcdTAwMDAhydRcdTAwMTQlbNkoyFx1MDAwYrZYp/LfX1+HWPJcIi9gg/Mq/sCixbq6Oqf79HL174ulpeX0tlx1MDAxOS2/XFxajm5KYVx1MDAxMpdb4fXyXHUwMDFmfvtV1GrHjTrvws7/7cZlq9Q58ixNm+2Xf/5ZXHUwMDBiW+dR2kzCUlx1MDAxNFxcxe3LMGmnl+W4XHUwMDExlFx1MDAxYbU/4zSqtf/rf+6EteivZqNWTltBdpGVqFx1MDAxY6eN1vdrRUlUi+ppm7/9b/5/aenfzs/c6FpRKVxy69Uk6pzQ2ZVcctBcdTAwMDD1b91p1DuDNUaRVcaZ7lx1MDAwMXH7XHUwMDE1Xy6Nyry3wkOOsj1+07K+Ofp8eHXS3LFHt3fHn1x1MDAwZrU6KWN21UqcJFx1MDAwN+lt8n0mwtLZZSs3pnbaapxHx3E5Pfsxcbnt3fPaXHKehOysVuOyelaP2v7+obu10VxmS3F6y9uc6G78Plx1MDAwNy+Xsi03/N+KXHUwMDBlSElcco60sJZcdTAwMDCzK/vzQcuAJFx1MDAxOGVRXHUwMDE4YbTqXHUwMDFi13oj4SfB4/pccpxR4Wk2stOwdF7l4dXL3WPSVlhvN8NcdTAwMTY/r+y46/s7llZcdTAwMDYoJKDu7jqL4upZ6mdcdTAwMDNNoDU5LY0mcoQ2XHUwMDFiRtR5XHUwMDFjnVx1MDAxMaIlm92dv3hzq9xBxj/5XHSrl+8nrH6ZJNl4/Y7XOTRl51xcNsthen9cdTAwMTmrXHUwMDE136mTaDNcXCRx/bz/65JG6TxcdTAwMDNKZ+u3P1x1MDAxZVx1MDAwMFBcdTAwMTAgi1x1MDAxMOo0II9mXG6EJudvL07OWttm//VeYuBcXO3i7UZcdTAwMDFCS61Gu71yXHUwMDE2pqWzeaNcdTAwMTTEWJiSXGLIXGJGoWVcdTAwMWOCQtlcdTAwMDNTS1x1MDAwMVx1MDAxMfFUOKl4PnJT1lx1MDAwZlPR+TxcdTAwMWOmWqnASJRKXG5cclx1MDAwMEPAKo1cYlx1MDAxY1xiUspITyw0XHUwMDAzYJWkXHUwMDE5SFK7p1x1MDAwMKtcdTAwMDRcdTAwMGLOPlx0WG3OevRhXHUwMDE1XHUwMDE0gHDOkptcdTAwMTiselx1MDAxYtdLlZ3rXHUwMDEwV48/X8sqnCTtXCJz2lx1MDAwN7jngylcdTAwMDRsXHUwMDFhpLFCailcdTAwMTBcdTAwMDZgqlx1MDAxYym0qK11QPOEqVxmXHUwMDA0XHUwMDE4jU5bYLPoXHUwMDA2cYouMEYwX4xAI1x1MDAxOdODOCWpjVNcdTAwMDaexKiyXHUwMDAxZ/czK5xGSVx1MDAxMjfbw32+1kUoZSdI2mmaXHUwMDE4o/tblavt2/btu8b5fvpqq7Xd3jy7eFxiRuHpMOpRKEGxXHQzXHUwMDBlnKBejDpcbrRlMEhEIZxcdTAwMTbFXHUwMDFlf1x1MDAwMoz+Vlx0NWpcdTAwMWPEJ8hAXHRETVx1MDAwNvlcdTAwMTfbbDVcYlDAQHtcdTAwMTHGRlSglKhcdTAwMDa8PqNcdTAwMDadcvIp8GlJSmFmZkdH4NOKQk1cbkDEXCIsr1x0xiFcdTAwMTRL9epd8ql8UD9z7037IPz62Vx1MDAxNrn8RUGo5icvhLLOgkFgZ9mLUFx1MDAxNzAkrHYktGJcdTAwMDSbR1wi9FRcYj0vhEq2v1Yw1/6/XHUwMDEwalxuNSmwN0GSrOQnRujx3vnd3seztUR8NDtbu69cdTAwMGU+hSBcdTAwMTdcdTAwMWOhqFx1MDAwMmWV0lx1MDAxYVxmgGFcZuheiNrAXG6Bjkhaw/B4lKP/XHLwlEXtvCDK4yNhnTA/XHUwMDFmREeKUeegXGKkXHUwMDE2LT9cdTAwMWE1ReBUPtjcK5feXHUwMDFkXHUwMDFlfbXXl+9vtnVaufo4U4yWw/ZZVFx1MDAwMFLxIJCKgE2nZFx1MDAwNUhKWFx1MDAwZVVcdTAwMTimPSBcdTAwMDVNXHUwMDAxKFwiRFx1MDAwNrHkR6NcdTAwMWaFUsviqYI4xNn7iEhbdJKU4YB1aIRvOIBTqLS0SvDPXHUwMDFjlH9YUmUsx3dmXHUwMDFhmP5cdTAwMDBMXHUwMDA2XHUwMDE5eb/lWzF6u+dkZ3dh8OHi6vqotH1z/mXjjbn7dLu+d4U33Vx1MDAxYu7BZthqNa6Xu3u+3f81Wlx1MDAwMIOUs+JGXHUwMDFh3aTDaJFPXFz0225kuYfscyfnxWf68O44OXh9VHVrlcu1la33unT4sJTXXHUwMDEzWm/JXHUwMDAyg8VcdTAwMDOLXHUwMDBidD5Igj5eIFx1MDAwN/DGXHUwMDAy81x1MDAwNlBcdTAwMDL1x4+zXHUwMDBi0zCXx8i4oPuxzyzV3tnM0EY/XHUwMDE3yontP1x1MDAxYpspUJ6BqVFPXHUwMDBm4jt/XVx1MDAxND1bN8JanNz24KFcdTAwMDP/l52ZrkZpwNNfjlonfLXo99u/xH/yj6xcdTAwMWT5zf5s13P6alx1MDAxMlc9Y5aTqNJLpTQuhUl3d9poZntLPJyQv661Ve6/rUYrrsb1MDlcdTAwMWMztFx1MDAwN9FaymJak2GpLJCySVx1MDAxYpt6OSyJ7Z3j2Lb23Ea7XHUwMDE27tLm262fgNZcdTAwMTAwY31cdTAwMTJcdTAwMTmlQsh8yG1cdTAwMDczLJfY3Vx1MDAxOWFcYpx1OVx1MDAxMM6c1sPygoO0Zt2Filx1MDAxZF5mX+bJ6ovSOl1cdTAwMWVcXFx1MDAxY5TIJW+2w8r51sX705mxmvWFy+jzLKyGxWU1PJDVXG5HOGtgXHUwMDBmpmQutFx1MDAxY5v9t1W8S/ejq4+7a+vxp0q8sofXP1x1MDAwMatZPXJcdTAwMWOtOeBHpjj1hlrIpFx1MDAwNyeNXHUwMDE2IDjMcY9TsWO89ZBcdTAwMDBriLeWRGBYwj5ccq9cdTAwMWKlL2Hrc/XNUby3c0tcdTAwMDf753Sxvzs7XrOdnCYpO1x1MDAwN17j4vJcdTAwMWHH8Pr7hFx1MDAwZiE22Fx1MDAxMcxmLDsjlZ6c2aNcdTAwMTXbojJcdTAwMWJQXHUwMDA1TGpcdIZcdTAwMWShhlxcKb7zXHUwMDA1xlx1MDAwNEZIjkhAXHUwMDE4a3N7Z81rllxmgZIojbLaKeNy5eUuzW3AY+DwXHUwMDEzUbAlUjY3mlx1MDAxZlx1MDAxNWi+XHRkXHUwMDE5Nlxy61x1MDAwN1x1MDAwM1S83zJcIkCdi7hup2ErXYvr5bhe7Vx1MDAxZNh9h8XWXHUwMDA08Z6/57DJx+lAslx1MDAxZfDRPIFPUeVcdTAwMGWoNEqX/jZWXHUwMDA0XHUwMDFmozjgXHUwMDE1XHUwMDAyNJt1qaxcdTAwMWG4dybb+DGNzs10x6RcdTAwMDJcdTAwMGX70Fx0K1x1MDAxZIByomBQWlx1MDAxYbbfzjdcdTAwMTOgXCI3OKYkbKfrjVotTnnq91x1MDAxYXE97Z/izlxcrnrqn0XhgGHhe8rv67dcdTAwMTFN/429Rj/7aymjUOef7t///DH06GJk+89cdTAwMDCms697kf89tXWjnMXvM27KIbFHUZOnXHUwMDE4RuvWxbVtLrDagNOs4ZC07a2yoaCAxYzPXHUwMDBlk1VujilcdTAwMDbWiXwlQYJcdTAwMTWU0EbYIcZcclx1MDAwMmdRKKOUYjpKUplr7Vo3zSjik6cptc3cuqGRuVr07K3b6LC3z05cdTAwMTBJr0qN1Fxm9yyp1rU3NkDtbaAlXHUwMDBiqI2mgXufyLrFr5Tcfp2EXHUwMDE3sqbOpfy6cX1T2y1cdTAwMThcdTAwMTMqjdbDylk2pzJcdTAwMTNcdTAwMTC5QVx0ckBsXG6kNlxmwsFB/Vxc5q1cYtudvVx1MDAwM6ielX1j/qv+zT9cZlx1MDAxY1+RXHUwMDFmunNTWLjRXG5+cS1cdTAwMWNcdTAwMDb8XHUwMDAwlE/bO3anqle+oXZcdTAwMDFcIu9k22dcdTAwMTVcbphfXFxcdTAwMDYgXHUwMDAzXHUwMDAwa1x1MDAwNFlhnTFmSPaFrTGjwLJcdTAwMWRhU0yoXHUwMDA36lx1MDAwYmBcdTAwMWPfhbR2mqas2Vs4ma+tzt7CjU5cdTAwMDHkrIlcYvyQhEGwguWZyTXN5Fx1MDAxNJXivVx1MDAwNoxiU0PqoVx1MDAxNq7S/lK3Z+dX71x1MDAwZk/v1j7U6u06bSbDx6S0XHUwMDE3KlqTIYRcXMCQXHUwMDEznlx1MDAxNiSw3tFOkFwio39u+1ZcYmz/WVx1MDAxOcD0lPatsEQkXHUwMDBiXHUwMDFiUFhcdTAwMWQ7R9O0nH7Q5eZXiCv66HhXvq9cdTAwMWRUV6vh6oybokfVTlx1MDAxZtRcdTAwMTWtKFx1MDAxMFqwW1eCXHUwMDAzXHUwMDA2tH1cdTAwMDVcIoOBN3uoOTz1TnSOKSeX3XSWcspcdTAwMDbbNV6CXHThi+tTXHUwMDE4r4ennJrutLpcdTAwMGLXglx1MDAxZn1cZrXTt5eVzcPV5Tw2XHUwMDFmUVx1MDAwNtXeWT825Vx1MDAwNKZnazfllNnWXHUwMDFmKaeDtFx1MDAxNTd//7tcdTAwMWRV/a3/sdT9I1xign9cbjJPpudb5p15XHUwMDFhM8KRXHUwMDE0XHUwMDFm2SFBrjBcdTAwMDcl2WFcdTAwMWLjYPJcdTAwMTTUaJ26mFx1MDAxZFx1MDAxMiyZXHUwMDE1WuGYOuznkC1bX6tcdTAwMTmQXHQ0XGLtrFx1MDAwMsFm4HF9PHPukGDlyVx1MDAwMac09DhcdTAwMDXzyFx1MDAxNol5lpmc8vX6p2lcdTAwMWZcdTAwMDKhXG6TXHUwMDE44PhRXHUwMDAyu93Jm9lHi5xcdTAwMDWlh1xmkNhcdTAwMDc64dtshctJ447ClypcdTAwMDDBXHUwMDFhS1x1MDAxOO8rXHUwMDFm14Y5Z26AsnyE1e5Z24fmWapxXHUwMDFjc1x1MDAxMsCsuFGoXHJdcXMya1SGis7Vvca2XHUwMDBm7ey+Wa2JVbprn8qtV4d3ur3eXFx4cehcdTAwMDKWKOTDXGaSyvSFvVx1MDAwMlx1MDAwMo5cIrVi1iiL+nG0mIk2lKCcT2nPTFx1MDAxYY4y0IpcdTAwMWM9KID9Jd4m7/UpXlx1MDAxMmhcclqj5Vx1MDAxNFx1MDAxNNy6OUluV6rHuGJaXHUwMDFm4NPm2+al2lt4XG6awHJ879dcdTAwMDEqYuXWl1xclzaQzvpYXHUwMDE1XGa7apxjXHUwMDAz32RcdTAwMWN00oBcdTAwMTb6SVx1MDAxNqp8d1x1MDAwNL9cdTAwMDKo+XIwt6KyP0WCWrEnzFx1MDAxNSrGUVx1MDAxMOyO2Wx93Ph8udZ6Xd6/Wf2y594+qzZcdTAwMWNPQFx1MDAxOVxiTSiZX9pIYUxfr51cZlhxSak5ZNLWWFPsXHUwMDA1J1g3PkJcdTAwMWOCXHUwMDFltrJxsCWHdaomO11otKj5XHUwMDEx31xi/3QtOe+YY0ure1tLx51cdTAwMDaYRWjF6Vx1MDAxZtJIXHUwMDAyXHUwMDE3XHUwMDE3cUYsYpJoWcTRXHUwMDE0XHUwMDFkOKNcdTAwMWb5nFx1MDAxNys/iMLAUZNwQCT9anW/YLmHwlx1MDAxY3WRr4Zw0GVcdTAwMTSCUXOjMFx1MDAwNYTWknFIXHUwMDFjn1x0NaQlnny/vkFnObwgXHUwMDA0kZuke1x1MDAwN8tcdTAwMDGqtbNskJ9cdTAwMGJcdTAwMDMnrNKMdlxiS/mKiOis7idJfPdgncDBMlxyYGBJcEDCqoBcdTAwMDW6+1FcdTAwMDadskwzOpG/1FuI9kVvK3iiSFGuWNpcdTAwMWSTXHUwMDBifMOmlpLHbCzan7xcdTAwMGVdXGJh/1x1MDAxOVx1MDAwMG/2dS/yv1x1MDAxZpamyivQgcZ/we53mj7C6t715/2tVXVU0lx1MDAxOFx1MDAxZlbLpdKnXHUwMDBm5WdVXCJ6glx1MDAxY67TQlx1MDAxMlx1MDAwNz2ChYjvWO2VXCJWeKmiXGZHTSSketx69lx1MDAxMWkq3SnHOVx1MDAxZYhfpVx1MDAwNjCsV3hsnspJpoPLv0fj/yxN5ZdcdTAwMDbr2b07p7Bx3lx1MDAxNb81R1xu7H2pxThWbOxcXNXWV1x1MDAxMnXSbJ7WTva3dr/S6eYzh8hqrELnXHUwMDE4XHUwMDE4hUNk0Fx1MDAxYt/Q2Mdcbun7z7T2K2G1yq9cdTAwMWV6tlx1MDAxMiZTVEF+1c78XFw4oWK/RFOg8FeM/FBcdTAwMDdcdTAwMDXSXHUwMDE1UdGvsFx1MDAxNEZbOTlcdTAwMTe/bJ6+XHKbXHUwMDA3d28+XHUwMDFjmbXD+MvtdvV6ti9cdTAwMGKYh4cygpTtLDtcdTAwMDOHorebgJ1WYDlQXHUwMDE2llU2MiFcdTAwMTe6yFxiyFx1MDAxNlx1MDAwM5Sc/zrsUeyVOt/HOi9cdTAwMWZcIlxugauclkhcdTAwMDCT1//e1Y/dUSk+Xr9z9crhyWbra7PZWHhcdTAwMWZCXHUwMDAxaybDN0vsRYj6gFx1MDAwYqyspOr0wVwi+ddQPLtcdTAwMGbRvulVXHUwMDFhN7uVVyN9XGLz+bErmX/5kIl8yIhcdTAwMTfLsbBHSTDFi+VWReVqy1RcZiRXeq/0Pv566q6KSlx1MDAxZU/jQ8Yy0SdjgONKwzwzXHUwMDFj6vfmaqSSbNlccvnXfKAzrn9cXFx1MDAwYuVBWHBKwdHa8zpcdTAwMTBcdTAwMDdi7rVySYWoVWA0+ZU3XHUwMDEzg/a2vXF516Aj65JqUrYnXHUwMDFi+u7t60V3IJqDc0SD/MDJZ+dUXHUwMDFmbFn5MJJASHRkzLOX6Tg0XHUwMDE1pK1+klo5+1x1MDAwZmaanlx1MDAwMoS//Mf3XGZDZ06Xw2bzIOVcdTAwMTntmlx1MDAwN35Wcfl+WrLrLF/F0fXasFx1MDAxN791Pv5bO7z2XGaKOrbm24tv/1x1MDAwM0RNWtcifQ== + eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1cXGtT28hcdTAwMTL9nl9BsV/2Vlx1MDAwNe109zxTtXVcdTAwMGJIIISER4BcdTAwMTBya4tcdTAwMTK2bFx1MDAxNORcdTAwMDe2eG7lv99cdTAwMWWHWPJDxjY2ce5d71x1MDAwNowk2+OZc7pPP0Z/v1haWk7vmtHyq6Xl6LZcdTAwMTQmcblcdTAwMTXeLL/0x6+jVjtu1PlcdTAwMTR2/m43rlqlzpXnadpsv/rjj1rYuojSZlx1MDAxMpai4DpuX4VJO70qx42g1Kj9XHUwMDExp1Gt/W//cyesRX82XHUwMDFitXLaXG6yXHUwMDBmWYnKcdpoff+sKIlqUT1t87v/h/9eWvq78zM3ulZUSsN6NYk6L+icylx1MDAwNqjR9Vx1MDAxZt1p1DuDtWhJkJa6e0Hcfs1cdTAwMWaXRmU+W+EhR9lcdTAwMTl/aFndXHUwMDFlnVx1MDAxY16fNnfM0d398cmhkqdlzD61XHUwMDEyJ8lBepd8n4mwdH7Vyo2pnbZcdTAwMWFcdTAwMTfRcVxcTs/5vOw73n1du8GTkL2q1biqntejtv/+0D3aaIalOL3zX0J0XHUwMDBmfp+DV0vZkVv+a0VcdTAwMDVOklx1MDAwMuuUMMZcdJd9sn89KFxurFJGgUGhrXR941pvJLxcdTAwMTI8rt9UhcolmY3sLCxdVHl49XL3mrRcdTAwMTXW282wxeuVXXfz8I3JUICCXHUwMDAwVffUeVx1MDAxNFfPU1x1MDAwZiPUgVLOKtLKOevQZMOIOstcdTAwMDFaXHUwMDFhRONM9u38hze3ylx1MDAxZGT8lZ+wevlhwupXSZKN159404+mPKJyK920Z9VduFx1MDAxMVxmiFx1MDAxOGpn764qm4er3e/UXHUwMDAzv7DVatwsd898e3iWjeiqWVx1MDAwZdOHL2GURGLgWYvd80lcXL/oXHUwMDFmbNIoXWQw7Fx1MDAxY/32clxu+IOAQvzzSljhQMmx8Z9cXLy7PD1vbev9N3uJhlx1MDAwYrmLd1x1MDAxYlx1MDAwNfgvtVx1MDAxYe32ynmYls6LOIAz4lx1MDAwMIhHSeBE4LRgiFx1MDAxYqFcdTAwMDXwXHUwMDFh9JDAuMA5XHUwMDA3vCokrdVAhSRcdTAwMTCdx/QkUFJcdTAwMDaakCRcdFx1MDAwNVx1MDAwMEOoQFpcdTAwMDRcdTAwMTaYplKTpy3qXHUwMDAxKpBTXGYkUnZmVFx1MDAxOFx1MDAwMVaDRln9LGA1aFx1MDAwYrHqnCG2XGZkx1x1MDAwNqvaxvVSZecmxNXjk1x1MDAxYqrCadIuMtZ9gPt5MIWA0JA2glx1MDAxNFx0hFx1MDAwMZhK6yRcdTAwMWFUxlhw84QpXHUwMDA1XHUwMDAytEKrXGaw0bWDOEVcdTAwMWJoLZgvWqAmxvQgTlx1MDAxZCn2KFx1MDAxYWZnslx1MDAxZsOpmVx1MDAxNU6jJImb7eGKQkNcdTAwMTFKnbcsykpcdTAwMWNcdTAwMWKk+1uV6+279t37xsV++nqrtd3ePL+cXHUwMDA2pPB8IPUwJJBsw7RcdTAwMDX2IL0gtS5QhtFAiEJYJfqFzkQg/a1cdTAwMTIqVDhcYlCgQFxuROU08i822nJcdTAwMTChgIHSWjJcbkEgXHUwMDExylx1MDAwMVEh2bxaySrwf1xuoCbnVvpcdTAwMDCK1pFGY8dcdTAwMDcolurV++Rz+aB+bj/o9kH49cRcdTAwMTS5/EVcdTAwMDGo4oVcdTAwMTdCXHUwMDFha0AjsLPsXHUwMDA1qFxyXHUwMDE4XHUwMDExvFx1MDAxNk4oyVx1MDAwMNZPXHUwMDA06JlcdTAwMTBqXlx1MDAwMCW2v0Yw1Z5ccqD2OVx1MDAwMKpcdTAwMGI1qXKs0YhyjHxcZqDHe1x1MDAxN/d7n87XXHUwMDEy8UnvbO2+PvhcdTAwMWNcdTAwMDItOEBRXHUwMDA20kipXHUwMDE0aFx1MDAwMM1cdTAwMTBQvVxiNYFcdTAwMTGCueqIp4qVwJNcdTAwMTBcbnjGmnZeXGLl8TlhrNC/XHUwMDFlQkdqUetcbr08XHUwMDAwSZBKuPFBWj7Y3CuX3lx1MDAxZlx1MDAxZX01N1dcdTAwMWZut1Vauf70XFwgXHUwMDE1U4FUXHUwMDA0QILYO3JULpSPm7BcdTAwMDekoFxcXHUwMDAw0jlEXHUwMDA2MVx1MDAxOYbIk1BqnFx1MDAxMlx1MDAxNcQhrt5cdTAwMDdEikNcdTAwMDNyUqNcdTAwMWOePtBcdTAwMWO/SZSKjFx1MDAxNPwzXHUwMDA35Vx1MDAxZoZUasPhnZ5cdTAwMDSmWVrgXHUwMDA3ZOjhyLdi9HZfMySp8PHy+uaotH178WXjrb7/fLe+d4234yVcdTAwMTVejnrfuSYrXGZcdTAwMWJkSbPiXFxcdTAwMWHdpsPohrYw9EMjrVx1MDAwNDOBSzhxXHUwMDFm31x1MDAxZidcdTAwMDdvjqp2rXK1trL1QZVcdTAwMGWnS9M9o1Mgli0sSViyoPWhXHUwMDE39NFcclx1MDAwMz5swElcdTAwMDFI4Pqj0tlcdTAwMDV/mMuOZFx1MDAxNFP9lGLyK+/DZmj650SeR0HOVoVtuppcdTAwMDDkXHUwMDE5mFx1MDAxYfX0IL7vXHUwMDAwVfRcdTAwMWPdXGJrcXLXg4dcdTAwMGX6X3Vmulx1MDAxYaVcdTAwMDFPfzlqnfKnRb/f/Sn+lV+yduRcdTAwMGb7V9uel68mcdVcdTAwMTNmOYkqvUxK41KYdE+njWZ2tsTDXHT57Vpb5f6v1WjF1bhcdTAwMWUmh49cZm0qVlNxqFxmXHUwMDFjXHUwMDBikdMwfvJRXHUwMDFklsT2znFsWnt2o11cdTAwMGJ33ea7rV+C1Up4SrNzXCKle4NlNq1cdTAwMDEosFx1MDAxNkmwi2NcdTAwMTU2P1ZcdTAwMGZLNlx1MDAwZbKa1Vx1MDAxY0p2o5l5mSepL0vr7urg8qDkbPJ2O6xcXGxdfjibXHUwMDE5qVnTimzZflxuqWFxSVxyU5JajojehHLkI5qxSZ2YKt6n+9H1p9219fhzJV7Zw5tfgNQqQMGKmONcdTAwMTFcdTAwMTamprekhopcdTAwMDJhQLP168R4pm9gs/TUQ2K2IZ6anOPh2GdcInWj9CVsnVTfXHUwMDFlxXs7d+5g/8Jd7u/OjtSan/1cXFLj4pJcdTAwMWFcdTAwMWYh9fdcdFx1MDAxZsJqsLk0Wb+vJiU5XG5cdTAwMTg/aThaqy0qq1lWM29cdTAwMWRcdTAwMWFAzf6YSd6rwDWfVtrn45RcdTAwMDUgnJ9cdTAwMDJcdTAwMDewgVWGPOJRO8rlKLPUXGZcdTAwMDRoXHUwMDA1x2eWQ3Difznp8KNkzr5cdTAwMWOJ9CS0XHUwMDFmXGZ68eHIiKB3XHUwMDE0X0lLmspcdLfTsJWuxfVyXFyv9lx1MDAwZeyhJWRrjGCvw/DSlVx1MDAxZuWKXGKUlZJNtjI8JG0zseVnJmzyNTqwqKzzVXOSzFxioVx1MDAwNr480+3xQX1U5eZXiCvq6HiXPtRcdTAwMGWqq9VwtWBQXHUwMDA2wPB/1pLjNVdcdTAwMDNjXHUwMDAyXG5cYqWUwvjAz6EzXHUwMDAzY0rCdrreqNXilOd+r1x1MDAxMdfT/jnuTOaqJ/95XHUwMDE0XHUwMDBlmFx1MDAxNv5O+XP9VqLp37HX7GfPljJcdTAwMWF1/ug+/+vl0KuLsf39bD+qs/d7kf89uYFcdTAwMTNS9Fx1MDAxZv5h4CRcdTAwMThfUp+guDxauC6uhbNcdTAwMDFLe9ZcdTAwMDO+PFwiOf7qXHUwMDE1LiBcdTAwMDOn0WhpNIl8d8LMXHJcdTAwMWPKwFdg0DphhVOZQ8/6IJitvCp8gXS+ijOsJUghaWEmalx0mrl94zHgJL08k9q30WFvzpSIXHUwMDAwhFx1MDAwNuGs9Wl9XHUwMDBlrVx1MDAxNeSu+m5LePk77S+KULMxXHUwMDE5/OpjWbeTnd23qzWx6u7bZ7T1+vBetdebw4eEmlx1MDAxOExaW7Ik+HPNwJBAXHUwMDA0JFx1MDAxNS8w+1XprFx1MDAwNPdrm7dcImT7x8ogqGdm3vKxwEDzXGbrXHUwMDE29M1EY9u30Vx1MDAxYX5x7Vx1MDAxYrJEQ1x1MDAwNGRWSqFtb1x1MDAwZVx1MDAxNTXbN+NcdTAwMWJalCAgkvOLzNin+5KEVk5LadlMXHLpoFx1MDAwMVx1MDAxNVx1MDAwMFx1MDAwM1x1MDAwNSQrXHUwMDEydJZoiIWz1lxuMmaSTq+ZW7ipI64xLdzoXHUwMDFjQI85YelGzCpjkWWEM4NcdTAwMDbO16TYhnhcdTAwMDPoW+zUYMVmLFx1MDAwYrd1e5rcrVSPcUW3PsLnzXfNK7k3fEhsctF6YUasZyzpIUNcIoau0lxuoNP++ovbt0Jg+8fKIKYnNHCFWSco7lxyRI7IWDVOXHUwMDEyoI7U51x1MDAxM5m3wu7Actg+j2bZyc3YXHUwMDE2qHjWmY/ev/dFp8h+lGkhnWKZl/dcdTAwMDYzzzrl2oeyrFM21q7xXHUwMDEyXGZcdTAwMDRHM+yu6mJptkXQl6Ped751J2JdlKVTp8tmge452s1mZaWGXHUwMDFm2ayDtFx1MDAxNTd//087qvopfbnUfVx1MDAxMlx1MDAwNMFfXHUwMDA1SS3d8y7zTmo9MsKRpmNkP4fLtSn221x1MDAwZtZGPvKewH7EryVtv0nCS6rJXHUwMDBioq9cdTAwMWI3t7VcInm0IP1cdTAwMWMrXCKQaIT1qlx1MDAxM1xmWtvfXHUwMDE3XHUwMDA3Tlx1MDAwN1xuXHUwMDA0XHUwMDBiVO8/zdOa4Ofdz+FNvlx1MDAwNtLuacroiVxyXHUwMDFk8yxfkW9cdTAwMDM3z9PsxIFjYfLXi+lOz8H4RZ1K+0vdnF9cXH84PLtf+1irt+tuM1l4dqBwznI4L1x1MDAxOXhGqL6aXHUwMDBlSfa+TivB/6N4Ws/onLnB4ouv6Nm18Vx1MDAxM7gxzypcdTAwMTD5lqBcXNpnTk1JJFx1MDAwYiud2vq9RGqCRurRSZNcdTAwMDWVnDogjUL4rVNWXHSw/d1cdTAwMGJcIjDo87iW8WjNXHUwMDFjXHUwMDBinWNKTuLoz29cdTAwMTN6XHUwMDE2xTlX62+F31fzjzKciTIs7k8q7jpcdTAwMTSKpFx1MDAxNdaNnzNcdTAwMWKdNFhQhqtcdTAwMDBcdTAwMTUq0dnAxXymfrdnXHUwMDAyXHJW++SF1ajnV1x1MDAxMVx1MDAxOJPhljQoXprniSnn6sNcdTAwMThcXEr/XHUwMDEz+82X4a44Ka6N76PNV8dcdTAwMWYjOJhcdTAwMWS92fq0cXK11npT3r9d/bJn381U146i9zBh+yi9/T5O5dCXu5Umlvn9/Fx1MDAwZVgtXHUwMDEyKVx1MDAwZfeU0UZcdTAwMTdcdTAwMGLbMbb/j1x1MDAxMLaghm0hXHUwMDFk7FRija2cmSysW9Rd/lJcdTAwMTgzVWVwqk6l90yypdW9raXjTl/QXCJ0KPVcdTAwMGZpJINcdTAwMGIrW7r4XHUwMDBlXHUwMDA2KKRcdTAwMDSfa1x1MDAxZb+LePSSz3lX+FRcdTAwMTT2XHJcdTAwMTOGY0Gyxlx1MDAxN4Ogd1x1MDAxYlx1MDAwZVx1MDAwN4zOXHUwMDE3iDhe1Fx1MDAxMmGEh35cdTAwMWGBpVx1MDAwYoS0vqeRQ1x1MDAwMVx1MDAxNlx1MDAwNEPobGWgrZagJZtcdTAwMTOgXFz+6MF3I1x1MDAwN21mlvtcdTAwMDVGsk/lXHUwMDBiXHUwMDAys69ajXZcdTAwMDZLPUVwYVx00Vx1MDAxOFx1MDAxMKT81FHuqodcIjhcdTAwMDbGMZitXHUwMDAzniRp7WCPz1h1q9H7zHpcdTAwMDYloNM6gdJcdTAwMDdcdTAwMWHo01x1MDAxZYOjooC1oUFeclx1MDAwNpiU4tcuXVx1MDAxNULYP/rBm73Zi/zv6dJrVHxjXHUwMDBi35BmnZ5AhVT3bk72t1blUUlhfFgtl0qfP5ZnqkImtGDqUVx1MDAwYlx1MDAxNiAozfAxvu8kn9TsaFx1MDAxMCNcdTAwMDLpXHUwMDA0XHUwMDBiXHUwMDE0R8hRRv+wZpZbU96OknWK/OpcdTAwMDNcZuudfjS5ZslqY1xyPq2ncpFza7xcdTAwMDIzvOFLkTBXuW6HPkpcdTAwMTAvgGOvP75T39i5rq2vJPK02Tyrne5v7X51Z5s/OfKWj0rzwJBjny4sXHUwMDE4Ry5/XHUwMDAzle+0kFx1MDAwMet2sEjOXGIj5rjfb9x6rtTkd8o9z81cYizHXHUwMDAyk2w7/Sc6ntY7gSws/liw3v+Pn1x1MDAwM/uyefYubFx1MDAxZdy//Xik11x1MDAwZeMvd9vVm2e7XHUwMDFiw1TOybeFS/Y9yMFcdTAwMDSgXHUwMDEykEv6d7BcInRgWFx1MDAxM1xinlx1MDAwN806ySx0XVx1MDAxNFhnWpA0/43uI6sz/j4z8/YgUlx1MDAxNXpcdTAwMTDQYNCx3lx1MDAxON+FvK9cdTAwMWbbo1J8vH5v65XD083W12azseguhFx1MDAwMoaLUuyzfVecXHUwMDEzvVx1MDAxYlZcYjj08Dd11PyDr/rpXHUwMDBlRFx1MDAxOVL+plGz24Y20oFImKhE+I9cdTAwMDOZ2oFcdTAwMTTfY1x1MDAxNaThZfftx2MzcVVUrrd0RUNyrfZKXHUwMDFm4q9n9rqojPIsLuRRXHUwMDFh+lx1MDAxNFxmXGJGtkO/P6xv31x1MDAxOElcdTAwMGWjjXaC40q0+Z7kXHUwMDA19Fx1MDAxZlx1MDAxY4uSr3z9XFz34UvN83dcdTAwMWaFXHJhKCx6XHUwMDFkYMbXPXftjav7hjsyNqkmZXO6oe7fvVl098FoIHKotZ9vwVx1MDAxZaJcdTAwMWa4Olx1MDAxMP5mR75Q5XdcdTAwMGL99Po+XG7lt1SoXHUwMDE5llx1MDAwN0Z6XHUwMDEwjrqeelOQ/ztcdTAwMGby4iG5sFx1MDAxYzabXHUwMDA3Kc9o10LwWsXlh2nJPmf5Oo5u1obdWa/z8O/aobbnUNQxN99efPsvXHUwMDFht7uQIn0= - widget.render_line(y=0)widget.render_line(y=1)widget.render_line(y=2)Strip([segment, segment, ...])Strip([segment, segment, ...])Strip([segment, segment, ...])Line API WidgetStrip([segment, segment, ...])Strip([segment, segment, ...])Strip([segment, segment, ...]) + widget.render_line(y=0)widget.render_line(y=1)widget.render_line(y=2)Strip([segment, segment, ...])Strip([segment, segment, ...])Strip([segment, segment, ...])Line API WidgetStrip([segment, segment, ...])Strip([segment, segment, ...])Strip([segment, segment, ...]) diff --git a/docs/images/scroll_view.excalidraw.svg b/docs/images/scroll_view.excalidraw.svg new file mode 100644 index 000000000..4ca739d02 --- /dev/null +++ b/docs/images/scroll_view.excalidraw.svg @@ -0,0 +1,16 @@ + + + eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nN1dZ3NcIsmy/b6/YmLe16VvuSyzXHUwMDExN17IIYOQR4ZcdTAwMTc3XHUwMDE0mEa01Fx1MDAxOEFjpFx1MDAxYvvfXybSisY0gpFG04MmYmZEY4quU5nnZGVm/fePb9++R09t//tf3777w0opXGaqndLg+5/0eN/vdINWXHUwMDEzL4nR791Wr1NcdTAwMTk9s1x1MDAxZUXt7l//+lej1Hnwo3ZYqvheP+j2SmE36lWDlldpNf5cdTAwMTVEfqP7v/T3Uanh/7vdalSjjjf+kIxfXHKiVufls/zQb/jNqIvv/n/4+7dv/1x1MDAxZP1cdTAwMWRcdTAwMWJdx69EpeZd6I9eMLo0XHUwMDFloOZ8+tGjVnM0WOCWW1xyIN+eXHUwMDEwdLfx41wiv4pXazhkf3yFXHUwMDFl+n5+WONN/354WtnMdC7c/Vx1MDAxZbRvd8efWlx1MDAwYsLwPHpcbl/uRKlS73ViY+pGndaDf1x1MDAxNVSjOl7nU4+/va7bwpswflWn1burN/1ud+I1rXapXHUwMDEyRE/4mGRvXHUwMDBmvtyDv76NXHUwMDFmXHUwMDE54m9cdTAwMTkrPFx1MDAxMJw7Lq1QUrxdpFcraz1cdTAwMDXGSCaFYFx1MDAwZbicXHUwMDFh1lYrxInAYf1cdTAwMGZcdTAwMWL9jFx1MDAwN1YuVVx1MDAxZe5wdM3q+DlC2LKvx89cdTAwMTm8flmtPaaMXHUwMDAxJVx1MDAxNFjNnHp7Rt1cdTAwMGbu6lx1MDAxMT7FXGLPStBMayaYtXY8zq4/mlx1MDAwZsGlUnjdmbcr9PHt/epcYlx1MDAxYf+J37Fm9fWONXthOFx1MDAxZTFd2InBafyaXrtaepl2rnGYSmuQTrm362HQfJh+u7BVeVx1MDAxOCNl9Ojff/5cdTAwMDBCjbVJXGLlWnFcdTAwMDZcXDOzNES3XHUwMDBitYf74cbRY15enZ7XLvrFwvZGyiFcbtrjoIyWSlx1MDAxOIm3w+lZkErpQHLEqTRcXKdcdTAwMTakOHzlXHUwMDFj07BmXHUwMDE41UYkYdRcdTAwMTlhnDGCL1xyUf9i71x1MDAwMO9euVxcVWcn+X2by508qJRDNMOlx7iz4LhBp2Etn8QoaOkpbVx1MDAxOV7iwqrpcaVcdTAwMDehXHUwMDFjpLT4h69cdTAwMWJCtUtCKGdcbn1cdTAwMDc6uOVcdTAwMWT99XH5KHdcdTAwMDO23Do5bZSu+3vIb7q/XHUwMDE2opy9a0aVZ1x1MDAwMPmMsJo79Fx1MDAxY2ZcdTAwMDKhWjpcdTAwMGZJgHNooVx1MDAxOChcdTAwMTCphajgxihcdTAwMWPq2kHUJlNRgU7PxrnNe1xibWSKJZmtbFx1MDAxNM6L9Vx1MDAwN3+7frKzXHUwMDFi8rRcdTAwMWJRyz1kocpKLSyuypiZXHUwMDFjIVQxXHUwMDBmLahcdTAwMDLFXHUwMDE1LldcdTAwMDOpRSg3OF2APmDtuCgkc1GLRlx1MDAwM+9cdTAwMTd3S2PUmZKGsHV/36tcdTAwMTT4bdirto7NUcoxylx1MDAwNXhOI+k2XHUwMDFjIeBigujFzVx1MDAwM1JR5ONWSMVkbP5TXHUwMDA30dFrOfKVr4IosK+BqJGJXHUwMDEwXHUwMDA1po1hsFx1MDAwMlx1MDAxN+08XHUwMDFm1DfMzlx1MDAxMfC9Pelntp+zJ/XLtDt6nFv09MjhjFx1MDAwMPxcdTAwMTfspKhH5HrCopbXWignRJrJKLPcXHUwMDFhxtZcdTAwMGakOlx1MDAxMaROc1x1MDAwNcwsb0Y3ZNlFjf1n26rvne9cdTAwMTSueOmymXZJn3FcdTAwMWVjXHUwMDEylyMuRvT4ztgpiIInXHUwMDA1zoZRgOIxvVx1MDAwMOVGo6bH91kzgGrDklx1MDAwMCpcdTAwMTjn3FomYGmEbjrVclH0sFnuPoiGrvLuTbOdcoRyXHUwMDA3XHUwMDFlV4YzriRcdTAwMDXZxKSnV1YjNlx1MDAxOP7DtEB/z1V6QSpcdTAwMTE/1uCr11xmpKhcdTAwMTSSQGq0tMahpl1cdTAwMWGjuXBY367LYj5ifrijXHUwMDA3T2F7eJFyjEopPWSaXGbQkTOrxXTwXHUwMDFlIYomXHUwMDE2QWGYxGWb3rgorjFkI1xm5JdcdTAwMDVGv8rRi0RH76RDcsPY8lx1MDAxMIWHnbBcXK31s+d9YNXidXjb3Fx0U1x1MDAwZVGhlYdLXHUwMDExXHUwMDE3I1d431x1MDAxOUxAXHUwMDE0NPcsQldcdFxuS4lcdTAwMTTbUNCGXHUwMDBinK/1XHUwMDAzaGLkXmryb2pcdTAwMDVcdTAwMWK69aBuXCJ+X70rXHUwMDFjVTqXg44s1EultKslMqJKOe0kXCKAczbJRCnoxIW2hvZGudUphqiRktNfa1x1MDAwN9HY9ua0WFx1MDAxMjhcdTAwMTSJXHUwMDBic1x1MDAwNSN6f7N3cGhcbsdcdTAwMWLKb4mD48vLdtGkXHUwMDFkoyhcbj1cdTAwMDSHldohIZVyXHUwMDA2pMJDrYRcdTAwMDRAoHKKsaLUYVx1MDAxNFx1MDAxN5lFovJ1m/RfRUV5sqBcdTAwMDenXHUwMDE0V9YsXHUwMDFmdVx1MDAxMntcdTAwMWLmLDxcYor9rfb93bFo1Nh9MeWOXjHpaYG60DnlOOr2KUePNEAgNJi0TqCtTS9ENVijXGaYdTOjVibugKJcdTAwMGI0xki9vFx1MDAxNT2KbvuBelx1MDAxOF62N1x1MDAxZotcdTAwMWLRU/78rL7zXHUwMDFiIFRcdTAwMDGnfCZAPynkpKJHfuchbmn6LaN8k9RClFJhjOFq7Vx1MDAxMMpcdTAwMTK5KJdcbmjThS2/R39zONgt14e1sGev9pvuaqvVPL5Ju6NHpulpY1x1MDAwNSolri1cYmWmMGo8JizOXGaCh1x1MDAxMolSi1HBnEQzis5vzUBcdTAwMWGH4HRklMLBpGSXt6PhzXPxWFfgtKFcdTAwMDfN6DjXuO9cXJ+m3I5mLEpcIs0kt1JSzGZcbqKOeYZrNLS4XFyFVelcdTAwMTVMXG6kcFoquWZcYrWgklx1MDAxMMqtMEY5sULC6N2gkr1cZrrc3lx1MDAxNp5cdTAwMGWCutLnxZ1fXHUwMDFjXHUwMDE3XVwi1cmgXHUwMDFlkkjKXHUwMDExg1bZOD5eMMqRXHRoh1PDUfHHbFTaMIpcdTAwMTetlcqu3Vx1MDAwNmgyRonbaK2X313yuzVVubjIQa691dbFfK++U95Lu1xy5cxDpums5lx1MDAwMijhLuYz6FxyLIJcdTAwMDdlXHUwMDE0in2UzIid9JJRq61hbt2oqLHJWSRcdTAwMDKnXHUwMDA0OZhcXF7PO1x1MDAxNtajXnnX+IfVgOWvK73MYSvlXGJFXHUwMDEzioJQOEAuXG4uvttLL3eKeU5cdFxcq0owmeJcdTAwMTRcdTAwMTLU8UhF5PrF7WPhpFx1MDAxOVx1MDAxYSosY1qsYELLrTq/61x1MDAxY+RcdTAwMWHPmyeNu3774Ph6J59ygGaM9Vxmulxu9N9cdTAwMTaZJlx1MDAxYdIphEqPNL41XHUwMDAynLZcIr3ZooIhmWaCs7UjojJxf14xYFayXHUwMDE1bOh5/iZz8XB9tN+v3PT2j/1wMDjup1x1MDAxY6JcXKBcdTAwMTFF52iYQHzqqc1P9JyolFDpo1x1MDAxMFHO8PRm3Fx1MDAxYmHAraFcdTAwMTGNf9Xp3Xmh0LdJvTxAd/bt4O4y18z3/dPCxuZF9bbXTHtIlFx1MDAwMEqln1x1MDAwZVxykKSakFx0gFwi+1x1MDAwNI9JQCNcdTAwMGKgdDxJL21cYuWa/oBbPzGvXHUwMDEziSg4UFx1MDAxY9n30lxi7e9cdTAwMGWi67P9u/x20d86uL85qNRL1ylHaMZ4XG59o0J3XHUwMDAxWlx1MDAxYjeVy4xcdTAwMTA1nlX4w5DpoZZKr1x1MDAxMUX7L1xmY2uXhWdE4r6SZVKD4CtU0LvDXHUwMDA33ogye4ftx5NwN5dpXHUwMDBl4fYk7dEm7lxmldAz9PJcYtU463lcdDZcdE9xXHUwMDE41dyNNjHSi1COa4ip9YuIuuRcZlx1MDAxMqesXHUwMDAy59zyfr5cdTAwMTjUud/KZINmvlx1MDAxMEB+b7flXHUwMDA2v1jML5PkpD1cdTAwMDQgUVH09256W1x0ISpcdTAwMWS3XHUwMDA0XHUwMDFlQbs2qYWo1lx1MDAwMlDR2bUzoi7RiDpcdTAwMTRKkmoklkbo9eVppeVOq3dXwVnhXHUwMDAyoPPc2bxKuZ9cdTAwMTeadjZRryuFpFx1MDAxNMnmZMTe4mWFQlx1MDAxZeW+XHUwMDA0kWZcdTAwMWIqqMKMXGLzmiE03lx1MDAxMmCmgFx1MDAxZTUuuj29vJ8vXFxcdTAwMWa0d4P93bN8Z/9cdTAwMTAuovOry27aO+VIXHSe5VxmaSgyUmXdpFhyinuWMkuEJFef4pAol5Kj29Nf14Xk1yfhXHUwMDE5So7UdoVke7d53bzs12+jsti0e7Xn8rY/eEw5QrmzXHUwMDFlSnnDnFx1MDAwM1xynEs9XHUwMDA1UeWh+1x1MDAxNHgrXHUwMDE4l0KmV847XFxnhpm1I6JxozFTVudw3lx1MDAxONjljejJUc70MvKKXzzdN1x1MDAwNyYrovKen3KIKobwQCGM8yuYxEmejok6T1x1MDAwM4p6pKia9kbTXHUwMDBiUdBKOFxy65bJ7JLTRFFcdTAwMWRKSuJd3ojulMNm5/G4nLX8YPPsYfdhsFx1MDAxZJ6lXHUwMDFloeTmkYkyg2aIs0lcdTAwMWI6ioniSkXzqpyWMr2pI1xc4PJB7bB2RtQlt2xE5uVor2V5XHUwMDFium1y/WKws/1cdTAwMWPehe2gye3u3W4j5VxiRevpMdTrXHUwMDAyZ1x1MDAxZt04s2ZcdTAwMWGitDPqJFx1MDAwN8DZifO81GFUUXWqiG+zrFx1MDAwN0alTNxZQu5FU6NWXHUwMDEw9PV+aT97aMPhTXk7a1x1MDAwZUr2LOcnZThNgW1cdTAwMTKi08mYb6+qlrp1f1x1MDAwNYyad+P2VlDASYAyXHUwMDE0d5qO21NtsuZUUmlcdTAwMDDExC7GXHUwMDBmYDTqlJrddqmDXHUwMDE4mMUpgPK0Nlx1MDAxNlxmt05CrI7/XHKniFBv1FFcblx1MDAwMMkzn9dyjOrMjFCfh9PXXHUwMDBiY2DF5rtlbvaOb8RG+/K8e1lcdTAwMGI6rqnz4+ZdXHUwMDEzKCx1Oq3B97crf/+56H3z14f3rHSlj+rnXHUwMDE1dtIuR2FcdTAwMGX2l3vf1/8lry7K+bWgv2R1cctcdTAwMTLTXHUwMDA3jbC0YblCM7+N21opujs5Pm9l6rJcXMmen++qpFjEj62un9HKT2lcdTAwMGKgrUKVXHUwMDA3bqpcdTAwMWNQWk8qLt2ojilcdTAwMWWK+lx1MDAxMftfq9VmXHUwMDE3lVTWXHUwMDAzyv1cdTAwMTIkMy23s4tcblx1MDAxZJPnqJBcdTAwMDG1psZJmVx1MDAxM4dwgkpuYd2Mv1vU3cdYUt92+dwsU9nLNSvlKHrK9a3eeK5cdTAwMWWq3jDt+1xyXHUwMDAwnpTURVx1MDAxMuGnQGkzyaK14p401F9cdTAwMWJcdOpEzXXaXHUwMDE4iuFSMbV2m7bOJkJU472a7F34XHUwMDFlQvc3XG79QfhQUMWbw+3sZkFtmrNK2lx1MDAxMcqd8lx1MDAwNMp3SX0nXHUwMDE4XHUwMDE3alx1MDAwNqCMa8GQqVHBdZopNCVHOFg7XG7tYjd9JvNFkFx1MDAwNOfLQ9Re7uYvh49mb3PrPNdX16Hc2vvFPdKW2bRcdTAwMTWeQdvjkFx1MDAxZjvjzHTHXiU8SdWA1qF3TXPzXHUwMDE0R1vs6Fx1MDAwNNbNivJ4i9rpbVvNrY1ff1x1MDAwZqLDp42HsFx1MDAxNO5cdTAwMWNcdTAwMTTPhsXipSuHh7bzIzR0Olx1MDAwNeqn0lDG8J4jXHUwMDAzdEDJ1lNeXHUwMDFleSjScYNcdTAwMDDVlF3wM2io8UDhXHUwMDE4JHVoXHUwMDAzNUfbceLCklx1MDAwYomyXHUwMDBlZSaPl9K85lx1MDAxNFxiy6RUn7hcdTAwMWT2emGuXHUwMDA2XHUwMDEzYviU63b5+UA1+cHTyYE7XHUwMDBmup+g7VrX1cst8Vh7PKpcdTAwMWY7rVub+iR/8WnaTlxuiDVcbvrgmvLDMGh3568oyZM7XGYr5yxzq2RCbGX2nvfPj1xucnhcIrv3xXZ2cFx1MDAxYyZF91x1MDAxNq6przP6XHUwMDFhiVx1MDAwN51xXHUwMDAx1Pg03lbwxeRcdTAwMDPSXHUwMDEyVE2oqsxcdTAwMDerv2slwEmdXVMk7HDJ4utcdTAwMTUyo/hpXHUwMDFhb2vKok9cdTAwMTBmVDw56icvp5dcdTAwMTR3Qo1cdTAwMWF7ft7uyE9aU0tgXzmIybWfif3kM1xuwFx1MDAwMUi1SmD7iVx1MDAwNTf1/oHhfJBcdTAwMWLWXFwh2+tdi3RD36CppqpxjdCmcpjp9u9I2KnXoXVcdTAwMTLR9cFk9FqpjG7j56Cfmk9q+Mxg4ZfRnUXwVFwiMVx1MDAxNVx1MDAxZIU8MvJ4R5J3W8JcdTAwMGVPLs+u77rNfKd5oSHT2Fx1MDAwN5tNNzyd9tBgSsGMY8qJKXBKz+JcdTAwMDLmhrLUwHysv1x1MDAxMVx1MDAxN2Vr55Dxz1x1MDAwMKdQynKxhkG35NMzuFx1MDAwNGXUSlx1MDAxNbuP+9lgj4iDuXlu9jbEw0Bmf3FcdTAwMDeuJei4RlxmOjlSjFx1MDAwMPFzw0ZbNnRMXHUwMDE2JZOiiEO3luL+RpxcdTAwMGJNmzFr1+Ao3rBnZldcdTAwMTBHwrhZoVZcImtcdTAwMWVcdTAwMDPjXHUwMDBlaq2to63raCNo+LntX9xVe7mYhpNcdTAwMDBcdTAwMTZ1I0V+J308NY+hXCJcdTAwMWXUaZRcdTAwMGKV5iRKXHUwMDFjnTSW/Y5OfnHnd5VYK2GFcJrqXHUwMDA3loZo4TTfk4em8LjNdjZzuX5hUyRWnKXEy4PxkFuilURHayF++ucoP1xyXHUwMDA0XHUwMDFhUW2onSOdifkhfFb8qqqWZvEpkelKw6TRQlx1MDAxYlxcLLG43zisQf1cdTAwMTStQ7pB4Vx1MDAxNdBaTyN0XHUwMDE09TCSr1vQzSVHXGJIJFlcdKvQUD9T4mf3NmpfPcnNnWHtML+dej+PPt4zQlx1MDAwM50wRKdoiNnuRoxpOslNKzRT6W1cdTAwMTJnR1lcdTAwMTl67WwofudELsrwnjnrlofoTdjdkNnh9uVcciuUjrWJStlccpluXHUwMDFiyqX1kMFcdTAwMTmJ5lx1MDAxMlx1MDAxOY2FqSRfQLFkUU4yZpHruY95eVXRfm2OkueS46eAXHUwMDExTvBRavWcXHUwMDFjXHUwMDA1OrVTMFxyXHUwMDE2mLPU3l1NQ1TTQYlcdTAwMDBfd9jgV1lRkSiXlFx1MDAxMeji2FxuPbVvXHUwMDFlh0enN1x1MDAxYqWDKLeZk1d3bGuHp39cdTAwMDNYSI+yeYGSza1cdTAwMDA23WpcdTAwMTM8bZWUXHUwMDEyvT3EXHUwMDBmvkubXHUwMDE15Vx1MDAxNI8wZv16XHUwMDFmOJG8XHUwMDE3XHUwMDAwVlx1MDAxMEWC5UFcdTAwMWFtXHUwMDA1R/WgWzdcdTAwMWI1lW1l7lx1MDAwM3Ppl9NcdTAwMGVS9PJUsoN+XFxcdTAwMWGlXHUwMDEwitMgdVx1MDAxZdovxCqFdOJN5NJcdTAwMDZSnCluKVx1MDAxZnTNMFx1MDAxYT9cdTAwMTFipnCXXHUwMDBiZsRcbudk1bcv3P5Teeu2pE6e29cl/7Rym3TydUpcXL202lx1MDAxM1x1MDAwMtmopSTe+Fx0taOwKGhUMlx1MDAwZUWIZFoy9zF8Jukl9OKKsk81UJqhdrGaorGnd4rO6zLcKK6EZmZ2z1xuIU69XHUwMDBm1q9jMSzYVjKGsVWOeLnh2UplWDhcdTAwMGVOs+y+556zXHUwMDE3YifpQMzUQFx1MDAxNI0kbeRcdTAwMDFyUif1JFx1MDAxOdWkllx1MDAxMKD2RTzqj0Xuk8go8mHlLNVIcyRXLFZcdTAwMWQwRihjnjI4PIaTgsYyXHUwMDE2vv2nlyGaeerR8Fx1MDAxYpacRf4wmodNnlxcVa6RkFx1MDAxYlx1MDAwMSvkIZ5W84/Hd2ew275cdTAwMWJcZlx1MDAxYvno3u+ap3SXSSiH2ETLiV7CUidcdTAwMTI9mUFjXHUwMDE0XHUwMDE19DKgvIdcdTAwMGZcdTAwMWYnPDeDRsBcdTAwMWNvLmeCSWhTXHUwMDExXHUwMDBln1x1MDAxOUx6vfCl9Vx1MDAwZpfZU13JVEphsSOHw61iodhcdTAwMTPnn5QnoLWlLmorLJgxMFvN6Dx4XHUwMDFlbVx1MDAwM9iJR7OlRlx1MDAxMD5NgGu0knCA/aBcdTAwMTP1SuFtXHUwMDE3X+i9zlxcbPq7Plx1MDAwZWD0jjDx0o0wuKOF9z30a5MrMlxu8Ma8XY5asa2EXG5cdTAwMGWlhG/X2a9Of6VWJ7hcdTAwMGKapfBiwbBcdTAwMTZahpdcdTAwMWI9xzTIZIVcbijKhNErdDFfjKhfkFxc965lQD3qIaqUQJ+BtMRNXHUwMDE1+Vx1MDAxOVx0XHUwMDFlnYdhkfcjq5FcdTAwMWZzW/OT69AvXG5UlYBcIphROs+c7Wb0q55DgkFcdHbKonebsVx1MDAxY+SxjLGWfYXXWr1cYil220qdaDNoVoPmXHUwMDFkXlx1MDAxY1uP7/7LR+8v4WPoy5XaL5SUXCLRXHUwMDFj74xDT6rHZ3KNVnalR18kwzzkxXRwl5OG+KtcdTAwMTWvT3ozY9/9ZvX9QS2uXHUwMDBmfFx1MDAxYpTknlPc4mxcdTAwMDCXbsxB4kPiXHUwMDFl51Kh1lx1MDAwNIlUmrJtZoZcdTAwMTSWutFWq9FcYlwivPUnraBcdTAwMTlN3+LRvdyghV33SzMmXHUwMDAzv1L82rRcdTAwMDVo0ztOuojx/76NV8nol7f//+fPuc/OJIN4dHlcdTAwMTa/43f8I/7vyuaLJ1x1MDAxZk5Le/Ocs1x1MDAxNWLAi1x1MDAxZFc6zZfzUPMpyv3WUnOY2qOw1mPMKGGRknNmf0KJXHUwMDFh81x1MDAwMFmToNguoLDjZk5ysGTCXHUwMDEzmo7w1pyaUdhZ88XpQHYrxdeR7vH9/lx1MDAwNebrXHUwMDFmQ8A8a1x1MDAxZLlZh8JeUZnujDlBTcM0amqUKiSqtVtsvCa/xW9kQ5KBRD+zXHUwMDEwWtGCJEmjeC/TmYQmXHUwMDAzgKZ8XHUwMDA1bdQqPF01TMtvV/LX3WM1dJC5/KGd+K/TRijbjUPNjrLIWlTHkzvxVDXoKapcdTAwMWWUqKul+mDsc744UkuJI0pVNWA/8Vx1MDAxMIw3XGJ9blx1MDAxMfdCcXT7iFx1MDAxNL+xVb3bvkV/uXdxcrxfqX+aOHLUzunXiKOX2UybNnpcdTAwMTnVj3FcdTAwMGJcdTAwMDHJXHUwMDA1xki0KTNpeW6xXHUwMDE4T2nkXHUwMDE2UiGro+OaKVdcdTAwMWOMXHUwMDEw05ZBXGLPoltS1OROfvTwnLmWgTPt0d4yMmlEuFJsjqHgnkZSqdExUltL62LBk3+iKsZcdTAwMWEmv2Lb7udcdTAwMGKjxVx1MDAwZeaNNDhPOGOENnzUko/NXHUwMDE1IcxjXHUwMDE0XHKj7Fx1MDAxNkZpoOh0XHUwMDE3c4tcdTAwMGZcbiNhPY5uxpGfMZKOvJkvjZhjtNGArFxih6V/c2mUXGZh+snMondFXpOsjExihqFcdTAwMDGUwtbEsi3es16LvVZcdTAwMWGtl1FcdTAwMWX1t2NcdTAwMDJcdTAwMTSDiVx1MDAwNnKvxst4xjJcdFrhXHUwMDE34vDBXCKvucZcdTAwMGJQ5VtNMLa4xOLNKOLGyzBljdOSmjHiQGaMXHUwMDE3woK7rzon+Yf5w2eZr5hxoiOEXHUwMDA1MlPpmKXOXHUwMDAx8djOa/hcdTAwMDfoaFwiOnmIW8Glcu/Yr99WXHUwMDFiZZKx9HJ5XHUwMDA2RitakWR1lGhEqK1cYt56I5a3XCIn9d3BMCzmn56et0Bu5Fx1MDAxNTRr27/Sirh3xZFHLcQsXHUwMDFk0IbSR05ZXHUwMDExoM5cdTAwMDHGOIfYQ1x1MDAxMfNTjMg8qzFHXHUwMDFijVx1MDAwZWVxnymOXHUwMDE27sHgLVx1MDAxOfOpny0zupVOK1xmb1u1WtdPxe7L5IB+zD1Llth3QzCEnEKvtHyRyuLS3DT654xcdTAwMDGPS+CS0qe51GoyX0A5Ntqx1VYoZDL8J0QuueCe0tRXXHUwMDAx7eaoXHUwMDFi7lx1MDAxY/+spWfokGVj0Fx0aTnbXkvQSVx1MDAxZUZ9zYEzeFx1MDAxZoT+sd3PKVx1MDAwNz3tyt533YuL/OOuW1ErXGIrkF+imcDbXHUwMDEzXHUwMDE3XHUwMDFmb45bOmqpwoxcdTAwMDQpUVxm/N5cdTAwMWM/XHUwMDExSaOrM1x1MDAxOPok56yTc4uVo0TCXHUwMDE1OvecXT1cdTAwMWM37/qXwX3vJl9cdTAwMDJ5UeVcdTAwMDdhql1zhjvnOS6cpoP90PPZ6Vph56HWZNbRXHSq8Yq/T299OVx1MDAxZf9cIlx1MDAxN40q0Fx1MDAxOfeZOe5vOEp9o5KPXHUwMDExgKd/szS4fVx1MDAxYcaPOfvkNnCoPo0wYoVcdTAwMWSGhfOazoUqXHUwMDE1ddDiXHUwMDFjkKFS+e1kIFx1MDAxMVWnZ9FowqhcdTAwMTbQsVx1MDAwZlX7LVxcqNyiqmR07rmQjDNcdTAwMDVz8lx1MDAwM9Uoo4DqpTRSMJjTT9MwwVx1MDAxY1UnfonD/+GVtqRcIl9s+L/Ftyq5odwrTcE7bpRSc/y68jRnXHUwMDE2hTgqVVx0+m2XbsWA4uImXd8mczuorVx1MDAxNEVcdTAwMTU5XHUwMDE31up5XHUwMDFiqFx1MDAxMlx1MDAxN1x1MDAxOaP+fdRa38rfm2skYph+MjPwTeJcdTAwMWF/vH7C91K7fVx1MDAxZSHo3qZcdTAwMDPhXHUwMDFjVF9t9Phrfu9cdTAwMDf+YHM+tyZ6/cfrXHIlXHUwMDBi5NOX/e/ff/z9/96SXHUwMDBlNiJ9 + + + + virtual_size.heightvirtual_size.widthscroll_offsety=0 diff --git a/mkdocs.yml b/mkdocs.yml index bbb962580..ad2d90d47 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -167,6 +167,7 @@ nav: - "api/query.md" - "api/reactive.md" - "api/screen.md" + - "api/scroll_view.md" - "api/static.md" - "api/strip.md" - "api/text_log.md" From 25b498896dc583570fccbcef43d220a1bb2e7b3c Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Sat, 4 Feb 2023 11:28:07 +0100 Subject: [PATCH 82/93] checker 4 and diagram --- docs/examples/guide/widgets/checker04.py | 108 +++++++++++++++++++++++ docs/guide/widgets.md | 37 ++++---- docs/images/scroll_view.excalidraw.svg | 6 +- 3 files changed, 132 insertions(+), 19 deletions(-) create mode 100644 docs/examples/guide/widgets/checker04.py diff --git a/docs/examples/guide/widgets/checker04.py b/docs/examples/guide/widgets/checker04.py new file mode 100644 index 000000000..54f2d7522 --- /dev/null +++ b/docs/examples/guide/widgets/checker04.py @@ -0,0 +1,108 @@ +from __future__ import annotations + +from textual import events +from textual.app import App, ComposeResult +from textual.geometry import Offset, Region, Size +from textual.reactive import var +from textual.strip import Strip +from textual.scroll_view import ScrollView + +from rich.segment import Segment +from rich.style import Style + + +class CheckerBoard(ScrollView): + COMPONENT_CLASSES = { + "checkerboard--white-square", + "checkerboard--black-square", + "checkerboard--cursor-square", + } + + DEFAULT_CSS = """ + CheckerBoard > .checkerboard--white-square { + background: #A5BAC9; + } + CheckerBoard > .checkerboard--black-square { + background: #004578; + } + CheckerBoard > .checkerboard--cursor-square { + background: darkred + } + """ + + cursor_square: var[Offset | None] = var(None) + + def __init__(self, board_size: int) -> None: + super().__init__() + self.board_size = board_size + # Each square is 4 rows and 8 columns + self.virtual_size = Size(board_size * 8, board_size * 4) + + def on_mouse_move(self, event: events.MouseMove) -> None: + """Called when the user moves the mouse over the widget.""" + mouse_position = event.offset + self.scroll_offset + self.cursor_square = Offset(mouse_position.x // 8, mouse_position.y // 4) + + def watch_cursor_square( + self, previous_square: Offset | None, cursor_square: Offset | None + ) -> None: + """Called when the cursor square changes.""" + + def get_square_region(square_offset: Offset) -> Region: + """Get region relative to widget from square coordinate.""" + x, y = square_offset + region = Region(x * 8, y * 4, 8, 4) + # Move the region in to the widgets frame of reference + region = region.translate(-self.scroll_offset) + return region + + # Refresh the previous cursor square + if previous_square is not None: + self.refresh(get_square_region(previous_square)) + + # Refresh the new cursor square + if cursor_square is not None: + self.refresh(get_square_region(cursor_square)) + + def render_line(self, y: int) -> Strip: + """Render a line of the widget. y is relative to the top of the widget.""" + + scroll_x, scroll_y = self.scroll_offset # The current scroll position + y += scroll_y # The line at the top of the widget is now `scroll_y`, not zero! + row_index = y // 4 # four lines per row + + white = self.get_component_rich_style("checkerboard--white-square") + black = self.get_component_rich_style("checkerboard--black-square") + cursor = self.get_component_rich_style("checkerboard--cursor-square") + + if row_index >= self.board_size: + return Strip.blank(self.size.width) + + is_odd = row_index % 2 + + def get_square_style(column: int, row: int) -> Style: + """Get the cursor style at the given position on the checkerboard.""" + if self.cursor_square == Offset(column, row): + square_style = cursor + else: + square_style = black if (column + is_odd) % 2 else white + return square_style + + segments = [ + Segment(" " * 8, get_square_style(column, row_index)) + for column in range(self.board_size) + ] + strip = Strip(segments, self.board_size * 8) + # Crop the strip so that is covers the visible area + strip = strip.crop(scroll_x, scroll_x + self.size.width) + return strip + + +class BoardApp(App): + def compose(self) -> ComposeResult: + yield CheckerBoard(100) + + +if __name__ == "__main__": + app = BoardApp() + app.run() diff --git a/docs/guide/widgets.md b/docs/guide/widgets.md index 80676284c..96b9cbff1 100644 --- a/docs/guide/widgets.md +++ b/docs/guide/widgets.md @@ -206,7 +206,7 @@ Textual offers an alternative API which reduces the amount of work required refr !!! note - The Line API requires a little more work that typical Rich renderables, but can produce very power widgets such as the builtin [DataTable](./../widgets/data_table.md) which can handle thousands or even millions of rows. + The Line API requires a little more work that typical Rich renderables, but can produce powerful widgets such as the builtin [DataTable](./../widgets/data_table.md) which can handle thousands or even millions of rows. ### Render Line method @@ -251,11 +251,11 @@ This would create the following object: --8<-- "docs/images/segment.excalidraw.svg" -Both Rich and Textual work with segments to generate content. When you run a Textual app you are seeing hundreds or perhaps thousands of segments combined together. +Both Rich and Textual work with segments to generate content. A Textual app is the result of combining hundreds, or perhaps thousands, of segments, #### Strips -A [Strip][textual.strip.Strip] is a container for a number of segments covering a single *line* (or row) in the Widget. A Strip will at least one segment, but often many more. +A [Strip][textual.strip.Strip] is a container for a number of segments covering a single *line* (or row) in the Widget. A Strip will contain at least one segment, but often many more. A `Strip` is constructed from a list of Segment objects. Here's now you might construct a strip that displays the text "Hello, World!", but with the second word in bold: @@ -268,15 +268,15 @@ segments = [ strip = Strip(segments) ``` -The first and third strip omit a style, which results in the widgets default style being used. The second segment has a style object which applies bold to the text "World". If this were part of a Strip it would produce the text: Hello, **World**! +The first and third Segment omit a style, which results in the widgets default style being used. The second segment has a style object which applies bold to the text "World". If this were part of a widget it would produce the text: Hello, **World**! -The `Strip` constructor has an optional second parameter, which should be the *cell length* of the strip. In the code above, the length of the strip is 13, so we could have constructed it like this: +The `Strip` constructor has an optional second parameter, which should be the *cell length* of the strip. The strip above has a length of 13, so we could have constructed it like this: ```python strip = Strip(segments, 13) ``` -Note that the cell length parameter is _not_ the total number of characters in the string. It is the number of terminal "cells". Some characters (such as Asian language characters and certain emoji) take up the space of two Western alphabet characters. If you don't know in advance the number of cells your segments will occupy, it is best to leave the length parameter blank. +Note that the cell length parameter is _not_ the total number of characters in the string. It is the number of terminal "cells". Some characters (such as Asian language characters and certain emoji) take up the space of two Western alphabet characters. If you don't know in advance the number of cells your segments will occupy, it is best to leave the length parameter blank so that Textual calculates it automatically. ### Component classes @@ -297,24 +297,23 @@ The following example replaces our hard-coded colors with component classes. ```{.textual path="docs/examples/guide/widgets/checker02.py"} ``` -The `COMPONENT_CLASSES` class variable above adds two class names: `checkerboard--white-square` and `checkerboard--black-square`. These are set in the `DEFAULT_CSS` but can modified int he apps `CSS` class variable or external CSS. +The `COMPONENT_CLASSES` class variable above adds two class names: `checkerboard--white-square` and `checkerboard--black-square`. These are set in the `DEFAULT_CSS` but can modified in the app's `CSS` class variable or external CSS. !!! tip Component classes typically begin with the name of the widget followed by *two* hyphens. This is a convention to avoid potential name clashes. -The `render_line` method calls [get_component_rich_style][textual.widget.Widget.get_component_rich_style] to get `Style` object from the CSS, which we apply to the segments to create a more colorful looking checkerboard. +The `render_line` method calls [get_component_rich_style][textual.widget.Widget.get_component_rich_style] to get `Style` objects from the CSS, which we apply to the segments to create a more colorful looking checkerboard. ### Scrolling -Line API widgets require a little more more work to handle scrolling. +A Line API widget can be made to scroll by extending the [ScrollView][textual.scroll_view.ScrollView] class (rather than `Widget`). +The `ScrollView` class will do most of the work, but we will need to manage the following details: -A Line API widget can be made to scroll by extending the [ScrollView][textual.scroll_view.ScrollView] class. We also need to manage the following details: +1. The ScrollView class requires a *virtual size* which is the size of the scrollable content, and should be set via the `virtual_size` property. If this is larger than the widget then Textual will add scrollbars. +2. We need to update the `render_line` method to generate strips according to the current position of the scrollbars. -1. The ScrollView requires a *virtual size* which is the size of the scrollable content, and should be set via the `virtual_size` property. If this is larger than the widget then Textual will add scrollbars. -2. We need to update the `render_line` method to compensate for the current position of the scrollbars. - -Lets add scrolling to our checkerboard example. A standard 8 x 8 board isn't really sufficient to demonstrate scrolling so we will also make the size of the board configurable, and set it to 100 x 100, for a total of 10,000 squares. +Lets add scrolling to our checkerboard example. A standard 8 x 8 board isn't really sufficient to demonstrate scrolling so we will make the size of the board configurable and set it to 100 x 100, for a total of 10,000 squares. === "checker03.py" @@ -327,9 +326,15 @@ Lets add scrolling to our checkerboard example. A standard 8 x 8 board isn't rea ```{.textual path="docs/examples/guide/widgets/checker03.py"} ``` -The virtual size is set in the constructor to match the total size of the board, which will enable scrollbars (unless you have your terminal zoomed out very far). +The virtual size is set in the constructor to match the total size of the board, which will enable scrollbars (unless you have your terminal zoomed out very far). You can update the `virtual_size` attribute dynamically as required, but our checkerboard isn't going to change size. -The `render_line` method gets the scroll offset via the `scroll_offset` property. This attribute is an [Offset][textual.geometry.Offset] that indicates the position of the scroll bars. It starts at `(0, 0)` but will change if you move any of the scrollbars. +The `render_line` method gets the *scroll offset* which us an [Offset][textual.geometry.Offset] containing the current position of the scrollbars. We add `scroll_offset.y` to the `y` argument because `y` is relative to the top of the widget, and we need the line relative to the scrollable content. + +We also need to compensate for the position of the horizontal scrollbar. This is done in the call to `strip.crop` which *crops* the strip to the visible area between `scroll_x` and `scroll_x + self.size.width`. + +!!! tip + + [Strip][textual.strip.Strip] objects are immutable, so methods will return a new Strip rather than modifying the original.
--8<-- "docs/images/scroll_view.excalidraw.svg" diff --git a/docs/images/scroll_view.excalidraw.svg b/docs/images/scroll_view.excalidraw.svg index 4ca739d02..0d3ba66a8 100644 --- a/docs/images/scroll_view.excalidraw.svg +++ b/docs/images/scroll_view.excalidraw.svg @@ -1,6 +1,6 @@ - + - eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nN1dZ3NcIsmy/b6/YmLe16VvuSyzXHUwMDExN17IIYOQR4ZcdTAwMTc3XHUwMDE0mEa01Fx1MDAxOEFjpFx1MDAxYvvfXybSisY0gpFG04MmYmZEY4quU5nnZGVm/fePb9++R09t//tf3777w0opXGaqndLg+5/0eN/vdINWXHUwMDEzL4nR791Wr1NcdTAwMTk9s1x1MDAxZUXt7l//+lej1Hnwo3ZYqvheP+j2SmE36lWDlldpNf5cdTAwMTVEfqP7v/T3Uanh/7vdalSjjjf+kIxfXHKiVufls/zQb/jNqIvv/n/4+7dv/1x1MDAxZP1cdTAwMWRcdTAwMWJdx69EpeZd6I9eMLo0XHUwMDFloOZ8+tGjVnM0WOCWW1xyIN+eXHUwMDEwdLfx41wiv4pXazhkf3yFXHUwMDFl+n5+WONN/354WtnMdC7c/Vx1MDAxZbRvd8efWlx1MDAwYsLwPHpcbl/uRKlS73ViY+pGndaDf1x1MDAxNVSjOl7nU4+/va7bwpswflWn1burN/1ud+I1rXapXHUwMDEyRE/4mGRvXHUwMDBmvtyDv76NXHUwMDFmXHUwMDE54m9cdTAwMTkrPFx1MDAxMJw7Lq1QUrxdpFcraz1cdTAwMDXGSCaFYFx1MDAwZbicXHUwMDFh1lYrxInAYf1cdTAwMGZcdTAwMWL9jFx1MDAwN1YuVVx1MDAxZe5wdM3q+DlC2LKvx89cdTAwMTm8flmtPaaMXHUwMDAxJVx1MDAxNFjNnHp7Rt1cdTAwMGbu6lx1MDAxMT7FXGLPStBMayaYtXY8zq4/mlx1MDAwZsGlUnjdmbcr9PHt/epcYlx1MDAxYf+J37Fm9fWONXthOFx1MDAxZTFd2InBafyaXrtaepl2rnGYSmuQTrm362HQfJh+u7BVeVx1MDAxOCNl9Ojff/5cdTAwMDBCjbVJXGLlWnFcdTAwMDZcXDOzNES3XHUwMDBitYf74cbRY15enZ7XLvrFwvZGyiFcbtrjoIyWSlx1MDAxOIm3w+lZkErpQHLEqTRcXKdcdTAwMTakOHzlXHUwMDFj07BmXHUwMDE41UYkYdRcdTAwMTlhnDGCL1xyUf9i71x1MDAwMO9euVxcVWcn+X2by508qJRDNMOlx7iz4LhBp2Etn8QoaOkpbVx1MDAxOV7iwqrpcaVcdTAwMDehXHUwMDFjpLT4h69cdTAwMWJCtUtCKGdcbn1cdTAwMDc6uOVcdTAwMWT99XH5KHdcdTAwMDO23Do5bZSu+3vIb7q/XHUwMDE2opy9a0aVZ1x1MDAwMPmMsJo79Fx1MDAxY2ZcdTAwMDKhWjpcdTAwMGZJgHNooVx1MDAxOChcdTAwMTCphajgxihcdTAwMWPq2kHUJlNRgU7PxrnNe1xibWSKJZmtbFx1MDAxNM6L9Vx1MDAwN3+7frKzXHUwMDFi8rRcdTAwMWJRyz1kocpKLSyuypiZXHUwMDFjIVQxXHUwMDBmLahcdTAwMDLFXHUwMDE1LldcdTAwMDOpRSg3OF2APmDtuCgkc1GLRlx1MDAwM+9cdTAwMTd3S2PUmZKGsHV/36tcdTAwMTT4bdirto7NUcoxylx1MDAwNXhOI+k2XHUwMDFjIeBigujFzVx1MDAwM1JR5ONWSMVkbP5TXHUwMDA30dFrOfKVr4IosK+BqJGJXHUwMDEwXHUwMDA1po1hsFx1MDAwMlx1MDAxN+08XHUwMDFm1DfMzlx1MDAxMfC9Pelntp+zJ/XLtDt6nFv09MjhjFx1MDAwMPxcdTAwMTfspKhH5HrCopbXWignRJrJKLPcXHUwMDFhxtZcdTAwMGakOlx1MDAxMaROc1x1MDAwNcwsb0Y3ZNlFjf1n26rvne9cdTAwMTSueOmymXZJn3FcdTAwMWVjXHUwMDEylyMuRvT4ztgpiIInXHUwMDA1zoZRgOIxvVx1MDAwMOVGo6bH91kzgGrDklx1MDAwMCpcdTAwMTjn3FomYGmEbjrVclH0sFnuPoiGrvLuTbOdcoRyXHUwMDA3XHUwMDFlV4YzriRcdTAwMDXZxKSnV1YjNlx1MDAxOP7DtEB/z1V6QSpcdTAwMTE/1uCr11xmpKhcdTAwMTSSQGq0tMahpl1cdTAwMWGjuXBY367LYj5ifrijXHUwMDA3T2F7eJFyjEopPWSaXGbQkTOrxXTwXHUwMDFlIYomXHUwMDE2QWGYxGWb3rgorjFkI1xm5JdcdTAwMDVGv8rRi0RH76RDcsPY8lx1MDAxMIWHnbBcXK31s+d9YNXidXjb3Fx0U1x1MDAwZVGhlYdLXHUwMDExXHUwMDE3I1d431x1MDAxOUxAXHUwMDE0NPcsQldcdFxuS4lcdTAwMTTbUNCGXHUwMDBinK/1XHUwMDAzaGLkXmryb2pcdTAwMDVcdTAwMWK69aBuXCJ+X70rXHUwMDFjVTqXg44s1EultKslMqJKOe0kXCKAczbJRCnoxIW2hvZGudUphqiRktNfa1x1MDAwN9HY9ua0WFx1MDAxMjhcdTAwMTSJXHUwMDBic1x1MDAwNSN6f7N3cGhcbsdcdTAwMWLKb4mD48vLdtGkXHUwMDFkoyhcbj1cdTAwMDSHldohIZVyXHUwMDA2pMJDrYRcdTAwMDRAoHKKsaLUYVx1MDAxNFx1MDAxN5lFovJ1m/RfRUV5sqBcdTAwMDenXHUwMDE0V9YsXHUwMDFmdVx1MDAxMntcdTAwMWLmLDxcYor9rfb93bFo1Nh9MeWOXjHpaYG60DnlOOr2KUePNEAgNJi0TqCtTS9ENVijXGaYdTOjVibugKJcdTAwMGI0xki9vFx1MDAxNT2KbvuBelx1MDAxOF62N1x1MDAxZotcdTAwMWLRU/78rL7zXHUwMDFiIFRcdTAwMDGnfCZAPynkpKJHfuchbmn6LaN8k9RClFJhjOFq7Vx1MDAxMMpcdTAwMTK5KJdcbmjThS2/R39zONgt14e1sGev9pvuaqvVPL5Ju6NHpulpY1x1MDAwNSolri1cYmWmMGo8JizOXGaCh1x1MDAxMolSi1HBnEQzis5vzUBcdTAwMWGH4HRklMLBpGSXt6PhzXPxWFfgtKFcdTAwMDfN6DjXuO9cXJ+m3I5mLEpcIs0kt1JSzGZcbqKOeYZrNLS4XFyFVelcdTAwMTVMXG6kcFoquWZcYrWgklx1MDAxMMqtMEY5sULC6N2gkr1cZrrc3lx1MDAxNp5cdTAwMGWCutLnxZ1fXHUwMDFjXHUwMDE3XVwi1cmgXHUwMDFlkkjKXHUwMDExg1bZOD5eMMqRXHRoh1PDUfHHbFTaMIpcdTAwMTetlcqu3Vx1MDAwNmgyRonbaK2X313yuzVVubjIQa691dbFfK++U95Lu1xy5cxDpums5lx1MDAwMijhLuYz6FxyLIJcdTAwMDdlXHUwMDE0in2UzIid9JJRq61hbt2oqLHJWSRcdTAwMDKnXHUwMDA0OZhcXF7PO1x1MDAxNtajXnnX+IfVgOWvK73MYSvlXGJFXHUwMDEzioJQOEAuXG4uvttLL3eKeU5cdFxcq0owmeJcdTAwMTRcdTAwMTLU8UhF5PrF7WPhpFx1MDAxOVx1MDAxYSosY1qsYELLrTq/61x1MDAxY+RcdTAwMWHPmyeNu3774Ph6J59ygGaM9Vxmulxu9N9cdTAwMTaZJlx1MDAxYdIphEqPNL41XHUwMDAynLZcIr3ZooIhmWaCs7UjojJxf14xYFayXHUwMDE1bOh5/iZz8XB9tN+v3PT2j/1wMDjup1x1MDAxY6JcXKBcdTAwMTFF52iYQHzqqc1P9JyolFDpo1x1MDAxMFHO8PRm3Fx1MDAxYmHAraFcdTAwMTGNf9Xp3Xmh0LdJvTxAd/bt4O4y18z3/dPCxuZF9bbXTHtIlFx1MDAwMEqln1x1MDAwZVxykKSakFx0gFwi+1x1MDAwNI9JQCNcdTAwMGKgdDxJL21cYuWa/oBbPzGvXHUwMDEziSg4UFx1MDAxY9n30lxi7e9cdTAwMGWi67P9u/x20d86uL85qNRL1ylHaMZ4XG59o0J3XHUwMDAxWlx1MDAxYjeVy4xcdTAwMTA1nlX4w5DpoZZKr1x1MDAxMUX7L1xmY2uXhWdE4r6SZVKD4CtU0LvDXHUwMDA33ogye4ftx5NwN5dpXHUwMDBl4fYk7dEm7lxmldAz9PJcYtU463lcdDZcdE9xXHUwMDE41dyNNjHSi1COa4ip9YuIuuRcZlx1MDAxMqesXHUwMDAy59zyfr5cdTAwMTjUud/KZINmvlx1MDAxMEB+b7flXHUwMDA2v1jML5PkpD1cdTAwMDQgUVH09256W1x0ISpcdTAwMWS3XHUwMDA0XHUwMDFlQbs2qYWo1lx1MDAwMlDR2bUzoi7RiDpcdTAwMTRKkmoklkbo9eVppeVOq3dXwVnhXHUwMDAyoPPc2bxKuZ9cdTAwMTeadjZRryuFpFx1MDAxNMnmZMTe4mWFQlx1MDAxZeW+XHUwMDA0kWZcdTAwMWIqqMKMXGLzmiE03lx1MDAxMmCmgFx1MDAxZTUuuj29vJ8vXFxcdTAwMWa0d4P93bN8Z/9cdTAwMTAuovOry27aO+VIXHSe5VxmaSgyUmXdpFhyinuWMkuEJFef4pAol5Kj29Nf14Xk1yfhXHUwMDE5So7UdoVke7d53bzs12+jsti0e7Xn8rY/eEw5QrmzXHUwMDFlSnnDnFx1MDAwM1xynEs9XHUwMDA1UeWh+1x1MDAxNHgrXHUwMDE4l0KmV847XFxnhpm1I6JxozFTVudw3lx1MDAxONjljejJUc70MvKKXzzdN1x1MDAwNyYrovKen3KIKobwQCGM8yuYxEmejok6T1x1MDAwM4p6pKia9kbTXHUwMDBiUdBKOFxy65bJ7JLTRFFcdTAwMWRKSuJd3ojulMNm5/G4nLX8YPPsYfdhsFx1MDAxZJ6lXHUwMDFloeTmkYkyg2aIs0lcdTAwMWI6ioniSkXzqpyWMr2pI1xc4PJB7bB2RtQlt2xE5uVor2V5XHUwMDFium1y/WKws/1cdTAwMWPehe2gye3u3W4j5VxiRevpMdTrXHUwMDAyZ1x1MDAxZt04s2ZcdTAwMWGitDPqJFx1MDAwN8DZifO81GFUUXWqiG+zrFx1MDAwN0alTNxZQu5FU6NWXHUwMDEw9PV+aT97aMPhTXk7a1x1MDAwZUr2LOcnZThNgW1cdTAwMTKi08mYb6+qlrp1f1x1MDAwNYyad+P2VlDASYAyXHUwMDE0d5qO21NtsuZUUmlcdTAwMDDExC7GXHUwMDBmYDTqlJrddqmDXHUwMDE4mMUpgPK0Nlx1MDAxNlxmt05CrI7/XHKniFBv1FFcblx1MDAwMMkzn9dyjOrMjFCfh9PXXHUwMDBiY2DF5rtlbvaOb8RG+/K8e1lcdTAwMGI6rqnz4+ZdXHUwMDEzKCx1Oq3B97crf/+56H3z14f3rHSlj+rnXHUwMDE1dtIuR2FcdTAwMGX2l3vf1/8lry7K+bWgv2R1cctcdTAwMTLTXHUwMDA3jbC0YblCM7+N21opujs5Pm9l6rJcXMmen++qpFjEj62un9HKT2lcdTAwMGKgrUKVXHUwMDA3bqpcdTAwMWNQWk8qLt2ojilcdTAwMWWK+lx1MDAxMftfq9VmXHUwMDE3lVTWXHUwMDAzyv1cdTAwMTIkMy23s4tcblx1MDAxZJPnqJBcdTAwMDG1psZJmVx1MDAxM4dwgkpuYd2Mv1vU3cdYUt92+dwsU9nLNSvlKHrK9a3eeK5cdTAwMWWq3jDt+1xyXHUwMDAwnpTURVx1MDAxMuGnQGkzyaK14p401F9cdTAwMWJcdOpEzXXaXHUwMDE4iuFSMbV2m7bOJkJU472a7F34XHUwMDFlQvc3XG79QfhQUMWbw+3sZkFtmrNK2lx1MDAxMcqd8lx1MDAwNMp3SX0nXHUwMDE4XHUwMDE3alx1MDAwNqCMa8GQqVHBdZopNCVHOFg7XG7tYjd9JvNFkFx1MDAwNOfLQ9Re7uYvh49mb3PrPNdX16Hc2vvFPdKW2bRcdTAwMTWeQdvjkFx1MDAxZjvjzHTHXiU8SdWA1qF3TXPzXHUwMDE0R1vs6Fx1MDAwNNbNivJ4i9rpbVvNrY1ff1x1MDAwZqLDp42HsFx1MDAxNO5cdTAwMWNcdTAwMTTPhsXipSuHh7bzIzR0Olx1MDAwNeqn0lDG8J4jXHUwMDAzdEDJ1lNeXHUwMDFleSjScYNcdTAwMDDVlF3wM2io8UDhXHUwMDE4JHVoXHUwMDAzNUfbceLCklx1MDAwYomyXHUwMDBlZSaPl9K85lx1MDAxNFxiy6RUn7hcdTAwMWT2emGuXHUwMDA2XHUwMDEzYviU63b5+UA1+cHTyYE7XHUwMDBmup+g7VrX1cst8Vh7PKpcdTAwMWY7rVub+iR/8WnaTlxuiDVcbvrgmvLDMGh3568oyZM7XGYr5yxzq2RCbGX2nvfPj1xucnhcIrv3xXZ2cFx1MDAxYyZF91x1MDAxNq6przP6XHUwMDFhiVx1MDAwN51xXHUwMDAx1Pg03lbwxeRcdTAwMDPSXHUwMDEyVE2oqsxcdTAwMDerv2slwEmdXVMk7HDJ4utcdTAwMTUyo/hpXHUwMDFhb2vKok9cdTAwMTBmVDw56icvp5dcdTAwMTR3Qo1cdTAwMWF7ft7uyE9aU0tgXzmIybWfif3kM1xuwFx1MDAwMUi1SmD7iVx1MDAwNTf1/oHhfJBcdTAwMWLWXFwh2+tdi3RD36CppqpxjdCmcpjp9u9I2KnXoXVcdTAwMTLR9cFk9FqpjG7j56Cfmk9q+Mxg4ZfRnUXwVFwiMVx1MDAxNVx1MDAxZIU8MvJ4R5J3W8JcdTAwMGVPLs+u77rNfKd5oSHT2Fx1MDAwN5tNNzyd9tBgSsGMY8qJKXBKz+JcdTAwMDLmhrLUwHysv1x1MDAxMVx1MDAxN2Vr55Dxz1x1MDAwMKdQynKxhkG35NMzuFx1MDAwNGXUSlx1MDAxNbuP+9lgj4iDuXlu9jbEw0Bmf3FcdTAwMDeuJei4RlxmOjlSjFx1MDAwMPFzw0ZbNnRMXHUwMDE2JZOiiEO3luL+RpxcdTAwMGJNmzFr1+Ao3rBnZldcdTAwMTBHwrhZoVZcImtcdTAwMWVcdTAwMDPjXHUwMDBlaq2to63raCNo+LntX9xVe7mYhpNcdTAwMDBcdTAwMTZ1I0V+J308NY+hXCJcdTAwMWXUaZRcdTAwMGKV5iRKXHUwMDFjnTSW/Y5OfnHnd5VYK2GFcJrqXHUwMDA3loZo4TTfk4em8LjNdjZzuX5hUyRWnKXEy4PxkFuilURHayF++ucoP1xyXHUwMDA0XHUwMDFhUW2onSOdifkhfFb8qqqWZvEpkelKw6TRQlx1MDAxYlxcLLG43zisQf1cdTAwMTStQ7pB4Vx1MDAxNdBaTyN0XHUwMDE09TCSr1vQzSVHXGJIJFlcdKvQUD9T4mf3NmpfPcnNnWHtML+dej+PPt4zQlx1MDAwM50wRKdoiNnuRoxpOslNKzRT6W1cdTAwMTJnR1lcdTAwMTl67WwofudELsrwnjnrlofoTdjdkNnh9uVcciuUjrWJStlccpluXHUwMDFiyqX1kMFcdTAwMTmJ5lx1MDAxMlx1MDAxOY2FqSRfQLFkUU4yZpHruY95eVXRfm2OkueS46eAXHUwMDExTvBRavWcXHUwMDFjXHUwMDA1OrVTMFxyXHUwMDE2mLPU3l1NQ1TTQYlcdTAwMDBfd9jgV1lRkSiXlFx1MDAxMeji2FxuPbVvXHUwMDFlh0enN1x1MDAxYqWDKLeZk1d3bGuHp39cdTAwMDNYSI+yeYGSza1cdTAwMDA23WpcdTAwMTM8bZWUXHUwMDEyvT3EXHUwMDBmvkubXHUwMDE15Vx1MDAxNI8wZv16XHUwMDFmOJG8XHUwMDE3XHUwMDAwVlx1MDAxMEWC5UFcdTAwMWFtXHUwMDA1R/WgWzdcdTAwMWI1lW1l7lx1MDAwM3Ppl9NcdTAwMGVS9PJUsoN+XFxcdTAwMWGlXHUwMDEwitMgdVx1MDAxZdovxCqFdOJN5NJcdTAwMDZSnCluKVx1MDAxZnTNMFx1MDAxYT9cdTAwMTFipnCXXHUwMDBiZsRcbudk1bcv3P5Teeu2pE6e29cl/7Rym3TydUpcXL202lx1MDAxM1x1MDAwMtmopSTe+Fx0taOwKGhUMlx1MDAwZUWIZFoy9zF8Jukl9OKKsk81UJqhdrGaorGnd4rO6zLcKK6EZmZ2z1xuIU69XHUwMDBm1q9jMSzYVjKGsVWOeLnh2UplWDhcdTAwMGVOs+y+556zXHUwMDE3YifpQMzUQFx1MDAxNI0kbeRcdTAwMDFyUif1JFx1MDAxOdWkllx1MDAxMKD2RTzqj0Xuk8go8mHlLNVIcyRXLFZcdTAwMWQwRihjnjI4PIaTgsYyXHUwMDE2vv2nlyGaeerR8Fx1MDAxYpacRf4wmodNnlxcVa6RkFx1MDAxYlx1MDAwMSvkIZ5W84/Hd2ew275cdTAwMWJcZlx1MDAxYvno3u+ap3SXSSiH2ETLiV7CUidcdTAwMTI9mUFjXHUwMDE0XHUwMDE19DKgvIdcdTAwMGZcdTAwMWYnPDeDRsBcdTAwMWNvLmeCSWhTXHUwMDExXHUwMDBln1x1MDAxOUx6vfCl9Vx1MDAwZpfZU13JVEphsSOHw61iodhcdTAwMTPnn5QnoLWlLmorLJgxMFvN6Dx4XHUwMDFlbVx1MDAwM9iJR7OlRlx1MDAxMD5NgGu0knCA/aBcdTAwMTP1SuFtXHUwMDE3X+i9zlxcbPq7Plx1MDAwZWD0jjDx0o0wuKOF9z30a5MrMlxu8Ma8XY5asa2EXG5cdTAwMGWlhG/X2a9Of6VWJ7hcdTAwMGKapfBiwbBcdTAwMTZahpdcdTAwMWI9xzTIZIVcbijKhNErdDFfjKhfkFxc965lQD3qIaqUQJ+BtMRNXHUwMDE1+Vx1MDAxOVx0XHUwMDFlnYdhkfcjq5FcdTAwMWZzW/OT69AvXG5UlYBcIphROs+c7Wb0q55DgkFcdHbKonebsVx1MDAxY+SxjLGWfYXXWr1cYil220qdaDNoVoPmXHUwMDFkXlx1MDAxY1uP7/7LR+8v4WPoy5XaL5SUXCLRXHUwMDFj74xDT6rHZ3KNVnalR18kwzzkxXRwl5OG+KtcdTAwMTWvT3ozY9/9ZvX9QS2uXHUwMDBmfFx1MDAxYpTknlPc4mxcdTAwMDCXbsxB4kPiXHUwMDFl51Kh1lx1MDAwNIlUmrJtZoZcdTAwMTSWutFWq9FcYlwivPUnraBcdTAwMTlN3+LRvdyghV33SzMmXHUwMDAzv1L82rRcdTAwMDVo0ztOuojx/76NV8nol7f//+fPuc/OJIN4dHlcdTAwMTa/43f8I/7vyuaLJ1x1MDAxZk5Le/Ocs1x1MDAxNWLAi1x1MDAxZFc6zZfzUPMpyv3WUnOY2qOw1mPMKGGRknNmf0KJXHUwMDFh81x1MDAwMFmToNguoLDjZk5ysGTCXHUwMDEzmo7w1pyaUdhZ88XpQHYrxdeR7vH9/lx1MDAwNebrXHUwMDFmQ8A8a1x1MDAxZLlZh8JeUZnujDlBTcM0amqUKiSqtVtsvCa/xW9kQ5KBRD+zXHUwMDEwWtGCJEmjeC/TmYQmXHUwMDAzgKZ8XHUwMDA1bdQqPF01TMtvV/LX3WM1dJC5/KGd+K/TRijbjUPNjrLIWlTHkzvxVDXoKapcdTAwMWWUqKul+mDsc744UkuJI0pVNWA/8Vx1MDAxMIw3XGJ9blx1MDAxMfdCcXT7iFx1MDAxNL+xVb3bvkV/uXdxcrxfqX+aOHLUzunXiKOX2UybNnpcdTAwMTnVj3FcdTAwMGJcdTAwMDHJXHUwMDA1xki0KTNpeW6xXHUwMDE4T2nkXHUwMDE2UiGro+OaKVdcdTAwMWOMXHUwMDEw05ZBXGLPoltS1OROfvTwnLmWgTPt0d4yMmlEuFJsjqHgnkZSqdExUltL62LBk3+iKsZcdTAwMWEmv2Lb7udcdTAwMGKjxVx1MDAwZeaNNDhPOGOENnzUko/NXHUwMDE1IcxjXHUwMDE0XHKj7Fx1MDAxNkZpoOh0XHUwMDE3c4tcdTAwMGZcbiNhPY5uxpGfMZKOvJkvjZhjtNGArFxih6V/c2mUXGZh+snMondFXpOsjExihqFcdTAwMDGUwtbEsi3es16LvVZcdTAwMWGtl1FcdTAwMWX1t2NcdTAwMDJcdTAwMTSDiVx1MDAwNnKvxst4xjJcdFrhXHUwMDE34vDBXCKvucZcdTAwMGJQ5VtNMLa4xOLNKOLGyzBljdOSmjHiQGaMXHUwMDE3woK7rzon+Yf5w2eZr5hxoiOEXHUwMDA1MlPpmKXOXHUwMDAx8djOa/hcdTAwMDfoaFwiOnmIW8Glcu/Yr99WXHUwMDFiZZKx9HJ5XHUwMDA2RitakWR1lGhEqK1cYt56I5a3XCIn9d3BMCzmn56et0Bu5Fx1MDAxNTRr27/Sirh3xZFHLcQsXHUwMDFk0IbSR05ZXHUwMDExoM5cdTAwMDHGOIfYQ1x1MDAxMfNTjMg8qzFHXHUwMDFijVx1MDAwZWVxnymOXHUwMDE27sHgLVx1MDAxOfOpny0zupVOK1xmb1u1WtdPxe7L5IB+zD1Llth3QzCEnEKvtHyRyuLS3DT654xcdTAwMDGPS+CS0qe51GoyX0A5Ntqx1VYoZDL8J0QuueCe0tRXXHUwMDAx7eaoXHUwMDFi7lx1MDAxY/+spWfokGVj0Fx0aTnbXkvQSVx1MDAxZUZ9zYEzeFx1MDAxZoT+sd3PKVx1MDAwNz3tyt533YuL/OOuW1ErXGIrkF+imcDbXHUwMDEzXHUwMDE3XHUwMDFmb45bOmqpwoxcdTAwMDQpUVxm/N5cdTAwMWM/XHUwMDExSaOrM1x1MDAxOPok56yTc4uVo0TCXHUwMDE1OvecXT1cdTAwMWM37/qXwX3vJl9cdTAwMDJ5UeVcdTAwMDdhql1zhjvnOS6cpoP90PPZ6Vph56HWZNbRXHSq8Yq/T299OVx1MDAxZf9cIlx1MDAxN40q0Fx1MDAxOfeZOe5vOEp9o5KPXHUwMDExgKd/szS4fVx1MDAxYcaPOfvkNnCoPo0wYoVcdTAwMWSGhfOazoUqXHUwMDE1ddDiXHUwMDFjkKFS+e1kIFx1MDAxMVWnZ9FowqhcdTAwMTbQsVx1MDAwZlX7LVxcqNyiqmR07rmQjDNcdTAwMDVz8lx1MDAwM9Uoo4DqpTRSMJjTT9MwwVx1MDAxY1UnfonD/+GVtqRcIl9s+L/Ftyq5odwrTcE7bpRSc/y68jRnXHUwMDE2hTgqVVx0+m2XbsWA4uImXd8mczuorVx1MDAxNEVcdTAwMTU5XHUwMDE31up5XHUwMDFiqFx1MDAxMlx1MDAxN1x1MDAxOaP+fdRa38rfm2skYph+MjPwTeJcdTAwMWF/vH7C91K7fVx1MDAxZSHo3qZcdTAwMDPhXHUwMDFjVF9t9Phrfu9cdTAwMDf+YHM+tyZ6/cfrXHIlXHUwMDBi5NOX/e/ff/z9/96SXHUwMDBlNiJ9 + eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nN1da1NcIsuy/b5/xcScj3dTp96VdVwibtzw/cQ3Prj3hMGjXHUwMDExtFx1MDAwNYRG0Vx1MDAxM/u/30x0S/NoXHUwMDA0RaZcdTAwMDdnwlx1MDAxOWnAomtV5lpZmVn/+ePHj5/RczP4+a9cdTAwMWY/g26pXHUwMDEw1sqtwtPPP+nxx6DVrjXqeEn2fm43Oq1S75nVKGq2//XPf95cdTAwMTdad0HUXGZcdTAwMGKlgD3W2p1C2I465VqDlVx1MDAxYff/rEXBfft/6PtB4T7472bjvlx1MDAxY7VY/5dkgnItarRef1dcdTAwMTBcdTAwMDb3QT1q47v/L/7848d/et9jo2tcdTAwMDWlqFC/XHSD3lx1MDAwYnqX+lx1MDAwM7RCXGY/etCo91x1MDAwNmtcdTAwMDRcYrDGqPcn1Nrr+OuioIxXKzjkoH+FXHUwMDFl+nm6X1x1MDAxMfXgtntcXFrNtM787bZpXm/1f2ulXHUwMDE2hqfRc/h6J1xupWqnXHUwMDE1XHUwMDFiUztqNe6Ci1o5quJ1MfT4++vaXHK8XHT9V7VcdTAwMWGdm2o9aLdcdTAwMDde02hcdTAwMTZKtehcdTAwMTlcdTAwMWZT/P3B13vwr1x1MDAxZv1HuvhTXHUwMDA2JDNSXGIvXHUwMDE0SK3k+0V6tVx1MDAwNmDaOKe4kpJ7I9TQsNZcdTAwMWEhTlx1MDAwNFx1MDAwZetcdTAwMWa899VcdTAwMWZYsVC6u8HR1cv950hcdMXA9p/z9PZhrWVcXDtntNRcdTAwMDYs9/r9XHUwMDE51aB2U43wKU4yUMZya7nkXHUwMDAw0Fx1MDAxZmc76M2HXHUwMDE0Smu87t37XHUwMDE1+vXNnXJcdTAwMGZcdTAwMWH/jt+xevntjtU7YdhcdTAwMWYxXdiIwan/mk6zXFx4nXZhcZjaWqO89u/Xw1r9bvjtwkbpro+U3qN//flcdIQ6gCSEXG6rXHUwMDA1N8JyNzVE13OVu9vuysFDVl1cdTAwMWOfVs5cdTAwMWXzufWVlEPUWCaMdlZp6Vx1MDAxNN5cdTAwMGVvR0GqlDdKIE6VXHUwMDEzNrUgxeFr77k1S4ZR62RcdTAwMTJGvZPOOyfF1Fx1MDAxMFxyzrZ38e5cdTAwMTWLZX1ylN2Bvb2jO51yiGaEYlxceDBeOHRcdTAwMWFcdTAwMDBiXHUwMDEwo8Yqpi1wvCQk6OFxpVx1MDAwN6HCKFx1MDAwNfhHLFx1MDAxYkKtT0Ko4Fx1MDAxYX1cdTAwMDc6uOlcdTAwMWT95WHxYO/KQLFxdHxfuHzcRn7T/rVcdTAwMTBcdTAwMTX8QzOqmTPIZyRY4dFzuFx1MDAwMYRa5Vx1MDAxOZJcdTAwMDDv0UJxo41MLUSlcE7jUJdcdTAwMGWikExFJTo9iHObj1x1MDAxMHqfyVx1MDAxN9RmaSV3mq/eXHUwMDA169Wjja1QpN2IgmDIQjUoK1x1MDAwMVdlzEz2XHUwMDEwqjlDXHUwMDBiqo1cdTAwMTZcdTAwMWGXqzOpRahwOF1cdTAwMDZ9wNJxUZPMRVx1MDAwMY1cdTAwMDbeL+Gnxqh3XHUwMDA1a8LG7W2nlFx1MDAxM9dhp9w4dFx1MDAwNynHqJCGeYuk21x0hICPXHSiVzdvkIpcIlx1MDAxZlx1MDAwN6k0V7H5T1x1MDAxZER7r1x1MDAxNchXXHUwMDE2XHUwMDA1UcNcdTAwMTdcdTAwMDNRp1x1MDAxMiFquHWOm1x1MDAxObho62W3uuI2XHUwMDBljNjeVkFm/WXzqHqedkePc4ueXHUwMDFlOZyTXHUwMDA2/zUwKOpcdTAwMTG5TFx1MDAwMmp5a6X2UqaZjHJcdTAwMTDgOF8+kNpEkHortOFuejO6ooo+ut95gUZ1+3QjdyFcbuf1tEv6jGecK1xcjrhcdTAwMTjR43tcdTAwMDdDXHUwMDEwNUxJnFxyp1xyisf0XHUwMDAyVDiLmlx1MDAxZd9nyVx1MDAwMGpcdTAwMWRPXHUwMDAyqORCXGJcdTAwMDAuzdRcYl31uuGj6G612L6T97Ys2lf1ZspcdTAwMTEqvGFCO8GFVlx1MDAxNGSTg55eg0VscPyHW4n+Xuj0glQhfsDhq5dcZqSoXHUwMDE0kkDqrFx1MDAwMudR006N0b2wW12vqnw24kG4YZ+ew2b3LOVcdTAwMThVSjFkmtygI+dg5XDwXHUwMDFlIYomXHUwMDE2QeG4wmWb3rgorjFkI9yohVx1MDAwNUZcdTAwMTfl6GWio/fKI7nhfHqImruNsFiuPG6ePlx1MDAxYV7OX4bX9Y0w5Vx1MDAxMJVWM1xcirhcdTAwMTiFxvvOzVx1MDAwMESNXHUwMDE1XGZcdTAwMTC6WlJYSqbYhlx1MDAxYeuExPlaPoAmRu6VJf+mZ7Cha3f6Klx1MDAxMrflm9xBqXX+1FK5aqGQdrVERlRrb71CXHUwMDA0XGLBXHUwMDA3mShcdTAwMDWdhLTgaG9UgE0xRJ1Sgr4tXHUwMDFkRGPbm8NiSeJQXHUwMDE0LsxcdTAwMTmM6O3V9u6+y1x1MDAxZK7ooCF3XHUwMDBmz8+beZd2jKIoZFxiXHUwMDBlUNYjIVVqXHUwMDA0pJKhVkJcdTAwMDIgUTnFWFHqMIqLXGaQqCxuk35RVFQkXHUwMDBieuO1XHUwMDE2XHUwMDFh3PRRJ7m94k7C3Vr+ca15e3Mo7yv8Np9yR6+5YlaiLvRee4G6fcjRI1xykFxiXHKuwEu0temFqDXgtDNu2cwoqMRcdTAwMWRQdIHOOWWnt6JcdTAwMDfR9WNN33XPm6tcdTAwMGb5leg5e3pS3fhcclx1MDAxMKqNoHwmg35SqkFFj/yOIW5p+oFTvklqIUqpMM5cdL10XGLliVxcVChtaNOFT79Hf7X/tFWsdithXHUwMDA3Lnbq/mKtUT+8SrujR6bJrFx1MDAwM4lKSVgwUrshjDrGJeDMIHgokSi1XHUwMDE4ldwrNKPo/JZcZqRxXGJcdTAwMGVHRilcdTAwMWNMSnZ6O1x1MDAxYV695Fx1MDAwZm3JXHUwMDFj39unenS4d3/bujxOuVx1MDAxZM1cdTAwMDBKXCLLlVx1MDAwMKUoZjNcdTAwMDRRz5lcdTAwMTNcdTAwMTZcci0uV1x0Or2CSVx1MDAxYiW9VVotXHUwMDE5QsHoJIRcbpDOaS9nSFx1MDAxOL15Km2e19pcdTAwMDKuc8+7taq2p/mNX1x1MDAxY1x1MDAxN51cItXJoVx1MDAxZVJIylx1MDAxMYOgIY6PV4xcbmRcdTAwMDLW49RcYlT8MVx1MDAxYpU2jOJFXHUwMDAwpWHpNkCTMUrcxlo7/e5S0K7o0tnZntlrrjVtPtupblx1MDAxNLfTbkNcdTAwMDVnyDQ9WCFcciXcxXxcdTAwMDa9XHUwMDAxIHhQRqHYR8mM2EkvXHUwMDE5XHUwMDA1XHUwMDBijvtlo6JcdTAwMGWSs0gkTlx0cjA1vZ73PKxGneKWXHUwMDBi9ss1nr0sdTL7jZQjXHUwMDE0TShcbkLpXHJyUePju730cq8581riWtWSq1x1MDAxNKeQoI5HKqKWL25cdTAwMWZcdTAwMGInjdBQXHScWzmDXHQtNqriprW7d/+yenR/89jcPbzcyKZcdTAwMWOgXHUwMDE5XHUwMDA3zKGrQP9ccsg00ZBcdTAwMGUhVDHS+OCk8Vx1MDAxNmR6s0UlRzLNpeBLR0RV4v685oaD4jPY0NPsVebs7vJg57F01dk5XGbCp6fDx5RDVEg0ouhcdTAwMWNcdTAwMWSXiE87tPmJnlx1MDAxM5VcdTAwMTIqfVx1MDAxNFwi2juR3ox7J53xS2hE41x1MDAxZnV4d15q9G3KTlx1MDAwZtCNXHUwMDFkeLo536tnXHUwMDFmg+PcyupZ+bpTT3tIlFx1MDAwMEqln1x1MDAxZVxykKKakFx1MDAwMYBcIvs0jCuDRtZcdTAwMTht40l6aUOosPTH+OVcdTAwMTPzNpGIXHUwMDFhb7RA9j01Qlx1MDAxZreeosuTnZvsej5Y27292i1VXHUwMDBilylHaMYxjb5Ro7sw1jo/lMuMXHUwMDEwdVxmNH5xZHqopdJrRNH+S8f50mXhOZm4r1x1MDAwNFxcWSPFXGZcdTAwMTX0fv9O3EeZ7f3mw1G4tZepd831UdqjTcI7KqHn6OVcdTAwMTGqcdbzXHUwMDFhbJJMXHUwMDBi06u5621ipFx1MDAxN6FcdTAwMDLXXHUwMDEw18tcdTAwMTdcdTAwMTH1yVx1MDAxOSRegzbe++n9fL5WXHUwMDE1QSOzWatnczWT3d5q+KdfLOanSXKyXGZcdTAwMDFIVFx1MDAxNP29XHUwMDFm3lZCiCovgMAjadcmtVx1MDAxMLVWXHUwMDFhVHSwdEbUJ1x1MDAxYVGPQklRjcTUXGK9PD8uNfxx+eaidpI7M6b10lq9SLmfl5Z2NlGva42kXHUwMDE0yeZgxFx1MDAxZfCyRiGPcl9cdTAwMTmZZlx1MDAxYiqpwoxcYvOSITTeXHUwMDEyYKSAXHUwMDFlNS66PTu9n89d7ja3ajtbJ9nWzr45i04vzttp75SjlGEgONJQZKRcdTAwMWH8oFjyWjCgzFx1MDAxMqnI1ac4JCqUXHUwMDEy6Pbs4rqQ/PokPEfJkVx1MDAxNmZItverl/Xzx+p1VJSrsF15Ka5cdTAwMDdPXHUwMDBmKUeo8MBQyjvuvbFGXGJlhyCqXHUwMDE5uk+Jt4JcdTAwMGIlVXrlvMd15rhbOlwiXHUwMDFhN1x1MDAxYSNldVx1MDAxZeeNXHUwMDFimN6IXHUwMDFlXHUwMDFk7LlORl2Is+fb+pPblFFxO0g5RDVHeKBcdTAwMTDG+ZVcXOEkXHUwMDBmx0Q9s1x1MDAwNkU9UlRLe6PphaixWnprli2T2SeniaI6VJTEO71cdTAwMTHdKIb11sNhcVx1MDAxM8Tu6snd1t3TeniSeoSSm0cmylx1MDAxZJohwVx1MDAwN21oLyaKK1x1MDAxNc2r9lap9KaOXGKJy1x1MDAwN7XD0lx1MDAxOVGf3LJcdTAwMTGZl6e9lult6Lrbe8zXNtZfwpuwWatcdTAwMGLYutm6TzlC0Xoyjnpd4uyjXHUwMDFi5+CGIUo7o15cdGNwduI8L3VcdTAwMTjVVJ0q49ssy4FRpVx1MDAxMneWkHvR1OhcdTAwMTlcdTAwMDR99bGws7lcdTAwMGZh96q4vul2XHUwMDBicLJcdTAwMTckZThccoFtXHUwMDEwosPJmO+vKlx1MDAxN9rVYFx1MDAwNoy6XHUwMDBm4/YgKeAkjXZcdTAwMTR3XHUwMDFhjttTbbJcdTAwMTVUUumMkVx1MDAwM7tcdTAwMTifwGjUKtTbzUJcdTAwMGIxMIpTYzSz1oFxXHUwMDAyvDKxOv53nFwiQlmvo5QxSJ7FuJZjVGfmpJ5cdTAwMWZO3y70gVx1MDAxNZvvhrvaPrySK83z0/Z5pdbydZvtN+9cdTAwMWFAYaHVajz9fL/y15+T3jd7uX/LXHUwMDBiXHUwMDE39qB6WuJHzWJcdTAwMTTumZ3p3vftf8mri3J+wdiFrC5cdTAwMDEx2jhcdTAwMWOMXHUwMDEwTkol4onnXHUwMDFmNlC5rlx1MDAxNKKbo8PTRqaqiqXN09MtnVx1MDAxNIz43PL6jl5+2oIxXHUwMDE2NMo844fqXHUwMDAxXHUwMDE1MKWF8r1Cpngs6jNcdTAwMGWgUqmMriqlgVx1MDAxOUr+kqQzQcDoqkLPxDxVMqDYtFx1MDAxMsyYQISXVHNrXHUwMDE2tKouy+dr8qHycFA99NY2Vu1R9mwu6EfB4rXQseLgb+U/k5pcdTAwMDc5IHFcdTAwMGbTp3650vZevVSMoue9R7ArL+V93emmfTvDXHUwMDE4plx1MDAxNDWpRHBro61cdTAwMWIk6VZcdTAwMGKmXHUwMDFjte9G/jtQ0p02XHUwMDAy5ITSXFwv3Z6wh0SIWrxXg61cdTAwMTE/QujOSu7xKbzL6fzV/vrmak6vupNS2lx1MDAxMYpcdTAwMDaBSWucorZcdTAwMTZcXEg9XHUwMDAyUC6s5EhcdTAwMDSpnjvNXGadci+8WTqG7lVcIoUwXpLCXHUwMDE300NcdTAwMTTOt7Ln3Vx1MDAwN7e9una696gvQ7W2/YtbsE2zJyyZQ9vjkX57591wQ2AtmaJiQ/Dou9Pcm8XTXHUwMDBlPjqBZbOiXCLeXHUwMDAxd3hX2FxugPj1jyDafV65XHUwMDBiXHUwMDBi4cZu/qSbz5/7YrhcdTAwMGatz5Dc4Vxmq28luZzjPUd+6Vxy5XJcdTAwMGZ5eWS5XHUwMDBlXHUwMDA1JFx1MDAwMtRS8sJ3kFxcx4zGMShqXHUwMDAwZ/RcdTAwMTjpKIhpK4GKXHUwMDAzqEuMiFfqvKUsSOBK6Tnutk1cIrlSdp/32m1x+qTrYvf5aNef1trTkdyJ0vH7yDNJRyVNrFx1MDAwZtFcdTAwMTfXVFx1MDAxMIa1Znv8ilJcIrmBsfZcdTAwMWW4nyXRYi2z/bJzepBT3SPVvs03N59cdTAwMGXDpODhxDW1OKNvkXjQXHUwMDExXHUwMDFhhvqqxrtcdTAwMTa+mnyDtFx1MDAwNDVcdTAwMTlqNvfF4vJKweCkjq4pko24ZPH1XHUwMDFhmVH8sI73NVx1MDAwNehcdTAwMTOk69Vm9trVq+ElJbzUvb6h89t8+aY1NVx1MDAwNfa1NzG59p3YTz5cdTAwMDLBeGOUniVu/sxrV9XHXSfE01634nObnc6lTDf0XHUwMDFkmmoqSrdcYm2qtlx1MDAxOe4uj4SdWimCV4iuL+a6V1xuRXRcdTAwMWLfg37qbWnNPGORXHUwMDBiozuT4KllYqY7XG55ZOTxhidcdTAwMWZ2nO1cdTAwMWWdn1xc3rTr2Vb9zJrM/Y6BzXTD01uGXHUwMDA2U0nuPNdeXHUwMDBlgVMxwFx1MDAwNSxcdTAwMWMlwVx1MDAxOfe19klCXHUwMDE2XHUwMDAxxpDxeYBTalxyQs4zpJdcdTAwMGUu7pNcdTAwMGbnXHUwMDEwyminZypcYn7Y2axtXHUwMDEzcXBXL/XOirx7Upu/uMHXXHUwMDE0dNxcIlx1MDAwNr3qKUZj4seS9XaE6Fx1MDAxNC7KVUVcdTAwMTGHbi3F7ZOEkJb2epauf1K8XHUwMDFm0MimI46EXHUwMDBiN0MpxqZ7qDm/W2msXHUwMDFkrF1GK7X7YG/9XHUwMDE3N+2eLqbhlTGAupFcIr+DPp5601CNXHUwMDEw6jRKtUpzjiaOTjngv6OTn9xYPrlnN5F6QPc3Q1x1MDAxNvFxtqP2Xe5hnW+s7u095lZlYkFbSry8cVxmuSVaSXS0YOKHi/bS34xEI2pcdTAwMWR1i6QjN7+Ez1JQ1uXCKD5cdTAwMTUyXeW4clZah4slXHUwMDE29+uHNahdI3ikXHUwMDFiXHUwMDE0XjHW2mGE9qJcdTAwMWVOiflcdTAwMDXd3i6MlWB201x1MDAxY5xl1ctcdTAwMGVcbteN64xZ27o+OJ6LXHUwMDA0M1xcgOYm5ry/N3cpXHUwMDE5/lxiXHUwMDA2UGZcdTAwMTaSXHUwMDFiZFxu4uRcdTAwMTai5sWzWt3oVvaz66lnXHUwMDExyCCYk9bQ8Uh0XHUwMDA0iFx1MDAxY23NxLmlY+gsTotMb4c76KWU2KWz0GBcdTAwMTNTl0BcIren7phTQ/QqbK+oze76+Vx1MDAxNc9cdTAwMTVcdTAwMGWti1xumysq3Vx1MDAxNlx1MDAxYWUms1x1MDAxMkWWXHUwMDE2XHUwMDBlXHUwMDE1l4WhXGZlo5mnXHUwMDFlxpwyXHUwMDE34GtKTJdsUFx1MDAxOVx1MDAxMyZcdTAwMTDeM4WKV3hJPaL4mOxcbiFcdTAwMTlCV1rq5lx1MDAwNtrH2/C918rhJzBmXHUwMDAx5yTSXHUwMDFlJnWlXlx1MDAxMM2ViVJMO4nuk8/QXHUwMDBl/Oqhe3B8tVLYjfZW99TFXHJf21x1MDAxMOnfXFyWilFcIrKhPHmQhlx1MDAwZndcdEVcdTAwMDSDVkohkzDxM/vSZkNcdTAwMDXFOpxbvrZccrhukzCKRkNcdTAwMTL9miFBLVqrXHUwMDFkVGvtqlup6M1G5rbmzoNi2kGKPp6qjdCLK6c1QnFcdTAwMTiknkluXHUwMDExq1x1MDAxNC6K979LXHUwMDFiSHGmXHUwMDA0UCrrkmFcdTAwMTRsYlx1MDAwNoTstb6JXHUwMDE3t3yYorx+5neei2vXXHUwMDA1ffTSvCxcdTAwMDTHpeukQ7tT4uhcdTAwMTVYJiVyUaD84/jhur2Qq7Gokjxcblx1MDAxY8Wt4v5r+EzSYkIxTYmz1lCCpPWxvNa+p/eajlx1MDAxYXNcdTAwMDJcdTAwMTmJljYukPtp9I7aNsyx2fLbhbFibDtfs+FZ/SVqXHUwMDE3+NXa8cHGXfelMTcxZlFzLqqkObnfOKWpcWvMXGZUV2yWSt3cYe14k992/MvmmdxIOio0NStcdTAwMDCYsFJcdTAwMGKiu9xcdTAwMGI7SHUtSjXllPROOan813L0k7kudZMmmlx1MDAwYlxidCpcdTAwMDdcdTAwMTljo0FSM0pOJZK9ipZcdTAwMTGuS92mqX3F91fjXHUwMDEx15U4XHUwMDA2mFx1MDAxN0SjoFx1MDAxYo1Dp0guuLcoxJw0M+RQXHUwMDFll7NcdTAwMGaHNydmq3nz1L3PRrdB2z2nu4JEo1x1MDAwNqLKXHUwMDEwiSSCmrTYwexcdTAwMWanqdaZXHUwMDFiytn48knLY7N/pFx1MDAxOYNENVx1MDAxMlxiQ5uNLntRgbDvKlxyOd88tqVMqVx1MDAxMOZbqttdy+fyXHUwMDFkeTqnXHUwMDFjXHUwMDA3a4FcdTAwMWHMzbBg+sBs1KPT2kuPk8DAo5uF+1r4PFx1MDAwMK7eSsJcdTAwMDE+1lpRp1x1MDAxMF638YXsbeZi099cdTAwMGVwXHUwMDAwvXc0XHUwMDAzL11cdGs3tPB+hkFlcEVGNbwx75ejRmxcdTAwMWKkhEMp4Nu1dsrDXHUwMDFmqdGq3dTqhfBswrAmWobXXHUwMDFiPcY0qGRcdTAwMDVsUPRJZ2do8D5cdTAwMTlRvyAx8EPLgHqXIaq0RFx1MDAwNoe0x1x1MDAwZtU/OmVcdTAwMThcdTAwMWRcdTAwMTVcdTAwMDKoK5A1qa+FaMYnXHUwMDA2Mo5uXHUwMDExXHRcdTAwMTeKbE6pSGO2yqXWzDvjKTlQXHUwMDAzWDFiOchjOVx1MDAwN8BcdTAwMTdRQz57fVbstlx1MDAxNVrRaq1ertVv8GLfevxcZl5/9c5cdTAwMTQ+hj5cXKH5Snkpzi3wznhUO7FccqHeyi516INkOEPeTWeaeeWIXHUwMDFmg3x70rtcdTAwMTn7XHUwMDE51MtcdTAwMWZcdTAwMGZqcunk+6CUYF5cdTAwMGLA2TBC+T4vjFx1MDAwZkkwIZRGLWtcdTAwMTRSdcpcdTAwMTRcdTAwMWFcdTAwMTlSWGhHa437+1qEt/6oUatHw7e4dy9XaGFXg8KIycCPXHUwMDE0vzZsXHUwMDAxmvSOgy6i/79cdTAwMWb9VdL74f3///5z7LMzySDuXVx1MDAxZcVv/1x1MDAxZP+I/zuz+Vx1MDAxMsl7gJRXIES8n/xH5muy40qn+fJcZjWlprx1izLbXGbtgCAp59zRXqjzgsM3XHUwMDE073FmkDUhrzLcoHBcdTAwMTRuTGKz4pJJS6ebW0F9OmDUfFx0OqtcdTAwMWVF1OJaYMyySTd38/W3IeBcZsCTm/WANpUqmEfMXHQwXHUwMDE0j6jZXHUwMDA1XHUwMDAwiXbrJ1x1MDAxYq/BT/FcdTAwMWLZkGQg0dcohGa0IEnSKN7mdSRcdTAwMTlcdTAwMGIlXHUwMDFhmvJcdTAwMTm0USP3fHHvXHUwMDFhQbOUvWxcdTAwMWbqrjeZ809lXHUwMDExLE5cdTAwMWKBZ85cdTAwMWJUzdqgbOZ8MIuAKlx1MDAxZZmmykdcdTAwMTT4SukvxlbHiyM9lTiiNFtnYI7ng7xDaL717Vx1MDAxM8XR9Vx1MDAwM1L8+7Xyzfo1+svts6PDnVJ1buLIU6erXyOOXmczbdrodVSf41x1MDAxNtIkXHUwMDE3RyPRpqyq6bnFZDylkVsojayOTrKmPHdDnVx1MDAxMIYsg5RcZijjhfr/qa+eKzTWMlxiblx1MDAxOeCvRiaNXGLXmo8xXHUwMDE0glkklVx1MDAxNlx1MDAxZCN1/Fx1MDAwNFx1MDAxZlx1MDAwYp78XHUwMDFkVXHguFrEtuD3XHUwMDBio8lcdTAwMGXmnTR4Jr1z0jrR61bIx4pcdTAwMTDOOEXDKHeGU1xuKzrdydzii8JIXHUwMDAyXHUwMDEzkvJcdTAwMGLQzzhFp1x1MDAwMY2XRtxz2shQlFx1MDAxN2Hsby6NkiFMX5lR9M7Ia5KVkUtsVO1cZkphcGL6XHKJyV4rjdbLaUat/7g0mpuB3npvxssxXHUwMDA3XFxcdTAwMTmr8Vx1MDAwM1x088VcdTAwMDK1scbLoMpcdTAwMDdLMFx1MDAwNlxcYvFGXHUwMDFhcePluFx1MDAwNuetoj6VOJBcdTAwMTHjhbBcdTAwMTB+UUdIf5o/zMt8xYxcdTAwMTOdriyRmSrPgbpcdTAwMWXEYztv4Vx1MDAxZkOnNtGhTFx1MDAwMqRQ2n9gv35bbZRJxtLr5Vx1MDAxMVx1MDAxOM1oRVx1MDAxMtVRcvNccrQggJiZ4cyQo+rWUzfMZ5+fX9aMWslqU6+s/0oj4j/sPKaYRNugnNCSmuPBUCtxo/DOa6Q+YD1Y578hvjKtOOpcdTAwMWRY4+epjpJ3LSVcdTAwMTfcwSxF1l/TXHUwMDE5qCQqrF1qNcLwulGptINU7MGMXHUwMDE51ec8tUruQCY5rjCNXHUwMDBlavpChslcdTAwMTXGaXTVXHUwMDE5Z5hQRijK01x1MDAxNsrqwdRcdTAwMDHteW/z1oLUSGrENywyIVx1MDAwNdOW2kOgXHTt9VxmXHUwMDFl46qtYo6OonZcdTAwMGX9kVWjPcgknXfi9GKO5cH7IO3nNkKHfPWwV/vYi0/uVVx1MDAxMPfimjpagESqidxcdTAwMTZvT1xch7z7cOWpM1xmd8oohbrg96b7iUjqXVx1MDAxZMHQnPy0SDYhXHUwMDFhx6GUnKFcdTAwMTTq5OLusH7zeF677VxcZVx1MDAwYkadlcVumG4/LYVjSmo6hJvjXz90bpK1llx0R2RcdTAwMDX1p+P8+zqEitjin+SuUVx1MDAxMnrkXHUwMDBic0yof0fS4ttcdTAwMTXSWVSLI1x1MDAwM69cdTAwMWX3OVx1MDAxNVx1MDAxNODvsXzO8Us9oW+YXHUwMDAzXHUwMDA3Wk5cdTAwMWZgnDzHqVxctVS+JdA90KG64NRQ0ixIRl3ikVx1MDAwMCtJNuz71iyyeFx1MDAwZd6j4DRcdTAwMGVoK2SM93eU32tcdTAwMDVcdTAwMWRhKfGvXHUwMDFiSVx1MDAxYlx1MDAxNFx1MDAwZXmxp4rLXHUwMDA1MPAvLLoplfpkJ/AjvoUpPWhqXHUwMDBlj5ZXcydjXHUwMDFl729cdTAwMWavXHUwMDE53jlAfY5cdTAwMDJWXHUwMDE56z6ZgDG5ue7AmEBcboppXG5cXEdIy5xcdTAwMWRcdTAwMWSTY9yjx0D00amCyrrfm3ckYpi+MqPwnVx1MDAxM/GQMnn7VKBittrPcFxuiTzKtNbD41x1MDAxNd9ccnNbudr9bl5cdTAwMDCk24YpYPhcdLmm1sXUXHJg6JhcdTAwMWPnXHUwMDE4OXque7Hfr5X4TTZiMSoxkXj0XHUwMDBlRpvjbsgk3vG9pdZcdTAwMTStXFw47+imiHd0P8k7dKxX2/CilVx1MDAwNmVcdTAwMTh+n554TJ7kNC5aScFUw42WXHUwMDAyXHUwMDE1XHUwMDE5wNCapbPU0U2hwtbKf/HIi4lrVlx1MDAxOdpg7Vx1MDAxZIntpIiXXCL0eVx1MDAwN/pO6o/EXHTz1nM1skEgnCCnOs+k8Y9cdTAwMTbdd6ZOTXZcdTAwMDE/XHUwMDA2ty+N94pcdTAwMDKf6OC1pLlcdTAwMWFx857R0V9KoS9CTT66lThcdTAwMTXvmNxcdTAwMWIkNibqNyC5kFx1MDAwMII6p+uR4YxuUPxOJCNcdTAwMTGv9JVcdTAwMTmF6pw4hpqUosV7jVx1MDAxZvz0u1x1MDAxMFx1MDAxYnZrrds5z8NcdTAwMTU/vburVc15mEs3x5BcdTAwMWFcdTAwMThcdTAwMDWNXHUwMDE0cmjq56JcdTAwMDZcdTAwMTMxPFx1MDAxNaZSQqOToMxcdTAwMTerXHUwMDBiJ3NcZlx1MDAxOFx1MDAxM9yIn/D7VkAl8Ylaz/Fg3UlcdTAwMWPjOytcYmUvM2LxXHUwMDFj48d//V/9dXthcmaVXHUwMDFkfItF0Y5xw/tcdTAwMWNcdTAwMTORyUlcbsiqXHUwMDA1R188fWn7ZCSkcWXjckX14KhpunA8nqree7mhsjTtKDxuuVx1MDAxNMPFnHMkXCKWec1cdTAwMDWdr+SohL5/XHUwMDAz+kSE07neXG7QyjurjLCjlcOKXHUwMDFhTlx1MDAxMn1cXFx1MDAwMFx1MDAxMfnCypySiEz2XHUwMDEzQ0TEgpV0hCO3qPJcXL92+t3xO0b1XHUwMDFlkvZcdTAwMDU0UIX455jI5NL4wUFR0p7GgTlJJ7v50bCMYeg9fS/tg3tcdTAwMDD9e3OTJFxi01dmXHUwMDE0vUnc5I+33/Cz0GyeRoi599lANNfKbya9/zF/PtaCp9XxXHUwMDFijbTX+MfbXHIl81x1MDAxM9CH/c9ff/z1/1x1MDAxNXZx8iJ9 - virtual_size.heightvirtual_size.widthscroll_offsety=0 + virtual_size.heightvirtual_size.widthself.scroll_offsetscroll_yscroll_xscroll_x +self.size.width From fb7c1642bcd8e54f6d402a13163af7b82a603d76 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Sat, 4 Feb 2023 15:40:36 +0100 Subject: [PATCH 83/93] Checker example 4, docs and diagram --- docs/examples/guide/widgets/checker04.py | 12 +++---- docs/guide/widgets.md | 41 ++++++++++++++++++++++++ docs/images/scroll_view.excalidraw.svg | 6 ++-- 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/docs/examples/guide/widgets/checker04.py b/docs/examples/guide/widgets/checker04.py index 54f2d7522..1243a3e6d 100644 --- a/docs/examples/guide/widgets/checker04.py +++ b/docs/examples/guide/widgets/checker04.py @@ -26,11 +26,11 @@ class CheckerBoard(ScrollView): background: #004578; } CheckerBoard > .checkerboard--cursor-square { - background: darkred + background: darkred; } """ - cursor_square: var[Offset | None] = var(None) + cursor_square = var(Offset(0, 0)) def __init__(self, board_size: int) -> None: super().__init__() @@ -44,7 +44,7 @@ class CheckerBoard(ScrollView): self.cursor_square = Offset(mouse_position.x // 8, mouse_position.y // 4) def watch_cursor_square( - self, previous_square: Offset | None, cursor_square: Offset | None + self, previous_square: Offset, cursor_square: Offset ) -> None: """Called when the cursor square changes.""" @@ -57,12 +57,10 @@ class CheckerBoard(ScrollView): return region # Refresh the previous cursor square - if previous_square is not None: - self.refresh(get_square_region(previous_square)) + self.refresh(get_square_region(previous_square)) # Refresh the new cursor square - if cursor_square is not None: - self.refresh(get_square_region(cursor_square)) + self.refresh(get_square_region(cursor_square)) def render_line(self, y: int) -> Strip: """Render a line of the widget. y is relative to the top of the widget.""" diff --git a/docs/guide/widgets.md b/docs/guide/widgets.md index 96b9cbff1..9470d123f 100644 --- a/docs/guide/widgets.md +++ b/docs/guide/widgets.md @@ -339,3 +339,44 @@ We also need to compensate for the position of the horizontal scrollbar. This is
--8<-- "docs/images/scroll_view.excalidraw.svg"
+ +### Region updates + +When you call the [refresh][textual.widget.Widget.refresh] method it will refresh the entire widget. +The Line API makes it possible to refresh parts of a widget, as small as a single character. +Refreshing smaller regions makes updates more efficient, and keeps your widget feeling responsive. + +To demonstrate this we will update the checkerboard to highlight the square under the mouse pointer. +Here's the code: + +=== "checker04.py" + + ```python title="checker04.py" hl_lines="18 28-30 33 41-44 46-63 74 81-92" + --8<-- "docs/examples/guide/widgets/checker04.py" + ``` + +=== "Output" + + ```{.textual path="docs/examples/guide/widgets/checker04.py"} + ``` + +We've added a style to the checkerboard which is the color of the highlighted square, with a default of "darkred". We will need this when we come to render the highlighted square. + +We've also added a reactive variable called `cursor_square` which will hold the coordinate of the square underneath the mouse. Note that we have used [var][textual.reactive.var] which gives as reactive superpowers but won't automatically refresh the whole widget, because we want to update only the squares under the cursor. + +The `on_mouse_move` handler takes the mouse coordinates from the [MouseMove][textual.events.MouseMove] object and calculates the coordinate of the square underneath the mouse. There's a little math here, so let's break it down. + +- The event contains the coordinates of the mouse relative to the top left of the widget, but we need the coordinate relative to the top left of board which depends on the position of the scrollbars. +We can make this conversion by adding `event.offset` to `self.scroll_offset`. +- Once we have the board coordinate underneath the mouse we divide the x coordinate by 8 and divide the y coordinate by 4 to give us the coordinate of a square. + +If the cursor square coordinate calculated in `on_mouse_move` changes, Textual will call `watch_cursor_square` with the previous coordinate and new coordinate of the square. This method works out the regions of the widget to update and essentially does the reverse of the steps we took to go from mouse coordinates to square coordinates. +The `get_square_region` function calculates a [Region][textual.geometry.Region] object for each square and uses them as a positional argument in a call to [refresh][textual.widget.Widget.refresh]. Passing Regions to `refresh` tells Textual to update only the cells underneath those regions, and not the entire region. + +!!! note + + Textual is smart about performing updates. If you refresh multiple regions (even if they overlap), Textual will combine them in to as few non-overlapping regions as possible. + +The final step is to update the `render_line` method to use the cursor style when rendering the square underneath the mouse. + +You should find that if you move the mouse over the widget now, it will highlight the square underneath the mouse pointer in red. diff --git a/docs/images/scroll_view.excalidraw.svg b/docs/images/scroll_view.excalidraw.svg index 0d3ba66a8..4dfc06120 100644 --- a/docs/images/scroll_view.excalidraw.svg +++ b/docs/images/scroll_view.excalidraw.svg @@ -1,6 +1,6 @@ - + - eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nN1da1NcIsuy/b5/xcScj3dTp96VdVwibtzw/cQ3Prj3hMGjXHUwMDExtFx1MDAwNYRG0Vx1MDAxM/u/30x0S/NoXHUwMDA0RaZcdTAwMDdnwlx1MDAxOWnAomtV5lpZmVn/+ePHj5/RczP4+a9cdTAwMWY/g26pXHUwMDEw1sqtwtPPP+nxx6DVrjXqeEn2fm43Oq1S75nVKGq2//XPf95cdTAwMTdad0HUXGZcdTAwMGKlgD3W2p1C2I465VqDlVx1MDAxYff/rEXBfft/6PtB4T7472bjvlx1MDAxY7VY/5dkgnItarRef1dcdTAwMTBcdTAwMDb3QT1q47v/L/7848d/et9jo2tcdTAwMDWlqFC/XHSD3lx1MDAwYnqX+lx1MDAwM7RCXGY/etCo91x1MDAwNmtcdTAwMDRcYrDGqPcn1Nrr+OuioIxXKzjkoH+FXHUwMDFl+nm6X1x1MDAxMfXgtntcXFrNtM787bZpXm/1f2ulXHUwMDE2hqfRc/h6J1xupWqnXHUwMDE1XHUwMDFiUztqNe6Ci1o5quJ1MfT4++vaXHK8XHT9V7VcdTAwMWGdm2o9aLdcdTAwMDde02hcdTAwMTZKtehcdTAwMTlcdTAwMWZT/P3B13vwr1x1MDAxZv1HuvhTXHUwMDA2JDNSXGIvXHUwMDE0SK3k+0V6tVx1MDAwNmDaOKe4kpJ7I9TQsNZcdTAwMWEhTlx1MDAwNFx1MDAwZetcdTAwMWa899VcdTAwMWZYsVC6u8HR1cv950hcdMXA9p/z9PZhrWVcXDtntNRcdTAwMDYs9/r9XHUwMDE51aB2U43wKU4yUMZya7nkXHUwMDAw0Fx1MDAxZmc76M2HXHUwMDE0Smu87t37XHUwMDE1+vXNnXJcdTAwMGZcdTAwMWH/jt+xevntjtU7YdhcdTAwMWYxXdiIwan/mk6zXFx4nXZhcZjaWqO89u/Xw1r9bvjtwkbpro+U3qN//flcdIQ6gCSEXG6rXHUwMDA1N8JyNzVE13OVu9vuysFDVl1cdTAwMWOfVs5cdTAwMWXzufWVlEPUWCaMdlZp6Vx1MDAxNN5cdTAwMGVvR0GqlDdKIE6VXHUwMDEzNrUgxeFr77k1S4ZR62RcdTAwMTJGvZPOOyfF1Fx1MDAxMFxyzrZ38e5cdTAwMTWLZX1ylN2Bvb2jO51yiGaEYlxceDBeOHRcdTAwMWFcdTAwMDBiXHUwMDEwo8Yqpi1wvCQk6OFxpVx1MDAwN6HCKFx1MDAwNfhHLFx1MDAxYkKtT0Ko4Fx1MDAxYX1cdTAwMDc6uOlcdTAwMWT95WHxYO/KQLFxdHxfuHzcRn7T/rVcdTAwMTBcdTAwMTX8QzOqmTPIZyRY4dFzuFx1MDAwMYRa5Vx1MDAxOZJcdTAwMDDv0UJxo41MLUSlcE7jUJdcdTAwMGWikExFJTo9iHObj1x1MDAxMHqfyVx1MDAxN9RmaSV3mq/eXHUwMDA169Wjja1QpN2IgmDIQjUoK1x1MDAwMVdlzEz2XHUwMDEwqjlDXHUwMDBiqo1cdTAwMTZcdTAwMWGXqzOpRahwOF1cdTAwMDZ9wNJxUZPMRVx1MDAwMY1cdTAwMDbeL+Gnxqh3XHUwMDA1a8LG7W2nlFx1MDAxM9dhp9w4dFx1MDAwNynHqJCGeYuk21x0hICPXHSiVzdvkIpcIlx1MDAxZlx1MDAwN6k0V7H5T1x1MDAxZER7r1x1MDAxNchXXHUwMDE2XHUwMDA1UcNcdTAwMTdcdTAwMDNRp1x1MDAxMiFquHWOm1x1MDAxObho62W3uuI2XHUwMDBljNjeVkFm/WXzqHqedkePc4ueXHUwMDFlOZyTXHUwMDA2/zUwKOpcdTAwMTG5TFx1MDAwMmp5a6X2UqaZjHJcdTAwMTDgOF8+kNpEkHortOFuejO6ooo+ut95gUZ1+3QjdyFcbuf1tEv6jGecK1xcjrhcdTAwMTjR43tcdTAwMDdDXHUwMDEwNUxJnFxyp1xyisf0XHUwMDAyVDiLmlx1MDAxZd9nyVx1MDAwMGpcdTAwMWRPXHUwMDAyqORCXGJcdTAwMDAuzdRcYl31uuGj6G612L6T97Ys2lf1ZspcdTAwMTEqvGFCO8GFVlx1MDAxNGSTg55eg0VscPyHW4n+Xuj0glQhfsDhq5dcZqSoXHUwMDE0kkDqrFx1MDAwMudR006N0b2wW12vqnw24kG4YZ+ew2b3LOVcdTAwMThVSjFkmtygI+dg5XDwXHUwMDFlIYomXHUwMDE2QeG4wmWb3rgorjFkI9yohVx1MDAwNUZcdTAwMTfl6GWio/fKI7nhfHqImruNsFiuPG6ePlx1MDAxYV7OX4bX9Y0w5Vx1MDAxMJVWM1xcirhcdTAwMTiFxvvOzVx1MDAwMESNXHUwMDE1XGZcdTAwMTC6WlJYSqbYhlx1MDAxYeuExPlaPoAmRu6VJf+mZ7Cha3f6Klx1MDAxMrflm9xBqXX+1FK5aqGQdrVERlRrb71CXHUwMDA0XGLBXHUwMDA3mShcdTAwMDWdhLTgaG9UgE0xRJ1Sgr4tXHUwMDFkRGPbm8NiSeJQXHUwMDE0LsxcdTAwMTmM6O3V9u6+y1x1MDAxZK7ooCF3XHUwMDBmz8+beZd2jKIoZFxiXHUwMDBlUNYjIVVqXHUwMDA0pJKhVkJcdTAwMDIgUTnFWFHqMIqLXGaQqCxuk35RVFQkXHUwMDBieuO1XHUwMDE2XHUwMDFh3PRRJ7m94k7C3Vr+ca15e3Mo7yv8Np9yR6+5YlaiLvRee4G6fcjRI1xykFxiXHKuwEu0temFqDXgtDNu2cwoqMRcdTAwMWRQdIHOOWWnt6JcdTAwMDfR9WNN33XPm6tcdTAwMGb5leg5e3pS3fhcclx1MDAxMKqNoHwmg35SqkFFj/yOIW5p+oFTvklqIUqpMM5cdL10XGLliVxcVChtaNOFT79Hf7X/tFWsdithXHUwMDA3Lnbq/mKtUT+8SrujR6bJrFx1MDAwM4lKSVgwUrshjDrGJeDMIHgokSi1XHUwMDE4ldwrNKPo/JZcZqRxXGJcdTAwMGVHRilcdTAwMWNMSnZ6O1x1MDAxYV695Fx1MDAwZm3JXHUwMDFj39unenS4d3/bujxOuVx1MDAxZM1cdTAwMDBKXCLLlVx1MDAwMKUoZjNcdTAwMDRRz5lcdTAwMTNcdTAwMTZcci0uV1x0Or2CSVx1MDAxYiW9VVotXHUwMDE5QsHoJIRcbpDOaS9nSFx1MDAxOL15Km2e19pcdTAwMDKuc8+7taq2p/mNX1x1MDAxY1x1MDAxN51cItXJoVx1MDAxZVJIylx1MDAxMYOgIY6PV4xcbmRcdTAwMDLW49RcYlT8MVx1MDAxYpU2jOJFXHUwMDAwpWHpNkCTMUrcxlo7/e5S0K7o0tnZntlrrjVtPtupblx1MDAxNLfTbkNcdTAwMDVnyDQ9WCFcciXcxXxcdTAwMDa9XHUwMDAxIHhQRqHYR8mM2EkvXHUwMDE5XHUwMDA1XHUwMDBijvtlo6JcdTAwMGWSs0gkTlx0cjA1vZ73PKxGneKWXHUwMDBi9ss1nr0sdTL7jZQjXHUwMDE0TShcbkLpXHJyUePju730cq8581riWtWSq1x1MDAxNKeQoI5HKqKWL25cdTAwMWZcdTAwMGInjdBQXHScWzmDXHQtNqriprW7d/+yenR/89jcPbzcyKZcdTAwMWOgXHUwMDE5XHUwMDA3zKGrQP9ccsg00ZBcdTAwMGUhVDHS+OCk8Vx1MDAxNmR6s0UlRzLNpeBLR0RV4v685oaD4jPY0NPsVebs7vJg57F01dk5XGbCp6fDx5RDVEg0ouhcdTAwMWNcdTAwMWSXiE87tPmJnlx1MDAxM5VcdTAwMTIqfVx1MDAxNFwi2juR3ox7J53xS2hE41x1MDAxZnV4d15q9G3KTlx1MDAwZtCNXHUwMDFkeLo536tnXHUwMDFmg+PcyupZ+bpTT3tIlFx1MDAwMEqln1x1MDAxZVxykKKakFx1MDAwMYBcIvs0jCuDRtZcdTAwMTht40l6aUOosPTH+OVcdTAwMTPzNpGIXHUwMDFhb7RA9j01Qlx1MDAxZreeosuTnZvsej5Y27292i1VXHUwMDBilylHaMYxjb5Ro7sw1jo/lMuMXHUwMDEwdVxmNH5xZHqopdJrRNH+S8f50mXhOZm4r1x1MDAwNFxcWSPFXGZcdTAwMTX0fv9O3EeZ7f3mw1G4tZepd831UdqjTcI7KqHn6OVcdTAwMTGqcdbzXHUwMDFhbJJMXHUwMDBi06u5621ipFx1MDAxN6FcdTAwMDLXXHUwMDEw18tcdTAwMTdcdTAwMTH1yVx1MDAxOSRegzbe++n9fL5WXHUwMDE1QSOzWatnczWT3d5q+KdfLOanSXKyXGZcdTAwMDFIVFx1MDAxNP29XHUwMDFm3lZCiCovgMAjadcmtVx1MDAxMLVWXHUwMDFhVHSwdEbUJ1x1MDAxYVGPQklRjcTUXGK9PD8uNfxx+eaidpI7M6b10lq9SLmfl5Z2NlGva42kXHUwMDE0yeZgxFx1MDAxZfCyRiGPcl9cdTAwMTmZZlx1MDAxYiqpwoxcYvOSITTeXHUwMDEyYKSAXHUwMDFlNS66PTu9n89d7ja3ajtbJ9nWzr45i04vzttp75SjlGEgONJQZKRcdTAwMWH8oFjyWjCgzFx1MDAxMqnI1ac4JCqUXHUwMDEy6Pbs4rqQ/PokPEfJkVx1MDAxNmZItverl/Xzx+p1VJSrsF15Ka5cdTAwMDdPXHUwMDBmKUeo8MBQyjvuvbFGXGJlhyCqXHUwMDE5uk+Jt4JcdTAwMGIlVXrlvMd15rhbOlwiXHUwMDFhN1x1MDAxYSNldVx1MDAxZeeNXHUwMDFimN6IXHUwMDFlXHUwMDFk7LlORl2Is+fb+pPblFFxO0g5RDVHeKBcdTAwMTDG+ZVcXOEkXHUwMDBmx0Q9s1x1MDAwNkU9UlRLe6PphaixWnprli2T2SeniaI6VJTEO71cdTAwMTHdKIb11sNhcVx1MDAxM8Tu6snd1t3TeniSeoSSm0cmylx1MDAxZJohwVx1MDAwN21oLyaKK1x1MDAxNc2r9lap9KaOXGKJy1x1MDAwN7XD0lx1MDAxOVGf3LJcdTAwMTGZl6e9lult6Lrbe8zXNtZfwpuwWatcdTAwMGLYutm6TzlC0Xoyjnpd4uyjXHUwMDFi5+CGIUo7o15cdGNwduI8L3VcdTAwMTjVVJ0q49ssy4FRpVx1MDAxMneWkHvR1OhcdTAwMTlcdTAwMDR99bGws7lcdTAwMGZh96q4vul2XHUwMDBicLJcdTAwMTckZThccoFtXHUwMDEwosPJmO+vKlx1MDAxN9rVYFx1MDAwNoy6XHUwMDBm4/YgKeAkjXZcdTAwMTR3XHUwMDFhjttTbbJcdTAwMTVUUumMkVx1MDAwM7tcdTAwMTifwGjUKtTbzUJcdTAwMGIxMIpTYzSz1oFxXHUwMDAyvDKxOv53nFwiQlmvo5QxSJ7FuJZjVGfmpJ5cdTAwMWZO3y70gVx1MDAxNZvvhrvaPrySK83z0/Z5pdbydZvtN+9cdTAwMWFAYaHVajz9fL/y15+T3jd7uX/LXHUwMDBiXHUwMDE39qB6WuJHzWJcdTAwMTTumZ3p3vftf8mri3J+wdiFrC5cdTAwMDEx2jhcdTAwMWOMXHUwMDEwTkol4onnXHUwMDFmNlC5rlx1MDAxNKKbo8PTRqaqiqXN09MtnVx1MDAxNIz43PL6jl5+2oIxXHUwMDE2NMo844fqXHUwMDAxXHUwMDE1MKWF8r1Cpngs6jNcdTAwMGWgUqmMriqlgVx1MDAxOUr+kqQzQcDoqkLPxDxVMqDYtFx1MDAxMsyYQISXVHNrXHUwMDE2tKouy+dr8qHycFA99NY2Vu1R9mwu6EfB4rXQseLgb+U/k5pcdTAwMDc5IHFcdTAwMGbTp3650vZevVSMoue9R7ArL+V93emmfTvDXHUwMDE4plx1MDAxNDWpRHBro61cdTAwMWIk6VZcdTAwMGKmXHUwMDFjte9G/jtQ0p02XHUwMDAy5ITSXFwv3Z6wh0SIWrxXg61cdTAwMTE/QujOSu7xKbzL6fzV/vrmak6vupNS2lx1MDAxMYpcdTAwMDaBSWucorZcdTAwMTZcXEg9XHUwMDAyUC6s5EhcdTAwMDSpnjvNXGadci+8WTqG7lVcIoUwXpLCXHUwMDE300NcdTAwMTTOt7Ln3Vx1MDAwN7e9una696gvQ7W2/YtbsE2zJyyZQ9vjkX57591wQ2AtmaJiQ/Dou9Pcm8XTXHUwMDBlPjqBZbOiXCLeXHUwMDAxd3hX2FxugPj1jyDafV65XHUwMDBiXHUwMDBi4cZu/qSbz5/7YrhcdTAwMGatz5Dc4Vxmq28luZzjPUd+6Vxy5XJcdTAwMGZ5eWS5XHUwMDBlXHUwMDA1JFx1MDAwMtRS8sJ3kFxcx4zGMShqXHUwMDAwZ/RcdTAwMTjpKIhpK4GKXHUwMDAzqEuMiFfqvKUsSOBK6Tnutk1cIrlSdp/32m1x+qTrYvf5aNef1trTkdyJ0vH7yDNJRyVNrFx1MDAwZtFcdTAwMTfXVFx1MDAxMIa1Znv8ilJcIrmBsfZcdTAwMWW4nyXRYi2z/bJzepBT3SPVvs03N59cdTAwMGXDpODhxDW1OKNvkXjQXHUwMDExXHUwMDFhhvqqxrtcdTAwMTa+mnyDtFx1MDAwNDVcdTAwMTlqNvfF4vJKweCkjq4pko24ZPH1XHUwMDFhmVH8sI73NVx1MDAwNehcdTAwMTOk69Vm9trVq+ElJbzUvb6h89t8+aY1NVx1MDAwNfa1NzG59p3YTz5cdTAwMDLBeGOUniVu/sxrV9XHXSfE01634nObnc6lTDf0XHUwMDFkmmoqSrdcYm2qtlx1MDAxOe4uj4SdWimCV4iuL+a6V1xuRXRcdTAwMWLfg37qbWnNPGORXHUwMDBiozuT4KllYqY7XG55ZOTxhidcdTAwMWZ2nO1cdTAwMWWdn1xc3rTr2Vb9zJrM/Y6BzXTD01uGXHUwMDA2U0nuPNdeXHUwMDBlgVMxwFx1MDAwNSxcdTAwMWMlwVx1MDAxOfe19klCXHUwMDE2XHUwMDAxxpDxeYBTalxyQs4zpJdcdTAwMGUu7pNcdTAwMGbnXHUwMDEwyminZypcYn7Y2axtXHUwMDEzcXBXL/XOirx7Upu/uMHXXHUwMDE0dNxcIlx1MDAwNr3qKUZj4seS9XaE6Fx1MDAxNC7KVUVcdTAwMTGHbi3F7ZOEkJb2epauf1K8XHUwMDFm0MimI46EXHUwMDBiN0MpxqZ7qDm/W2msXHUwMDFkrF1GK7X7YG/9XHUwMDE3N+2eLqbhlTGAupFcIr+DPp5601CNXHUwMDEw6jRKtUpzjiaOTjngv6OTn9xYPrlnN5F6QPc3Q1x1MDAxNvFxtqP2Xe5hnW+s7u095lZlYkFbSry8cVxmuSVaSXS0YOKHi/bS34xEI2pcdTAwMWR1i6QjN7+Ez1JQ1uXCKD5cdTAwMTUyXeW4clZah4slXHUwMDE29+uHNahdI3ikXHUwMDFiXHUwMDE0XjHW2mGE9qJcdTAwMWVOiflcdTAwMDXd3i6MlWB201x1MDAxY5xl1ctcdTAwMGVcbteN64xZ27o+OJ6LXHUwMDA0M1xcgOYm5ry/N3cpXHUwMDE5/lxiXHUwMDA2UGZcdTAwMTaSXHUwMDFiZFxu4uRcdTAwMTai5sWzWt3oVvaz66lnXHUwMDExyCCYk9bQ8Uh0XHUwMDA0iFx1MDAxY23NxLmlY+gsTotMb4c76KWU2KWz0GBcdTAwMTNTl0BcIren7phTQ/QqbK+oze76+Vx1MDAxNc9cdTAwMTVcdTAwMGWti1xumysq3Vx1MDAxNlx1MDAxYWUms1x1MDAxMkWWXHUwMDE2XHUwMDBlXHUwMDE1l4WhXGZlo5mnXHUwMDFlxpwyXHUwMDE34GtKTJdsUFx1MDAxOVx1MDAxMyZcdTAwMTDeM4WKV3hJPaL4mOxcbiFcdTAwMTlCV1rq5lx1MDAwNtrH2/C918rhJzBmXHUwMDAx5yTSXHUwMDFlJnWlXlx1MDAxMM2ViVJMO4nuk8/QXHUwMDBl/Oqhe3B8tVLYjfZW99TFXHJf21x1MDAxMOnfXFyWilFcIrKhPHmQhlx1MDAwZndcdEVcdTAwMDSDVkohkzDxM/vSZkNcdTAwMDXFOpxbvrZccrhukzCKRkNcdTAwMTL9miFBLVqrXHUwMDFkVGvtqlup6M1G5rbmzoNi2kGKPp6qjdCLK6c1QnFcdTAwMTiknkluXHUwMDExq1x1MDAxNC6K979LXHUwMDFiSHGmXHUwMDA0UCrrkmFcdTAwMTRsYlx1MDAwNoTstb6JXHUwMDE3t3yYorx+5neei2vXXHUwMDA1ffTSvCxcdTAwMDTHpeukQ7tT4uhcdTAwMTVYJiVyUaD84/jhur2Qq7Gokjxcblx1MDAxY8Wt4v5r+EzSYkIxTYmz1lCCpPWxvNa+p/eajlx1MDAxYXNcdTAwMDJcdTAwMTmJljYukPtp9I7aNsyx2fLbhbFibDtfs+FZ/SVqXHUwMDE3+NXa8cHGXfelMTcxZlFzLqqkObnfOKWpcWvMXGZUV2yWSt3cYe14k992/MvmmdxIOio0NStcdTAwMDCYsFJcdTAwMGKiu9xcdTAwMGI7SHUtSjXllPROOan813L0k7kudZMmmlx1MDAwYlxidCpcdTAwMDdcdTAwMTljo0FSM0pOJZK9ipZcdTAwMTGuS92mqX3F91fjXHUwMDEx15U4XHUwMDA2mFx1MDAxN0SjoFx1MDAxYo1Dp0guuLcoxJw0M+RQXHUwMDFll7NcdTAwMGaHNydmq3nz1L3PRrdB2z2nu4JEo1x1MDAwNqLKXHUwMDEwiSSCmrTYwexcdTAwMWanqdaZXHUwMDFiytn48knLY7N/pFx1MDAxOYNENVx1MDAxMlxiQ5uNLntRgbDvKlxyOd88tqVMqVx1MDAxMOZbqttdy+fyXHUwMDFkeTqnXHUwMDFjXHUwMDA3a4FcdTAwMWHMzbBg+sBs1KPT2kuPk8DAo5uF+1r4PFx1MDAwMK7eSsJcdTAwMDE+1lpRp1x1MDAxMF638YXsbeZi099cdTAwMGVwXHUwMDAwvXc0XHUwMDAzL11cdGs3tPB+hkFlcEVGNbwx75ejRmxcdTAwMWKkhEMp4Nu1dsrDXHUwMDFmqdGq3dTqhfBswrAmWobXXHUwMDFiPcY0qGRcdTAwMDVsUPRJZ2do8D5cdTAwMTlRvyAx8EPLgHqXIaq0RFx1MDAwNoe0x1x1MDAwZtU/OmVcdTAwMThcdTAwMWRcdTAwMTVcdTAwMDKoK5A1qa+FaMYnXHUwMDA2Mo5uXHUwMDExXHRcdTAwMTeKbE6pSGO2yqXWzDvjKTlQXHUwMDAzWDFiOchjOVx1MDAwN8BcdTAwMTdRQz57fVbstlx1MDAxNVrRaq1ertVv8GLfevxcZl5/9c5cdTAwMTQ+hj5cXKH5Snkpzi3wznhUO7FccqHeyi516INkOEPeTWeaeeWIXHUwMDFmg3x70rtcdTAwMTn7XHUwMDE51MtcdTAwMWZcdTAwMGZqcunk+6CUYF5cdTAwMGLA2TBC+T4vjFx1MDAwZkkwIZRGLWtcdTAwMTRSdcpcdTAwMTRcdTAwMWFcdTAwMTlSWGhHa437+1qEt/6oUatHw7e4dy9XaGFXg8KIycCPXHUwMDE0vzZsXHUwMDAxmvSOgy6i/79cdTAwMWb9VdL74f3///5z7LMzySDuXVx1MDAxZcVv/1x1MDAxZP+I/zuz+Vx1MDAxMsl7gJRXIES8n/xH5muy40qn+fJcZjWlprx1izLbXGbtgCAp59zRXqjzgsM3XHUwMDE073FmkDUhrzLcoHBcdTAwMTRuTGKz4pJJS6ebW0F9OmDUfFx0OqtcdTAwMWVF1OJaYMyySTd38/W3IeBcZsCTm/WANpUqmEfMXHQwXHUwMDE0j6jZXHUwMDA1XHUwMDAwiXbrJ1x1MDAxYq/BT/FcdTAwMWLZkGQg0dcohGa0IEnSKN7mdSRcdTAwMTlcdTAwMGIlXHUwMDFhmvJcdTAwMTm0USP3fHHvXHUwMDFhQbOUvWxcdTAwMWbqrjeZ809lXHUwMDExLE5cdTAwMWKBZ85cdTAwMWJUzdqgbOZ8MIuAKlx1MDAxZZmmykdcdTAwMTT4SukvxlbHiyM9lTiiNFtnYI7ng7xDaL717Vx1MDAxM8XR9Vx1MDAwM1L8+7Xyzfo1+svts6PDnVJ1buLIU6erXyOOXmczbdrodVSf41x1MDAxNtIkXHUwMDE3RyPRpqyq6bnFZDylkVsojayOTrKmPHdDnVx1MDAxMIYsg5RcZijjhfr/qa+eKzTWMlxiblx1MDAxOeCvRiaNXGLXmo8xXHUwMDE0glkklVx1MDAxNlx1MDAxZCN1/Fx1MDAwNFx1MDAxZlx1MDAwYp78XHUwMDFkVXHguFrEtuD3XHUwMDBio8lcdTAwMGXmnTR4Jr1z0jrR61bIx4pcdTAwMTDOOEXDKHeGU1xuKzrdydzii8JIXHUwMDAyXHUwMDEzkvJcdTAwMGLQzzhFp1x1MDAwMY2XRtxz2shQlFx1MDAxN2Hsby6NkiFMX5lR9M7Ia5KVkUtsVO1cZkphcGL6XHKJyV4rjdbLaUat/7g0mpuB3npvxssxXHUwMDA3XFxcdTAwMTmr8Vx1MDAwM1x088VcdTAwMDK1scbLoMpcdTAwMDdLMFx1MDAwNlxcYvFGXHUwMDFhcePluFx1MDAwNuetoj6VOJBcdTAwMTHjhbBcdTAwMTB+UUdIf5o/zMt8xYxcdTAwMTOdriyRmSrPgbpcdTAwMWXEYztv4Vx1MDAxZkOnNtGhTFx1MDAwMqRQ2n9gv35bbZRJxtLr5Vx1MDAxMVx1MDAxOM1oRVx1MDAxMtVRcvNccrQggJiZ4cyQo+rWUzfMZ5+fX9aMWslqU6+s/0oj4j/sPKaYRNugnNCSmuPBUCtxo/DOa6Q+YD1Y578hvjKtOOpcdTAwMWRY4+epjpJ3LSVcdTAwMTfcwSxF1l/TXHUwMDE5qCQqrF1qNcLwulGptINU7MGMXHUwMDE51ec8tUruQCY5rjCNXHUwMDBlavpChslcdTAwMTXGaXTVXHUwMDE5Z5hQRijK01x1MDAxNsrqwdRcdTAwMDHteW/z1oLUSGrENywyIVx1MDAwNdOW2kOgXHTt9VxmXHUwMDFl46qtYo6OonZcdTAwMGX9kVWjPcgknXfi9GKO5cH7IO3nNkKHfPWwV/vYi0/uVVx1MDAxMPfimjpagESqidxcdTAwMTZvT1xch7z7cOWpM1xmd8oohbrg96b7iUjqXVx1MDAxZMHQnPy0SDYhXHUwMDFhx6GUnKFcdTAwMTTq5OLusH7zeF677VxcZVx1MDAwYkadlcVumG4/LYVjSmo6hJvjXz90bpK1llx0R2RcdTAwMDX1p+P8+zqEitjin+SuUVx1MDAxMnrkXHUwMDBic0yof0fS4ttcdTAwMTXSWVSLI1x1MDAwM69cdTAwMWX3OVx1MDAxNVx1MDAxNODvsXzO8Us9oW+YXHUwMDAzXHUwMDA3Wk5cdTAwMWZgnDzHqVxctVS+JdA90KG64NRQ0ixIRl3ikVx1MDAwMCtJNuz71iyyeFx1MDAwZd6j4DRcdTAwMGVoK2SM93eU32tcdTAwMDVcdTAwMWRhKfGvXHUwMDFiSVx1MDAxYlx1MDAxNFx1MDAwZXmxp4rLXHUwMDA1MPAvLLoplfpkJ/AjvoUpPWhqXHUwMDBlj5ZXcydjXHUwMDFl729cdTAwMWavXHUwMDE53jlAfY5cdTAwMDJWXHUwMDE56z6ZgDG5ue7AmEBcboppXG5cXEdIy5xcdTAwMWRcdTAwMWSTY9yjx0D00amCyrrfm3ckYpi+MqPwnVx1MDAxM/GQMnn7VKBittrPcFxuiTzKtNbD41x1MDAxNd9ccnNbudr9bl5cdTAwMDCk24YpYPhcdLmm1sXUXHJg6JhcdTAwMWPnXHUwMDE4OXque7Hfr5X4TTZiMSoxkXj0XHUwMDBlRpvjbsgk3vG9pdZcdTAwMTStXFw47+imiHd0P8k7dKxX2/CilVx1MDAwNmVcdTAwMTh+n554TJ7kNC5aScFUw42WXHUwMDAyXHUwMDE1XHUwMDE5wNCapbPU0U2hwtbKf/HIi4lrVlx1MDAxOdpg7Vx1MDAxZIntpIiXXCL0eVx1MDAwN/pO6o/EXHTz1nM1skEgnCCnOs+k8Y9cdTAwMTbdd6ZOTXZcdTAwMDE/XHUwMDA2ty+N94pcdTAwMDKf6OC1pLlcdTAwMWFx857R0V9KoS9CTT66lThcdTAwMTXvmNxcdTAwMWIkNibqNyC5kFx1MDAwMII6p+uR4YxuUPxOJCNcdTAwMTGv9JVcdTAwMTmF6pw4hpqUosV7jVx1MDAxZvz0u1x1MDAxMFx1MDAxYnZrrds5z8NcdTAwMTU/vburVc15mEs3x5BcdTAwMWFcdTAwMThcdTAwMDWNXHUwMDE0cmjq56JcdTAwMDZcdTAwMTMxPFx1MDAxNaZSQqOToMxcdTAwMTerXHUwMDBiJ3NcZlx1MDAxOFx1MDAxM9yIn/D7VkAl8Ylaz/Fg3UlcdTAwMWPjOytcYmUvM2LxXHUwMDFj48d//V/9dXthcmaVXHUwMDFkfItF0Y5xw/tcdTAwMWNcdTAwMTORyUlcbsiqXHUwMDA1R188fWn7ZCSkcWXjckX14KhpunA8nqree7mhsjTtKDxuuVx1MDAxNMPFnHMkXCKWec1cdTAwMDWdr+SohL5/XHUwMDAz+kSE07neXG7QyjurjLCjlcOKXHUwMDFhTlx1MDAxMn1cXFx1MDAwMFx1MDAxMfnCypySiEz2XHUwMDEzQ0TEgpV0hCO3qPJcXL92+t3xO0b1XHUwMDFlkvZcdTAwMDU0UIX455jI5NL4wUFR0p7GgTlJJ7v50bCMYeg9fS/tg3tcdTAwMDD9e3OTJFxi01dmXHUwMDE0vUnc5I+33/Cz0GyeRoi599lANNfKbya9/zF/PtaCp9XxXHUwMDFijbTX+MfbXHIl81x1MDAxM9CH/c9ff/z1/1x1MDAxNXZx8iJ9 + eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nN1da1NcIsuy/b5/xcTcj3fTp7Kyslx1MDAxZSfixFxy309866j3njCQh6AtIKCiJ/Z/v5nollx1MDAwNmxcdTAwMDRHnVx1MDAxZZiImZFcdTAwMDYsulZlrpWVmfWfP759+955aJa///Pb93K3WIhrpVbh/vuf8vxdudWuNep8Sfd+bjduW8XeK6udTrP9z3/847rQuip3mnGhWI7uau3bQtzu3JZqjajYuP5HrVO+bv+P/L1VuC7/q9m4LnVaUf+X5MqlWqfRevpd5bh8Xa532vzp/8s/f/v2n97fidG1ysVOoX5cdTAwMTGXe2/oXepcdTAwMGbQXHUwMDAyXGY/u9Wo91x1MDAwNkvgwVtcInx5Qa29yL+uUy7x1VxuXHUwMDBmudy/XCJPfd/frEC9fNndLc7nWlx1MDAwN+FylZpnK/3fWqnF8X7nIX66XHUwMDEzhWL1tpVcdTAwMThTu9NqXFyVf9RKnSpfh6HnX97XbvBN6L+r1bi9qNbL7fbAe1x1MDAxYc1CsdZ54OdQvTz5dFx1MDAwZv75rf9Ml3/KeVx1MDAxZJFcdTAwMDZcYoBeXHUwMDFi1C9cdTAwMTfl3cb7yJBzqFBrXHUwMDE1XGJwaFhcdTAwMGKNmCeCh/VfqvfoXHUwMDBm7LxQvLrg0dVL/ddo7c/Ltv+a++cva22kjHNktCFvVTAvr6iWa1x1MDAxN9VcdTAwMGW/xOnII1llrdLKe99cdTAwMWZnu9ybXHUwMDBmXHJoXGZfXHUwMDBm7uWK/PrmWqlcdTAwMDeNfyfvWL30fMfqt3HcXHUwMDFmsVxcWErAqf+e22ap8DTtYHmYxlrCYMLL9bhWv1x1MDAxYf64uFG86iOl9+xff75cdTAwMDOhzvs0hII1oFxirHJcdTAwMTNDdPGwcnXZndu6yeOP3f3Kwd3p4eJcXMYhSjZcdTAwMDIyzqLRXHUwMDBl+XZcdTAwMDQ7XG5SxEBcYoxTdGAzXHUwMDBiUlx1MDAxZb5cdEFZmjGMWqfTMFx1MDAxYZx2wTlcclx1MDAxM0O0fLC6znfv/Lxk9nbya35jY+fKZFx1MDAxY6I5wEhB8Fx1MDAxNMCx0/BcdTAwMWVcdTAwMDYxSlx1MDAxNiNjveJLoL1cdTAwMTlcdTAwMWVXdlx1MDAxMFxuhOj5XHUwMDBmzFx1MDAxYUJtSEMoKMO+g1x1MDAxZNzkjv54+3xr44T8eWNn97pwfLfK/Kb9ayFcbupNM2pcIkfMZ7S3XHUwMDEw2HO4XHUwMDAxhFpcZlx1MDAxMZOAXHUwMDEw2EIpMqQzXHUwMDBiUVxyzlx1MDAxOVx1MDAxZerMQdSnU1HNTs8nuc1bXGK9zp1cdTAwMTZwuTh3uH9avSovVneWVmLIulx1MDAxMfVcdTAwMTAxXHUwMDBiNVx1MDAxZa32vCpcdTAwMTNmsodQo1witqCGXGZcdTAwMThero4yi1BwPF3EPmDmuCilc1HPRoPvXHUwMDE3hIkxXHUwMDFhXFzBUty4vLwtXHUwMDFlwll8W2psu62MY1x1MDAxNDRFwTLpdsBcdTAwMTBcYlx0QfTk5ompKPNxr9EoTMx/5iDaey8wX/kqiJL6XHUwMDFhiDpMhSgp65yiKbho63G9OueWtlxiVlexnFt8XFzeqVx1MDAxZWXd0fPcsqdnXHUwMDBl5zTxv+RcdTAwMDdFPSM30p61vLXaXHUwMDA0rbNMRpVcdTAwMDfvlJo9kNpUkFx1MDAwNlx1MDAwYoaUm9yMzuF56Fxcrz36RnV1f+nwXHUwMDA3XHUwMDE0jupZl/S5XHUwMDEwKYW8XHUwMDFjeTGyx1x1MDAwZs5cdTAwMGZBlFwi1DxcdTAwMWLOXHUwMDEwi8fsXHUwMDAyXHUwMDE0nGVNz58zY1x1MDAwMLVOpVx1MDAwMVQrXHUwMDAw8F5pmlx1MDAxOKHzwTRCp3M1f96+0te2XHUwMDA07ZN6M+NcYoVAXHUwMDExXHUwMDE4XHUwMDA3XG5cZkqQTVx1MDAwZnp64y1jQ/E/ymr292CyXHUwMDBiUmT8eMfvnjGQslJIXHUwMDAzqbPoXWBNOzFGN+JudbGKp/mOKsdL9v4hbnZcdTAwMGYyjlFEjJhpKmJHrrzVw8F7hiibWFx1MDAwNoVTyMs2u3FRXmPMRlx1MDAxNOGXXHUwMDA1Rr/K0etUR1x1MDAxZjAwuVFqcojS1VJ8XqrcLe/fkSqdXHUwMDFlx2f1pTjjXHUwMDEw1dZEvFx1MDAxNHkxguH7rmhcdTAwMDCiZCHyXGZdoyUspTNsQ8k60Dxfs1x1MDAwN9DUyD1a8W9mXG5cdTAwMWK6cGVOOnBZujjcKraO7lt4WC1cdTAwMTSyrpbEiFx1MDAxYVx1MDAxM2xARlx1MDAwMIBcdTAwMWFkolx1MDAxMnRcdTAwMDJtvZO9UfA2w1x1MDAxMHWIIH/NXHUwMDFjRFx1MDAxM9ubw2JJ81CQXHUwMDE35lx1MDAxNEb08mR1fdNcdTAwMWRuz5lyQ69vXHUwMDFmXHUwMDFkNU9d1jHKojBicHi0gVx0KeJcYkh1xFqJXHSAZuWUYEWZwygvMs9E5es26b+KikK6oKdgXGZcdTAwMTjvJo866dU5t1x1MDAxN6/XTu9cdTAwMTaal1x1MDAxN9v6uqIuTzPu6I3CyGrWhSGYXHUwMDAwrNuHXHUwMDFjPdNcdTAwMDDN0FDog2Zbm12IWvLOOHKzZkY9pu6Aslx1MDAwYnTOoZ3cim51zu5q5qp71Jy/OZ3rPOT396pLv1x1MDAwMUJccoHkM1x1MDAxMftJjYOKnvldxLiV6fdK8k0yXHUwMDBiUUmFcVx1MDAwZczMIVSlclFAQ7Lpoibfoz/ZvF85r3Yr8a3/sVZcdTAwMGY/XHUwMDE2XHUwMDFh9e2TrDt6ZpqRdV6zUlx1MDAwMutJXHUwMDFiN4RRXHUwMDE3Ke15Zlx1MDAxODySSJRZjGpcdTAwMTWQzSg7v1x1MDAxOVx1MDAwM2lcdTAwMTKCw5FRXHRcdTAwMDeLkp3cjsYnj6fbtki71/a+3tneuL5sXHUwMDFk72bcjuY8S1wiq1x1MDAxMDyixGyGIFx1MDAxYVTkwLKh5eWqvcmuYDKEOlg0OGNcYvVk0lx1MDAxMFxuXjtngp5cImH04r64fFRrgz87fFivVY3dP136xXHRXHRSnVx1MDAxY+shZFLOXHUwMDE49MYn8fGEUWAmYFx1MDAwM09ccrDiT9iorGGUL3qPxs/cXHUwMDA2aDpGhdtYayffXSq3K6Z4cLBBXHUwMDFizYWmPc3fVpfOV7NuQ0FFzDSDt6BJXHUwMDEy7lx1MDAxMj5DPsAzeFhGsdhnyczYyS5cdTAwMTn11jtcdTAwMTVmjYo6n55FonlKmIPh5Ho+qLjauT1fceXNUk3lj4u3uc1Gxlx1MDAxMcomlFx1MDAwNaFcdTAwMGXEXFyUQnK3V95cdTAwMWWMioLRvFaNVpjhXHUwMDE0XHUwMDEy1vFMRXD24vaJcNJcYlxy1V4pq6cwoeeNKlxctNY3rlx1MDAxZud3ri/umuvbx0v5jFx1MDAwMzTnfOTYVbD/9sw02ZBcdTAwMGUhXHUwMDE0I9H43mlcbtbr7GaLasVkWmlQM0dE0yNOxoBiRVx1MDAwZpNDdD9/kju4Ot5auyue3K5tl+P7++27jENcdTAwMTS0i4zYSDDIgthcZvt4XHUwMDA2MLDSZyFigoPsZtw77Sh8iVx1MDAxMSVR0MaS+1x1MDAxYYD69LIlbXje0E7u5JfW/P3F0UY9f1fePZybPyid3dazXHUwMDFlXHUwMDEyXHUwMDE1gErpZ2BcdTAwMDOEUlx1MDAxMzJcdTAwMDBQZp9cdTAwMTQpJDayRMYmk/SyhlCw8ofC7Il5m0pEKZBcdTAwMDFm31x1MDAxMyP0buW+c7y3dpFfPC0vrF+erFx1MDAxN6uF44wjNMdcdTAwMTaUfSP7XG5P1rowlMvMXHUwMDEwdZE3/FDM9FhLZdeIXHUwMDAyX3BKzVxcXHUwMDE2ntOpXt4rtKRhilxu+rB5XHUwMDA1153c6mbzZide2cjVu3S2k/VoXHUwMDEzXHUwMDA0JyX0SmnNUPU4lMtcdTAwMWN0ZIB6NXe9TYzsXCJcdTAwMTR4XHIpM3tcdTAwMTHRkJ5BXHUwMDEyjDdcdTAwMTRCmNzPn9aqUG7klmv1/GGN8qsrjXD/i8X8JElONmJcdTAwMDBcdTAwMDZyXHUwMDEyUVxuw9tKXGZRXGbgXHUwMDA1PFp2bTJcdTAwMGJRazWxovMzZ0RDqlx1MDAxMVxyyiNKjcTEXGI9PtotNsJu6eJHbe/wgKj12Jr/kXE/r63sbLJeZ2XIXHUwMDEwtENcdTAwMTF7z5dcclx1MDAwYnmW+0g6yzZUS4WZXHUwMDEw5lx1MDAxOUNosiXASFx1MDAwMb1cbsRuz07u51x1MDAwZo/Xmyu1tZW9fGttk1x1MDAwZTr7P47aWe+Ug0iRXHUwMDA3xTSUXHUwMDE5qfFhUCxcdTAwMDVcdTAwMDORl8xcdTAwMTKN4uozXHUwMDFjXHUwMDEyXHUwMDA1RGC3Z7+uXHUwMDBiya9PwnOSXHUwMDFjaf1cdTAwMTTJ9mH+uH50Vz3rnOt5v1p5PF8s399kXHUwMDFjoVx1MDAxMHzEUt6pXHUwMDEwyFx1MDAxMlx1MDAwMNohiJqI3afmW6FcdTAwMDA1ZlfOXHUwMDA3XmdOuZkjokmjMVJWXHUwMDE3eN5cdTAwMTT5yY3oztaGu83hXHUwMDBmOHi4rN+7Zd05Xy1nXHUwMDFjokYxPFhcYvP8aoU8yYP7St6GyFx1MDAxMot6pqhW9kazXHUwMDBiUbJGh2SkcjYgXHUwMDFh0oP2rFx1MDAwZVGSeCc3okvncb11s32+7GF9fu9q5ep+Md7LPELFzTNcdTAwMTNVjs1cdTAwMTCoQVx1MDAxYtqLifJKZfNqgkXMbupcYmhePqxcdTAwMWRmzoiG9JaNzLxcdTAwMDKriCmI6KLbuDutLS0+xlx1MDAxN3GzVlx1MDAwN79ysXKdcYSy9YxcdTAwMTTrdc2zz25cXHk3XGZR2Vx1MDAxOVxyXGJEPDtJnpc5jFx1MDAxYalO1cltltnAKGLqzlx1MDAxMnMvmVx1MDAxYTOFoK/eXHUwMDE11pY3fdw9OV9cXHbrXHUwMDA1v7dRTstwXHUwMDFhXHUwMDAy2yBEh5MxX95VKrSr5Skw6t6M23stXHUwMDAxJ03GSdxpOG4vtclcdTAwMTakpNJcdTAwMTHpgV2Md2C00yrU281CizEwilNcIlx1MDAxM1nrPDnwXHUwMDAxKVHH/4JTRmjU6yhFxORcdTAwMTleazkmdWZOm4/D6fOFPrBcdTAwMTLz3XAnq9sneq55tN8+qtRaoW7z/eZdXHUwMDAzKCy0Wo377y9X/vpz3OfmjzcvVeGH3aruXHUwMDE31U7zvFx1MDAxM2/Q2mSf+/y/9NUlOb+e7JesLvBcdNo4XHUwMDFjjFx1MDAwMKc1QjLx/M1cdTAwMDYqZ5VC52Jne7+Rq+J5cXl/f8WkXHUwMDA1I963vD6jl5+xnsh6wzKPwlA9IPpcYlxyYOhcdTAwMTUyJWNR73FcdTAwMDCVSmV0VaHxXHUwMDExSfKXXHUwMDE2nenBj64q9kxRkEpcdTAwMDZcdTAwMTabVnt6JVx1MDAxMFx1MDAxMbTU3NJcdTAwMTetquPS0YK+qdxsVbeDtY15u5M/+Fx1MDAxMPSzYFx0XHUwMDA2TKI4+FP5z7jmQc6LuPeT59W44upGvXje6Txs3Hk791jaNLfdrG9nXHUwMDEwRYjSpJLBbchYN0jSrYFcYp2072b+O1DSnTVcdTAwMDLkXHUwMDAwjTIztydcdTAwMWN8KkQt36vB1ohvIXRt7vDuPr46NKcnm4vL84dm3u1cdTAwMTWzjlA2XGKRtuRQ2loo0GZcdTAwMDSgXG6sVkxcdTAwMDSlnjvLXGZdci9cdTAwMDLNXHUwMDFjQ1x1MDAwZphKIShoUfgwOUT90Ur+qHvjVudcdTAwMTf2N+7McYxcdTAwMGKrv7hcdTAwMDXbJHvCOnJse1x1MDAwMtPv4IJcdTAwMWJuXGJsdIRSbOhcdTAwMDP77iz3Zlx0soPPTmDWrCgkO+BcdTAwMGXvXG5b8D55/S2Idlx1MDAxZuau4kK8tH661z09PVxu5/Gmb72H5Fx1MDAwZWdYfSrJVYrvOfPLQJLLPeTlmeU6XHUwMDE2kFxmUCvJXHUwMDBin0FyXUSGx4DSXHUwMDAwjswr0lx1MDAxMYRpI7Di8NIlXHUwMDA2kpU6zylcdTAwMGLaK0Tzgbtt40iu1t2HjXZcdTAwMWL2701cdTAwMWTWXHUwMDFmdtbDfq09XHUwMDE5yVx1MDAxZCtcdTAwMWQ/jzyLdERNiT5EP7mmynFcXGu2X19RXGLpXHKMTVxiXoVpXHUwMDEyLVx1MDAxNnKrj2v7W4fY3cH25Wlz+X47Tlx1MDAwYlx1MDAxZY5dU19n9C1cdTAwMTNcdTAwMGY5QoOkr2qya+GTySemJazJWLO5nywur1x1MDAxNIgndXRNiWzkJcvvN8yMkod1vKwpzz5Bu15tZq9dPVx1MDAwZS8pXGLa9PqGftzmyyetqVx0sG9cdTAwMDIl5NpnYj/9XGJcdTAwMDRcbkRopombP6jaSfVu3Vx1MDAwMdxvdCvhcPn29lhnXHUwMDFi+o5NtVx1MDAxNKVbhrZU21xmd5dnwi6tXHUwMDE0fUBG10/muldcbufsNj5cdTAwMDf90tvS0kfGXCK/jO6Mg6fRqZnuLOSZkSdcdTAwMWKevNlxtrtztHd80a7nW/VcdTAwMDNLues18svZhmewXHUwMDExXHUwMDFiTNTKXHUwMDA1ZYJcdTAwMWVcdTAwMDInRp5cdTAwMTcwOEmCI/dz7ZNAn3v/XG5cdTAwMTn/XGJwamM86I9cZullg4uH9MM5XHUwMDAwyTgzVUHwzdpybVWIgzt5rN/O6at7XFz+xVxyviag45YxXHUwMDE4sKdcdTAwMTiJkseS9XaE5Fx1MDAxNC7JVWVcdTAwMTHHbi3D7ZNcdTAwMDC0lb2emeuflOxcdTAwMDc0sunII1HgpijFWHY3NVx1MDAxN9YrjYWthePOXFzturyx+Iubdk9cdTAwMTbTXGJI5Fk3SuR30MdLb1x1MDAxYalcdTAwMTFinSapVlnO0eTRofPqd3Ty41x1MDAxYsun9+xcdTAwMTZS79n9TZFFvJu/xU13eLOoluY3Nu5cdTAwMGXndWpBW0a8PLmIuSVbSXa0npKHi/bS30izXHUwMDExtU66RcqRmz+Fz2K5ZEqFUXxcIjNddFxundXW8WJJxP36YVxyadfoXHUwMDAz01xyXHSvkLV2XHUwMDE4ob2oh0P4uKDb84VXJZhdpq2DPD6usXBdOsvRwsrZ1u6HSDBS4I2ihPP+3NyldPgzXHUwMDE4PNI0JLecK8Depe80fzzg/FK3splfzDyLYFx1MDAwNlx1MDAxMTltSY5HkiNA9GhrJqWsXHUwMDFjQ2d5WnR2O9z5XkqJnTlcdTAwMGLtbWrqktfM7aU75sRcdTAwMTA9idtzuNxdPDpRh4Vt6zqF5TnMtoVmmVx1MDAxOVnNXCLLgGPFZf1QhjKZKEhcdTAwMGZjJZlcdTAwMGL+55SYKdpy5ZUwXHUwMDAxhFx1MDAxMCErXlxiWnpEqVeyK0BHXGZdbaWbmzch2YbvpVaOv1x1MDAwMdFcdTAwMTeckyh7mNKV+otork6VYsZpdp9qinbgJzfdrd2TucJ6Z2N+XHUwMDAzf1xcqIUlyP7mssZIXHUwMDEykUny5L0mNdwllFx1MDAxMexcclwiMpOg5Jl9WbOhILFcdTAwMGXnZq9tXHUwMDAzr9s0jLLR0EK/pkhQ6yzUtqq1dtXNVcxyI3dZc0fl86yDlH28VFx1MDAxYrFcdTAwMTdHZ1xmQ3FcdTAwMTikIdLKMlYlXFyU7H+XNZDyTIGXVNZcdTAwMTnDqLepXHUwMDE5XHUwMDEwutf6Jlnc8maK8uJBWHs4XzgrmJ3H5nGhvFs8Szu0OyOOXHUwMDFlvY20Zi7qJf84ebhuL+RKllVSYIGDyqJcbj+HzzQtXHUwMDA2XHUwMDE4XHUwMDE5SZy1JFx0kjYk8lr7nj5cdTAwMTg5asyB9JHSNimQ+2n0Tto2fGCz5edcdTAwMGKvirHV05qNXHUwMDBm6o+ddkGdLOxuLV11XHUwMDFmXHUwMDFiXHUwMDFmJsYsa86vKmlO7zcuaWrKXHUwMDEyTUF1YblY7Fx1MDAxZW7XdpfV5W14XFw+0EtpR4VmZlx1MDAwNfhcYqw2IHRXXHUwMDA1sINU17JUQ4c6OHRcdTAwMWHDz+Xop3Nd6SYtNNcz0KVcdTAwMWPkXHUwMDE1XHUwMDFi7bU0o1RSXCLZq2hcdTAwMTnhutJtWtpXfH41nnBdzWPwXHUwMDFmXHUwMDA10U6523lccp2AqfthXHUwMDE2jSfvp+jivFvK32xf7NFK8+K+e53vXFyW2+4h21x1MDAxNSSGNZBUhmgmXHUwMDEx0qTFXHUwMDBlZv8443vZa1oh00c95qjlYtBcdTAwMDVdXHUwMDE4XHUwMDBmzlfTfzS9XHUwMDAyRVx1MDAxY4mEsdFmn/1VkbDPqlxyOVretcVcXLFcdTAwMTCftrDbXTg9PL3V+1x1MDAxZmPUwTnJ0ZsmO76PzEa9s1977JFcdTAwMTI/8Oxy4bpcdTAwMTY/XGagq7eUeIB3tVbntlx1MDAxMJ+1+Y3R88wlpr9d5lx1MDAwMfQ+kVx1MDAwNt46XHUwMDE31y5k5X2Py5XBJdmp8Y15udxpJPZBijyUXHUwMDAyf1xca600/JVcdTAwMWGt2kWtXohcdTAwMGbGXGZrrGl4utGv2Fx1MDAwNkxEzUZavKtARlx1MDAxMuYmNlx1MDAwZeMh9Vx1MDAwYlJcdTAwMDPftFxyrHgjJsug5HBoo0lcdTAwMGZpXHUwMDBipFxiia2D6Vx1MDAxNVx1MDAwN2D6Ps+7bVx1MDAwM0aKjY72Qlx1MDAxMVx1MDAxZDP2V1JcdTAwMDO1ZFNcdTAwMTkjXHQnhjTzs4RcdTAwMGV/5m1eNtNcdTAwMDdcZvknNtZkXHUwMDFmbmCaXHUwMDAyrcRdK7Q687V6qVa/4It96/G9/PSr1yZwMvLlXG7NJ84rgW6ggIHlTmJHqLeyi7fyRXIqYuIth5rx/Vx1MDAxNYLs9fOLXszY93K99PagxtdOvlxmXG4hXG5cdTAwMDY8z1x1MDAwNlx1MDAwMYY+MUxcdTAwMGVcdFwiXHUwMDAwNCxmXHSZq0uq0MiQ4kK7s9C4vq51+NbvNGr1zvAt7t3LOVnY1XJhxGTwV0peXHUwMDFitlx1MDAwME35xEFcdTAwMTfR/9+3/lwi6f3w8v9///nqq3OpXHUwMDE47l1cdTAwMWSFb/9cdTAwMDP/SP47tfWChMBcdTAwMWSOMFtWOuSmqF9cdTAwMWHvtzJpvEBH3rDMYTNtTbJT0NNcdTAwMDZcYi9cdTAwMGZcdFlIK1x1MDAxMGV9+lx1MDAxZeD7eU1cdTAwMTQoKC9dNlx1MDAxOMc6XHUwMDExIO7brmCjoK2R9sRcdTAwMDEg8ZKXJFxuOaieXHUwMDE11Fx1MDAxN5kuyUv6labrbyOgXCLvXHUwMDAzotXBs26X8uVcdTAwMTFT4iNWjjx34L0odlx1MDAxYsZcdTAwMWKuwW/xO9mPVFx1MDAxY8ljXHUwMDA0QVNajzRZpCn9ZFx1MDAwNulcdTAwMTQ71WGgjcOHXHUwMDFm165Rblx1MDAxNvPH7W3TXHKUO3pXXHUwMDAywdfJXCJcdTAwMWZcIr7dbLVccitAlviDXHRcdTAwMDRS7Fx1MDAxOFx1MDAxOSl6ZG2PaMaEVd9vPsxEskgybFx1MDAxZPlcdTAwMGY8XHUwMDFh5Fx1MDAwNUFcdTAwMWZb2j5WXHUwMDE2nd0wub9eKF0snrHMXFw92NleK1Y/TFx1MDAxNiEkj/b5Uln0NJtZU0VPo3pcdTAwMWat0JTa5Vx1MDAxNcBrfkyTWzRcdTAwMWVQWeRcdTAwMTVomNDJKdaS407SXHUwMDA1Ycg0aOZcdTAwMWSS7Vwivf9w3JlC7zZccqBs5PlXM4lmiFx1MDAxYlx1MDAxNmCjllx1MDAwMlwiy0KEiU+Qbp8+JOImf1x1MDAwN1RcdTAwMWPLOvz8LcHeXG7kXHUwMDFi9pnEYryHeeFcZiHSgVmOddDrVKhe1Vx1MDAxZipSXHUwMDEyXHSTvFx1MDAxOSXpq1x1MDAwNv92qp+jibSP2JtKfYpcdTAwMDe+UaTptVFcdTAwMDGDTskmXHUwMDA2Sk5cdTAwMDTZ31xcXHUwMDE1pUNYXHUwMDFluVH0Tslr0lWRT1VFYCE4YG00+W7EeL+VRfPlMFx1MDAxMpWvjFx1MDAxM3R7P3hYXG7IUVx1MDAxNc4rJGv4XHUwMDBiXHUwMDAxpSeGvd96XHUwMDExK3xvXHUwMDA1x57XWLKLRtJ6OWW8XHUwMDBiXHUwMDE2pUklJk6w+dt6MS4gfMX50T37ReinOSzlw+1XwjpcdTAwMDFTUtb2pEVcIklzvFx1MDAxMYtcIr3ztDNcdTAwMWGZXHUwMDE4PjWOXHUwMDE5b8B+X22UjqWnyyMwmtKMpMqj9Fx1MDAwZaPA9EeiXG6Tx1Z2qiv33fg0//DwuEA4lzdUryz+SiNcdTAwMTLebDuGkWbbgFx1MDAwZYyWznh+qI84Id95w9zH2+CtXHUwMDBi6Y03Pl1cdTAwMWX1TqtcdFx1MDAxZqmPxm7AiDv7MqXBWqJcdTAwMTK1i61GXHUwMDFjnzUqlXY5XHUwMDEz+y+vjOp9rlx1MDAxYdPPk5De4OzFpuhcdTAwMWQyvro4i5465yhcdTAwMDIkQMnRXHUwMDA2tGYwbcBcdTAwMDTV27jl22B6yPtcdTAwMDRPrSEyVlpDsFx1MDAwNe31XHUwMDBifsVTW4ycXHUwMDFjQ+2c8sriaP8xLWedOPNcdTAwMTVH8vTKjtFOVWWQ5qqHndrbTnx8n4KkXHUwMDEzN9LNwmtcdTAwMDfA3JZvT1KHvLhwXGbSXHUwMDE1RrxcdFwi64Lfm+6nXCKpd3VcdTAwMDRDXHUwMDFm5aZNelx1MDAxNFODYoJcdTAwMTTc5CZk78fVdv3i7qh2eXuSL1x1MDAxMFx1MDAxZZRgPc62n2ZcdTAwMDSxbpRjXGKIv21wg7FcbsZfXHUwMDA0ipRccrw+dXr+8lx1MDAwNFx1MDAwNmRsb9BkXHUwMDFisnG+mlx1MDAwNWFgsvCBqfQvOPraRoVshuQgx2mSl36OXHQ8fPvXt2eX+5BcdTAwMDVcdTAwMGUwMJ73eX9Krz+wcniNXHUwMDA1mGJcdTAwMDdi7FxcZ3LpgjKR8axt2KH1jl1cdTAwMWNO7MaIlKxr1oCa1d7nrV45csVcdTAwMWE54JeRbYx+Ra9cdTAwMWJcdTAwMTVZXHUwMDFlJZCXhlnJY5ZeSlx1MDAxMFTvaFb7JVx1MDAxYpk2KLTTdDpKI1x1MDAwMWmufrwr+JbcyNTBXHUwMDFi6Vx1MDAwZq/43imnXHUwMDEzju9vV29cIkmxMUH6t1wiWffOXHUwMDE0jPH9dVx1MDAwN8bEzFlCm0DBSSTIjo7JsdvQRlx1MDAwZXSWg1x1MDAwNdG635t+pEJYXHUwMDFluVx1MDAxMfR+XHUwMDEw/TDpLWQtXHUwMDBmiW/uXHUwMDE0UVx1MDAwMr2Tay3Gu3OhXHUwMDFiXHUwMDFmrlx1MDAxY9au1095rNk2YY6RJlx1MDAxOS1cdTAwMWV9YJpLQ+fkWFx1MDAxMzHzYzdpJfhtf2pcdTAwMGZ1vFx1MDAwNXP97zyWf/RORvvALZFx9ONcdTAwMTNrrcVcdTAwMDBcdTAwMDZy7zKA76JcdTAwMWbdvrvvZoF+XGaM5330I6TnfjJVZuOYrGR5a+2On+ssrl1cYkI+iFx1MDAwNVx1MDAxMlx1MDAxODaINFx1MDAxON9jK1x1MDAxYSlWrsRY41x1MDAxN4w5KLDE2kNV3r900UYuKM1SmNiDUnilWVx1MDAxMig2MyCWm6S1XHUwMDA1hNFcdTAwMDRQXHUwMDA34lk/Mnd83Nqz7FPDe9behORjvCf4NriVSSGgxEDZXHUwMDE3XHUwMDE5VoB21NeHSI5cdTAwMDBDXHUwMDA0lDzEN7ZcbtLGNL5HSGJM0ndAK9Deg3RQNyPDXHUwMDE53Wz9nZhGKl7lkVx1MDAxYoXqXHUwMDA3MVxyXHUwMDFjc+KZQUd2mkT1Jbuy0L09OvUnav/qqlalo/gw20xDo5aTRcBcdTAwMDVpyy9lVkNMQzNcclx1MDAwN81UXFzOQlCfp5X0a1x1MDAxOepuJK2TSbXnJfmB5+uOY1x1MDAxYZ9YSFxiTkqs35fa8dNM49t//1/9abNhfKaVXHUwMDFk/JivJFx1MDAxZq9ccvF9fFx1MDAwNCmVj0ibNctcdTAwMTR7cj4yXHUwMDFlXHUwMDExWVxc4dJNSTn+jqC0XHUwMDFmboondFx1MDAwNMiSXCKjpXz3p5JcdTAwMTbGrm+j5UBcdTAwMDGRiprFvE+e+fKy3IOPWGqCNEsnSc1cdTAwMWStI0ZpP0lIX5J7xVx1MDAxYd6bz6xHXHUwMDE57y6G6Ij1VsuBjspcdTAwMWG+gf1K6lx1MDAxN/fvXCIp/tCyU8BcdTAwMDTUvZXWnZp7NbZQfnBQksZneGBOyzlvYTRCQ1x1MDAxMSrpt2htUMF781szlHRcYssjN4LeNIbyx/Nv+F5oNvc7jLmX2WA010rPlr3/Nb/f1cr3869vPcru41x1MDAxZs83VKxPWb7sf/7646//XHUwMDA3cfF+/CJ9 - virtual_size.heightvirtual_size.widthself.scroll_offsetscroll_yscroll_xscroll_x +self.size.width + virtual_size.heightvirtual_size.widthself.scroll_offsety = scroll_yx = scroll_xx = scroll_x +self.size.width From de2a4fd78d3fb14ee01109b0a10db3ee252053a8 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Sat, 4 Feb 2023 15:53:00 +0100 Subject: [PATCH 84/93] test fixes --- tests/test_styles_cache.py | 49 +++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/tests/test_styles_cache.py b/tests/test_styles_cache.py index fdeb72dd6..a436c846b 100644 --- a/tests/test_styles_cache.py +++ b/tests/test_styles_cache.py @@ -10,7 +10,7 @@ from textual.geometry import Region, Size from textual.strip import Strip -def _extract_content(lines: list[list[Segment]]): +def _extract_content(lines: list[Strip]) -> list[str]: """Extract the text content from lines.""" content = ["".join(segment.text for segment in line) for line in lines] return content @@ -28,9 +28,9 @@ def test_set_dirty(): def test_no_styles(): """Test that empty style returns the content un-altered""" content = [ - [Segment("foo")], - [Segment("bar")], - [Segment("baz")], + Strip([Segment("foo")]), + Strip([Segment("bar")]), + Strip([Segment("baz")]), ] styles = Styles() cache = StylesCache() @@ -54,9 +54,9 @@ def test_no_styles(): def test_border(): content = [ - [Segment("foo")], - [Segment("bar")], - [Segment("baz")], + Strip([Segment("foo")]), + Strip([Segment("bar")]), + Strip([Segment("baz")]), ] styles = Styles() styles.border = ("heavy", "white") @@ -85,9 +85,9 @@ def test_border(): def test_padding(): content = [ - [Segment("foo")], - [Segment("bar")], - [Segment("baz")], + Strip([Segment("foo")]), + Strip([Segment("bar")]), + Strip([Segment("baz")]), ] styles = Styles() styles.padding = 1 @@ -116,9 +116,9 @@ def test_padding(): def test_padding_border(): content = [ - [Segment("foo")], - [Segment("bar")], - [Segment("baz")], + Strip([Segment("foo")]), + Strip([Segment("bar")]), + Strip([Segment("baz")]), ] styles = Styles() styles.padding = 1 @@ -150,9 +150,9 @@ def test_padding_border(): def test_outline(): content = [ - [Segment("foo")], - [Segment("bar")], - [Segment("baz")], + Strip([Segment("foo")]), + Strip([Segment("bar")]), + Strip([Segment("baz")]), ] styles = Styles() styles.outline = ("heavy", "white") @@ -177,9 +177,9 @@ def test_outline(): def test_crop(): content = [ - [Segment("foo")], - [Segment("bar")], - [Segment("baz")], + Strip([Segment("foo")]), + Strip([Segment("bar")]), + Strip([Segment("baz")]), ] styles = Styles() styles.padding = 1 @@ -203,17 +203,17 @@ def test_crop(): assert text_content == expected_text -def test_dirty_cache(): +def test_dirty_cache() -> None: """Check that we only render content once or if it has been marked as dirty.""" content = [ - [Segment("foo")], - [Segment("bar")], - [Segment("baz")], + Strip([Segment("foo")]), + Strip([Segment("bar")]), + Strip([Segment("baz")]), ] rendered_lines: list[int] = [] - def get_content_line(y: int) -> list[Segment]: + def get_content_line(y: int) -> Strip: rendered_lines.append(y) return content[y] @@ -232,6 +232,7 @@ def test_dirty_cache(): del rendered_lines[:] text_content = _extract_content(lines) + print(text_content) expected_text = [ "┏━━━━━┓", "┃ ┃", From 59def1a591b56246fc83f366b7dc325e031d8a79 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Sat, 4 Feb 2023 16:10:33 +0100 Subject: [PATCH 85/93] test fix --- src/textual/_styles_cache.py | 2 +- tests/test_styles_cache.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/textual/_styles_cache.py b/src/textual/_styles_cache.py index dea21a3fb..4a8a74208 100644 --- a/src/textual/_styles_cache.py +++ b/src/textual/_styles_cache.py @@ -212,7 +212,7 @@ class StylesCache: padding: Spacing, base_background: Color, background: Color, - render_content_line: RenderLineCallback, + render_content_line: Callable[[int], Strip], ) -> Strip: """Render a styled line. diff --git a/tests/test_styles_cache.py b/tests/test_styles_cache.py index a436c846b..01045e322 100644 --- a/tests/test_styles_cache.py +++ b/tests/test_styles_cache.py @@ -227,12 +227,13 @@ def test_dirty_cache() -> None: Color.parse("blue"), Color.parse("green"), get_content_line, + Size(3, 3), ) assert rendered_lines == [0, 1, 2] del rendered_lines[:] text_content = _extract_content(lines) - print(text_content) + expected_text = [ "┏━━━━━┓", "┃ ┃", From dc1fce3da78df77d45e8c19b4d86458104b8ccf6 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Sat, 4 Feb 2023 16:13:42 +0100 Subject: [PATCH 86/93] svg update --- docs/images/scroll_view.excalidraw.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/images/scroll_view.excalidraw.svg b/docs/images/scroll_view.excalidraw.svg index 4dfc06120..3d78e166c 100644 --- a/docs/images/scroll_view.excalidraw.svg +++ b/docs/images/scroll_view.excalidraw.svg @@ -1,6 +1,6 @@ - eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nN1da1NcIsuy/b5/xcTcj3fTp7Kyslx1MDAxZSfixFxy309866j3njCQh6AtIKCiJ/Z/v5nollx1MDAwNmxcdTAwMDRHnVx1MDAxZZiImZFcdTAwMDYsulZlrpWVmfWfP759+955aJa///Pb93K3WIhrpVbh/vuf8vxdudWuNep8Sfd+bjduW8XeK6udTrP9z3/847rQuip3mnGhWI7uau3bQtzu3JZqjajYuP5HrVO+bv+P/L1VuC7/q9m4LnVaUf+X5MqlWqfRevpd5bh8Xa532vzp/8s/f/v2n97fidG1ysVOoX5cdTAwMTGXe2/oXepcdTAwMGbQXHUwMDAyXGY/u9Wo91x1MDAwNkvgwVtcInx5Qa29yL+uUy7x1VxuXHUwMDBmudy/XCJPfd/frEC9fNndLc7nWlx1MDAwN+FylZpnK/3fWqnF8X7nIX66XHUwMDEzhWL1tpVcdTAwMThTu9NqXFyVf9RKnSpfh6HnX97XbvBN6L+r1bi9qNbL7fbAe1x1MDAxYc1CsdZ54OdQvTz5dFx1MDAwZv75rf9Ml3/KeVx1MDAxZJFcdTAwMDZcYoBeXHUwMDFi1C9cdTAwMTfl3cb7yJBzqFBrXHUwMDE1XGJwaFhcdTAwMGKNmCeCh/VfqvfoXHUwMDBm7LxQvLrg0dVL/ddo7c/Ltv+a++cva22kjHNktCFvVTAvr6iWa1x1MDAxN9VcdTAwMGW/xOnII1llrdLKe99cdTAwMWZnu9ybXHUwMDBmXHJoXGZfXHUwMDBm7uWK/PrmWqlcdTAwMDeNfyfvWL30fMfqt3HcXHUwMDFmsVxcWErAqf+e22ap8DTtYHmYxlrCYMLL9bhWv1x1MDAxYf64uFG86iOl9+xff75cdTAwMDOhzvs0hII1oFxirHJcdTAwMTNDdPGwcnXZndu6yeOP3f3Kwd3p4eJcXMYhSjZcdTAwMDIyzqLRXHUwMDBl+XZcdTAwMDQ7XG5SxEBcYoxTdGAzXHUwMDBiUlx1MDAxZb5cdEFZmjGMWqfTMFx1MDAxYZx2wTlcclx1MDAxM0O0fLC6znfv/Lxk9nbya35jY+fKZFx1MDAxY6I5wEhB8Fx1MDAxNMCx0/BcdTAwMWVcdTAwMDYxSlx1MDAxNiNjveJLoL1cdTAwMTlcdTAwMWVXdlx1MDAxMFxuhOj5XHUwMDBmzFx1MDAxYUJtSEMoKMO+g1x1MDAxZNzkjv54+3xr44T8eWNn97pwfLfK/Kb9ayFcbupNM2pcIkfMZ7S3XHUwMDEw2HO4XHUwMDAxhFpcZlx1MDAxMZOAXHUwMDEw2EIpMqQzXHUwMDBiUVxyzlx1MDAxOVx1MDAxZerMQdSnU1HNTs8nuc1bXGK9zp1cdTAwMTZwuTh3uH9avSovVneWVmLIulx1MDAxMfVcdTAwMTAxXHUwMDBiNVx1MDAxZa32vCpcdTAwMTNmsodQo1witqCGXGZcdTAwMThero4yi1BwPF3EPmDmuCilc1HPRoPvXHUwMDE3hIkxXHUwMDFhXFzBUty4vLwtXHUwMDFlwll8W2psu62MY1x1MDAxNDRFwTLpdsBcdTAwMTBcYlx0QfTk5ompKPNxr9EoTMx/5iDaey8wX/kqiJL6XHUwMDFhiDpMhSgp65yiKbho63G9OueWtlxiVlexnFt8XFzeqVx1MDAxZWXd0fPcsqdnXHUwMDBl5zTxv+RcdTAwMDdFPSM30p61vLXaXHUwMDA0rbNMRpVcdTAwMDfvlJo9kNpUkFx1MDAwNlx1MDAwYoaUm9yMzuF56Fxcrz36RnV1f+nwXHUwMDA3XHUwMDE0jupZl/S5XHUwMDEwKYW8XHUwMDFjeTGyx1x1MDAwZs5cdTAwMGZBlFwi1DxcdTAwMWLOXHUwMDEwi8fsXHUwMDAyXHUwMDE0nGVNz58zY1x1MDAwMLVOpVx1MDAwMVQrXHUwMDAw8F5pmlx1MDAxOKHzwTRCp3M1f96+0te2XHUwMDA07ZN6M+NcYoVAXHUwMDExXHUwMDE4XHUwMDA3XG5cZkqQTVx1MDAwZnp64y1jQ/E/ymr292CyXHUwMDBiUmT8eMfvnjGQslJIXHUwMDAzqbPoXWBNOzFGN+JudbGKp/mOKsdL9v4hbnZcdTAwMGYyjlFEjJhpKmJHrrzVw8F7hiibWFx1MDAwNoVTyMs2u3FRXmPMRlx1MDAxNOGXXHUwMDA1Rr/K0etUR1x1MDAxZjAwuVFqcojS1VJ8XqrcLe/fkSqdXHUwMDFlx2f1pTjjXHUwMDEw1dZEvFx1MDAxNHkxguH7rmhcdTAwMDCiZCHyXGZdoyUspTNsQ8k60Dxfs1x1MDAwN9DUyD1a8W9mXG5cdTAwMWK6cGVOOnBZujjcKraO7lt4WC1cdTAwMTSyrpbEiFx1MDAxYVx1MDAxM2xARlx1MDAwMIBcdTAwMWFkolx1MDAxMnRcdTAwMDJtvZO9UfA2w1x1MDAxMHWIIH/NXHUwMDFjRFx1MDAxM9ubw2JJ81CQXHUwMDE35lx1MDAxNEb08mR1fdNcdTAwMWRuz5lyQ69vXHUwMDFmXHUwMDFkNU9d1jHKojBicHi0gVx0KeJcYkh1xFqJXHSAZuWUYEWZwygvMs9E5es26b+KikK6oKdgXGZcdTAwMTjvJo866dU5t1x1MDAxN6/XTu9cdTAwMTaal1x1MDAxN9v6uqIuTzPu6I3CyGrWhSGYXHUwMDAwrNuHXHUwMDFjPdNcdTAwMDDN0FDog2Zbm12IWvLOOHKzZkY9pu6Aslx1MDAwYnTOoZ3cim51zu5q5qp71Jy/OZ3rPOT396pLv1x1MDAwMUJccoHkM1x1MDAxMftJjYOKnvldxLiV6fdK8k0yXHUwMDBiUUmFcVx1MDAwZczMIVSlclFAQ7Lpoibfoz/ZvF85r3Yr8a3/sVZcdTAwMGY/XHUwMDE2XHUwMDFh9e2TrDt6ZpqRdV6zUlx1MDAwMutJXHUwMDFiN4RRXHUwMDE3Ke15Zlx1MDAxODySSJRZjGpcdTAwMTWQzSg7v1x1MDAxOVx1MDAwM2lcdTAwMTKCw5FRXHRcdTAwMDeLkp3cjsYnj6fbtki71/a+3tneuL5sXHUwMDFk72bcjuY8S1wiq1x1MDAxMDyixGyGIFx1MDAxYVTkwLKh5eWqvcmuYDKEOlg0OGNcYvVk0lx1MDAxMFxuXjtngp5cImH04r64fFRrgz87fFivVY3dP136xXHRXHRSnVx1MDAxY+shZFLOXHUwMDE49MYn8fGEUWAmYFx1MDAwM09ccrDiT9iorGGUL3qPxs/cXHUwMDA2aDpGhdtYayffXSq3K6Z4cLBBXHUwMDFizYWmPc3fVpfOV7NuQ0FFzDSDt6BJXHUwMDEy7lx1MDAxMj5DPsAzeFhGsdhnyczYyS5cdTAwMTn11jtcdTAwMTVmjYo6n55FonlKmIPh5Ho+qLjauT1fceXNUk3lj4u3uc1Gxlx1MDAxMcomlFx1MDAwNaFcdTAwMGXEXFyUQnK3V95cdTAwMWWMioLRvFaNVpjhXHUwMDE0XHUwMDEy1vFMRXD24vaJcNJcYlxy1V4pq6cwoeeNKlxctNY3rlx1MDAxZud3ri/umuvbx0v5jFx1MDAwMzTnfOTYVbD/9sw02ZBcdTAwMGUhXHUwMDE0I9H43mlcbtbr7GaLasVkWmlQM0dE0yNOxoBiRVx1MDAwZpNDdD9/kju4Ot5auyue3K5tl+P7++27jENcdTAwMTS0i4zYSDDIgthcZvt4XHUwMDA2MLDSZyFigoPsZtw77Sh8iVx1MDAxMSVR0MaS+1x1MDAxYYD69LIlbXje0E7u5JfW/P3F0UY9f1fePZybPyid3dazXHUwMDFlXHUwMDEyXHUwMDE1gErpZ2BcdTAwMDOEUlx1MDAxMzJcdTAwMDBQZp9cdTAwMTQpJDayRMYmk/SyhlCw8ofC7Il5m0pEKZBcdTAwMDFm31x1MDAxMyP0buW+c7y3dpFfPC0vrF+erFx1MDAxN6uF44wjNMdcdTAwMTaUfSP7XG5P1rowlMvMXHUwMDEwdZE3/FDM9FhLZdeIXHUwMDAyX3BKzVxcXHUwMDE2ntOpXt4rtKRhilxu+rB5XHUwMDA1153c6mbzZide2cjVu3S2k/VoXHUwMDEzXHUwMDA0JyX0SmnNUPU4lMtcdTAwMWN0ZIB6NXe9TYzsXCJcdTAwMTR4XHIpM3tcdTAwMTHRkJ5BXHUwMDEyjDdcdTAwMTRCmNzPn9aqUG7klmv1/GGN8qsrjXD/i8X8JElONmJcdTAwMDBcdTAwMDZyXHUwMDEyUVxuw9tKXGZRXGbgXHUwMDA1PFp2bTJcdTAwMGJRazWxovMzZ0RDqlx1MDAxMVxyyiNKjcTEXGI9PtotNsJu6eJHbe/wgKj12Jr/kXE/r63sbLJeZ2XIXHUwMDEwtENcdTAwMTF7z5dcclx1MDAwYnmW+0g6yzZUS4WZXHUwMDEw5lx1MDAxOUNosiXASFx1MDAwMb1cbsRuz07u51x1MDAwZo/Xmyu1tZW9fGttk1x1MDAwZTr7P47aWe+Ug0iRXHUwMDA3xTSUXHUwMDE5qfFhUCxcdTAwMDVcdTAwMDORl8xcdTAwMTKN4uozXHUwMDFjXHUwMDEyXHUwMDA1RGC3Z7+uXHUwMDBiya9PwnOSXHUwMDFjaf1cdTAwMTTJ9mH+uH50Vz3rnOt5v1p5PF8s399kXHUwMDFjoVx1MDAxMHzEUt6pXHUwMDEwyFx1MDAxMlx1MDAwMNohiJqI3afmW6FcdTAwMDA1ZlfOXHUwMDA3XmdOuZkjokmjMVJWXHUwMDE3eN5cdTAwMTT5yY3oztaGu83hXHUwMDBmOHi4rN+7Zd05Xy1nXHUwMDFjokYxPFhcYvP8aoU8yYP7St6GyFx1MDAxMot6pqhW9kazXHUwMDBiUbJGh2SkcjYgXHUwMDFh0oP2rFx1MDAwZVGSeCc3okvncb11s32+7GF9fu9q5ep+Md7LPELFzTNcdTAwMTNVjs1cdTAwMTCoQVx1MDAxYtqLifJKZfNqgkXMbupcYmhePqxcdTAwMWRmzoiG9JaNzLxcdTAwMDKriCmI6KLbuDutLS0+xlx1MDAxN3GzVlx1MDAwN79ysXKdcYSy9YxcdTAwMTTrdc2zz25cXHk3XGZR2Vx1MDAxOVxyXGJEPDtJnpc5jFx1MDAxYalO1cltltnAKGLqzlx1MDAxMnMvmVx1MDAxYTOFoK/eXHUwMDE11pY3fdw9OV9cXHbrXHUwMDA1v7dRTstwXHUwMDFhXHUwMDAy2yBEh5MxX95VKrSr5Skw6t6M23stXHUwMDAxJ03GSdxpOG4vtclcdTAwMTakpNJcdTAwMTHpgV2Md2C00yrU281CizEwilNcIlx1MDAxM1nrPDnwXHUwMDAxKVHH/4JTRmjU6yhFxORcdTAwMTleazkmdWZOm4/D6fOFPrBcdTAwMTLz3XAnq9sneq55tN8+qtRaoW7z/eZdXHUwMDAzKCy0Wo377y9X/vpz3OfmjzcvVeGH3aruXHUwMDE31U7zvFx1MDAxM2/Q2mSf+/y/9NUlOb+e7JesLvBcdNo4XHUwMDFjjFx1MDAwMKc1QjLx/M1cdTAwMDYqZ5VC52Jne7+Rq+J5cXl/f8WkXHUwMDA1I963vD6jl5+xnsh6wzKPwlA9IPpcYlxyYOhcdTAwMTUyJWNR73FcdTAwMDCVSmV0VaHxXHUwMDExSfKXXHUwMDE2nenBj64q9kxRkEpcdTAwMDZcdTAwMTabVnt6JVx1MDAxMFx1MDAxMbTU3NJcdTAwMTetquPS0YK+qdxsVbeDtY15u5M/+Fx1MDAxMPSzYFx0XHUwMDA2TKI4+FP5z7jmQc6LuPeT59W44upGvXje6Txs3Hk791jaNLfdrG9nXHUwMDEwRYjSpJLBbchYN0jSrYFcYp2072b+O1DSnTVcdTAwMDLkXHUwMDAwjTIztydcdTAwMWN8KkQt36vB1ohvIXRt7vDuPr46NKcnm4vL84dm3u1cdTAwMTWzjlA2XGKRtuRQ2loo0GZcdTAwMDSgXG6sVkxcdTAwMDSlnjvLXGZdci9cdTAwMDLNXHUwMDFjQ1x1MDAwZphKIShoUfgwOUT90Ur+qHvjVudcdTAwMTf2N+7McYxcdTAwMGKrv7hcdTAwMDXbJHvCOnJse1x1MDAwMtPv4IJcdTAwMWJuXGJsdIRSbOhcdTAwMDP77iz3Zlx0soPPTmDWrCgkO+BcdTAwMGXvXG5b8D55/S2Idlx1MDAxZuau4kK8tH661z09PVxu5/Gmb72H5Fx1MDAwZWdYfSrJVYrvOfPLQJLLPeTlmeU6XHUwMDE2kFxmUCvJXHUwMDBin0FyXUSGx4DSXHUwMDAwjswr0lx1MDAxMYRpI7Di8NIlXHUwMDA2kpU6zylcdTAwMGLaK0Tzgbtt40iu1t2HjXZcdTAwMWL2701cdTAwMWTWXHUwMDFmdtbDfq09XHUwMDE5yVx1MDAxZCtcdTAwMWQ/jzyLdERNiT5EP7mmynFcXGu2X19RXGLpXHKMTVxiXoVpXHUwMDEyLVx1MDAxNnKrj2v7W4fY3cH25Wlz+X47Tlx1MDAwYlx1MDAxZY5dU19n9C1cdTAwMTNcdTAwMGY5QoOkr2qya+GTySemJazJWLO5nywur1x1MDAxNIgndXRNiWzkJcvvN8yMkod1vKwpzz5Bu15tZq9dPVx1MDAwZS8pXGLa9PqGftzmyyetqVx0sG9cdTAwMDIl5NpnYj/9XGJcdTAwMDRcbkRopombP6jaSfVu3Vx1MDAwMdxvdCvhcPn29lhnXHUwMDFi+o5NtVx1MDAxNKVbhrZU21xmd5dnwi6tXHUwMDE0fUBG10/muldcbufsNj5cdTAwMDf90tvS0kfGXCK/jO6Mg6fRqZnuLOSZkSdcdTAwMWKevNlxtrtztHd80a7nW/VcdTAwMDNLues18svZhmewXHUwMDExXHUwMDFiTNTKXHUwMDA1ZYJcdTAwMWVcdTAwMDInRp5cdTAwMTcwOEmCI/dz7ZNAn3v/XG5cdTAwMTn/XGJwamM86I9cZullg4uH9MM5XHUwMDAwyTgzVUHwzdpybVWIgzt5rN/O6at7XFz+xVxyviag45YxXHUwMDE4sKdcdTAwMTiJkseS9XaE5Fx1MDAxNC7JVWVcdTAwMTHHbi3D7ZNcdTAwMDC0lb2emeuflOxcdTAwMDc0sunII1HgpijFWHY3NVx1MDAxN9YrjYWthePOXFzturyx+Iubdk9cdTAwMTbTXGJI5Fk3SuR30MdLb1x1MDAxYalcdTAwMTFinSapVlnO0eTRofPqd3Ty41x1MDAxYsun9+xcdTAwMTZS79n9TZFFvJu/xU13eLOoluY3Nu5cdTAwMGXndWpBW0a8PLmIuSVbSXa0npKHi/bS30izXHUwMDExtU66RcqRmz+Fz2K5ZEqFUXxcIjNddFxundXW8WJJxP36YVxyadfoXHUwMDAz01xyXHSvkLV2XHUwMDE4ob2oh0P4uKDb84VXJZhdpq2DPD6usXBdOsvRwsrZ1u6HSDBS4I2ihPP+3NyldPgzXHUwMDE4PNI0JLecK8Depe80fzzg/FK3splfzDyLYFx1MDAwNlx1MDAxMTltSY5HkiNA9GhrJqWsXHUwMDFjQ2d5WnR2O9z5XkqJnTlcdTAwMGLtbWrqktfM7aU75sRcdTAwMTA9idtzuNxdPDpRh4Vt6zqF5TnMtoVmmVx1MDAxOVnNXCLLgGPFZf1QhjKZKEhcdTAwMGZjJZlcdTAwMGL+55SYKdpy5ZUwXHUwMDAxhFx1MDAxMCErXlxiWnpEqVeyK0BHXGZdbaWbmzch2YbvpVaOv1x1MDAwMdFcdTAwMTeckyh7mNKV+otork6VYsZpdp9qinbgJzfdrd2TucJ6Z2N+XHUwMDAzf1xcqIUlyP7mssZIXHUwMDEykUny5L0mNdwllFx1MDAxMexcclwiMpOg5Jl9WbOhILFcdTAwMGXnZq9tXHUwMDAzr9s0jLLR0EK/pkhQ6yzUtqq1dtXNVcxyI3dZc0fl86yDlH28VFx1MDAxYrFcdTAwMTdHZ1xmQ3FcdTAwMTikIdLKMlYlXFyU7H+XNZDyTIGXVNZcdTAwMTnDqLepXHUwMDE5XHUwMDEwutf6Jlnc8maK8uJBWHs4XzgrmJ3H5nGhvFs8Szu0OyOOXHUwMDFlvY20Zi7qJf84ebhuL+RKllVSYIGDyqJcbj+HzzQtXHUwMDA2XHUwMDE4XHUwMDE5SZy1JFx0kjYk8lr7nj5cdTAwMTg5asyB9JHSNimQ+2n0Tto2fGCz5edcdTAwMGKvirHV05qNXHUwMDBm6o+ddkGdLOxuLV11XHUwMDFmXHUwMDFiXHUwMDFmJsYsa86vKmlO7zcuaWrKXHUwMDEyTUF1YblY7Fx1MDAxZW7XdpfV5W14XFw+0EtpR4VmZlx1MDAwNfhcYqw2IHRXXHUwMDA1sINU17JUQ4c6OHRcdTAwMWHDz+Xop3Nd6SYtNNcz0KVcdTAwMWPkXHUwMDE1XHUwMDFi7bU0o1RSXCLZq2hcdTAwMTnhutJtWtpXfH41nnBdzWPwXHUwMDFmXHUwMDA10U6523lccp2AqfthXHUwMDE2jSfvp+jivFvK32xf7NFK8+K+e53vXFyW2+4h21x1MDAxNSSGNZBUhmgmXHUwMDEx0qTFXHUwMDBlZv8443vZa1oh00c95qjlYtBcdTAwMDVdXHUwMDE4XHUwMDBmzlfTfzS9XHUwMDAyRVx1MDAxY4mEsdFmn/1VkbDPqlxyOVretcVcXLFcdTAwMTCftrDbXTg9PL3V+1x1MDAxZmPUwTnJ0ZsmO76PzEa9s1977JFcdTAwMTI/8Oxy4bpcdTAwMTY/XGagq7eUeIB3tVbntlx1MDAxMJ+1+Y3R88wlpr9d5lx1MDAwMfQ+kVx1MDAwNt46XHUwMDE31y5k5X2Py5XBJdmp8Y15udxpJPZBijyUXHUwMDAyf1xca600/JVcdTAwMWGt2kWtXohcdTAwMGbGXGZrrGl4utGv2Fx1MDAwNkxEzUZavKtARlx1MDAxMuYmNlx1MDAwZeMh9Vx1MDAwYlJcdTAwMDPftFxyrHgjJsug5HBoo0lcdTAwMGZpXHUwMDBipFxiia2D6Vx1MDAxNVx1MDAwN2D6Ps+7bVx1MDAwM0aKjY72Qlx1MDAxMVx1MDAxZDP2V1JcdTAwMDO1ZFNcdTAwMTkjXHQnhjTzs4RcdTAwMGV/5m1eNtNcdTAwMDdcZvknNtZkXHUwMDFmbmCaXHUwMDAyrcRdK7Q687V6qVa/4It96/G9/PSr1yZwMvLlXG7NJ84rgW6ggIHlTmJHqLeyi7fyRXIqYuIth5rx/Vx1MDAxNYLs9fOLXszY93K99PagxtdOvlxmXG4hXG5cdTAwMDY8z1x1MDAwNlx1MDAwMYY+MUxcdTAwMGVcdFwiXHUwMDAwNCxmXHSZq0uq0MiQ4kK7s9C4vq51+NbvNGr1zvAt7t3LOVnY1XJhxGTwV0peXHUwMDFitlx1MDAwME35xEFcdTAwMTfR/9+3/lwi6f3w8v9///nqq3OpXHUwMDE47l1cdTAwMWSFb/9cdTAwMDP/SP47tfWChMBcdTAwMWSOMFtWOuSmqF9cdTAwMWHvtzJpvEBH3rDMYTNtTbJT0NNcdTAwMDZcYi9cdTAwMGZcdFlIK1x1MDAxMGV9+lx1MDAxZeD7eU1cdTAwMTQoKC9dNlx1MDAxOMc6XHUwMDExIO7brmCjoK2R9sRcdTAwMDEg8ZKXJFxuOaieXHUwMDE11Fx1MDAxN5kuyUv6labrbyOgXCLvXHUwMDAzotXBs26X8uVcdTAwMTFT4iNWjjx34L0odlx1MDAxYsZcdTAwMWKuwW/xO9mPVFx1MDAxY8ljXHUwMDA0QVNajzRZpCn9ZFx1MDAwNulcdTAwMTQ71WGgjcOHXHUwMDFm165Rblx1MDAxNvPH7W3TXHKUO3pXXHUwMDAywdfJXCJcdTAwMWZcIr7dbLVccitAlviDXHRcdTAwMDRS7Fx1MDAxOFx1MDAxOSl6ZG2PaMaEVd9vPsxEskgybFx1MDAxZPlcdTAwMGY8XHUwMDFh5Fx1MDAwNUFcdTAwMWZb2j5WXHUwMDE2nd0wub9eKF0snrHMXFw92NleK1Y/TFx1MDAxNiEkj/b5Uln0NJtZU0VPo3pcdTAwMWat0JTa5Vx1MDAxNcBrfkyTWzRcdTAwMWVQWeRcdTAwMTVomNDJKdaS407SXHUwMDA1Ycg0aOZcdTAwMWSS7Vwivf9w3JlC7zZccqBs5PlXM4lmiFx1MDAxYlx1MDAxNmCjllx1MDAwMlwiy0KEiU+Qbp8+JOImf1x1MDAwN1RcdTAwMWPLOvz8LcHeXG7kXHUwMDFi9pnEYryHeeFcZiHSgVmOddDrVKhe1Vx1MDAxZipSXHUwMDEyXHSTvFx1MDAxOSXpq1x1MDAwNv92qp+jibSP2JtKfYpcdTAwMDe+UaTptVFcdTAwMDGDTskmXHUwMDA2Sk5cdTAwMDTZ31xcXHUwMDE1pUNYXHUwMDFluVH0Tslr0lWRT1VFYCE4YG00+W7EeL+VRfPlMFx1MDAxMpWvjFx1MDAxM3R7P3hYXG7IUVx1MDAxNc4rJGv4XHUwMDBiXHUwMDAxpSeGvd96XHUwMDExK3xvXHUwMDA1x57XWLKLRtJ6OWW8XHUwMDBiXHUwMDE2pUklJk6w+dt6MS4gfMX50T37ReinOSzlw+1XwjpcdTAwMDFTUtb2pEVcIklzvFx1MDAxMYtcIr3ztDNcdTAwMWGZXHUwMDE4PjWOXHUwMDE5b8B+X22UjqWnyyMwmtKMpMqj9Fx1MDAwZaPA9EeiXG6Tx1Z2qiv33fg0//DwuEA4lzdUryz+SiNcdTAwMTLebDuGkWbbgFx1MDAwZYyWznh+qI84Id95w9zH2+CtXHUwMDBi6Y03Pl1cdTAwMWX1TqtcdFx1MDAxZqmPxm7AiDv7MqXBWqJcdTAwMTK1i61GXHUwMDFjnzUqlXY5XHUwMDEz+y+vjOp9rlx1MDAxYdPPk5De4OzFpuhcdTAwMWQyvro4i5465yhcdTAwMDIkQMnRXHUwMDA2tGYwbcBcdTAwMDTV27jl22B6yPtcdTAwMDRPrSEyVlpDsFx1MDAwNe31XHUwMDBifsVTW4ycXHUwMDFjQ+2c8sriaP8xLWedOPNcdTAwMTVH8vTKjtFOVWWQ5qqHndrbTnx8n4KkXHUwMDEzN9LNwmtcdTAwMDfA3JZvT1KHvLhwXGbSXHUwMDE1RrxcdFwi64Lfm+6nXCKpd3VcdTAwMDRDXHUwMDFm5aZNelx1MDAxNFODYoJcdTAwMTTc5CZk78fVdv3i7qh2eXuSL1x1MDAxMFx1MDAxZZRgPc62n2ZcdTAwMDSxbpRjXGKIv21wg7FcbsZfXHUwMDA0ipRccrw+dXr+8lx1MDAwNFx1MDAwNmRsb9BkXHUwMDFisnG+mlx1MDAwNWFgsvCBqfQvOPraRoVshuQgx2mSl36OXHQ8fPvXt2eX+5BcdTAwMDVcdTAwMGUwMJ73eX9Krz+wcniNXHUwMDA1mGJcdTAwMDdi7FxcZ3LpgjKR8axt2KH1jl1cdTAwMWNO7MaIlKxr1oCa1d7nrV45csVcdTAwMWE54JeRbYx+Ra9cdTAwMWJcdTAwMTVZXHUwMDFlJZCXhlnJY5ZeSlx1MDAxMFTvaFb7JVx1MDAxYpk2KLTTdDpKI1x1MDAwMWmufrwr+JbcyNTBXHUwMDFi6Vx1MDAwZq/43imnXHUwMDEzju9vV29cIkmxMUH6t1wiWffOXHUwMDE0jPH9dVx1MDAwN8bEzFlCm0DBSSTIjo7JsdvQRlx1MDAwZXSWg1x1MDAwNdG635t+pEJYXHUwMDFluVx1MDAxMfR+XHUwMDEw/TDpLWQtXHUwMDBmiW/uXHUwMDE0UVx1MDAwMr2Tay3Gu3OhXHUwMDFiXHUwMDFmrlx1MDAxY9au1095rNk2YY6RJlx1MDAxOS1cdTAwMWV9YJpLQ+fkWFx1MDAxMzHzYzdpJfhtf2pcdTAwMGZ1vFx1MDAwNXP97zyWf/RORvvALZFx9ONcdTAwMTNrrcVcdTAwMDBcdTAwMDZy7zKA76JcdTAwMWbdvrvvZoF+XGaM5330I6TnfjJVZuOYrGR5a+2On+ssrl1cYkI+iFx1MDAwNVx1MDAxMlx1MDAxODaINFx1MDAxON9jK1x1MDAxYSlWrsRY41x1MDAxN4w5KLDE2kNV3r900UYuKM1SmNiDUnilWVx1MDAxMig2MyCWm6S1XHUwMDA1hNFcdTAwMDRQXHUwMDA34lk/Mnd83Nqz7FPDe9behORjvCf4NriVSSGgxEDZXHUwMDE3XHUwMDE5VoB21NeHSI5cdTAwMDBDXHUwMDA0lDzEN7ZcbtLGNL5HSGJM0ndAK9Deg3RQNyPDXHUwMDE53Wz9nZhGKl7lkVx1MDAxYoXqXHUwMDA3MVxyXHUwMDFjc+KZQUd2mkT1Jbuy0L09OvUnav/qqlalo/gw20xDo5aTRcBcdTAwMDVpyy9lVkNMQzNcclx1MDAwN81UXFzOQlCfp5X0a1x1MDAxOepuJK2TSbXnJfmB5+uOY1x1MDAxYZ9YSFxiTkqs35fa8dNM49t//1/9abNhfKaVXHUwMDFk/JivJFx1MDAxZq9ccvF9fFx1MDAwNCmVj0ibNctcdTAwMTR7cj4yXHUwMDFlXHUwMDExWVxc4dJNSTn+jqC0XHUwMDFmboondFx1MDAwNMiSXCKjpXz3p5JcdTAwMTbGrm+j5UBcdTAwMDGRiprFvE+e+fKy3IOPWGqCNEsnSc1cdTAwMWStI0ZpP0lIX5J7xVx1MDAxYd6bz6xHXHUwMDE57y6G6Ij1VsuBjspcdTAwMWG+gf1K6lx1MDAxN/fvXCIp/tCyU8BcdTAwMDTUvZXWnZp7NbZQfnBQksZneGBOyzlvYTRCQ1x1MDAxMSrpt2htUMF781szlHRcYssjN4LeNIbyx/Nv+F5oNvc7jLmX2WA010rPlr3/Nb/f1cr3869vPcru41x1MDAxZs83VKxPWb7sf/7646//XHUwMDA3cfF+/CJ9 + eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nOVdaVNcdTAwMWLLkv1+f4XD83Gu6tWWVVkv4sVcdTAwMDT7KnZh0MxcdTAwMDShXHUwMDE1XHRcdTAwMWFJaFx1MDAwMcGL+98nU3BRa2lZwlx1MDAxOLc1OMI2ai2lrlOZ52RlZv37jy9fvnafWpWv//zytdIvXHUwMDE1onq5XXj8+ic//lBpd+rNXHUwMDA2XdKD3zvNXrs0eGat2211/vmPf9xcdTAwMTXat5VuKyqUKuKh3ulcdTAwMTWiTrdXrjdFqXn3j3q3ctf5L/77oHBX+VereVfutsXwQzKVcr3bbL98ViWq3FVcdTAwMWHdXHUwMDBlvft/0+9fvvx78HdsdO1KqVtoXFxHlcFcdTAwMGJcdTAwMDaXhlx1MDAwM3TGjT960GxcZlx1MDAwNquUROW1UertXHUwMDE59c46fV63UqbLVVx1MDAxYXNleIVcdTAwMWb6erpfVY3KTf+4tJppn4WbbWhdbVxyP7Zaj6LT7lP0citcbqVar1x1MDAxZFx1MDAxYlSn227eVr7Vy91cdTAwMWF/+tjjb6/rNOkuXGZf1W72rmuNSqcz8ppmq1Cqd5/oMSPfXHUwMDFlfLlcdP/8MnykT79lUFx1MDAwYtBKXHUwMDA1ZVBbo98u8qstorDgvZFGa1x1MDAxOUCZsWGtNSOaXHRcdTAwMWHWf8jBz3BgxULp9ppG1yhcdTAwMGafozVcdTAwMTYrbvicx9cv65yQ1nuw2lx1MDAwMjpcdTAwMTns2zNqlfp1rUtP8VqgXHUwMDAxJ52TWlwi4nCcncpgPrQy1tL14N+u8Me3dspcdTAwMDNs/G/8jjXKr3es0Yui4Yj5wkZcZk/D1/Ra5cLLtCvnXHUwMDAxtFx1MDAwMa+8XHUwMDFm3u+o3rhcdTAwMWR/u6hZulx1MDAxZFwiZfDoX3++XHUwMDAzolx1MDAxOLvpY1x1MDAxMFxyXG6scdqGuVx1MDAxMbqeq97e9FdcdTAwMGXus+bb8Wn17CGfW19JOULBXHT6nt5cdTAwMTmrvfGIwU1i1JhcdTAwMDBGXHUwMDExTI1XLrVcdTAwMTil4dtcdTAwMTCkgyWDaHzRjUFUk21cdTAwMDFcdGjmhmjlbHuX7l6xWLYnR9lcdTAwMWTc2zu6tSmHaEZcdTAwMTkhVUBcYsordIhqXHUwMDE0o+CMsFx1MDAwZSVdUlx1MDAxYe34uNKDUFx1MDAwNcYg/VFLh1CbhFBcdTAwMWFcdTAwMDN5NvTez1xy0YvD4sHeJWCxeXR8V7h42CZ+0/m1XHUwMDEwVfK7ZtRcblx1MDAwZo7uOzpcdTAwMTWcVX5cdTAwMDShzlx1MDAwNEFcdTAwMWMgXHUwMDA0slBcdTAwMTIs6NRCVNOEWVx1MDAxYeqyQdTLZCpKtFx1MDAwNiw6Mz9cdTAwMTW9y+RcdTAwMGJms7SSO83XbivrtaONrUil3YqiXHUwMDEyxEItXHUwMDEyp0ElbcxOXHUwMDBlIGqlIFx1MDAxM2rBKktE1UNqIao8kVx1MDAxNCAnsGxcXNRjXCJcdTAwMTdVXHUwMDEyQGFcdTAwMDA9P1x1MDAxOVxyvuAgat7c9Eo5dVx1MDAxNfXKzUN/kHKMKlxyXCI4XHTk51x0XHUwMDAyIcbNX/w8XHUwMDEwXHUwMDE3ddKjNlaa2PynXHUwMDBlooPXKlwiLMtcdTAwMDbRgMlcdTAwMTA1jr4wefu5Idp+3q2t+I1cdTAwMDNQ29umkll/3jyqnafd09PckqsnXHUwMDEy5zXQv4Cjop6QKzSSlncsXHUwMDFktU4zXHUwMDFilajQS7lsIMXYN5rQ9ETB0Nj5MbpiiqF7t/OMzdr26UbumyqcN9Ku6TNBSGkk3XjNXHUwMDFlP3hcdTAwMWODKFxio1x1MDAwM0hvIfhcdTAwMTRbUeVcdTAwMWSJenqfJVx1MDAwM6iL2chxgNJcdTAwMWTT3s4v6FeDbYZu93a12LnVd66sOpeNVsrxqVx1MDAwMlxiZb2Sylx1MDAxYWI2So/6eeLihFxmSf9Ip8nbK5teiFx1MDAxYUJcdTAwMGZ6evWSQVx1MDAxNH0yXHUwMDE3XHKWXHUwMDE0hJN2fr20XHUwMDE39WvrNZPPdmUl2nCPT1Grf5ZykFx1MDAxYWNcdTAwMDRcdTAwMTFNXHTkxyU6PVx1MDAxZbsnjJKFJVR4abRMcVxclFx1MDAxNlx1MDAxOZFcdTAwMTFcdGbZXHUwMDAyo1x1MDAxZVx1MDAxMv08WVx1MDAxNUSgiZs/7Fx1MDAwNLdcdTAwMWJRsVxcfdg8fVx1MDAwMFnOX0RXjY0o5Vx1MDAxONXOXG6DZICCslx1MDAwZVxiq2N6SVx0JOxazXEpnWIrXG7OK01kdOlcdTAwMTBqXHUwMDEzQ/fKgyFcIoZhfoSu3drLrropX+dcdTAwMGVK7fPHtsnVXG6FtMslNqPWXHUwMDA2XHUwMDE3XGZBgHd9RyDKUSelXHUwMDFket5cdTAwMWNV6FKMUW+M4r+WXHUwMDBlo7Fd6Vx0T+80eVx1MDAwZvDzyyW4udze3fe5w1x1MDAxNVtp6t3D8/NW3qdcdTAwMWSj9D1cdTAwMDWBXHUwMDAzjVx1MDAwYsRJjZlcdTAwMDCpXHUwMDE2JJaIXHUwMDAyaFqxXHUwMDE204tRWmRIVGX5dultoqcn6kXTRoppfsmkt1f8SbRbzz+stW6uXHUwMDBm9V1V3uRT7umtNIJYN8pcdTAwMTBsUN6Oe3riXHUwMDAxmqAhXHJcdTAwMDZNtja9XHUwMDEwdYDkXHUwMDE14yZlSSDqkvdAnSFcdTAwMTOCQc1cdTAwMWa8P+hePdTtbf+8tXqfX+k+ZU9Palx1MDAxYr9cdTAwMDFELSjOaFx1MDAwMnKU2oyqemJ4goDL84+SU05Si1HOhiHY2KWDqE5mo4qMhrbOze/pL/dcdTAwMWa3irV+Nerht51G+LbWbFx1MDAxY16m3dNcdTAwMTPVXHUwMDE0zqMmraRcdTAwMWNcdTAwMGJEP4ZRL6RGnlx1MDAxOXScS5RajGpcdTAwMTlcZtlRXHUwMDA1y1x1MDAxNnjyesZcdTAwMGVcdTAwMTNvViyQ7Fx1MDAxNF0+51x1MDAwZl1cdI7v3GOje7h3d9O+OE65XHUwMDE1zSApXCInXHJ9T8NBmzGAXHUwMDA2KbxyZGbJxGq06dVLXHUwMDE2jFx1MDAwZc7EXCLZy4FP9CFcdTAwMTGfarDvK/38XGK9fixtntc7XG6vck+79Zp1p/mNX1x1MDAxY1x1MDAxOJ0j18mTXHUwMDFjMkhUTnm0XHUwMDE4x8dcdTAwMGJGXHUwMDE18Vx1MDAwMFx1MDAxN2hq6I7ImIVKXHUwMDFiRulcIqKxuGxcdTAwMWKgflx1MDAwNkadXHUwMDAxSYNZYIep0qna0tnZXHUwMDFl7LXWWi6f7dU2ittpt6JKXG5imlx1MDAwMZ3SwDl3scSZp1x1MDAwMVt3gnRcdTAwMTSpfdLMhJ70klF06OM5XHUwMDE1y4FQlIleXktcdTAwMWI4wUvOT0WDjGrdXnHLV/bLdZm9KPUy+82UI5SMqHFBXHUwMDA3IC5cbkHqUUFcdTAwMWasXHUwMDE0wWrSIVZLk+IkXHUwMDEyXHUwMDEy8kRGzPJF7kNivmhcdTAwMDDet1x1MDAwZTC/mi82a+q6vbt397x6dHf90No9vNjIplx1MDAxY59cdTAwMTmPwnvveCeNqCbZ0TGAXHUwMDFhwVx1MDAxMlx1MDAxZr2G4FCnN11US2LTUiu5dEw0OeJEXHUwMDA2xXtcdTAwMGIwv5M/zV5mzm4vXHUwMDBldlx1MDAxZUqXvZ3DSvT4ePiQcogq7YVlXHUwMDEzScuR9LBcdTAwMWR38Vx1MDAwNGBFQp+UiFxyXqU3595rXHUwMDBmYVx0bWiQifEmtEbS7YpccuR7XHUwMDAw3djBx+vzvUb2oXKcW1k9K1/1XHUwMDFhaY+IMkC59jOQXHUwMDAxMlxcXHUwMDE1Mlx1MDAwMlBcIp8gJE2Id1x1MDAwMNbFs/TShlDl+Fx1MDAwZoTlU/OYXHUwMDFjbfJBk+dwXHUwMDBipDk9bD12L052rrPr+cra7s3lbqlWuEg5RjNkQ8k7WpBcYs75MJbOTCD1XHUwMDAyLf1IonokptJrRlx1MDAxNV3wNIKli4hCop93dL+0XG5cdTAwMGJcdTAwMTSF7N+qu25me791f1x1MDAxNG3tZVx1MDAxYX24Okp7vElcdTAwMDXPRfRSak1IRTOWzVx1MDAxY7SwXG5cdTAwMDZld4RiSK9WUoqWkLTLXHUwMDE3XHUwMDEzXHLJKSRcdTAwMGWR5k3Nn+SUr9dUpZnZrDeyuTpkt7ea4fFcdTAwMTdr+XmSnJwg/Fx1MDAwNfBcdTAwMWNQXG7ju0qEUFx1MDAxM1x1MDAxNDJ2NG/apFx1MDAxNqHOaSBFh8tmQuPJj1x1MDAxM5tKpJPAhlx1MDAwNYzoxflxqVx1MDAxOY7L19/qJ7kzgPZze/Vbyt28dryzSYLdWmKl1o3F7JEuW1LypPdccug021DtPTFcdTAwMTFizMtcdTAwMDZRSC5QXHUwMDBl/oWgzc9Ec1x1MDAxN7utrfrO1km2vbNcdTAwMGZn3dNv552098oxXHUwMDA2XHUwMDA0KkkslFxiqcUwqpaCVVx1MDAwMjmzRFx1MDAxYjfIS0wvRI1RwaJbukYkM7LwXHUwMDE077NcZuJcdTAwMTjzU9HVi8b5Q+2qW9SruF19Lq5XXHUwMDFl71NcdTAwMGVRXHUwMDE1UJCY9zJcdTAwMDRwoJRxY1x1MDAxOLWCXHUwMDFjqHaas1x1MDAxMrVJr6BcdTAwMGbIyVx1MDAxNH75mKhKtKJcdTAwMGVcdTAwMDJcdTAwMTeULZAnenSw53tcdTAwMTnzTZ093TRcdTAwMWX9pu5cdTAwMTa3KylHqJWEXHUwMDBl0sE0vVpcdTAwMWGa49F9JXRBOCBNT1x1MDAxY9Xxmk0vQoHEbXCwbKnMITlqT6RGWVxcpGZpo1x1MDAxODXa94fFTVS7qye3W7eP69FJ6lx1MDAxMcpunpio9GSFlFx1MDAxYzWhg6Bo8JKsq1xytFjTmzyiNC1cdTAwMWaJy6jmXHUwMDEzbSjpKFx1MDAwMK4on1x1MDAxYqHrfu8hX99Yf46uo1a9oXDreusu5VxiJespJFx0ds3uQlx1MDAxYol+XHUwMDFjorw1XHUwMDFhjKL16nyc56VcdTAwMGWjllx1MDAwYlSZMS9cdTAwMTlGXHIkbi2ROFxmysSbXHUwMDA2fFx1MDAwZqO1h8LO5j5G/cvi+qbfLeDJXiUpwWlcZmujXGJcdTAwMWTPxnx7VbnQqVVcdTAwMTaAqJ+nryirILCe407jUXuuTnaKayp5YpTDXHUwMDFmym/qtlx1MDAwYo1Oq9AmXGJMwlx1MDAxNMBcbuc80uRjMFx1MDAxMCvlf4MpXHUwMDAxVFxmWkqR7fC0eKY0XHUwMDFks7yPq+3HwfT1wlx1MDAxMFex+W76y+3DS73SOj/tnFfr7dBw2WH3rlx1MDAxMVx1MDAxMFx1MDAxNtrt5uPXtyt//TnrfbNcdTAwMTf7N7LwzVx1MDAxZNROS/KoVexGe7Az3/u+/i9cdTAwMWSLS2HMRE7sinFTXHUwMDFi8t/zx1wiVq6qhe710eFpM1MzxdLm6emWTYpFvG95/YxuftYhgCPJq1xmhLF6QIPCWGXCoJApXHUwMDFlinqP/a9Wq5OrylhcdTAwMTTAuV+aVSYqnFxcVeSYROBCXHUwMDA2LoDTXGJT4lx1MDAxMEFzzS180qq6KJ+v6fvq/UHtMDjXXFx1R9mzXHUwMDBmRb/+XHUwMDFj9Mvk9kGsmpm3zo19X9rea5SK3e7T3lx1MDAwM7qV5/K+7fXTvptcdTAwMDEgjOEulYRtXHUwMDBilijOKPytXHUwMDEyhqy9QmK/I1x1MDAxNd1poz9eXHUwMDE5K+0nblxif1x1MDAxNkJVXCJCPWkmXGZ+gcyvnZXcw2N0m7P5y/31zdWcXfUnpbRDVFx1MDAwNSu0XHUwMDAzb7ivhVTaTiBUKqclUXSu504zQefAfoBPJOifg9HgXHUwMDEyKURQXHUwMDE2QclcdTAwMDXareL5Vva8f++3V9dO91x1MDAxZexFZNa2f3FcdTAwMGa2efaEtfBkfFx1MDAwMtHv4INcdTAwMWZvXHRstTBcXGuIgXx3mpuzXHUwMDA0XHUwMDBiIZBcdTAwMTdYOjNqXWKgQ0uSNI5z8+ZcdTAwMDZp/2nlNipEXHUwMDFiu/mTfj5/XHUwMDFlitE+tt9Dc8czrH4qzZVcdTAwMTIsUVlcZsDZ3GOOnniuJ1x0SVx1MDAxMHWcvvAzaK5cdTAwMTdgaVxmhrvAxYuS3vCpmGtcdTAwMWKlXHLpRtKxKl6q85q0oFFcdTAwMWFjP3C7bVx1MDAxNs3Vuv+01+mo00fbULtPR7vhtN6Zj+bOXHUwMDE0j79cdTAwMTF9rkRRvdWZvqbi4dDJ8DZ5a7dcdTAwMDB7XstsP++cXHUwMDFl5Ez/yHRu8q3Nx8MoKXg4c0l9ntV3xDz4XHUwMDE0XHLgzqrxzoUvNlx1MDAxZoiX0HSQaPM/WFxcXi2AXHUwMDA2PbmkWDfSiqXXW6JG8fM63pZcdTAwMTSSU9B+UJ05mFx1MDAxMzO+olTgtEKDXHUwMDFm2HjzJy2pVEE/+Vx1MDAxNFx1MDAwNHYl5G1cdTAwMTZIg3uS9cvaw65X6nGvX1xyuc1e70KnXHUwMDFi+p4sNZelO4K2XHUwMDFl4Xcv2CfGzt1cdTAwMTSRXHUwMDFiTehcdTAwMWZMdq9cdTAwMTaK5DV+XHUwMDBl+klCoYOPXGZGplx1MDAwMp7WJqe6O1KUVoGeXHUwMDFmn6v9o/OTi+tOI9tunDnI3O1cdTAwMDBuplx1MDAxYp/BXHSymEZLXHUwMDFmpFxyelxmnUagMrRCOVx1MDAwYlx1MDAwZfyPNVBSuog4hY5/XHUwMDA0OjXNo9JcdTAwMWZcdTAwMTnUS1x0XHUwMDFil8kndHCqXHUwMDAyoF6gVeL9zmZ9m5mDv3xu9Fb07aPZ/MUtvuag444wXHUwMDE4zEAzXHUwMDAyeFx1MDAxY8tz55O4gkRDMs5an+L+SUppx7s9n9hA6ZOCXHUwMDFhJjHuRuaTi2P0XHUwMDAy7HbT39d92K0211x1MDAwZdYuuiv1u8re+i/u3D1fVCNcdTAwMThcdTAwMDAk3cjB31Enz+1p+DaQTpPcdCHFSZo0OuNR/o5efnZv+eTG3exz/FwioeHccbZn9n3ufl1urO7tPeRWdWI9W0qcPHhB3JKMJPlZhHhcdTAwMDX0IP1ccjTZUOctXHUwMDAz1OhcdTAwMWarZStVyrZcXJiEpyGma7w03rHYNVx1MDAxMGP9w6hcdTAwMDb3a8RAbIOjK+Bikai/e9Nw0MNcdTAwMWL1cVG311x1MDAwYlMlmNuEg7Osed4h4bpxlYG1rauD43RKsNlcdTAwMTY6OfygXHUwMDAyV+CCWaBcdTAwMGLjaiVTUCc32G19ezKrXHUwMDFi/ep+dj31LIJcdTAwMTiE8NpcdTAwMDGfkMSngIztjXB3JqJafFx1MDAxNJ2zZFx1MDAwNNPb4lx1MDAwZVx1MDAwN0klbuksNGJi7pIja1x1MDAwMUTv5ofoZdRZMZv99fNLmStcdTAwMWM63y1srph0m2hlQThNXCLLKk+Ky+FYijJYXHUwMDExuIux5NxcdTAwMDX8MSVmS65SnVx1MDAxMicgYyBM8EpcdTAwMDXNTaLklPxcbqVcdTAwMDVBVzuSilx1MDAxMm2Id+J7q5ajb1x1MDAwMPCJZyV+llx1MDAxMU0uRVwiXHUwMDAxq1x1MDAxN+rBeHnfPzi+XFwp7Hb3VvfMt2u5tqHSv72sjeBMZOBEedQgx9uEXHUwMDEygtFcdTAwMWFjiErwIWCptaGKY1x1MDAxZN5/ZuOGT8IoJDp6i8py2/r5Mdpdq1x1MDAxZtTqnZpfqdrNZuam7s8rxbRjlFxcPFx1MDAxN1x1MDAxYpFcdTAwMTM33lpC4jhGg+BdTFx1MDAwNVx1MDAxYy2K979LXHUwMDFiRlxyXHUwMDA0hZzLumRcdTAwMTDFkJhcdTAwMDLhPdH7kbMkvpujvH5cdTAwMTZ2noprV1x1MDAwNXv03LooVI5LV0nndqfEz1x1MDAxYnRCa6KiyFx0yPFTzFx1MDAwN1x1MDAxMVdwpJJcdTAwMDLRcSOdkeHH8JmkxZRcdTAwMTGWKCTH80n7uVx1MDAxMCuHXHUwMDFhOvpg+bgxXHUwMDAyhiXD4eLtW4dp9J7bNnxgs+XXXHUwMDBiU8XYdr7uorPGc7dTkJdrx1x1MDAwN1x1MDAxYrf95+ZvKMZwRpI+V1F6s0CPx0u1WSr1c4f1401501x1MDAwYs+bZ3oj6azQ1CxcdTAwMDBcdTAwMTTKaauY7Mqg3CjRdSTUyFxi6OCN1yb8WI5+MtPldtJMcpFwztUgU0w0au5FKWXwclDQMsF0ud00t6/4vGK8XHUwMDBmQ2i30u9OXHUwMDAzp3LJsVxc7oRcdTAwMTRcdTAwMTSZrLnReVxczt5cdTAwMWZen8BW6/qxf5ft3lQ6/indJSSWJFx1MDAxMJeGaFwiXHUwMDExXHUwMDE2LbjR5Fx1MDAxZm9xkL6mpSH2qGdcdTAwMWO2XFxcbrqgXHUwMDBis9E5NftHw1x1MDAxNCyaiUhcdTAwMThcdTAwMTltXHUwMDEz4LNcImE/qzjkfPPYlTKlQpRvm35/LZ/L9/TpLzPqQ2Q2XHUwMDFi3dP68yDnXHJHXHUwMDFl3Szc1aOnXHUwMDExdFxy1lx1MDAxMlxy8KHe7vZcbtFVh14oXmcuNv2dXG5ccmDwjjDy0pWofs1L72tUqY6uyW6dbszb5W4ztlxyUqKhXHUwMDE06O3aO+Xxr9Rs16/rjUJ0NmNYM23Dy42eYlx1MDAxY1xmJFx1MDAxN8BcdTAwMTCRQJLBbv7NyNmQ+lx1MDAwNZmB37VcciR4hbVOST5cdTAwMWXa6nhiwOD1XHUwMDA2XHUwMDA0Yc5KOyhcdTAwMGYwyds877ZcckZIMjpcdTAwMWGZXCL6XHUwMDAwckpmoOZsKms54cSCJn5cdTAwMTaT4a+8XHJ5L1x1MDAxZFGm02/F7lqh3V2tN8r1xjVdXHUwMDFjWo+vlZeP3pnDyfCXK7ReOK/BQSDRXHUwMDA0XHUwMDA3xlxy78tgZZd6/EUyUlx1MDAxMPHmU83o/jJBRv36pDcz9rXSKH9/ULOLJ99cdTAwMDZllFxiViHNXHUwMDA2KFx1MDAxM+tcdTAwMTdcdTAwMTZcdTAwMWaSXHUwMDEySlx1MDAxOUtiXHUwMDE2XGZxdU5cdTAwMTWaXHUwMDE4UlTodNead3f1Lt36o2a90Vx1MDAxZL/Fg3u5wlx1MDAwYrtWKUyYXGb6SvFr41x1MDAxNqDF7zjqXCKG//syXFwkg1/e/v+/f059diZcdTAwMTHDg6uT8Fx1MDAxZL7hXHUwMDFm8X9cdTAwMTe2Xsonx+8kLVry5zHy8z3rNdtxpdJ6KS3QOrJbhGtcdTAwMWJvXHUwMDE19LJcdTAwMDFC64NjXHUwMDE2XHUwMDEySPU5TN5cdTAwMDR8P7FcdTAwMTGBXHUwMDA0XHUwMDBlcptcclx1MDAwMrKOXHUwMDA1iIfGKzhcdTAwMTH0YEOKlIGKPeUtiUJcdTAwMDWn0Oj/J7brbysgXHUwMDA1YjDG6YCKT01cdTAwMWJcdTAwMWVcdTAwMTT3ZktQOJo3I1x1MDAxNVwiS3ZcdTAwMTdmW67Rb/E7XHUwMDE5kERcdTAwMWPxz1x1MDAwNIJcdTAwMTY0XHUwMDFmScJIJ1x1MDAxZlx1MDAxZWJg4PRcdTAwMTdoUNLMPX27881Kq5S96Fx1MDAxY9p+gMz5uzJcYj5PXHUwMDE3YVx1MDAxMHS7yWxbIM0s5WhcdTAwMDZcdTAwMDHXO1xuy3WPpO6NsTPiqu83XHUwMDFmdi5dxCm2XHUwMDFl8Fx1MDAwM89cdTAwMDZ5Q9DHXHUwMDE2t8/URVf3xO7v1srX61fkl7bPjlx1MDAwZXdKtWXQRS+zmTZZ9DKq9/GKeDbq+Ma1I7bpXHUwMDEw54+YzMZTXHUwMDFhaYWxROj4XHUwMDE4a85xXHUwMDA3r/W4ZdBEO8gpWe79Z2adKfRuy6CkXHUwMDEzSFx1MDAxZk0kmlx1MDAxMG7JXHUwMDE2T1x1MDAxYVxuJZw2xH5cXOBunyOnjv5cdTAwMWRQ8STrTDp3XHUwMDA0XHUwMDE35Vx1MDAxNbNcdTAwMWTMXHUwMDFiZVxiQlx1MDAwN1x1MDAxYYjj4XB3vqn6Q1xuyZEwTpuRnL1qzd8+9edoXCKNQmlOLSA/41xyn1x1MDAwNDRdXHUwMDE1ySB5XHUwMDEzw3BKXHUwMDA0uN9cXFx1MDAxNSVDmH8yk+hdkNYkW6/kI6dcdTAwMTTdYD4+XHUwMDE55t+OmO220mi+vFx1MDAxMazypfWM7ripfrVeXniUXHUwMDA2XHUwMDFj60NcdTAwMDXJeWHvt15AXG5cdTAwMWZcdTAwMWTjXHUwMDE4XHUwMDFkn+Q5bTNOOC8t+uBcZveopIFMWC/ChVxun3mA9C+1XzHrpCxHXHUwMDFlJWhWSCHWyfLNonDvPO2tNsRcdTAwMGJfOsfMNmC/rzRKxtLL5Vx0XHUwMDE4LWhGktSR0cl9XHKc9U5cdTAwMDU1f2jlqLb12I/y2aen5zUwK1lcdTAwMGKN6vqvNFwi4XtGJGOEJttgvLKaO+PhWFx1MDAxZnEwdOctcVx1MDAxZnRcdTAwMDGdXHUwMDBmyZ03fro6XHUwMDFhXHUwMDFjV1x1MDAxMz5SXHUwMDFlpUdokJSoik6p3Yyiq2a12qmkYv9lyqje56qNSc7iRlx1MDAxM3RcYvFcdTAwMWVN31tls8uL0+iqM1x1MDAxZYQyoFxm52gr4+xo4oBccnKwc0ve3HJTuJ+wyJRWwjpuXHJBJnTQMHiKq3ZGeD6J2nuJ0pnJXHUwMDBlZJpcdTAwMGY78fYzj+T5XGJfPe7Vvu/FZzcqiHtxy90sUHuliNwy65zmw1x04jS50lx1MDAxYjCGhMHvzfdcdTAwMTORNLg6gaFcdTAwMGby0zqGhHFcdTAwMTNiXHUwMDEwnEK5QFx1MDAxOPPk2+1h4/rhvH7Tu8xcdTAwMTbAnJXVbpRuR01cYlwi4cjnXHUwMDEw8HnPwY9cdTAwMDYrXGJ/QkmQLtD61Mn5y3NcdTAwMTiQmd1BlZtiOKY4a1KEgdjCXHUwMDA3ptK/4Sj1vVZ+jFxuPH3515dXn/uUXHUwMDA2XHUwMDEyMDKe97l/mJF94Vx1MDAxMYFInVlgXHUwMDBiYuZkp3LtKmmFRVI33rjByYvjqd1GgOSFTSpQk977ecuXXHUwMDBmXSFZwz1cdTAwMTFAWaunKHYrhaNRKkDHXHUwMDFicmFcItyovFx1MDAxY5zO6pZjJ3O2L/hcdTAwMTLfydRcdTAwMDEtd4iXdO+k1zHP97evt4KTbGzgXHUwMDE2rlx1MDAwNpx/Z1x1MDAxMsbsXHUwMDE2uyNjQq04uKkgeI5cdTAwMDW5yTF58lx1MDAxYtrymc4k9bjE5ffmXHUwMDFmiVx1MDAxMOafzFx1MDAwNHo/iH+ATORcdTAwMWbacXHwXHUwMDAyuf/6KNNej45XQj/KbeXqd7t5XHUwMDFharotmCegcUpcdTAwMGJcdTAwMWEksWZh7JxcdTAwMWNnheJGpTQxge78XHUwMDBm7aHONmAxUzCTf1xmjkb7wD2RWfQjXcXWP0Y/+kN3309cdTAwMDP9XHUwMDE4XHUwMDE5z/voR0imXHUwMDFmoIF7XHLi/Gt39lxcp3HtqsDcXHUwMDAzjFx1MDAwZsqSPYTRXHUwMDAwXHUwMDFmXHUwMDE5USFJuVx1MDAwMlx1MDAxOVN6woyTXHUwMDAyy6Q9ZPX9S9c44YPUJIWBXHUwMDFjKIQpzZKUJDOj2HCDJlx1MDAxNaPCZFx1MDAwNqhX7Fg/Mnn8V3KP2Z7gy+heJoRgOFxiSk7eklx1MDAwMnSTrj5cYj5cdTAwMDLMXHUwMDE4Pi7Dqu/sXHUwMDE1JI1pdpOQ2Ji474CWSiMqLiG2XHUwMDEzw5ncbf2diEZcIl75JzNcdNVcdTAwMGZcIlx1MDAxYVYlXHUwMDE2WVx1MDAxMes2ltu4z1x1MDAxZivdcFtr/d55XHUwMDFlL+Xp7W29XHUwMDA251Eu3VRDXHUwMDFizYeL8GntXGauXHUwMDAwo1qJXHUwMDA0XHUwMDEy0XCliYrzcVxi8udpJT0tR91P5HVcdTAwMTKpRlqTXHUwMDFmeMTuLKqRrlLCj6NcdTAwMWFf/vN/XHUwMDFhL9tccrNTrdzo23wm+5g2xPdcdTAwMTFcdTAwMTKDyYXE0nHXwlx1MDAwNXo6zUZEXHUwMDFhVzj3U5JcdTAwMWWDV1LjeFc85iNcblx1MDAxY0iwmlx1MDAwYnh/KG1h5vq2ms9cdTAwMTRgqahJzGM8WeRtuVx1MDAwN1x1MDAxNCQ1XHUwMDE190tcdTAwMDfOzZ2sJDbcf1x1MDAxMlxmLEf21Wx3McZHXHUwMDFjOs1HOkpn6Vx1MDAwNlx1MDAwZWup3/y/XHUwMDE3XFz+oXmrgFx1MDAxOKj/Xl53YvbVzFL50UFxXCKfpYF5zSe9hclcYlxyXGIjueGic0FcdTAwMDZE+1tTlGRcYvNPZlx1MDAwMr1JXHUwMDE05Y/XT/haaLVOu4S5t9kgNNfLr5Z9+DW/PtQrj6vT91x1MDAxZXn78Y/XXHUwMDFiytanwl/233/98df/XHUwMDAx7Fxyf1x1MDAxNSJ9 From 8540900d3b5d7e6e53ba456ee63746a84a117f08 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Sat, 4 Feb 2023 17:16:00 +0100 Subject: [PATCH 88/93] add test for extend_cell_length --- tests/test_strip.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_strip.py b/tests/test_strip.py index 0dc082c2b..40f3975fe 100644 --- a/tests/test_strip.py +++ b/tests/test_strip.py @@ -83,6 +83,14 @@ def test_adjust_cell_length(): ) +def test_extend_cell_length(): + strip = Strip([Segment("foo"), Segment("bar")]) + assert strip.extend_cell_length(3).text == "foobar" + assert strip.extend_cell_length(6).text == "foobar" + assert strip.extend_cell_length(7).text == "foobar " + assert strip.extend_cell_length(9).text == "foobar " + + def test_simplify(): assert Strip([Segment("foo"), Segment("bar")]).simplify() == Strip( [Segment("foobar")] From e81ac17c6c947b3f28a4b5b71addfd8b93e9718d Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Sat, 4 Feb 2023 17:35:09 +0100 Subject: [PATCH 89/93] revised copy --- docs/guide/widgets.md | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/docs/guide/widgets.md b/docs/guide/widgets.md index 9470d123f..947bda5df 100644 --- a/docs/guide/widgets.md +++ b/docs/guide/widgets.md @@ -310,10 +310,10 @@ The `render_line` method calls [get_component_rich_style][textual.widget.Widget. A Line API widget can be made to scroll by extending the [ScrollView][textual.scroll_view.ScrollView] class (rather than `Widget`). The `ScrollView` class will do most of the work, but we will need to manage the following details: -1. The ScrollView class requires a *virtual size* which is the size of the scrollable content, and should be set via the `virtual_size` property. If this is larger than the widget then Textual will add scrollbars. -2. We need to update the `render_line` method to generate strips according to the current position of the scrollbars. +1. The ScrollView class requires a *virtual size*, which is the size of the scrollable content and should be set via the `virtual_size` property. If this is larger than the widget then Textual will add scrollbars. +2. We need to update the `render_line` method to generate strips for the visible area of the widget, taking in to account the current position of the scrollbars. -Lets add scrolling to our checkerboard example. A standard 8 x 8 board isn't really sufficient to demonstrate scrolling so we will make the size of the board configurable and set it to 100 x 100, for a total of 10,000 squares. +Lets add scrolling to our checkerboard example. A standard 8 x 8 board isn't sufficient to demonstrate scrolling so we will make the size of the board configurable and set it to 100 x 100, for a total of 10,000 squares. === "checker03.py" @@ -326,9 +326,9 @@ Lets add scrolling to our checkerboard example. A standard 8 x 8 board isn't rea ```{.textual path="docs/examples/guide/widgets/checker03.py"} ``` -The virtual size is set in the constructor to match the total size of the board, which will enable scrollbars (unless you have your terminal zoomed out very far). You can update the `virtual_size` attribute dynamically as required, but our checkerboard isn't going to change size. +The virtual size is set in the constructor to match the total size of the board, which will enable scrollbars (unless you have your terminal zoomed out very far). You can update the `virtual_size` attribute dynamically as required, but our checkerboard isn't going to change size so we only need to set it once. -The `render_line` method gets the *scroll offset* which us an [Offset][textual.geometry.Offset] containing the current position of the scrollbars. We add `scroll_offset.y` to the `y` argument because `y` is relative to the top of the widget, and we need the line relative to the scrollable content. +The `render_line` method gets the *scroll offset* which is an [Offset][textual.geometry.Offset] containing the current position of the scrollbars. We add `scroll_offset.y` to the `y` argument because `y` is relative to the top of the widget, and we need a Y coordinate relative to the scrollable content. We also need to compensate for the position of the horizontal scrollbar. This is done in the call to `strip.crop` which *crops* the strip to the visible area between `scroll_x` and `scroll_x + self.size.width`. @@ -342,7 +342,6 @@ We also need to compensate for the position of the horizontal scrollbar. This is ### Region updates -When you call the [refresh][textual.widget.Widget.refresh] method it will refresh the entire widget. The Line API makes it possible to refresh parts of a widget, as small as a single character. Refreshing smaller regions makes updates more efficient, and keeps your widget feeling responsive. @@ -360,14 +359,15 @@ Here's the code: ```{.textual path="docs/examples/guide/widgets/checker04.py"} ``` -We've added a style to the checkerboard which is the color of the highlighted square, with a default of "darkred". We will need this when we come to render the highlighted square. +We've added a style to the checkerboard which is the color of the highlighted square, with a default of "darkred". +We will need this when we come to render the highlighted square. We've also added a reactive variable called `cursor_square` which will hold the coordinate of the square underneath the mouse. Note that we have used [var][textual.reactive.var] which gives as reactive superpowers but won't automatically refresh the whole widget, because we want to update only the squares under the cursor. The `on_mouse_move` handler takes the mouse coordinates from the [MouseMove][textual.events.MouseMove] object and calculates the coordinate of the square underneath the mouse. There's a little math here, so let's break it down. - The event contains the coordinates of the mouse relative to the top left of the widget, but we need the coordinate relative to the top left of board which depends on the position of the scrollbars. -We can make this conversion by adding `event.offset` to `self.scroll_offset`. +We can perform this conversion by adding `self.scroll_offset` to `event.offset`. - Once we have the board coordinate underneath the mouse we divide the x coordinate by 8 and divide the y coordinate by 4 to give us the coordinate of a square. If the cursor square coordinate calculated in `on_mouse_move` changes, Textual will call `watch_cursor_square` with the previous coordinate and new coordinate of the square. This method works out the regions of the widget to update and essentially does the reverse of the steps we took to go from mouse coordinates to square coordinates. @@ -380,3 +380,11 @@ The `get_square_region` function calculates a [Region][textual.geometry.Region] The final step is to update the `render_line` method to use the cursor style when rendering the square underneath the mouse. You should find that if you move the mouse over the widget now, it will highlight the square underneath the mouse pointer in red. + +### Line API examples + +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) +- [Tree](https://github.com/Textualize/textual/blob/main/src/textual/widgets/_tree.py) From f56823a7335c84d10f8353ad33b0475527c23438 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Sat, 4 Feb 2023 17:47:49 +0100 Subject: [PATCH 90/93] better diagram --- docs/images/scroll_view.excalidraw.svg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/images/scroll_view.excalidraw.svg b/docs/images/scroll_view.excalidraw.svg index e06ee4634..e2fc7f3f3 100644 --- a/docs/images/scroll_view.excalidraw.svg +++ b/docs/images/scroll_view.excalidraw.svg @@ -1,6 +1,6 @@ - + - eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nOVdaXPiypL9fn9FR8/HuehV1pJV9VwiXkx4wyve8cLMhIPVYMuAQdjYL+5/n0zsa8RcIlx1MDAxYdput8zQXHUwMDExbVx1MDAxYiEopFOZ52RlZv37j2/fvkdP7er3f377Xu2Xi2Gj0ik+fv+Tn3+odrqNVpNcdTAwMGXJwd/dVq9TXHUwMDFlvLJcdTAwMWVF7e4///GPu2Lntlx1MDAxYbXDYrlcdTAwMWE8NLq9YtiNepVGKyi37v7RiKp33f/i//eLd9V/tVt3lahcdTAwMTNcZj8kU600olbn5bOqYfWu2oy69O7/TX9/+/bvwf+x0XWq5ajYvFx1MDAwZauDXHUwMDEzXHUwMDA2h4ZcdTAwMDNELcaf3W81XHUwMDA3g1x1MDAwNXTSoLFcdTAwMDbeXtHortPnRdVcblx1MDAxZK7RmKvDI/zU95O9XHUwMDFhNKs3/aPyaqZz6m+2TPtqc/ixtUZcdTAwMTieRE/hy6Uoluu9TmxQ3ajTuq2eNypRnT997Pm387otulxuw7M6rd51vVntdkfOabWL5Ub0RM+p4fd7uVxi//w2fKZPf2WcXGaMXHUwMDA08KCc1Eq+XHUwMDFk5LO1c4E21iqhpFx1MDAxNN6AXHUwMDFhXHUwMDFi1lorpDtBw/pcdTAwMGYxeFxmXHUwMDA3ViqWb69pdM3K8DVSulJcdTAwMTWHr3l8/bKIgdDWXHUwMDFhLbVxKLx+e0W92riuR/RcdTAwMTIrXHUwMDAzp1xmXG5EIYVzbjjObnVwPyQorem4t29H+OPb25VcdTAwMDE2/jd+xZqV1yvW7IXhcMR8YCOGp+E5vXal+HLbXHUwMDAxrTGSXHUwMDA3apx/O1x1MDAxZTaat+NvXHUwMDE3tsq3Q6RcZp7968+fgKhcdTAwMDObXGJR6b31XHUwMDFlcH6Irudrtzf9lf37nDo/OqmdPlx1MDAxNPLrKymHqMFcdTAwMDCMtqi0tMo653FcdTAwMTKkSnmjgHCqLGBqQUrD195cdTAwMGI0S4ZRKyBcdTAwMTGjViqh6ZKouTFaPd3aoctXKlX08WFu2+3uXHUwMDFl3uqUYzRcdTAwMDMqXHUwMDEw4J3xYMGhczBcblKDKtDoXHUwMDA0XHUwMDFkXHUwMDAy6fT4uNJcdTAwMDNRMEo5+lx1MDAwN0tcdTAwMDZR9C5cdKLOaDBcdTAwMDTQ+Vx1MDAxMXpxUNrfvTSu1Do8uitePGxcdTAwMTHB6f5ehIL4oVx1MDAxOdWBJUJjpEPwqGNuhc9H5Vx1MDAwM1wiXHUwMDAx5FC0XHUwMDE3Rlx1MDAxYplahEqwVtNQl1xyoVx1MDAxNpK5qDZIvo+My9xcdTAwMTC9y1x1MDAxNIoqW17Jn1x1MDAxNOq31fX64cZmXGJpN6JcdTAwMGVcdTAwMDKiodoplFx1MDAwZYSOmcmnXHUwMDE3slx1MDAxZZBcdTAwMDXVNF01MVVrUlx1MDAwYlGwRFJcZvmAZSOj1iWSUSnI/4Ej4zE3Rr0tolx0Wzc3vXJcdTAwMWWuwl6ldWD3U45RkCbwKFxmuXmCgI8polx1MDAxNzdviIuisE4qLVTs/qdcdTAwMGWig3MhblKWXHUwMDAzok7IJIhcdTAwMDLdXHUwMDE39GJ+P9953qmv2I19XHUwMDAzW1uqmll/zlx1MDAxZdbP0u7n6c6SoydcdTAwMDZnpaGfxo1qesJtIFx1MDAxZEl5RKm9lGmmosKBs0IsXHUwMDFkRFWyp1x1MDAxN0JcdTAwMTlDZtTOXHLSXHUwMDE1VfLR3faza9W3Tjby51A8a6Zd0mdAXHUwMDA2iiNLpOtcdTAwMWR5XHUwMDBlXHUwMDAy41x1MDAxOEhccrFRIaX1ZKPQpzfuXHUwMDA0YJFkPb3PkoFcdTAwMTS9T1x1MDAwNCmi9lbCXHUwMDAyodFVr1s+im5XS91beYdcdTAwMTXoXjbbKVx1MDAwNyl4XHUwMDAyoSZaXHUwMDBlWlx1MDAwMXl8OerrtUPChqBcdTAwMWZcdTAwMDIleVx1MDAxNtDpXHUwMDA1qVwi/DhLZy9cdTAwMTlInU3mo544XHUwMDFh6dj5MbpcdTAwMWL26+t1VchFolx1MDAxYW7g41PY7p+mXHUwMDFjo0qpgLimIH2ohUM5XHUwMDFlvyeIXG60XHUwMDA0XG4rlFx1MDAxNClcdTAwMGWN0lx1MDAxYyMnIIxautioMUlcdTAwMTBcdTAwMDVPqp7unvFzY9TcboSlSu0he/JgRKVwXHUwMDExXjU3wpRjVKJcdTAwMGWUI/vjQaNRMYH4XCKZIHCEXS05NCVTbERcclqQxEiXXHUwMDBmoYnRe1RI31x1MDAxN+z8bHTtVl9GcFO5zu+XO2ePXHUwMDFkla9cdTAwMTeLaZdMbEW19sQzXHRcdTAwMDFcdTAwMDDCjSCU404g0VleXHUwMDFmXHUwMDA1hymGqFVcbvi/T4OoXHUwMDE3n1x1MDAwM1GFSVx1MDAxMCVcdTAwMGJK1Mzg/Fx1MDAxMDU3l1s7ezZ/sKKrLblzcHbWLti0Q5R8RUDYcFxuPVx1MDAxMVKlJjAqXHUwMDAzIyxcdTAwMTFcdTAwMDBJ2km79EKU5pgjovJ56/SfXHUwMDA0UadcdTAwMTP9vFNSL2ZF5daKPVx1MDAwZXdcdTAwMWGFh7X2zfWBvKuJm0LK3bxcdTAwMTYqQOmc8F57pt5jbp5IXHUwMDAwTVPS+s7LVCt6NM5qa+yyXHUwMDE50fh3XHUwMDFkZ6JcdTAwMDROT994gVx1MDAwNab96OqhoW/7Z+3V+8JK9JQ7Oa5vfFx1MDAwNYiisMRFjXNqLHjPQSfiqk5cdTAwMTBcdTAwMTVgtZ9escTJMNaCXjqEqkQmXG7gnFbxVetcdTAwMWZcdTAwMDH0cu9xs1Tv18KeO99u+vO1VvPgMu1unmhmgKRcdEkmXHUwMDAxOrr6dlxmojZcdTAwMTDS8Y1xyJlEqYWoXHUwMDE0XpFcdTAwMTVcdTAwMDXzaSGnT6OiiVbUXCKQXVx1MDAwMT+/nFx1MDAwZi+fXHUwMDBiXHUwMDA3WDZHd/jYjFx1MDAwZXbvbjpcdTAwMTdHKTeiXHUwMDE5R3JcYoVcdTAwMDKnXHUwMDE0XHUwMDA3bMZcdTAwMTDqRWBcdTAwMDG1XHUwMDAxXCI80un0iiVtlPSotFoygJJjSzSiXGJGkP2w8y+BXj+Ws2eNLrir/NNOo67xpLDxm4Oic6Q6WVx1MDAxMkPKXHUwMDExk1x1MDAwM+vIocfw8YJRXGK0QU+3XHUwMDA2SO3HTFTaMEpcdTAwMDfpdmr3aSugn2VEZ2DUe28txlx1MDAwM8E/TFx1MDAxOO3WdPn0dNfsttfaWMj16lx1MDAxYqWttFtRXHUwMDEwXHUwMDAxXHRccu9cdTAwMTCk4ZQ76Ucw6lxiPiSjSOuTYib0pDeTxKGzwi9cdTAwMWRcdTAwMTWFZLHkpOLlXG63QKaTXGLrUa+0aat7lYbIXZR7mb1Wylx1MDAxMUpGVKGX3lx1MDAxMFx1MDAxOaWrLkf1vNdcIvBaklx1MDAwZdFSqFx1MDAxNGeRkKolMqI+L2r/WVx1MDAwMI15wck8J621lH5+gJZadbju7OzePa9cdTAwMWXeXT+0d1x1MDAwZS42cilcdTAwMDdoxrrAkqsgXHUwMDBm7ohrXCLIMYSqQEmaplZcdTAwMWHPxVxcqYWoXHUwMDE0RKeFXHUwMDA0sXRUXHUwMDE0XHUwMDEzk+6NRK28WCDP6SR3mTm9vdjffihf9rZcdTAwMGaq4ePjwUPKIVxu0lx1MDAwNpptJPlcdTAwMGJSxHrcx1x1MDAxM4CBpD5JXHUwMDEx7S2kN+feSo5cci6fXHUwMDExjVdcdTAwMTGMXHUwMDAzXHUwMDE0LWptXHUwMDE2yFx1MDAxNt3Ydo/XZ7vN3EP1KL+yelq56jXTXHUwMDFlXHUwMDExZYBy8acnXHUwMDAzpLgqZFx1MDAwNKDEPk0glGE+bjR6m+J1JeR/xi+fnPcz8pml8srH5eGPMPqw+Vx1MDAxOF1cdTAwMWNvX+fWXHUwMDBi1bWdm8udcr14kXKMZsiGknekuehcZqL1Y1x0zVx1MDAwNFJcdTAwMWI4XCI8WnC1tkxx6Vx1MDAxMtBcdTAwMDFLI1i6mKhJ9PPe83KKm9+K+r1buIsyW3vt+8NwczfT7Jurw7RcdTAwMDecwFuuolx1MDAxN0JKQqpTo4vz1suAS1xmueyOUGzSK5ZcdTAwMDBoXG5cdL18QVGfnEBiyX5YsqLzi6VCo1x1MDAwZdVWJtto5vJccpPb2mz5x9+s5ufJccKAXHUwMDAw6I3lkJJcdTAwMWZfWFwiiCpcdTAwMGaO74nkdZvUQlx1MDAxNFFcdTAwMWGSdG7ZbKiTyTZUKq5cbtFcdTAwMGJcdTAwMTQon1x1MDAxZJVb/qhyfd44zp9cdTAwMWHTee6snqfczUvktU1cdTAwMTLsWlx1MDAxMysltjlcdTAwMWG0d3RYk5Inva+MTLNccpXWXHUwMDEyXHUwMDEzQbNsYokuSFx1MDAxMkKl4JpxWETO5y922puN7c3jXFxne8+cRifnZ920N8tRylx1MDAwNFx1MDAwZVx1MDAwNLFQXCKk2vlRteQ1XHUwMDA0ztpB6lxip3anXHUwMDE4okpcdTAwMDHJXHUwMDA2/LxGJClIwkMpwNlcdTAwMDWMqF+9aJ491K+iklxcdVu159J69fE+5VxiXHUwMDA171x1MDAwMtLyVnhv0Fx1MDAwMChcdTAwMWODqFx1MDAwZch9SiSeXG5KqvTqeU/zzFxuu3xEVCZcdTAwMWFRh8pYb1x1MDAxNyigP9zftb2MOofTp5vmo83KqLRVTTlCtSB0kFxmptsrhVx1MDAxMnasXHUwMDFjxKFcdTAwMGbQXHUwMDEwJSeGirw2ml6EXHUwMDFh1NKjWbZEZp9cdTAwMWO0J9cnnCb2Pb+b3yiFzc79QSnrYGf1+Hbz9nE9PE49RNnNXHUwMDEzXHUwMDEzXHUwMDE1lsxcdTAwMTCIUVx1MDAxYjpcYorSNCXzqj0qld7sXHUwMDExkDR/hFtCNT+jbaM3ZiBcdTAwMTDmh+i63X0oNDbWn8PrsN1ogtu83rxLOUTJflx1MDAwNoJcdTAwMDS7pNtPflxcODuOUV5cdTAwMWL1XG7oaqCNXHUwMDEzvdSBVHN5KlPmJVx1MDAwM2n8XHUwMDFijXt6jsGAXyDbvv5Q3M7uubB/WVrP2p2iO96tJqU4jWFtXHUwMDE0oeP5mG9nVYrdenVcdTAwMDGI2nk6i7JcZjLactxpPGzPtclcYlxcUck3XHUwMDA20L0rwynqXHUwMDE0m912sUNcdTAwMTCYhKkxOkC0zlhwnmyDmoQpXHUwMDAxNFx1MDAxOPSUMobIM0zrOqZ5IVfqj4Pp64EhrmL3u2Uvt1x1MDAwZS7lSvvspHtWa3R8XHUwMDEzc8P2XSMgLHY6rcfvb0f++nPW++Yu9m5E8Vx1MDAxY/frJ2Vx2C5F4a7Znu99X39Lx+RcdTAwMDKSc0mzXHUwMDBiPVFLTlwinHt2rVxc1YrR9eHBSStTV6Vy9uRkUyfFXCJ+bnb9im5+XHUwMDFhnTHoNKk848fKXHUwMDAxlVx1MDAwYpRcdTAwMDblXHUwMDA3hUzxUNT43FKoa2U72/zXarXJSaW0XHUwMDBiXGYnf0mWmVx1MDAwZdzkpFwiv1x1MDAxNHguZSCtidKZKfWqaFx1MDAxNNerflx1MDAxY4eeOakuKmdr8r52v18/8IitVTzMnX5F8Fx1MDAwYpFcdTAwMTjmUJ5r7DzMX4Ngy1u7zXIpip52XHUwMDFmXHUwMDFjrjxX9nSvn/bVXGZjXHUwMDAypbhNJSFIXHUwMDFiTVx1MDAxNGdcdTAwMTT/XHUwMDFhXHUwMDAyRdZcdTAwMWVcdTAwMWPR35GC7rTRXHUwMDFmXHUwMDBiSlx1MDAwYr10K8Isklx1MDAxMu2ztFxuuVXz3Fx1MDAxMN1eyT88hrd5XbjcW8+u5vWqPS6nXHUwMDFkouB1INFYxV0tXHUwMDA0SD2BUFx1MDAwMShcdTAwMDVRdK7nTjNB59RcdTAwMGIyK8tG0H2sk8NE12pLs9LoXHUwMDA1Mmjd2WburH9vt1bXTnZcdTAwMWb0RajWtn5zXHUwMDFitnlcdTAwMTaFZUDflHvIO2+9XHUwMDFkb1xurGWguN7QefLeae7N4rXxntzA0tnR+FL8RFx1MDAxZIKXPDlcdTAwMTdYdOs/rdyGxXBjp3DcL1x1MDAxNM58KdxznZ8huuM5Vr+U6PLiXCKRWedccudzj3l6YrooyMrSYVx1MDAwZU2+r1wiNoHp2sBoXHUwMDFhhOImcPFl+DeAXHUwMDAy021cdTAwMDVSkXIkJVx1MDAwYvFynde0XHUwMDA16cjj6Vx1MDAwZlxccZvFdKXsP+12u3DyqJuw83S4409cdTAwMWHd+ZjuTPn4hVx1MDAxOHQ1XGZcdTAwMWLt7vRJpXRi2zivXGZcdTAwMTdeL1DDu5bZet4+2c+r/qHq3lx1MDAxNNrZx4MwKXw4c059ntlH4lx1MDAxZbyRhuH2qvHGhS9G31x1MDAxMDORinWbfWeFea1opJGTU4qlo0VH52tcIkfxLTveppQjryDtoEJz0LRejc8o8Fx1MDAwNFx1MDAxOW5cdTAwMTf+OeLxXHUwMDFkUypV0E/eXHUwMDA3gXxcdN1cdJLq82P/STQu61x1MDAwZjtcdTAwMTbgcbdf8/lsr3ch0419S6aaa9ORsM0lN+M95om0cztF51x1MDAxNd2Wdya814olclx1MDAxYr9cdTAwMDb+ljxcdTAwMWaaj4xHplx1MDAwMp/xnScm+I4hXHUwMDA361HMz3dW+4dnx1x1MDAxN9fdZq7TPEWTuds2LptufHpcZshkKimsXHUwMDE32ssxdKrA0Vx1MDAwNFx1MDAwNcuZcMa+r4lcdTAwMTLIknNTXGL5R6BTau1gpEPwl0Hnj+JcdTAwMWGJS4/ouVx1MDAxMHFcdTAwMTHzeb+dbWwxdbCXz83eirx9VNnf3OZrXHUwMDBlQo6EQa9cdTAwMDaq0Vx1MDAxOKJcdTAwMTEjXHUwMDEwtbxcdTAwMWKXXHUwMDE3TpGQ09rq9NatXHUwMDAzSORcdTAwMDWfpeui5FVyx1malMT8XHUwMDE2yYLL2vuG9Tu11tr+2kW00rir7q7/5s7d88U16DJcdTAwMThHypHjv6NOnnvUcJ1cdTAwMTBcdDXBnVx1MDAxN1KcqEmjU9aJr+jlZ3eXT27cXHLo0SiM5yz8MJf4KNdTezZ/vy42Vnd3XHUwMDFm8qsysaotJW7e2IDYJZlJ8rSOV51HXHUwMDEwSkyHrChazVx1MDAxMFXyfVx1MDAxNW3lakVXipNcdTAwMDBVxHWVXHUwMDE1yqJEy7rXTlwiXHUwMDE0uGuj88Q3aJzeYGyfhr9b1HDcwyr4uMjb64GpKlxms2b/NKeet0m7blxcZcza5tX+UTpV2Fx1MDAwZjKYkvGvrPfct2B+XHUwMDFivVrNXHUwMDE04fjGRe3zJ7W60a/t5dZTzyOIQ1x1MDAwNFai4X2SXHUwMDA09z+YbNIkXHUwMDA08oZ0qMlcZqa31Z1cdTAwMWJkluDS2eh4fcPE+lxiXHUwMDExXVx1MDAxMHaBXHUwMDFji8uwu6Ky/fWzS5EvXHUwMDFloI2K2Vx1MDAxNZVuXHUwMDFiTWI0QEk6S4Ml0YVuLFXZ6MBzL2NhXHUwMDEwjHufXHUwMDE402Ws1qaEXG7A+0B5XHUwMDBi4CU3i1x1MDAxMlPSLEBcdTAwMDaEXYmkXHUwMDE2hdM+3pHvrWaOvoExn7dl4mdZUZsoxrSw0lxi1Fx1MDAwYlx1MDAwMPS+v390uVLciXZXd9X5tVjbgPQvMktcdTAwMTNcdTAwMDDhXHUwMDEzPHd9Jyc9vtlcdTAwMTdcdTAwMDaKXHUwMDEzJTSpMVx1MDAxZs+zSZtcdTAwMTFcdTAwMDVcdTAwMGV3WLt8/Vx1MDAxYrxN3EdJKbpcdTAwMWXWLFB5XHUwMDFjrTX2641u3a7UdLaVuWnYs2op7Vx1MDAxOJWoXHUwMDAyIySZJlx1MDAwNHA40XVcdTAwMTnJimryoVx1MDAxMuhHilva8v6rjlx1MDAxM1qXXGahzifnQXB4xHPzn7khWl8/9dtPpbWroj58bl9cdTAwMTSrR+WrpN27U+LnlcNASuKijtOQ0Y8lQVx1MDAxOLKhnErvaboq4d9X75EkxkBcdTAwMDWaOCSH9GkmYHxcdTAwMWLVoaP3mndcdTAwMWOzbPC1RGEn18RcYuPcveFcdTAwMDO7Lr9cdTAwMWWYqsa2XG5cclxmT5vPUbcoLteO9jdu+8+tL6jGXFxyqr4lm6RcdTAwMTbJJb6EbLncz1x1MDAxZjSOsuKm55+zp3Ijac/Q1ODfXHUwMDA1gFJcdTAwMDNzXeFcdTAwMDFHeS6SUFNWSaJcdTAwMThWKv++RP1kostdpZnjOoI5l4RMMdFOcktKIYj3XHKqWiaILned5lx1MDAxZVx1MDAxNl+wJi+q9qNp2IxvXCI9XkbiPSih5PzG+aiSuz+4Pjab7evH/l0uuql27VO6y0g0XHQgLlx1MDAwZpHSOO20XHUwMDE53ylUu0FcdTAwMDabXHUwMDE0irijfOeOy1Pzf6SZXHUwMDAyRTVcdTAwMTFcYiOTrUh8fVIg7FdcdTAwMTWInGWPsJwpXHUwMDE3w0JH9ftrhXyhJ09+m0lcdTAwMWZcIrPVjE5cdTAwMWHP/LmxPin8bLZ411xin0bQNZhKNMCHRifqXHUwMDE1w6sunVx1MDAxOLzeudjt71ZpXHUwMDAwg3c0I6euhI1rnnnfw2ptdEpGXHK6MG+Ho1ZsXHUwMDFkpExDKdLbdbYr41+p1WlcXDeaxfB0xrBmmoaXXHUwMDBiPcU2qOS+W8BLW85cYju/cZhccqnfkFx1MDAxY/hD20BfL2CLL2n6a1x1MDAxN+9cdDQ4XZlAXHUwMDE5TuRcdTAwMWRUXGKo9y3zTE9cclxmyPyS12J+aH28WeSwXGKG06m05oRcdTAwMTNNU0DF9+Z6JW2O19KdXHUwMDEz6fRasatW7ESrjWal0bymg0Pj8b368tHbc/iYwawt93iUXHUwMDE5XHUwMDExeE6ON7zDs2bmqmOvui62X2ixXCJVXHUwMDA2xqvBqpB2r694s2Lfq83Kj1x1MDAwNzW7fjI+KFxiXHUwMDAwOPOH9+tChTg5Jlx1MDAwNVx1MDAwMWqSRDQuYz16nFx1MDAxOFJY7EZrrbu7RkSX/rDVaEbjl3hwLVd4XterxVx0i0FfKX5s3Fx1MDAwMLT5XHUwMDFkRz3E8Ldvwzky+OPt9//9c+qrM4lcdTAwMThcdTAwMWVcdTAwMWOdhO/wXHL/iP9cXNh4gUtcdTAwMGUvXHUwMDAzIHDr8fmXQGb7rVRcdTAwMWEvkIHTqIleONTxdkEvy1x1MDAxZoR8jlhcYkOSXHUwMDBm3fuWXHUwMDAwp/OawJMlcNxrg1x1MDAxNKeMRYeHxstj4CVqblLs6a7ETOjfSVx1MDAxNOBcdTAwMTGckv/PbJdcYpzzSqH0jlQ71zBPmFx0zktHunfgXHUwMDFj63X0sy3X6Lf4Slx1MDAwNiRcdTAwMTFH/JhA0ILmI0lcdTAwMTZJNyNoRXB0fpHN7Fr5p/M726q2y7mL7oHue5M5+6lcdTAwMDSCz9NFzlx1MDAwN3S9yW5rQ5JZiNGoKtc8XHUwMDA2mmtcdTAwMWZJ3Cul5fuiVtPth55LXHUwMDE3cY6tNe5cdTAwMDN3XGJ5g9DHXHUwMDE2uM/URVf3xO7v1irX61ekM7dOXHUwMDBmXHUwMDBmtsv1ZdBFL3czbbLoZVQ/RyykS1RFvFVcdD1wgW1cdTAwMDRnXHUwMDAzKo3EQmmidLyVNWe5XHUwMDFiK+W4aZBEPMgtae5cdTAwMDCo3ru10FTTXHUwMDAwXHUwMDAyXHUwMDAzR1x1MDAxZu1cZjE54vViiqUgXHUwMDE2LVx1MDAxNfFcdTAwMWbkZFx1MDAxN26mXHUwMDE4M1GvXHUwMDExXHUwMDE162y8TPgrM4vZXHUwMDFl5o00+EB6ayVaVlx1MDAxZtxMerpu4i5cXJrTZlx1MDAwNC/QaPW3V/1oVfQyKOlcdTAwMDLg5Tt2NFbxhkDThZPwgtcwXHUwMDE0Z0RcdTAwMTjESan2lWhNMoT5kZlE74LEJtl8QeLeaKR/6Vx1MDAxMi+y1/lst5VG62VV4EjuXGJtXHUwMDE53G4splx1MDAwM7xhhXVCXHUwMDE5ulx1MDAxNIR781x1MDAwYuo9wUBcdTAwMDBcdTAwMGVcdTAwMTnGjqZcdTAwMTi4aUtxXHUwMDAxWqFcdTAwMWTJf8WdKlWs28HfxotgXHUwMDAxPq27SH+4+YpcdTAwMTknIEZcbqRbJUsk7pA3YVC4gZ60WirihS/tY2bbr6+rjZKx9HJ4XHUwMDAyRlx1MDAwYlqRJHmkZLI8ctKRSXNufityWN987IeF3NPT85pRKzltmrX132lF/I+sSEZcdTAwMDWSjIOyoCW3x3Nj3cTNIIhH3Mehd2h9cvuNspdFWfyF8miwaY3/SH2UXHUwMDFlpUFaolx1MDAxNnTLnVZcdTAwMThetWq1bjVcdTAwMTVcdTAwMGIwU0b1c646nlA3XlxmpiRcdTAwMDd+cP5cdTAwMTjE7Fx1MDAwMuM0uuqMNVx1MDAwMShcdTAwMDOKU7SByMlo3lx1MDAwMHGVwcoteXNNdFx1MDAxNn7BXHUwMDFjXHUwMDAzXHSBRu5cdTAwMGVBJnTQNniKq0ZcdTAwMTVY3o/aWuFcdTAwMDSqyTZkknc8sTqt+/Ik+epxr/ZjLz67VUHci2vuZ+GkXHUwMDA1IG5LlyeuQ958uPLcXHUwMDFhRlhlaFwiqMn45lfy1MlIXHUwMDFhXHUwMDFjncDQXHUwMDA3+WlpXHUwMDEz96WhKcVcdTAwMGIvZn5cdTAwMGJyfH570Lx+OGvc9C5zRaNOK7BcdTAwMTOm201cdTAwMTOASDbyXlx1MDAwNIY3M7PjuaHEnoRcdTAwMTHoaXrK5Fr8OezHzFx1MDAwNqHxXHUwMDFjm1mu2nF/dv+RefRvMEp9s5X3XHUwMDExgadv//r26nGf0kBcdTAwMDFGxvNzzt+YRIptjVCkftxcdTAwMDK5XHUwMDE3M+91KqcuWadAO9I2VuFg88WxXHUwMDFkpYCzvnlek1x1MDAwNpSk9n7d7OV9V1DzNr/GgNZyil7XXCJAXHUwMDFhJVx1MDAxOIdMy/xErFx1MDAxMaxcdTAwMThs0IrLsZA521x1MDAxNXyLL2RK7zQ3iVx1MDAxN3TtRKy1/9DT61x1MDAwMIEri7iLqzJo/3Z+XHUwMDBiRlx1MDAxYmd32Vx1MDAxZFx1MDAxOZOTwJFNMN5yJFxiJ8dkyW1Izds6k85TXG7t12ZcdTAwMWaJXHUwMDEw5kdmXHUwMDAyvVx1MDAxZsQ+jEhkXHUwMDFm4Fx1MDAxOFxyxOrmt2HyMNNZXHUwMDBmj1Z8P8xv5lx1MDAxYnc7XHUwMDA1XHUwMDFhbLptmCWocU6LU85cdTAwMGZcIqsjJsyjXHUwMDBliPkpMnJcdTAwMWP8xuQ11HebMDv8zjNcdMhgf7RcdTAwMGZcXFx1MDAxMpnFP9JVa/0+/tFcdTAwMWb6+35cdTAwMWH4x8h4fo5/+GT+QVx1MDAxNlx1MDAxMblSaYGNgWff7DROXvBMP4yyXHUwMDFlNJlEM1x1MDAxYeAjO1x1MDAxYVxikq6G7Cm9YMZ+gVx1MDAxNVJcdTAwMWai9vNzV2FgvZCkhVxy+VDjp7RLXHUwMDAyQXZcdTAwMDbYdjPyXHUwMDFk+MlcdTAwMWNQXHUwMDBi7Fs/Mnv8d9KP2a7g2+hapvFecVx1MDAxMJT8vCZccoiT3t5cdTAwMDe8XHUwMDEzmFKgWFx1MDAxMf9kXG7o7CYhsTFx31x1MDAwMSmAXGI88JZxU5JSvzTXSMQrPzKTUP0grqFlYrqnIe1vUS3QlGhcdTAwMDM31/q9s4K7XHUwMDE0J7e3jbo5XHUwMDBi8+mmXHUwMDFhUkneYFx1MDAwNKznrfe40GqMakhcIuIgiYwr3u7116klOS1J3U4kdnI1Lk3JXHUwMDBm3GZ3XHUwMDE21UhXIeHHUY1v//k/zZfVhtmpVjj6Np/JPqZccvHnXGKJmpGSiZozXHUwMDEy9fx8ZDZcItI4w7mdkrDOW1x1MDAxMNKNt8VjOlx1MDAwMlx1MDAwNo0wZFx1MDAwN62G5LSF985vLXlfXHUwMDAxXHUwMDE2i5JcdTAwMDSci+/98jbdveNcblx1MDAwNuCW6YaTcyfriFx1MDAxNTegNMosR/LVbHcxRkfQoeSNXHUwMDFkXHUwMDA1arqAw0rqN/dvXHUwMDAzbiAtealA81x1MDAxMt9cdTAwMGZcdTAwMTK7XHUwMDEzk69mXHUwMDE2yo9cdTAwMGWK8/g0XHLMSt7tzU/GaEygXHUwMDA0d1xcRPTCO6e/NENJhjA/Mlx1MDAxM+j9IIZcdTAwMTIvb5hsjayscGaBvVlcdTAwMWJusy9z2bDQysls5XhbXHUwMDFjl1x1MDAwZXZSbcDAmcBcdTAwMTjeylx0rVx1MDAwNYk43mlcdTAwMWZcdTAwMDNniZooZbVRMzp7XHUwMDEykyvqd+zXZqdIqNi2uW+N59Cj1+LjXHUwMDE2Y341h4hZ/Vx1MDAxMVx1MDAwZTE0sn9ziNVWsVNZabenkoXY23xcdTAwMDZZeFx1MDAxYsvLpPrjddp+L7bbJ1x1MDAxMV2lN1x1MDAxM0fXv1F5/arDd/z+0Kg+rk5f0Oc1/T9eJyrPiCrfhn//9cdf/1x1MDAwNzDbVCwifQ== + eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nOVdaXNcIkmS/d6/QlbzcZuccI97zMbWdKFcdTAwMTPd6GB3TMYpkFKAOCSktv7v645UXCI5XHUwMDEyoYvKYtVtqiqSI4h87v6eh4fHX38sLf3oPDbLP/619KPcK+bDWqmVf/jxJz9+X261a406XcL+v9uNbqvYf2a102m2//XPf97mWzflTjPMXHUwMDE3y8F9rd3Nh+1Ot1RrXHUwMDA0xcbtP2ud8m37v/n3Xv62/O9m47bUaVx1MDAwNYNcdTAwMGZJlUu1TqP1/FnlsHxbrnfa9O7/Q/9eWvqr/zsyula52MnXr8Jy/1x1MDAwNf1Lg1x1MDAwMVx1MDAxYWVHXHUwMDFm3WvU+4NcdTAwMDVcdTAwMTRotdBavj6j1l6jz+uUS3S5QmMuXHUwMDBmrvBDP453K1AvX/dcdTAwMGWLK6nWib/e1M3LjcHHVmpheNx5XGafp1wiX6x2W5FBtTutxk35rFbqVPnTR1x1MDAxZX99XbtBszB4VavRvarWy+320GtcdTAwMWHNfLHWeaTHpHh98HlcdTAwMTL+tTR4pEf/SjlcZjRcdTAwMDJ4kFx1MDAwZZXE14v8auVcXKC0tVJIROE1yJFhrTZCulx1MDAxMzSsf4j+z2BghXzx5opGVy9ccp6D6FxuZTN4zsPLlzUmXHUwMDEwylqtUGlnhFevz6iWa1fVXHUwMDBlPcVi4KQ2wlx1MDAxOIHCOTdcdTAwMThnu9y/XHUwMDFmXGJSKbruXHUwMDA3N5Q/vrlV6mPjP9FcdTAwMTmrl15mrN5ccsPBiPnCelx1MDAwNE+D13SbpfzzbVx1MDAwN2O1Rk9DoLl5vVx1MDAxZdbqN6NvXHUwMDE3Noo3XHUwMDAzpPRcdTAwMWb9+89cdTAwMGZAlO5KXHUwMDFjRK1cdTAwMDaNXHUwMDAy3OxcYl3LVm6ue8t7d1x1MDAxOXl2eFxcObnPZdeWXHUwMDEzjlBtXHUwMDAy0MpcdTAwMWGp0ErrnDfjXHUwMDE4ldJrXHRcdTAwMDRTacEkXHUwMDE2ozR85b0wesEgaoWLgyhISSZcdORDZsZo+WRzm1x1MDAwNl8olNTRQWbL7excdTAwMWPcqIRjNFx1MDAwNTJcdTAwMTDgnfZgwVx1MDAxOedgXHUwMDE4pNrIQFx1MDAxOSfoXHUwMDEyoFOj40pcdTAwMGVEQUvp6D9YOIjqOIhaa5SQxsLMXGI93y/s7VxcaFdoXHUwMDFjXHUwMDFj3ubP7zeJ37R/LUJBvOlGVWC1oWl3XHUwMDA2vFFgh1x1MDAwMGqkXHUwMDBmiFx1MDAwM3hPXHUwMDFlSmilMbFcYkWwVtFQXHUwMDE3XHUwMDBloVx1MDAxMEtFUSjiNqD97IH+NpXLy3RxOXucq96U16pcdTAwMDfrXHUwMDFiISTdiTpcYoiFKidccjqg71xmw1x1MDAxMFVcIiBcdTAwMGaqtFx1MDAwMkVE1erEQlx1MDAxNCyRXHUwMDE0TTFg0bio9bFcXFx1MDAxNFx1MDAxY01cdTAwMTdFNz27XHUwMDFi9TZvdNi4vu5cdTAwMTazcFx1MDAxOXZLjX27l3CMXHUwMDAy6sBcdTAwMWKhKcxcdTAwMTNcdTAwMDR8RFx1MDAxMD2HeU1cXNRcYutQ9mNKciHafy25lPm5UT9cdTAwMWa5JHxcdTAwMWNEtfDOXHUwMDAy+plcdTAwMDHaetquLtv1PVxym5uynFp7Slx1MDAxZlRPk1x1MDAxZefpzlKgJ1x1MDAwNmdR05/aXHJLesJtgI6UvDGoPGKSqahw4KxcdTAwMTBcdTAwMGJcdTAwMDdRXHUwMDE5n3RykmKbfIdcdTAwMTNdllx1MDAwNd+53Xpyjerm8Xr2XGbyp/WkK/pcdTAwMTRgIDmvRLLeoSBHaUYwqomMXG5E68lFXHUwMDE5n9ysXHUwMDEzgDWk6oWaX6SfXHUwMDBmRq0wcVx1MDAxOPXgPOiogHhcdTAwMGKjK141fKdzs1Jo3+CtKUH7ot5MOEbBXHUwMDEzXHUwMDA2lVx1MDAwNVx1MDAwMUpcdTAwMDLFe1x1MDAxY470ylx1MDAxOYKGoD+EQYr3oJKLUUnwcdZEXHUwMDAy42JglD4pXHUwMDBlo8zMlNJmdozuhL3qWlXmMlx1MDAxZFFcdTAwMGXXzcNj2OydJFx1MDAxY6NSyoCYptBcdTAwMTTKhTM4mrwniFxuY+mOWCFRJDgxSjZGMUBoOb/M6JzcqME4iFwiKE30Rjo9M0b1zXpYKFXu08f3WpRy5+FlfT1MOEbRqEA68j9cdTAwMWWU0TKShntcdTAwMTZMXHUwMDEwOMKuQk5MYYKdKPlcdTAwMTJA4qNcdTAwMGKHUFx1MDAxZJu7d2hQ0q2ZPeu0eqMuOnBdusruXHUwMDE1W6dcdTAwMGYtma3m80lcdTAwMTdM7EWV8kQzXHRcdTAwMDFcdTAwMDCRtYyfWSdA4ywvjoIzXHSGqJVcdTAwMTL418JBVMk4iKKj2dL+XHUwMDFkq0v6+mJze9dm95dVuYHb+6enzZxNOkTBm4Cw4STxXHUwMDFhXHUwMDAxUo5hXHUwMDE0XHUwMDAzLSjWkGJcImbuklx1MDAwYlGyMbpjOMdF+jlRUVx1MDAxZFx1MDAwYlFtke5cdTAwMWH42SU9bi7bo3C7lrtfbV5f7eNtRVxc51x1MDAxMlx1MDAxZeaVkIEha1x1MDAxNN4rXHUwMDBmVo2GeVwiXHUwMDAxSMhcdTAwMTDSeUy0oDfaWWW1XTQn6lxcbF6UvIvxRkaXSN+C6F7n8r6mbnqnzZW73HLnMXN8VF3/XHUwMDFkIGqEJS6qnZMjqXvOOVx1MDAxMVd1gqhcdTAwMDCr/eSKJS6FsVx1MDAxNtTCIVTGMlFcdTAwMDM0a1rI2X3oxe7DRqHaq4Rdd7ZV92erjfr+RdLDPNHMwFiHJJPAOI3KjkDUXHUwMDA2XHUwMDAyXHUwMDFk31x1MDAxOGe4jiixXHUwMDEwReEleVHQi5ZysjLWiyryoUJqNbtcdTAwMTNcci+ecvumqFx1MDAwZm/NQ72zv3N73To/TLhcdTAwMTNNOZJD9D3BSclcdJtcdTAwMTGEelx1MDAxMVgwSoNcdTAwMDaLTiVXLCktkWJeRFksXHUwMDA2QJ1cdTAwMTdxXHUwMDAwXHUwMDA1Vlx1MDAxMHTv9OxcYr16KKZPa21wl9nH7VpVmePc+i9Ois5Q6GRJXGZJ+qaEQZr3KD6eMVxugdJkrFZcdTAwMDOp/YiLSlx1MDAxYUbpXCLxXHUwMDE05Vx1MDAxNm3908ZjlL+wgWhZxZvVou2KKp6c7Oid5mrT5DLd6nphM+lOXHUwMDE0REA6wztcdTAwMDOoud4uUpPAb+BcYj2kokjqk2Am8CS3jMRcdTAwMTlnhV84JlxusVFcdTAwMWWFXHUwMDE2Xoj3rCx5XHUwMDExVjvdwoYt75ZqXCJzXuymdlx1MDAxYlx0Ryj5UGk8ek1cXFR7gcNy3itcdTAwMTF4hSRDXHUwMDE0XG6Z4Fx1MDAxMlx1MDAxMpLxxEXkwiXtnYivXHUwMDE1JXySdphcdTAwMWSehUZcdTAwMTWuWts7t08rXHUwMDA3t1f3ze398/VMwuGZsi6w1pIs1I6IJrnRXHUwMDExfMpAopTOoqbJwORWiqIgLi1cdTAwMTDEwvFQXHUwMDFiW2+PLJSMtbPT0OPMRerk5nxv67540d3aL4dcdTAwMGZcdTAwMGb791x0hyigXHJcdTAwMTR7SFCS5LBcdTAwMWGN8Fx1MDAwNGAgnU86RHlcdTAwMGLJLbe3aLVfQFx1MDAxN+ohNtukpLXo4Vx1MDAxZEp+fcs9XFyd7tQz9+XD7PLKSemyW096OpRcdTAwMDHKy1x1MDAxMp5cdTAwMWOQ5FxyIUNcdTAwMDAl7qlcdTAwMDMhNTlZrZXxNsGLSob/035cdTAwMDG1fHwps1x1MDAwNE9cIvZcdTAwMWRL8/dcdTAwMWJcdTAwMGad86Otq8xarry6fX2xXazmz1x1MDAxM1x1MDAwZdFcdTAwMTS5UMF1MsJpY6xcdTAwMWYpZSaM2sAp+lx1MDAxMUT0SEol14tcdTAwMDJdsDSChcuHmtgw7zQ4XHUwMDE33Y79pk7avYHbTmpzt3l3XHUwMDEwbuyk6j19eZD0ZFx1MDAxM3jL2+eFQCSkRpcwnnNNXHUwMDE4KND9XHJ3hGKdXFylXHUwMDA0QCYk1OIlRH188VxiOO2dXHUwMDE3XHUwMDA2Zlx1MDAwZvS5Wlx1MDAxNcqNVLpWz2RrOrO50fBcdTAwMGa/WMvPUuBkXHUwMDAyQqDXllx1MDAxM0p+dFWJMCqJXHIxepBcdTAwMTdtXHUwMDEyi1FjUJOkc4vmRJ2MdaIgOIXt5DtcbpzOT1x1MDAwZotccn9YujqrXHUwMDFkZU+0bj21Vs5cdTAwMTJcdTAwMWXo0fDKJil2pYiWXHUwMDEy3Vx1MDAxY07ZO7qsSMqT4JdcdTAwMWGT7EXRWuJcIkYvmlpyJjbhpFhcdTAwMWPCe7YsZc+3m1x1MDAxYrWtjaNMa2tXn3SOz07bSW+TI6VcdTAwMGVcdTAwMWNcYqKhxEhp1ofVkldcdTAwMTA4a/t1I+RpXHUwMDEznFx1MDAxMVx1MDAwNcnKwZk59iCZV1x1MDAwNV5spT2QNDBWKJw9zvuV8/rpffWyU8BcdTAwMTW3WXkqrJVcdTAwMWbuXHUwMDEyXHUwMDBlUfAuIDFvhffaaFx1MDAwMGlGMKpcdTAwMDKKn2iIqYJEmVxcQe/J0Kywi0dFMX7fp1x1MDAxMZr3jNvZNydcdTAwMWbs7dhuSp7ByeN1/cGmsVPYLCdcdTAwMWOiSlx1MDAxMDxIXHTT/UUhhVx1MDAxZNlcckJyMTCaVD1xVMNro8mFqDZcbr3Ri1bH7OPT9oCO63tRzVx1MDAwZdH1Qlhv3e1cdTAwMTfSXHUwMDBltleObjZuXHUwMDFl1sKjxEOUXHUwMDAzPVFRYclcdTAwMGaBXHUwMDE4dqL9tKi3gvyr8kbK5Fx1MDAxNo9cdTAwMDCS/Vxit4iCPt6LUtBcdTAwMTDc9WB2Lrpmd+5ztfW1p/AqbNbq4DauNm5cdTAwMTNcdTAwMGVR8p+BIMmOdPspkFx1MDAwYmdHMcqro16C1nR7olQvcSBVvDuVSfOCgVSa+I54zit05Edmz91X7/Nb6V1cdTAwMTf2Llxua2m7nXdHO+W4XHUwMDFhp1x1MDAxMbBcckN0tFx1MDAxZfP1VaV8u1p+XHUwMDA3Ru0sbUVZXHRpZTn1NJq6573JXHUwMDA2eEcl31x1MDAxOTDuUyVOnVa+3m7mW4SBcZxqrVx1MDAwMmOs05ZmXurIkskrTlx0oUG/o5TWRJ9hUs8xxWu5qL5cdTAwMGWnL1x1MDAxN1x1MDAwNsCK3O+Gvdjcv8Dl5ulx+7RSa/m6yVxmmndccqEw32o1XHUwMDFlfrxe+fvPae+bOd+9XHUwMDE2+TOzVz0uioNmoVx1MDAxM+7ordne9+VvybAucDZ+M4vgLXJcdTAwMTbf0c5v+bKS71xcXHUwMDFk7Fx1MDAxZjdSVVkopo+PN1RcXD5iqnmNrj99azM/bblcdTAwMTZNXHUwMDExTyalN8yjjXRcdTAwMDFcdTAwMDV+0CwriG/Hl2HTs/LqjVx1MDAwMFCpVMatSipHI1BI4ZY+30VcdTAwMTbTX62KXCJT4HkvXHUwMDAzyU2DUX3+umHVaMlcdTAwMWJWv45FT7Wq89LpKt5V7vaq+96Yxoo5yJz8juhcdTAwMTdcdTAwMTCf6rA0o85F6/LfQr8tbu7Ui4VO53Hn3pnlp9Ku6vaSvqShdSB5r1x1MDAwNVx1MDAxMoSUVsRyhlxyQEEgyd+DI1x1MDAwNjy0pTtpXGbIglRCLdy6MFxijMcoXHRJ40HNzn+2lrP3XHUwMDBm4U1W5S5219IrWbVij4pJhyh4XHUwMDE1oNFWcl9cdTAwMGIyWTWGUFx1MDAwMVx1MDAwNlx1MDAwNXlp3tGdZI7OXHUwMDA1XHUwMDE4Xi9cdTAwMWNH91x1MDAxMYowuqbRryrRZvZUhzvdyJz27uzmyurxzr06XHUwMDBm5ermL27DNsvCMFx1MDAwNqSYvSdcdTAwMDLurbejPYFcdTAwMTVcdTAwMDaSN1x1MDAxYzpP0TvJzVm80t5TXHUwMDE0WDg3qlxcrFx1MDAxYjVWSVx1MDAxMlc4e6TvPS7fhPlwfTt31MvlTn0h3HWtxPNcXCG04jVGr7mkeyTOXHUwMDEz0SWKS1x1MDAxNJIgqpz93I7YXHUwMDE4omtcdTAwMDOtaFx1MDAxMJKbwGk1QT7CM9tGYl7cKFx1MDAwNqL7dV52XHUwMDFiXG6Dyir4ukW3aURcdTAwMTex97jTbsPxg6rD9uPBtj+utWcjulPl429EoMthWGu2J9tcdTAwMTTxxjibQsKIXHUwMDA2Zdzs4nE1tfm0dbyXlb1cdTAwMDPZvs4101x1MDAwZvthXFxcdTAwMDJxqlHNz+1cdTAwMWKiXHUwMDFlfI5cdTAwMDa3XHUwMDFmXHUwMDEz0c6Fz05fXHUwMDEzMUHJus1+cot5Ja9R47hNsXS0xtHryb+p6IlcdTAwMWSvNuUoKqDtb9Hs96yXoyZcdTAwMDVcdTAwMWVcdTAwMTVyt/D5iMdP2FSSsK+mtPXkXHLsxsvZXHUwMDE3yVx1MDAxZkXtonq/bVx1MDAwMVx1MDAxZXZ6XHUwMDE1n013u+eYbOhbctVAmDFeSEUqeVQ2qn5iXVvlUVxu87l64kq+QN7ke9BvKfJcdTAwMTn9lfnIZMBTx+f1nOZ2rO9pUrPSOzg9Or9q1zOt+onRqdst7dLJxqc3XHUwMDAxeUyJwnpBXHUwMDE4XHUwMDFjQadcZlx1MDAxY0hcdTAwMDOWa+GI9Hxcbp2AXHUwMDA15ybw8a9AJyrlXHUwMDAwo0dcdTAwMDH8Nuh8K/NcdTAwMTa79qiNJOeJcnbJeLeVrm0yc7BcdTAwMTdP9e4y3jzI9C9u8zVcdTAwMDMhN4RBL/uiUWvrRurd+SwuuiOSdJxcIsKb3I3rXHUwMDAwaHjBZ+G6KHlcdTAwMTWfeFPsRH20XHUwMDEx9FtcdTAwMThN27ua9duVxure6nlnuXZb3ln7xZ27Z0trXHUwMDEwj9GOlCNnf4drNblHXHLvXHUwMDE1XCKhJrj1QoJrNWl00jrxO0b56aduxm9sXHUwMDAzT1x1MDAxOFx1MDAxNVx1MDAxNFneUU58mOnKXZu9W1x1MDAxM+srOzv32Vx1MDAxNYzd2fblYV58XGKh2lx1MDAwNki+UlxuirROR49cdTAwMTjtl8FpRVxi9U5JQb/d51LDxXJJlfLjXHUwMDAwlcR1pVx1MDAxNUSB0ViylkhkXHUwMDFiJDYwMF4765ltSHKYalx1MDAxNKLIXUeV/Uo3+nJholxuM2m9d5KRT1ukXdcvU3p143LvcDZcdTAwMTX257T33czVTHhSf+q08+Ji9XBv/ab31PgqdUdwILo+XHUwMDFm51x1MDAxZp/aIOZsXHUwMDE0yvdcdTAwMWPEuFJO5eHo2nWaZ49yZb1X2c2sJZ6gXHUwMDEwOVx0LFx1MDAxYc3HL1x07q0w3v1JXGLD59xcdTAwMTlF/jW5PfRcXL9kxSyc85+6Ic9xu1x1MDAwZfOOKuiLsL0s072101x1MDAwYpHN71x1MDAxYtvJp5dlsp0/qdxcdTAwMDClXHUwMDA04tDcjlSPIJS9v6BcdTAwMTBoiK55Jz/ZcFxcXHUwMDE1TbkyIVx0XHUwMDAx3lx1MDAwN9JbXHUwMDAwj9yHSkyo3yDvT+BFQzpUOOWjNVx1MDAwNT8hXG5cXH6iXHUwMDE27oCmaD/xXHUwMDExiEq2SVx1MDAxZF1cZn1cdTAwMTOhd729w4vl/HZnZ2VHnl2J1XVI/uI16lx1MDAwMKxcInhwP3mMXHUwMDFlY/HcidRcdTAwMDSSXHUwMDBiMFx1MDAxNOk8XHUwMDFmLeBJmlx1MDAxN1x1MDAwNU6kWLt4zSHIaOMwalx1MDAxNUU4mozZI31ntbZXrbWrdrmi0o3Udc2ellx1MDAwYknHKFx1MDAxYVx1MDAxOWiB5JpcZoAzY/2cTUBcdTAwMTSagihywjfBzXKl5lx1MDAxM7VcZs4tXHUwMDEzXHUwMDExab/4rdkygFg2yrkh3nw/O0Srayd+67GweplXXHUwMDA3T83zfPmweFx1MDAxOXcqeEJcdTAwMDK9dC6g6GmRmDc5Slx1MDAxY85DXHUwMDE4TVx1MDAxMVihUJI7zmvxPTJcdTAwMGZkoIhE8mJcdTAwMDFZgvGRj1x1MDAxOVx1MDAwNHqv+Cwzy1x1MDAwZV8hUZLxxTbB8mHo1d8p875Vjs3TXHUwMDAyplfBWURcdTAwMDPvqDG6gHSx2Mvu11x1MDAwZdPiuuuf0ie4XHUwMDFld1x1MDAxY2lC1jPYXHUwMDA0LHeC9rxcdTAwMWRFXHUwMDFhXHUwMDFjWXAjscY9KZSxRDC9/tySRizXhcCQtCBkU5RcdTAwMTAmmltcdTAwMWGsaSA3vFx1MDAxNMJb0d8zM8Z1uaU198iYz3Lz91pcdTAwMDBxh1xiw/2kXHUwMDA1dMq9zkTw2/hcXFx1MDAwNDvDfmO5mbF/WMrc7V9cdTAwMWTpjebVQ+8207kut+1jsrfAXHUwMDEw5lx1MDAwM97agqiJXHUwMDE1Kj16yKly/eI7XHUwMDE0ktgpfvKs6Im1S6gn8JHoyVx1MDAxNi+bWYWWnjuKzVx1MDAwNdrftbnlNH1oiqliPsy1ZK+3msvmunj8y4LGXHUwMDAwmY1657j21K8uckOPpvO3tfBxXGJdfVuiXHUwMDAx3tdanW4+vGzTXHUwMDBig5c7XHUwMDE3uf3tMlxyoP+Oeuily2Htik3vR1iuXGbbZKdGXHUwMDEz83q504is4Vx1MDAxNGkoeXq71lZp9Cs1WrWrWj1cdTAwMWaeTFx1MDAxOdZU3/A80Vx1MDAxM5yDjO9cdTAwMWLG68rkqmdcdTAwMTfY01x1MDAwMfVcdTAwMGLKXHUwMDFh3/RcZlrYgMNcdJLxK1x1MDAxN21o1H+51IHU5Fx1MDAxYVR/Z4P83Fx1MDAwMtXkosZAcDrfSevpfy0mXHUwMDE0NVwi14EpxedcdTAwMWIrjUpGT1x1MDAxNXshhY6rXHUwMDAwXFy0XHUwMDE3cZKES2TW8q3OSq1eqtWv6OLAdfwoP3/01lxmXHUwMDExpm+zxS6PMiVcdTAwMDLPRf2aj6ZWpP5cdTAwMDbHXFzwXHUwMDE05JvPtFuS6lx1MDAwM+2lN8R7lHt5xqtcdTAwMGb7Ua6X3lx1MDAxZdT0nZ/RQUFAZkM3RIO1Rlx1MDAxYTM+Jkk0qK+6pNbWXHUwMDFib8aGXHUwMDE05tud1cbtba1DU3/QqNU7o1Pcn8tltupqOT/mL+grRa+Nmn+T33E4Plxm/rY0sJH+P17//p8/Jz47XHUwMDE1i+H+1XH4XHUwMDBl3vCP6J/vdl3g4lx1MDAwZsxcdTAwMTQ0rzp6XHUwMDE4+Fu+a3rQSqTvXHUwMDAyXGacMoq4hTPK4OjiOlx1MDAwMZ9cdTAwMTNcIkKTojTuk1x1MDAxNXRcdTAwMTNJTUBKQTjuXHUwMDEyQoJcdTAwMTYnkXn0JvBoXHUwMDE0N1j2Q2vJr9VcdTAwMWbgSVx1MDAxMkj8f+a6ROCclyTDvFx1MDAwM7qFkSbdr16CXHUwMDBi6lxy3TuCOadcdTAwMDOMn+64hr/F7+Q/YnHEP2NcYnqn94hcdTAwMTNFXHUwMDE4aVx1MDAxYzbqPKRA8lTvKM5pZFx1MDAxZs9ubaPcLGbO2/uq53Xq9EOFXHUwMDBm89NEzlx1MDAwNzTd5LWVdlx1MDAwZYRcdTAwMTjO2fJOzUDxjk3pnJRcbj/XXmWy+1AzaVwiXHUwMDEwXHUwMDEyrHZfeLTJK4K+dmP+VE10eUfM/na1dLV2SVx1MDAxYXPz5GB/q1hdXHUwMDA0TfR8N5MmiZ5H9TFagT5eXHUwMDExkSP22pnZN8xOx1NcdTAwMTJphVTE5/hcdTAwMDRuzV1cdTAwMDEs4qhnQKJcdTAwMWRcdTAwMTSUXHUwMDE09y6Unz1cdTAwMTJpomdcdTAwMDBhXHUwMDAyR1x1MDAxZu1IgFx1MDAxMmsk/TXuKIhCoyT2Yzw3K3U+kjP5mUyxzlxuObfVxm/lXHUwMDE101x1MDAwM8wrZfBcdTAwMDF6a9FYllx1MDAxZdxcdTAwMDd7smhcdTAwMTKcXHUwMDA146JcdTAwMWPBZbdK/oypXy2Jnlx1MDAwN4UuXHUwMDAwzjtznLGSXHUwMDBmMpqsmoRcdTAwMTe8QEKciIZlzLhO+51ITTyE+Sc1jt530pp474XxR7p5x4fcvKPubHrUSqL3sjJwJHZI+TG43UhCXHUwMDA3+KhccuuE1EZ5bsvyXHLbVEFDXHUwMDAwXHUwMDE0XCJcYsaOTFxm3KR1voC7oDrSqJJbbMrIdtSfzotgQaFmfodf/1r3XHUwMDE1cU5AhFx1MDAxNEi1XCJcdTAwMGIk7uw35lC48Vx1MDAxZlqFkmjhc9Ob6f7r91VG8Vh6vjxcdTAwMDajd3qROHFkpm3MRXDeR1Olb/ZHrW489MJcXObx8WlVy+WM0vXK2q/0XCL+LS+S0nyomXL9XHUwMDAzd4zGkcMktFS8nmRRXHUwMDBiukNexi+WXHUwMDE2PeYx/43iqH/ajv9KdTStfppl+mBcZt+tM0hJVIJ2sdVcYsPLRqXSLidi6WXCqD5cdTAwMTapdXzrNG1cdTAwMDRZdZS8vmVj03dFJzFSp6znXHUwMDEzqsnXW66vJa8/ZGOqf4C1JetcdTAwMTJGO4VTemd+2MhcdTAwMDBVYJXnXHUwMDE0XHUwMDFjSC8hsmFrXHUwMDEwqo1cZri3XHUwMDA3elSWrHIsg8l1Q4pcdTAwMGL95tLyXHUwMDA0rDPqK0L1aFB7O4hPb1oywuWl53RcdTAwMWVqcpXCXHJK/pZcdTAwMDbJTU/S0mrF7W608vL3ZvuxQOpfXHUwMDFkhdBcdTAwMTdF6eh+37H6fetcdTAwMWRzg9mj9NHZzX796v60dt29yOS1PCnBdpjsKI3OkGrkQ1x1MDAxNLRA6e1o3SmRJ6GF8WSeXHUwMDE433huXHUwMDA29zG1rSmYXHRcdTAwMWK2JoRqkoOMdfi6XHUwMDFh/VdcdTAwMWP9ilx1MDAxNjFzTDg+Lv176SXiPiaBXHUwMDAyXGaN52PB30yR6aRrjFx1MDAxNPJcdTAwMWR1XHUwMDE3U+91XCJNXHUwMDE3hFxulCNpY6Xpn1x1MDAxYTmyOYyrZT2FXGLe4m7UlMqLT1uv8oHzvHvGcz9cdTAwMTIhJ3BcdTAwMDBSYVx1MDAwZcBcdHR82ICa0Fx1MDAwMseK/smyZi6rmGA1XHUwMDA1ky/gXHUwMDAwcZF+eihYiq5iUpwnXHTCfcOF9laPXHUwMDA3elBcdTAwMDFdI3fBrWelNm682OFr2Vx1MDAwN9FGQYzCsjDiqr5J3IN71HE9oHRa0vB/b+5cdTAwMTFcdTAwMGJg/kmNYfeLyIeRscVcdTAwMTc0XHUwMDBloKDrZ+9cdTAwMGaDXHUwMDA3qdZaeLjse2F2I1u73c6Bc8l2YOSZXHUwMDAydl7kw4zhxZAh/+UlXHUwMDA2SE5cXDllLZeifJ//slx1MDAxM1x1MDAxY9Yk9tE/1O1cdTAwMGKXQ6aRj0/sXHUwMDBlf5t8XHUwMDEwnfaRpv7fTT56g2DfS1x1MDAwMvlcdTAwMThcdTAwMWHPx8hcdTAwMDeXp8VcdTAwMTkvzbAw7zr+aPrNTqTxXHUwMDAy9yVBpygmeTJgXHUwMDFjyT2QTlx1MDAwYlx1MDAxMNGT/Vx1MDAxYe99fO1DibSHqHzCeJ1cdTAwMGZQsCZUxnKX7HFTNuTdjdJeKvLfWsFY41x1MDAwN7DAgfUri8a/xfhm5Fx1MDAxZdNDQSSGXHUwMDBiJTiuXHRyr9xBYFx1MDAwMlx1MDAxM+CVTor0XHUwMDFj4ZWy3HOByPHH2Mf0ziZcdTAwMDOyM2FcdTAwMTTc5Vx1MDAwMFx1MDAwNaBzgLxs/nuTjTjA8k9qXGarX8Q1tI7lXHUwMDFhUvSLe9/RwnzdbKz2uqc5dyGOb25qVX1cdTAwMWFmk801NK9cdTAwMDKR7XnBp11ZO9xcdTAwMWKaZjvgynVNXCKK7suUXHUwMDBl+5+lXHUwMDFhOKk63Y4lRHmbL1x1MDAxOdxcdTAwMTdcdTAwMWVcdTAwMGU8jWp8b8NcdTAwMThUVnxIaX2aaiz91//Wn5dcdTAwMWGmV1mZ4beZJ/uYNMRcdTAwMGZcdTAwMTJcdTAwMTLw8VvUKOKAtO85pWA6Jlx1MDAxMmni3lx1MDAwN55z4ZJcdTAwMWKB2JGaK1wiXCKBRd7VzkdcdTAwMTR4XHUwMDFiT0g+rSbIv5NIXHUwMDA3329RJGDSKqTl4lx1MDAxNW208dKSjejx7Shk/PRiO5+OfsinWOiP2OiMjGR6wFiKZlx1MDAxZZDPylx1MDAwND6uwFC8x/GdXHUwMDFmKuBSW3IqgMhV31x1MDAxZt2MMnVcdTAwMGL+0JCI8lx1MDAxMNEl5Vx1MDAwZp6X7u3YkFxcoPlwXGbNjVx1MDAwZpif/N51V6l4XHUwMDA09y+PgfeLKFxuQPwxcyRcInjh/Fx1MDAxZOdg1dxGXHUwMDBmM+kw18hgunS0JY5cbvvbiXZg4HSgNVx1MDAxZj/FvJDsYPR4XHUwMDAwXHUwMDEzOEt6SkpyXHUwMDBicko70lx1MDAxOc7BmurB7IQ2uZFcXPvPTlx1MDAxZlxc1+Hn2C/p06slkfa/QyxiXHUwMDEwKX6yiJVGvlVabjYn0oXI28yDLryO5dmo/nix21x1MDAxZvlm87hDs/Tq42j+a6WXrzp4x1x1MDAxZve18sPK5OV8XtH/48VQ2VwiynxcdTAwMWL++vuPv/9cdTAwMGZcYuGAVyJ9 - virtual_size.heightvirtual_size.widthself.scroll_offsety = scroll_yx = scroll_xx = scroll_x +self.size.widthBoardApp + virtual_size.heightvirtual_size.widthself.scroll_offsety = scroll_yx = scroll_xx = scroll_x +self.size.widthBoardApp From 738837fd663cbd03b375568e50458b310e32d274 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Sun, 5 Feb 2023 12:06:48 +0100 Subject: [PATCH 91/93] review update --- docs/guide/widgets.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/guide/widgets.md b/docs/guide/widgets.md index 947bda5df..58a76d7aa 100644 --- a/docs/guide/widgets.md +++ b/docs/guide/widgets.md @@ -201,8 +201,8 @@ TODO: Explanation of compound widgets ## Line API A downside of widgets that return Rich renderables is that Textual will redraw the entire widget when its state is updated or it changes size. -If a widget is large enough to require scrolling or updates frequently then this redrawing can make your app feel less responsive. -Textual offers an alternative API which reduces the amount of work required refresh a widget, and makes it possible to update portions of a widget (as small as a single character) without a full redraw. This is known as the *line API*. +If a widget is large enough to require scrolling, or updates frequently, then this redrawing can make your app feel less responsive. +Textual offers an alternative API which reduces the amount of work required to refresh a widget, and makes it possible to update portions of a widget (as small as a single character) without a full redraw. This is known as the *line API*. !!! note @@ -211,7 +211,7 @@ Textual offers an alternative API which reduces the amount of work required refr ### Render Line method To build a widget with the line API, implement a `render_line` method rather than a `render` method. The `render_line` method takes a single integer argument `y` which is an offset from the top of the widget, and should return a [Strip][textual.strip.Strip] object containing that line's content. -Textual will call this method as required to to get content for every row of characters in the widget. +Textual will call this method as required to get content for every row of characters in the widget.
--8<-- "docs/images/render_line.excalidraw.svg" @@ -239,7 +239,7 @@ You may have noticed that the checkerboard widget makes use of some objects we h A [Segment](https://rich.readthedocs.io/en/latest/protocol.html#low-level-render) is a class borrowed from the [Rich](https://github.com/Textualize/rich) project. It is small object (actually a named tuple) which bundles a string to be displayed and a [Style](https://rich.readthedocs.io/en/latest/style.html) which tells Textual how the text should look (color, bold, italic etc). -Lets look at a simple segment which would produce the text "Hello, World!" in bold. +Let's look at a simple segment which would produce the text "Hello, World!" in bold. ```python greeting = Segment("Hello, World!", Style(bold=True)) @@ -257,7 +257,7 @@ Both Rich and Textual work with segments to generate content. A Textual app is t A [Strip][textual.strip.Strip] is a container for a number of segments covering a single *line* (or row) in the Widget. A Strip will contain at least one segment, but often many more. -A `Strip` is constructed from a list of Segment objects. Here's now you might construct a strip that displays the text "Hello, World!", but with the second word in bold: +A `Strip` is constructed from a list of `Segment` objects. Here's now you might construct a strip that displays the text "Hello, World!", but with the second word in bold: ```python segments = [ @@ -268,7 +268,7 @@ segments = [ strip = Strip(segments) ``` -The first and third Segment omit a style, which results in the widgets default style being used. The second segment has a style object which applies bold to the text "World". If this were part of a widget it would produce the text: Hello, **World**! +The first and third `Segment` omit a style, which results in the widgets default style being used. The second segment has a style object which applies bold to the text "World". If this were part of a widget it would produce the text: Hello, **World**! The `Strip` constructor has an optional second parameter, which should be the *cell length* of the strip. The strip above has a length of 13, so we could have constructed it like this: @@ -276,7 +276,7 @@ The `Strip` constructor has an optional second parameter, which should be the *c strip = Strip(segments, 13) ``` -Note that the cell length parameter is _not_ the total number of characters in the string. It is the number of terminal "cells". Some characters (such as Asian language characters and certain emoji) take up the space of two Western alphabet characters. If you don't know in advance the number of cells your segments will occupy, it is best to leave the length parameter blank so that Textual calculates it automatically. +Note that the cell length parameter is _not_ the total number of characters in the string. It is the number of terminal "cells". Some characters (such as Asian language characters and certain emoji) take up the space of two Western alphabet characters. If you don't know in advance the number of cells your segments will occupy, it is best to omit the length parameter so that Textual calculates it automatically. ### Component classes @@ -310,10 +310,10 @@ The `render_line` method calls [get_component_rich_style][textual.widget.Widget. A Line API widget can be made to scroll by extending the [ScrollView][textual.scroll_view.ScrollView] class (rather than `Widget`). The `ScrollView` class will do most of the work, but we will need to manage the following details: -1. The ScrollView class requires a *virtual size*, which is the size of the scrollable content and should be set via the `virtual_size` property. If this is larger than the widget then Textual will add scrollbars. -2. We need to update the `render_line` method to generate strips for the visible area of the widget, taking in to account the current position of the scrollbars. +1. The `ScrollView` class requires a *virtual size*, which is the size of the scrollable content and should be set via the `virtual_size` property. If this is larger than the widget then Textual will add scrollbars. +2. We need to update the `render_line` method to generate strips for the visible area of the widget, taking into account the current position of the scrollbars. -Lets add scrolling to our checkerboard example. A standard 8 x 8 board isn't sufficient to demonstrate scrolling so we will make the size of the board configurable and set it to 100 x 100, for a total of 10,000 squares. +Let's add scrolling to our checkerboard example. A standard 8 x 8 board isn't sufficient to demonstrate scrolling so we will make the size of the board configurable and set it to 100 x 100, for a total of 10,000 squares. === "checker03.py" @@ -362,7 +362,7 @@ Here's the code: We've added a style to the checkerboard which is the color of the highlighted square, with a default of "darkred". We will need this when we come to render the highlighted square. -We've also added a reactive variable called `cursor_square` which will hold the coordinate of the square underneath the mouse. Note that we have used [var][textual.reactive.var] which gives as reactive superpowers but won't automatically refresh the whole widget, because we want to update only the squares under the cursor. +We've also added a [reactive variable](./reactivity.md) called `cursor_square` which will hold the coordinate of the square underneath the mouse. Note that we have used [var][textual.reactive.var] which gives as reactive superpowers but won't automatically refresh the whole widget, because we want to update only the squares under the cursor. The `on_mouse_move` handler takes the mouse coordinates from the [MouseMove][textual.events.MouseMove] object and calculates the coordinate of the square underneath the mouse. There's a little math here, so let's break it down. From b12c5e1cdfe4ae1f1c0e73d29c6f5a5c7cdb18da Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Sun, 5 Feb 2023 12:12:27 +0100 Subject: [PATCH 92/93] typo --- docs/guide/widgets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/widgets.md b/docs/guide/widgets.md index 58a76d7aa..ee54f5899 100644 --- a/docs/guide/widgets.md +++ b/docs/guide/widgets.md @@ -362,7 +362,7 @@ Here's the code: We've added a style to the checkerboard which is the color of the highlighted square, with a default of "darkred". We will need this when we come to render the highlighted square. -We've also added a [reactive variable](./reactivity.md) called `cursor_square` which will hold the coordinate of the square underneath the mouse. Note that we have used [var][textual.reactive.var] which gives as reactive superpowers but won't automatically refresh the whole widget, because we want to update only the squares under the cursor. +We've also added a [reactive variable](./reactivity.md) called `cursor_square` which will hold the coordinate of the square underneath the mouse. Note that we have used [var][textual.reactive.var] which gives us reactive superpowers but won't automatically refresh the whole widget, because we want to update only the squares under the cursor. The `on_mouse_move` handler takes the mouse coordinates from the [MouseMove][textual.events.MouseMove] object and calculates the coordinate of the square underneath the mouse. There's a little math here, so let's break it down. From f8577f79d7a826bf422127d817bb5244c03b6b7f Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 6 Feb 2023 11:34:37 +0000 Subject: [PATCH 93/93] review fixes --- docs/examples/guide/widgets/checker02.py | 2 -- docs/guide/widgets.md | 8 ++++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/examples/guide/widgets/checker02.py b/docs/examples/guide/widgets/checker02.py index f108de8c5..b498ef204 100644 --- a/docs/examples/guide/widgets/checker02.py +++ b/docs/examples/guide/widgets/checker02.py @@ -1,8 +1,6 @@ from rich.segment import Segment -from rich.style import Style from textual.app import App, ComposeResult -from textual.geometry import Size from textual.strip import Strip from textual.widget import Widget diff --git a/docs/guide/widgets.md b/docs/guide/widgets.md index ee54f5899..8c8f20c3b 100644 --- a/docs/guide/widgets.md +++ b/docs/guide/widgets.md @@ -268,7 +268,7 @@ segments = [ strip = Strip(segments) ``` -The first and third `Segment` omit a style, which results in the widgets default style being used. The second segment has a style object which applies bold to the text "World". If this were part of a widget it would produce the text: Hello, **World**! +The first and third `Segment` omit a style, which results in the widget's default style being used. The second segment has a style object which applies bold to the text "World". If this were part of a widget it would produce the text: Hello, **World**! The `Strip` constructor has an optional second parameter, which should be the *cell length* of the strip. The strip above has a length of 13, so we could have constructed it like this: @@ -280,7 +280,7 @@ Note that the cell length parameter is _not_ the total number of characters in t ### Component classes -When applying styles to widgets we can use CSS to select the child widgets. Widgets rendered with the line API don't have children per-se, but we can still use CSS to apply styles to parts of our widget by defining *component classes*. Component classes are associated with a widget by defining a `COMPONENT_CLASSES` class variable which should be a set of strings containing CSS class names. +When applying styles to widgets we can use CSS to select the child widgets. Widgets rendered with the line API don't have children per-se, but we can still use CSS to apply styles to parts of our widget by defining *component classes*. Component classes are associated with a widget by defining a `COMPONENT_CLASSES` class variable which should be a `set` of strings containing CSS class names. In the checkerboard example above we hard-coded the color of the squares to "white" and "black". But what if we want to create a checkerboard with different colors? We can do this by defining two component classes, one for the "white" squares and one for the "dark" squares. This will allow us to change the colors with CSS. @@ -288,7 +288,7 @@ The following example replaces our hard-coded colors with component classes. === "checker02.py" - ```python title="checker02.py" hl_lines="13-15 18-25 37-38" + ```python title="checker02.py" hl_lines="11-13 16-23 35-36" --8<-- "docs/examples/guide/widgets/checker02.py" ``` @@ -317,7 +317,7 @@ Let's add scrolling to our checkerboard example. A standard 8 x 8 board isn't su === "checker03.py" - ```python title="checker03.py" hl_lines="26-30 35-36 52-53" + ```python title="checker03.py" hl_lines="4 26-30 35-36 52-53" --8<-- "docs/examples/guide/widgets/checker03.py" ```