-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Static typing improvements in click.shell_completion
#3460
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
base: stable
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| .. currentmodule:: click | ||
|
|
||
| Version 8.4.dev | ||
| Version 8.4.1 | ||
| ------------------ | ||
|
|
||
| Unreleased | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,7 +22,7 @@ def shell_complete( | |
| prog_name: str, | ||
| complete_var: str, | ||
| instruction: str, | ||
| ) -> int: | ||
| ) -> t.Literal[0, 1]: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should perhaps create a type for ExitCode, but at the same time, I'm not sure there's that much value. Kind of good to know the specific exit codes as well. 🤷️ |
||
| """Perform shell completion for the given CLI program. | ||
|
|
||
| :param cli: Command being called. | ||
|
|
@@ -54,7 +54,16 @@ def shell_complete( | |
| return 1 | ||
|
|
||
|
|
||
| class CompletionItem: | ||
| if t.TYPE_CHECKING: | ||
| from typing_extensions import TypeVar | ||
|
|
||
| # `Any` is used as default for backwards compatibility (instead of e.g. `str`) | ||
| _ValueT_co = TypeVar("_ValueT_co", covariant=True, default=t.Any) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we need the underscore prefix, or at least I don't think we should as we don't do it elsewhere. |
||
| else: | ||
| _ValueT_co = t.TypeVar("_ValueT_co", covariant=True) | ||
|
|
||
|
|
||
| class CompletionItem(t.Generic[_ValueT_co]): | ||
| """Represents a completion value and metadata about the value. The | ||
| default metadata is ``type`` to indicate special shell handling, | ||
| and ``help`` if a shell supports showing a help string next to the | ||
|
|
@@ -77,12 +86,12 @@ class CompletionItem: | |
|
|
||
| def __init__( | ||
| self, | ||
| value: t.Any, | ||
| value: _ValueT_co, | ||
| type: str = "plain", | ||
| help: str | None = None, | ||
| **kwargs: t.Any, | ||
| ) -> None: | ||
| self.value: t.Any = value | ||
| self.value: _ValueT_co = value | ||
| self.type: str = type | ||
| self.help: str | None = help | ||
| self._info = kwargs | ||
|
|
@@ -201,6 +210,12 @@ def __getattr__(self, name: str) -> t.Any: | |
| """ | ||
|
|
||
|
|
||
| class _SourceVarsDict(t.TypedDict): | ||
| complete_func: str | ||
| complete_var: str | ||
| prog_name: str | ||
|
|
||
|
|
||
| class ShellComplete: | ||
| """Base class for providing shell completion support. A subclass for | ||
| a given shell will override attributes and methods to implement the | ||
|
|
@@ -245,7 +260,7 @@ def func_name(self) -> str: | |
| safe_name = re.sub(r"\W*", "", self.prog_name.replace("-", "_"), flags=re.ASCII) | ||
| return f"_{safe_name}_completion" | ||
|
|
||
| def source_vars(self) -> dict[str, t.Any]: | ||
| def source_vars(self) -> _SourceVarsDict: | ||
| """Vars for formatting :attr:`source_template`. | ||
|
|
||
| By default this provides ``complete_func``, ``complete_var``, | ||
|
|
@@ -272,7 +287,9 @@ def get_completion_args(self) -> tuple[list[str], str]: | |
| """ | ||
| raise NotImplementedError | ||
|
|
||
| def get_completions(self, args: list[str], incomplete: str) -> list[CompletionItem]: | ||
| def get_completions( | ||
| self, args: list[str], incomplete: str | ||
| ) -> list[CompletionItem[str]]: | ||
| """Determine the context and last complete command or parameter | ||
| from the complete args. Call that object's ``shell_complete`` | ||
| method to get the completions for the incomplete value. | ||
|
|
@@ -284,7 +301,7 @@ def get_completions(self, args: list[str], incomplete: str) -> list[CompletionIt | |
| obj, incomplete = _resolve_incomplete(ctx, args, incomplete) | ||
| return obj.shell_complete(ctx, incomplete) | ||
|
|
||
| def format_completion(self, item: CompletionItem) -> str: | ||
| def format_completion(self, item: CompletionItem[str]) -> str: | ||
| """Format a completion item into the form recognized by the | ||
| shell script. This must be implemented by subclasses. | ||
|
|
||
|
|
@@ -360,7 +377,7 @@ def get_completion_args(self) -> tuple[list[str], str]: | |
|
|
||
| return args, incomplete | ||
|
|
||
| def format_completion(self, item: CompletionItem) -> str: | ||
| def format_completion(self, item: CompletionItem[t.Any]) -> str: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason it's Any here instead of str? |
||
| return f"{item.type},{item.value}" | ||
|
|
||
|
|
||
|
|
@@ -382,7 +399,7 @@ def get_completion_args(self) -> tuple[list[str], str]: | |
|
|
||
| return args, incomplete | ||
|
|
||
| def format_completion(self, item: CompletionItem) -> str: | ||
| def format_completion(self, item: CompletionItem[str]) -> str: | ||
| help_ = item.help or "_" | ||
| # The zsh completion script uses `_describe` on items with help | ||
| # texts (which splits the item help from the item value at the | ||
|
|
@@ -420,7 +437,7 @@ def get_completion_args(self) -> tuple[list[str], str]: | |
|
|
||
| return args, incomplete | ||
|
|
||
| def format_completion(self, item: CompletionItem) -> str: | ||
| def format_completion(self, item: CompletionItem[str]) -> str: | ||
| """ | ||
| .. versionchanged:: 8.4 | ||
| Escape newlines in value and help to fix completion errors with | ||
|
|
@@ -437,19 +454,18 @@ def format_completion(self, item: CompletionItem) -> str: | |
| return f"{item.type}\n{value}\n{help_escaped}" | ||
|
|
||
|
|
||
| ShellCompleteType = t.TypeVar("ShellCompleteType", bound="type[ShellComplete]") | ||
|
|
||
|
|
||
| _available_shells: dict[str, type[ShellComplete]] = { | ||
| _available_shells: t.Final[dict[str, type[ShellComplete]]] = { | ||
| "bash": BashComplete, | ||
| "fish": FishComplete, | ||
| "zsh": ZshComplete, | ||
| } | ||
|
|
||
| _ShellCompleteT = t.TypeVar("_ShellCompleteT", bound="ShellComplete") | ||
|
|
||
|
|
||
| def add_completion_class( | ||
| cls: ShellCompleteType, name: str | None = None | ||
| ) -> ShellCompleteType: | ||
| cls: type[_ShellCompleteT], name: str | None = None | ||
| ) -> type[_ShellCompleteT]: | ||
| """Register a :class:`ShellComplete` subclass under the given name. | ||
| The name will be provided by the completion instruction environment | ||
| variable during completion. | ||
|
|
@@ -467,6 +483,14 @@ def add_completion_class( | |
| return cls | ||
|
|
||
|
|
||
| @t.overload | ||
| def get_completion_class(shell: t.Literal["bash"]) -> type[BashComplete]: ... | ||
| @t.overload | ||
| def get_completion_class(shell: t.Literal["fish"]) -> type[FishComplete]: ... | ||
| @t.overload | ||
| def get_completion_class(shell: t.Literal["zsh"]) -> type[ZshComplete]: ... | ||
| @t.overload | ||
| def get_completion_class(shell: str) -> type[ShellComplete] | None: ... | ||
| def get_completion_class(shell: str) -> type[ShellComplete] | None: | ||
| """Look up a registered :class:`ShellComplete` subclass by the name | ||
| provided by the completion instruction environment variable. If the | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's not include version changes I think?