From ca532f6f35d65bae0656c4c1f033b0bec0c002b0 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 25 Dec 2025 20:25:13 +0100 Subject: [PATCH 01/31] Add tests for `docs_src/app_dir/tutorial001_py39.py` --- pyproject.toml | 1 - tests/test_tutorial/test_app_dir/__init__.py | 0 .../test_app_dir/test_tutorial001.py | 45 +++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_app_dir/__init__.py create mode 100644 tests/test_tutorial/test_app_dir/test_tutorial001.py diff --git a/pyproject.toml b/pyproject.toml index eb13579db6..a4518c1bc9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/app_dir/tutorial001_py39.py", # ToDo: add test "docs_src/arguments/optional/tutorial000_py39.py", # ToDo: add test "docs_src/launch/tutorial001_py39.py", # ToDo: add test "docs_src/launch/tutorial002_py39.py", # ToDo: add test diff --git a/tests/test_tutorial/test_app_dir/__init__.py b/tests/test_tutorial/test_app_dir/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_tutorial/test_app_dir/test_tutorial001.py b/tests/test_tutorial/test_app_dir/test_tutorial001.py new file mode 100644 index 0000000000..25a40e5fdb --- /dev/null +++ b/tests/test_tutorial/test_app_dir/test_tutorial001.py @@ -0,0 +1,45 @@ +import subprocess +import sys +from pathlib import Path + +import pytest +import typer +from typer.testing import CliRunner + +from docs_src.app_dir import tutorial001_py39 as mod + +runner = CliRunner() + + +@pytest.fixture(name="config_file") +def create_config_file(): + app_dir = Path(typer.get_app_dir("my-super-cli-app")) + app_dir.mkdir(parents=True, exist_ok=True) + config_path = app_dir / "config.json" + config_path.touch(exist_ok=True) + + yield config_path + + config_path.unlink() + app_dir.rmdir() + + +def test_cli_config_doesnt_exist(): + result = runner.invoke(mod.app) + assert result.exit_code == 0 + assert "Config file doesn't exist yet" in result.output + + +def test_cli_config_exists(config_file: Path): + result = runner.invoke(mod.app) + assert result.exit_code == 0 + assert "Config file doesn't exist yet" not in result.output + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From 63fb62b6e767a1cc67bb92196e9cab40521a6479 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 25 Dec 2025 20:32:29 +0100 Subject: [PATCH 02/31] Add tests for `docs_src/arguments/optional/tutorial000_py39.py` --- docs/tutorial/arguments/optional.md | 2 +- .../arguments/optional/tutorial000_an_py39.py | 11 ++++ pyproject.toml | 1 - .../test_optional/test_tutorial000.py | 51 +++++++++++++++++++ 4 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 docs_src/arguments/optional/tutorial000_an_py39.py create mode 100644 tests/test_tutorial/test_arguments/test_optional/test_tutorial000.py diff --git a/docs/tutorial/arguments/optional.md b/docs/tutorial/arguments/optional.md index ec13be1edb..da80e1f5be 100644 --- a/docs/tutorial/arguments/optional.md +++ b/docs/tutorial/arguments/optional.md @@ -39,7 +39,7 @@ In the [First Steps](../first-steps.md#add-a-cli-argument){.internal-link target Now let's see an alternative way to create the same *CLI argument*: -{* docs_src/arguments/optional/tutorial000_py39.py hl[4] *} +{* docs_src/arguments/optional/tutorial000_an_py39.py hl[6] *} Or, using an explicit `Typer()` instance creation: diff --git a/docs_src/arguments/optional/tutorial000_an_py39.py b/docs_src/arguments/optional/tutorial000_an_py39.py new file mode 100644 index 0000000000..65904062cd --- /dev/null +++ b/docs_src/arguments/optional/tutorial000_an_py39.py @@ -0,0 +1,11 @@ +from typing import Annotated + +import typer + + +def main(name: Annotated[str, typer.Argument()]): + print(f"Hello {name}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/pyproject.toml b/pyproject.toml index a4518c1bc9..93af2c5ecf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/arguments/optional/tutorial000_py39.py", # ToDo: add test "docs_src/launch/tutorial001_py39.py", # ToDo: add test "docs_src/launch/tutorial002_py39.py", # ToDo: add test "docs_src/options/callback/tutorial002_*.py", # ToDo: add test diff --git a/tests/test_tutorial/test_arguments/test_optional/test_tutorial000.py b/tests/test_tutorial/test_arguments/test_optional/test_tutorial000.py new file mode 100644 index 0000000000..bbe1ddc646 --- /dev/null +++ b/tests/test_tutorial/test_arguments/test_optional/test_tutorial000.py @@ -0,0 +1,51 @@ +import importlib +import subprocess +import sys +from types import ModuleType + +import pytest +import typer +from typer.testing import CliRunner + +runner = CliRunner() + + +@pytest.fixture( + name="mod", + params=[ + pytest.param("tutorial000_py39"), + pytest.param("tutorial000_an_py39"), + ], +) +def get_mod(request: pytest.FixtureRequest) -> ModuleType: + module_name = f"docs_src.arguments.optional.{request.param}" + mod = importlib.import_module(module_name) + return mod + + +@pytest.fixture(name="app") +def get_app(mod: ModuleType) -> typer.Typer: + app = typer.Typer() + app.command()(mod.main) + return app + + +def test_cli(app: typer.Typer): + result = runner.invoke(app, ["World"]) + assert result.exit_code == 0 + assert "Hello World" in result.output + + +def test_cli_missing_argument(app: typer.Typer): + result = runner.invoke(app) + assert result.exit_code == 2 + assert "Missing argument 'NAME'" in result.output + + +def test_script(mod: ModuleType): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From bc5c388e86cf1e8076b7e511c526d4657a6e586f Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 25 Dec 2025 20:52:48 +0100 Subject: [PATCH 03/31] Add tests for `docs_src/launch/tutorial001_py39.py` --- pyproject.toml | 1 - .../test_launch/test_tutorial001.py | 27 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_launch/test_tutorial001.py diff --git a/pyproject.toml b/pyproject.toml index 93af2c5ecf..de49c7cf49 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/launch/tutorial001_py39.py", # ToDo: add test "docs_src/launch/tutorial002_py39.py", # ToDo: add test "docs_src/options/callback/tutorial002_*.py", # ToDo: add test "docs_src/options/help/tutorial004_*.py", # ToDo: add test diff --git a/tests/test_tutorial/test_launch/test_tutorial001.py b/tests/test_tutorial/test_launch/test_tutorial001.py new file mode 100644 index 0000000000..570627387a --- /dev/null +++ b/tests/test_tutorial/test_launch/test_tutorial001.py @@ -0,0 +1,27 @@ +import subprocess +import sys +from unittest.mock import patch + +from typer.testing import CliRunner + +from docs_src.launch import tutorial001_py39 as mod + +runner = CliRunner() + + +def test_cli(): + with patch("typer.launch") as launch_mock: + result = runner.invoke(mod.app) + + assert result.exit_code == 0 + assert result.output.strip() == "Opening Typer's docs" + launch_mock.assert_called_once_with("https://typer.tiangolo.com") + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From 099e7063defe5b45ec18a28520ae4603fabba1fd Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 25 Dec 2025 21:02:31 +0100 Subject: [PATCH 04/31] Add tests for `docs_src/launch/tutorial002_py39.py` --- pyproject.toml | 1 - .../test_launch/test_tutorial002.py | 47 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_launch/test_tutorial002.py diff --git a/pyproject.toml b/pyproject.toml index de49c7cf49..084f1a9732 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/launch/tutorial002_py39.py", # ToDo: add test "docs_src/options/callback/tutorial002_*.py", # ToDo: add test "docs_src/options/help/tutorial004_*.py", # ToDo: add test "docs_src/options/password/tutorial001_*.py", # ToDo: add test diff --git a/tests/test_tutorial/test_launch/test_tutorial002.py b/tests/test_tutorial/test_launch/test_tutorial002.py new file mode 100644 index 0000000000..42a2dae6c7 --- /dev/null +++ b/tests/test_tutorial/test_launch/test_tutorial002.py @@ -0,0 +1,47 @@ +import subprocess +import sys +from pathlib import Path +from unittest.mock import patch + +import pytest +import typer +from typer.testing import CliRunner + +from docs_src.launch import tutorial002_py39 as mod + +runner = CliRunner() + + +@pytest.fixture(name="app_dir") +def app_dir(): + app_dir = Path(typer.get_app_dir("my-super-cli-app")) + if app_dir.exists(): # pragma: no cover + for item in app_dir.iterdir(): + if item.is_file(): + item.unlink() + + yield app_dir + + if app_dir.exists(): + for item in app_dir.iterdir(): + if item.is_file(): + item.unlink() + app_dir.rmdir() + + +def test_cli(app_dir: Path): + with patch("typer.launch") as launch_mock: + result = runner.invoke(mod.app) + + assert result.exit_code == 0 + assert "Opening config directory" in result.output + launch_mock.assert_called_with(str(app_dir / "config.json"), locate=True) + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From 05d4dc191ceb89ec275f5909b6350965cbc509fb Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 25 Dec 2025 21:14:19 +0100 Subject: [PATCH 05/31] Add tests for `docs_src/options/callback/tutorial002_*.py` --- pyproject.toml | 1 - .../test_callback/test_tutorial002.py | 50 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_options/test_callback/test_tutorial002.py diff --git a/pyproject.toml b/pyproject.toml index 084f1a9732..b578133a93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/options/callback/tutorial002_*.py", # ToDo: add test "docs_src/options/help/tutorial004_*.py", # ToDo: add test "docs_src/options/password/tutorial001_*.py", # ToDo: add test "docs_src/options/password/tutorial002_*.py", # ToDo: add test diff --git a/tests/test_tutorial/test_options/test_callback/test_tutorial002.py b/tests/test_tutorial/test_options/test_callback/test_tutorial002.py new file mode 100644 index 0000000000..f06d7b668c --- /dev/null +++ b/tests/test_tutorial/test_options/test_callback/test_tutorial002.py @@ -0,0 +1,50 @@ +import importlib +import subprocess +import sys +from types import ModuleType + +import pytest +from typer.testing import CliRunner + +from ....utils import needs_py310 + +runner = CliRunner() + + +@pytest.fixture( + name="mod", + params=[ + pytest.param("tutorial002_py39"), + pytest.param("tutorial002_py310", marks=needs_py310), + pytest.param("tutorial002_an_py39"), + pytest.param("tutorial002_an_py310", marks=needs_py310), + ], +) +def get_mod(request: pytest.FixtureRequest) -> ModuleType: + module_name = f"docs_src.options.callback.{request.param}" + mod = importlib.import_module(module_name) + return mod + + +def test_1(mod: ModuleType): + result = runner.invoke(mod.app, ["--name", "Camila"]) + assert result.exit_code == 0 + assert "Validating name" in result.output + assert "Hello Camila" in result.output + + +def test_2(mod: ModuleType): + result = runner.invoke(mod.app, ["--name", "rick"]) + assert result.exit_code != 0 + assert "Validating name" in result.output + assert "Invalid value for '--name'" in result.output + assert "Only Camila is allowed" in result.output + + +def test_script(mod: ModuleType): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From 7f66abfd8889772f2dfc3bcc98b63369b6f1a19b Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 25 Dec 2025 21:14:47 +0100 Subject: [PATCH 06/31] Add tests for `docs_src/options/help/tutorial004_*.py` --- pyproject.toml | 1 - .../test_help/test_tutorial004.py | 45 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_options/test_help/test_tutorial004.py diff --git a/pyproject.toml b/pyproject.toml index b578133a93..5880056ca9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/options/help/tutorial004_*.py", # ToDo: add test "docs_src/options/password/tutorial001_*.py", # ToDo: add test "docs_src/options/password/tutorial002_*.py", # ToDo: add test "docs_src/options/version/tutorial001_*.py", # ToDo: add test diff --git a/tests/test_tutorial/test_options/test_help/test_tutorial004.py b/tests/test_tutorial/test_options/test_help/test_tutorial004.py new file mode 100644 index 0000000000..74d7f3bee3 --- /dev/null +++ b/tests/test_tutorial/test_options/test_help/test_tutorial004.py @@ -0,0 +1,45 @@ +import importlib +import subprocess +import sys +from types import ModuleType + +import pytest +from typer.testing import CliRunner + +runner = CliRunner() + + +@pytest.fixture( + name="mod", + params=[ + pytest.param("tutorial004_py39"), + pytest.param("tutorial004_an_py39"), + ], +) +def get_mod(request: pytest.FixtureRequest) -> ModuleType: + module_name = f"docs_src.options.help.{request.param}" + mod = importlib.import_module(module_name) + return mod + + +def test_call(mod: ModuleType): + result = runner.invoke(mod.app) + assert result.exit_code == 0 + assert "Hello Wade Wilson" in result.output + + +def test_help(mod: ModuleType): + result = runner.invoke(mod.app, ["--help"]) + assert result.exit_code == 0 + assert "--fullname" in result.output + assert "TEXT" in result.output + assert "[default: (Deadpoolio the amazing's name)]" in result.output + + +def test_script(mod: ModuleType): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From c8056950efbf9c2fa24dfea0bf08588cb28b0fec Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 25 Dec 2025 21:32:17 +0100 Subject: [PATCH 07/31] Add `strip_double_spaces` function to `utils` --- tests/utils.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/utils.py b/tests/utils.py index ee190d8438..2cb8d71f3e 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,3 +1,4 @@ +import re import sys from os import getenv @@ -21,3 +22,7 @@ not getenv("_TYPER_RUN_INSTALL_COMPLETION_TESTS", False), reason="Test requires permission to run completion installation tests", ) + + +def strip_double_spaces(text: str) -> str: + return re.sub(r" {2,}", " ", text) From 7b2a61a1acb07241bf9b1b1d5eabbd59c40fa14a Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 25 Dec 2025 21:32:35 +0100 Subject: [PATCH 08/31] Add tests for `docs_src/options/password/tutorial001_*.py` --- pyproject.toml | 1 - .../test_options/test_password/__init__.py | 0 .../test_password/test_tutorial001.py | 56 +++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_options/test_password/__init__.py create mode 100644 tests/test_tutorial/test_options/test_password/test_tutorial001.py diff --git a/pyproject.toml b/pyproject.toml index 5880056ca9..e2ab9d2fdc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/options/password/tutorial001_*.py", # ToDo: add test "docs_src/options/password/tutorial002_*.py", # ToDo: add test "docs_src/options/version/tutorial001_*.py", # ToDo: add test "docs_src/options/version/tutorial002_*.py", # ToDo: add test diff --git a/tests/test_tutorial/test_options/test_password/__init__.py b/tests/test_tutorial/test_options/test_password/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_tutorial/test_options/test_password/test_tutorial001.py b/tests/test_tutorial/test_options/test_password/test_tutorial001.py new file mode 100644 index 0000000000..4f0a79b8b4 --- /dev/null +++ b/tests/test_tutorial/test_options/test_password/test_tutorial001.py @@ -0,0 +1,56 @@ +import importlib +import subprocess +import sys +from types import ModuleType + +import pytest +from typer.testing import CliRunner + +from tests.utils import strip_double_spaces + +runner = CliRunner() + + +@pytest.fixture( + name="mod", + params=[ + pytest.param("tutorial001_py39"), + pytest.param("tutorial001_an_py39"), + ], +) +def get_mod(request: pytest.FixtureRequest) -> ModuleType: + module_name = f"docs_src.options.password.{request.param}" + mod = importlib.import_module(module_name) + return mod + + +def test_option_email(mod: ModuleType): + result = runner.invoke(mod.app, ["Camila", "--email", "camila@example.com"]) + assert result.exit_code == 0 + assert "Hello Camila, your email is camila@example.com" in result.output + + +def test_option_email_prompt(mod: ModuleType): + result = runner.invoke( + mod.app, ["Camila"], input="camila@example.com\ncamila@example.com\n" + ) + assert result.exit_code == 0 + assert "Email: camila@example.com" in result.output + assert "Repeat for confirmation: camila@example.com" in result.output + assert "Hello Camila, your email is camila@example.com" in result.output + + +def test_help(mod: ModuleType): + result = runner.invoke(mod.app, ["--help"]) + assert result.exit_code == 0 + output_without_double_spaces = strip_double_spaces(result.output) + assert "--email TEXT [required]" in output_without_double_spaces + + +def test_script(mod: ModuleType): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From b2dba5d5561b02eb554c4c7f7c38a94e5cae13e4 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 25 Dec 2025 21:40:52 +0100 Subject: [PATCH 09/31] Add tests for `docs_src/options/password/tutorial002_*.py` --- pyproject.toml | 1 - .../test_password/test_tutorial002.py | 60 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_options/test_password/test_tutorial002.py diff --git a/pyproject.toml b/pyproject.toml index e2ab9d2fdc..0f99b0fd24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/options/password/tutorial002_*.py", # ToDo: add test "docs_src/options/version/tutorial001_*.py", # ToDo: add test "docs_src/options/version/tutorial002_*.py", # ToDo: add test "docs_src/options_autocompletion/tutorial001_*.py", # ToDo: add test diff --git a/tests/test_tutorial/test_options/test_password/test_tutorial002.py b/tests/test_tutorial/test_options/test_password/test_tutorial002.py new file mode 100644 index 0000000000..ce67001a67 --- /dev/null +++ b/tests/test_tutorial/test_options/test_password/test_tutorial002.py @@ -0,0 +1,60 @@ +import importlib +import subprocess +import sys +from types import ModuleType + +import pytest +from typer.testing import CliRunner + +from tests.utils import strip_double_spaces + +runner = CliRunner() + + +@pytest.fixture( + name="mod", + params=[ + pytest.param("tutorial002_py39"), + pytest.param("tutorial002_an_py39"), + ], +) +def get_mod(request: pytest.FixtureRequest) -> ModuleType: + module_name = f"docs_src.options.password.{request.param}" + mod = importlib.import_module(module_name) + return mod + + +def test_option_password(mod: ModuleType): + result = runner.invoke(mod.app, ["Camila", "--password", "secretpassword"]) + assert result.exit_code == 0 + assert "Hello Camila. Doing something very secure with password." in result.output + assert "...just kidding, here it is, very insecure: secretpassword" in result.output + + +def test_option_password_prompt(mod: ModuleType): + result = runner.invoke( + mod.app, ["Camila"], input="secretpassword\nsecretpassword\n" + ) + assert result.exit_code == 0 + assert "Password: " in result.output + assert "Password: secretpassword" not in result.output + assert "Repeat for confirmation: " in result.output + assert "Repeat for confirmation: secretpassword" not in result.output + assert "Hello Camila. Doing something very secure with password." in result.output + assert "...just kidding, here it is, very insecure: secretpassword" in result.output + + +def test_help(mod: ModuleType): + result = runner.invoke(mod.app, ["--help"]) + assert result.exit_code == 0 + output_without_double_spaces = strip_double_spaces(result.output) + assert "--password TEXT [required]" in output_without_double_spaces + + +def test_script(mod: ModuleType): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From 1f65c4eb95677f209e376de20f3d331a857e5d88 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 25 Dec 2025 22:01:54 +0100 Subject: [PATCH 10/31] Add tests for `docs_src/options/version/tutorial001_*.py` --- pyproject.toml | 1 - .../test_version/test_tutorial001.py | 72 +++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_options/test_version/test_tutorial001.py diff --git a/pyproject.toml b/pyproject.toml index 0f99b0fd24..43e657738a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/options/version/tutorial001_*.py", # ToDo: add test "docs_src/options/version/tutorial002_*.py", # ToDo: add test "docs_src/options_autocompletion/tutorial001_*.py", # ToDo: add test "docs_src/options_autocompletion/tutorial005_*.py", # ToDo: add test diff --git a/tests/test_tutorial/test_options/test_version/test_tutorial001.py b/tests/test_tutorial/test_options/test_version/test_tutorial001.py new file mode 100644 index 0000000000..88ab0c7e0f --- /dev/null +++ b/tests/test_tutorial/test_options/test_version/test_tutorial001.py @@ -0,0 +1,72 @@ +import importlib +import os +import subprocess +import sys +from pathlib import Path +from types import ModuleType + +import pytest +from typer.testing import CliRunner + +from ....utils import needs_py310 + +runner = CliRunner() + + +@pytest.fixture( + name="mod", + params=[ + pytest.param("tutorial001_py39"), + pytest.param("tutorial001_py310", marks=needs_py310), + pytest.param("tutorial001_an_py39"), + pytest.param("tutorial001_an_py310", marks=needs_py310), + ], +) +def get_mod(request: pytest.FixtureRequest) -> ModuleType: + module_name = f"docs_src.options.version.{request.param}" + mod = importlib.import_module(module_name) + return mod + + +def test_version(mod: ModuleType): + result = runner.invoke(mod.app, ["--version"]) + assert result.exit_code == 0 + assert "Awesome CLI Version: 0.1.0" in result.output + + +def test_version_with_name(mod: ModuleType): + result = runner.invoke(mod.app, ["--name", "rick", "--version"]) + assert result.exit_code == 0 + assert "Awesome CLI Version: 0.1.0" in result.output + assert "Hello" not in result.output + + +def test_no_version(mod: ModuleType): + result = runner.invoke(mod.app, ["--name", "Camila"]) + assert result.exit_code == 0 + assert "Hello Camila" in result.output + + +def test_script(mod: ModuleType): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout + + +def test_completion(mod: ModuleType): + file_name = Path(mod.__file__).name + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + f"_{file_name.upper()}_COMPLETE": "complete_bash", + "COMP_WORDS": f"{file_name} --name Rick --v", + "COMP_CWORD": "3", + }, + ) + assert "--version" in result.stdout From b322b10815c12370bf3905b56393471880203277 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 25 Dec 2025 22:02:16 +0100 Subject: [PATCH 11/31] Add tests for `docs_src/options/version/tutorial002_*.py` --- pyproject.toml | 1 - .../test_version/test_tutorial002.py | 72 +++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_options/test_version/test_tutorial002.py diff --git a/pyproject.toml b/pyproject.toml index 43e657738a..327cdd3ca5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/options/version/tutorial002_*.py", # ToDo: add test "docs_src/options_autocompletion/tutorial001_*.py", # ToDo: add test "docs_src/options_autocompletion/tutorial005_*.py", # ToDo: add test "docs_src/options_autocompletion/tutorial006_*.py", # ToDo: add test diff --git a/tests/test_tutorial/test_options/test_version/test_tutorial002.py b/tests/test_tutorial/test_options/test_version/test_tutorial002.py new file mode 100644 index 0000000000..670e3559a4 --- /dev/null +++ b/tests/test_tutorial/test_options/test_version/test_tutorial002.py @@ -0,0 +1,72 @@ +import importlib +import os +import subprocess +import sys +from pathlib import Path +from types import ModuleType + +import pytest +from typer.testing import CliRunner + +from ....utils import needs_py310 + +runner = CliRunner() + + +@pytest.fixture( + name="mod", + params=[ + pytest.param("tutorial002_py39"), + pytest.param("tutorial002_py310", marks=needs_py310), + pytest.param("tutorial002_an_py39"), + pytest.param("tutorial002_an_py310", marks=needs_py310), + ], +) +def get_mod(request: pytest.FixtureRequest) -> ModuleType: + module_name = f"docs_src.options.version.{request.param}" + mod = importlib.import_module(module_name) + return mod + + +def test_version_camila(mod: ModuleType): + result = runner.invoke(mod.app, ["--name", "Camila", "--version"]) + assert result.exit_code == 0 + assert "Awesome CLI Version: 0.1.0" in result.output + + +def test_version_not_camila(mod: ModuleType): + result = runner.invoke(mod.app, ["--name", "rick", "--version"]) + assert result.exit_code != 0 + assert "Invalid value for '--name'" in result.output + assert "Only Camila is allowed" in result.output + + +def test_version_camila_no_version(mod: ModuleType): + result = runner.invoke(mod.app, ["--name", "Camila"]) + assert result.exit_code == 0 + assert "Hello None" in result.output + + +def test_script(mod: ModuleType): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout + + +def test_completion(mod: ModuleType): + file_name = Path(mod.__file__).name + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + f"_{file_name.upper()}_COMPLETE": "complete_bash", + "COMP_WORDS": f"{file_name} --name Rick --v", + "COMP_CWORD": "3", + }, + ) + assert "--version" in result.stdout From 11aa2f54a57884c3d19cef7260c4ad09e8c296b1 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 25 Dec 2025 22:07:17 +0100 Subject: [PATCH 12/31] Add tests for `docs_src/options_autocompletion/tutorial001_*.py` --- pyproject.toml | 1 - .../test_tutorial001.py | 37 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_options_autocompletion/test_tutorial001.py diff --git a/pyproject.toml b/pyproject.toml index 327cdd3ca5..e59c8d760e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/options_autocompletion/tutorial001_*.py", # ToDo: add test "docs_src/options_autocompletion/tutorial005_*.py", # ToDo: add test "docs_src/options_autocompletion/tutorial006_*.py", # ToDo: add test "docs_src/printing/tutorial001_py39.py", # ToDo: add test diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial001.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial001.py new file mode 100644 index 0000000000..69b141521b --- /dev/null +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial001.py @@ -0,0 +1,37 @@ +import importlib +import subprocess +import sys +from types import ModuleType + +import pytest +from typer.testing import CliRunner + +runner = CliRunner() + + +@pytest.fixture( + name="mod", + params=[ + pytest.param("tutorial001_py39"), + pytest.param("tutorial001_an_py39"), + ], +) +def get_mod(request: pytest.FixtureRequest) -> ModuleType: + module_name = f"docs_src.options_autocompletion.{request.param}" + mod = importlib.import_module(module_name) + return mod + + +def test_1(mod: ModuleType): + result = runner.invoke(mod.app, ["--name", "Camila"]) + assert result.exit_code == 0 + assert "Hello Camila" in result.output + + +def test_script(mod: ModuleType): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From 53df1a99a90ddfefb7472082da7565befb9b01eb Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 25 Dec 2025 22:09:06 +0100 Subject: [PATCH 13/31] Add tests for `docs_src/options_autocompletion/tutorial005_*.py` --- .../{test_tutorial004.py => test_tutorial004_tutorial005.py} | 2 ++ 1 file changed, 2 insertions(+) rename tests/test_tutorial/test_options_autocompletion/{test_tutorial004.py => test_tutorial004_tutorial005.py} (94%) diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial004.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial004_tutorial005.py similarity index 94% rename from tests/test_tutorial/test_options_autocompletion/test_tutorial004.py rename to tests/test_tutorial/test_options_autocompletion/test_tutorial004_tutorial005.py index 25bfa108b3..919b836bbf 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial004.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial004_tutorial005.py @@ -16,6 +16,8 @@ params=[ pytest.param("tutorial004_py39"), pytest.param("tutorial004_an_py39"), + pytest.param("tutorial005_py39"), + pytest.param("tutorial005_an_py39"), ], ) def get_mod(request: pytest.FixtureRequest) -> ModuleType: From c3983eb2613bf540b5c93816aeac425789635990 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 25 Dec 2025 22:13:49 +0100 Subject: [PATCH 14/31] Add tests for `docs_src/options_autocompletion/tutorial006_*.py` --- pyproject.toml | 2 - .../test_tutorial006.py | 44 +++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 tests/test_tutorial/test_options_autocompletion/test_tutorial006.py diff --git a/pyproject.toml b/pyproject.toml index e59c8d760e..d8721e26df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,8 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/options_autocompletion/tutorial005_*.py", # ToDo: add test - "docs_src/options_autocompletion/tutorial006_*.py", # ToDo: add test "docs_src/printing/tutorial001_py39.py", # ToDo: add test "docs_src/printing/tutorial002_py39.py", # ToDo: add test "docs_src/printing/tutorial003_py39.py", # ToDo: add test diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial006.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial006.py new file mode 100644 index 0000000000..4aa21d7f53 --- /dev/null +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial006.py @@ -0,0 +1,44 @@ +import importlib +import subprocess +import sys +from types import ModuleType + +import pytest +from typer.testing import CliRunner + +runner = CliRunner() + + +@pytest.fixture( + name="mod", + params=[ + pytest.param("tutorial006_py39"), + pytest.param("tutorial006_an_py39"), + ], +) +def get_mod(request: pytest.FixtureRequest) -> ModuleType: + module_name = f"docs_src.options_autocompletion.{request.param}" + mod = importlib.import_module(module_name) + return mod + + +def test_1(mod: ModuleType): + result = runner.invoke(mod.app, ["--name", "Camila"]) + assert result.exit_code == 0 + assert "Hello Camila" in result.output + + +def test_2(mod: ModuleType): + result = runner.invoke(mod.app, ["--name", "Camila", "--name", "Sebastian"]) + assert result.exit_code == 0 + assert "Hello Camila" in result.output + assert "Hello Sebastian" in result.output + + +def test_script(mod: ModuleType): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From 6a0091cd61b9028290842baa0557c3098b7e9e6e Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 25 Dec 2025 23:51:25 +0100 Subject: [PATCH 15/31] Add `normalize_rich_output` function to `utils --- tests/utils.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/utils.py b/tests/utils.py index 2cb8d71f3e..786e57014f 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -26,3 +26,18 @@ def strip_double_spaces(text: str) -> str: return re.sub(r" {2,}", " ", text) + + +def normalize_rich_output( + text: str, replace_with: str = "*", squash_whitespaces: bool = True +) -> str: + """ + Replace all rich formatting characters with a simple placeholder. + """ + text = re.sub(r"\x1b\[[0-9;]*m", replace_with, text) + text = re.sub(r"[\u2500-\u257F]", replace_with, text) + text = re.sub(r"[\U0001F300-\U0001F6FF]", replace_with, text) + text = re.sub(f"{re.escape(replace_with)}+", replace_with, text) + if squash_whitespaces: + text = strip_double_spaces(text) + return text From e0f7f9a3fd23046dc4d43a7af79778359f8cc2d3 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 25 Dec 2025 22:22:51 +0100 Subject: [PATCH 16/31] Add tests for `docs_src/printing/tutorial001_py39.py` --- pyproject.toml | 1 - tests/test_tutorial/test_printing/__init__.py | 0 .../test_printing/test_tutorial001.py | 44 +++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_printing/__init__.py create mode 100644 tests/test_tutorial/test_printing/test_tutorial001.py diff --git a/pyproject.toml b/pyproject.toml index d8721e26df..5db30c8a5e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/printing/tutorial001_py39.py", # ToDo: add test "docs_src/printing/tutorial002_py39.py", # ToDo: add test "docs_src/printing/tutorial003_py39.py", # ToDo: add test "docs_src/printing/tutorial004_py39.py", # ToDo: add test diff --git a/tests/test_tutorial/test_printing/__init__.py b/tests/test_tutorial/test_printing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_tutorial/test_printing/test_tutorial001.py b/tests/test_tutorial/test_printing/test_tutorial001.py new file mode 100644 index 0000000000..27ed3b0e79 --- /dev/null +++ b/tests/test_tutorial/test_printing/test_tutorial001.py @@ -0,0 +1,44 @@ +import subprocess +import sys +from unittest.mock import patch + +from rich.console import Console # noqa: TID251 +from typer.testing import CliRunner + +import docs_src.printing.tutorial001_py39 as mod +from tests.utils import normalize_rich_output + +app = mod.app + +runner = CliRunner() + + +def test_cli(): + console = Console(force_terminal=True, width=100) + with patch("rich.get_console", return_value=console): + result = runner.invoke(app) + + assert result.exit_code == 0 + + # Replace all Rich formatting with `*` characters + normalized_output = normalize_rich_output(result.output, squash_whitespaces=False) + + assert ( + "Here's the data\n" + "*{*\n" + " *'name'*: *'Rick'*,\n" + " *'age'*: *42*,\n" + " *'items'*: *[*{*'name'*: *'Portal Gun'*}*, *{*'name'*: *'Plumbus'*}*]*,\n" + " *'active'*: *True*,\n" + " *'affiliation'*: *None*\n" + "*}*\n" + ) in normalized_output + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From 4d4788d72743c79f19268253f5709826533538f3 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 25 Dec 2025 23:02:41 +0100 Subject: [PATCH 17/31] Add tests for `docs_src/printing/tutorial002_py39.py` --- pyproject.toml | 1 - .../test_printing/test_tutorial002.py | 41 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_printing/test_tutorial002.py diff --git a/pyproject.toml b/pyproject.toml index 5db30c8a5e..b6c9f3d677 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/printing/tutorial002_py39.py", # ToDo: add test "docs_src/printing/tutorial003_py39.py", # ToDo: add test "docs_src/printing/tutorial004_py39.py", # ToDo: add test "docs_src/printing/tutorial005_py39.py", # ToDo: add test diff --git a/tests/test_tutorial/test_printing/test_tutorial002.py b/tests/test_tutorial/test_printing/test_tutorial002.py new file mode 100644 index 0000000000..158dea4dc4 --- /dev/null +++ b/tests/test_tutorial/test_printing/test_tutorial002.py @@ -0,0 +1,41 @@ +import subprocess +import sys +from unittest.mock import patch + +from rich.console import Console # noqa: TID251 +from typer.testing import CliRunner + +import docs_src.printing.tutorial002_py39 as mod +from tests.utils import normalize_rich_output + +app = mod.app + +runner = CliRunner() + + +def test_cli(): + console = Console(force_terminal=True, width=100) + with patch("rich.get_console", return_value=console): + result = runner.invoke(app) + + # Replace all Rich formatting with `*` characters + normalized_output = normalize_rich_output(result.output) + + assert result.exit_code == 0 + assert "*Alert!* *Portal gun* shooting! *" in normalized_output + + +def test_cli_without_formatting(): + result = runner.invoke(app) + + assert result.exit_code == 0 + assert "Alert! Portal gun shooting! 💥" in result.output + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From f0bd9da1f405538ea1cfca6899190e8154457e23 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 25 Dec 2025 23:51:37 +0100 Subject: [PATCH 18/31] Add tests for `docs_src/printing/tutorial003_py39.py` --- pyproject.toml | 1 - .../test_printing/test_tutorial003.py | 32 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_printing/test_tutorial003.py diff --git a/pyproject.toml b/pyproject.toml index b6c9f3d677..b1016fab67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/printing/tutorial003_py39.py", # ToDo: add test "docs_src/printing/tutorial004_py39.py", # ToDo: add test "docs_src/printing/tutorial005_py39.py", # ToDo: add test "docs_src/printing/tutorial006_py39.py", # ToDo: add test diff --git a/tests/test_tutorial/test_printing/test_tutorial003.py b/tests/test_tutorial/test_printing/test_tutorial003.py new file mode 100644 index 0000000000..ac4bf0deb6 --- /dev/null +++ b/tests/test_tutorial/test_printing/test_tutorial003.py @@ -0,0 +1,32 @@ +import subprocess +import sys + +from typer.testing import CliRunner + +import docs_src.printing.tutorial003_py39 as mod +from tests.utils import normalize_rich_output + +app = mod.app + +runner = CliRunner() + + +def test_cli(): + result = runner.invoke(app) + + # Replace all Rich formatting with `*` characters + normalized_output = normalize_rich_output(result.output) + + assert result.exit_code == 0 + assert ( + "*\n* Name * Item *\n*\n* Rick * Portal Gun *\n* Morty * Plumbus *\n*\n" + ) in normalized_output + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From 9b5c428bba2baccd35344f19583c9860c9c7fdf4 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Fri, 26 Dec 2025 12:03:23 +0100 Subject: [PATCH 19/31] Add tests for `docs_src/printing/tutorial004_py39.py` --- pyproject.toml | 1 - .../test_printing/test_tutorial004.py | 32 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_printing/test_tutorial004.py diff --git a/pyproject.toml b/pyproject.toml index b1016fab67..68bef4d282 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/printing/tutorial004_py39.py", # ToDo: add test "docs_src/printing/tutorial005_py39.py", # ToDo: add test "docs_src/printing/tutorial006_py39.py", # ToDo: add test "docs_src/progressbar/tutorial001_py39.py", # ToDo: add test diff --git a/tests/test_tutorial/test_printing/test_tutorial004.py b/tests/test_tutorial/test_printing/test_tutorial004.py new file mode 100644 index 0000000000..f8a6cc8d69 --- /dev/null +++ b/tests/test_tutorial/test_printing/test_tutorial004.py @@ -0,0 +1,32 @@ +import platform +import subprocess +import sys + +import pytest +from typer.testing import CliRunner + +import docs_src.printing.tutorial004_py39 as mod + +app = mod.app + +runner = CliRunner() + + +@pytest.mark.xfail( + condition=((platform.system() == "Windows") and (sys.version_info < (3, 10))), + reason="On Windows with Python 3.9, output is in stdout instead of stderr", +) +def test_cli(): + result = runner.invoke(app) + assert result.exit_code == 0 + assert result.stdout == "" + assert "Here is something written to standard error" in result.stderr + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From 1e0278e66b16497607a23bb96d542ce51521a281 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Fri, 26 Dec 2025 16:33:28 +0100 Subject: [PATCH 20/31] Add tests for `docs_src/printing/tutorial005_py39.py` --- pyproject.toml | 1 - .../test_printing/test_tutorial005.py | 42 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_printing/test_tutorial005.py diff --git a/pyproject.toml b/pyproject.toml index 68bef4d282..c53cc604a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/printing/tutorial005_py39.py", # ToDo: add test "docs_src/printing/tutorial006_py39.py", # ToDo: add test "docs_src/progressbar/tutorial001_py39.py", # ToDo: add test "docs_src/progressbar/tutorial002_py39.py", # ToDo: add test diff --git a/tests/test_tutorial/test_printing/test_tutorial005.py b/tests/test_tutorial/test_printing/test_tutorial005.py new file mode 100644 index 0000000000..9335245551 --- /dev/null +++ b/tests/test_tutorial/test_printing/test_tutorial005.py @@ -0,0 +1,42 @@ +import subprocess +import sys + +from typer.testing import CliRunner + +import docs_src.printing.tutorial005_py39 as mod +from tests.utils import normalize_rich_output + +app = mod.app + +runner = CliRunner() + + +def test_good_true(): + result = runner.invoke(app, color=True) + assert result.exit_code == 0 + + # Replace all Rich formatting with `*` characters + normalized_output = normalize_rich_output(result.output) + + assert "everything is *good*" in normalized_output + # We don't check exact colors here, just that text has formatting + + +def test_good_false(): + result = runner.invoke(app, ["--no-good"], color=True) + assert result.exit_code == 0 + + # Replace all Rich formatting with `*` characters + normalized_output = normalize_rich_output(result.output) + + assert "everything is *bad*" in normalized_output + # We don't check exact colors here, just that text has formatting + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From a0f97bf7e356eff155d528b33682a3d8f8147e71 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Fri, 26 Dec 2025 16:37:51 +0100 Subject: [PATCH 21/31] Add tests for `docs_src/printing/tutorial006_py39.py` --- pyproject.toml | 1 - .../test_printing/test_tutorial006.py | 31 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_printing/test_tutorial006.py diff --git a/pyproject.toml b/pyproject.toml index c53cc604a6..123e60afe7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/printing/tutorial006_py39.py", # ToDo: add test "docs_src/progressbar/tutorial001_py39.py", # ToDo: add test "docs_src/progressbar/tutorial002_py39.py", # ToDo: add test "docs_src/progressbar/tutorial003_py39.py", # ToDo: add test diff --git a/tests/test_tutorial/test_printing/test_tutorial006.py b/tests/test_tutorial/test_printing/test_tutorial006.py new file mode 100644 index 0000000000..1e7803d2ef --- /dev/null +++ b/tests/test_tutorial/test_printing/test_tutorial006.py @@ -0,0 +1,31 @@ +import subprocess +import sys + +from typer.testing import CliRunner + +import docs_src.printing.tutorial006_py39 as mod +from tests.utils import normalize_rich_output + +app = mod.app + +runner = CliRunner() + + +def test_cli(): + result = runner.invoke(app, ["everyone"], color=True) + assert result.exit_code == 0 + + # Replace all Rich formatting with `*` characters + normalized_output = normalize_rich_output(result.output) + + assert "*Welcome here everyone*" in normalized_output + # We don't check exact colors here, just that text has formatting + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From eb15c2e2ec0f393217c8f3eac60a4d828d528a60 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Fri, 26 Dec 2025 16:50:42 +0100 Subject: [PATCH 22/31] Add tests for `docs_src/progressbar/tutorial001_py39.py` --- pyproject.toml | 1 - .../test_progressbar/__init__.py | 0 .../test_progressbar/test_tutorial001.py | 48 +++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_progressbar/__init__.py create mode 100644 tests/test_tutorial/test_progressbar/test_tutorial001.py diff --git a/pyproject.toml b/pyproject.toml index 123e60afe7..5824bd81bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/progressbar/tutorial001_py39.py", # ToDo: add test "docs_src/progressbar/tutorial002_py39.py", # ToDo: add test "docs_src/progressbar/tutorial003_py39.py", # ToDo: add test "docs_src/progressbar/tutorial004_py39.py", # ToDo: add test diff --git a/tests/test_tutorial/test_progressbar/__init__.py b/tests/test_tutorial/test_progressbar/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_tutorial/test_progressbar/test_tutorial001.py b/tests/test_tutorial/test_progressbar/test_tutorial001.py new file mode 100644 index 0000000000..c5a3c845e2 --- /dev/null +++ b/tests/test_tutorial/test_progressbar/test_tutorial001.py @@ -0,0 +1,48 @@ +import subprocess +import sys +from unittest.mock import patch + +import typer +from typer.testing import CliRunner + +import docs_src.progressbar.tutorial001_py39 as mod +from tests.utils import normalize_rich_output + +app = mod.app + +runner = CliRunner() + + +def test_cli_one_step(): + with patch("time.sleep") as sleep_mock: + sleep_mock.side_effect = typer.Exit() # Exit on first `time.sleep()` call + result = runner.invoke(app) + + # Replace all Rich formatting with `*` characters + normalized_output = normalize_rich_output(result.output) + + assert result.exit_code == 0 + assert "Processing... 0%" in normalized_output + + +def test_cli(): + with patch("time.sleep") as mock_sleep: + result = runner.invoke(app) + + # Replace all Rich formatting with `*` characters + normalized_output = normalize_rich_output(result.output) + + assert result.exit_code == 0 + assert mock_sleep.call_count == 100 + assert "Processing..." in normalized_output + assert "100%" in normalized_output + assert "Processed 100 things." in normalized_output + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From d66a63173d533fd76d5de1bd3d1bd8afd07a3d8b Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Fri, 26 Dec 2025 17:04:08 +0100 Subject: [PATCH 23/31] Add tests for `docs_src/progressbar/tutorial002_py39.py` --- pyproject.toml | 1 - .../test_progressbar/test_tutorial002.py | 33 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_progressbar/test_tutorial002.py diff --git a/pyproject.toml b/pyproject.toml index 5824bd81bf..7924acdda6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/progressbar/tutorial002_py39.py", # ToDo: add test "docs_src/progressbar/tutorial003_py39.py", # ToDo: add test "docs_src/progressbar/tutorial004_py39.py", # ToDo: add test "docs_src/progressbar/tutorial005_py39.py", # ToDo: add test diff --git a/tests/test_tutorial/test_progressbar/test_tutorial002.py b/tests/test_tutorial/test_progressbar/test_tutorial002.py new file mode 100644 index 0000000000..8d744806ac --- /dev/null +++ b/tests/test_tutorial/test_progressbar/test_tutorial002.py @@ -0,0 +1,33 @@ +import subprocess +import sys +from unittest.mock import patch + +from typer.testing import CliRunner + +import docs_src.progressbar.tutorial002_py39 as mod +from tests.utils import normalize_rich_output + +app = mod.app + +runner = CliRunner() + + +def test_cli(): # Checking only final state of spinner progress bar + with patch("time.sleep") as mock_sleep: + result = runner.invoke(app) + + # Replace all Rich formatting with `*` characters + normalized_output = normalize_rich_output(result.output) + + assert result.exit_code == 0 + mock_sleep.assert_called_once_with(5) + assert "Done!" in normalized_output + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From c850ddba70521ee8e1f644797a96784c533d9cd6 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Fri, 26 Dec 2025 17:10:50 +0100 Subject: [PATCH 24/31] Add tests for `docs_src/progressbar/tutorial003_py39.py` --- pyproject.toml | 1 - .../test_progressbar/test_tutorial003.py | 33 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_progressbar/test_tutorial003.py diff --git a/pyproject.toml b/pyproject.toml index 7924acdda6..f0f81b4bb2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/progressbar/tutorial003_py39.py", # ToDo: add test "docs_src/progressbar/tutorial004_py39.py", # ToDo: add test "docs_src/progressbar/tutorial005_py39.py", # ToDo: add test "docs_src/progressbar/tutorial006_py39.py", # ToDo: add test diff --git a/tests/test_tutorial/test_progressbar/test_tutorial003.py b/tests/test_tutorial/test_progressbar/test_tutorial003.py new file mode 100644 index 0000000000..545fb9bfc3 --- /dev/null +++ b/tests/test_tutorial/test_progressbar/test_tutorial003.py @@ -0,0 +1,33 @@ +import subprocess +import sys +from unittest.mock import patch + +from typer.testing import CliRunner + +import docs_src.progressbar.tutorial003_py39 as mod +from tests.utils import normalize_rich_output + +app = mod.app + +runner = CliRunner() + + +def test_cli(): # Checking only final state of progress bar + with patch("time.sleep") as mock_sleep: + result = runner.invoke(app) + + # Replace all Rich formatting with `*` characters + normalized_output = normalize_rich_output(result.output) + + assert result.exit_code == 0 + assert mock_sleep.call_count == 100 + assert "Processed 100 things." in normalized_output + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From 65554ba0f1127553a3e134cbb7585e5f656621cc Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Fri, 26 Dec 2025 17:30:02 +0100 Subject: [PATCH 25/31] Add tests for `docs_src/progressbar/tutorial004_py39.py` --- pyproject.toml | 1 - .../test_progressbar/test_tutorial004.py | 61 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_progressbar/test_tutorial004.py diff --git a/pyproject.toml b/pyproject.toml index f0f81b4bb2..bf2bafd1df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/progressbar/tutorial004_py39.py", # ToDo: add test "docs_src/progressbar/tutorial005_py39.py", # ToDo: add test "docs_src/progressbar/tutorial006_py39.py", # ToDo: add test "docs_src/prompt/tutorial004_py39.py", # ToDo: add test diff --git a/tests/test_tutorial/test_progressbar/test_tutorial004.py b/tests/test_tutorial/test_progressbar/test_tutorial004.py new file mode 100644 index 0000000000..43ecc451d6 --- /dev/null +++ b/tests/test_tutorial/test_progressbar/test_tutorial004.py @@ -0,0 +1,61 @@ +import subprocess +import sys +from unittest.mock import patch + +from typer.testing import CliRunner + +import docs_src.progressbar.tutorial004_py39 as mod +from tests.utils import normalize_rich_output + +app = mod.app + +runner = CliRunner() + + +def test_cli(): # Checking only final state of progress bar + consumed = [] + + def fake_iterate_user_ids(): + for i in range(100): + consumed.append(i) + yield i + + with ( + patch("time.sleep") as mock_sleep, + patch( + "docs_src.progressbar.tutorial004_py39.iterate_user_ids", + side_effect=fake_iterate_user_ids, + ), + ): + result = runner.invoke(app) + + # Replace all Rich formatting with `*` characters + normalized_output = normalize_rich_output(result.output) + + assert result.exit_code == 0 + assert len(consumed) == 100 + assert mock_sleep.call_count == 100 + assert "Processed 100 user IDs." in normalized_output + + +def test_cli_no_mock_generator(): + with ( + patch("time.sleep") as mock_sleep, + ): + result = runner.invoke(app) + + # Replace all Rich formatting with `*` characters + normalized_output = normalize_rich_output(result.output) + + assert result.exit_code == 0 + assert mock_sleep.call_count == 100 + assert "Processed 100 user IDs." in normalized_output + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From f32c543b4d1f746142c483c1c860df472f5eb793 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Fri, 26 Dec 2025 17:32:22 +0100 Subject: [PATCH 26/31] Add tests for `docs_src/progressbar/tutorial005_py39.py` --- pyproject.toml | 1 - .../test_progressbar/test_tutorial005.py | 33 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_progressbar/test_tutorial005.py diff --git a/pyproject.toml b/pyproject.toml index bf2bafd1df..26656804f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/progressbar/tutorial005_py39.py", # ToDo: add test "docs_src/progressbar/tutorial006_py39.py", # ToDo: add test "docs_src/prompt/tutorial004_py39.py", # ToDo: add test "docs_src/using_click/tutorial001_py39.py", # ToDo: add test diff --git a/tests/test_tutorial/test_progressbar/test_tutorial005.py b/tests/test_tutorial/test_progressbar/test_tutorial005.py new file mode 100644 index 0000000000..011de5db3c --- /dev/null +++ b/tests/test_tutorial/test_progressbar/test_tutorial005.py @@ -0,0 +1,33 @@ +import subprocess +import sys +from unittest.mock import patch + +from typer.testing import CliRunner + +import docs_src.progressbar.tutorial005_py39 as mod +from tests.utils import normalize_rich_output + +app = mod.app + +runner = CliRunner() + + +def test_cli(): # Checking only final state of progress bar + with patch("time.sleep") as mock_sleep: + result = runner.invoke(app) + + # Replace all Rich formatting with `*` characters + normalized_output = normalize_rich_output(result.output) + + assert result.exit_code == 0 + assert mock_sleep.call_count == 100 + assert "Processed 100 things." in normalized_output + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From aa8ca0a8bf05687e446f0dbf0f435b3a49cd2841 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Fri, 26 Dec 2025 17:35:08 +0100 Subject: [PATCH 27/31] Add tests for `docs_src/progressbar/tutorial006_py39.py` --- pyproject.toml | 1 - .../test_progressbar/test_tutorial006.py | 33 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_progressbar/test_tutorial006.py diff --git a/pyproject.toml b/pyproject.toml index 26656804f3..3925d375f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/progressbar/tutorial006_py39.py", # ToDo: add test "docs_src/prompt/tutorial004_py39.py", # ToDo: add test "docs_src/using_click/tutorial001_py39.py", # ToDo: add test "docs_src/using_click/tutorial002_py39.py", # ToDo: add test diff --git a/tests/test_tutorial/test_progressbar/test_tutorial006.py b/tests/test_tutorial/test_progressbar/test_tutorial006.py new file mode 100644 index 0000000000..3fbe78d4e1 --- /dev/null +++ b/tests/test_tutorial/test_progressbar/test_tutorial006.py @@ -0,0 +1,33 @@ +import subprocess +import sys +from unittest.mock import patch + +from typer.testing import CliRunner + +import docs_src.progressbar.tutorial006_py39 as mod +from tests.utils import normalize_rich_output + +app = mod.app + +runner = CliRunner() + + +def test_cli(): # Checking only final state of progress bar + with patch("time.sleep") as mock_sleep: + result = runner.invoke(app) + + # Replace all Rich formatting with `*` characters + normalized_output = normalize_rich_output(result.output) + + assert result.exit_code == 0 + assert mock_sleep.call_count == 4 + assert "Processed 1000 things in batches." in normalized_output + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From e1192d89e3c31ee0d5f41fdffe0a873628315118 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Fri, 26 Dec 2025 17:52:19 +0100 Subject: [PATCH 28/31] Add tests for `docs_src/prompt/tutorial004_py39.py` --- pyproject.toml | 1 - .../test_prompt/test_tutorial004.py | 25 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_prompt/test_tutorial004.py diff --git a/pyproject.toml b/pyproject.toml index 3925d375f6..e5188a9e85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/prompt/tutorial004_py39.py", # ToDo: add test "docs_src/using_click/tutorial001_py39.py", # ToDo: add test "docs_src/using_click/tutorial002_py39.py", # ToDo: add test ] diff --git a/tests/test_tutorial/test_prompt/test_tutorial004.py b/tests/test_tutorial/test_prompt/test_tutorial004.py new file mode 100644 index 0000000000..97e9475b97 --- /dev/null +++ b/tests/test_tutorial/test_prompt/test_tutorial004.py @@ -0,0 +1,25 @@ +import subprocess +import sys + +from typer.testing import CliRunner + +from docs_src.prompt import tutorial004_py39 as mod + +runner = CliRunner() +app = mod.app + + +def test_cli(): + result = runner.invoke(app, input="World\n") + assert result.exit_code == 0 + assert "Enter your name" in result.output + assert "Hey there World!" in result.output + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From 1a94ffd02e0d7b6f14cb34da17ae4e6c6ebb11e3 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Fri, 26 Dec 2025 18:00:35 +0100 Subject: [PATCH 29/31] Add tests for `docs_src/using_click/tutorial002_py39.py` --- pyproject.toml | 1 - .../test_using_click/test_tutorial002.py | 35 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_using_click/test_tutorial002.py diff --git a/pyproject.toml b/pyproject.toml index e5188a9e85..ab6280c58b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -131,7 +131,6 @@ source = [ omit = [ "typer/_typing.py", "docs_src/using_click/tutorial001_py39.py", # ToDo: add test - "docs_src/using_click/tutorial002_py39.py", # ToDo: add test ] context = '${CONTEXT}' relative_files = true diff --git a/tests/test_tutorial/test_using_click/test_tutorial002.py b/tests/test_tutorial/test_using_click/test_tutorial002.py new file mode 100644 index 0000000000..bcc71ac37c --- /dev/null +++ b/tests/test_tutorial/test_using_click/test_tutorial002.py @@ -0,0 +1,35 @@ +import subprocess +import sys + +from click.testing import CliRunner + +from docs_src.using_click import tutorial002_py39 as mod + +runner = CliRunner() + + +def test_help(): + result = runner.invoke(mod.cli, ["--help"]) + assert result.exit_code == 0 + assert "Commands" in result.output + assert "initdb" in result.output + assert "dropdb" in result.output + + +def test_initdb(): + result = runner.invoke(mod.cli, ["initdb"]) + assert "Initialized the database" in result.stdout + + +def test_dropdb(): + result = runner.invoke(mod.cli, ["dropdb"]) + assert "Dropped the database" in result.stdout + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From 8add38277ecd4a9282a2dd596796bbca792fe178 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Fri, 26 Dec 2025 18:11:28 +0100 Subject: [PATCH 30/31] Add tests for `docs_src/using_click/tutorial001_py39.py` --- pyproject.toml | 1 - .../test_using_click/test_tutorial001.py | 47 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 tests/test_tutorial/test_using_click/test_tutorial001.py diff --git a/pyproject.toml b/pyproject.toml index ab6280c58b..5fb7841547 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ source = [ ] omit = [ "typer/_typing.py", - "docs_src/using_click/tutorial001_py39.py", # ToDo: add test ] context = '${CONTEXT}' relative_files = true diff --git a/tests/test_tutorial/test_using_click/test_tutorial001.py b/tests/test_tutorial/test_using_click/test_tutorial001.py new file mode 100644 index 0000000000..92d0f8e254 --- /dev/null +++ b/tests/test_tutorial/test_using_click/test_tutorial001.py @@ -0,0 +1,47 @@ +import subprocess +import sys + +from click.testing import CliRunner + +from docs_src.using_click import tutorial001_py39 as mod + +runner = CliRunner() + + +def test_help(): + result = runner.invoke(mod.hello, ["--help"]) + assert result.exit_code == 0 + assert ( + "Simple program that greets NAME for a total of COUNT times." in result.output + ) + assert "--name" in result.output + assert "--count" in result.output + + +def test_cli_prompt(): + result = runner.invoke(mod.hello, input="Camila\n") + assert result.exit_code == 0 + assert "Your name: Camila" in result.stdout + assert "Hello Camila!" in result.stdout + + +def test_cli_with_name(): + result = runner.invoke(mod.hello, ["--name", "Camila"]) + assert result.exit_code == 0 + assert "Hello Camila!" in result.stdout + + +def test_cli_with_name_and_count(): + result = runner.invoke(mod.hello, ["--name", "Camila", "--count", "3"]) + assert result.exit_code == 0 + assert "Hello Camila!" in result.stdout + assert result.stdout.count("Hello Camila!") == 3 + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From 393f99f535606c59441406a878c52e9fcfe6ce1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 30 Dec 2025 12:34:50 +0100 Subject: [PATCH 31/31] =?UTF-8?q?=F0=9F=93=9D=20Update=20docs=20examples?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/tutorial/options/version.md | 2 +- docs_src/options/version/tutorial002_an_py310.py | 1 + docs_src/options/version/tutorial002_an_py39.py | 1 + docs_src/options/version/tutorial002_py310.py | 1 + docs_src/options/version/tutorial002_py39.py | 1 + .../test_tutorial/test_options/test_version/test_tutorial002.py | 2 +- 6 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/options/version.md b/docs/tutorial/options/version.md index b4bb36d1ce..a903d91069 100644 --- a/docs/tutorial/options/version.md +++ b/docs/tutorial/options/version.md @@ -57,7 +57,7 @@ Awesome CLI Version: 0.1.0 But now let's say that the `--name` *CLI option* that we declared before `--version` is required, and it has a callback that could exit the program: -{* docs_src/options/version/tutorial002_an_py310.py hl[16:18,24:26] *} +{* docs_src/options/version/tutorial002_an_py310.py hl[16:19,25:27] *} Then our CLI program could not work as expected in some cases as it is *right now*, because if we use `--version` after `--name` then the callback for `--name` will be processed before and we can get its error: diff --git a/docs_src/options/version/tutorial002_an_py310.py b/docs_src/options/version/tutorial002_an_py310.py index a867fe199b..9104195dff 100644 --- a/docs_src/options/version/tutorial002_an_py310.py +++ b/docs_src/options/version/tutorial002_an_py310.py @@ -16,6 +16,7 @@ def version_callback(value: bool): def name_callback(name: str): if name != "Camila": raise typer.BadParameter("Only Camila is allowed") + return name @app.command() diff --git a/docs_src/options/version/tutorial002_an_py39.py b/docs_src/options/version/tutorial002_an_py39.py index 32f5315133..060fa312a9 100644 --- a/docs_src/options/version/tutorial002_an_py39.py +++ b/docs_src/options/version/tutorial002_an_py39.py @@ -16,6 +16,7 @@ def version_callback(value: bool): def name_callback(name: str): if name != "Camila": raise typer.BadParameter("Only Camila is allowed") + return name @app.command() diff --git a/docs_src/options/version/tutorial002_py310.py b/docs_src/options/version/tutorial002_py310.py index 471ae2e303..9e881619c0 100644 --- a/docs_src/options/version/tutorial002_py310.py +++ b/docs_src/options/version/tutorial002_py310.py @@ -14,6 +14,7 @@ def version_callback(value: bool): def name_callback(name: str): if name != "Camila": raise typer.BadParameter("Only Camila is allowed") + return name @app.command() diff --git a/docs_src/options/version/tutorial002_py39.py b/docs_src/options/version/tutorial002_py39.py index a880c15c0a..75581cc293 100644 --- a/docs_src/options/version/tutorial002_py39.py +++ b/docs_src/options/version/tutorial002_py39.py @@ -16,6 +16,7 @@ def version_callback(value: bool): def name_callback(name: str): if name != "Camila": raise typer.BadParameter("Only Camila is allowed") + return name @app.command() diff --git a/tests/test_tutorial/test_options/test_version/test_tutorial002.py b/tests/test_tutorial/test_options/test_version/test_tutorial002.py index 670e3559a4..17aed359ca 100644 --- a/tests/test_tutorial/test_options/test_version/test_tutorial002.py +++ b/tests/test_tutorial/test_options/test_version/test_tutorial002.py @@ -44,7 +44,7 @@ def test_version_not_camila(mod: ModuleType): def test_version_camila_no_version(mod: ModuleType): result = runner.invoke(mod.app, ["--name", "Camila"]) assert result.exit_code == 0 - assert "Hello None" in result.output + assert "Hello Camila" in result.output def test_script(mod: ModuleType):