diff --git a/litestar/cli/__init__.py b/litestar/cli/__init__.py index a167be38fa..eb36e640aa 100644 --- a/litestar/cli/__init__.py +++ b/litestar/cli/__init__.py @@ -4,26 +4,19 @@ from importlib.util import find_spec -# Ensure `rich_click` patching occurs before we do any imports from `click`. if find_spec("rich_click") is not None: # pragma: no cover - import rich_click as click - - try: - from rich_click.patch import patch as rich_click_patch - except ImportError: - from rich_click.cli import patch as rich_click_patch - - rich_click_patch() - click.rich_click.USE_RICH_MARKUP = True - click.rich_click.USE_MARKDOWN = False - click.rich_click.SHOW_ARGUMENTS = True - click.rich_click.GROUP_ARGUMENTS_OPTIONS = True - click.rich_click.STYLE_ERRORS_SUGGESTION = "magenta italic" - click.rich_click.ERRORS_SUGGESTION = "" - click.rich_click.ERRORS_EPILOGUE = "" - click.rich_click.MAX_WIDTH = 80 - click.rich_click.SHOW_METAVARS_COLUMN = True - click.rich_click.APPEND_METAVARS_HELP = True + import rich_click + + rich_click.rich_click.USE_RICH_MARKUP = True + rich_click.rich_click.USE_MARKDOWN = False + rich_click.rich_click.SHOW_ARGUMENTS = True + rich_click.rich_click.GROUP_ARGUMENTS_OPTIONS = True + rich_click.rich_click.STYLE_ERRORS_SUGGESTION = "magenta italic" + rich_click.rich_click.ERRORS_SUGGESTION = "" + rich_click.rich_click.ERRORS_EPILOGUE = "" + rich_click.rich_click.MAX_WIDTH = 80 + rich_click.rich_click.SHOW_METAVARS_COLUMN = True + rich_click.rich_click.APPEND_METAVARS_HELP = True from .main import litestar_group diff --git a/litestar/cli/_utils.py b/litestar/cli/_utils.py index c29b442ed4..41494bc5d1 100644 --- a/litestar/cli/_utils.py +++ b/litestar/cli/_utils.py @@ -15,7 +15,13 @@ from pathlib import Path from typing import TYPE_CHECKING, Any, Callable, Generator, Iterable, Sequence, TypeVar, cast -from click import ClickException, Command, Context, Group, pass_context +try: + from rich_click import RichCommand as Command + from rich_click import RichGroup as Group +except ImportError: + from click import Command, Group # type: ignore[assignment] + +from click import ClickException, Context, pass_context from rich import get_console from rich.table import Table from typing_extensions import ParamSpec, get_type_hints @@ -128,7 +134,7 @@ class LoadedApp: is_factory: bool -class LitestarGroup(Group): +class LitestarGroup(Group): # pyright: ignore """:class:`click.Group` subclass that automatically injects ``app`` and ``env` kwargs into commands that request it. Use this as the ``cls`` for :class:`click.Group` if you're extending the internal CLI with a group. For ``command``s @@ -145,7 +151,7 @@ def __init__( self.group_class = LitestarGroup super().__init__(name=name, commands=commands, **attrs) - def add_command(self, cmd: Command, name: str | None = None) -> None: + def add_command(self, cmd: Command, name: str | None = None) -> None: # type: ignore[override] """Add command. If necessary, inject ``app`` and ``env`` kwargs @@ -207,7 +213,7 @@ def _prepare(self, ctx: Context) -> None: self._prepare_done = True - def make_context( + def make_context( # type: ignore[override] self, info_name: str | None, args: list[str], diff --git a/litestar/cli/commands/core.py b/litestar/cli/commands/core.py index 3fc8334e08..f3a328f254 100644 --- a/litestar/cli/commands/core.py +++ b/litestar/cli/commands/core.py @@ -8,8 +8,10 @@ from contextlib import AbstractContextManager, ExitStack, contextmanager from typing import TYPE_CHECKING, Any, Iterator -import click -from click import Context, command, option +try: + import rich_click as click +except ImportError: + import click # type: ignore[no-redef] from rich.tree import Tree from litestar.app import DEFAULT_OPENAPI_CONFIG @@ -113,8 +115,8 @@ class CommaSplittedPath(click.Path): envvar_list_splitter = "," -@command(name="version") -@option("-s", "--short", help="Exclude release level and serial information", is_flag=True, default=False) +@click.command(name="version") +@click.option("-s", "--short", help="Exclude release level and serial information", is_flag=True, default=False) def version_command(short: bool) -> None: """Show the currently installed Litestar version.""" from litestar import __version__ @@ -122,16 +124,16 @@ def version_command(short: bool) -> None: click.echo(__version__.formatted(short=short)) -@command(name="info") +@click.command(name="info") def info_command(app: Litestar) -> None: """Show information about the detected Litestar app.""" show_app_info(app) -@command(name="run") -@option("-r", "--reload", help="Reload server on changes", default=False, is_flag=True, envvar="LITESTAR_RELOAD") -@option( +@click.command(name="run") +@click.option("-r", "--reload", help="Reload server on changes", default=False, is_flag=True, envvar="LITESTAR_RELOAD") +@click.option( "-R", "--reload-dir", help="Directories to watch for file changes", @@ -139,7 +141,7 @@ def info_command(app: Litestar) -> None: multiple=True, envvar="LITESTAR_RELOAD_DIRS", ) -@option( +@click.option( "-I", "--reload-include", help="Glob patterns for files to include when watching for file changes", @@ -147,7 +149,7 @@ def info_command(app: Litestar) -> None: multiple=True, envvar="LITESTAR_RELOAD_INCLUDES", ) -@option( +@click.option( "-E", "--reload-exclude", help="Glob patterns for files to exclude when watching for file changes", @@ -155,8 +157,10 @@ def info_command(app: Litestar) -> None: multiple=True, envvar="LITESTAR_RELOAD_EXCLUDES", ) -@option("-p", "--port", help="Serve under this port", type=int, default=8000, show_default=True, envvar="LITESTAR_PORT") -@option( +@click.option( + "-p", "--port", help="Serve under this port", type=int, default=8000, show_default=True, envvar="LITESTAR_PORT" +) +@click.option( "-W", "--wc", "--web-concurrency", @@ -166,8 +170,10 @@ def info_command(app: Litestar) -> None: default=1, envvar=["LITESTAR_WEB_CONCURRENCY", "WEB_CONCURRENCY"], ) -@option("-H", "--host", help="Server under this host", default="127.0.0.1", show_default=True, envvar="LITESTAR_HOST") -@option( +@click.option( + "-H", "--host", help="Server under this host", default="127.0.0.1", show_default=True, envvar="LITESTAR_HOST" +) +@click.option( "-F", "--fd", "--file-descriptor", @@ -177,7 +183,7 @@ def info_command(app: Litestar) -> None: show_default=True, envvar="LITESTAR_FILE_DESCRIPTOR", ) -@option( +@click.option( "-U", "--uds", "--unix-domain-socket", @@ -186,11 +192,11 @@ def info_command(app: Litestar) -> None: show_default=True, envvar="LITESTAR_UNIX_DOMAIN_SOCKET", ) -@option("-d", "--debug", help="Run app in debug mode", is_flag=True, envvar="LITESTAR_DEBUG") -@option("-P", "--pdb", "--use-pdb", help="Drop into PDB on an exception", is_flag=True, envvar="LITESTAR_PDB") -@option("--ssl-certfile", help="Location of the SSL cert file", default=None, envvar="LITESTAR_SSL_CERT_PATH") -@option("--ssl-keyfile", help="Location of the SSL key file", default=None, envvar="LITESTAR_SSL_KEY_PATH") -@option( +@click.option("-d", "--debug", help="Run app in debug mode", is_flag=True, envvar="LITESTAR_DEBUG") +@click.option("-P", "--pdb", "--use-pdb", help="Drop into PDB on an exception", is_flag=True, envvar="LITESTAR_PDB") +@click.option("--ssl-certfile", help="Location of the SSL cert file", default=None, envvar="LITESTAR_SSL_CERT_PATH") +@click.option("--ssl-keyfile", help="Location of the SSL key file", default=None, envvar="LITESTAR_SSL_KEY_PATH") +@click.option( "--create-self-signed-cert", help="If certificate and key are not found at specified locations, create a self-signed certificate and a key", is_flag=True, @@ -211,7 +217,7 @@ def run_command( ssl_certfile: str | None, ssl_keyfile: str | None, create_self_signed_cert: bool, - ctx: Context, + ctx: click.Context, ) -> None: """Run a Litestar app; requires ``uvicorn``. @@ -299,9 +305,9 @@ def run_command( ) -@command(name="routes") -@option("--schema", help="Include schema routes", is_flag=True, default=False) -@option("--exclude", help="routes to exclude via regex", type=str, is_flag=False, multiple=True) +@click.command(name="routes") +@click.option("--schema", help="Include schema routes", is_flag=True, default=False) +@click.option("--exclude", help="routes to exclude via regex", type=str, is_flag=False, multiple=True) def routes_command(app: Litestar, exclude: tuple[str, ...], schema: bool) -> None: # pragma: no cover """Display information about the application's routes.""" diff --git a/litestar/cli/commands/schema.py b/litestar/cli/commands/schema.py index a323bc7871..ecc64dd5ac 100644 --- a/litestar/cli/commands/schema.py +++ b/litestar/cli/commands/schema.py @@ -2,7 +2,12 @@ import msgspec from click import Path as ClickPath -from click import group, option + +try: + import rich_click as click +except ImportError: + import click # type: ignore[no-redef] + from yaml import dump as dump_yaml from litestar import Litestar @@ -15,7 +20,7 @@ __all__ = ("generate_openapi_schema", "generate_typescript_specs", "schema_group") -@group(cls=LitestarGroup, name="schema") +@click.group(cls=LitestarGroup, name="schema") def schema_group() -> None: """Manage server-side OpenAPI schemas.""" @@ -42,7 +47,7 @@ def _generate_openapi_schema(app: Litestar, output: Path) -> None: @schema_group.command("openapi") # type: ignore[misc] -@option( +@click.option( "--output", help="output file path", type=ClickPath(dir_okay=False, path_type=Path), @@ -55,14 +60,14 @@ def generate_openapi_schema(app: Litestar, output: Path) -> None: @schema_group.command("typescript") # type: ignore[misc] -@option( +@click.option( "--output", help="output file path", type=ClickPath(dir_okay=False, path_type=Path), default=Path("api-specs.ts"), show_default=True, ) -@option("--namespace", help="namespace to use for the typescript specs", type=str, default="API") +@click.option("--namespace", help="namespace to use for the typescript specs", type=str, default="API") def generate_typescript_specs(app: Litestar, output: Path, namespace: str) -> None: """Generate TypeScript specs from the OpenAPI schema.""" if JSBEAUTIFIER_INSTALLED: # pragma: no cover diff --git a/litestar/cli/commands/sessions.py b/litestar/cli/commands/sessions.py index f048dd13a7..cc29a5726a 100644 --- a/litestar/cli/commands/sessions.py +++ b/litestar/cli/commands/sessions.py @@ -1,4 +1,7 @@ -from click import argument, group +try: + import rich_click as click +except ImportError: + import click # type: ignore[no-redef] from rich.prompt import Confirm from litestar import Litestar @@ -24,13 +27,13 @@ def get_session_backend(app: Litestar) -> ServerSideSessionBackend: raise LitestarCLIException("Session middleware not installed") -@group(cls=LitestarGroup, name="sessions") +@click.group(cls=LitestarGroup, name="sessions") def sessions_group() -> None: """Manage server-side sessions.""" @sessions_group.command("delete") # type: ignore[misc] -@argument("session-id") +@click.argument("session-id") def delete_session_command(session_id: str, app: Litestar) -> None: """Delete a specific session.""" import anyio diff --git a/litestar/cli/main.py b/litestar/cli/main.py index 32505f62d8..5eee0b4399 100644 --- a/litestar/cli/main.py +++ b/litestar/cli/main.py @@ -2,7 +2,11 @@ from pathlib import Path -from click import Context, group, option, pass_context +try: + import rich_click as click +except ImportError: + import click # type: ignore[no-redef] + from click import Path as ClickPath from ._utils import LitestarEnv, LitestarExtensionGroup @@ -11,17 +15,17 @@ __all__ = ("litestar_group",) -@group(cls=LitestarExtensionGroup, context_settings={"help_option_names": ["-h", "--help"]}) -@option("--app", "app_path", help="Module path to a Litestar application") -@option( +@click.group(cls=LitestarExtensionGroup, context_settings={"help_option_names": ["-h", "--help"]}) +@click.option("--app", "app_path", help="Module path to a Litestar application") +@click.option( "--app-dir", help="Look for APP in the specified directory, by adding this to the PYTHONPATH. Defaults to the current working directory.", default=None, type=ClickPath(dir_okay=True, file_okay=False, path_type=Path), show_default=False, ) -@pass_context -def litestar_group(ctx: Context, app_path: str | None, app_dir: Path | None = None) -> None: +@click.pass_context +def litestar_group(ctx: click.Context, app_path: str | None, app_dir: Path | None = None) -> None: """Litestar CLI.""" if ctx.obj is None: # env has not been loaded yet, so we can lazy load it ctx.obj = lambda: LitestarEnv.from_env(app_path, app_dir=app_dir) @@ -29,9 +33,9 @@ def litestar_group(ctx: Context, app_path: str | None, app_dir: Path | None = No # add sub commands here -litestar_group.add_command(core.info_command) -litestar_group.add_command(core.run_command) -litestar_group.add_command(core.routes_command) -litestar_group.add_command(core.version_command) -litestar_group.add_command(sessions.sessions_group) -litestar_group.add_command(schema.schema_group) +litestar_group.add_command(core.info_command) # pyright: ignore +litestar_group.add_command(core.run_command) # pyright: ignore +litestar_group.add_command(core.routes_command) # pyright: ignore +litestar_group.add_command(core.version_command) # pyright: ignore +litestar_group.add_command(sessions.sessions_group) # pyright: ignore +litestar_group.add_command(schema.schema_group) # pyright: ignore