diff --git a/typer/_compat_utils.py b/typer/_compat_utils.py deleted file mode 100644 index 637e8ceb0d..0000000000 --- a/typer/_compat_utils.py +++ /dev/null @@ -1,5 +0,0 @@ -import click - - -def _get_click_major() -> int: - return int(click.__version__.split(".")[0]) diff --git a/typer/_completion_click8.py b/typer/_completion_classes.py similarity index 100% rename from typer/_completion_click8.py rename to typer/_completion_classes.py diff --git a/typer/_completion_click7.py b/typer/_completion_click7.py deleted file mode 100644 index 9f4ad73f30..0000000000 --- a/typer/_completion_click7.py +++ /dev/null @@ -1,157 +0,0 @@ -import os -import re -import sys - -import click -import click._bashcomplete - -from ._completion_shared import get_completion_script - -try: - import shellingham -except ImportError: # pragma: nocover - shellingham = None - - -_click_patched = False - - -def do_bash_complete(cli: click.Command, prog_name: str) -> bool: - cwords = click.parser.split_arg_string(os.getenv("COMP_WORDS", "")) - cword = int(os.getenv("COMP_CWORD", 0)) - args = cwords[1:cword] - try: - incomplete = cwords[cword] - except IndexError: - incomplete = "" - - for item in click._bashcomplete.get_choices(cli, prog_name, args, incomplete): - click.echo(item[0]) - return True - - -def do_zsh_complete(cli: click.Command, prog_name: str) -> bool: - completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") - cwords = click.parser.split_arg_string(completion_args) - args = cwords[1:] - if args and not completion_args.endswith(" "): - incomplete = args[-1] - args = args[:-1] - else: - incomplete = "" - - def escape(s: str) -> str: - return ( - s.replace('"', '""') - .replace("'", "''") - .replace("$", "\\$") - .replace("`", "\\`") - ) - - res = [] - for item, help in click._bashcomplete.get_choices(cli, prog_name, args, incomplete): - if help: - res.append(f'"{escape(item)}":"{escape(help)}"') - else: - res.append(f'"{escape(item)}"') - if res: - args_str = "\n".join(res) - click.echo(f"_arguments '*: :(({args_str}))'") - else: - click.echo("_files") - return True - - -def do_fish_complete(cli: click.Command, prog_name: str) -> bool: - completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") - complete_action = os.getenv("_TYPER_COMPLETE_FISH_ACTION", "") - cwords = click.parser.split_arg_string(completion_args) - args = cwords[1:] - if args and not completion_args.endswith(" "): - incomplete = args[-1] - args = args[:-1] - else: - incomplete = "" - show_args = [] - for item, help in click._bashcomplete.get_choices(cli, prog_name, args, incomplete): - if help: - formatted_help = re.sub(r"\s", " ", help) - show_args.append(f"{item}\t{formatted_help}") - else: - show_args.append(item) - if complete_action == "get-args": - if show_args: - for arg in show_args: - click.echo(arg) - elif complete_action == "is-args": - if show_args: - # Activate complete args (no files) - sys.exit(0) - else: - # Deactivate complete args (allow files) - sys.exit(1) - return True - - -def do_powershell_complete(cli: click.Command, prog_name: str) -> bool: - completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") - incomplete = os.getenv("_TYPER_COMPLETE_WORD_TO_COMPLETE", "") - cwords = click.parser.split_arg_string(completion_args) - args = cwords[1:] - for item, help in click._bashcomplete.get_choices(cli, prog_name, args, incomplete): - click.echo(f"{item}:::{help or ' '}") - - return True - - -def do_shell_complete(*, cli: click.Command, prog_name: str, shell: str) -> bool: - if shell == "bash": - return do_bash_complete(cli, prog_name) - elif shell == "zsh": - return do_zsh_complete(cli, prog_name) - elif shell == "fish": - return do_fish_complete(cli, prog_name) - elif shell in {"powershell", "pwsh"}: - return do_powershell_complete(cli, prog_name) - return False - - -def handle_shell_complete( - cli: click.Command, prog_name: str, complete_var: str, complete_instr: str -) -> bool: - if "_" not in complete_instr: - click.echo("Invalid completion instruction.", err=True) - sys.exit(1) - command, shell = complete_instr.split("_", 1) - if command == "source": - click.echo( - get_completion_script( - prog_name=prog_name, complete_var=complete_var, shell=shell - ) - ) - return True - elif command == "complete": - return do_shell_complete(cli=cli, prog_name=prog_name, shell=shell) - click.echo(f'Completion instruction "{command}" not supported.', err=True) - return False - - -def completion_init() -> None: - global _click_patched - if not _click_patched: - testing = os.getenv("_TYPER_COMPLETE_TESTING") - - def testing_handle_shell_complete( - cli: click.Command, prog_name: str, complete_var: str, complete_instr: str - ) -> bool: - result = handle_shell_complete(cli, prog_name, complete_var, complete_instr) - if result: - # Avoid fast_exit(1) in Click so Coverage can finish - sys.exit(1) - return result - - if testing: - click._bashcomplete.bashcomplete = testing_handle_shell_complete - else: - click._bashcomplete.bashcomplete = handle_shell_complete - _click_patched = True diff --git a/typer/_completion_shared.py b/typer/_completion_shared.py index 7cbaf98d75..95f566830d 100644 --- a/typer/_completion_shared.py +++ b/typer/_completion_shared.py @@ -14,9 +14,6 @@ shellingham = None -from typing import Optional - - class Shells(str, Enum): bash = "bash" zsh = "zsh" diff --git a/typer/completion.py b/typer/completion.py index c42ad95b81..1fbbc5d85d 100644 --- a/typer/completion.py +++ b/typer/completion.py @@ -4,7 +4,7 @@ import click -from ._compat_utils import _get_click_major +from ._completion_classes import completion_init from ._completion_shared import Shells, get_completion_script, install from .models import ParamMeta from .params import Option @@ -102,17 +102,6 @@ def _install_completion_no_auto_placeholder_function( pass # pragma no cover -def completion_init() -> None: - if _get_click_major() < 8: - from ._completion_click7 import completion_init - - completion_init() - else: - from ._completion_click8 import completion_init - - completion_init() - - # Re-implement Click's shell_complete to add error message with: # Invalid completion instruction # To use 7.x instruction style for compatibility diff --git a/typer/core.py b/typer/core.py index ec17bbcf83..13d607760e 100644 --- a/typer/core.py +++ b/typer/core.py @@ -1,11 +1,9 @@ import errno -import inspect import os import sys from enum import Enum from gettext import gettext as _ from typing import ( - TYPE_CHECKING, Any, Callable, Dict, @@ -23,11 +21,9 @@ import click.core import click.formatting import click.parser +import click.shell_completion import click.types import click.utils -from typer.completion import completion_init - -from ._compat_utils import _get_click_major if sys.version_info >= (3, 8): from typing import Literal @@ -42,30 +38,9 @@ except ImportError: # pragma: nocover rich = None # type: ignore -if TYPE_CHECKING: # pragma: no cover - if _get_click_major() == 7: - import click.shell_completion - MarkupMode = Literal["markdown", "rich", None] -# TODO: when deprecating Click 7, remove this -def _typer_param_shell_complete( - self: click.core.Parameter, ctx: click.Context, incomplete: str -) -> List["click.shell_completion.CompletionItem"]: - if self._custom_shell_complete is not None: - results = self._custom_shell_complete(ctx, self, incomplete) - - if results and isinstance(results[0], str): - from click.shell_completion import CompletionItem - - results = [CompletionItem(c) for c in results] - - return cast(List["click.shell_completion.CompletionItem"], results) - - return self.type.shell_complete(ctx, self, incomplete) - - def _typer_param_setup_autocompletion_compat( self: click.Parameter, *, @@ -166,13 +141,7 @@ def _extract_default_help_str( ctx.resilient_parsing = True try: - if _get_click_major() > 7: - default_value = obj.get_default(ctx, call=False) - else: - if inspect.isfunction(obj.default): - default_value = _("(dynamic)") - else: - default_value = obj.default + default_value = obj.get_default(ctx, call=False) finally: ctx.resilient_parsing = resilient return default_value @@ -201,23 +170,10 @@ def _main( args = list(args) if prog_name is None: - if _get_click_major() > 7: - prog_name = click.utils._detect_program_name() - else: - from click.utils import make_str - - prog_name = make_str( - os.path.basename(sys.argv[0] if sys.argv else __file__) - ) + prog_name = click.utils._detect_program_name() # Process shell completion requests and exit early. - if _get_click_major() > 7: - self._main_shell_completion(extra, prog_name, complete_var) - else: - completion_init() - from click.core import _bashcomplete # type: ignore - - _bashcomplete(self, prog_name, complete_var) + self._main_shell_completion(extra, prog_name, complete_var) try: try: @@ -315,27 +271,21 @@ def __init__( self.show_envvar = show_envvar self.hidden = hidden self.rich_help_panel = rich_help_panel - kwargs: Dict[str, Any] = { - "param_decls": param_decls, - "type": type, - "required": required, - "default": default, - "callback": callback, - "nargs": nargs, - "metavar": metavar, - "expose_value": expose_value, - "is_eager": is_eager, - "envvar": envvar, - } - if _get_click_major() > 7: - kwargs["shell_complete"] = shell_complete - else: - kwargs["autocompletion"] = autocompletion - super().__init__(**kwargs) - if _get_click_major() > 7: - _typer_param_setup_autocompletion_compat( - self, autocompletion=autocompletion - ) + + super().__init__( + param_decls=param_decls, + type=type, + required=required, + default=default, + callback=callback, + nargs=nargs, + metavar=metavar, + expose_value=expose_value, + is_eager=is_eager, + envvar=envvar, + shell_complete=shell_complete, + ) + _typer_param_setup_autocompletion_compat(self, autocompletion=autocompletion) def _get_default_string( self, @@ -417,11 +367,6 @@ def make_metavar(self) -> str: var += "..." return var - def shell_complete( - self, ctx: click.Context, incomplete: str - ) -> List["click.shell_completion.CompletionItem"]: - return _typer_param_shell_complete(self, ctx=ctx, incomplete=incomplete) - class TyperOption(click.core.Option): def __init__( @@ -463,43 +408,34 @@ def __init__( # Rich settings rich_help_panel: Union[str, None] = None, ): - # TODO: when deprecating Click 7, remove custom kwargs with prompt_required - # and call super().__init__() directly - kwargs: Dict[str, Any] = { - "param_decls": param_decls, - "type": type, - "required": required, - "default": default, - "callback": callback, - "nargs": nargs, - "metavar": metavar, - "expose_value": expose_value, - "is_eager": is_eager, - "envvar": envvar, - "show_default": show_default, - "prompt": prompt, - "confirmation_prompt": confirmation_prompt, - "hide_input": hide_input, - "is_flag": is_flag, - "flag_value": flag_value, - "multiple": multiple, - "count": count, - "allow_from_autoenv": allow_from_autoenv, - "help": help, - "hidden": hidden, - "show_choices": show_choices, - "show_envvar": show_envvar, - } - if _get_click_major() > 7: - kwargs["prompt_required"] = prompt_required - kwargs["shell_complete"] = shell_complete - else: - kwargs["autocompletion"] = autocompletion - super().__init__(**kwargs) - if _get_click_major() > 7: - _typer_param_setup_autocompletion_compat( - self, autocompletion=autocompletion - ) + super().__init__( + param_decls=param_decls, + type=type, + required=required, + default=default, + callback=callback, + nargs=nargs, + metavar=metavar, + expose_value=expose_value, + is_eager=is_eager, + envvar=envvar, + show_default=show_default, + prompt=prompt, + confirmation_prompt=confirmation_prompt, + hide_input=hide_input, + is_flag=is_flag, + flag_value=flag_value, + multiple=multiple, + count=count, + allow_from_autoenv=allow_from_autoenv, + help=help, + hidden=hidden, + show_choices=show_choices, + show_envvar=show_envvar, + prompt_required=prompt_required, + shell_complete=shell_complete, + ) + _typer_param_setup_autocompletion_compat(self, autocompletion=autocompletion) self.rich_help_panel = rich_help_panel def _get_default_string( @@ -522,9 +458,6 @@ def _extract_default_help_str( return _extract_default_help_str(self, ctx=ctx) def get_help_record(self, ctx: click.Context) -> Optional[Tuple[str, str]]: - # Click 7.x was not breaking this use case, so in that case, re-use its logic - if _get_click_major() < 8: - return super().get_help_record(ctx) # Duplicate all of Click's logic only to modify a single line, to allow boolean # flags with only names for False values as it's currently supported by Typer # Ref: https://typer.tiangolo.com/tutorial/parameter-types/bool/#only-names-for-false @@ -609,11 +542,6 @@ def _write_opts(opts: Sequence[str]) -> str: return ("; " if any_prefix_is_slash else " / ").join(rv), help - def shell_complete( - self, ctx: click.Context, incomplete: str - ) -> List["click.shell_completion.CompletionItem"]: - return _typer_param_shell_complete(self, ctx=ctx, incomplete=incomplete) - def _typer_format_options( self: click.core.Command, *, ctx: click.Context, formatter: click.HelpFormatter diff --git a/typer/models.py b/typer/models.py index 9809a1c5a0..9bbe2a36d2 100644 --- a/typer/models.py +++ b/typer/models.py @@ -14,13 +14,9 @@ ) import click - -from ._compat_utils import _get_click_major +import click.shell_completion if TYPE_CHECKING: # pragma: no cover - if _get_click_major() > 7: - import click.shell_completion - from .core import TyperCommand, TyperGroup from .main import Typer