diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5dd3c73170..533c4cf417 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,9 +13,6 @@ jobs: strategy: matrix: python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] - click-version: - - click-7 - - click-8 fail-fast: false steps: @@ -28,18 +25,14 @@ jobs: run: pip install flit - name: Install Dependencies run: python -m flit install --symlink - - name: Install Click 7 - if: matrix.click-version == 'click-7' - run: pip install "click<8.0.0" - name: Lint - if: ${{ matrix.click-version == 'click-8' }} run: bash scripts/lint.sh - run: mkdir coverage - name: Test run: bash scripts/test.sh env: - COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}-${{ matrix.click-version }} - CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}-${{ matrix.click-version }} + COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }} + CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }} - name: Store coverage files uses: actions/upload-artifact@v3 with: diff --git a/docs/tutorial/options/callback-and-context.md b/docs/tutorial/options/callback-and-context.md index c6c80114c7..4c1c3a6527 100644 --- a/docs/tutorial/options/callback-and-context.md +++ b/docs/tutorial/options/callback-and-context.md @@ -8,7 +8,7 @@ For example, you could do some validation before the rest of the code is execute === "Python 3.7+" - ```Python hl_lines="5-8 11" + ```Python hl_lines="7-10 13" {!> ../docs_src/options/callback/tutorial001_an.py!} ``` @@ -17,7 +17,7 @@ For example, you could do some validation before the rest of the code is execute !!! tip Prefer to use the `Annotated` version if possible. - ```Python hl_lines="4-7 10" + ```Python hl_lines="6-9 12" {!> ../docs_src/options/callback/tutorial001.py!} ``` @@ -107,7 +107,7 @@ Let's say that when the callback is running, we want to show a message saying th === "Python 3.7+" - ```Python hl_lines="6" + ```Python hl_lines="8" {!> ../docs_src/options/callback/tutorial002_an.py!} ``` @@ -116,7 +116,7 @@ Let's say that when the callback is running, we want to show a message saying th !!! tip Prefer to use the `Annotated` version if possible. - ```Python hl_lines="5" + ```Python hl_lines="7" {!> ../docs_src/options/callback/tutorial002.py!} ``` @@ -155,7 +155,7 @@ The "context" has some additional data about the current execution of your progr === "Python 3.7+" - ```Python hl_lines="5-7" + ```Python hl_lines="7-9" {!> ../docs_src/options/callback/tutorial003_an.py!} ``` @@ -164,7 +164,7 @@ The "context" has some additional data about the current execution of your progr !!! tip Prefer to use the `Annotated` version if possible. - ```Python hl_lines="4-6" + ```Python hl_lines="6-8" {!> ../docs_src/options/callback/tutorial003.py!} ``` @@ -172,7 +172,7 @@ The `ctx.resilient_parsing` will be `True` when handling completion, so you can But it will be `False` when calling the program normally. So you can continue the execution of your previous code. -That's all is needed to fix completion 🚀 +That's all is needed to fix completion. 🚀 Check it: @@ -200,7 +200,7 @@ The same way you can access the `typer.Context` by declaring a function paramete === "Python 3.7+" - ```Python hl_lines="5 8" + ```Python hl_lines="7 10" {!> ../docs_src/options/callback/tutorial004_an.py!} ``` @@ -209,7 +209,7 @@ The same way you can access the `typer.Context` by declaring a function paramete !!! tip Prefer to use the `Annotated` version if possible. - ```Python hl_lines="4 7" + ```Python hl_lines="6 9" {!> ../docs_src/options/callback/tutorial004.py!} ``` diff --git a/docs_src/options/callback/tutorial001.py b/docs_src/options/callback/tutorial001.py index 6af7c09fa8..f64b8b7b68 100644 --- a/docs_src/options/callback/tutorial001.py +++ b/docs_src/options/callback/tutorial001.py @@ -1,3 +1,5 @@ +from typing import Optional + import typer @@ -7,7 +9,7 @@ def name_callback(value: str): return value -def main(name: str = typer.Option(..., callback=name_callback)): +def main(name: Optional[str] = typer.Option(default=None, callback=name_callback)): print(f"Hello {name}") diff --git a/docs_src/options/callback/tutorial001_an.py b/docs_src/options/callback/tutorial001_an.py index 9f57221dc8..c539740bc8 100644 --- a/docs_src/options/callback/tutorial001_an.py +++ b/docs_src/options/callback/tutorial001_an.py @@ -1,3 +1,5 @@ +from typing import Optional + import typer from typing_extensions import Annotated @@ -8,7 +10,7 @@ def name_callback(value: str): return value -def main(name: Annotated[str, typer.Option(callback=name_callback)]): +def main(name: Annotated[Optional[str], typer.Option(callback=name_callback)] = None): print(f"Hello {name}") diff --git a/docs_src/options/callback/tutorial002.py b/docs_src/options/callback/tutorial002.py index df9f24b43e..98863c7546 100644 --- a/docs_src/options/callback/tutorial002.py +++ b/docs_src/options/callback/tutorial002.py @@ -1,3 +1,5 @@ +from typing import Optional + import typer @@ -8,7 +10,7 @@ def name_callback(value: str): return value -def main(name: str = typer.Option(..., callback=name_callback)): +def main(name: Optional[str] = typer.Option(default=None, callback=name_callback)): print(f"Hello {name}") diff --git a/docs_src/options/callback/tutorial002_an.py b/docs_src/options/callback/tutorial002_an.py index 2f5a67f8be..f9a7013d43 100644 --- a/docs_src/options/callback/tutorial002_an.py +++ b/docs_src/options/callback/tutorial002_an.py @@ -1,3 +1,5 @@ +from typing import Optional + import typer from typing_extensions import Annotated @@ -9,7 +11,7 @@ def name_callback(value: str): return value -def main(name: Annotated[str, typer.Option(callback=name_callback)]): +def main(name: Annotated[Optional[str], typer.Option(callback=name_callback)] = None): print(f"Hello {name}") diff --git a/docs_src/options/callback/tutorial003.py b/docs_src/options/callback/tutorial003.py index 1891b9793e..1cc5e9b3ed 100644 --- a/docs_src/options/callback/tutorial003.py +++ b/docs_src/options/callback/tutorial003.py @@ -1,3 +1,5 @@ +from typing import Optional + import typer @@ -10,7 +12,7 @@ def name_callback(ctx: typer.Context, value: str): return value -def main(name: str = typer.Option(..., callback=name_callback)): +def main(name: Optional[str] = typer.Option(default=None, callback=name_callback)): print(f"Hello {name}") diff --git a/docs_src/options/callback/tutorial003_an.py b/docs_src/options/callback/tutorial003_an.py index 18a374d65d..609b82268d 100644 --- a/docs_src/options/callback/tutorial003_an.py +++ b/docs_src/options/callback/tutorial003_an.py @@ -1,3 +1,5 @@ +from typing import Optional + import typer from typing_extensions import Annotated @@ -11,7 +13,7 @@ def name_callback(ctx: typer.Context, value: str): return value -def main(name: Annotated[str, typer.Option(callback=name_callback)]): +def main(name: Annotated[Optional[str], typer.Option(callback=name_callback)] = None): print(f"Hello {name}") diff --git a/docs_src/options/callback/tutorial004.py b/docs_src/options/callback/tutorial004.py index 61c9c19991..9502c65e34 100644 --- a/docs_src/options/callback/tutorial004.py +++ b/docs_src/options/callback/tutorial004.py @@ -1,3 +1,5 @@ +from typing import Optional + import typer @@ -10,7 +12,7 @@ def name_callback(ctx: typer.Context, param: typer.CallbackParam, value: str): return value -def main(name: str = typer.Option(..., callback=name_callback)): +def main(name: Optional[str] = typer.Option(default=None, callback=name_callback)): print(f"Hello {name}") diff --git a/docs_src/options/callback/tutorial004_an.py b/docs_src/options/callback/tutorial004_an.py index d8412ed784..670f0cbc66 100644 --- a/docs_src/options/callback/tutorial004_an.py +++ b/docs_src/options/callback/tutorial004_an.py @@ -1,3 +1,5 @@ +from typing import Optional + import typer from typing_extensions import Annotated @@ -11,7 +13,7 @@ def name_callback(ctx: typer.Context, param: typer.CallbackParam, value: str): return value -def main(name: Annotated[str, typer.Option(callback=name_callback)]): +def main(name: Annotated[Optional[str], typer.Option(callback=name_callback)] = None): print(f"Hello {name}") diff --git a/pyproject.toml b/pyproject.toml index 1d96c62021..c6e282a545 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ classifiers = [ "License :: OSI Approved :: MIT License" ] requires = [ - "click >= 7.1.1, <9.0.0", + "click >= 8.0.0", "typing-extensions >= 3.7.4.3", ] description-file = "README.md" diff --git a/tests/test_compat/test_option_get_help.py b/tests/test_compat/test_option_get_help.py index 41a9d5269c..c4966dc9b6 100644 --- a/tests/test_compat/test_option_get_help.py +++ b/tests/test_compat/test_option_get_help.py @@ -52,5 +52,4 @@ def test_completion(): "_TYPER_COMPLETE_TESTING": "True", }, ) - # TODO: when deprecating Click 7, remove second option - assert "Jonny" in result.stdout or "_files" in result.stdout + assert "Jonny" in result.stdout diff --git a/tests/test_completion/test_completion_install.py b/tests/test_completion/test_completion_install.py index 0856b9b565..32c6f71dae 100644 --- a/tests/test_completion/test_completion_install.py +++ b/tests/test_completion/test_completion_install.py @@ -27,11 +27,7 @@ def test_completion_install_no_shell(): "_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION": "True", }, ) - # TODO: when deprecating Click 7, remove second option - assert ( - "Option '--install-completion' requires an argument" in result.stderr - or "--install-completion option requires an argument" in result.stderr - ) + assert "Option '--install-completion' requires an argument" in result.stderr def test_completion_install_bash(): diff --git a/tests/test_completion/test_completion_show.py b/tests/test_completion/test_completion_show.py index 02e70cb8ae..196f27f048 100644 --- a/tests/test_completion/test_completion_show.py +++ b/tests/test_completion/test_completion_show.py @@ -1,9 +1,18 @@ import os import subprocess import sys +from unittest import mock + +import shellingham +import typer +from typer.testing import CliRunner from docs_src.commands.index import tutorial001 as mod +runner = CliRunner() +app = typer.Typer() +app.command()(mod.main) + def test_completion_show_no_shell(): result = subprocess.run( @@ -17,11 +26,7 @@ def test_completion_show_no_shell(): "_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION": "True", }, ) - # TODO: when deprecating Click 7, remove second option - assert ( - "Option '--show-completion' requires an argument" in result.stderr - or "--show-completion option requires an argument" in result.stderr - ) + assert "Option '--show-completion' requires an argument" in result.stderr def test_completion_show_bash(): @@ -146,3 +151,11 @@ def test_completion_source_pwsh(): "Register-ArgumentCompleter -Native -CommandName tutorial001.py -ScriptBlock $scriptblock" in result.stdout ) + + +def test_completion_show_invalid_shell(): + with mock.patch.object( + shellingham, "detect_shell", return_value=("xshell", "/usr/bin/xshell") + ): + result = runner.invoke(app, ["--show-completion"]) + assert "Shell xshell not supported" in result.stdout diff --git a/tests/test_others.py b/tests/test_others.py index 2c257518ca..3bc7fd7d72 100644 --- a/tests/test_others.py +++ b/tests/test_others.py @@ -10,6 +10,7 @@ import shellingham import typer import typer.completion +from typer.core import _split_opt from typer.main import solve_typer_info_defaults, solve_typer_info_help from typer.models import ParameterInfo, TyperInfo from typer.testing import CliRunner @@ -155,11 +156,7 @@ def test_completion_untyped_parameters(): }, ) assert "info name is: completion_no_types.py" in result.stderr - # TODO: when deprecating Click 7, remove second option - assert ( - "args is: []" in result.stderr - or "args is: ['--name', 'Sebastian', '--name']" in result.stderr - ) + assert "args is: []" in result.stderr assert "incomplete is: Ca" in result.stderr assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout @@ -188,11 +185,7 @@ def test_completion_untyped_parameters_different_order_correct_names(): }, ) assert "info name is: completion_no_types_order.py" in result.stderr - # TODO: when deprecating Click 7, remove second option - assert ( - "args is: []" in result.stderr - or "args is: ['--name', 'Sebastian', '--name']" in result.stderr - ) + assert "args is: []" in result.stderr assert "incomplete is: Ca" in result.stderr assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout @@ -233,12 +226,8 @@ def main(arg1, arg2: int, arg3: "int", arg4: bool = False, arg5: "bool" = False) print(f"arg5: {type(arg5)} {arg5}") result = runner.invoke(app, ["Hello", "2", "invalid"]) - # TODO: when deprecating Click 7, remove second option - assert ( - "Invalid value for 'ARG3': 'invalid' is not a valid integer" in result.stdout - or "Invalid value for 'ARG3': invalid is not a valid integer" in result.stdout - ) + assert "Invalid value for 'ARG3': 'invalid' is not a valid integer" in result.stdout result = runner.invoke(app, ["Hello", "2", "3", "--arg4", "--arg5"]) assert ( "arg1: Hello\narg2: 2\narg3: 3\narg4: True\narg5: True\n" @@ -255,3 +244,21 @@ def main(name: str): result = runner.invoke(app, ["main", "-h"]) assert "Show this message and exit." in result.stdout + + +def test_split_opt(): + prefix, opt = _split_opt("--verbose") + assert prefix == "--" + assert opt == "verbose" + + prefix, opt = _split_opt("//verbose") + assert prefix == "//" + assert opt == "verbose" + + prefix, opt = _split_opt("-verbose") + assert prefix == "-" + assert opt == "verbose" + + prefix, opt = _split_opt("verbose") + assert prefix == "" + assert opt == "verbose" diff --git a/tests/test_tutorial/test_commands/test_callback/test_tutorial001.py b/tests/test_tutorial/test_commands/test_callback/test_tutorial001.py index 8bda783bb9..68bcdf1fd0 100644 --- a/tests/test_tutorial/test_commands/test_callback/test_tutorial001.py +++ b/tests/test_tutorial/test_commands/test_callback/test_tutorial001.py @@ -63,11 +63,7 @@ def test_delete_verbose(): def test_wrong_verbose(): result = runner.invoke(app, ["delete", "--verbose", "Camila"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option - assert ( - "No such option: --verbose" in result.output - or "no such option: --verbose" in result.output - ) + assert "No such option: --verbose" in result.output def test_script(): diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial001.py b/tests/test_tutorial/test_commands/test_help/test_tutorial001.py index bad756c405..e2c99e35c4 100644 --- a/tests/test_tutorial/test_commands/test_help/test_tutorial001.py +++ b/tests/test_tutorial/test_commands/test_help/test_tutorial001.py @@ -69,44 +69,28 @@ def test_create(): def test_delete(): result = runner.invoke(app, ["delete", "Camila"], input="y\n") assert result.exit_code == 0 - # TODO: when deprecating Click 7, remove second option - assert ( - "Are you sure you want to delete the user? [y/n]:" in result.output - or "Are you sure you want to delete the user? [y/N]:" in result.output - ) + assert "Are you sure you want to delete the user? [y/n]:" in result.output assert "Deleting user: Camila" in result.output def test_no_delete(): result = runner.invoke(app, ["delete", "Camila"], input="n\n") assert result.exit_code == 0 - # TODO: when deprecating Click 7, remove second option - assert ( - "Are you sure you want to delete the user? [y/n]:" in result.output - or "Are you sure you want to delete the user? [y/N]:" in result.output - ) + assert "Are you sure you want to delete the user? [y/n]:" in result.output assert "Operation cancelled" in result.output def test_delete_all(): result = runner.invoke(app, ["delete-all"], input="y\n") assert result.exit_code == 0 - # TODO: when deprecating Click 7, remove second option - assert ( - "Are you sure you want to delete ALL users? [y/n]:" in result.output - or "Are you sure you want to delete ALL users? [y/N]:" in result.output - ) + assert "Are you sure you want to delete ALL users? [y/n]:" in result.output assert "Deleting all users" in result.output def test_no_delete_all(): result = runner.invoke(app, ["delete-all"], input="n\n") assert result.exit_code == 0 - # TODO: when deprecating Click 7, remove second option - assert ( - "Are you sure you want to delete ALL users? [y/n]:" in result.output - or "Are you sure you want to delete ALL users? [y/N]:" in result.output - ) + assert "Are you sure you want to delete ALL users? [y/n]:" in result.output assert "Operation cancelled" in result.output diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial001_an.py b/tests/test_tutorial/test_commands/test_help/test_tutorial001_an.py index 8be85698d0..e349718463 100644 --- a/tests/test_tutorial/test_commands/test_help/test_tutorial001_an.py +++ b/tests/test_tutorial/test_commands/test_help/test_tutorial001_an.py @@ -69,44 +69,28 @@ def test_create(): def test_delete(): result = runner.invoke(app, ["delete", "Camila"], input="y\n") assert result.exit_code == 0 - # TODO: when deprecating Click 7, remove second option - assert ( - "Are you sure you want to delete the user? [y/n]:" in result.output - or "Are you sure you want to delete the user? [y/N]:" in result.output - ) + assert "Are you sure you want to delete the user? [y/n]:" in result.output assert "Deleting user: Camila" in result.output def test_no_delete(): result = runner.invoke(app, ["delete", "Camila"], input="n\n") assert result.exit_code == 0 - # TODO: when deprecating Click 7, remove second option - assert ( - "Are you sure you want to delete the user? [y/n]:" in result.output - or "Are you sure you want to delete the user? [y/N]:" in result.output - ) + assert "Are you sure you want to delete the user? [y/n]:" in result.output assert "Operation cancelled" in result.output def test_delete_all(): result = runner.invoke(app, ["delete-all"], input="y\n") assert result.exit_code == 0 - # TODO: when deprecating Click 7, remove second option - assert ( - "Are you sure you want to delete ALL users? [y/n]:" in result.output - or "Are you sure you want to delete ALL users? [y/N]:" in result.output - ) + assert "Are you sure you want to delete ALL users? [y/n]:" in result.output assert "Deleting all users" in result.output def test_no_delete_all(): result = runner.invoke(app, ["delete-all"], input="n\n") assert result.exit_code == 0 - # TODO: when deprecating Click 7, remove second option - assert ( - "Are you sure you want to delete ALL users? [y/n]:" in result.output - or "Are you sure you want to delete ALL users? [y/N]:" in result.output - ) + assert "Are you sure you want to delete ALL users? [y/n]:" in result.output assert "Operation cancelled" in result.output diff --git a/tests/test_tutorial/test_commands/test_options/test_tutorial001.py b/tests/test_tutorial/test_commands/test_options/test_tutorial001.py index ce3307e3f8..f15e76bb5f 100644 --- a/tests/test_tutorial/test_commands/test_options/test_tutorial001.py +++ b/tests/test_tutorial/test_commands/test_options/test_tutorial001.py @@ -29,55 +29,35 @@ def test_create(): def test_delete(): result = runner.invoke(app, ["delete", "Camila"], input="y\n") assert result.exit_code == 0 - # TODO: when deprecating Click 7, remove second option - assert ( - "Are you sure you want to delete the user? [y/n]:" in result.output - or "Are you sure you want to delete the user? [y/N]:" in result.output - ) + assert "Are you sure you want to delete the user? [y/n]:" in result.output assert "Deleting user: Camila" in result.output def test_no_delete(): result = runner.invoke(app, ["delete", "Camila"], input="n\n") assert result.exit_code == 0 - # TODO: when deprecating Click 7, remove second option - assert ( - "Are you sure you want to delete the user? [y/n]:" in result.output - or "Are you sure you want to delete the user? [y/N]:" in result.output - ) + assert "Are you sure you want to delete the user? [y/n]:" in result.output assert "Operation cancelled" in result.output def test_delete_all(): result = runner.invoke(app, ["delete-all"], input="y\n") assert result.exit_code == 0 - # TODO: when deprecating Click 7, remove second option - assert ( - "Are you sure you want to delete ALL users? [y/n]:" in result.output - or "Are you sure you want to delete ALL users? [y/N]:" in result.output - ) + assert "Are you sure you want to delete ALL users? [y/n]:" in result.output assert "Deleting all users" in result.output def test_no_delete_all(): result = runner.invoke(app, ["delete-all"], input="n\n") assert result.exit_code == 0 - # TODO: when deprecating Click 7, remove second option - assert ( - "Are you sure you want to delete ALL users? [y/n]:" in result.output - or "Are you sure you want to delete ALL users? [y/N]:" in result.output - ) + assert "Are you sure you want to delete ALL users? [y/n]:" in result.output assert "Operation cancelled" in result.output def test_delete_all_force(): result = runner.invoke(app, ["delete-all", "--force"]) assert result.exit_code == 0 - # TODO: when deprecating Click 7, remove second option - assert ( - "Are you sure you want to delete ALL users? [y/n]:" not in result.output - or "Are you sure you want to delete ALL users? [y/N]:" not in result.output - ) + assert "Are you sure you want to delete ALL users? [y/n]:" not in result.output assert "Deleting all users" in result.output diff --git a/tests/test_tutorial/test_commands/test_options/test_tutorial001_an.py b/tests/test_tutorial/test_commands/test_options/test_tutorial001_an.py index b65fe03f44..9dd919c0f4 100644 --- a/tests/test_tutorial/test_commands/test_options/test_tutorial001_an.py +++ b/tests/test_tutorial/test_commands/test_options/test_tutorial001_an.py @@ -29,55 +29,35 @@ def test_create(): def test_delete(): result = runner.invoke(app, ["delete", "Camila"], input="y\n") assert result.exit_code == 0 - # TODO: when deprecating Click 7, remove second option - assert ( - "Are you sure you want to delete the user? [y/n]:" in result.output - or "Are you sure you want to delete the user? [y/N]:" in result.output - ) + assert "Are you sure you want to delete the user? [y/n]:" in result.output assert "Deleting user: Camila" in result.output def test_no_delete(): result = runner.invoke(app, ["delete", "Camila"], input="n\n") assert result.exit_code == 0 - # TODO: when deprecating Click 7, remove second option - assert ( - "Are you sure you want to delete the user? [y/n]:" in result.output - or "Are you sure you want to delete the user? [y/N]:" in result.output - ) + assert "Are you sure you want to delete the user? [y/n]:" in result.output assert "Operation cancelled" in result.output def test_delete_all(): result = runner.invoke(app, ["delete-all"], input="y\n") assert result.exit_code == 0 - # TODO: when deprecating Click 7, remove second option - assert ( - "Are you sure you want to delete ALL users? [y/n]:" in result.output - or "Are you sure you want to delete ALL users? [y/N]:" in result.output - ) + assert "Are you sure you want to delete ALL users? [y/n]:" in result.output assert "Deleting all users" in result.output def test_no_delete_all(): result = runner.invoke(app, ["delete-all"], input="n\n") assert result.exit_code == 0 - # TODO: when deprecating Click 7, remove second option - assert ( - "Are you sure you want to delete ALL users? [y/n]:" in result.output - or "Are you sure you want to delete ALL users? [y/N]:" in result.output - ) + assert "Are you sure you want to delete ALL users? [y/n]:" in result.output assert "Operation cancelled" in result.output def test_delete_all_force(): result = runner.invoke(app, ["delete-all", "--force"]) assert result.exit_code == 0 - # TODO: when deprecating Click 7, remove second option - assert ( - "Are you sure you want to delete ALL users? [y/n]:" not in result.output - or "Are you sure you want to delete ALL users? [y/N]:" not in result.output - ) + assert "Are you sure you want to delete ALL users? [y/n]:" not in result.output assert "Deleting all users" in result.output diff --git a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial002.py b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial002.py index 88e2dfe506..ad323c3681 100644 --- a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial002.py +++ b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial002.py @@ -30,12 +30,7 @@ def test_defaults(): def test_invalid_args(): result = runner.invoke(app, ["Draco", "Hagrid"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option - - assert ( - "Argument 'names' takes 3 values" in result.stdout - or "argument names takes 3 values" in result.stdout - ) + assert "Argument 'names' takes 3 values" in result.stdout def test_valid_args(): diff --git a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial002_an.py b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial002_an.py index d99a38e651..e420acb46d 100644 --- a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial002_an.py +++ b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial002_an.py @@ -32,12 +32,7 @@ def test_defaults(): def test_invalid_args(): result = runner.invoke(app, ["Draco", "Hagrid"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option - - assert ( - "Argument 'names' takes 3 values" in result.stdout - or "argument names takes 3 values" in result.stdout - ) + assert "Argument 'names' takes 3 values" in result.stdout def test_valid_args(): diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial001.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial001.py index fcb216595c..013b19f873 100644 --- a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial001.py +++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial001.py @@ -35,12 +35,7 @@ def test_user_2(): def test_invalid_user(): result = runner.invoke(app, ["--user", "Camila", "50"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option - - assert ( - "Option '--user' requires 3 arguments" in result.output - or "--user option requires 3 arguments" in result.output - ) + assert "Option '--user' requires 3 arguments" in result.output def test_script(): diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial001_an.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial001_an.py index c83b7b6bb2..de8cfa9f20 100644 --- a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial001_an.py +++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial001_an.py @@ -35,12 +35,7 @@ def test_user_2(): def test_invalid_user(): result = runner.invoke(app, ["--user", "Camila", "50"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option - - assert ( - "Option '--user' requires 3 arguments" in result.output - or "--user option requires 3 arguments" in result.output - ) + assert "Option '--user' requires 3 arguments" in result.output def test_script(): diff --git a/tests/test_tutorial/test_options/test_name/test_tutorial001.py b/tests/test_tutorial/test_options/test_name/test_tutorial001.py index fd0f5d59c6..da14c934c1 100644 --- a/tests/test_tutorial/test_options/test_name/test_tutorial001.py +++ b/tests/test_tutorial/test_options/test_name/test_tutorial001.py @@ -29,11 +29,7 @@ def test_call(): def test_call_no_args(): result = runner.invoke(app, ["--name"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option - assert ( - "Option '--name' requires an argument" in result.output - or "--name option requires an argument" in result.output - ) + assert "Option '--name' requires an argument" in result.output def test_script(): diff --git a/tests/test_tutorial/test_options/test_name/test_tutorial001_an.py b/tests/test_tutorial/test_options/test_name/test_tutorial001_an.py index 5bf1aa7d9c..c5c57b2ee8 100644 --- a/tests/test_tutorial/test_options/test_name/test_tutorial001_an.py +++ b/tests/test_tutorial/test_options/test_name/test_tutorial001_an.py @@ -29,11 +29,7 @@ def test_call(): def test_call_no_args(): result = runner.invoke(app, ["--name"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option - assert ( - "Option '--name' requires an argument" in result.output - or "--name option requires an argument" in result.output - ) + assert "Option '--name' requires an argument" in result.output def test_script(): diff --git a/tests/test_tutorial/test_options/test_prompt/test_tutorial003.py b/tests/test_tutorial/test_options/test_prompt/test_tutorial003.py index ba3204adab..2684bcedc9 100644 --- a/tests/test_tutorial/test_options/test_prompt/test_tutorial003.py +++ b/tests/test_tutorial/test_options/test_prompt/test_tutorial003.py @@ -23,12 +23,7 @@ def test_prompt_not_equal(): app, input="Old Project\nNew Spice\nOld Project\nOld Project\n" ) assert result.exit_code == 0 - # TODO: when deprecating Click 7, remove second option - - assert ( - "Error: The two entered values do not match" in result.output - or "Error: the two entered values do not match" in result.output - ) + assert "Error: The two entered values do not match" in result.output assert "Deleting project Old Project" in result.output diff --git a/tests/test_tutorial/test_options/test_prompt/test_tutorial003_an.py b/tests/test_tutorial/test_options/test_prompt/test_tutorial003_an.py index 4588b31b06..b163386438 100644 --- a/tests/test_tutorial/test_options/test_prompt/test_tutorial003_an.py +++ b/tests/test_tutorial/test_options/test_prompt/test_tutorial003_an.py @@ -23,12 +23,7 @@ def test_prompt_not_equal(): app, input="Old Project\nNew Spice\nOld Project\nOld Project\n" ) assert result.exit_code == 0 - # TODO: when deprecating Click 7, remove second option - - assert ( - "Error: The two entered values do not match" in result.output - or "Error: the two entered values do not match" in result.output - ) + assert "Error: The two entered values do not match" in result.output assert "Deleting project Old Project" in result.output diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial008.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial008.py index 78a787a821..982c7d94c0 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial008.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial008.py @@ -25,8 +25,7 @@ def test_completion(): assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout assert '"Sebastian":"The type hints guy."' in result.stdout - # TODO: when deprecating Click 7, remove second option - assert "[]" in result.stderr or "['--name']" in result.stderr + assert "[]" in result.stderr def test_1(): diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial008_an.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial008_an.py index adacd66e8d..8c662543ea 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial008_an.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial008_an.py @@ -25,8 +25,7 @@ def test_completion(): assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout assert '"Sebastian":"The type hints guy."' in result.stdout - # TODO: when deprecating Click 7, remove second option - assert "[]" in result.stderr or "['--name']" in result.stderr + assert "[]" in result.stderr def test_1(): diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial009.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial009.py index ee7918d5dc..999240d970 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial009.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial009.py @@ -25,8 +25,7 @@ def test_completion(): assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout assert '"Sebastian":"The type hints guy."' not in result.stdout - # TODO: when deprecating Click 7, remove second option - assert "[]" in result.stderr or "['--name', 'Sebastian', '--name']" in result.stderr + assert "[]" in result.stderr def test_1(): diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial009_an.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial009_an.py index 8ac91a0aaa..b56523bcbb 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial009_an.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial009_an.py @@ -25,8 +25,7 @@ def test_completion(): assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout assert '"Sebastian":"The type hints guy."' not in result.stdout - # TODO: when deprecating Click 7, remove second option - assert "[]" in result.stderr or "['--name', 'Sebastian', '--name']" in result.stderr + assert "[]" in result.stderr def test_1(): diff --git a/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial001.py b/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial001.py index cc4b4548fe..356a99284b 100644 --- a/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial001.py +++ b/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial001.py @@ -34,12 +34,7 @@ def test_force(): def test_invalid_no_force(): result = runner.invoke(app, ["--no-force"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option - - assert ( - "No such option: --no-force" in result.output - or "no such option: --no-force" in result.output - ) + assert "No such option: --no-force" in result.output def test_script(): diff --git a/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial001_an.py b/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial001_an.py index 52cd1cd290..ef92385022 100644 --- a/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial001_an.py +++ b/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial001_an.py @@ -34,12 +34,7 @@ def test_force(): def test_invalid_no_force(): result = runner.invoke(app, ["--no-force"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option - - assert ( - "No such option: --no-force" in result.output - or "no such option: --no-force" in result.output - ) + assert "No such option: --no-force" in result.output def test_script(): diff --git a/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial002.py b/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial002.py index 339ebce3b6..413ccd4f67 100644 --- a/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial002.py +++ b/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial002.py @@ -53,12 +53,7 @@ def test_reject(): def test_invalid_no_accept(): result = runner.invoke(app, ["--no-accept"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option - - assert ( - "No such option: --no-accept" in result.output - or "no such option: --no-accept" in result.output - ) + assert "No such option: --no-accept" in result.output def test_script(): diff --git a/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial002_an.py b/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial002_an.py index d13e645438..0498c3299f 100644 --- a/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial002_an.py +++ b/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial002_an.py @@ -53,12 +53,7 @@ def test_reject(): def test_invalid_no_accept(): result = runner.invoke(app, ["--no-accept"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option - - assert ( - "No such option: --no-accept" in result.output - or "no such option: --no-accept" in result.output - ) + assert "No such option: --no-accept" in result.output def test_script(): diff --git a/tests/test_tutorial/test_parameter_types/test_datetime/test_tutorial001.py b/tests/test_tutorial/test_parameter_types/test_datetime/test_tutorial001.py index f6be63e645..215e5deb8a 100644 --- a/tests/test_tutorial/test_parameter_types/test_datetime/test_tutorial001.py +++ b/tests/test_tutorial/test_parameter_types/test_datetime/test_tutorial001.py @@ -32,11 +32,7 @@ def test_invalid(): "Invalid value for 'BIRTH:[%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S]':" in result.stdout ) - # TODO: when deprecating Click 7, remove second option - assert ( - "'july-19-1989' does not match the formats" in result.output - or "invalid datetime format: july-19-1989. (choose from" in result.output - ) + assert "'july-19-1989' does not match the formats" in result.output assert "%Y-%m-%d" in result.output assert "%Y-%m-%dT%H:%M:%S" in result.output assert "%Y-%m-%d %H:%M:%S" in result.output diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial001.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial001.py index d1debcd399..3dc997a2d2 100644 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial001.py +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial001.py @@ -29,13 +29,7 @@ def test_main(): def test_invalid_case(): result = runner.invoke(app, ["--network", "CONV"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option - - assert ( - "Invalid value for '--network': 'CONV' is not one of" in result.output - or "Invalid value for '--network': invalid choice: CONV. (choose from" - in result.output - ) + assert "Invalid value for '--network': 'CONV' is not one of" in result.output assert "simple" in result.output assert "conv" in result.output assert "lstm" in result.output @@ -44,13 +38,7 @@ def test_invalid_case(): def test_invalid_other(): result = runner.invoke(app, ["--network", "capsule"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option - - assert ( - "Invalid value for '--network': 'capsule' is not one of" in result.output - or "Invalid value for '--network': invalid choice: capsule. (choose from" - in result.output - ) + assert "Invalid value for '--network': 'capsule' is not one of" in result.output assert "simple" in result.output assert "conv" in result.output assert "lstm" in result.output diff --git a/tests/test_tutorial/test_parameter_types/test_index/test_tutorial001.py b/tests/test_tutorial/test_parameter_types/test_index/test_tutorial001.py index 62d16a60ab..b232dba532 100644 --- a/tests/test_tutorial/test_parameter_types/test_index/test_tutorial001.py +++ b/tests/test_tutorial/test_parameter_types/test_index/test_tutorial001.py @@ -35,12 +35,7 @@ def test_params(): def test_invalid(): result = runner.invoke(app, ["Camila", "--age", "15.3"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option - - assert ( - "Invalid value for '--age': '15.3' is not a valid integer" in result.output - or "Invalid value for '--age': 15.3 is not a valid integer" in result.output - ) + assert "Invalid value for '--age': '15.3' is not a valid integer" in result.output def test_script(): diff --git a/tests/test_tutorial/test_parameter_types/test_number/test_tutorial001.py b/tests/test_tutorial/test_parameter_types/test_number/test_tutorial001.py index 1e08f0d493..efec0467b9 100644 --- a/tests/test_tutorial/test_parameter_types/test_number/test_tutorial001.py +++ b/tests/test_tutorial/test_parameter_types/test_number/test_tutorial001.py @@ -45,39 +45,23 @@ def test_params(): def test_invalid_id(): result = runner.invoke(app, ["1002"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option assert ( - ( - "Invalid value for 'ID': 1002 is not in the range 0<=x<=1000." - in result.output - ) - or "Invalid value for 'ID': 1002 is not in the valid range of 0 to 1000." - in result.output + "Invalid value for 'ID': 1002 is not in the range 0<=x<=1000." in result.output ) def test_invalid_age(): result = runner.invoke(app, ["5", "--age", "15"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option - - assert ( - "Invalid value for '--age': 15 is not in the range x>=18" in result.output - or "Invalid value for '--age': 15 is smaller than the minimum valid value 18." - in result.output - ) + assert "Invalid value for '--age': 15 is not in the range x>=18" in result.output def test_invalid_score(): result = runner.invoke(app, ["5", "--age", "20", "--score", "100.5"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option - assert ( "Invalid value for '--score': 100.5 is not in the range x<=100." in result.output - or "Invalid value for '--score': 100.5 is bigger than the maximum valid value" - in result.output ) diff --git a/tests/test_tutorial/test_parameter_types/test_number/test_tutorial001_an.py b/tests/test_tutorial/test_parameter_types/test_number/test_tutorial001_an.py index c77048b460..6bc60beeb2 100644 --- a/tests/test_tutorial/test_parameter_types/test_number/test_tutorial001_an.py +++ b/tests/test_tutorial/test_parameter_types/test_number/test_tutorial001_an.py @@ -45,39 +45,23 @@ def test_params(): def test_invalid_id(): result = runner.invoke(app, ["1002"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option assert ( - ( - "Invalid value for 'ID': 1002 is not in the range 0<=x<=1000." - in result.output - ) - or "Invalid value for 'ID': 1002 is not in the valid range of 0 to 1000." - in result.output + "Invalid value for 'ID': 1002 is not in the range 0<=x<=1000." in result.output ) def test_invalid_age(): result = runner.invoke(app, ["5", "--age", "15"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option - - assert ( - "Invalid value for '--age': 15 is not in the range x>=18" in result.output - or "Invalid value for '--age': 15 is smaller than the minimum valid value 18." - in result.output - ) + assert "Invalid value for '--age': 15 is not in the range x>=18" in result.output def test_invalid_score(): result = runner.invoke(app, ["5", "--age", "20", "--score", "100.5"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option - assert ( "Invalid value for '--score': 100.5 is not in the range x<=100." in result.output - or "Invalid value for '--score': 100.5 is bigger than the maximum valid value" - in result.output ) diff --git a/tests/test_tutorial/test_parameter_types/test_number/test_tutorial002.py b/tests/test_tutorial/test_parameter_types/test_number/test_tutorial002.py index 2e33f2e7ba..2bc0e23e17 100644 --- a/tests/test_tutorial/test_parameter_types/test_number/test_tutorial002.py +++ b/tests/test_tutorial/test_parameter_types/test_number/test_tutorial002.py @@ -15,12 +15,8 @@ def test_invalid_id(): result = runner.invoke(app, ["1002"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option - assert ( "Invalid value for 'ID': 1002 is not in the range 0<=x<=1000" in result.output - or "Invalid value for 'ID': 1002 is not in the valid range of 0 to 1000." - in result.output ) diff --git a/tests/test_tutorial/test_parameter_types/test_number/test_tutorial002_an.py b/tests/test_tutorial/test_parameter_types/test_number/test_tutorial002_an.py index 31f9729cc9..de1eaa982f 100644 --- a/tests/test_tutorial/test_parameter_types/test_number/test_tutorial002_an.py +++ b/tests/test_tutorial/test_parameter_types/test_number/test_tutorial002_an.py @@ -15,12 +15,8 @@ def test_invalid_id(): result = runner.invoke(app, ["1002"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option - assert ( "Invalid value for 'ID': 1002 is not in the range 0<=x<=1000" in result.output - or "Invalid value for 'ID': 1002 is not in the valid range of 0 to 1000." - in result.output ) diff --git a/tests/test_tutorial/test_parameter_types/test_uuid/test_tutorial001.py b/tests/test_tutorial/test_parameter_types/test_uuid/test_tutorial001.py index b22ad1251c..0f60bc544d 100644 --- a/tests/test_tutorial/test_parameter_types/test_uuid/test_tutorial001.py +++ b/tests/test_tutorial/test_parameter_types/test_uuid/test_tutorial001.py @@ -22,13 +22,9 @@ def test_main(): def test_invalid_uuid(): result = runner.invoke(app, ["7479706572-72756c6573"]) assert result.exit_code != 0 - # TODO: when deprecating Click 7, remove second option - assert ( "Invalid value for 'USER_ID': '7479706572-72756c6573' is not a valid UUID" in result.output - or "Invalid value for 'USER_ID': 7479706572-72756c6573 is not a valid UUID value" - in result.output ) diff --git a/tests/test_tutorial/test_using_click/test_tutorial003.py b/tests/test_tutorial/test_using_click/test_tutorial003.py index 58af540680..55f7f16f9e 100644 --- a/tests/test_tutorial/test_using_click/test_tutorial003.py +++ b/tests/test_tutorial/test_using_click/test_tutorial003.py @@ -10,8 +10,7 @@ def test_cli(): result = runner.invoke(mod.typer_click_object, []) - # TODO: when deprecating Click 7, remove second option - assert "Missing command" in result.stdout or "Usage" in result.stdout + assert "Missing command" in result.stdout def test_help(): 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..3202e18ed8 100644 --- a/typer/_completion_shared.py +++ b/typer/_completion_shared.py @@ -1,7 +1,6 @@ import os import re import subprocess -import sys from enum import Enum from pathlib import Path from typing import Optional, Tuple @@ -14,9 +13,6 @@ shellingham = None -from typing import Optional - - class Shells(str, Enum): bash = "bash" zsh = "zsh" @@ -88,7 +84,7 @@ def get_completion_script(*, prog_name: str, complete_var: str, shell: str) -> s script = _completion_scripts.get(shell) if script is None: click.echo(f"Shell {shell} not supported.", err=True) - sys.exit(1) + raise click.exceptions.Exit(1) return ( script % dict( diff --git a/typer/completion.py b/typer/completion.py index c42ad95b81..fce42965d3 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,24 +102,13 @@ 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 # And to add extra error messages, for compatibility with Typer in previous versions # This is only called in new Command method, only used by Click 8.x+ def shell_complete( - cli: click.BaseCommand, + cli: click.Command, ctx_args: MutableMapping[str, Any], prog_name: str, complete_var: str, diff --git a/typer/core.py b/typer/core.py index ec17bbcf83..c56acf8522 100644 --- a/typer/core.py +++ b/typer/core.py @@ -5,7 +5,6 @@ from enum import Enum from gettext import gettext as _ from typing import ( - TYPE_CHECKING, Any, Callable, Dict, @@ -23,11 +22,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,28 +39,17 @@ 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) +# Copy from click.parser._split_opt +def _split_opt(opt: str) -> Tuple[str, str]: + first = opt[:1] + if first.isalnum(): + return "", opt + if opt[1:2] == first: + return opt[:2], opt[2:] + return first, opt[1:] def _typer_param_setup_autocompletion_compat( @@ -125,7 +111,7 @@ def _get_default_string( ) elif isinstance(default_value, Enum): default_string = str(default_value.value) - elif callable(default_value): + elif inspect.isfunction(default_value): default_string = _("(dynamic)") elif isinstance(obj, TyperOption) and obj.is_bool_flag and obj.secondary_opts: # For boolean flags that have distinct True/False opts, @@ -136,11 +122,11 @@ def _get_default_string( # )[1] if obj.default: if obj.opts: - default_string = click.parser.split_opt(obj.opts[0])[1] + default_string = _split_opt(obj.opts[0])[1] else: default_string = str(default_value) else: - default_string = click.parser.split_opt(obj.secondary_opts[0])[1] + default_string = _split_opt(obj.secondary_opts[0])[1] # Typer override end elif ( isinstance(obj, TyperOption) @@ -166,13 +152,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 +181,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 +282,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 +378,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 +419,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 +469,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 +553,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 diff --git a/typer/rich_utils.py b/typer/rich_utils.py index 51da5ca6ea..ff91e51662 100644 --- a/typer/rich_utils.py +++ b/typer/rich_utils.py @@ -383,19 +383,15 @@ def _print_options_panel( # Range - from # https://github.com/pallets/click/blob/c63c70dabd3f86ca68678b4f00951f78f52d0270/src/click/core.py#L2698-L2706 # noqa: E501 - try: - # skip count with default range type - if ( - isinstance(param.type, click.types._NumberRangeBase) - and isinstance(param, click.Option) - and not (param.count and param.type.min == 0 and param.type.max is None) - ): - range_str = param.type._describe_range() - if range_str: - metavar.append(RANGE_STRING.format(range_str)) - except AttributeError: # pragma: no cover - # click.types._NumberRangeBase is only in Click 8x onwards - pass + # skip count with default range type + if ( + isinstance(param.type, click.types._NumberRangeBase) + and isinstance(param, click.Option) + and not (param.count and param.type.min == 0 and param.type.max is None) + ): + range_str = param.type._describe_range() + if range_str: + metavar.append(RANGE_STRING.format(range_str)) # Required asterisk required: Union[str, Text] = "" @@ -621,7 +617,7 @@ def rich_format_help( console=console, ) - if isinstance(obj, click.MultiCommand): + if isinstance(obj, click.Group): panel_to_commands: DefaultDict[str, List[click.Command]] = defaultdict(list) for command_name in obj.list_commands(ctx): command = obj.get_command(ctx, command_name)