Skip to content

Commit

Permalink
new: Add multi-version support and support for widgets up to textual …
Browse files Browse the repository at this point in the history
…0.38.
  • Loading branch information
dfrtz authored Sep 30, 2023
1 parent 794ac19 commit 9ec0de0
Show file tree
Hide file tree
Showing 6 changed files with 459 additions and 52 deletions.
215 changes: 163 additions & 52 deletions textology/widgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,57 +5,168 @@
any extension functionality is used. All Textual basic widgets should be 100% forward compatible with Textology widgets,
and all Textology widgets should be 100% backwards compatible with Textual widgets, in settings where no extended
functionality is being used.
This file must be kept in sync with the __init__.pyi's import list.
The pyi file is used by text editors and type checkers to see the lazily loaded classes.
"""

from ._button import Button
from ._extensions import Clickable
from ._extensions import WidgetExtension
from ._extensions import WidgetInitExtension
from ._horizontal_menus import HorizontalMenus
from ._list_item import ListItem
from ._list_item_header import ListItemHeader
from ._list_item_meta import ListItemMeta
from ._list_view import ListView
from ._location import Location
from ._modal_dialog import ModalDialog
from ._popup_text import PopupText
from ._store import Store
from ._textual._checkbox import Checkbox
from ._textual._containers import Center
from ._textual._containers import Container
from ._textual._containers import Grid
from ._textual._containers import Horizontal
from ._textual._containers import HorizontalScroll
from ._textual._containers import Middle
from ._textual._containers import PageContainer
from ._textual._containers import ScrollableContainer
from ._textual._containers import Vertical
from ._textual._containers import VerticalScroll
from ._textual._content_switcher import ContentSwitcher
from ._textual._data_table import DataTable
from ._textual._directory_tree import DirectoryTree
from ._textual._footer import Footer
from ._textual._header import Header
from ._textual._label import Label
from ._textual._loading_indicator import LoadingIndicator
from ._textual._log import Log
from ._textual._markdown import Markdown
from ._textual._markdown import MarkdownViewer
from ._textual._option_list import OptionList
from ._textual._pretty import Pretty
from ._textual._progress_bar import ProgressBar
from ._textual._radio_button import RadioButton
from ._textual._radio_set import RadioSet
from ._textual._rich_log import RichLog
from ._textual._select import Select
from ._textual._selection_list import SelectionList
from ._textual._sparkline import Sparkline
from ._textual._static import Static
from ._textual._switch import Switch
from ._textual._tabbed_content import TabbedContent
from ._textual._tabbed_content import TabPane
from ._textual._tabs import Tab
from ._textual._tabs import Tabs
from ._textual._text_input import TextInput
from ._textual._tooltip import Tooltip
from ._textual._tree import Tree
import typing
from importlib import import_module

from textual import __version__ as textual_version # Dynamic attribute in textual. pylint: disable=no-name-in-module
from textual.widget import Widget

_major, _minor, _maintenance = textual_version.split(".")
_major, _minor, _maintenance = int(_major), int(_minor), int(_maintenance)

# Lazily load widgets to decrease startup time and allow multi version support.
if typing.TYPE_CHECKING:
from ._button import Button
from ._extensions import Clickable
from ._extensions import WidgetExtension
from ._extensions import WidgetInitExtension
from ._horizontal_menus import HorizontalMenus
from ._list_item import ListItem
from ._list_item_header import ListItemHeader
from ._list_item_meta import ListItemMeta
from ._list_view import ListView
from ._location import Location
from ._modal_dialog import ModalDialog
from ._popup_text import PopupText
from ._store import Store
from ._textual._checkbox import Checkbox
from ._textual._containers import Center
from ._textual._containers import Container
from ._textual._containers import Grid
from ._textual._containers import Horizontal
from ._textual._containers import HorizontalScroll
from ._textual._containers import Middle
from ._textual._containers import PageContainer
from ._textual._containers import ScrollableContainer
from ._textual._containers import Vertical
from ._textual._containers import VerticalScroll
from ._textual._content_switcher import ContentSwitcher
from ._textual._data_table import DataTable
from ._textual._directory_tree import DirectoryTree
from ._textual._footer import Footer
from ._textual._header import Header
from ._textual._label import Label
from ._textual._loading_indicator import LoadingIndicator
from ._textual._log import Log
from ._textual._markdown import Markdown
from ._textual._markdown import MarkdownViewer
from ._textual._option_list import OptionList
from ._textual._pretty import Pretty
from ._textual._progress_bar import ProgressBar
from ._textual._radio_button import RadioButton
from ._textual._radio_set import RadioSet
from ._textual._rich_log import RichLog
from ._textual._select import Select
from ._textual._selection_list import SelectionList
from ._textual._sparkline import Sparkline
from ._textual._static import Static
from ._textual._switch import Switch
from ._textual._tabbed_content import TabbedContent
from ._textual._tabbed_content import TabPane
from ._textual._tabs import Tab
from ._textual._tabs import Tabs
from ._textual._text_input import TextInput
from ._textual._tooltip import Tooltip
from ._textual._tree import Tree

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

_widget_cache: dict[str, type[Widget]] = {
"Widget": Widget,
}
_widget_module_map = {
"Button": "._button",
"Center": "._textual._containers",
"Checkbox": "._textual._checkbox",
"Clickable": "._extensions",
"Container": "._textual._containers",
"ContentSwitcher": "._textual._content_switcher",
"DataTable": "._textual._data_table",
"DirectoryTree": "._textual._directory_tree",
"Footer": "._textual._footer",
"Grid": "._textual._containers",
"Header": "._textual._header",
"Horizontal": "._textual._containers",
"HorizontalMenus": "._horizontal_menus",
"HorizontalScroll": "._textual._containers",
"Label": "._textual._label",
"ListItem": "._list_item",
"ListItemHeader": "._list_item_header",
"ListItemMeta": "._list_item_meta",
"ListView": "._list_view",
"LoadingIndicator": "._textual._loading_indicator",
"Location": "._location",
"Log": "._textual._log",
"Markdown": "._textual._markdown",
"MarkdownViewer": "._textual._markdown",
"Middle": "._textual._containers",
"ModalDialog": "._modal_dialog",
"OptionList": "._textual._option_list",
"PageContainer": "._textual._containers",
"PopupText": "._popup_text",
"Pretty": "._textual._pretty",
"ProgressBar": "._textual._progress_bar",
"RadioButton": "._textual._radio_button",
"RadioSet": "._textual._radio_set",
"RichLog": "._textual._rich_log",
"ScrollableContainer": "._textual._containers",
"Select": "._textual._select",
"SelectionList": "._textual._selection_list",
"Sparkline": "._textual._sparkline",
"Static": "._textual._static",
"Store": "._store",
"Switch": "._textual._switch",
"Tab": "._textual._tabs",
"TabbedContent": "._textual._tabbed_content",
"Tabs": "._textual._tabs",
"TabPane": "._textual._tabbed_content",
"TextInput": "._textual._text_input",
"Tooltip": "._textual._tooltip",
"Tree": "._textual._tree",
"Vertical": "._textual._containers",
"VerticalScroll": "._textual._containers",
"WidgetExtension": "._extensions",
"WidgetInitExtension": "._extensions",
}
if _major >= 0:
if _minor >= 33:
_widget_module_map["Digits"] = "._textual._digits"
if _minor >= 36:
_widget_module_map["Rule"] = "._textual._rule"
if _minor >= 37:
_widget_module_map["Collapsible"] = "._textual._collapsible"
if _minor >= 38:
_widget_module_map["TextArea"] = "._textual._text_area"
__all__ = [
*_widget_module_map.keys(),
]


def __getattr__(class_name: str) -> type[Widget]:
"""Lazily load widgets to decrease startup time."""
try:
return _widget_cache[class_name]
except KeyError:
pass

if class_name not in __all__:
raise ImportError(f"Package 'textology.widgets' has no class '{class_name}'")

widget_module_path = _widget_module_map.get(class_name)
module = import_module(widget_module_path, package="textology.widgets")
widget_class = getattr(module, class_name)
_widget_cache[class_name] = widget_class
return widget_class
73 changes: 73 additions & 0 deletions textology/widgets/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Collection of new, and extended, Textual widgets.
This interface file must be kept in sync with the __init__.py's `__all__` list.
This file is used by text editors and type checkers to see the lazily loaded classes.
"""

