Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lazy load Rich #154

Merged
merged 2 commits into from
Feb 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 6 additions & 9 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,14 @@ repos:
name: Ruff - Linter
args: [--fix]

- repo: local
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.1.1
hooks:
- id: black
name: Black - Auto-formatter.
description: Black is the uncompromising Python code formatter. Writing to files.
entry: black
language: python
types: [python]
require_serial: true
additional_dependencies:
- black
language_version: python3.12

- repo: local
hooks:
- id: mypy
name: mypy - Static type checking
description: Mypy helps ensure that we use our functions and variables correctly by checking the types.
Expand Down
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
40 changes: 20 additions & 20 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 All @@ -24,8 +27,7 @@

# variant: no call, directly as decorator for a function.
@overload
def group(name: _AnyCallable) -> RichGroup:
...
def group(name: _AnyCallable) -> RichGroup: ...


# variant: with positional name and with positional or keyword cls argument:
Expand All @@ -35,8 +37,7 @@ def group(
name: Optional[str],
cls: Type[GrpType],
**attrs: Any,
) -> Callable[[_AnyCallable], GrpType]:
...
) -> Callable[[_AnyCallable], GrpType]: ...


# variant: name omitted, cls _must_ be a keyword argument, @group(cmd=GroupCls, ...)
Expand All @@ -46,14 +47,12 @@ def group(
*,
cls: Type[GrpType],
**attrs: Any,
) -> Callable[[_AnyCallable], GrpType]:
...
) -> Callable[[_AnyCallable], GrpType]: ...


# variant: with optional string name, no cls argument provided.
@overload
def group(name: Optional[str] = ..., cls: None = None, **attrs: Any) -> Callable[[_AnyCallable], RichGroup]:
...
def group(name: Optional[str] = ..., cls: None = None, **attrs: Any) -> Callable[[_AnyCallable], RichGroup]: ...


def group(
Expand All @@ -80,8 +79,7 @@ def group(

# variant: no call, directly as decorator for a function.
@overload
def command(name: _AnyCallable) -> RichCommand:
...
def command(name: _AnyCallable) -> RichCommand: ...


# variant: with positional name and with positional or keyword cls argument:
Expand All @@ -91,8 +89,7 @@ def command(
name: Optional[str],
cls: Type[CmdType],
**attrs: Any,
) -> Callable[[_AnyCallable], CmdType]:
...
) -> Callable[[_AnyCallable], CmdType]: ...


# variant: name omitted, cls _must_ be a keyword argument, @command(cls=CommandCls, ...)
Expand All @@ -102,14 +99,12 @@ def command(
*,
cls: Type[CmdType],
**attrs: Any,
) -> Callable[[_AnyCallable], CmdType]:
...
) -> Callable[[_AnyCallable], CmdType]: ...


# variant: with optional string name, no cls argument provided.
@overload
def command(name: Optional[str] = ..., cls: None = None, **attrs: Any) -> Callable[[_AnyCallable], RichCommand]:
...
def command(name: Optional[str] = ..., cls: None = None, **attrs: Any) -> Callable[[_AnyCallable], RichCommand]: ...


def command(
Expand Down Expand Up @@ -138,7 +133,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 +147,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
91 changes: 47 additions & 44 deletions src/rich_click/rich_click.py
Original file line number Diff line number Diff line change
@@ -1,63 +1,66 @@
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[
Literal["auto", "standard", "256", "truecolor", "windows"]
] = "auto" # Set to None to disable colors
COLOR_SYSTEM: Optional[Literal["auto", "standard", "256", "truecolor", "windows"]] = (
"auto" # Set to None to disable colors
)
FORCE_TERMINAL: Optional[bool] = force_terminal_default()

# Fixed strings
Expand Down
34 changes: 20 additions & 14 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 Expand Up @@ -228,12 +238,10 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
self._register_rich_context_settings_from_callback()

@overload
def command(self, __func: Callable[..., Any]) -> RichCommand:
...
def command(self, __func: Callable[..., Any]) -> RichCommand: ...

@overload
def command(self, *args: Any, **kwargs: Any) -> Callable[[Callable[..., Any]], RichCommand]:
...
def command(self, *args: Any, **kwargs: Any) -> Callable[[Callable[..., Any]], RichCommand]: ...

def command(self, *args: Any, **kwargs: Any) -> Union[Callable[[Callable[..., Any]], RichCommand], RichCommand]:
# This method override is required for Click 7.x compatibility.
Expand All @@ -243,12 +251,10 @@ def command(self, *args: Any, **kwargs: Any) -> Union[Callable[[Callable[..., An
return super().command(*args, **kwargs) # type: ignore[no-any-return]

@overload
def group(self, __func: Callable[..., Any]) -> "RichGroup":
...
def group(self, __func: Callable[..., Any]) -> "RichGroup": ...

@overload
def group(self, *args: Any, **kwargs: Any) -> Callable[[Callable[..., Any]], "RichGroup"]:
...
def group(self, *args: Any, **kwargs: Any) -> Callable[[Callable[..., Any]], "RichGroup"]: ...

def group(self, *args: Any, **kwargs: Any) -> Union[Callable[[Callable[..., Any]], "RichGroup"], "RichGroup"]:
# This method override is required for Click 7.x compatibility.
Expand Down
Loading
Loading