Export types & doc improvements (#2329)

* Export types used in app.py

* Export more linked types/errors/classes.

* Remove custom template.

* Address review comments.

We need to have explicit 'Returns:' sections in properties if we want to link to the return type while https://github.com/mkdocstrings/python/issues/65 is open.

* Improve docs.
This commit is contained in:
Rodrigo Girão Serrão
2023-05-02 15:12:53 +01:00
committed by GitHub
parent 911ffdb144
commit 914e50a70f
20 changed files with 108 additions and 103 deletions

View File

@@ -1,67 +0,0 @@
{{ log.debug("Rendering " + attribute.path) }}
<div class="doc doc-object doc-attribute">
{% 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 %}
<span class="doc doc-object-name doc-attribute-name">{% if show_full_path %}{{ attribute.path }}{% else %}{{ attribute.name }}{% endif %}</span>
{% 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 %}
<div class="doc doc-contents {% if root %}first{% endif %}">
{% with docstring_sections = attribute.docstring.parsed %}
{% include "docstring.html" with context %}
{% endwith %}
</div>
{% endwith %}
</div>

1
docs/api/errors.md Normal file
View File

@@ -0,0 +1 @@
::: textual.errors

1
docs/api/filter.md Normal file
View File

@@ -0,0 +1 @@
::: textual.filter

1
docs/api/scrollbar.md Normal file
View File

@@ -0,0 +1 @@
::: textual.scrollbar

1
docs/api/types.md Normal file
View File

@@ -0,0 +1 @@
::: textual.types

View File

@@ -164,6 +164,8 @@ nav:
- "api/coordinate.md" - "api/coordinate.md"
- "api/dom_node.md" - "api/dom_node.md"
- "api/events.md" - "api/events.md"
- "api/errors.md"
- "api/filter.md"
- "api/geometry.md" - "api/geometry.md"
- "api/index.md" - "api/index.md"
- "api/logger.md" - "api/logger.md"
@@ -175,9 +177,11 @@ nav:
- "api/query.md" - "api/query.md"
- "api/reactive.md" - "api/reactive.md"
- "api/screen.md" - "api/screen.md"
- "api/scrollbar.md"
- "api/scroll_view.md" - "api/scroll_view.md"
- "api/strip.md" - "api/strip.md"
- "api/timer.md" - "api/timer.md"
- "api/types.md"
- "api/walk.md" - "api/walk.md"
- "api/widget.md" - "api/widget.md"
- "api/work.md" - "api/work.md"

View File

@@ -21,6 +21,10 @@ if TYPE_CHECKING:
"""Animation keys are the id of the object and the attribute being animated.""" """Animation keys are the id of the object and the attribute being animated."""
EasingFunction = Callable[[float], float] EasingFunction = Callable[[float], float]
"""Signature for a function that parametrises animation speed.
An easing function must map the interval [0, 1] into the interval [0, 1].
"""
class AnimationError(Exception): class AnimationError(Exception):
@@ -32,6 +36,12 @@ ReturnType = TypeVar("ReturnType")
@runtime_checkable @runtime_checkable
class Animatable(Protocol): class Animatable(Protocol):
"""Protocol for objects that can have their intrinsic values animated.
For example, the transition between two colors can be animated
because the class [`Color`][textual.color.Color.blend] satisfies this protocol.
"""
def blend( def blend(
self: ReturnType, destination: ReturnType, factor: float self: ReturnType, destination: ReturnType, factor: float
) -> ReturnType: # pragma: no cover ) -> ReturnType: # pragma: no cover

View File

@@ -11,7 +11,7 @@ if TYPE_CHECKING:
class NoActiveAppError(RuntimeError): class NoActiveAppError(RuntimeError):
pass """Runtime error raised if we try to retrieve the active app when there is none."""
active_app: ContextVar["App"] = ContextVar("active_app") active_app: ContextVar["App"] = ContextVar("active_app")

View File

@@ -8,6 +8,8 @@ if TYPE_CHECKING:
class MessageTarget(Protocol): class MessageTarget(Protocol):
"""Protocol that must be followed by objects that can receive messages."""
async def _post_message(self, message: "Message") -> bool: async def _post_message(self, message: "Message") -> bool:
... ...
@@ -25,6 +27,7 @@ class EventTarget(Protocol):
SegmentLines = List[List["Segment"]] SegmentLines = List[List["Segment"]]
CallbackType = Union[Callable[[], Awaitable[None]], Callable[[], None]] CallbackType = Union[Callable[[], Awaitable[None]], Callable[[], None]]
"""Type used for arbitrary callables used in callbacks."""
WatchCallbackType = Union[ WatchCallbackType = Union[
Callable[[], Awaitable[None]], Callable[[], Awaitable[None]],
Callable[[Any], Awaitable[None]], Callable[[Any], Awaitable[None]],
@@ -33,3 +36,4 @@ WatchCallbackType = Union[
Callable[[Any], None], Callable[[Any], None],
Callable[[Any, Any], None], Callable[[Any, Any], None],
] ]
"""Type used for callbacks passed to the `watch` method of widgets."""

View File

@@ -97,8 +97,11 @@ from .widget import AwaitMount, Widget
if TYPE_CHECKING: if TYPE_CHECKING:
from typing_extensions import Coroutine, TypeAlias from typing_extensions import Coroutine, TypeAlias
# Unused & ignored imports are needed for the docs to link to these objects:
from .css.query import WrongType # type: ignore # noqa: F401
from .devtools.client import DevtoolsClient from .devtools.client import DevtoolsClient
from .pilot import Pilot from .pilot import Pilot
from .widget import MountError # type: ignore # noqa: F401
PLATFORM = platform.system() PLATFORM = platform.system()
WINDOWS = PLATFORM == "Windows" WINDOWS = PLATFORM == "Windows"
@@ -137,6 +140,7 @@ ComposeResult = Iterable[Widget]
RenderResult = RenderableType RenderResult = RenderableType
AutopilotCallbackType: TypeAlias = "Callable[[Pilot], Coroutine[Any, Any, None]]" AutopilotCallbackType: TypeAlias = "Callable[[Pilot], Coroutine[Any, Any, None]]"
"""Signature for valid callbacks that can be used to control apps."""
class AppError(Exception): class AppError(Exception):
@@ -167,6 +171,7 @@ CSSPathType = Union[
PurePath, PurePath,
List[Union[str, PurePath]], List[Union[str, PurePath]],
] ]
"""Valid ways of specifying paths to CSS files."""
CallThreadReturnType = TypeVar("CallThreadReturnType") CallThreadReturnType = TypeVar("CallThreadReturnType")
@@ -186,17 +191,7 @@ class _NullFile:
@rich.repr.auto @rich.repr.auto
class App(Generic[ReturnType], DOMNode): class App(Generic[ReturnType], DOMNode):
"""The base class for Textual Applications. """The base class for Textual Applications."""
Args:
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.
Raises:
CssPathError: When the supplied CSS path(s) are an unexpected type.
"""
CSS: ClassVar[str] = "" CSS: ClassVar[str] = ""
"""Inline CSS, useful for quick scripts. This is loaded after CSS_PATH, """Inline CSS, useful for quick scripts. This is loaded after CSS_PATH,
@@ -218,8 +213,10 @@ class App(Generic[ReturnType], DOMNode):
""" """
SCREENS: ClassVar[dict[str, Screen | Callable[[], Screen]]] = {} SCREENS: ClassVar[dict[str, Screen | Callable[[], Screen]]] = {}
"""Screens associated with the app for the lifetime of the app."""
_BASE_PATH: str | None = None _BASE_PATH: str | None = None
CSS_PATH: ClassVar[CSSPathType | None] = None CSS_PATH: ClassVar[CSSPathType | None] = None
"""File paths to load CSS from."""
TITLE: str | None = None TITLE: str | None = None
"""A class variable to set the *default* title for the application. """A class variable to set the *default* title for the application.
@@ -255,6 +252,20 @@ class App(Generic[ReturnType], DOMNode):
css_path: CSSPathType | None = None, css_path: CSSPathType | None = None,
watch_css: bool = False, watch_css: bool = False,
): ):
"""Create an instance of an app.
Args:
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.
Raises:
CssPathError: When the supplied CSS path(s) are an unexpected type.
"""
super().__init__() super().__init__()
self.features: frozenset[FeatureFlag] = parse_features(os.getenv("TEXTUAL", "")) self.features: frozenset[FeatureFlag] = parse_features(os.getenv("TEXTUAL", ""))
@@ -406,7 +417,7 @@ class App(Generic[ReturnType], DOMNode):
@property @property
def return_value(self) -> ReturnType | None: def return_value(self) -> ReturnType | None:
"""The return value of the app, or `None` if it as not yet been set. """The return value of the app, or `None` if it has not yet been set.
The return value is set when calling [exit][textual.app.App.exit]. The return value is set when calling [exit][textual.app.App.exit].
""" """
@@ -414,10 +425,11 @@ class App(Generic[ReturnType], DOMNode):
@property @property
def children(self) -> Sequence["Widget"]: def children(self) -> Sequence["Widget"]:
"""A view on to the App's children. """A view onto the app's immediate children.
This attribute exists on all widgets. This attribute exists on all widgets.
In the case of the App, it will only every contain a single child, which will be the currently active screen. In the case of the App, it will only ever contain a single child, which will
be the currently active screen.
Returns: Returns:
A sequence of widgets. A sequence of widgets.
@@ -499,7 +511,7 @@ class App(Generic[ReturnType], DOMNode):
@property @property
def screen_stack(self) -> Sequence[Screen]: def screen_stack(self) -> Sequence[Screen]:
"""The current screen stack. """A snapshot of the current screen stack.
Returns: Returns:
A snapshot of the current state of the screen stack. A snapshot of the current state of the screen stack.
@@ -523,7 +535,7 @@ class App(Generic[ReturnType], DOMNode):
@property @property
def focused(self) -> Widget | None: def focused(self) -> Widget | None:
"""The widget that is focused on the currently active screen. """The widget that is focused on the currently active screen, or `None`.
Focused widgets receive keyboard input. Focused widgets receive keyboard input.
@@ -534,7 +546,7 @@ class App(Generic[ReturnType], DOMNode):
@property @property
def namespace_bindings(self) -> dict[str, tuple[DOMNode, Binding]]: def namespace_bindings(self) -> dict[str, tuple[DOMNode, Binding]]:
"""Get current bindings. """Get currently active bindings.
If no widget is focused, then app-level bindings are returned. If no widget is focused, then app-level bindings are returned.
If a widget is focused, then any bindings present in the active screen and app are merged and returned. If a widget is focused, then any bindings present in the active screen and app are merged and returned.
@@ -542,8 +554,7 @@ class App(Generic[ReturnType], DOMNode):
This property may be used to inspect current bindings. This property may be used to inspect current bindings.
Returns: Returns:
A mapping of keys onto pairs of nodes and bindings.
A mapping of keys on to node + binding.
""" """
namespace_binding_map: dict[str, tuple[DOMNode, Binding]] = {} namespace_binding_map: dict[str, tuple[DOMNode, Binding]] = {}
@@ -650,7 +661,7 @@ class App(Generic[ReturnType], DOMNode):
@property @property
def screen(self) -> Screen: def screen(self) -> Screen:
"""Screen: The current screen. """The current active screen.
Returns: Returns:
The currently active (visible) screen. The currently active (visible) screen.
@@ -689,7 +700,7 @@ class App(Generic[ReturnType], DOMNode):
@property @property
def log(self) -> Logger: def log(self) -> Logger:
"""Textual log interface. """The textual logger.
Example: Example:
```python ```python
@@ -1162,14 +1173,15 @@ class App(Generic[ReturnType], DOMNode):
Args: Args:
id: The ID of the node to search for. id: The ID of the node to search for.
expect_type: Require the object be of the supplied type, or None for any type. expect_type: Require the object be of the supplied type,
or use `None` to apply no type restriction.
Returns: Returns:
The first child of this node with the specified ID. The first child of this node with the specified ID.
Raises: Raises:
NoMatches: if no children could be found for this ID NoMatches: If no children could be found for this ID.
WrongType: if the wrong type was found. WrongType: If the wrong type was found.
""" """
return ( return (
self.screen.get_child_by_id(id) self.screen.get_child_by_id(id)
@@ -1463,7 +1475,6 @@ class App(Generic[ReturnType], DOMNode):
with [install_screen][textual.app.App.install_screen]. with [install_screen][textual.app.App.install_screen].
Textual will also uninstall screens automatically on exit. Textual will also uninstall screens automatically on exit.
Args: Args:
screen: The screen to uninstall or the name of a installed screen. screen: The screen to uninstall or the name of a installed screen.
@@ -1836,6 +1847,7 @@ class App(Generic[ReturnType], DOMNode):
*widgets: The widget(s) to register. *widgets: The widget(s) to register.
before: A location to mount before. before: A location to mount before.
after: A location to mount after. after: A location to mount after.
Returns: Returns:
List of modified widgets. List of modified widgets.
""" """
@@ -2140,7 +2152,7 @@ class App(Generic[ReturnType], DOMNode):
or None to use app. or None to use app.
Returns: Returns:
True if the event has handled. True if the event has been handled.
""" """
if isinstance(action, str): if isinstance(action, str):
target, params = actions.parse(action) target, params = actions.parse(action)

View File

@@ -536,7 +536,7 @@ class Color(NamedTuple):
return self.darken(-amount, alpha) return self.darken(-amount, alpha)
@lru_cache(maxsize=1024) @lru_cache(maxsize=1024)
def get_contrast_text(self, alpha=0.95) -> Color: def get_contrast_text(self, alpha: float = 0.95) -> Color:
"""Get a light or dark color that best contrasts this color, for use with text. """Get a light or dark color that best contrasts this color, for use with text.
Args: Args:
@@ -576,9 +576,8 @@ class Gradient:
Positions that are between stops will return a blended color. Positions that are between stops will return a blended color.
Args: Args:
factor: A number between 0 and 1, where 0 is the first stop, and 1 is the last. position: A number between 0 and 1, where 0 is the first stop, and 1 is the last.
Returns: Returns:
A color. A color.

View File

@@ -54,12 +54,16 @@ if TYPE_CHECKING:
from .worker import Worker, WorkType, ResultType from .worker import Worker, WorkType, ResultType
from typing_extensions import Self, TypeAlias from typing_extensions import Self, TypeAlias
# Unused & ignored imports are needed for the docs to link to these objects:
from .css.query import NoMatches, TooManyMatches, WrongType # type: ignore # noqa: F401
from typing_extensions import Literal from typing_extensions import Literal
_re_identifier = re.compile(IDENTIFIER) _re_identifier = re.compile(IDENTIFIER)
WalkMethod: TypeAlias = Literal["depth", "breadth"] WalkMethod: TypeAlias = Literal["depth", "breadth"]
"""Valid walking methods for the [`DOMNode.walk_children` method][textual.dom.DOMNode.walk_children]."""
class BadIdentifier(Exception): class BadIdentifier(Exception):

View File

@@ -14,4 +14,8 @@ class RenderError(TextualError):
class DuplicateKeyHandlers(TextualError): class DuplicateKeyHandlers(TextualError):
"""More than one handler for a single key press. E.g. key_ctrl_i and key_tab handlers both found on one object.""" """More than one handler for a single key press.
For example, if the handlers `key_ctrl_i` and `key_tab` were defined on the same
widget, then this error would be raised.
"""

View File

@@ -27,6 +27,7 @@ if TYPE_CHECKING:
SpacingDimensions: TypeAlias = Union[ SpacingDimensions: TypeAlias = Union[
int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int] int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]
] ]
"""The valid ways in which you can specify spacing."""
T = TypeVar("T", int, float) T = TypeVar("T", int, float)
@@ -1043,4 +1044,4 @@ class Spacing(NamedTuple):
NULL_OFFSET: Final = Offset(0, 0) NULL_OFFSET: Final = Offset(0, 0)
"""An Offset constant for (0, 0).""" """An [offset][textual.geometry.Offset] constant for (0, 0)."""

