From 2fb9813c16e306d764d25838effd43bfe8ba96aa Mon Sep 17 00:00:00 2001 From: Apostol Fet Date: Sun, 19 Oct 2025 22:26:50 +0300 Subject: [PATCH] feat(cli): add suggestion for option --version and -V --- litestar/cli/_suggestions.py | 28 +++++++++++++++++++++++++ litestar/cli/_utils.py | 11 +++++++++- tests/unit/test_cli/test_suggestions.py | 11 ++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 litestar/cli/_suggestions.py create mode 100644 tests/unit/test_cli/test_suggestions.py diff --git a/litestar/cli/_suggestions.py b/litestar/cli/_suggestions.py new file mode 100644 index 0000000000..69b7925bf8 --- /dev/null +++ b/litestar/cli/_suggestions.py @@ -0,0 +1,28 @@ +from typing import Final, NoReturn + +try: + import rich_click as click +except ImportError: + import click # type: ignore[no-redef] + +_SUGGEST_OPTION_POSSIBILITIES: Final = { + "-V": ["command `version`"], + "--version": ["command `version`"], +} + + +def suggest_option(error: click.NoSuchOption) -> NoReturn: + if error.possibilities: + raise error + + new_possibilities = _SUGGEST_OPTION_POSSIBILITIES.get(error.option_name) + if new_possibilities is None: + raise error + + new_error = click.NoSuchOption( + option_name=error.option_name, + possibilities=new_possibilities, + message=error.message, + ctx=error.ctx, + ) + raise new_error from error diff --git a/litestar/cli/_utils.py b/litestar/cli/_utils.py index 729ecabb94..88a8a568ba 100644 --- a/litestar/cli/_utils.py +++ b/litestar/cli/_utils.py @@ -15,11 +15,14 @@ from pathlib import Path from typing import TYPE_CHECKING, Any, Callable, TypeVar, cast +from litestar.cli._suggestions import suggest_option + try: + from rich_click import NoSuchOption 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 Command, Group, NoSuchOption # type: ignore[assignment] from typing import get_type_hints @@ -183,6 +186,12 @@ def decorator(f: AnyCallable) -> Command: return decorator + def parse_args(self, *args: Any, **kwargs: Any) -> list[str]: + try: + return super().parse_args(*args, **kwargs) + except NoSuchOption as e: + suggest_option(e) + class LitestarExtensionGroup(LitestarGroup): """``LitestarGroup`` subclass that will load Litestar-CLI extensions from the `litestar.commands` entry_point. diff --git a/tests/unit/test_cli/test_suggestions.py b/tests/unit/test_cli/test_suggestions.py new file mode 100644 index 0000000000..c014a0d6ba --- /dev/null +++ b/tests/unit/test_cli/test_suggestions.py @@ -0,0 +1,11 @@ +import pytest +from click.testing import CliRunner + +from litestar.cli.main import litestar_group as cli_command + + +@pytest.mark.parametrize("option", ["--version", "-V"]) +def test_suggest_version(option: str, runner: CliRunner) -> None: + result = runner.invoke(cli_command, option) + + assert "Did you mean command `version`?" in result.output