from textual import __version__ as textual_version

_major, _minor, _maintenance = textual_version.split(".")
_major, _minor, _maintenance = int(_major), int(_minor), int(_maintenance)

from ._button import Button as Button
from ._extensions import Clickable as Clickable
from ._extensions import WidgetExtension as WidgetExtension
from ._extensions import WidgetInitExtension as WidgetInitExtension
from ._horizontal_menus import HorizontalMenus as HorizontalMenus
from ._list_item import ListItem as ListItem
from ._list_item_header import ListItemHeader as ListItemHeader
from ._list_item_meta import ListItemMeta as ListItemMeta
from ._list_view import ListView as ListView
from ._location import Location as Location
from ._modal_dialog import ModalDialog as ModalDialog
from ._popup_text import PopupText as PopupText
from ._store import Store as Store
from ._textual._checkbox import Checkbox as Checkbox
from ._textual._containers import Center as Center
from ._textual._containers import Container as Container
from ._textual._containers import Grid as Grid
from ._textual._containers import Horizontal as Horizontal
from ._textual._containers import HorizontalScroll as HorizontalScroll
from ._textual._containers import Middle as Middle
from ._textual._containers import PageContainer as PageContainer
from ._textual._containers import ScrollableContainer as ScrollableContainer
from ._textual._containers import Vertical as Vertical
from ._textual._containers import VerticalScroll as VerticalScroll
from ._textual._content_switcher import ContentSwitcher as ContentSwitcher
from ._textual._data_table import DataTable as DataTable
from ._textual._directory_tree import DirectoryTree as DirectoryTree
from ._textual._footer import Footer as Footer
from ._textual._header import Header as Header
from ._textual._label import Label as Label
from ._textual._loading_indicator import LoadingIndicator as LoadingIndicator
from ._textual._log import Log as Log
from ._textual._markdown import Markdown as Markdown
from ._textual._markdown import MarkdownViewer as MarkdownViewer
from ._textual._option_list import OptionList as OptionList
from ._textual._pretty import Pretty as Pretty
from ._textual._progress_bar import ProgressBar as ProgressBar
from ._textual._radio_button import RadioButton as RadioButton
from ._textual._radio_set import RadioSet as RadioSet
from ._textual._rich_log import RichLog as RichLog
from ._textual._select import Select as Select
from ._textual._selection_list import SelectionList as SelectionList
from ._textual._sparkline import Sparkline as Sparkline
from ._textual._static import Static as Static
from ._textual._switch import Switch as Switch
from ._textual._tabbed_content import TabbedContent as TabbedContent
from ._textual._tabbed_content import TabPane as TabPane
from ._textual._tabs import Tab as Tab
from ._textual._tabs import Tabs as Tabs
from ._textual._text_input import TextInput as TextInput
from ._textual._tooltip import Tooltip as Tooltip
from ._textual._tree import Tree as Tree