View File

@@ -337,7 +337,7 @@ class MessagePump(metaclass=_MessagePumpMeta):
self._timers.add(timer) self._timers.add(timer)
return timer return timer
def call_after_refresh(self, callback: Callable, *args, **kwargs) -> None: def call_after_refresh(self, callback: Callable, *args: Any, **kwargs: Any) -> None:
"""Schedule a callback to run after all messages are processed and the screen """Schedule a callback to run after all messages are processed and the screen
has been refreshed. Positional and keyword arguments are passed to the callable. has been refreshed. Positional and keyword arguments are passed to the callable.
@@ -350,7 +350,7 @@ class MessagePump(metaclass=_MessagePumpMeta):
message = messages.InvokeLater(partial(callback, *args, **kwargs)) message = messages.InvokeLater(partial(callback, *args, **kwargs))
self.post_message(message) self.post_message(message)
def call_later(self, callback: Callable, *args, **kwargs) -> None: def call_later(self, callback: Callable, *args: Any, **kwargs: Any) -> None:
"""Schedule a callback to run after all messages are processed in this object. """Schedule a callback to run after all messages are processed in this object.
Positional and keywords arguments are passed to the callable. Positional and keywords arguments are passed to the callable.
@@ -362,7 +362,7 @@ class MessagePump(metaclass=_MessagePumpMeta):
message = events.Callback(callback=partial(callback, *args, **kwargs)) message = events.Callback(callback=partial(callback, *args, **kwargs))
self.post_message(message) self.post_message(message)
def call_next(self, callback: Callable, *args, **kwargs) -> None: def call_next(self, callback: Callable, *args: Any, **kwargs: Any) -> None:
"""Schedule a callback to run immediately after processing the current message. """Schedule a callback to run immediately after processing the current message.
Args: Args:

View File

@@ -41,6 +41,9 @@ from .widget import Widget
if TYPE_CHECKING: if TYPE_CHECKING:
from typing_extensions import Final from typing_extensions import Final
# Unused & ignored imports are needed for the docs to link to these objects:
from .errors import NoWidget # type: ignore # noqa: F401
# Screen updates will be batched so that they don't happen more often than 60 times per second: # Screen updates will be batched so that they don't happen more often than 60 times per second:
UPDATE_PERIOD: Final[float] = 1 / 60 UPDATE_PERIOD: Final[float] = 1 / 60

View File

@@ -1,3 +1,8 @@
"""
Implements the scrollbar-related widgets for internal use.
You will not need to use the widgets defined in this module.
"""
from __future__ import annotations from __future__ import annotations
from math import ceil from math import ceil
@@ -18,7 +23,7 @@ from .widget import Widget
class ScrollMessage(Message, bubble=False): class ScrollMessage(Message, bubble=False):
pass """Base class for all scrollbar messages."""
@rich.repr.auto @rich.repr.auto

View File

@@ -20,6 +20,7 @@ from ._time import sleep
from ._types import MessageTarget from ._types import MessageTarget
TimerCallback = Union[Callable[[], Awaitable[None]], Callable[[], None]] TimerCallback = Union[Callable[[], Awaitable[None]], Callable[[], None]]
"""Type of valid callbacks to be used with timers."""
class EventTargetGone(Exception): class EventTargetGone(Exception):

20
src/textual/types.py Normal file
View File

@@ -0,0 +1,20 @@
"""
Export some objects that are used by Textual and that help document other features.
"""
from ._animator import Animatable, EasingFunction
from ._context import NoActiveAppError
from ._types import CallbackType, MessageTarget, WatchCallbackType
from .actions import ActionParseResult
from .css.styles import RenderStyles
__all__ = [
"ActionParseResult",
"Animatable",
"CallbackType",
"EasingFunction",
"MessageTarget",
"NoActiveAppError",
"RenderStyles",
"WatchCallbackType",
]

View File

@@ -99,6 +99,7 @@ WorkType: TypeAlias = Union[
Callable[[], ResultType], Callable[[], ResultType],
Awaitable[ResultType], Awaitable[ResultType],
] ]
"""Type used for [workers](/guide/workers/)."""
class _ReprText: class _ReprText: