Skip to content
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

✨ Add support for Click 8 while keeping compatibility with Click 7 #317

Merged
merged 13 commits into from Aug 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/test.yml
Expand Up @@ -11,18 +11,25 @@ jobs:
strategy:
matrix:
python-version: [3.6, 3.7, 3.8]
click-7: [true, false]
fail-fast: false

steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v1
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install Flit
run: pip install flit
- name: Install Dependencies
run: flit install --deps=develop --symlink
- name: Install Click 7
if: matrix.click-7
run: pip install "click<8.0.0"
- name: Lint
if: ${{ matrix.python-version != '3.6' && matrix.click-7 == false }}
run: bash scripts/lint.sh
- name: Test
run: bash scripts/test.sh
- name: Upload coverage
Expand Down
12 changes: 1 addition & 11 deletions docs/tutorial/using-click.md
Expand Up @@ -99,17 +99,7 @@ $ python main.py
// Notice we have both subcommands, top and hello
Usage: main.py [OPTIONS] COMMAND [ARGS]...

Typer app, including Click subapp

Options:
--install-completion Install completion for the current shell.
--show-completion Show completion for the current shell, to copy it or customize the installation.

--help Show this message and exit.

Commands:
hello Simple program that greets NAME for a total of COUNT times.
top Top level command, form Typer
Error: Missing command.

// Call the Typer part
$ python main.py top
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Expand Up @@ -27,7 +27,7 @@ classifiers = [
"License :: OSI Approved :: MIT License"
]
requires = [
"click >= 7.1.1, <7.2.0"
"click >= 7.1.1, <9.0.0"
]
description-file = "README.md"
requires-python = ">=3.6"
Expand All @@ -43,7 +43,7 @@ test = [
"coverage >=5.2,<6.0",
"pytest-xdist >=1.32.0,<2.0.0",
"pytest-sugar >=0.9.4,<0.10.0",
"mypy ==0.782",
"mypy ==0.910",
"black >=19.10b0,<20.0b0",
"isort >=5.0.6,<6.0.0"
]
Expand Down
1 change: 1 addition & 0 deletions scripts/get-pwsh-activate.sh
@@ -0,0 +1 @@
curl https://raw.githubusercontent.com/python/cpython/main/Lib/venv/scripts/common/Activate.ps1 -o Activate.ps1
1 change: 0 additions & 1 deletion scripts/test.sh
Expand Up @@ -4,6 +4,5 @@ set -e
set -x

bash ./scripts/test-files.sh
bash ./scripts/lint.sh
# Use xdist-pytest --forked to ensure modified sys.path to import relative modules in examples keeps working
pytest --cov=typer --cov=tests --cov=docs_src --cov-report=term-missing --cov-report=xml -o console_output_style=progress --forked --numprocesses=auto ${@}
Empty file added tests/assets/__init__.py
Empty file.
29 changes: 29 additions & 0 deletions tests/assets/compat_click7_8.py
@@ -0,0 +1,29 @@
from typing import List

import click
import typer

app = typer.Typer()


def shell_complete(
ctx: click.Context, param: click.Parameter, incomplete: str
) -> List[str]:
return ["Jonny"]


@app.command(context_settings={"auto_envvar_prefix": "TEST"})
def main(
name: str = typer.Option("John", hidden=True),
lastname: str = typer.Option("Doe", "/lastname", show_default="Mr. Doe"),
age: int = typer.Option(lambda: 42, show_default=True),
nickname: str = typer.Option("", shell_complete=shell_complete),
):
"""
Say hello.
"""
typer.echo(f"Hello {name} {lastname}, it seems you have {age}, {nickname}")


if __name__ == "__main__":
app()
Empty file added tests/test_compat/__init__.py
Empty file.
41 changes: 41 additions & 0 deletions tests/test_compat/test_option_get_help.py
@@ -0,0 +1,41 @@
import os
import subprocess

from typer.testing import CliRunner

from tests.assets import compat_click7_8 as mod

runner = CliRunner()


def test_hidden_option():
result = runner.invoke(mod.app, ["--help"])
assert result.exit_code == 0
assert "Say hello" in result.output
assert "--name" not in result.output
assert "/lastname" in result.output
assert "TEST_LASTNAME" in result.output
assert "(dynamic)" in result.output


def test_coverage_call():
result = runner.invoke(mod.app)
assert result.exit_code == 0
assert "Hello John Doe, it seems you have 42" in result.output


def test_completion():
result = subprocess.run(
["coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
env={
**os.environ,
"_COMPAT_CLICK7_8.PY_COMPLETE": "complete_zsh",
"_TYPER_COMPLETE_ARGS": "compat_click7_8.py --nickname ",
"_TYPER_COMPLETE_TESTING": "True",
},
)
# TODO: when deprecating Click 7, remove second option
assert "Jonny" in result.stdout or "_files" in result.stdout
2 changes: 1 addition & 1 deletion tests/test_completion/test_completion.py
Expand Up @@ -106,7 +106,7 @@ def test_completion_source_invalid_instruction():
"_TYPER_COMPLETE_TESTING": "True",
},
)
assert "Hello World" in result.stdout
assert 'Completion instruction "explode" not supported.' in result.stderr


def test_completion_source_zsh():
Expand Down
6 changes: 5 additions & 1 deletion tests/test_completion/test_completion_install.py
Expand Up @@ -26,7 +26,11 @@ def test_completion_install_no_shell():
"_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION": "True",
},
)
assert "Error: --install-completion option requires an argument" in result.stderr
# TODO: when deprecating Click 7, remove second option
assert (
"Error: Option '--install-completion' requires an argument" in result.stderr
or "Error: --install-completion option requires an argument" in result.stderr
)


def test_completion_install_bash():
Expand Down
6 changes: 5 additions & 1 deletion tests/test_completion/test_completion_show.py
Expand Up @@ -16,7 +16,11 @@ def test_completion_show_no_shell():
"_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION": "True",
},
)
assert "Error: --show-completion option requires an argument" in result.stderr
# TODO: when deprecating Click 7, remove second option
assert (
"Error: Option '--show-completion' requires an argument" in result.stderr
or "Error: --show-completion option requires an argument" in result.stderr
)


def test_completion_show_bash():
Expand Down
20 changes: 15 additions & 5 deletions tests/test_others.py
Expand Up @@ -141,11 +141,14 @@ def test_completion_untyped_parameters():
},
)
assert "info name is: completion_no_types.py" in result.stderr
assert "args is: ['--name', 'Sebastian', '--name']" 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 "incomplete is: Ca" in result.stderr
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

result = subprocess.run(
["coverage", "run", str(file_path)],
Expand All @@ -171,11 +174,14 @@ def test_completion_untyped_parameters_different_order_correct_names():
},
)
assert "info name is: completion_no_types_order.py" in result.stderr
assert "args is: ['--name', 'Sebastian', '--name']" 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 "incomplete is: Ca" in result.stderr
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

result = subprocess.run(
["coverage", "run", str(file_path)],
Expand Down Expand Up @@ -213,8 +219,12 @@ def main(arg1, arg2: int, arg3: "int", arg4: bool = False, arg5: "bool" = False)
typer.echo(f"arg5: {type(arg5)} {arg5}")

result = runner.invoke(app, ["Hello", "2", "invalid"])
# TODO: when deprecating Click 7, remove second option

assert (
"Error: Invalid value for 'ARG3': invalid is not a valid integer"
"Error: Invalid value for 'ARG3': 'invalid' is not a valid integer"
in result.stdout
or "Error: Invalid value for 'ARG3': invalid is not a valid integer"
in result.stdout
)
result = runner.invoke(app, ["Hello", "2", "3", "--arg4", "--arg5"])
Expand Down
Expand Up @@ -49,7 +49,11 @@ def test_delete_verbose():
def test_wrong_verbose():
result = runner.invoke(app, ["delete", "--verbose", "Camila"])
assert result.exit_code != 0
assert "Error: no such option: --verbose" in result.output
# TODO: when deprecating Click 7, remove second option
assert (
"Error: No such option: --verbose" in result.output
or "Error: no such option: --verbose" in result.output
)


def test_script():
Expand Down
24 changes: 20 additions & 4 deletions tests/test_tutorial/test_commands/test_help/test_tutorial001.py
Expand Up @@ -66,28 +66,44 @@ def test_create():
def test_delete():
result = runner.invoke(app, ["delete", "Camila"], input="y\n")
assert result.exit_code == 0
assert "Are you sure you want to delete the user? [y/N]:" in result.output
# 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 "Deleting user: Camila" in result.output


def test_no_delete():
result = runner.invoke(app, ["delete", "Camila"], input="n\n")
assert result.exit_code == 0
assert "Are you sure you want to delete the user? [y/N]:" in result.output
# 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 "Operation cancelled" in result.output


def test_delete_all():
result = runner.invoke(app, ["delete-all"], input="y\n")
assert result.exit_code == 0
assert "Are you sure you want to delete ALL users? [y/N]:" in result.output
# 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 "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
assert "Are you sure you want to delete ALL users? [y/N]:" in result.output
# 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 "Operation cancelled" in result.output


Expand Down
30 changes: 25 additions & 5 deletions tests/test_tutorial/test_commands/test_options/test_tutorial001.py
Expand Up @@ -28,35 +28,55 @@ def test_create():
def test_delete():
result = runner.invoke(app, ["delete", "Camila"], input="y\n")
assert result.exit_code == 0
assert "Are you sure you want to delete the user? [y/N]:" in result.output
# 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 "Deleting user: Camila" in result.output


def test_no_delete():
result = runner.invoke(app, ["delete", "Camila"], input="n\n")
assert result.exit_code == 0
assert "Are you sure you want to delete the user? [y/N]:" in result.output
# 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 "Operation cancelled" in result.output


def test_delete_all():
result = runner.invoke(app, ["delete-all"], input="y\n")
assert result.exit_code == 0
assert "Are you sure you want to delete ALL users? [y/N]:" in result.output
# 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 "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
assert "Are you sure you want to delete ALL users? [y/N]:" in result.output
# 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 "Operation cancelled" in result.output


def test_delete_all_force():
result = runner.invoke(app, ["delete-all", "--force"])
assert result.exit_code == 0
assert "Are you sure you want to delete ALL users? [y/N]:" not in result.output
# 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 "Deleting all users" in result.output


Expand Down
Expand Up @@ -29,7 +29,12 @@ def test_defaults():
def test_invalid_args():
result = runner.invoke(app, ["Draco", "Hagrid"])
assert result.exit_code != 0
assert "Error: argument names takes 3 values" in result.stdout
# TODO: when deprecating Click 7, remove second option

assert (
"Error: Argument 'names' takes 3 values" in result.stdout
or "Error: argument names takes 3 values" in result.stdout
)


def test_valid_args():
Expand Down
Expand Up @@ -34,7 +34,12 @@ def test_user_2():
def test_invalid_user():
result = runner.invoke(app, ["--user", "Camila", "50"])
assert result.exit_code != 0
assert "Error: --user option requires 3 arguments" in result.output
# TODO: when deprecating Click 7, remove second option

assert (
"Error: Option '--user' requires 3 arguments" in result.output
or "Error: --user option requires 3 arguments" in result.output
)


def test_script():
Expand Down
Expand Up @@ -28,7 +28,8 @@ 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
assert "['--name']" in result.stderr
# TODO: when deprecating Click 7, remove second option
assert "[]" in result.stderr or "['--name']" in result.stderr


def test_1():
Expand Down
Expand Up @@ -28,7 +28,8 @@ 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
assert "['--name', 'Sebastian', '--name']" in result.stderr
# TODO: when deprecating Click 7, remove second option
assert "[]" in result.stderr or "['--name', 'Sebastian', '--name']" in result.stderr


def test_1():
Expand Down