if _major >= 0:
if _minor >= 32:
from ._textual._digits import Digits as Digits
if _minor >= 36:
from ._textual._rule import Rule as Rule
if _minor >= 37:
from ._textual._collapsible import Collapsible as Collapsible
if _minor >= 38:
from ._textual._text_area import TextArea as TextArea
62 changes: 62 additions & 0 deletions textology/widgets/_textual/_collapsible.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Extended Textual Collapsible widget."""

from typing import Any
from typing import Callable

from textual import events
from textual import widgets
from textual.widget import Widget

from .._extensions import WidgetExtension


class Collapsible(WidgetExtension, widgets.Collapsible):
"""An extended collapsible container."""

def __init__(
self,
*children: Widget,
title: str = "Toggle",
collapsed: bool = True,
collapsed_symbol: str = "▶",
expanded_symbol: str = "▼",
name: str | None = None,
id: str | None = None,
classes: str | None = None,
disabled: bool = False,
styles: dict[str, Any] | None = None,
disabled_messages: list[type[events.Message]] | None = None,
callbacks: dict[str, Callable] | None = None,
) -> None:
"""Initialize the collapsible widget.
Args:
*children: Contents that will be collapsed/expanded.
title: Title of the collapsed/expanded contents.
collapsed: Default status of the contents.
collapsed_symbol: Collapsed symbol before the title.
expanded_symbol: Expanded symbol before the title.
name: The name of the collapsible.
id: The ID of the widget in the DOM.
classes: The CSS classes of the widget.
disabled: Whether the widget is disabled or not.
styles: Local inline styles to apply on top of the class' styles for only this instance.
disabled_messages: List of messages to disable on this widget instance only.
callbacks: Mapping of callbacks to send messages to instead of sending to default handler.
"""
super().__init__(
*children,
title=title,
collapsed=collapsed,
collapsed_symbol=collapsed_symbol,
expanded_symbol=expanded_symbol,
name=name,
id=id,
classes=classes,
disabled=disabled,
)
self.__extend_widget__(
styles=styles,
disabled_messages=disabled_messages,
callbacks=callbacks,
)
Loading

0 comments on commit 9ec0de0

Please sign in to comment.