Skip to content

Commit

Permalink
Fix return value and type signature of `shell_completion.add_completi…
Browse files Browse the repository at this point in the history
…on_class` function (#2421)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Sagi Buchbinder Shadur <saroad2@gmail.com>
  • Loading branch information
3 people authored May 8, 2023
1 parent eb1d760 commit e002dbc
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Unreleased

- Improve type hinting for decorators and give all generic types parameters.
:issue:`2398`
- Fix return value and type signature of `shell_completion.add_completion_class` function. :pr:`2421`


Version 8.1.3
Expand Down
9 changes: 7 additions & 2 deletions src/click/shell_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,9 @@ def format_completion(self, item: CompletionItem) -> str:
return f"{item.type},{item.value}"


ShellCompleteType = t.TypeVar("ShellCompleteType", bound=t.Type[ShellComplete])


_available_shells: t.Dict[str, t.Type[ShellComplete]] = {
"bash": BashComplete,
"fish": FishComplete,
Expand All @@ -397,8 +400,8 @@ def format_completion(self, item: CompletionItem) -> str:


def add_completion_class(
cls: t.Type[ShellComplete], name: t.Optional[str] = None
) -> None:
cls: ShellCompleteType, name: t.Optional[str] = None
) -> ShellCompleteType:
"""Register a :class:`ShellComplete` subclass under the given name.
The name will be provided by the completion instruction environment
variable during completion.
Expand All @@ -413,6 +416,8 @@ def add_completion_class(

_available_shells[name] = cls

return cls


def get_completion_class(shell: str) -> t.Optional[t.Type[ShellComplete]]:
"""Look up a registered :class:`ShellComplete` subclass by the name
Expand Down
72 changes: 72 additions & 0 deletions tests/test_shell_completion.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import pytest

import click.shell_completion
from click.core import Argument
from click.core import Command
from click.core import Group
from click.core import Option
from click.shell_completion import add_completion_class
from click.shell_completion import CompletionItem
from click.shell_completion import ShellComplete
from click.types import Choice
Expand Down Expand Up @@ -342,3 +344,73 @@ def test_choice_case_sensitive(value, expect):
)
completions = _get_words(cli, ["-a"], "a")
assert completions == expect


@pytest.fixture()
def _restore_available_shells(tmpdir):
prev_available_shells = click.shell_completion._available_shells.copy()
click.shell_completion._available_shells.clear()
yield
click.shell_completion._available_shells.clear()
click.shell_completion._available_shells.update(prev_available_shells)


@pytest.mark.usefixtures("_restore_available_shells")
def test_add_completion_class():
# At first, "mysh" is not in available shells
assert "mysh" not in click.shell_completion._available_shells

class MyshComplete(ShellComplete):
name = "mysh"
source_template = "dummy source"

# "mysh" still not in available shells because it is not registered
assert "mysh" not in click.shell_completion._available_shells

# Adding a completion class should return that class
assert add_completion_class(MyshComplete) is MyshComplete

# Now, "mysh" is finally in available shells
assert "mysh" in click.shell_completion._available_shells
assert click.shell_completion._available_shells["mysh"] is MyshComplete


@pytest.mark.usefixtures("_restore_available_shells")
def test_add_completion_class_with_name():
# At first, "mysh" is not in available shells
assert "mysh" not in click.shell_completion._available_shells
assert "not_mysh" not in click.shell_completion._available_shells

class MyshComplete(ShellComplete):
name = "not_mysh"
source_template = "dummy source"

# "mysh" and "not_mysh" are still not in available shells because
# it is not registered yet
assert "mysh" not in click.shell_completion._available_shells
assert "not_mysh" not in click.shell_completion._available_shells

# Adding a completion class should return that class.
# Because we are using the "name" parameter, the name isn't taken
# from the class.
assert add_completion_class(MyshComplete, name="mysh") is MyshComplete

# Now, "mysh" is finally in available shells
assert "mysh" in click.shell_completion._available_shells
assert "not_mysh" not in click.shell_completion._available_shells
assert click.shell_completion._available_shells["mysh"] is MyshComplete


@pytest.mark.usefixtures("_restore_available_shells")
def test_add_completion_class_decorator():
# At first, "mysh" is not in available shells
assert "mysh" not in click.shell_completion._available_shells

@add_completion_class
class MyshComplete(ShellComplete):
name = "mysh"
source_template = "dummy source"

# Using `add_completion_class` as a decorator adds the new shell immediately
assert "mysh" in click.shell_completion._available_shells
assert click.shell_completion._available_shells["mysh"] is MyshComplete

0 comments on commit e002dbc

Please # to comment.