Skip to content

Commit

Permalink
lazy load Rich
Browse files Browse the repository at this point in the history
  • Loading branch information
dwreeves committed Feb 3, 2024
1 parent f1f3227 commit 837c66f
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 126 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Version 1.8.0dev

- Lazy load Rich to reduce overhead when not rendering help text.
- Some internal refactors. These refactors are aimed at making the abstractions more maintainable over time, more consistent, and more adept for advanced used cases.
- `rich_click.py` is exclusively the global config; all formatting has been moved to `rich_help_rendering.py`.
- `RichCommand` now makes use of methods in the super class: `format_usage`, `format_help_text`, `format_options`, and `format_epilog`.
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ ignore = [
"D102",
"D205",
"E731",
"D105",
"D203",
"D212"
]
Expand Down
16 changes: 12 additions & 4 deletions src/rich_click/decorators.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from typing import Any, Callable, Dict, Mapping, Optional, Type, TypeVar, Union, cast, overload
from typing import TYPE_CHECKING, Any, Callable, Dict, Mapping, Optional, Type, TypeVar, Union, cast, overload

from click import Command, Group
from click import command as click_command
from click import pass_context as click_pass_context
from rich.console import Console
from typing_extensions import Concatenate, ParamSpec

from rich_click._compat_click import CLICK_IS_BEFORE_VERSION_8X
Expand All @@ -14,6 +13,10 @@
from . import rich_click # noqa: F401


if TYPE_CHECKING:
from rich.console import Console


_AnyCallable = Callable[..., Any]
F = TypeVar("F", bound=Callable[..., Any])
FC = TypeVar("FC", bound=Union[Command, _AnyCallable])
Expand Down Expand Up @@ -138,7 +141,9 @@ class NotSupportedError(Exception):


def rich_config(
help_config: Optional[Union[Mapping[str, Any], RichHelpConfiguration]] = None, *, console: Optional[Console] = None
help_config: Optional[Union[Mapping[str, Any], RichHelpConfiguration]] = None,
*,
console: Optional["Console"] = None,
) -> Callable[[FC], FC]:
"""
Use decorator to configure Rich Click settings.
Expand All @@ -150,11 +155,14 @@ def rich_config(
console: A Rich Console that will be accessible from the `RichContext`, `RichCommand`, and `RichGroup` instances
Defaults to None.
"""
from rich.console import Console

if isinstance(help_config, Console) and console is None:
import warnings

warnings.warn(
"`rich_config()`'s args have been swapped." " Please set the config first, and use a kwarg to set ",
"`rich_config()`'s args have been swapped."
" Please set the config first, and use a kwarg to set the console.",
DeprecationWarning,
stacklevel=2,
)
Expand Down
85 changes: 44 additions & 41 deletions src/rich_click/rich_click.py
Original file line number Diff line number Diff line change
@@ -1,58 +1,61 @@
from typing import Any, Dict, List, Optional, Tuple, Union
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union

import rich.align
import rich.padding
import rich.style
from typing_extensions import Literal

from rich_click.rich_help_configuration import force_terminal_default, terminal_width_default


if TYPE_CHECKING:
import rich.align
import rich.padding
import rich.style


