Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: pyranha-labs/textology
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.4.0
Choose a base ref
...
head repository: pyranha-labs/textology
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v0.5.0
Choose a head ref
  • 4 commits
  • 21 files changed
  • 1 contributor

Commits on Feb 18, 2024

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    5b66562 View commit details
  2. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    bb94923 View commit details
  3. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    dd07ade View commit details
  4. version: 0.5.0 (#81)

    dfrtz authored Feb 18, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    a397964 View commit details
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -157,18 +157,18 @@ item = ListItem(

### Extended Applications

Textology App classes, such as `LayoutApp`, can replace any regular Textual App, and be used as is without any
extensions turned on. Here are examples of the most commonly used application subclasses, `LayoutApp` and
Textology App classes, such as `WidgetApp`, can replace any regular Textual App, and be used as is without any
extensions turned on. Here are examples of the most commonly used application subclasses, `WidgetApp` and
`ExtendedApp`, and their primary extended functionality being used. More detailed examples of applications based
around routes, callbacks, and standard Textual applications can be found in [Examples](https://github.com/pyranha-labs/textology/examples).

- Basic App without subclassing:

```python
from textology.apps import LayoutApp
from textology.apps import WidgetApp
from textology.widgets import Button, Container, Label

app = LayoutApp(
app = WidgetApp(
Container(
Button("Ping", callbacks={
"on_button_pressed": lambda event: app.query_one('#label').update("Ping")
@@ -193,7 +193,7 @@ around routes, callbacks, and standard Textual applications can be found in [Exa
from textology.widgets import Button, Container, Label

app = ExtendedApp(
layout=Container(
child=Container(
Button("Ping", id="ping-btn"),
Button("Pong", id="pong-btn"),
Button("Sing-a-long", id="sing-btn"),
@@ -261,7 +261,7 @@ around routes, callbacks, and standard Textual applications can be found in [Exa
from textology.widgets import Button, Container, Label

app = ExtendedApp(
layout=Container(
child=Container(
Button("Ping", id="ping-btn"),
Button("Pong", id="pong-btn"),
Button("Sing-a-long", id="sing-btn"),
@@ -342,7 +342,7 @@ around routes, callbacks, and standard Textual applications can be found in [Exa
return Label(f"Ping, pong, sing-a-long song pressed {event.button.n_clicks}")

app = ExtendedApp(
layout=Page()
child=Page()
)

app.run()
2 changes: 1 addition & 1 deletion examples/example_4_textual_with_routes.py
Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@ def on_list_view_highlighted(self, event: ListView.Highlighted) -> None:

menu_item_styles = {"height": 3, "padding": 1}
app = SimpleApp(
layout=Horizontal(
child=Horizontal(
Location(id="url"),
ListView(
ListItem(Label("Page 1", styles={**menu_item_styles, "color": "lightgreen"}), data=1),
2 changes: 1 addition & 1 deletion examples/example_5_textual_with_observers.py
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@

menu_item_styles = {"height": 3, "padding": 1}
app = ExtendedApp(
layout=Horizontal(
child=Horizontal(
ListView(
ListItem(
Label("Page 1", styles={**menu_item_styles, "color": "lightgreen"}),
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
textual>=0.32.0
textual>=0.51.0
5 changes: 3 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -54,8 +54,9 @@ generated-members=fset,fget
mixin-class-rgx=.*[Mm]ixin|.*Extension

[pylint.VARIABLES]
# The "id" variable is an attribute on Textual widgets, and should be allowed in subclasses to maintain parity.
allowed-redefined-builtins=id
# The "id" and "type" variables are attributes on some Textual widgets, and should be allowed in subclasses
# to maintain parity.
allowed-redefined-builtins=id,type
good-names=id

[isort]
2 changes: 1 addition & 1 deletion textology/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Utilities for creating text interfaces."""

__version__ = "0.4.0"
__version__ = "0.5.0"
43 changes: 21 additions & 22 deletions textology/apps.py
Original file line number Diff line number Diff line change
@@ -38,7 +38,7 @@
_DEFAULT_URL_ID = "url"


class LayoutApp(App):
class WidgetApp(App):
"""Application with a single widget for the root layout, and basic extensions.
Extensions:
@@ -51,17 +51,18 @@ class LayoutApp(App):

def __init__(
self,
layout: Callable | Widget | None = None,
child: Callable | Widget | None = None,
driver_class: type[Driver] | None = None,
css_path: CSSPathType | None = None,
watch_css: bool = False,
css_theme: str | list[str] | None = None,
css_themes: dict[str, list[CSSPathType]] | None = None,
) -> None:
"""Initialize an application with a layout.
"""Initialize an application with a widget for the layout.
Args:
layout: Primary content widget, or function to create primary content widget.
child: Root widget, or function to create root widget.
Use a callable to delay creation until end of initialization.
Defaults to a blank Container with "content" id.
driver_class: Driver class or `None` to auto-detect.
This will be used by some Textual tools.
@@ -96,11 +97,11 @@ def __init__(
css_path=css_path,
watch_css=watch_css,
)
if not layout:
layout = Container(id=_DEFAULT_CONTENT_ID)
if not child:
child = Container(id=_DEFAULT_CONTENT_ID)
else:
layout = layout() if isinstance(layout, Callable) else layout
self.layout = layout
child = child() if isinstance(child, Callable) else child
self.child = child

def apply_theme(self, theme: str | list[str] | None = None) -> None:
"""Load a CSS theme.
@@ -122,13 +123,9 @@ def apply_theme(self, theme: str | list[str] | None = None) -> None:
for css_theme_path in self.css_themes[css_theme]:
if css_theme_path in css_paths:
css_paths.remove(css_theme_path)
# Post Textual 0.42.0 no longer casts to string, and stores as tuple.
# Manually cast to string for guaranteed behavior, and attempt to pop both types.
str_path = str(css_theme_path)
if (str_path, "") in stylesheet.source:
stylesheet.source.pop((str_path, ""))
elif str_path in stylesheet.source:
stylesheet.source.pop(str_path)
self.log.info(f"Removed CSS theme: {self.css_theme}")

# Apply new themes second.
@@ -167,15 +164,15 @@ def css_theme(self) -> list[str] | None:
return self._css_theme

def compose(self) -> ComposeResult:
"""Default compose with provided layout.
"""Provide the root widget for the application.
Yields:
Layout widget set on instantiation.
Child widget set on instantiation that will be used as the root of the application.
"""
yield self.layout() if isinstance(self.layout, Callable) else self.layout
yield self.child


class ExtendedApp(LayoutApp, ObserverManager):
class ExtendedApp(WidgetApp, ObserverManager):
"""Textual application with multiple Textology extensions for automating UI updates.
Extensions:
@@ -187,7 +184,7 @@ class ExtendedApp(LayoutApp, ObserverManager):

def __init__( # pylint: disable=too-many-arguments
self,
layout: Callable | Widget | None = None,
child: Callable | Widget | None = None,
use_pages: bool = False,
pages: list[Page | ModuleType | str | Callable] | None = None,
driver_class: type[Driver] | None = None,
@@ -200,7 +197,9 @@ def __init__( # pylint: disable=too-many-arguments
"""Initialize an application with tracking for input/output callbacks.
Args:
layout: Primary content widget, or function to create primary content widget.
child: Root widget, or function to create root widget.
Use a callable to delay creation until end of initialization.
Defaults to a blank Container with "content" id.
use_pages: Whether to enable multi-page application support.
When enabled, this will include automatic URL routing callbacks via "widgets.Location".
Force enabled if "pages" are provided. Enabling without "pages" allows registering pages later.
@@ -220,12 +219,12 @@ def __init__( # pylint: disable=too-many-arguments
Raises:
CssPathError: When the supplied CSS path(s) are an unexpected type.
"""
layout = layout or Container(
child = child or Container(
Location(id=_DEFAULT_URL_ID),
Container(id=_DEFAULT_CONTENT_ID) if not use_pages else PageContainer(id=_DEFAULT_CONTENT_ID),
)
super().__init__(
layout=layout,
child=child,
driver_class=driver_class,
css_path=css_path,
watch_css=watch_css,
@@ -295,7 +294,7 @@ def enable_pages(self) -> None:
raise ValueError("Location widget must have an id if pages are enabled")

page_container = None
for node in walk_all_children(self.layout):
for node in walk_all_children(self.child):
if isinstance(node, PageContainer):
page_container = node
break
@@ -491,7 +490,7 @@ def route(self, path: str, methods: list[str] = ("GET",)) -> Callable:

def _update_location(self) -> None:
"""Walk the layout tree to allow finding the application's Location widget at any point in the lifecycle."""
for node in walk_all_children(self.layout):
for node in walk_all_children(self.child):
if isinstance(node, Location):
self._location = node
return
56 changes: 56 additions & 0 deletions textology/dash_compat.py
Original file line number Diff line number Diff line change
@@ -10,9 +10,14 @@
developers may need to leverage the native classes and designs from the base libraries.
"""

import logging
from types import ModuleType
from typing import Callable

from textual.app import CSSPathType
from textual.driver import Driver
from textual.widget import Widget

from .apps import ExtendedApp

# Compatibility aliases for Dash.
@@ -44,6 +49,57 @@ class DashCompatApp(ExtendedApp):
- URL history for navigating via ".back()", ".forward()", etc.
"""

def __init__(
self,
layout: Callable | Widget | None = None,
use_pages: bool = False,
pages: list[Page | ModuleType | str | Callable] | None = None,
driver_class: type[Driver] | None = None,
css_path: CSSPathType | None = None,
watch_css: bool = False,
css_theme: str | list[str] | None = None,
css_themes: dict[str, list[CSSPathType]] | None = None,
logger: logging.Logger | None = None,
) -> None:
"""Initialize an application with a widget for the layout.
Compatibility alias for ExtendedApp.__init__() call with Dash variable names.
Args:
layout: Root widget, or function to create root widget.
Use a callable to delay creation until end of initialization.
Defaults to a blank Container with "content" id.
use_pages: Whether to enable multi-page application support.
When enabled, this will include automatic URL routing callbacks via "widgets.Location".
Force enabled if "pages" are provided. Enabling without "pages" allows registering pages later.
pages: Initial pages to load into multi-page applications.
Refer to "register_page()" for options.
driver_class: Driver class or `None` to auto-detect.
This will be used by some Textual tools.
css_path: Path to CSS or `None` to use the `CSS_PATH` class variable.
To load multiple CSS files, pass a list of strings or paths which will be loaded in order.
watch_css: Reload CSS if the files changed.
This is set automatically if you are using `textual run` with the `dev` switch.
css_theme: Initial CSS theme to load from "css_themes".
Themes are applied in addition to base "css_path" values, rather than in place of.
css_themes: Mapping of CSS paths by string names, or `None` to use the `CSS_THEMES` class variable.
logger: Custom logger to send callback messages to.
Raises:
CssPathError: When the supplied CSS path(s) are an unexpected type.
"""
super().__init__(
child=layout,
use_pages=use_pages,
pages=pages,
driver_class=driver_class,
css_path=css_path,
watch_css=watch_css,
css_theme=css_theme,
css_themes=css_themes,
logger=logger,
)

def callback(
self,
*dependencies: Dependency,
2 changes: 1 addition & 1 deletion textology/test/test_apps.py
Original file line number Diff line number Diff line change
@@ -144,7 +144,7 @@ def layout_page2(clicks: int | None = None) -> widgets.Label:
return widgets.Label(f"Page 2 clicks {clicks}")

app = apps.ExtendedApp(
layout=widgets.Container(
child=widgets.Container(
widgets.Button("Button 1", id="btn1"),
widgets.Button("Button 2", id="btn2"),
widgets.Location(id="url"),
27 changes: 8 additions & 19 deletions textology/widgets/__init__.py
Original file line number Diff line number Diff line change
@@ -34,6 +34,7 @@
from ._popup_text import PopupText
from ._store import Store
from ._textual._checkbox import Checkbox
from ._textual._collapsible import Collapsible
from ._textual._containers import Center
from ._textual._containers import Container
from ._textual._containers import Grid
@@ -46,6 +47,7 @@
from ._textual._containers import VerticalScroll
from ._textual._content_switcher import ContentSwitcher
from ._textual._data_table import DataTable
from ._textual._digits import Digits
from ._textual._directory_tree import DirectoryTree
from ._textual._footer import Footer
from ._textual._header import Header
@@ -60,6 +62,7 @@
from ._textual._radio_button import RadioButton
from ._textual._radio_set import RadioSet
from ._textual._rich_log import RichLog
from ._textual._rule import Rule
from ._textual._select import Select
from ._textual._selection_list import SelectionList
from ._textual._sparkline import Sparkline
@@ -69,20 +72,11 @@
from ._textual._tabbed_content import TabPane
from ._textual._tabs import Tab
from ._textual._tabs import Tabs
from ._textual._text_area import TextArea
from ._textual._text_input import TextInput
from ._textual._tooltip import Tooltip
from ._textual._tree import Tree

if textual_version.major >= 0:
if textual_version.minor >= 32:
from ._textual._digits import Digits
if textual_version.minor >= 36:
from ._textual._rule import Rule
if textual_version.minor >= 37:
from ._textual._collapsible import Collapsible
if textual_version.minor >= 38:
from ._textual._text_area import TextArea

_module_cache: dict[str, type[Widget]] = {
"Widget": Widget,
}
@@ -91,9 +85,11 @@
"Center": "._textual._containers",
"Checkbox": "._textual._checkbox",
"Clickable": "._extensions",
"Collapsible": "._textual._collapsible",
"Container": "._textual._containers",
"ContentSwitcher": "._textual._content_switcher",
"DataTable": "._textual._data_table",
"Digits": "._textual._digits",
"DirectoryTree": "._textual._directory_tree",
"Footer": "._textual._footer",
"Grid": "._textual._containers",
@@ -121,6 +117,7 @@
"RadioButton": "._textual._radio_button",
"RadioSet": "._textual._radio_set",
"RichLog": "._textual._rich_log",
"Rule": "._textual._rule",
"ScrollableContainer": "._textual._containers",
"Select": "._textual._select",
"SelectionList": "._textual._selection_list",
@@ -132,6 +129,7 @@
"TabbedContent": "._textual._tabbed_content",
"Tabs": "._textual._tabs",
"TabPane": "._textual._tabbed_content",
"TextArea": "._textual._text_area",
"TextInput": "._textual._text_input",
"Tooltip": "._textual._tooltip",
"Tree": "._textual._tree",
@@ -141,15 +139,6 @@
"WidgetInitExtension": "._extensions",
"walk_all_children": "._extensions",
}
if textual_version.major >= 0:
if textual_version.minor >= 33:
_module_map["Digits"] = "._textual._digits"
if textual_version.minor >= 36:
_module_map["Rule"] = "._textual._rule"
if textual_version.minor >= 37:
_module_map["Collapsible"] = "._textual._collapsible"
if textual_version.minor >= 38:
_module_map["TextArea"] = "._textual._text_area"
__all__ = tuple(_module_map.keys())


Loading