# Default styles
STYLE_OPTION: rich.style.StyleType = "bold cyan"
STYLE_ARGUMENT: rich.style.StyleType = "bold cyan"
STYLE_COMMAND: rich.style.StyleType = "bold cyan"
STYLE_SWITCH: rich.style.StyleType = "bold green"
STYLE_METAVAR: rich.style.StyleType = "bold yellow"
STYLE_METAVAR_APPEND: rich.style.StyleType = "dim yellow"
STYLE_METAVAR_SEPARATOR: rich.style.StyleType = "dim"
STYLE_HEADER_TEXT: rich.style.StyleType = ""
STYLE_EPILOG_TEXT: rich.style.StyleType = ""
STYLE_FOOTER_TEXT: rich.style.StyleType = ""
STYLE_USAGE: rich.style.StyleType = "yellow"
STYLE_USAGE_COMMAND: rich.style.StyleType = "bold"
STYLE_DEPRECATED: rich.style.StyleType = "red"
STYLE_HELPTEXT_FIRST_LINE: rich.style.StyleType = ""
STYLE_HELPTEXT: rich.style.StyleType = "dim"
STYLE_OPTION_HELP: rich.style.StyleType = ""
STYLE_OPTION_DEFAULT: rich.style.StyleType = "dim"
STYLE_OPTION_ENVVAR: rich.style.StyleType = "dim yellow"
STYLE_REQUIRED_SHORT: rich.style.StyleType = "red"
STYLE_REQUIRED_LONG: rich.style.StyleType = "dim red"
STYLE_OPTIONS_PANEL_BORDER: rich.style.StyleType = "dim"
ALIGN_OPTIONS_PANEL: rich.align.AlignMethod = "left"
STYLE_OPTION: "rich.style.StyleType" = "bold cyan"
STYLE_ARGUMENT: "rich.style.StyleType" = "bold cyan"
STYLE_COMMAND: "rich.style.StyleType" = "bold cyan"
STYLE_SWITCH: "rich.style.StyleType" = "bold green"
STYLE_METAVAR: "rich.style.StyleType" = "bold yellow"
STYLE_METAVAR_APPEND: "rich.style.StyleType" = "dim yellow"
STYLE_METAVAR_SEPARATOR: "rich.style.StyleType" = "dim"
STYLE_HEADER_TEXT: "rich.style.StyleType" = ""
STYLE_EPILOG_TEXT: "rich.style.StyleType" = ""
STYLE_FOOTER_TEXT: "rich.style.StyleType" = ""
STYLE_USAGE: "rich.style.StyleType" = "yellow"
STYLE_USAGE_COMMAND: "rich.style.StyleType" = "bold"
STYLE_DEPRECATED: "rich.style.StyleType" = "red"
STYLE_HELPTEXT_FIRST_LINE: "rich.style.StyleType" = ""
STYLE_HELPTEXT: "rich.style.StyleType" = "dim"
STYLE_OPTION_HELP: "rich.style.StyleType" = ""
STYLE_OPTION_DEFAULT: "rich.style.StyleType" = "dim"
STYLE_OPTION_ENVVAR: "rich.style.StyleType" = "dim yellow"
STYLE_REQUIRED_SHORT: "rich.style.StyleType" = "red"
STYLE_REQUIRED_LONG: "rich.style.StyleType" = "dim red"
STYLE_OPTIONS_PANEL_BORDER: "rich.style.StyleType" = "dim"
ALIGN_OPTIONS_PANEL: "rich.align.AlignMethod" = "left"
STYLE_OPTIONS_TABLE_SHOW_LINES: bool = False
STYLE_OPTIONS_TABLE_LEADING: int = 0
STYLE_OPTIONS_TABLE_PAD_EDGE: bool = False
STYLE_OPTIONS_TABLE_PADDING: rich.padding.PaddingDimensions = (0, 1)
STYLE_OPTIONS_TABLE_BOX: rich.style.StyleType = ""
STYLE_OPTIONS_TABLE_ROW_STYLES: Optional[List[rich.style.StyleType]] = None
STYLE_OPTIONS_TABLE_BORDER_STYLE: Optional[rich.style.StyleType] = None
STYLE_COMMANDS_PANEL_BORDER: rich.style.StyleType = "dim"
ALIGN_COMMANDS_PANEL: rich.align.AlignMethod = "left"
STYLE_OPTIONS_TABLE_PADDING: "rich.padding.PaddingDimensions" = (0, 1)
STYLE_OPTIONS_TABLE_BOX: "rich.style.StyleType" = ""
STYLE_OPTIONS_TABLE_ROW_STYLES: Optional[List["rich.style.StyleType"]] = None
STYLE_OPTIONS_TABLE_BORDER_STYLE: Optional["rich.style.StyleType"] = None
STYLE_COMMANDS_PANEL_BORDER: "rich.style.StyleType" = "dim"
ALIGN_COMMANDS_PANEL: "rich.align.AlignMethod" = "left"
STYLE_COMMANDS_TABLE_SHOW_LINES: bool = False
STYLE_COMMANDS_TABLE_LEADING: int = 0
STYLE_COMMANDS_TABLE_PAD_EDGE: bool = False
STYLE_COMMANDS_TABLE_PADDING: rich.padding.PaddingDimensions = (0, 1)
STYLE_COMMANDS_TABLE_BOX: rich.style.StyleType = ""
STYLE_COMMANDS_TABLE_ROW_STYLES: Optional[List[rich.style.StyleType]] = None
STYLE_COMMANDS_TABLE_BORDER_STYLE: Optional[rich.style.StyleType] = None
STYLE_COMMANDS_TABLE_PADDING: "rich.padding.PaddingDimensions" = (0, 1)
STYLE_COMMANDS_TABLE_BOX: "rich.style.StyleType" = ""
STYLE_COMMANDS_TABLE_ROW_STYLES: Optional[List["rich.style.StyleType"]] = None
STYLE_COMMANDS_TABLE_BORDER_STYLE: Optional["rich.style.StyleType"] = None
STYLE_COMMANDS_TABLE_COLUMN_WIDTH_RATIO: Optional[Union[Tuple[None, None], Tuple[int, int]]] = (None, None)
STYLE_ERRORS_PANEL_BORDER: rich.style.StyleType = "red"
ALIGN_ERRORS_PANEL: rich.align.AlignMethod = "left"
STYLE_ERRORS_SUGGESTION: rich.style.StyleType = "dim"
STYLE_ERRORS_SUGGESTION_COMMAND: rich.style.StyleType = "blue"
STYLE_ABORTED: rich.style.StyleType = "red"
STYLE_ERRORS_PANEL_BORDER: "rich.style.StyleType" = "red"
ALIGN_ERRORS_PANEL: "rich.align.AlignMethod" = "left"
STYLE_ERRORS_SUGGESTION: "rich.style.StyleType" = "dim"
STYLE_ERRORS_SUGGESTION_COMMAND: "rich.style.StyleType" = "blue"
STYLE_ABORTED: "rich.style.StyleType" = "red"
WIDTH: Optional[int] = terminal_width_default()
MAX_WIDTH: Optional[int] = terminal_width_default()
COLOR_SYSTEM: Optional[
Expand Down
22 changes: 16 additions & 6 deletions src/rich_click/rich_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
# or else rich_click.cli.patch() causes a recursion error.
from click import Command, CommandCollection, Group
from click.utils import PacifyFlushWrapper, make_str
from rich.console import Console

from rich_click._compat_click import CLICK_IS_BEFORE_VERSION_8X, CLICK_IS_BEFORE_VERSION_9X
from rich_click.rich_context import RichContext
from rich_click.rich_help_configuration import RichHelpConfiguration
from rich_click.rich_help_formatter import RichHelpFormatter
from rich_click.rich_help_rendering import get_rich_epilog, get_rich_help_text, get_rich_options, rich_format_error


if TYPE_CHECKING:
from rich.console import Console


class RichCommand(click.Command):
Expand Down Expand Up @@ -48,7 +50,7 @@ def _register_rich_context_settings_from_callback(self) -> None:
del self.callback.__rich_context_settings__

@property
def console(self) -> Optional[Console]:
def console(self) -> Optional["Console"]:
"""
Rich Console.
Expand All @@ -65,7 +67,7 @@ def help_config(self) -> Optional[RichHelpConfiguration]:
import warnings

warnings.warn(
"RichCommand.help_config is deprecated." " Please use the click.Context's help config instead.",
"RichCommand.help_config is deprecated. Please use the click.Context's help config instead.",
DeprecationWarning,
stacklevel=2,
)
Expand Down Expand Up @@ -141,7 +143,9 @@ def main(
except click.exceptions.ClickException as e:
if not standalone_mode:
raise
formatter = self.context_class.formatter_class(config=self.help_config, file=sys.stderr)
formatter = self.context_class.formatter_class(config=ctx.help_config, file=sys.stderr)
from rich_click.rich_help_rendering import rich_format_error

rich_format_error(e, formatter)
sys.exit(e.exit_code)
except OSError as e:
Expand All @@ -160,7 +164,7 @@ def main(
if not standalone_mode:
raise
try:
formatter = self.context_class.formatter_class(config=self.help_config)
formatter = self.context_class.formatter_class(config=ctx.help_config)
except Exception:
click.echo("Aborted!", file=sys.stderr)
else:
Expand All @@ -178,12 +182,18 @@ def format_help(self, ctx: RichContext, formatter: RichHelpFormatter) -> None:
self.format_epilog(ctx, formatter)

def format_help_text(self, ctx: RichContext, formatter: RichHelpFormatter) -> None: # type: ignore[override]
from rich_click.rich_help_rendering import get_rich_help_text

get_rich_help_text(self, ctx, formatter)

def format_options(self, ctx: RichContext, formatter: RichHelpFormatter) -> None: # type: ignore[override]
from rich_click.rich_help_rendering import get_rich_options

get_rich_options(self, ctx, formatter)

def format_epilog(self, ctx: RichContext, formatter: RichHelpFormatter) -> None: # type: ignore[override]
from rich_click.rich_help_rendering import get_rich_epilog

get_rich_epilog(self, ctx, formatter)

def make_context(
Expand Down
42 changes: 38 additions & 4 deletions src/rich_click/rich_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@
from rich_click.rich_help_formatter import RichHelpFormatter


if TYPE_CHECKING:
from types import TracebackType


class RichContext(click.Context):
"""Click Context class endowed with Rich superpowers."""

formatter_class: Type[RichHelpFormatter] = RichHelpFormatter
_console: Optional[Console] = None

def __init__(
self,
Expand All @@ -33,9 +38,10 @@ def __init__(
parent: Optional[RichContext] = kwargs.pop("parent", None)

if rich_console is None and hasattr(parent, "console"):
rich_console = parent.console # type: ignore[has-type,union-attr]
rich_console = parent.console # type: ignore[union-attr]

self.console = rich_console
if rich_console is not None:
self.console = rich_console

if rich_help_config is None:
if hasattr(parent, "help_config"):
Expand All @@ -54,8 +60,36 @@ def __init__(
else:
self.help_config = rich_help_config

@property
def console(self) -> Console:
if self._console is None:
return self.make_formatter().console
return self._console

@console.setter
def console(self, val: Console) -> None:
self._console = val

def make_formatter(self) -> RichHelpFormatter:
"""Create the Rich Help Formatter."""
return self.formatter_class(
width=self.terminal_width, max_width=self.max_content_width, config=self.help_config
formatter = self.formatter_class(
width=self.terminal_width,
max_width=self.max_content_width,
config=self.help_config,
)
if self._console is None:
self._console = formatter.console
return formatter

if TYPE_CHECKING:

def __enter__(self) -> "RichContext":
return super().__enter__() # type: ignore[return-value]

def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_value: Optional[BaseException],
tb: Optional[TracebackType],
) -> None:
return super().__exit__(exc_type, exc_value, tb)
Loading

0 comments on commit 837c66f

Please sign in to comment.