From afc9cf445498f7ba9acbbdac0ceeb4b56d986ad9 Mon Sep 17 00:00:00 2001 From: Mantas Date: Thu, 21 Jan 2021 18:57:51 +0200 Subject: [PATCH 01/44] Add option to use Enum names Fixes: https://github.com/tiangolo/typer/issues/151 Added `names` parameter, in order to user Enum names instead of values. Also for IntEnum, names are used by default, even if names is False. --- docs/tutorial/parameter-types/enum.md | 29 +++++++++++++++++ docs_src/parameter_types/enum/tutorial003.py | 20 ++++++++++++ docs_src/parameter_types/enum/tutorial004.py | 20 ++++++++++++ .../test_enum/test_tutorial003.py | 27 ++++++++++++++++ .../test_enum/test_tutorial004.py | 27 ++++++++++++++++ typer/main.py | 32 +++++++++++++++++-- typer/models.py | 6 ++++ typer/params.py | 4 +++ 8 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 docs_src/parameter_types/enum/tutorial003.py create mode 100644 docs_src/parameter_types/enum/tutorial004.py create mode 100644 tests/test_tutorial/test_parameter_types/test_enum/test_tutorial003.py create mode 100644 tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004.py diff --git a/docs/tutorial/parameter-types/enum.md b/docs/tutorial/parameter-types/enum.md index 6af834bf54..170b75301c 100644 --- a/docs/tutorial/parameter-types/enum.md +++ b/docs/tutorial/parameter-types/enum.md @@ -66,3 +66,32 @@ Training neural network of type: lstm ``` + + +### Using Enum names instead of values + +Some times you want to accept `Enum` names from command line and convert +that into `Enum` values in command handler. You can enable this with +`names=True` parameter: + +```Python hl_lines="14" +{!../docs_src/parameter_types/enum/tutorial003.py!} +``` + +And then the names of the `Enum` will be used instead of values: + +
+ +```console +$ python main.py --log-level debug + +Log level set to DEBUG +``` + +
+ +If `IntEnum` type is given, then enum names are used implicitly. + +```Python hl_lines="14" +{!../docs_src/parameter_types/enum/tutorial004.py!} +``` diff --git a/docs_src/parameter_types/enum/tutorial003.py b/docs_src/parameter_types/enum/tutorial003.py new file mode 100644 index 0000000000..6bec2546ca --- /dev/null +++ b/docs_src/parameter_types/enum/tutorial003.py @@ -0,0 +1,20 @@ +import logging +import enum + +import typer + + +class LogLevel(enum.Enum): + debug = logging.DEBUG + info = logging.INFO + warning = logging.WARNING + + +def main( + log_level: LogLevel = typer.Option(LogLevel.warning, names=True) +): + typer.echo(f"Log level set to: {logging.getLevelName(log_level.value)}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/docs_src/parameter_types/enum/tutorial004.py b/docs_src/parameter_types/enum/tutorial004.py new file mode 100644 index 0000000000..cc1a8f5934 --- /dev/null +++ b/docs_src/parameter_types/enum/tutorial004.py @@ -0,0 +1,20 @@ +import enum + +import typer + + +class Access(enum.IntEnum): + private = 1 + protected = 2 + public = 3 + open = 4 + + +def main( + access: Access = typer.Option(Access.private) +): + typer.echo(f"Access level: {access.name}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial003.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial003.py new file mode 100644 index 0000000000..011245962c --- /dev/null +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial003.py @@ -0,0 +1,27 @@ +import subprocess + +import typer +from typer.testing import CliRunner + +from docs_src.parameter_types.enum import tutorial003 as mod + +runner = CliRunner() + +app = typer.Typer() +app.command()(mod.main) + + +def test_enum_names(): + result = runner.invoke(app, ["--log-level", "debug"]) + assert result.exit_code == 0 + assert "Log level set to: DEBUG" in result.output + + +def test_script(): + result = subprocess.run( + ["coverage", "run", mod.__file__, "--help"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + encoding="utf-8", + ) + assert "Usage" in result.stdout diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004.py new file mode 100644 index 0000000000..159e16283b --- /dev/null +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004.py @@ -0,0 +1,27 @@ +import subprocess + +import typer +from typer.testing import CliRunner + +from docs_src.parameter_types.enum import tutorial004 as mod + +runner = CliRunner() + +app = typer.Typer() +app.command()(mod.main) + + +def test_int_enum(): + result = runner.invoke(app, ["--access", "open"]) + assert result.exit_code == 0 + assert "Access level: open" in result.output + + +def test_script(): + result = subprocess.run( + ["coverage", "run", mod.__file__, "--help"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + encoding="utf-8", + ) + assert "Usage" in result.stdout diff --git a/typer/main.py b/typer/main.py index 02d9a5d7fe..fa3ff3a5f6 100644 --- a/typer/main.py +++ b/typer/main.py @@ -463,6 +463,18 @@ def convertor(value: Any) -> Any: return convertor +def generate_enum_name_convertor(enum: Type[Enum]) -> Callable[..., Any]: + lower_name_map = {str(item.name).lower(): item for item in enum} + + def convertor(value: Any) -> Any: + if value is not None: + low = str(value).lower() + if low in lower_name_map: + return lower_name_map[low] + + return convertor + + def generate_iter_convertor(convertor: Callable[[Any], Any]) -> Callable[..., Any]: def internal_convertor(value: Any) -> List[Any]: return [convertor(v) for v in value] @@ -580,8 +592,12 @@ def get_click_type( atomic=parameter_info.atomic, ) elif lenient_issubclass(annotation, Enum): + if use_enum_names(parameter_info, annotation): + choices = [item.name for item in annotation] + else: + choices = [item.value for item in annotation] return click.Choice( - [item.value for item in annotation], + choices, case_sensitive=parameter_info.case_sensitive, ) raise RuntimeError(f"Type not yet supported: {annotation}") # pragma no cover @@ -593,6 +609,15 @@ def lenient_issubclass( return isinstance(cls, type) and issubclass(cls, class_or_tuple) +def use_enum_names(parameter_info: ParameterInfo, annotation: Type[Enum]) -> bool: + """Check if Enum names or values should be used + + If ParameterInfo.names is explicitly set to True, always use names, but also + try to guess if names should be used in cases, when Enum is ant IntEnum. + """ + return parameter_info.names or issubclass(annotation, int) + + def get_click_param( param: ParamMeta, ) -> Tuple[Union[click.Argument, click.Option], Any]: @@ -660,7 +685,10 @@ def get_click_param( if lenient_issubclass(main_type, Path): convertor = param_path_convertor if lenient_issubclass(main_type, Enum): - convertor = generate_enum_convertor(main_type) + if use_enum_names(parameter_info, main_type): + convertor = generate_enum_name_convertor(main_type) + else: + convertor = generate_enum_convertor(main_type) if convertor and is_list: convertor = generate_iter_convertor(convertor) # TODO: handle recursive conversion for tuples diff --git a/typer/models.py b/typer/models.py index 2b7dc6df9e..a57ed5ca19 100644 --- a/typer/models.py +++ b/typer/models.py @@ -173,6 +173,7 @@ def __init__( hidden: bool = False, # Choice case_sensitive: bool = True, + names: bool = False, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, @@ -211,6 +212,7 @@ def __init__( self.hidden = hidden # Choice self.case_sensitive = case_sensitive + self.names = names # Numbers self.min = min self.max = max @@ -262,6 +264,7 @@ def __init__( show_envvar: bool = True, # Choice case_sensitive: bool = True, + names: bool = False, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, @@ -301,6 +304,7 @@ def __init__( hidden=hidden, # Choice case_sensitive=case_sensitive, + names=names, # Numbers min=min, max=max, @@ -353,6 +357,7 @@ def __init__( hidden: bool = False, # Choice case_sensitive: bool = True, + names: bool = False, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, @@ -392,6 +397,7 @@ def __init__( hidden=hidden, # Choice case_sensitive=case_sensitive, + names=names, # Numbers min=min, max=max, diff --git a/typer/params.py b/typer/params.py index f502551dbf..8c36ce7e80 100644 --- a/typer/params.py +++ b/typer/params.py @@ -28,6 +28,7 @@ def Option( show_envvar: bool = True, # Choice case_sensitive: bool = True, + names: bool = False, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, @@ -75,6 +76,7 @@ def Option( show_envvar=show_envvar, # Choice case_sensitive=case_sensitive, + names=names, # Numbers min=min, max=max, @@ -117,6 +119,7 @@ def Argument( hidden: bool = False, # Choice case_sensitive: bool = True, + names: bool = False, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, @@ -159,6 +162,7 @@ def Argument( hidden=hidden, # Choice case_sensitive=case_sensitive, + names=names, # Numbers min=min, max=max, From 2e079f29d3296ea081ca7a17ec53ecbf41754dac Mon Sep 17 00:00:00 2001 From: Mantas Date: Thu, 21 Jan 2021 19:12:14 +0200 Subject: [PATCH 02/44] Fix black issues --- docs_src/parameter_types/enum/tutorial003.py | 4 +--- docs_src/parameter_types/enum/tutorial004.py | 4 +--- typer/main.py | 5 +---- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/docs_src/parameter_types/enum/tutorial003.py b/docs_src/parameter_types/enum/tutorial003.py index 6bec2546ca..f8cd384561 100644 --- a/docs_src/parameter_types/enum/tutorial003.py +++ b/docs_src/parameter_types/enum/tutorial003.py @@ -10,9 +10,7 @@ class LogLevel(enum.Enum): warning = logging.WARNING -def main( - log_level: LogLevel = typer.Option(LogLevel.warning, names=True) -): +def main(log_level: LogLevel = typer.Option(LogLevel.warning, names=True)): typer.echo(f"Log level set to: {logging.getLevelName(log_level.value)}") diff --git a/docs_src/parameter_types/enum/tutorial004.py b/docs_src/parameter_types/enum/tutorial004.py index cc1a8f5934..1f497af754 100644 --- a/docs_src/parameter_types/enum/tutorial004.py +++ b/docs_src/parameter_types/enum/tutorial004.py @@ -10,9 +10,7 @@ class Access(enum.IntEnum): open = 4 -def main( - access: Access = typer.Option(Access.private) -): +def main(access: Access = typer.Option(Access.private)): typer.echo(f"Access level: {access.name}") diff --git a/typer/main.py b/typer/main.py index fa3ff3a5f6..4defc488ad 100644 --- a/typer/main.py +++ b/typer/main.py @@ -596,10 +596,7 @@ def get_click_type( choices = [item.name for item in annotation] else: choices = [item.value for item in annotation] - return click.Choice( - choices, - case_sensitive=parameter_info.case_sensitive, - ) + return click.Choice(choices, case_sensitive=parameter_info.case_sensitive) raise RuntimeError(f"Type not yet supported: {annotation}") # pragma no cover From b0f534e6c64e0190732b46ec04767ab11fd1070c Mon Sep 17 00:00:00 2001 From: Mantas Date: Thu, 21 Jan 2021 19:15:58 +0200 Subject: [PATCH 03/44] Fix isort issues --- docs_src/parameter_types/enum/tutorial003.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs_src/parameter_types/enum/tutorial003.py b/docs_src/parameter_types/enum/tutorial003.py index f8cd384561..17a029eae8 100644 --- a/docs_src/parameter_types/enum/tutorial003.py +++ b/docs_src/parameter_types/enum/tutorial003.py @@ -1,5 +1,5 @@ -import logging import enum +import logging import typer From 5769c4a053e2d3e261eead9c96f614d444ca6e78 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 13:43:02 +0000 Subject: [PATCH 04/44] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20f?= =?UTF-8?q?ormat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/tutorial/parameter-types/enum.md | 4 ++-- typer/main.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/parameter-types/enum.md b/docs/tutorial/parameter-types/enum.md index f6213062b0..c8aacb77a0 100644 --- a/docs/tutorial/parameter-types/enum.md +++ b/docs/tutorial/parameter-types/enum.md @@ -129,8 +129,8 @@ This works just like any other parameter value taking a list of things: ### Using Enum names instead of values -Some times you want to accept `Enum` names from command line and convert -that into `Enum` values in command handler. You can enable this with +Some times you want to accept `Enum` names from command line and convert +that into `Enum` values in command handler. You can enable this with `names=True` parameter: ```Python hl_lines="14" diff --git a/typer/main.py b/typer/main.py index f8e49ff41a..6c412eabec 100644 --- a/typer/main.py +++ b/typer/main.py @@ -672,6 +672,7 @@ def convertor(value: Any) -> Any: return convertor + def get_callback( *, callback: Optional[Callable[..., Any]] = None, From f4a99b2cc1f055732c379682095ca50267998a8f Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 14 Aug 2024 15:44:40 +0200 Subject: [PATCH 05/44] rename 004 to 005 --- .../parameter_types/enum/{tutorial004.py => tutorial005.py} | 0 .../test_enum/{test_tutorial004.py => test_tutorial005.py} | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename docs_src/parameter_types/enum/{tutorial004.py => tutorial005.py} (100%) rename tests/test_tutorial/test_parameter_types/test_enum/{test_tutorial004.py => test_tutorial005.py} (89%) diff --git a/docs_src/parameter_types/enum/tutorial004.py b/docs_src/parameter_types/enum/tutorial005.py similarity index 100% rename from docs_src/parameter_types/enum/tutorial004.py rename to docs_src/parameter_types/enum/tutorial005.py diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py similarity index 89% rename from tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004.py rename to tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py index 159e16283b..79ae14dbe8 100644 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004.py +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py @@ -3,7 +3,7 @@ import typer from typer.testing import CliRunner -from docs_src.parameter_types.enum import tutorial004 as mod +from docs_src.parameter_types.enum import tutorial005 as mod runner = CliRunner() From 5d63dbdb60fe70fc4af80ebd2b48472b6698eced Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 14 Aug 2024 15:45:59 +0200 Subject: [PATCH 06/44] restore the original 003 as 004 --- docs_src/parameter_types/enum/tutorial004.py | 18 +++++++++++++ .../test_enum/test_tutorial004.py | 27 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 docs_src/parameter_types/enum/tutorial004.py create mode 100644 tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004.py diff --git a/docs_src/parameter_types/enum/tutorial004.py b/docs_src/parameter_types/enum/tutorial004.py new file mode 100644 index 0000000000..17a029eae8 --- /dev/null +++ b/docs_src/parameter_types/enum/tutorial004.py @@ -0,0 +1,18 @@ +import enum +import logging + +import typer + + +class LogLevel(enum.Enum): + debug = logging.DEBUG + info = logging.INFO + warning = logging.WARNING + + +def main(log_level: LogLevel = typer.Option(LogLevel.warning, names=True)): + typer.echo(f"Log level set to: {logging.getLevelName(log_level.value)}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004.py new file mode 100644 index 0000000000..f57bfee64a --- /dev/null +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004.py @@ -0,0 +1,27 @@ +import subprocess + +import typer +from typer.testing import CliRunner + +from docs_src.parameter_types.enum import tutorial004 as mod + +runner = CliRunner() + +app = typer.Typer() +app.command()(mod.main) + + +def test_enum_names(): + result = runner.invoke(app, ["--log-level", "debug"]) + assert result.exit_code == 0 + assert "Log level set to: DEBUG" in result.output + + +def test_script(): + result = subprocess.run( + ["coverage", "run", mod.__file__, "--help"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + encoding="utf-8", + ) + assert "Usage" in result.stdout From a77dddd92717bac60517d3ed9510ceb9f3f94c9f Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 11 Sep 2024 14:39:48 +0200 Subject: [PATCH 07/44] fix issues --- .../test_parameter_types/test_enum/test_tutorial004.py | 3 +-- .../test_parameter_types/test_enum/test_tutorial005.py | 3 +-- typer/params.py | 4 ++++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004.py index f57bfee64a..9154daebed 100644 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004.py +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004.py @@ -20,8 +20,7 @@ def test_enum_names(): def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, + capture_output=True, encoding="utf-8", ) assert "Usage" in result.stdout diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py index 79ae14dbe8..6e2ba78c8d 100644 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py @@ -20,8 +20,7 @@ def test_int_enum(): def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, + capture_output=True, encoding="utf-8", ) assert "Usage" in result.stdout diff --git a/typer/params.py b/typer/params.py index 77c86b694e..2f594c948c 100644 --- a/typer/params.py +++ b/typer/params.py @@ -109,6 +109,7 @@ def Option( show_envvar: bool = True, # Choice case_sensitive: bool = True, + names: bool = False, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, @@ -171,6 +172,7 @@ def Option( show_envvar: bool = True, # Choice case_sensitive: bool = True, + names: bool = False, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, @@ -282,6 +284,7 @@ def Argument( hidden: bool = False, # Choice case_sensitive: bool = True, + names: bool = False, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, @@ -337,6 +340,7 @@ def Argument( hidden: bool = False, # Choice case_sensitive: bool = True, + names: bool = False, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, From dfaf7b320ba564e6b0b7e5c915b496ec105f8962 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 11 Sep 2024 15:24:50 +0200 Subject: [PATCH 08/44] rename to enum_by_name and fix the convertor code order --- docs/tutorial/parameter-types/enum.md | 61 ++++++++++---------- docs_src/parameter_types/enum/tutorial004.py | 2 +- docs_src/parameter_types/enum/tutorial005.py | 2 +- typer/main.py | 45 ++++++--------- typer/models.py | 12 ++-- typer/params.py | 16 ++--- 6 files changed, 65 insertions(+), 73 deletions(-) diff --git a/docs/tutorial/parameter-types/enum.md b/docs/tutorial/parameter-types/enum.md index c8aacb77a0..fdfaf6785a 100644 --- a/docs/tutorial/parameter-types/enum.md +++ b/docs/tutorial/parameter-types/enum.md @@ -126,36 +126,6 @@ This works just like any other parameter value taking a list of things:
- -### Using Enum names instead of values - -Some times you want to accept `Enum` names from command line and convert -that into `Enum` values in command handler. You can enable this with -`names=True` parameter: - -```Python hl_lines="14" -{!../docs_src/parameter_types/enum/tutorial004.py!} -``` - -And then the names of the `Enum` will be used instead of values: - -
- -```console -$ python main.py --log-level debug - -Log level set to DEBUG -``` - -
- -If `IntEnum` type is given, then enum names are used implicitly. - -```Python hl_lines="14" -{!../docs_src/parameter_types/enum/tutorial005.py!} -``` - - ```console $ python main.py --help @@ -183,3 +153,34 @@ Buying groceries: Eggs, Bacon ```
+ + +### Using Enum names instead of values + +Some times you want to accept `Enum` names from the command line and convert +that into `Enum` values in the command handler. You can enable this by setting +`enum_by_name=True`: + +```Python hl_lines="14" +{!../docs_src/parameter_types/enum/tutorial004.py!} +``` + +And then the names of the `Enum` will be used instead of values: + +
+ +```console +$ python main.py --log-level debug + +Log level set to DEBUG +``` + +
+ +This can be particularly useful if the enum values are not strings: + +```Python hl_lines="7-10, 13" +{!../docs_src/parameter_types/enum/tutorial005.py!} +``` + + diff --git a/docs_src/parameter_types/enum/tutorial004.py b/docs_src/parameter_types/enum/tutorial004.py index 17a029eae8..5311d1888c 100644 --- a/docs_src/parameter_types/enum/tutorial004.py +++ b/docs_src/parameter_types/enum/tutorial004.py @@ -10,7 +10,7 @@ class LogLevel(enum.Enum): warning = logging.WARNING -def main(log_level: LogLevel = typer.Option(LogLevel.warning, names=True)): +def main(log_level: LogLevel = typer.Option(LogLevel.warning, enum_by_name=True)): typer.echo(f"Log level set to: {logging.getLevelName(log_level.value)}") diff --git a/docs_src/parameter_types/enum/tutorial005.py b/docs_src/parameter_types/enum/tutorial005.py index 1f497af754..e1a0a069c9 100644 --- a/docs_src/parameter_types/enum/tutorial005.py +++ b/docs_src/parameter_types/enum/tutorial005.py @@ -10,7 +10,7 @@ class Access(enum.IntEnum): open = 4 -def main(access: Access = typer.Option(Access.private)): +def main(access: Access = typer.Option(Access.private, enum_by_name=True)): typer.echo(f"Access level: {access.name}") diff --git a/typer/main.py b/typer/main.py index e8440d8ca9..a3a43631fa 100644 --- a/typer/main.py +++ b/typer/main.py @@ -644,6 +644,18 @@ def convertor(value: Any) -> Any: return convertor +def generate_enum_name_convertor(enum: Type[Enum]) -> Callable[..., Any]: + lower_name_map = {str(item.name).lower(): item for item in enum} + + def convertor(value: Any) -> Any: + if value is not None: + low = str(value).lower() + if low in lower_name_map: + return lower_name_map[low] + + return convertor + + def generate_list_convertor( convertor: Optional[Callable[[Any], Any]], default_value: Optional[Any] ) -> Callable[[Sequence[Any]], Optional[List[Any]]]: @@ -673,18 +685,6 @@ def internal_convertor( return internal_convertor -def generate_enum_name_convertor(enum: Type[Enum]) -> Callable[..., Any]: - lower_name_map = {str(item.name).lower(): item for item in enum} - - def convertor(value: Any) -> Any: - if value is not None: - low = str(value).lower() - if low in lower_name_map: - return lower_name_map[low] - - return convertor - - def get_callback( *, callback: Optional[Callable[..., Any]] = None, @@ -805,7 +805,7 @@ def get_click_type( atomic=parameter_info.atomic, ) elif lenient_issubclass(annotation, Enum): - if use_enum_names(parameter_info, annotation): + if parameter_info.enum_by_name: choices = [item.name for item in annotation] else: choices = [item.value for item in annotation] @@ -819,15 +819,6 @@ def lenient_issubclass( return isinstance(cls, type) and issubclass(cls, class_or_tuple) -def use_enum_names(parameter_info: ParameterInfo, annotation: Type[Enum]) -> bool: - """Check if Enum names or values should be used - - If ParameterInfo.names is explicitly set to True, always use names, but also - try to guess if names should be used in cases, when Enum is ant IntEnum. - """ - return parameter_info.names or issubclass(annotation, int) - - def get_click_param( param: ParamMeta, ) -> Tuple[Union[click.Argument, click.Option], Any]: @@ -895,17 +886,17 @@ def get_click_param( annotation=main_type, parameter_info=parameter_info ) convertor = determine_type_convertor(main_type) + if lenient_issubclass(main_type, Enum): + if parameter_info.enum_by_name: + convertor = generate_enum_name_convertor(main_type) + else: + convertor = generate_enum_convertor(main_type) if is_list: convertor = generate_list_convertor( convertor=convertor, default_value=default_value ) if is_tuple: convertor = generate_tuple_convertor(get_args(main_type)) - if lenient_issubclass(main_type, Enum): - if use_enum_names(parameter_info, main_type): - convertor = generate_enum_name_convertor(main_type) - else: - convertor = generate_enum_convertor(main_type) if isinstance(parameter_info, OptionInfo): if main_type is bool and parameter_info.is_flag is not False: is_flag = True diff --git a/typer/models.py b/typer/models.py index 4810c4c212..16b37ff962 100644 --- a/typer/models.py +++ b/typer/models.py @@ -192,7 +192,7 @@ def __init__( hidden: bool = False, # Choice case_sensitive: bool = True, - names: bool = False, + enum_by_name: bool = False, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, @@ -245,7 +245,7 @@ def __init__( self.hidden = hidden # Choice self.case_sensitive = case_sensitive - self.names = names + self.enum_by_name = enum_by_name # Numbers self.min = min self.max = max @@ -310,7 +310,7 @@ def __init__( show_envvar: bool = True, # Choice case_sensitive: bool = True, - names: bool = False, + enum_by_name: bool = False, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, @@ -357,7 +357,7 @@ def __init__( hidden=hidden, # Choice case_sensitive=case_sensitive, - names=names, + enum_by_name=enum_by_name, # Numbers min=min, max=max, @@ -423,7 +423,7 @@ def __init__( hidden: bool = False, # Choice case_sensitive: bool = True, - names: bool = False, + enum_by_name: bool = False, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, @@ -470,7 +470,7 @@ def __init__( hidden=hidden, # Choice case_sensitive=case_sensitive, - names=names, + enum_by_name=enum_by_name, # Numbers min=min, max=max, diff --git a/typer/params.py b/typer/params.py index 2f594c948c..846172710c 100644 --- a/typer/params.py +++ b/typer/params.py @@ -45,7 +45,7 @@ def Option( show_envvar: bool = True, # Choice case_sensitive: bool = True, - names: bool = False, + enum_by_name: bool = False, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, @@ -109,7 +109,7 @@ def Option( show_envvar: bool = True, # Choice case_sensitive: bool = True, - names: bool = False, + enum_by_name: bool = False, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, @@ -172,7 +172,7 @@ def Option( show_envvar: bool = True, # Choice case_sensitive: bool = True, - names: bool = False, + enum_by_name: bool = False, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, @@ -228,7 +228,7 @@ def Option( show_envvar=show_envvar, # Choice case_sensitive=case_sensitive, - names=names, + enum_by_name=enum_by_name, # Numbers min=min, max=max, @@ -284,7 +284,7 @@ def Argument( hidden: bool = False, # Choice case_sensitive: bool = True, - names: bool = False, + enum_by_name: bool = False, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, @@ -340,7 +340,7 @@ def Argument( hidden: bool = False, # Choice case_sensitive: bool = True, - names: bool = False, + enum_by_name: bool = False, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, @@ -395,7 +395,7 @@ def Argument( hidden: bool = False, # Choice case_sensitive: bool = True, - names: bool = False, + enum_by_name: bool = False, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, @@ -445,7 +445,7 @@ def Argument( hidden=hidden, # Choice case_sensitive=case_sensitive, - names=names, + enum_by_name=enum_by_name, # Numbers min=min, max=max, From d77ac5b9199869e1da72a3e51b9fe61f2506ece3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 11 Sep 2024 13:25:00 +0000 Subject: [PATCH 09/44] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20f?= =?UTF-8?q?ormat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/tutorial/parameter-types/enum.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/tutorial/parameter-types/enum.md b/docs/tutorial/parameter-types/enum.md index fdfaf6785a..2f80539237 100644 --- a/docs/tutorial/parameter-types/enum.md +++ b/docs/tutorial/parameter-types/enum.md @@ -182,5 +182,3 @@ This can be particularly useful if the enum values are not strings: ```Python hl_lines="7-10, 13" {!../docs_src/parameter_types/enum/tutorial005.py!} ``` - - From c39a5ea2c0804bff2f0ac4f7c11df28c1e33b755 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 11 Sep 2024 15:27:25 +0200 Subject: [PATCH 10/44] Add console example for IntEnum --- docs/tutorial/parameter-types/enum.md | 6 ++++++ docs_src/parameter_types/enum/tutorial005.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/tutorial/parameter-types/enum.md b/docs/tutorial/parameter-types/enum.md index fdfaf6785a..dada5bbf8f 100644 --- a/docs/tutorial/parameter-types/enum.md +++ b/docs/tutorial/parameter-types/enum.md @@ -183,4 +183,10 @@ This can be particularly useful if the enum values are not strings: {!../docs_src/parameter_types/enum/tutorial005.py!} ``` +```console +$ python main.py --access protected + +Access level: protected (2) +``` + diff --git a/docs_src/parameter_types/enum/tutorial005.py b/docs_src/parameter_types/enum/tutorial005.py index e1a0a069c9..8c88702220 100644 --- a/docs_src/parameter_types/enum/tutorial005.py +++ b/docs_src/parameter_types/enum/tutorial005.py @@ -11,7 +11,7 @@ class Access(enum.IntEnum): def main(access: Access = typer.Option(Access.private, enum_by_name=True)): - typer.echo(f"Access level: {access.name}") + typer.echo(f"Access level: {access.name} ({access.value})") if __name__ == "__main__": From 8312bb863a4eff3ff6ada70ac5122f2083a693a5 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 11 Sep 2024 15:32:47 +0200 Subject: [PATCH 11/44] Fix default values --- docs_src/parameter_types/enum/tutorial004.py | 2 +- docs_src/parameter_types/enum/tutorial005.py | 2 +- .../test_parameter_types/test_enum/test_tutorial004.py | 6 ++++++ .../test_parameter_types/test_enum/test_tutorial005.py | 8 +++++++- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/docs_src/parameter_types/enum/tutorial004.py b/docs_src/parameter_types/enum/tutorial004.py index 5311d1888c..d2ecd0c16d 100644 --- a/docs_src/parameter_types/enum/tutorial004.py +++ b/docs_src/parameter_types/enum/tutorial004.py @@ -10,7 +10,7 @@ class LogLevel(enum.Enum): warning = logging.WARNING -def main(log_level: LogLevel = typer.Option(LogLevel.warning, enum_by_name=True)): +def main(log_level: LogLevel = typer.Option("warning", enum_by_name=True)): typer.echo(f"Log level set to: {logging.getLevelName(log_level.value)}") diff --git a/docs_src/parameter_types/enum/tutorial005.py b/docs_src/parameter_types/enum/tutorial005.py index 8c88702220..2804d3960f 100644 --- a/docs_src/parameter_types/enum/tutorial005.py +++ b/docs_src/parameter_types/enum/tutorial005.py @@ -10,7 +10,7 @@ class Access(enum.IntEnum): open = 4 -def main(access: Access = typer.Option(Access.private, enum_by_name=True)): +def main(access: Access = typer.Option("private", enum_by_name=True)): typer.echo(f"Access level: {access.name} ({access.value})") diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004.py index 9154daebed..0eab8e9be9 100644 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004.py +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004.py @@ -11,6 +11,12 @@ app.command()(mod.main) +def test_enum_names_default(): + result = runner.invoke(app) + assert result.exit_code == 0 + assert "Log level set to: WARNING" in result.output + + def test_enum_names(): result = runner.invoke(app, ["--log-level", "debug"]) assert result.exit_code == 0 diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py index 6e2ba78c8d..db63a8dc48 100644 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py @@ -11,10 +11,16 @@ app.command()(mod.main) +def test_int_enum_default(): + result = runner.invoke(app) + assert result.exit_code == 0 + assert "Access level: private (1)" in result.output + + def test_int_enum(): result = runner.invoke(app, ["--access", "open"]) assert result.exit_code == 0 - assert "Access level: open" in result.output + assert "Access level: open (4)" in result.output def test_script(): From b5648edb1af86e80061defe36d9abb988f207755 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 11 Sep 2024 18:44:03 +0200 Subject: [PATCH 12/44] Add additional unit tests combining enums with list/tuple --- .../tutorial002.py | 25 ++++++++++ .../tutorial003.py | 25 ++++++++++ docs_src/parameter_types/enum/tutorial006.py | 18 +++++++ .../test_tutorial002.py | 49 +++++++++++++++++++ .../test_tutorial003.py | 49 +++++++++++++++++++ .../test_enum/test_tutorial006.py | 47 ++++++++++++++++++ 6 files changed, 213 insertions(+) create mode 100644 docs_src/multiple_values/options_with_multiple_values/tutorial002.py create mode 100644 docs_src/multiple_values/options_with_multiple_values/tutorial003.py create mode 100644 docs_src/parameter_types/enum/tutorial006.py create mode 100644 tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py create mode 100644 tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003.py create mode 100644 tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006.py diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial002.py b/docs_src/multiple_values/options_with_multiple_values/tutorial002.py new file mode 100644 index 0000000000..bf3a2bf556 --- /dev/null +++ b/docs_src/multiple_values/options_with_multiple_values/tutorial002.py @@ -0,0 +1,25 @@ +from enum import Enum +from typing import Tuple + +import typer + + +class Food(str, Enum): + f1 = "Eggs" + f2 = "Bacon" + f3 = "Cheese" + + +def main(user: Tuple[str, int, bool, Food] = typer.Option((None, None, None, Food.f1))): + username, coins, is_wizard, food = user + if not username: + print("No user provided") + raise typer.Abort() + print(f"The username {username} has {coins} coins") + if is_wizard: + print("And this user is a wizard!") + print(f"And they love eating {food.value}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial003.py b/docs_src/multiple_values/options_with_multiple_values/tutorial003.py new file mode 100644 index 0000000000..dfca3c3828 --- /dev/null +++ b/docs_src/multiple_values/options_with_multiple_values/tutorial003.py @@ -0,0 +1,25 @@ +from enum import Enum +from typing import Tuple + +import typer + + +class Food(str, Enum): + f1 = "Eggs" + f2 = "Bacon" + f3 = "Cheese" + + +def main(user: Tuple[str, int, bool, Food] = typer.Option((None, None, None, "f1"), enum_by_name=True)): + username, coins, is_wizard, food = user + if not username: + print("No user provided") + raise typer.Abort() + print(f"The username {username} has {coins} coins") + if is_wizard: + print("And this user is a wizard!") + print(f"And they love eating {food.value}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/docs_src/parameter_types/enum/tutorial006.py b/docs_src/parameter_types/enum/tutorial006.py new file mode 100644 index 0000000000..6c8a238dd1 --- /dev/null +++ b/docs_src/parameter_types/enum/tutorial006.py @@ -0,0 +1,18 @@ +from enum import Enum +from typing import List + +import typer + + +class Food(str, Enum): + f1 = "Eggs" + f2 = "Bacon" + f3 = "Cheese" + + +def main(groceries: List[Food] = typer.Option(["f1", "f3"], enum_by_name=True)): + print(f"Buying groceries: {', '.join([f.value for f in groceries])}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py new file mode 100644 index 0000000000..1533509a77 --- /dev/null +++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py @@ -0,0 +1,49 @@ +import subprocess +import sys + +import typer +from typer.testing import CliRunner + +from docs_src.multiple_values.options_with_multiple_values import tutorial002 as mod + +runner = CliRunner() +app = typer.Typer() +app.command()(mod.main) + + +def test_main(): + result = runner.invoke(app) + assert result.exit_code != 0 + assert "No user provided" in result.output + assert "Aborted" in result.output + + +def test_user_1(): + result = runner.invoke(app, ["--user", "Camila", "50", "yes", "Eggs"]) + assert result.exit_code == 0 + assert "The username Camila has 50 coins" in result.output + assert "And this user is a wizard!" in result.output + assert "And they love eating Eggs" in result.output + + +def test_user_2(): + result = runner.invoke(app, ["--user", "Morty", "3", "no", "Bacon"]) + assert result.exit_code == 0 + assert "The username Morty has 3 coins" in result.output + assert "And this user is a wizard!" not in result.output + assert "And they love eating Bacon" in result.output + + +def test_invalid_user(): + result = runner.invoke(app, ["--user", "Camila", "50"]) + assert result.exit_code != 0 + assert "Option '--user' requires 4 arguments" 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 diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003.py new file mode 100644 index 0000000000..75f457c806 --- /dev/null +++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003.py @@ -0,0 +1,49 @@ +import subprocess +import sys + +import typer +from typer.testing import CliRunner + +from docs_src.multiple_values.options_with_multiple_values import tutorial003 as mod + +runner = CliRunner() +app = typer.Typer() +app.command()(mod.main) + + +def test_main(): + result = runner.invoke(app) + assert result.exit_code != 0 + assert "No user provided" in result.output + assert "Aborted" in result.output + + +def test_user_1(): + result = runner.invoke(app, ["--user", "Camila", "50", "yes", "f1"]) + assert result.exit_code == 0 + assert "The username Camila has 50 coins" in result.output + assert "And this user is a wizard!" in result.output + assert "And they love eating Eggs" in result.output + + +def test_user_2(): + result = runner.invoke(app, ["--user", "Morty", "3", "no", "f2"]) + assert result.exit_code == 0 + assert "The username Morty has 3 coins" in result.output + assert "And this user is a wizard!" not in result.output + assert "And they love eating Bacon" in result.output + + +def test_invalid_user(): + result = runner.invoke(app, ["--user", "Camila", "50"]) + assert result.exit_code != 0 + assert "Option '--user' requires 4 arguments" 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 diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006.py new file mode 100644 index 0000000000..6afefbae02 --- /dev/null +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006.py @@ -0,0 +1,47 @@ +import subprocess +import sys + +import typer +from typer.testing import CliRunner + +from docs_src.parameter_types.enum import tutorial006 as mod + +runner = CliRunner() + +app = typer.Typer() +app.command()(mod.main) + + +def test_help(): + result = runner.invoke(app, ["--help"]) + assert result.exit_code == 0 + assert "--groceries" in result.output + assert "[f1|f2|f3]" in result.output + assert "default: f1, f3" in result.output + + +def test_call_no_arg(): + result = runner.invoke(app) + assert result.exit_code == 0 + assert "Buying groceries: Eggs, Cheese" in result.output + + +def test_call_single_arg(): + result = runner.invoke(app, ["--groceries", "f2"]) + assert result.exit_code == 0 + assert "Buying groceries: Bacon" in result.output + + +def test_call_multiple_arg(): + result = runner.invoke(app, ["--groceries", "f1", "--groceries", "f2"]) + assert result.exit_code == 0 + assert "Buying groceries: Eggs, Bacon" 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 e24f4465a54409da7beb8d3e8ffb5387588da992 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 11 Sep 2024 16:44:13 +0000 Subject: [PATCH 13/44] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20f?= =?UTF-8?q?ormat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../options_with_multiple_values/tutorial003.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial003.py b/docs_src/multiple_values/options_with_multiple_values/tutorial003.py index dfca3c3828..94715e1a13 100644 --- a/docs_src/multiple_values/options_with_multiple_values/tutorial003.py +++ b/docs_src/multiple_values/options_with_multiple_values/tutorial003.py @@ -10,7 +10,11 @@ class Food(str, Enum): f3 = "Cheese" -def main(user: Tuple[str, int, bool, Food] = typer.Option((None, None, None, "f1"), enum_by_name=True)): +def main( + user: Tuple[str, int, bool, Food] = typer.Option( + (None, None, None, "f1"), enum_by_name=True + ), +): username, coins, is_wizard, food = user if not username: print("No user provided") From 0644919541b636bb7c6f38a89dd43514e6039894 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 11 Sep 2024 19:06:03 +0200 Subject: [PATCH 14/44] pass along enum_by_name parameter to generate_X_convertor functions --- typer/main.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/typer/main.py b/typer/main.py index a3a43631fa..2fbb0218b3 100644 --- a/typer/main.py +++ b/typer/main.py @@ -616,12 +616,15 @@ def get_command_from_info( return command -def determine_type_convertor(type_: Any) -> Optional[Callable[[Any], Any]]: +def determine_type_convertor(type_: Any, enum_by_name: bool) -> Optional[Callable[[Any], Any]]: convertor: Optional[Callable[[Any], Any]] = None if lenient_issubclass(type_, Path): convertor = param_path_convertor if lenient_issubclass(type_, Enum): - convertor = generate_enum_convertor(type_) + if enum_by_name: + convertor = generate_enum_name_convertor(type_) + else: + convertor = generate_enum_convertor(type_) return convertor @@ -668,9 +671,9 @@ def internal_convertor(value: Sequence[Any]) -> Optional[List[Any]]: def generate_tuple_convertor( - types: Sequence[Any], + types: Sequence[Any], enum_by_name: bool, ) -> Callable[[Optional[Tuple[Any, ...]]], Optional[Tuple[Any, ...]]]: - convertors = [determine_type_convertor(type_) for type_ in types] + convertors = [determine_type_convertor(type_, enum_by_name) for type_ in types] def internal_convertor( param_args: Optional[Tuple[Any, ...]], @@ -885,18 +888,14 @@ def get_click_param( parameter_type = get_click_type( annotation=main_type, parameter_info=parameter_info ) - convertor = determine_type_convertor(main_type) - if lenient_issubclass(main_type, Enum): - if parameter_info.enum_by_name: - convertor = generate_enum_name_convertor(main_type) - else: - convertor = generate_enum_convertor(main_type) + enum_by_name = parameter_info.enum_by_name + convertor = determine_type_convertor(main_type, enum_by_name) if is_list: convertor = generate_list_convertor( convertor=convertor, default_value=default_value ) if is_tuple: - convertor = generate_tuple_convertor(get_args(main_type)) + convertor = generate_tuple_convertor(get_args(main_type), enum_by_name) if isinstance(parameter_info, OptionInfo): if main_type is bool and parameter_info.is_flag is not False: is_flag = True From 478d18310704904712c3355bafce46423c05e03e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 11 Sep 2024 17:06:31 +0000 Subject: [PATCH 15/44] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20f?= =?UTF-8?q?ormat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- typer/main.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/typer/main.py b/typer/main.py index 2fbb0218b3..f50ca9eb13 100644 --- a/typer/main.py +++ b/typer/main.py @@ -616,7 +616,9 @@ def get_command_from_info( return command -def determine_type_convertor(type_: Any, enum_by_name: bool) -> Optional[Callable[[Any], Any]]: +def determine_type_convertor( + type_: Any, enum_by_name: bool +) -> Optional[Callable[[Any], Any]]: convertor: Optional[Callable[[Any], Any]] = None if lenient_issubclass(type_, Path): convertor = param_path_convertor @@ -671,7 +673,8 @@ def internal_convertor(value: Sequence[Any]) -> Optional[List[Any]]: def generate_tuple_convertor( - types: Sequence[Any], enum_by_name: bool, + types: Sequence[Any], + enum_by_name: bool, ) -> Callable[[Optional[Tuple[Any, ...]]], Optional[Tuple[Any, ...]]]: convertors = [determine_type_convertor(type_, enum_by_name) for type_ in types] From eee3a4c9bfe456ee9fa31357157f4314517da1d4 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 11 Sep 2024 19:36:29 +0200 Subject: [PATCH 16/44] add Annotated versions of the new tests --- .../tutorial002_an.py | 26 ++++++++++ .../tutorial003_an.py | 26 ++++++++++ .../parameter_types/enum/tutorial004_an.py | 19 +++++++ .../parameter_types/enum/tutorial005_an.py | 19 +++++++ .../parameter_types/enum/tutorial006_an.py | 19 +++++++ .../test_tutorial002_an.py | 49 +++++++++++++++++++ .../test_tutorial003_an.py | 49 +++++++++++++++++++ .../test_enum/test_tutorial004_an.py | 32 ++++++++++++ .../test_enum/test_tutorial005_an.py | 32 ++++++++++++ .../test_enum/test_tutorial006_an.py | 47 ++++++++++++++++++ 10 files changed, 318 insertions(+) create mode 100644 docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py create mode 100644 docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py create mode 100644 docs_src/parameter_types/enum/tutorial004_an.py create mode 100644 docs_src/parameter_types/enum/tutorial005_an.py create mode 100644 docs_src/parameter_types/enum/tutorial006_an.py create mode 100644 tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002_an.py create mode 100644 tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003_an.py create mode 100644 tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004_an.py create mode 100644 tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005_an.py create mode 100644 tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006_an.py diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py b/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py new file mode 100644 index 0000000000..09805da5e0 --- /dev/null +++ b/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py @@ -0,0 +1,26 @@ +from enum import Enum +from typing import Tuple + +import typer +from typing_extensions import Annotated + + +class Food(str, Enum): + f1 = "Eggs" + f2 = "Bacon" + f3 = "Cheese" + + +def main(user: Annotated[Tuple[str, int, bool, Food], typer.Option()] = (None, None, None, Food.f1)): + username, coins, is_wizard, food = user + if not username: + print("No user provided") + raise typer.Abort() + print(f"The username {username} has {coins} coins") + if is_wizard: + print("And this user is a wizard!") + print(f"And they love eating {food.value}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py b/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py new file mode 100644 index 0000000000..638e0cd067 --- /dev/null +++ b/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py @@ -0,0 +1,26 @@ +from enum import Enum +from typing import Tuple + +import typer +from typing_extensions import Annotated + + +class Food(str, Enum): + f1 = "Eggs" + f2 = "Bacon" + f3 = "Cheese" + + +def main(user: Annotated[Tuple[str, int, bool, Food], typer.Option(enum_by_name=True)] = (None, None, None, "f1")): + username, coins, is_wizard, food = user + if not username: + print("No user provided") + raise typer.Abort() + print(f"The username {username} has {coins} coins") + if is_wizard: + print("And this user is a wizard!") + print(f"And they love eating {food.value}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/docs_src/parameter_types/enum/tutorial004_an.py b/docs_src/parameter_types/enum/tutorial004_an.py new file mode 100644 index 0000000000..ca4a15e416 --- /dev/null +++ b/docs_src/parameter_types/enum/tutorial004_an.py @@ -0,0 +1,19 @@ +import enum +import logging + +import typer +from typing_extensions import Annotated + + +class LogLevel(enum.Enum): + debug = logging.DEBUG + info = logging.INFO + warning = logging.WARNING + + +def main(log_level: Annotated[LogLevel, typer.Option(enum_by_name=True)] = "warning"): + typer.echo(f"Log level set to: {logging.getLevelName(log_level.value)}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/docs_src/parameter_types/enum/tutorial005_an.py b/docs_src/parameter_types/enum/tutorial005_an.py new file mode 100644 index 0000000000..8a0d2d4bac --- /dev/null +++ b/docs_src/parameter_types/enum/tutorial005_an.py @@ -0,0 +1,19 @@ +import enum + +import typer +from typing_extensions import Annotated + + +class Access(enum.IntEnum): + private = 1 + protected = 2 + public = 3 + open = 4 + + +def main(access: Annotated[Access, typer.Option(enum_by_name=True)] = "private"): + typer.echo(f"Access level: {access.name} ({access.value})") + + +if __name__ == "__main__": + typer.run(main) diff --git a/docs_src/parameter_types/enum/tutorial006_an.py b/docs_src/parameter_types/enum/tutorial006_an.py new file mode 100644 index 0000000000..03f325df32 --- /dev/null +++ b/docs_src/parameter_types/enum/tutorial006_an.py @@ -0,0 +1,19 @@ +from enum import Enum +from typing import List + +import typer +from typing_extensions import Annotated + + +class Food(str, Enum): + f1 = "Eggs" + f2 = "Bacon" + f3 = "Cheese" + + +def main(groceries: Annotated[List[Food], typer.Option(enum_by_name=True)] = ["f1", "f3"]): + print(f"Buying groceries: {', '.join([f.value for f in groceries])}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002_an.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002_an.py new file mode 100644 index 0000000000..e38c33329e --- /dev/null +++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002_an.py @@ -0,0 +1,49 @@ +import subprocess +import sys + +import typer +from typer.testing import CliRunner + +from docs_src.multiple_values.options_with_multiple_values import tutorial002_an as mod + +runner = CliRunner() +app = typer.Typer() +app.command()(mod.main) + + +def test_main(): + result = runner.invoke(app) + assert result.exit_code != 0 + assert "No user provided" in result.output + assert "Aborted" in result.output + + +def test_user_1(): + result = runner.invoke(app, ["--user", "Camila", "50", "yes", "Eggs"]) + assert result.exit_code == 0 + assert "The username Camila has 50 coins" in result.output + assert "And this user is a wizard!" in result.output + assert "And they love eating Eggs" in result.output + + +def test_user_2(): + result = runner.invoke(app, ["--user", "Morty", "3", "no", "Bacon"]) + assert result.exit_code == 0 + assert "The username Morty has 3 coins" in result.output + assert "And this user is a wizard!" not in result.output + assert "And they love eating Bacon" in result.output + + +def test_invalid_user(): + result = runner.invoke(app, ["--user", "Camila", "50"]) + assert result.exit_code != 0 + assert "Option '--user' requires 4 arguments" 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 diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003_an.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003_an.py new file mode 100644 index 0000000000..1f8da4a329 --- /dev/null +++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003_an.py @@ -0,0 +1,49 @@ +import subprocess +import sys + +import typer +from typer.testing import CliRunner + +from docs_src.multiple_values.options_with_multiple_values import tutorial003_an as mod + +runner = CliRunner() +app = typer.Typer() +app.command()(mod.main) + + +def test_main(): + result = runner.invoke(app) + assert result.exit_code != 0 + assert "No user provided" in result.output + assert "Aborted" in result.output + + +def test_user_1(): + result = runner.invoke(app, ["--user", "Camila", "50", "yes", "f1"]) + assert result.exit_code == 0 + assert "The username Camila has 50 coins" in result.output + assert "And this user is a wizard!" in result.output + assert "And they love eating Eggs" in result.output + + +def test_user_2(): + result = runner.invoke(app, ["--user", "Morty", "3", "no", "f2"]) + assert result.exit_code == 0 + assert "The username Morty has 3 coins" in result.output + assert "And this user is a wizard!" not in result.output + assert "And they love eating Bacon" in result.output + + +def test_invalid_user(): + result = runner.invoke(app, ["--user", "Camila", "50"]) + assert result.exit_code != 0 + assert "Option '--user' requires 4 arguments" 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 diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004_an.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004_an.py new file mode 100644 index 0000000000..d183ad3cb9 --- /dev/null +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004_an.py @@ -0,0 +1,32 @@ +import subprocess + +import typer +from typer.testing import CliRunner + +from docs_src.parameter_types.enum import tutorial004_an as mod + +runner = CliRunner() + +app = typer.Typer() +app.command()(mod.main) + + +def test_enum_names_default(): + result = runner.invoke(app) + assert result.exit_code == 0 + assert "Log level set to: WARNING" in result.output + + +def test_enum_names(): + result = runner.invoke(app, ["--log-level", "debug"]) + assert result.exit_code == 0 + assert "Log level set to: DEBUG" in result.output + + +def test_script(): + result = subprocess.run( + ["coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005_an.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005_an.py new file mode 100644 index 0000000000..7e7ffcd02c --- /dev/null +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005_an.py @@ -0,0 +1,32 @@ +import subprocess + +import typer +from typer.testing import CliRunner + +from docs_src.parameter_types.enum import tutorial005_an as mod + +runner = CliRunner() + +app = typer.Typer() +app.command()(mod.main) + + +def test_int_enum_default(): + result = runner.invoke(app) + assert result.exit_code == 0 + assert "Access level: private (1)" in result.output + + +def test_int_enum(): + result = runner.invoke(app, ["--access", "open"]) + assert result.exit_code == 0 + assert "Access level: open (4)" in result.output + + +def test_script(): + result = subprocess.run( + ["coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006_an.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006_an.py new file mode 100644 index 0000000000..695a817863 --- /dev/null +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006_an.py @@ -0,0 +1,47 @@ +import subprocess +import sys + +import typer +from typer.testing import CliRunner + +from docs_src.parameter_types.enum import tutorial006_an as mod + +runner = CliRunner() + +app = typer.Typer() +app.command()(mod.main) + + +def test_help(): + result = runner.invoke(app, ["--help"]) + assert result.exit_code == 0 + assert "--groceries" in result.output + assert "[f1|f2|f3]" in result.output + assert "default: f1, f3" in result.output + + +def test_call_no_arg(): + result = runner.invoke(app) + assert result.exit_code == 0 + assert "Buying groceries: Eggs, Cheese" in result.output + + +def test_call_single_arg(): + result = runner.invoke(app, ["--groceries", "f2"]) + assert result.exit_code == 0 + assert "Buying groceries: Bacon" in result.output + + +def test_call_multiple_arg(): + result = runner.invoke(app, ["--groceries", "f1", "--groceries", "f2"]) + assert result.exit_code == 0 + assert "Buying groceries: Eggs, Bacon" 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 e2053b18dd540197900e67a2266d91458e76215c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 11 Sep 2024 17:37:57 +0000 Subject: [PATCH 17/44] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20f?= =?UTF-8?q?ormat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../options_with_multiple_values/tutorial002_an.py | 9 ++++++++- .../options_with_multiple_values/tutorial003_an.py | 9 ++++++++- docs_src/parameter_types/enum/tutorial006_an.py | 4 +++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py b/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py index 09805da5e0..c28f832681 100644 --- a/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py +++ b/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py @@ -11,7 +11,14 @@ class Food(str, Enum): f3 = "Cheese" -def main(user: Annotated[Tuple[str, int, bool, Food], typer.Option()] = (None, None, None, Food.f1)): +def main( + user: Annotated[Tuple[str, int, bool, Food], typer.Option()] = ( + None, + None, + None, + Food.f1, + ), +): username, coins, is_wizard, food = user if not username: print("No user provided") diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py b/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py index 638e0cd067..534825977f 100644 --- a/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py +++ b/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py @@ -11,7 +11,14 @@ class Food(str, Enum): f3 = "Cheese" -def main(user: Annotated[Tuple[str, int, bool, Food], typer.Option(enum_by_name=True)] = (None, None, None, "f1")): +def main( + user: Annotated[Tuple[str, int, bool, Food], typer.Option(enum_by_name=True)] = ( + None, + None, + None, + "f1", + ), +): username, coins, is_wizard, food = user if not username: print("No user provided") diff --git a/docs_src/parameter_types/enum/tutorial006_an.py b/docs_src/parameter_types/enum/tutorial006_an.py index 03f325df32..bbe605428d 100644 --- a/docs_src/parameter_types/enum/tutorial006_an.py +++ b/docs_src/parameter_types/enum/tutorial006_an.py @@ -11,7 +11,9 @@ class Food(str, Enum): f3 = "Cheese" -def main(groceries: Annotated[List[Food], typer.Option(enum_by_name=True)] = ["f1", "f3"]): +def main( + groceries: Annotated[List[Food], typer.Option(enum_by_name=True)] = ["f1", "f3"], +): print(f"Buying groceries: {', '.join([f.value for f in groceries])}") From 221a8655d2b571392765236dcd01ad9f5c168085 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Thu, 12 Sep 2024 13:40:32 +0200 Subject: [PATCH 18/44] ignore 006 tutorial just like 003 (mutable default argument) --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index ce9d61afa3..e936bb6d22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -184,6 +184,7 @@ ignore = [ "docs_src/options_autocompletion/tutorial008_an.py" = ["B006"] "docs_src/options_autocompletion/tutorial009_an.py" = ["B006"] "docs_src/parameter_types/enum/tutorial003_an.py" = ["B006"] +"docs_src/parameter_types/enum/tutorial006_an.py" = ["B006"] # Loop control variable `value` not used within loop body "docs_src/progressbar/tutorial001.py" = ["B007"] "docs_src/progressbar/tutorial003.py" = ["B007"] From 7b599341f9efef7fd8cbade1ca839f3568a56e3b Mon Sep 17 00:00:00 2001 From: svlandeg Date: Thu, 12 Sep 2024 14:05:34 +0200 Subject: [PATCH 19/44] update enum.md to use the annotated versions as well --- docs/tutorial/parameter-types/enum.md | 116 +++++++++++++++++++++----- 1 file changed, 97 insertions(+), 19 deletions(-) diff --git a/docs/tutorial/parameter-types/enum.md b/docs/tutorial/parameter-types/enum.md index efdb2716ce..95aed2bee0 100644 --- a/docs/tutorial/parameter-types/enum.md +++ b/docs/tutorial/parameter-types/enum.md @@ -96,6 +96,77 @@ Training neural network of type: lstm +### Using Enum names instead of values + +Some times you want to accept `Enum` names from the command line and convert +that into `Enum` values in the command handler. You can enable this by setting +`enum_by_name=True`: + +//// tab | Python 3.7+ + +```Python hl_lines="14" +{!> ../docs_src/parameter_types/enum/tutorial004_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated + +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="13" +{!> ../docs_src/parameter_types/enum/tutorial004.py!} +``` + +//// + +And then the names of the `Enum` will be used instead of values: + +
+ +```console +$ python main.py --log-level debug + +Log level set to DEBUG +``` + +
+ +This can be particularly useful if the enum values are not strings: + +//// tab | Python 3.7+ + +```Python hl_lines="8-11, 14" +{!> ../docs_src/parameter_types/enum/tutorial005_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated + +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="7-10, 13" +{!../docs_src/parameter_types/enum/tutorial005.py!} +``` + +//// + +```console +$ python main.py --access protected + +Access level: protected (2) +``` + + ### List of Enum values A *CLI parameter* can also take a list of `Enum` values: @@ -154,37 +225,44 @@ Buying groceries: Eggs, Bacon +You can also combine `enum_by_name=True` with a list of enums: -### Using Enum names instead of values - -Some times you want to accept `Enum` names from the command line and convert -that into `Enum` values in the command handler. You can enable this by setting -`enum_by_name=True`: +//// tab | Python 3.7+ -```Python hl_lines="14" -{!../docs_src/parameter_types/enum/tutorial004.py!} +```Python hl_lines="15" +{!> ../docs_src/parameter_types/enum/tutorial006_an.py!} ``` -And then the names of the `Enum` will be used instead of values: +//// -
+//// tab | Python 3.7+ non-Annotated -```console -$ python main.py --log-level debug +/// tip -Log level set to DEBUG +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="13" +{!> ../docs_src/parameter_types/enum/tutorial006.py!} ``` -
+//// -This can be particularly useful if the enum values are not strings: +This works exactly the same, but you're using the enum names instead of values: -```Python hl_lines="7-10, 13" -{!../docs_src/parameter_types/enum/tutorial005.py!} -``` +
```console -$ python main.py --access protected +// Try it with a single value +$ python main.py --groceries "f1" -Access level: protected (2) +Buying groceries: Eggs + +// Try it with multiple values +$ python main.py --groceries "f1" --groceries "f2" + +Buying groceries: Eggs, Bacon ``` + +
\ No newline at end of file From 5759360e1a9355218c3e4a74f3f72d90b71ff8ce Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 12 Sep 2024 12:06:33 +0000 Subject: [PATCH 20/44] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20f?= =?UTF-8?q?ormat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/tutorial/parameter-types/enum.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/parameter-types/enum.md b/docs/tutorial/parameter-types/enum.md index 95aed2bee0..3eea7b291a 100644 --- a/docs/tutorial/parameter-types/enum.md +++ b/docs/tutorial/parameter-types/enum.md @@ -265,4 +265,4 @@ $ python main.py --groceries "f1" --groceries "f2" Buying groceries: Eggs, Bacon ``` - \ No newline at end of file + From 87ae5add6d1121ea4a0b3746f6cebcbee6b53b7b Mon Sep 17 00:00:00 2001 From: svlandeg Date: Thu, 12 Sep 2024 14:28:56 +0200 Subject: [PATCH 21/44] add tests with Argument --- docs/tutorial/parameter-types/enum.md | 2 +- .../tutorial003.py | 25 +++++++++ .../tutorial003_an.py | 25 +++++++++ .../test_tutorial003.py | 52 ++++++++++++++++++ .../test_tutorial003_an.py | 54 +++++++++++++++++++ 5 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py create mode 100644 docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py create mode 100644 tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py create mode 100644 tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py diff --git a/docs/tutorial/parameter-types/enum.md b/docs/tutorial/parameter-types/enum.md index 95aed2bee0..aa30a7bcdd 100644 --- a/docs/tutorial/parameter-types/enum.md +++ b/docs/tutorial/parameter-types/enum.md @@ -98,7 +98,7 @@ Training neural network of type: lstm ### Using Enum names instead of values -Some times you want to accept `Enum` names from the command line and convert +Sometimes you want to accept `Enum` names from the command line and convert that into `Enum` values in the command handler. You can enable this by setting `enum_by_name=True`: diff --git a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py new file mode 100644 index 0000000000..ddf69e6333 --- /dev/null +++ b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py @@ -0,0 +1,25 @@ +from enum import Enum +from typing import Tuple + +import typer + + +class SuperHero(str, Enum): + hero1 = "Superman" + hero2 = "Spiderman" + hero3 = "Wonder woman" + + +def main( + names: Tuple[str, str, str, SuperHero] = typer.Argument( + ("Harry", "Hermione", "Ron", "hero3"), + enum_by_name=True, + help="Select 4 characters to play with", + ), +): + for name in names: + print(f"Hello {name}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py new file mode 100644 index 0000000000..59d1b65bd1 --- /dev/null +++ b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py @@ -0,0 +1,25 @@ +from enum import Enum +from typing import Tuple + +import typer +from typing_extensions import Annotated + + +class SuperHero(str, Enum): + hero1 = "Superman" + hero2 = "Spiderman" + hero3 = "Wonder woman" + + +def main( + names: Annotated[ + Tuple[str, str, str, SuperHero], + typer.Argument(enum_by_name=True, help="Select 4 characters to play with"), + ] = ("Harry", "Hermione", "Ron", "hero3"), +): + for name in names: + print(f"Hello {name}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py new file mode 100644 index 0000000000..7e53566854 --- /dev/null +++ b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py @@ -0,0 +1,52 @@ +import subprocess +import sys + +import typer +from typer.testing import CliRunner + +from docs_src.multiple_values.arguments_with_multiple_values import tutorial003 as mod + +runner = CliRunner() +app = typer.Typer() +app.command()(mod.main) + + +def test_help(): + result = runner.invoke(app, ["--help"]) + assert result.exit_code == 0 + assert "[OPTIONS] [NAMES]..." in result.output + assert "Arguments" in result.output + assert "[default: Harry, Hermione, Ron, hero3]" in result.output + + +def test_defaults(): + result = runner.invoke(app) + assert result.exit_code == 0 + assert "Hello Harry" in result.output + assert "Hello Hermione" in result.output + assert "Hello Ron" in result.output + assert "Hello Wonder woman" in result.output + + +def test_invalid_args(): + result = runner.invoke(app, ["Draco", "Hagrid"]) + assert result.exit_code != 0 + assert "Argument 'names' takes 4 values" in result.stdout + + +def test_valid_args(): + result = runner.invoke(app, ["Draco", "Hagrid", "Dobby", "hero1"]) + assert result.exit_code == 0 + assert "Hello Draco" in result.stdout + assert "Hello Hagrid" in result.stdout + assert "Hello Dobby" in result.stdout + assert "Hello Superman" 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 diff --git a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py new file mode 100644 index 0000000000..814133dec4 --- /dev/null +++ b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py @@ -0,0 +1,54 @@ +import subprocess +import sys + +import typer +from typer.testing import CliRunner + +from docs_src.multiple_values.arguments_with_multiple_values import ( + tutorial003_an as mod, +) + +runner = CliRunner() +app = typer.Typer() +app.command()(mod.main) + + +def test_help(): + result = runner.invoke(app, ["--help"]) + assert result.exit_code == 0 + assert "[OPTIONS] [NAMES]..." in result.output + assert "Arguments" in result.output + assert "[default: Harry, Hermione, Ron, hero3]" in result.output + + +def test_defaults(): + result = runner.invoke(app) + assert result.exit_code == 0 + assert "Hello Harry" in result.output + assert "Hello Hermione" in result.output + assert "Hello Ron" in result.output + assert "Hello Wonder woman" in result.output + + +def test_invalid_args(): + result = runner.invoke(app, ["Draco", "Hagrid"]) + assert result.exit_code != 0 + assert "Argument 'names' takes 4 values" in result.stdout + + +def test_valid_args(): + result = runner.invoke(app, ["Draco", "Hagrid", "Dobby", "hero1"]) + assert result.exit_code == 0 + assert "Hello Draco" in result.stdout + assert "Hello Hagrid" in result.stdout + assert "Hello Dobby" in result.stdout + assert "Hello Superman" 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 adb7c03c98e19e0d7a3c6691eedca69eabfee63a Mon Sep 17 00:00:00 2001 From: svlandeg Date: Thu, 12 Sep 2024 15:26:12 +0200 Subject: [PATCH 22/44] fix printing of enum value (needed for Python 3.11 and 3.12) --- .../arguments_with_multiple_values/tutorial003.py | 5 ++++- .../arguments_with_multiple_values/tutorial003_an.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py index ddf69e6333..f752bdef87 100644 --- a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py +++ b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py @@ -18,7 +18,10 @@ def main( ), ): for name in names: - print(f"Hello {name}") + if isinstance(name, Enum): + print(f"Hello {name.value}") + else: + print(f"Hello {name}") if __name__ == "__main__": diff --git a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py index 59d1b65bd1..ac07a64b3d 100644 --- a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py +++ b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py @@ -18,7 +18,10 @@ def main( ] = ("Harry", "Hermione", "Ron", "hero3"), ): for name in names: - print(f"Hello {name}") + if isinstance(name, Enum): + print(f"Hello {name.value}") + else: + print(f"Hello {name}") if __name__ == "__main__": From 9ad26c2bfa3fc7bd21a3e882e46a05ad46cdd72d Mon Sep 17 00:00:00 2001 From: svlandeg Date: Thu, 12 Sep 2024 15:48:08 +0200 Subject: [PATCH 23/44] remove lowercasing from generator function - should be done with case_sensitive flag --- .../arguments_with_multiple_values/tutorial003.py | 1 + .../arguments_with_multiple_values/tutorial003_an.py | 2 +- .../test_tutorial003_an.py | 2 +- typer/main.py | 8 ++++---- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py index f752bdef87..bbfd575643 100644 --- a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py +++ b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py @@ -14,6 +14,7 @@ def main( names: Tuple[str, str, str, SuperHero] = typer.Argument( ("Harry", "Hermione", "Ron", "hero3"), enum_by_name=True, + case_sensitive=False, help="Select 4 characters to play with", ), ): diff --git a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py index ac07a64b3d..662b9d7fa2 100644 --- a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py +++ b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py @@ -14,7 +14,7 @@ class SuperHero(str, Enum): def main( names: Annotated[ Tuple[str, str, str, SuperHero], - typer.Argument(enum_by_name=True, help="Select 4 characters to play with"), + typer.Argument(enum_by_name=True, help="Select 4 characters to play with", case_sensitive=False), ] = ("Harry", "Hermione", "Ron", "hero3"), ): for name in names: diff --git a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py index 814133dec4..6e5f8c2d09 100644 --- a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py +++ b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py @@ -37,7 +37,7 @@ def test_invalid_args(): def test_valid_args(): - result = runner.invoke(app, ["Draco", "Hagrid", "Dobby", "hero1"]) + result = runner.invoke(app, ["Draco", "Hagrid", "Dobby", "HERO1"]) assert result.exit_code == 0 assert "Hello Draco" in result.stdout assert "Hello Hagrid" in result.stdout diff --git a/typer/main.py b/typer/main.py index f50ca9eb13..462f6d65bf 100644 --- a/typer/main.py +++ b/typer/main.py @@ -650,13 +650,13 @@ def convertor(value: Any) -> Any: def generate_enum_name_convertor(enum: Type[Enum]) -> Callable[..., Any]: - lower_name_map = {str(item.name).lower(): item for item in enum} + val_map = {str(item.name): item for item in enum} def convertor(value: Any) -> Any: if value is not None: - low = str(value).lower() - if low in lower_name_map: - return lower_name_map[low] + val = str(value) + if val in val_map: + return val_map[val] return convertor From 6ae6c9bd7e1faa6cecbdedde9dc2069587e1377f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 12 Sep 2024 13:48:26 +0000 Subject: [PATCH 24/44] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20f?= =?UTF-8?q?ormat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../arguments_with_multiple_values/tutorial003_an.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py index 662b9d7fa2..801ee8c6ce 100644 --- a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py +++ b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py @@ -14,7 +14,11 @@ class SuperHero(str, Enum): def main( names: Annotated[ Tuple[str, str, str, SuperHero], - typer.Argument(enum_by_name=True, help="Select 4 characters to play with", case_sensitive=False), + typer.Argument( + enum_by_name=True, + help="Select 4 characters to play with", + case_sensitive=False, + ), ] = ("Harry", "Hermione", "Ron", "hero3"), ): for name in names: From bfae3efe46ec012c4de15ac369f969ec84d9be67 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Thu, 12 Sep 2024 16:50:20 +0200 Subject: [PATCH 25/44] fix hl_lines --- docs/tutorial/parameter-types/enum.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/parameter-types/enum.md b/docs/tutorial/parameter-types/enum.md index 4b6bd25ed5..7064070cba 100644 --- a/docs/tutorial/parameter-types/enum.md +++ b/docs/tutorial/parameter-types/enum.md @@ -140,7 +140,7 @@ This can be particularly useful if the enum values are not strings: //// tab | Python 3.7+ -```Python hl_lines="8-11, 14" +```Python hl_lines="8-11 14" {!> ../docs_src/parameter_types/enum/tutorial005_an.py!} ``` @@ -154,7 +154,7 @@ Prefer to use the `Annotated` version if possible. /// -```Python hl_lines="7-10, 13" +```Python hl_lines="7-10 13" {!../docs_src/parameter_types/enum/tutorial005.py!} ``` From 7f4f4e1c9878ea9945f953a14d8ec3096a455006 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Thu, 28 Aug 2025 13:50:03 +0200 Subject: [PATCH 26/44] use new format --- docs/tutorial/parameter-types/enum.md | 64 ++------------------------- 1 file changed, 3 insertions(+), 61 deletions(-) diff --git a/docs/tutorial/parameter-types/enum.md b/docs/tutorial/parameter-types/enum.md index 2442515614..7e818912d7 100644 --- a/docs/tutorial/parameter-types/enum.md +++ b/docs/tutorial/parameter-types/enum.md @@ -80,27 +80,7 @@ Sometimes you want to accept `Enum` names from the command line and convert that into `Enum` values in the command handler. You can enable this by setting `enum_by_name=True`: -//// tab | Python 3.7+ - -```Python hl_lines="14" -{!> ../docs_src/parameter_types/enum/tutorial004_an.py!} -``` - -//// - -//// tab | Python 3.7+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="13" -{!> ../docs_src/parameter_types/enum/tutorial004.py!} -``` - -//// +{* docs_src/parameter_types/enum/tutorial004_an.py hl[14] *} And then the names of the `Enum` will be used instead of values: @@ -116,27 +96,7 @@ Log level set to DEBUG This can be particularly useful if the enum values are not strings: -//// tab | Python 3.7+ - -```Python hl_lines="8-11 14" -{!> ../docs_src/parameter_types/enum/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.7+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="7-10 13" -{!../docs_src/parameter_types/enum/tutorial005.py!} -``` - -//// +{* docs_src/parameter_types/enum/tutorial005_an.py hl[8:11,14] *} ```console $ python main.py --access protected @@ -187,25 +147,7 @@ You can also combine `enum_by_name=True` with a list of enums: //// tab | Python 3.7+ -```Python hl_lines="15" -{!> ../docs_src/parameter_types/enum/tutorial006_an.py!} -``` - -//// - -//// tab | Python 3.7+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="13" -{!> ../docs_src/parameter_types/enum/tutorial006.py!} -``` - -//// +{* docs_src/parameter_types/enum/tutorial006_an.py hl[15] *} This works exactly the same, but you're using the enum names instead of values: From c0107fbc717c0aa808fab1e0c267b73c8954fd01 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Thu, 28 Aug 2025 13:53:21 +0200 Subject: [PATCH 27/44] fix --- docs/tutorial/parameter-types/enum.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/tutorial/parameter-types/enum.md b/docs/tutorial/parameter-types/enum.md index 7e818912d7..c807e1bd62 100644 --- a/docs/tutorial/parameter-types/enum.md +++ b/docs/tutorial/parameter-types/enum.md @@ -145,8 +145,6 @@ Buying groceries: Eggs, Bacon You can also combine `enum_by_name=True` with a list of enums: -//// tab | Python 3.7+ - {* docs_src/parameter_types/enum/tutorial006_an.py hl[15] *} This works exactly the same, but you're using the enum names instead of values: From 857e2eac2e0a2f18a171789939d2c48dc456612d Mon Sep 17 00:00:00 2001 From: svlandeg Date: Thu, 28 Aug 2025 14:08:21 +0200 Subject: [PATCH 28/44] fix tests by checking output instead of stdout --- .../test_arguments_with_multiple_values/test_tutorial003.py | 2 +- .../test_arguments_with_multiple_values/test_tutorial003_an.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py index 7e53566854..5289683ad9 100644 --- a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py +++ b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py @@ -31,7 +31,7 @@ def test_defaults(): def test_invalid_args(): result = runner.invoke(app, ["Draco", "Hagrid"]) assert result.exit_code != 0 - assert "Argument 'names' takes 4 values" in result.stdout + assert "Argument 'names' takes 4 values" in result.output def test_valid_args(): diff --git a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py index 6e5f8c2d09..0d8b6f6b65 100644 --- a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py +++ b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py @@ -33,7 +33,7 @@ def test_defaults(): def test_invalid_args(): result = runner.invoke(app, ["Draco", "Hagrid"]) assert result.exit_code != 0 - assert "Argument 'names' takes 4 values" in result.stdout + assert "Argument 'names' takes 4 values" in result.output def test_valid_args(): From 727f06c59b0846e0c88c2208dba991ffd49db661 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Tue, 7 Oct 2025 16:35:10 +0200 Subject: [PATCH 29/44] move 007 to 004 to avoid conflict with master --- docs/tutorial/parameter-types/enum.md | 2 +- docs_src/parameter_types/enum/tutorial007.py | 18 +++++++++++ .../parameter_types/enum/tutorial007_an.py | 19 +++++++++++ .../test_enum/test_tutorial007.py | 32 +++++++++++++++++++ .../test_enum/test_tutorial007_an.py | 32 +++++++++++++++++++ 5 files changed, 102 insertions(+), 1 deletion(-) diff --git a/docs/tutorial/parameter-types/enum.md b/docs/tutorial/parameter-types/enum.md index 8b57ec6ee2..7e466bbb89 100644 --- a/docs/tutorial/parameter-types/enum.md +++ b/docs/tutorial/parameter-types/enum.md @@ -80,7 +80,7 @@ Sometimes you want to accept `Enum` names from the command line and convert that into `Enum` values in the command handler. You can enable this by setting `enum_by_name=True`: -{* docs_src/parameter_types/enum/tutorial004_an.py hl[14] *} +{* docs_src/parameter_types/enum/tutorial007_an.py hl[14] *} And then the names of the `Enum` will be used instead of values: diff --git a/docs_src/parameter_types/enum/tutorial007.py b/docs_src/parameter_types/enum/tutorial007.py index e69de29bb2..d2ecd0c16d 100644 --- a/docs_src/parameter_types/enum/tutorial007.py +++ b/docs_src/parameter_types/enum/tutorial007.py @@ -0,0 +1,18 @@ +import enum +import logging + +import typer + + +class LogLevel(enum.Enum): + debug = logging.DEBUG + info = logging.INFO + warning = logging.WARNING + + +def main(log_level: LogLevel = typer.Option("warning", enum_by_name=True)): + typer.echo(f"Log level set to: {logging.getLevelName(log_level.value)}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/docs_src/parameter_types/enum/tutorial007_an.py b/docs_src/parameter_types/enum/tutorial007_an.py index e69de29bb2..ca4a15e416 100644 --- a/docs_src/parameter_types/enum/tutorial007_an.py +++ b/docs_src/parameter_types/enum/tutorial007_an.py @@ -0,0 +1,19 @@ +import enum +import logging + +import typer +from typing_extensions import Annotated + + +class LogLevel(enum.Enum): + debug = logging.DEBUG + info = logging.INFO + warning = logging.WARNING + + +def main(log_level: Annotated[LogLevel, typer.Option(enum_by_name=True)] = "warning"): + typer.echo(f"Log level set to: {logging.getLevelName(log_level.value)}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007.py index e69de29bb2..bcca3f3a67 100644 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007.py +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007.py @@ -0,0 +1,32 @@ +import subprocess + +import typer +from typer.testing import CliRunner + +from docs_src.parameter_types.enum import tutorial007 as mod + +runner = CliRunner() + +app = typer.Typer() +app.command()(mod.main) + + +def test_enum_names_default(): + result = runner.invoke(app) + assert result.exit_code == 0 + assert "Log level set to: WARNING" in result.output + + +def test_enum_names(): + result = runner.invoke(app, ["--log-level", "debug"]) + assert result.exit_code == 0 + assert "Log level set to: DEBUG" in result.output + + +def test_script(): + result = subprocess.run( + ["coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007_an.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007_an.py index e69de29bb2..d183ad3cb9 100644 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007_an.py +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007_an.py @@ -0,0 +1,32 @@ +import subprocess + +import typer +from typer.testing import CliRunner + +from docs_src.parameter_types.enum import tutorial004_an as mod + +runner = CliRunner() + +app = typer.Typer() +app.command()(mod.main) + + +def test_enum_names_default(): + result = runner.invoke(app) + assert result.exit_code == 0 + assert "Log level set to: WARNING" in result.output + + +def test_enum_names(): + result = runner.invoke(app, ["--log-level", "debug"]) + assert result.exit_code == 0 + assert "Log level set to: DEBUG" in result.output + + +def test_script(): + result = subprocess.run( + ["coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout From 2a5e8c4f7ac28a36d689429721e198f7702e397f Mon Sep 17 00:00:00 2001 From: svlandeg Date: Tue, 7 Oct 2025 16:43:57 +0200 Subject: [PATCH 30/44] fix mod --- .../test_parameter_types/test_enum/test_tutorial007_an.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007_an.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007_an.py index d183ad3cb9..32839a002d 100644 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007_an.py +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007_an.py @@ -3,7 +3,7 @@ import typer from typer.testing import CliRunner -from docs_src.parameter_types.enum import tutorial004_an as mod +from docs_src.parameter_types.enum import tutorial007_an as mod runner = CliRunner() From 3d6df84cc4437bcf8689dfb5981ac8a1f0f79296 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Tue, 25 Nov 2025 18:09:17 +0100 Subject: [PATCH 31/44] update the tutorial examples to use explicit Typer() instance --- docs/tutorial/parameter-types/enum.md | 6 +++--- .../arguments_with_multiple_values/tutorial003.py | 6 +++++- .../arguments_with_multiple_values/tutorial003_an.py | 6 +++++- .../options_with_multiple_values/tutorial002.py | 6 +++++- .../options_with_multiple_values/tutorial002_an.py | 6 +++++- .../options_with_multiple_values/tutorial003.py | 6 +++++- .../options_with_multiple_values/tutorial003_an.py | 6 +++++- docs_src/parameter_types/enum/tutorial005.py | 6 +++++- docs_src/parameter_types/enum/tutorial005_an.py | 6 +++++- docs_src/parameter_types/enum/tutorial006.py | 6 +++++- docs_src/parameter_types/enum/tutorial006_an.py | 6 +++++- docs_src/parameter_types/enum/tutorial007.py | 6 +++++- docs_src/parameter_types/enum/tutorial007_an.py | 6 +++++- .../test_arguments_with_multiple_values/test_tutorial003.py | 4 +--- .../test_tutorial003_an.py | 4 +--- .../test_options_with_multiple_values/test_tutorial002.py | 4 +--- .../test_tutorial002_an.py | 4 +--- .../test_options_with_multiple_values/test_tutorial003.py | 4 +--- .../test_tutorial003_an.py | 4 +--- .../test_parameter_types/test_enum/test_tutorial005.py | 5 +---- .../test_parameter_types/test_enum/test_tutorial005_an.py | 5 +---- .../test_parameter_types/test_enum/test_tutorial006.py | 5 +---- .../test_parameter_types/test_enum/test_tutorial006_an.py | 5 +---- .../test_parameter_types/test_enum/test_tutorial007.py | 5 +---- .../test_parameter_types/test_enum/test_tutorial007_an.py | 5 +---- 25 files changed, 75 insertions(+), 57 deletions(-) diff --git a/docs/tutorial/parameter-types/enum.md b/docs/tutorial/parameter-types/enum.md index 970567c8b0..a212b1a8d8 100644 --- a/docs/tutorial/parameter-types/enum.md +++ b/docs/tutorial/parameter-types/enum.md @@ -80,7 +80,7 @@ Sometimes you want to accept `Enum` names from the command line and convert that into `Enum` values in the command handler. You can enable this by setting `enum_by_name=True`: -{* docs_src/parameter_types/enum/tutorial007_an.py hl[14] *} +{* docs_src/parameter_types/enum/tutorial007_an.py hl[18] *} And then the names of the `Enum` will be used instead of values: @@ -96,7 +96,7 @@ Log level set to DEBUG This can be particularly useful if the enum values are not strings: -{* docs_src/parameter_types/enum/tutorial005_an.py hl[8:11,14] *} +{* docs_src/parameter_types/enum/tutorial005_an.py hl[8:11,18] *} ```console $ python main.py --access protected @@ -145,7 +145,7 @@ Buying groceries: Eggs, Bacon You can also combine `enum_by_name=True` with a list of enums: -{* docs_src/parameter_types/enum/tutorial006_an.py hl[15] *} +{* docs_src/parameter_types/enum/tutorial006_an.py hl[19] *} This works exactly the same, but you're using the enum names instead of values: diff --git a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py index bbfd575643..0d23930f8e 100644 --- a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py +++ b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py @@ -10,6 +10,10 @@ class SuperHero(str, Enum): hero3 = "Wonder woman" +app = typer.Typer() + + +@app.command() def main( names: Tuple[str, str, str, SuperHero] = typer.Argument( ("Harry", "Hermione", "Ron", "hero3"), @@ -26,4 +30,4 @@ def main( if __name__ == "__main__": - typer.run(main) + app() diff --git a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py index 801ee8c6ce..ed84ca0699 100644 --- a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py +++ b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py @@ -11,6 +11,10 @@ class SuperHero(str, Enum): hero3 = "Wonder woman" +app = typer.Typer() + + +@app.command() def main( names: Annotated[ Tuple[str, str, str, SuperHero], @@ -29,4 +33,4 @@ def main( if __name__ == "__main__": - typer.run(main) + app() diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial002.py b/docs_src/multiple_values/options_with_multiple_values/tutorial002.py index bf3a2bf556..01b92bfabe 100644 --- a/docs_src/multiple_values/options_with_multiple_values/tutorial002.py +++ b/docs_src/multiple_values/options_with_multiple_values/tutorial002.py @@ -10,6 +10,10 @@ class Food(str, Enum): f3 = "Cheese" +app = typer.Typer() + + +@app.command() def main(user: Tuple[str, int, bool, Food] = typer.Option((None, None, None, Food.f1))): username, coins, is_wizard, food = user if not username: @@ -22,4 +26,4 @@ def main(user: Tuple[str, int, bool, Food] = typer.Option((None, None, None, Foo if __name__ == "__main__": - typer.run(main) + app() diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py b/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py index c28f832681..34e168e8f9 100644 --- a/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py +++ b/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py @@ -11,6 +11,10 @@ class Food(str, Enum): f3 = "Cheese" +app = typer.Typer() + + +@app.command() def main( user: Annotated[Tuple[str, int, bool, Food], typer.Option()] = ( None, @@ -30,4 +34,4 @@ def main( if __name__ == "__main__": - typer.run(main) + app() diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial003.py b/docs_src/multiple_values/options_with_multiple_values/tutorial003.py index 94715e1a13..0277b39d74 100644 --- a/docs_src/multiple_values/options_with_multiple_values/tutorial003.py +++ b/docs_src/multiple_values/options_with_multiple_values/tutorial003.py @@ -10,6 +10,10 @@ class Food(str, Enum): f3 = "Cheese" +app = typer.Typer() + + +@app.command() def main( user: Tuple[str, int, bool, Food] = typer.Option( (None, None, None, "f1"), enum_by_name=True @@ -26,4 +30,4 @@ def main( if __name__ == "__main__": - typer.run(main) + app() diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py b/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py index 534825977f..2c0353f77d 100644 --- a/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py +++ b/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py @@ -11,6 +11,10 @@ class Food(str, Enum): f3 = "Cheese" +app = typer.Typer() + + +@app.command() def main( user: Annotated[Tuple[str, int, bool, Food], typer.Option(enum_by_name=True)] = ( None, @@ -30,4 +34,4 @@ def main( if __name__ == "__main__": - typer.run(main) + app() diff --git a/docs_src/parameter_types/enum/tutorial005.py b/docs_src/parameter_types/enum/tutorial005.py index 2804d3960f..fc53a87a30 100644 --- a/docs_src/parameter_types/enum/tutorial005.py +++ b/docs_src/parameter_types/enum/tutorial005.py @@ -10,9 +10,13 @@ class Access(enum.IntEnum): open = 4 +app = typer.Typer() + + +@app.command() def main(access: Access = typer.Option("private", enum_by_name=True)): typer.echo(f"Access level: {access.name} ({access.value})") if __name__ == "__main__": - typer.run(main) + app() diff --git a/docs_src/parameter_types/enum/tutorial005_an.py b/docs_src/parameter_types/enum/tutorial005_an.py index 8a0d2d4bac..37372087b5 100644 --- a/docs_src/parameter_types/enum/tutorial005_an.py +++ b/docs_src/parameter_types/enum/tutorial005_an.py @@ -11,9 +11,13 @@ class Access(enum.IntEnum): open = 4 +app = typer.Typer() + + +@app.command() def main(access: Annotated[Access, typer.Option(enum_by_name=True)] = "private"): typer.echo(f"Access level: {access.name} ({access.value})") if __name__ == "__main__": - typer.run(main) + app() diff --git a/docs_src/parameter_types/enum/tutorial006.py b/docs_src/parameter_types/enum/tutorial006.py index 6c8a238dd1..34e593a781 100644 --- a/docs_src/parameter_types/enum/tutorial006.py +++ b/docs_src/parameter_types/enum/tutorial006.py @@ -10,9 +10,13 @@ class Food(str, Enum): f3 = "Cheese" +app = typer.Typer() + + +@app.command() def main(groceries: List[Food] = typer.Option(["f1", "f3"], enum_by_name=True)): print(f"Buying groceries: {', '.join([f.value for f in groceries])}") if __name__ == "__main__": - typer.run(main) + app() diff --git a/docs_src/parameter_types/enum/tutorial006_an.py b/docs_src/parameter_types/enum/tutorial006_an.py index bbe605428d..e1c1a8d4d4 100644 --- a/docs_src/parameter_types/enum/tutorial006_an.py +++ b/docs_src/parameter_types/enum/tutorial006_an.py @@ -11,6 +11,10 @@ class Food(str, Enum): f3 = "Cheese" +app = typer.Typer() + + +@app.command() def main( groceries: Annotated[List[Food], typer.Option(enum_by_name=True)] = ["f1", "f3"], ): @@ -18,4 +22,4 @@ def main( if __name__ == "__main__": - typer.run(main) + app() diff --git a/docs_src/parameter_types/enum/tutorial007.py b/docs_src/parameter_types/enum/tutorial007.py index d2ecd0c16d..a4cc486c6e 100644 --- a/docs_src/parameter_types/enum/tutorial007.py +++ b/docs_src/parameter_types/enum/tutorial007.py @@ -10,9 +10,13 @@ class LogLevel(enum.Enum): warning = logging.WARNING +app = typer.Typer() + + +@app.command() def main(log_level: LogLevel = typer.Option("warning", enum_by_name=True)): typer.echo(f"Log level set to: {logging.getLevelName(log_level.value)}") if __name__ == "__main__": - typer.run(main) + app() diff --git a/docs_src/parameter_types/enum/tutorial007_an.py b/docs_src/parameter_types/enum/tutorial007_an.py index ca4a15e416..92d308d07b 100644 --- a/docs_src/parameter_types/enum/tutorial007_an.py +++ b/docs_src/parameter_types/enum/tutorial007_an.py @@ -11,9 +11,13 @@ class LogLevel(enum.Enum): warning = logging.WARNING +app = typer.Typer() + + +@app.command() def main(log_level: Annotated[LogLevel, typer.Option(enum_by_name=True)] = "warning"): typer.echo(f"Log level set to: {logging.getLevelName(log_level.value)}") if __name__ == "__main__": - typer.run(main) + app() diff --git a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py index 5289683ad9..cc57aac480 100644 --- a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py +++ b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py @@ -1,14 +1,12 @@ import subprocess import sys -import typer from typer.testing import CliRunner from docs_src.multiple_values.arguments_with_multiple_values import tutorial003 as mod runner = CliRunner() -app = typer.Typer() -app.command()(mod.main) +app = mod.app def test_help(): diff --git a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py index 0d8b6f6b65..6ff743ea2f 100644 --- a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py +++ b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py @@ -1,7 +1,6 @@ import subprocess import sys -import typer from typer.testing import CliRunner from docs_src.multiple_values.arguments_with_multiple_values import ( @@ -9,8 +8,7 @@ ) runner = CliRunner() -app = typer.Typer() -app.command()(mod.main) +app = mod.app def test_help(): diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py index 1533509a77..1407974161 100644 --- a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py +++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py @@ -1,14 +1,12 @@ import subprocess import sys -import typer from typer.testing import CliRunner from docs_src.multiple_values.options_with_multiple_values import tutorial002 as mod runner = CliRunner() -app = typer.Typer() -app.command()(mod.main) +app = mod.app def test_main(): diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002_an.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002_an.py index e38c33329e..bef118c1ab 100644 --- a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002_an.py +++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002_an.py @@ -1,14 +1,12 @@ import subprocess import sys -import typer from typer.testing import CliRunner from docs_src.multiple_values.options_with_multiple_values import tutorial002_an as mod runner = CliRunner() -app = typer.Typer() -app.command()(mod.main) +app = mod.app def test_main(): diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003.py index 75f457c806..fa1a1de195 100644 --- a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003.py +++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003.py @@ -1,14 +1,12 @@ import subprocess import sys -import typer from typer.testing import CliRunner from docs_src.multiple_values.options_with_multiple_values import tutorial003 as mod runner = CliRunner() -app = typer.Typer() -app.command()(mod.main) +app = mod.app def test_main(): diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003_an.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003_an.py index 1f8da4a329..d008030297 100644 --- a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003_an.py +++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003_an.py @@ -1,14 +1,12 @@ import subprocess import sys -import typer from typer.testing import CliRunner from docs_src.multiple_values.options_with_multiple_values import tutorial003_an as mod runner = CliRunner() -app = typer.Typer() -app.command()(mod.main) +app = mod.app def test_main(): diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py index db63a8dc48..6aaf8521bd 100644 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py @@ -1,14 +1,11 @@ import subprocess -import typer from typer.testing import CliRunner from docs_src.parameter_types.enum import tutorial005 as mod runner = CliRunner() - -app = typer.Typer() -app.command()(mod.main) +app = mod.app def test_int_enum_default(): diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005_an.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005_an.py index 7e7ffcd02c..d77070dc37 100644 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005_an.py +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005_an.py @@ -1,14 +1,11 @@ import subprocess -import typer from typer.testing import CliRunner from docs_src.parameter_types.enum import tutorial005_an as mod runner = CliRunner() - -app = typer.Typer() -app.command()(mod.main) +app = mod.app def test_int_enum_default(): diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006.py index 6afefbae02..bf95a14e0f 100644 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006.py +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006.py @@ -1,15 +1,12 @@ import subprocess import sys -import typer from typer.testing import CliRunner from docs_src.parameter_types.enum import tutorial006 as mod runner = CliRunner() - -app = typer.Typer() -app.command()(mod.main) +app = mod.app def test_help(): diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006_an.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006_an.py index 695a817863..fd9b2404e7 100644 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006_an.py +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006_an.py @@ -1,15 +1,12 @@ import subprocess import sys -import typer from typer.testing import CliRunner from docs_src.parameter_types.enum import tutorial006_an as mod runner = CliRunner() - -app = typer.Typer() -app.command()(mod.main) +app = mod.app def test_help(): diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007.py index bcca3f3a67..d0b2834ee1 100644 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007.py +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007.py @@ -1,14 +1,11 @@ import subprocess -import typer from typer.testing import CliRunner from docs_src.parameter_types.enum import tutorial007 as mod runner = CliRunner() - -app = typer.Typer() -app.command()(mod.main) +app = mod.app def test_enum_names_default(): diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007_an.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007_an.py index 32839a002d..62ecc82e9d 100644 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007_an.py +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007_an.py @@ -1,14 +1,11 @@ import subprocess -import typer from typer.testing import CliRunner from docs_src.parameter_types.enum import tutorial007_an as mod runner = CliRunner() - -app = typer.Typer() -app.command()(mod.main) +app = mod.app def test_enum_names_default(): From 82ae00ac5511e0e8a7427cd3c3d5ab1ed41e6809 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 13:07:38 +0000 Subject: [PATCH 32/44] =?UTF-8?q?=F0=9F=8E=A8=20Auto=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../arguments_with_multiple_values/tutorial003_an.py | 3 +-- .../options_with_multiple_values/tutorial002_an.py | 3 +-- .../options_with_multiple_values/tutorial003_an.py | 3 +-- docs_src/parameter_types/enum/tutorial005_an.py | 2 +- docs_src/parameter_types/enum/tutorial006_an.py | 3 +-- docs_src/parameter_types/enum/tutorial007_an.py | 2 +- 6 files changed, 6 insertions(+), 10 deletions(-) diff --git a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py index ed84ca0699..ad8ddafc7a 100644 --- a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py +++ b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py @@ -1,8 +1,7 @@ from enum import Enum -from typing import Tuple +from typing import Annotated, Tuple import typer -from typing_extensions import Annotated class SuperHero(str, Enum): diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py b/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py index 34e168e8f9..ef672eac27 100644 --- a/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py +++ b/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py @@ -1,8 +1,7 @@ from enum import Enum -from typing import Tuple +from typing import Annotated, Tuple import typer -from typing_extensions import Annotated class Food(str, Enum): diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py b/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py index 2c0353f77d..6f1ff496c1 100644 --- a/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py +++ b/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py @@ -1,8 +1,7 @@ from enum import Enum -from typing import Tuple +from typing import Annotated, Tuple import typer -from typing_extensions import Annotated class Food(str, Enum): diff --git a/docs_src/parameter_types/enum/tutorial005_an.py b/docs_src/parameter_types/enum/tutorial005_an.py index 37372087b5..96aa2c7b4d 100644 --- a/docs_src/parameter_types/enum/tutorial005_an.py +++ b/docs_src/parameter_types/enum/tutorial005_an.py @@ -1,7 +1,7 @@ import enum +from typing import Annotated import typer -from typing_extensions import Annotated class Access(enum.IntEnum): diff --git a/docs_src/parameter_types/enum/tutorial006_an.py b/docs_src/parameter_types/enum/tutorial006_an.py index e1c1a8d4d4..e5a3ca58a1 100644 --- a/docs_src/parameter_types/enum/tutorial006_an.py +++ b/docs_src/parameter_types/enum/tutorial006_an.py @@ -1,8 +1,7 @@ from enum import Enum -from typing import List +from typing import Annotated, List import typer -from typing_extensions import Annotated class Food(str, Enum): diff --git a/docs_src/parameter_types/enum/tutorial007_an.py b/docs_src/parameter_types/enum/tutorial007_an.py index 92d308d07b..eaf9b3da36 100644 --- a/docs_src/parameter_types/enum/tutorial007_an.py +++ b/docs_src/parameter_types/enum/tutorial007_an.py @@ -1,8 +1,8 @@ import enum import logging +from typing import Annotated import typer -from typing_extensions import Annotated class LogLevel(enum.Enum): From d5168373fef357075cd262b5f31212cee87d3112 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Tue, 13 Jan 2026 14:39:02 +0100 Subject: [PATCH 33/44] use tuple, type and list --- .../arguments_with_multiple_values/tutorial003.py | 3 +-- .../arguments_with_multiple_values/tutorial003_an.py | 4 ++-- .../options_with_multiple_values/tutorial002.py | 3 +-- .../options_with_multiple_values/tutorial002_an.py | 4 ++-- .../options_with_multiple_values/tutorial003.py | 3 +-- .../options_with_multiple_values/tutorial003_an.py | 4 ++-- docs_src/parameter_types/enum/tutorial006.py | 3 +-- docs_src/parameter_types/enum/tutorial006_an.py | 4 ++-- typer/main.py | 2 +- 9 files changed, 13 insertions(+), 17 deletions(-) diff --git a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py index 0d23930f8e..0ddcdd4d74 100644 --- a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py +++ b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py @@ -1,5 +1,4 @@ from enum import Enum -from typing import Tuple import typer @@ -15,7 +14,7 @@ class SuperHero(str, Enum): @app.command() def main( - names: Tuple[str, str, str, SuperHero] = typer.Argument( + names: tuple[str, str, str, SuperHero] = typer.Argument( ("Harry", "Hermione", "Ron", "hero3"), enum_by_name=True, case_sensitive=False, diff --git a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py index ad8ddafc7a..12327332b0 100644 --- a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py +++ b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Annotated, Tuple +from typing import Annotated import typer @@ -16,7 +16,7 @@ class SuperHero(str, Enum): @app.command() def main( names: Annotated[ - Tuple[str, str, str, SuperHero], + tuple[str, str, str, SuperHero], typer.Argument( enum_by_name=True, help="Select 4 characters to play with", diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial002.py b/docs_src/multiple_values/options_with_multiple_values/tutorial002.py index 01b92bfabe..21306a5744 100644 --- a/docs_src/multiple_values/options_with_multiple_values/tutorial002.py +++ b/docs_src/multiple_values/options_with_multiple_values/tutorial002.py @@ -1,5 +1,4 @@ from enum import Enum -from typing import Tuple import typer @@ -14,7 +13,7 @@ class Food(str, Enum): @app.command() -def main(user: Tuple[str, int, bool, Food] = typer.Option((None, None, None, Food.f1))): +def main(user: tuple[str, int, bool, Food] = typer.Option((None, None, None, Food.f1))): username, coins, is_wizard, food = user if not username: print("No user provided") diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py b/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py index ef672eac27..98a1ea26d1 100644 --- a/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py +++ b/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Annotated, Tuple +from typing import Annotated import typer @@ -15,7 +15,7 @@ class Food(str, Enum): @app.command() def main( - user: Annotated[Tuple[str, int, bool, Food], typer.Option()] = ( + user: Annotated[tuple[str, int, bool, Food], typer.Option()] = ( None, None, None, diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial003.py b/docs_src/multiple_values/options_with_multiple_values/tutorial003.py index 0277b39d74..d815ee2297 100644 --- a/docs_src/multiple_values/options_with_multiple_values/tutorial003.py +++ b/docs_src/multiple_values/options_with_multiple_values/tutorial003.py @@ -1,5 +1,4 @@ from enum import Enum -from typing import Tuple import typer @@ -15,7 +14,7 @@ class Food(str, Enum): @app.command() def main( - user: Tuple[str, int, bool, Food] = typer.Option( + user: tuple[str, int, bool, Food] = typer.Option( (None, None, None, "f1"), enum_by_name=True ), ): diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py b/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py index 6f1ff496c1..e978fd3d6c 100644 --- a/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py +++ b/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Annotated, Tuple +from typing import Annotated import typer @@ -15,7 +15,7 @@ class Food(str, Enum): @app.command() def main( - user: Annotated[Tuple[str, int, bool, Food], typer.Option(enum_by_name=True)] = ( + user: Annotated[tuple[str, int, bool, Food], typer.Option(enum_by_name=True)] = ( None, None, None, diff --git a/docs_src/parameter_types/enum/tutorial006.py b/docs_src/parameter_types/enum/tutorial006.py index 34e593a781..aa4edd78e9 100644 --- a/docs_src/parameter_types/enum/tutorial006.py +++ b/docs_src/parameter_types/enum/tutorial006.py @@ -1,5 +1,4 @@ from enum import Enum -from typing import List import typer @@ -14,7 +13,7 @@ class Food(str, Enum): @app.command() -def main(groceries: List[Food] = typer.Option(["f1", "f3"], enum_by_name=True)): +def main(groceries: list[Food] = typer.Option(["f1", "f3"], enum_by_name=True)): print(f"Buying groceries: {', '.join([f.value for f in groceries])}") diff --git a/docs_src/parameter_types/enum/tutorial006_an.py b/docs_src/parameter_types/enum/tutorial006_an.py index e5a3ca58a1..0d633f792e 100644 --- a/docs_src/parameter_types/enum/tutorial006_an.py +++ b/docs_src/parameter_types/enum/tutorial006_an.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Annotated, List +from typing import Annotated import typer @@ -15,7 +15,7 @@ class Food(str, Enum): @app.command() def main( - groceries: Annotated[List[Food], typer.Option(enum_by_name=True)] = ["f1", "f3"], + groceries: Annotated[list[Food], typer.Option(enum_by_name=True)] = ["f1", "f3"], ): print(f"Buying groceries: {', '.join([f.value for f in groceries])}") diff --git a/typer/main.py b/typer/main.py index 2ae19a8135..bd0c7d8bc5 100644 --- a/typer/main.py +++ b/typer/main.py @@ -651,7 +651,7 @@ def convertor(value: Any) -> Any: return convertor -def generate_enum_name_convertor(enum: Type[Enum]) -> Callable[..., Any]: +def generate_enum_name_convertor(enum: type[Enum]) -> Callable[..., Any]: val_map = {str(item.name): item for item in enum} def convertor(value: Any) -> Any: From dc2cbaca101db5c8cf7a0e86184126a9da319e40 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Tue, 27 Jan 2026 17:26:11 +0100 Subject: [PATCH 34/44] update test format to latest from master --- ...torial003_an.py => tutorial003_an_py39.py} | 0 .../{tutorial003.py => tutorial003_py39.py} | 0 ...torial002_an.py => tutorial002_an_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 ...torial003_an.py => tutorial003_an_py39.py} | 0 .../{tutorial003.py => tutorial003_py39.py} | 0 ...torial005_an.py => tutorial005_an_py39.py} | 0 .../{tutorial005.py => tutorial005_py39.py} | 0 ...torial006_an.py => tutorial006_an_py39.py} | 0 .../{tutorial006.py => tutorial006_py39.py} | 0 ...torial007_an.py => tutorial007_an_py39.py} | 0 .../{tutorial007.py => tutorial007_py39.py} | 0 .../test_tutorial003.py | 37 ++++++++----- .../test_tutorial003_an.py | 52 ------------------- .../test_tutorial002.py | 37 ++++++++----- .../test_tutorial002_an.py | 47 ----------------- .../test_tutorial003.py | 39 +++++++++----- .../test_tutorial003_an.py | 47 ----------------- .../test_enum/test_tutorial005.py | 32 ++++++++---- .../test_enum/test_tutorial005_an.py | 29 ----------- .../test_enum/test_tutorial006.py | 37 ++++++++----- .../test_enum/test_tutorial006_an.py | 44 ---------------- .../test_enum/test_tutorial007.py | 32 ++++++++---- .../test_enum/test_tutorial007_an.py | 29 ----------- 24 files changed, 148 insertions(+), 314 deletions(-) rename docs_src/multiple_values/arguments_with_multiple_values/{tutorial003_an.py => tutorial003_an_py39.py} (100%) rename docs_src/multiple_values/arguments_with_multiple_values/{tutorial003.py => tutorial003_py39.py} (100%) rename docs_src/multiple_values/options_with_multiple_values/{tutorial002_an.py => tutorial002_an_py39.py} (100%) rename docs_src/multiple_values/options_with_multiple_values/{tutorial002.py => tutorial002_py39.py} (100%) rename docs_src/multiple_values/options_with_multiple_values/{tutorial003_an.py => tutorial003_an_py39.py} (100%) rename docs_src/multiple_values/options_with_multiple_values/{tutorial003.py => tutorial003_py39.py} (100%) rename docs_src/parameter_types/enum/{tutorial005_an.py => tutorial005_an_py39.py} (100%) rename docs_src/parameter_types/enum/{tutorial005.py => tutorial005_py39.py} (100%) rename docs_src/parameter_types/enum/{tutorial006_an.py => tutorial006_an_py39.py} (100%) rename docs_src/parameter_types/enum/{tutorial006.py => tutorial006_py39.py} (100%) rename docs_src/parameter_types/enum/{tutorial007_an.py => tutorial007_an_py39.py} (100%) rename docs_src/parameter_types/enum/{tutorial007.py => tutorial007_py39.py} (100%) delete mode 100644 tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py delete mode 100644 tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002_an.py delete mode 100644 tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003_an.py delete mode 100644 tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005_an.py delete mode 100644 tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006_an.py delete mode 100644 tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007_an.py diff --git a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an_py39.py similarity index 100% rename from docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py rename to docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an_py39.py diff --git a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_py39.py similarity index 100% rename from docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py rename to docs_src/multiple_values/arguments_with_multiple_values/tutorial003_py39.py diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py b/docs_src/multiple_values/options_with_multiple_values/tutorial002_an_py39.py similarity index 100% rename from docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py rename to docs_src/multiple_values/options_with_multiple_values/tutorial002_an_py39.py diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial002.py b/docs_src/multiple_values/options_with_multiple_values/tutorial002_py39.py similarity index 100% rename from docs_src/multiple_values/options_with_multiple_values/tutorial002.py rename to docs_src/multiple_values/options_with_multiple_values/tutorial002_py39.py diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py b/docs_src/multiple_values/options_with_multiple_values/tutorial003_an_py39.py similarity index 100% rename from docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py rename to docs_src/multiple_values/options_with_multiple_values/tutorial003_an_py39.py diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial003.py b/docs_src/multiple_values/options_with_multiple_values/tutorial003_py39.py similarity index 100% rename from docs_src/multiple_values/options_with_multiple_values/tutorial003.py rename to docs_src/multiple_values/options_with_multiple_values/tutorial003_py39.py diff --git a/docs_src/parameter_types/enum/tutorial005_an.py b/docs_src/parameter_types/enum/tutorial005_an_py39.py similarity index 100% rename from docs_src/parameter_types/enum/tutorial005_an.py rename to docs_src/parameter_types/enum/tutorial005_an_py39.py diff --git a/docs_src/parameter_types/enum/tutorial005.py b/docs_src/parameter_types/enum/tutorial005_py39.py similarity index 100% rename from docs_src/parameter_types/enum/tutorial005.py rename to docs_src/parameter_types/enum/tutorial005_py39.py diff --git a/docs_src/parameter_types/enum/tutorial006_an.py b/docs_src/parameter_types/enum/tutorial006_an_py39.py similarity index 100% rename from docs_src/parameter_types/enum/tutorial006_an.py rename to docs_src/parameter_types/enum/tutorial006_an_py39.py diff --git a/docs_src/parameter_types/enum/tutorial006.py b/docs_src/parameter_types/enum/tutorial006_py39.py similarity index 100% rename from docs_src/parameter_types/enum/tutorial006.py rename to docs_src/parameter_types/enum/tutorial006_py39.py diff --git a/docs_src/parameter_types/enum/tutorial007_an.py b/docs_src/parameter_types/enum/tutorial007_an_py39.py similarity index 100% rename from docs_src/parameter_types/enum/tutorial007_an.py rename to docs_src/parameter_types/enum/tutorial007_an_py39.py diff --git a/docs_src/parameter_types/enum/tutorial007.py b/docs_src/parameter_types/enum/tutorial007_py39.py similarity index 100% rename from docs_src/parameter_types/enum/tutorial007.py rename to docs_src/parameter_types/enum/tutorial007_py39.py diff --git a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py index cc57aac480..52c75d70aa 100644 --- a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py +++ b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py @@ -1,24 +1,37 @@ +import importlib import subprocess import sys +from types import ModuleType +import pytest from typer.testing import CliRunner -from docs_src.multiple_values.arguments_with_multiple_values import tutorial003 as mod - runner = CliRunner() -app = mod.app -def test_help(): - result = runner.invoke(app, ["--help"]) +@pytest.fixture( + name="mod", + params=[ + pytest.param("tutorial003_py39"), + pytest.param("tutorial003_an_py39"), + ], +) +def get_mod(request: pytest.FixtureRequest) -> ModuleType: + module_name = f"docs_src.multiple_values.arguments_with_multiple_values.{request.param}" + mod = importlib.import_module(module_name) + return mod + + +def test_help(mod: ModuleType): + result = runner.invoke(mod.app, ["--help"]) assert result.exit_code == 0 assert "[OPTIONS] [NAMES]..." in result.output assert "Arguments" in result.output assert "[default: Harry, Hermione, Ron, hero3]" in result.output -def test_defaults(): - result = runner.invoke(app) +def test_defaults(mod: ModuleType): + result = runner.invoke(mod.app) assert result.exit_code == 0 assert "Hello Harry" in result.output assert "Hello Hermione" in result.output @@ -26,14 +39,14 @@ def test_defaults(): assert "Hello Wonder woman" in result.output -def test_invalid_args(): - result = runner.invoke(app, ["Draco", "Hagrid"]) +def test_invalid_args(mod: ModuleType): + result = runner.invoke(mod.app, ["Draco", "Hagrid"]) assert result.exit_code != 0 assert "Argument 'names' takes 4 values" in result.output -def test_valid_args(): - result = runner.invoke(app, ["Draco", "Hagrid", "Dobby", "hero1"]) +def test_valid_args(mod: ModuleType): + result = runner.invoke(mod.app, ["Draco", "Hagrid", "Dobby", "hero1"]) assert result.exit_code == 0 assert "Hello Draco" in result.stdout assert "Hello Hagrid" in result.stdout @@ -41,7 +54,7 @@ def test_valid_args(): assert "Hello Superman" in result.stdout -def test_script(): +def test_script(mod: ModuleType): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], capture_output=True, diff --git a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py deleted file mode 100644 index 6ff743ea2f..0000000000 --- a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py +++ /dev/null @@ -1,52 +0,0 @@ -import subprocess -import sys - -from typer.testing import CliRunner - -from docs_src.multiple_values.arguments_with_multiple_values import ( - tutorial003_an as mod, -) - -runner = CliRunner() -app = mod.app - - -def test_help(): - result = runner.invoke(app, ["--help"]) - assert result.exit_code == 0 - assert "[OPTIONS] [NAMES]..." in result.output - assert "Arguments" in result.output - assert "[default: Harry, Hermione, Ron, hero3]" in result.output - - -def test_defaults(): - result = runner.invoke(app) - assert result.exit_code == 0 - assert "Hello Harry" in result.output - assert "Hello Hermione" in result.output - assert "Hello Ron" in result.output - assert "Hello Wonder woman" in result.output - - -def test_invalid_args(): - result = runner.invoke(app, ["Draco", "Hagrid"]) - assert result.exit_code != 0 - assert "Argument 'names' takes 4 values" in result.output - - -def test_valid_args(): - result = runner.invoke(app, ["Draco", "Hagrid", "Dobby", "HERO1"]) - assert result.exit_code == 0 - assert "Hello Draco" in result.stdout - assert "Hello Hagrid" in result.stdout - assert "Hello Dobby" in result.stdout - assert "Hello Superman" 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 diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py index 1407974161..0a2a4fb7f0 100644 --- a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py +++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py @@ -1,44 +1,57 @@ +import importlib import subprocess import sys +from types import ModuleType +import pytest from typer.testing import CliRunner -from docs_src.multiple_values.options_with_multiple_values import tutorial002 as mod - runner = CliRunner() -app = mod.app -def test_main(): - result = runner.invoke(app) +@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.multiple_values.options_with_multiple_values.{request.param}" + mod = importlib.import_module(module_name) + return mod + + +def test_main(mod: ModuleType): + result = runner.invoke(mod.app) assert result.exit_code != 0 assert "No user provided" in result.output assert "Aborted" in result.output -def test_user_1(): - result = runner.invoke(app, ["--user", "Camila", "50", "yes", "Eggs"]) +def test_user_1(mod: ModuleType): + result = runner.invoke(mod.app, ["--user", "Camila", "50", "yes", "Eggs"]) assert result.exit_code == 0 assert "The username Camila has 50 coins" in result.output assert "And this user is a wizard!" in result.output assert "And they love eating Eggs" in result.output -def test_user_2(): - result = runner.invoke(app, ["--user", "Morty", "3", "no", "Bacon"]) +def test_user_2(mod: ModuleType): + result = runner.invoke(mod.app, ["--user", "Morty", "3", "no", "Bacon"]) assert result.exit_code == 0 assert "The username Morty has 3 coins" in result.output assert "And this user is a wizard!" not in result.output assert "And they love eating Bacon" in result.output -def test_invalid_user(): - result = runner.invoke(app, ["--user", "Camila", "50"]) +def test_invalid_user(mod: ModuleType): + result = runner.invoke(mod.app, ["--user", "Camila", "50"]) assert result.exit_code != 0 assert "Option '--user' requires 4 arguments" in result.output -def test_script(): +def test_script(mod: ModuleType): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], capture_output=True, diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002_an.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002_an.py deleted file mode 100644 index bef118c1ab..0000000000 --- a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002_an.py +++ /dev/null @@ -1,47 +0,0 @@ -import subprocess -import sys - -from typer.testing import CliRunner - -from docs_src.multiple_values.options_with_multiple_values import tutorial002_an as mod - -runner = CliRunner() -app = mod.app - - -def test_main(): - result = runner.invoke(app) - assert result.exit_code != 0 - assert "No user provided" in result.output - assert "Aborted" in result.output - - -def test_user_1(): - result = runner.invoke(app, ["--user", "Camila", "50", "yes", "Eggs"]) - assert result.exit_code == 0 - assert "The username Camila has 50 coins" in result.output - assert "And this user is a wizard!" in result.output - assert "And they love eating Eggs" in result.output - - -def test_user_2(): - result = runner.invoke(app, ["--user", "Morty", "3", "no", "Bacon"]) - assert result.exit_code == 0 - assert "The username Morty has 3 coins" in result.output - assert "And this user is a wizard!" not in result.output - assert "And they love eating Bacon" in result.output - - -def test_invalid_user(): - result = runner.invoke(app, ["--user", "Camila", "50"]) - assert result.exit_code != 0 - assert "Option '--user' requires 4 arguments" 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 diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003.py index fa1a1de195..59b4f3de27 100644 --- a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003.py +++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003.py @@ -1,44 +1,59 @@ +import importlib import subprocess import sys +from types import ModuleType +import pytest from typer.testing import CliRunner -from docs_src.multiple_values.options_with_multiple_values import tutorial003 as mod - runner = CliRunner() -app = mod.app -def test_main(): - result = runner.invoke(app) +@pytest.fixture( + name="mod", + params=[ + pytest.param("tutorial003_py39"), + pytest.param("tutorial003_an_py39"), + ], +) +def get_mod(request: pytest.FixtureRequest) -> ModuleType: + module_name = ( + f"docs_src.multiple_values.options_with_multiple_values.{request.param}" + ) + mod = importlib.import_module(module_name) + return mod + + +def test_main(mod: ModuleType): + result = runner.invoke(mod.app) assert result.exit_code != 0 assert "No user provided" in result.output assert "Aborted" in result.output -def test_user_1(): - result = runner.invoke(app, ["--user", "Camila", "50", "yes", "f1"]) +def test_user_1(mod: ModuleType): + result = runner.invoke(mod.app, ["--user", "Camila", "50", "yes", "f1"]) assert result.exit_code == 0 assert "The username Camila has 50 coins" in result.output assert "And this user is a wizard!" in result.output assert "And they love eating Eggs" in result.output -def test_user_2(): - result = runner.invoke(app, ["--user", "Morty", "3", "no", "f2"]) +def test_user_2(mod: ModuleType): + result = runner.invoke(mod.app, ["--user", "Morty", "3", "no", "f2"]) assert result.exit_code == 0 assert "The username Morty has 3 coins" in result.output assert "And this user is a wizard!" not in result.output assert "And they love eating Bacon" in result.output -def test_invalid_user(): - result = runner.invoke(app, ["--user", "Camila", "50"]) +def test_invalid_user(mod: ModuleType): + result = runner.invoke(mod.app, ["--user", "Camila", "50"]) assert result.exit_code != 0 assert "Option '--user' requires 4 arguments" in result.output -def test_script(): +def test_script(mod: ModuleType): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], capture_output=True, diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003_an.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003_an.py deleted file mode 100644 index d008030297..0000000000 --- a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003_an.py +++ /dev/null @@ -1,47 +0,0 @@ -import subprocess -import sys - -from typer.testing import CliRunner - -from docs_src.multiple_values.options_with_multiple_values import tutorial003_an as mod - -runner = CliRunner() -app = mod.app - - -def test_main(): - result = runner.invoke(app) - assert result.exit_code != 0 - assert "No user provided" in result.output - assert "Aborted" in result.output - - -def test_user_1(): - result = runner.invoke(app, ["--user", "Camila", "50", "yes", "f1"]) - assert result.exit_code == 0 - assert "The username Camila has 50 coins" in result.output - assert "And this user is a wizard!" in result.output - assert "And they love eating Eggs" in result.output - - -def test_user_2(): - result = runner.invoke(app, ["--user", "Morty", "3", "no", "f2"]) - assert result.exit_code == 0 - assert "The username Morty has 3 coins" in result.output - assert "And this user is a wizard!" not in result.output - assert "And they love eating Bacon" in result.output - - -def test_invalid_user(): - result = runner.invoke(app, ["--user", "Camila", "50"]) - assert result.exit_code != 0 - assert "Option '--user' requires 4 arguments" 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 diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py index 6aaf8521bd..e4963b4855 100644 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py @@ -1,28 +1,42 @@ +import importlib import subprocess +import sys +from types import ModuleType +import pytest from typer.testing import CliRunner -from docs_src.parameter_types.enum import tutorial005 as mod - runner = CliRunner() -app = mod.app -def test_int_enum_default(): - result = runner.invoke(app) +@pytest.fixture( + name="mod", + params=[ + pytest.param("tutorial005_py39"), + pytest.param("tutorial005_an_py39"), + ], +) +def get_mod(request: pytest.FixtureRequest) -> ModuleType: + module_name = f"docs_src.parameter_types.enum.{request.param}" + mod = importlib.import_module(module_name) + return mod + + +def test_int_enum_default(mod: ModuleType): + result = runner.invoke(mod.app) assert result.exit_code == 0 assert "Access level: private (1)" in result.output -def test_int_enum(): - result = runner.invoke(app, ["--access", "open"]) +def test_int_enum(mod: ModuleType): + result = runner.invoke(mod.app, ["--access", "open"]) assert result.exit_code == 0 assert "Access level: open (4)" in result.output -def test_script(): +def test_script(mod: ModuleType): result = subprocess.run( - ["coverage", "run", mod.__file__, "--help"], + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], capture_output=True, encoding="utf-8", ) diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005_an.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005_an.py deleted file mode 100644 index d77070dc37..0000000000 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005_an.py +++ /dev/null @@ -1,29 +0,0 @@ -import subprocess - -from typer.testing import CliRunner - -from docs_src.parameter_types.enum import tutorial005_an as mod - -runner = CliRunner() -app = mod.app - - -def test_int_enum_default(): - result = runner.invoke(app) - assert result.exit_code == 0 - assert "Access level: private (1)" in result.output - - -def test_int_enum(): - result = runner.invoke(app, ["--access", "open"]) - assert result.exit_code == 0 - assert "Access level: open (4)" in result.output - - -def test_script(): - result = subprocess.run( - ["coverage", "run", mod.__file__, "--help"], - capture_output=True, - encoding="utf-8", - ) - assert "Usage" in result.stdout diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006.py index bf95a14e0f..c884fa3e08 100644 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006.py +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006.py @@ -1,41 +1,54 @@ +import importlib import subprocess import sys +from types import ModuleType +import pytest from typer.testing import CliRunner -from docs_src.parameter_types.enum import tutorial006 as mod - runner = CliRunner() -app = mod.app -def test_help(): - result = runner.invoke(app, ["--help"]) +@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.parameter_types.enum.{request.param}" + mod = importlib.import_module(module_name) + return mod + + +def test_help(mod: ModuleType): + result = runner.invoke(mod.app, ["--help"]) assert result.exit_code == 0 assert "--groceries" in result.output assert "[f1|f2|f3]" in result.output assert "default: f1, f3" in result.output -def test_call_no_arg(): - result = runner.invoke(app) +def test_call_no_arg(mod: ModuleType): + result = runner.invoke(mod.app) assert result.exit_code == 0 assert "Buying groceries: Eggs, Cheese" in result.output -def test_call_single_arg(): - result = runner.invoke(app, ["--groceries", "f2"]) +def test_call_single_arg(mod: ModuleType): + result = runner.invoke(mod.app, ["--groceries", "f2"]) assert result.exit_code == 0 assert "Buying groceries: Bacon" in result.output -def test_call_multiple_arg(): - result = runner.invoke(app, ["--groceries", "f1", "--groceries", "f2"]) +def test_call_multiple_arg(mod: ModuleType): + result = runner.invoke(mod.app, ["--groceries", "f1", "--groceries", "f2"]) assert result.exit_code == 0 assert "Buying groceries: Eggs, Bacon" in result.output -def test_script(): +def test_script(mod: ModuleType): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], capture_output=True, diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006_an.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006_an.py deleted file mode 100644 index fd9b2404e7..0000000000 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006_an.py +++ /dev/null @@ -1,44 +0,0 @@ -import subprocess -import sys - -from typer.testing import CliRunner - -from docs_src.parameter_types.enum import tutorial006_an as mod - -runner = CliRunner() -app = mod.app - - -def test_help(): - result = runner.invoke(app, ["--help"]) - assert result.exit_code == 0 - assert "--groceries" in result.output - assert "[f1|f2|f3]" in result.output - assert "default: f1, f3" in result.output - - -def test_call_no_arg(): - result = runner.invoke(app) - assert result.exit_code == 0 - assert "Buying groceries: Eggs, Cheese" in result.output - - -def test_call_single_arg(): - result = runner.invoke(app, ["--groceries", "f2"]) - assert result.exit_code == 0 - assert "Buying groceries: Bacon" in result.output - - -def test_call_multiple_arg(): - result = runner.invoke(app, ["--groceries", "f1", "--groceries", "f2"]) - assert result.exit_code == 0 - assert "Buying groceries: Eggs, Bacon" 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 diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007.py index d0b2834ee1..8e9f670576 100644 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007.py +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007.py @@ -1,28 +1,42 @@ +import importlib import subprocess +import sys +from types import ModuleType +import pytest from typer.testing import CliRunner -from docs_src.parameter_types.enum import tutorial007 as mod - runner = CliRunner() -app = mod.app -def test_enum_names_default(): - result = runner.invoke(app) +@pytest.fixture( + name="mod", + params=[ + pytest.param("tutorial007_py39"), + pytest.param("tutorial007_an_py39"), + ], +) +def get_mod(request: pytest.FixtureRequest) -> ModuleType: + module_name = f"docs_src.parameter_types.enum.{request.param}" + mod = importlib.import_module(module_name) + return mod + + +def test_enum_names_default(mod: ModuleType): + result = runner.invoke(mod.app) assert result.exit_code == 0 assert "Log level set to: WARNING" in result.output -def test_enum_names(): - result = runner.invoke(app, ["--log-level", "debug"]) +def test_enum_names(mod: ModuleType): + result = runner.invoke(mod.app, ["--log-level", "debug"]) assert result.exit_code == 0 assert "Log level set to: DEBUG" in result.output -def test_script(): +def test_script(mod: ModuleType): result = subprocess.run( - ["coverage", "run", mod.__file__, "--help"], + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], capture_output=True, encoding="utf-8", ) diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007_an.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007_an.py deleted file mode 100644 index 62ecc82e9d..0000000000 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007_an.py +++ /dev/null @@ -1,29 +0,0 @@ -import subprocess - -from typer.testing import CliRunner - -from docs_src.parameter_types.enum import tutorial007_an as mod - -runner = CliRunner() -app = mod.app - - -def test_enum_names_default(): - result = runner.invoke(app) - assert result.exit_code == 0 - assert "Log level set to: WARNING" in result.output - - -def test_enum_names(): - result = runner.invoke(app, ["--log-level", "debug"]) - assert result.exit_code == 0 - assert "Log level set to: DEBUG" in result.output - - -def test_script(): - result = subprocess.run( - ["coverage", "run", mod.__file__, "--help"], - capture_output=True, - encoding="utf-8", - ) - assert "Usage" in result.stdout From a877e98d18733270371cc1f2573221441892b3ba Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 16:26:49 +0000 Subject: [PATCH 35/44] =?UTF-8?q?=F0=9F=8E=A8=20Auto=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test_arguments_with_multiple_values/test_tutorial003.py | 4 +++- .../test_options_with_multiple_values/test_tutorial002.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py index 52c75d70aa..5f5fd2a7b2 100644 --- a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py +++ b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py @@ -17,7 +17,9 @@ ], ) def get_mod(request: pytest.FixtureRequest) -> ModuleType: - module_name = f"docs_src.multiple_values.arguments_with_multiple_values.{request.param}" + module_name = ( + f"docs_src.multiple_values.arguments_with_multiple_values.{request.param}" + ) mod = importlib.import_module(module_name) return mod diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py index 0a2a4fb7f0..52a4aa7b4d 100644 --- a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py +++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py @@ -17,7 +17,9 @@ ], ) def get_mod(request: pytest.FixtureRequest) -> ModuleType: - module_name = f"docs_src.multiple_values.options_with_multiple_values.{request.param}" + module_name = ( + f"docs_src.multiple_values.options_with_multiple_values.{request.param}" + ) mod = importlib.import_module(module_name) return mod From 04c68a6b4a67ca0df35162a5aea0300a8b71d653 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 28 Jan 2026 15:41:14 +0100 Subject: [PATCH 36/44] fix lint and formatting --- pyproject.toml | 6 +++--- .../test_arguments_with_multiple_values/test_tutorial003.py | 4 +++- .../test_options_with_multiple_values/test_tutorial002.py | 4 +++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0aa2b9e4e1..29172c7c16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ standard = [ dev = [ { include-group = "tests" }, { include-group = "docs" }, - "prek==0.3.0", + "prek==0.2.24", ] docs = [ "cairosvg==2.8.2", @@ -86,7 +86,7 @@ tests = [ "pytest-sugar>=0.9.4,<1.2.0", "pytest-xdist>=1.32.0,<4.0.0", "rich>=10.11.0", - "ruff==0.14.14", + "ruff==0.14.10", "shellingham>=1.3.0", ] @@ -225,7 +225,7 @@ ignore = [ "docs_src/options_autocompletion/tutorial008_an_py39.py" = ["B006"] "docs_src/options_autocompletion/tutorial009_an_py39.py" = ["B006"] "docs_src/parameter_types/enum/tutorial003_an_py39.py" = ["B006"] -"docs_src/parameter_types/enum/tutorial006_an.py" = ["B006"] +"docs_src/parameter_types/enum/tutorial006_an_py39.py" = ["B006"] # Loop control variable `value` not used within loop body "docs_src/progressbar/tutorial001_py39.py" = ["B007"] "docs_src/progressbar/tutorial003_py39.py" = ["B007"] diff --git a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py index 52c75d70aa..5f5fd2a7b2 100644 --- a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py +++ b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py @@ -17,7 +17,9 @@ ], ) def get_mod(request: pytest.FixtureRequest) -> ModuleType: - module_name = f"docs_src.multiple_values.arguments_with_multiple_values.{request.param}" + module_name = ( + f"docs_src.multiple_values.arguments_with_multiple_values.{request.param}" + ) mod = importlib.import_module(module_name) return mod diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py index 0a2a4fb7f0..52a4aa7b4d 100644 --- a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py +++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py @@ -17,7 +17,9 @@ ], ) def get_mod(request: pytest.FixtureRequest) -> ModuleType: - module_name = f"docs_src.multiple_values.options_with_multiple_values.{request.param}" + module_name = ( + f"docs_src.multiple_values.options_with_multiple_values.{request.param}" + ) mod = importlib.import_module(module_name) return mod From bf153ff5d82728004982140b2c47115260bf95d6 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 28 Jan 2026 15:47:15 +0100 Subject: [PATCH 37/44] fix wrongly resolved merge conflict --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 29172c7c16..33e0fbdea9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ standard = [ dev = [ { include-group = "tests" }, { include-group = "docs" }, - "prek==0.2.24", + "prek==0.3.0", ] docs = [ "cairosvg==2.8.2", @@ -86,7 +86,7 @@ tests = [ "pytest-sugar>=0.9.4,<1.2.0", "pytest-xdist>=1.32.0,<4.0.0", "rich>=10.11.0", - "ruff==0.14.10", + "ruff==0.14.14", "shellingham>=1.3.0", ] From 85e2a4be840765c2937d8a8a5f185a067ebcb9e7 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 28 Jan 2026 16:24:28 +0100 Subject: [PATCH 38/44] fix docs --- docs/tutorial/parameter-types/enum.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tutorial/parameter-types/enum.md b/docs/tutorial/parameter-types/enum.md index b000e14ee1..e9799709b5 100644 --- a/docs/tutorial/parameter-types/enum.md +++ b/docs/tutorial/parameter-types/enum.md @@ -80,9 +80,9 @@ Sometimes you want to accept `Enum` names from the command line and convert that into `Enum` values in the command handler. You can enable this by setting `enum_by_name=True`: -{* docs_src/parameter_types/enum/tutorial007_an.py hl[18] *} +{* docs_src/parameter_types/enum/tutorial007_an_py39.py hl[18] *} -And then the names of the `Enum` will be used instead of values: +And then the names of the `Enum` can be used instead of values:
@@ -96,7 +96,7 @@ Log level set to DEBUG This can be particularly useful if the enum values are not strings: -{* docs_src/parameter_types/enum/tutorial005_an.py hl[8:11,18] *} +{* docs_src/parameter_types/enum/tutorial005_an_py39.py hl[8:11,18] *} ```console $ python main.py --access protected @@ -145,7 +145,7 @@ Buying groceries: Eggs, Bacon You can also combine `enum_by_name=True` with a list of enums: -{* docs_src/parameter_types/enum/tutorial006_an.py hl[19] *} +{* docs_src/parameter_types/enum/tutorial006_an_py39.py hl[18] *} This works exactly the same, but you're using the enum names instead of values: From dbce376d550eaf02faa73441fa2e4ddff8b054a2 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 28 Jan 2026 16:37:03 +0100 Subject: [PATCH 39/44] remove unnecessary (and unmentioned) extra files --- .../tutorial003_an_py39.py | 35 ---------- .../tutorial003_py39.py | 32 --------- .../tutorial002_an_py39.py | 36 ---------- .../tutorial002_py39.py | 28 -------- .../tutorial003_an_py39.py | 36 ---------- .../tutorial003_py39.py | 32 --------- .../test_tutorial003.py | 65 ------------------- .../test_tutorial002.py | 62 ------------------ .../test_tutorial003.py | 62 ------------------ 9 files changed, 388 deletions(-) delete mode 100644 docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an_py39.py delete mode 100644 docs_src/multiple_values/arguments_with_multiple_values/tutorial003_py39.py delete mode 100644 docs_src/multiple_values/options_with_multiple_values/tutorial002_an_py39.py delete mode 100644 docs_src/multiple_values/options_with_multiple_values/tutorial002_py39.py delete mode 100644 docs_src/multiple_values/options_with_multiple_values/tutorial003_an_py39.py delete mode 100644 docs_src/multiple_values/options_with_multiple_values/tutorial003_py39.py delete mode 100644 tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py delete mode 100644 tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py delete mode 100644 tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003.py diff --git a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an_py39.py b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an_py39.py deleted file mode 100644 index 12327332b0..0000000000 --- a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an_py39.py +++ /dev/null @@ -1,35 +0,0 @@ -from enum import Enum -from typing import Annotated - -import typer - - -class SuperHero(str, Enum): - hero1 = "Superman" - hero2 = "Spiderman" - hero3 = "Wonder woman" - - -app = typer.Typer() - - -@app.command() -def main( - names: Annotated[ - tuple[str, str, str, SuperHero], - typer.Argument( - enum_by_name=True, - help="Select 4 characters to play with", - case_sensitive=False, - ), - ] = ("Harry", "Hermione", "Ron", "hero3"), -): - for name in names: - if isinstance(name, Enum): - print(f"Hello {name.value}") - else: - print(f"Hello {name}") - - -if __name__ == "__main__": - app() diff --git a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_py39.py b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_py39.py deleted file mode 100644 index 0ddcdd4d74..0000000000 --- a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_py39.py +++ /dev/null @@ -1,32 +0,0 @@ -from enum import Enum - -import typer - - -class SuperHero(str, Enum): - hero1 = "Superman" - hero2 = "Spiderman" - hero3 = "Wonder woman" - - -app = typer.Typer() - - -@app.command() -def main( - names: tuple[str, str, str, SuperHero] = typer.Argument( - ("Harry", "Hermione", "Ron", "hero3"), - enum_by_name=True, - case_sensitive=False, - help="Select 4 characters to play with", - ), -): - for name in names: - if isinstance(name, Enum): - print(f"Hello {name.value}") - else: - print(f"Hello {name}") - - -if __name__ == "__main__": - app() diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial002_an_py39.py b/docs_src/multiple_values/options_with_multiple_values/tutorial002_an_py39.py deleted file mode 100644 index 98a1ea26d1..0000000000 --- a/docs_src/multiple_values/options_with_multiple_values/tutorial002_an_py39.py +++ /dev/null @@ -1,36 +0,0 @@ -from enum import Enum -from typing import Annotated - -import typer - - -class Food(str, Enum): - f1 = "Eggs" - f2 = "Bacon" - f3 = "Cheese" - - -app = typer.Typer() - - -@app.command() -def main( - user: Annotated[tuple[str, int, bool, Food], typer.Option()] = ( - None, - None, - None, - Food.f1, - ), -): - username, coins, is_wizard, food = user - if not username: - print("No user provided") - raise typer.Abort() - print(f"The username {username} has {coins} coins") - if is_wizard: - print("And this user is a wizard!") - print(f"And they love eating {food.value}") - - -if __name__ == "__main__": - app() diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial002_py39.py b/docs_src/multiple_values/options_with_multiple_values/tutorial002_py39.py deleted file mode 100644 index 21306a5744..0000000000 --- a/docs_src/multiple_values/options_with_multiple_values/tutorial002_py39.py +++ /dev/null @@ -1,28 +0,0 @@ -from enum import Enum - -import typer - - -class Food(str, Enum): - f1 = "Eggs" - f2 = "Bacon" - f3 = "Cheese" - - -app = typer.Typer() - - -@app.command() -def main(user: tuple[str, int, bool, Food] = typer.Option((None, None, None, Food.f1))): - username, coins, is_wizard, food = user - if not username: - print("No user provided") - raise typer.Abort() - print(f"The username {username} has {coins} coins") - if is_wizard: - print("And this user is a wizard!") - print(f"And they love eating {food.value}") - - -if __name__ == "__main__": - app() diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial003_an_py39.py b/docs_src/multiple_values/options_with_multiple_values/tutorial003_an_py39.py deleted file mode 100644 index e978fd3d6c..0000000000 --- a/docs_src/multiple_values/options_with_multiple_values/tutorial003_an_py39.py +++ /dev/null @@ -1,36 +0,0 @@ -from enum import Enum -from typing import Annotated - -import typer - - -class Food(str, Enum): - f1 = "Eggs" - f2 = "Bacon" - f3 = "Cheese" - - -app = typer.Typer() - - -@app.command() -def main( - user: Annotated[tuple[str, int, bool, Food], typer.Option(enum_by_name=True)] = ( - None, - None, - None, - "f1", - ), -): - username, coins, is_wizard, food = user - if not username: - print("No user provided") - raise typer.Abort() - print(f"The username {username} has {coins} coins") - if is_wizard: - print("And this user is a wizard!") - print(f"And they love eating {food.value}") - - -if __name__ == "__main__": - app() diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial003_py39.py b/docs_src/multiple_values/options_with_multiple_values/tutorial003_py39.py deleted file mode 100644 index d815ee2297..0000000000 --- a/docs_src/multiple_values/options_with_multiple_values/tutorial003_py39.py +++ /dev/null @@ -1,32 +0,0 @@ -from enum import Enum - -import typer - - -class Food(str, Enum): - f1 = "Eggs" - f2 = "Bacon" - f3 = "Cheese" - - -app = typer.Typer() - - -@app.command() -def main( - user: tuple[str, int, bool, Food] = typer.Option( - (None, None, None, "f1"), enum_by_name=True - ), -): - username, coins, is_wizard, food = user - if not username: - print("No user provided") - raise typer.Abort() - print(f"The username {username} has {coins} coins") - if is_wizard: - print("And this user is a wizard!") - print(f"And they love eating {food.value}") - - -if __name__ == "__main__": - app() diff --git a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py deleted file mode 100644 index 5f5fd2a7b2..0000000000 --- a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py +++ /dev/null @@ -1,65 +0,0 @@ -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("tutorial003_py39"), - pytest.param("tutorial003_an_py39"), - ], -) -def get_mod(request: pytest.FixtureRequest) -> ModuleType: - module_name = ( - f"docs_src.multiple_values.arguments_with_multiple_values.{request.param}" - ) - mod = importlib.import_module(module_name) - return mod - - -def test_help(mod: ModuleType): - result = runner.invoke(mod.app, ["--help"]) - assert result.exit_code == 0 - assert "[OPTIONS] [NAMES]..." in result.output - assert "Arguments" in result.output - assert "[default: Harry, Hermione, Ron, hero3]" in result.output - - -def test_defaults(mod: ModuleType): - result = runner.invoke(mod.app) - assert result.exit_code == 0 - assert "Hello Harry" in result.output - assert "Hello Hermione" in result.output - assert "Hello Ron" in result.output - assert "Hello Wonder woman" in result.output - - -def test_invalid_args(mod: ModuleType): - result = runner.invoke(mod.app, ["Draco", "Hagrid"]) - assert result.exit_code != 0 - assert "Argument 'names' takes 4 values" in result.output - - -def test_valid_args(mod: ModuleType): - result = runner.invoke(mod.app, ["Draco", "Hagrid", "Dobby", "hero1"]) - assert result.exit_code == 0 - assert "Hello Draco" in result.stdout - assert "Hello Hagrid" in result.stdout - assert "Hello Dobby" in result.stdout - assert "Hello Superman" in result.stdout - - -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 diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py deleted file mode 100644 index 52a4aa7b4d..0000000000 --- a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py +++ /dev/null @@ -1,62 +0,0 @@ -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("tutorial002_py39"), - pytest.param("tutorial002_an_py39"), - ], -) -def get_mod(request: pytest.FixtureRequest) -> ModuleType: - module_name = ( - f"docs_src.multiple_values.options_with_multiple_values.{request.param}" - ) - mod = importlib.import_module(module_name) - return mod - - -def test_main(mod: ModuleType): - result = runner.invoke(mod.app) - assert result.exit_code != 0 - assert "No user provided" in result.output - assert "Aborted" in result.output - - -def test_user_1(mod: ModuleType): - result = runner.invoke(mod.app, ["--user", "Camila", "50", "yes", "Eggs"]) - assert result.exit_code == 0 - assert "The username Camila has 50 coins" in result.output - assert "And this user is a wizard!" in result.output - assert "And they love eating Eggs" in result.output - - -def test_user_2(mod: ModuleType): - result = runner.invoke(mod.app, ["--user", "Morty", "3", "no", "Bacon"]) - assert result.exit_code == 0 - assert "The username Morty has 3 coins" in result.output - assert "And this user is a wizard!" not in result.output - assert "And they love eating Bacon" in result.output - - -def test_invalid_user(mod: ModuleType): - result = runner.invoke(mod.app, ["--user", "Camila", "50"]) - assert result.exit_code != 0 - assert "Option '--user' requires 4 arguments" 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 diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003.py deleted file mode 100644 index 59b4f3de27..0000000000 --- a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003.py +++ /dev/null @@ -1,62 +0,0 @@ -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("tutorial003_py39"), - pytest.param("tutorial003_an_py39"), - ], -) -def get_mod(request: pytest.FixtureRequest) -> ModuleType: - module_name = ( - f"docs_src.multiple_values.options_with_multiple_values.{request.param}" - ) - mod = importlib.import_module(module_name) - return mod - - -def test_main(mod: ModuleType): - result = runner.invoke(mod.app) - assert result.exit_code != 0 - assert "No user provided" in result.output - assert "Aborted" in result.output - - -def test_user_1(mod: ModuleType): - result = runner.invoke(mod.app, ["--user", "Camila", "50", "yes", "f1"]) - assert result.exit_code == 0 - assert "The username Camila has 50 coins" in result.output - assert "And this user is a wizard!" in result.output - assert "And they love eating Eggs" in result.output - - -def test_user_2(mod: ModuleType): - result = runner.invoke(mod.app, ["--user", "Morty", "3", "no", "f2"]) - assert result.exit_code == 0 - assert "The username Morty has 3 coins" in result.output - assert "And this user is a wizard!" not in result.output - assert "And they love eating Bacon" in result.output - - -def test_invalid_user(mod: ModuleType): - result = runner.invoke(mod.app, ["--user", "Camila", "50"]) - assert result.exit_code != 0 - assert "Option '--user' requires 4 arguments" 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 0e62f69ea65657d925fde82c120899e3cef2e784 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:09:58 +0000 Subject: [PATCH 40/44] =?UTF-8?q?=F0=9F=8E=A8=20Auto=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- typer/params.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/typer/params.py b/typer/params.py index b5e86624f3..ef1feb5df5 100644 --- a/typer/params.py +++ b/typer/params.py @@ -600,7 +600,7 @@ def main( """ For a CLI Option representing an [Enum (choice)](https://typer.tiangolo.com/tutorial/parameter-types/enum), accept names from the command line instead of values. - + **Example** ```python @@ -610,7 +610,7 @@ class Food(str, Enum): f1 = "Eggs" f2 = "Bacon" f3 = "Cheese" - + @app.command() def main( groceries: Annotated[list[Food], typer.Option(enum_by_name=True)] = ["f1", "f3"] From cb75b2fb6c133c1ee4fb86df3a24c8d2907ebe76 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Sat, 14 Feb 2026 18:11:03 +0100 Subject: [PATCH 41/44] fix --- typer/params.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typer/params.py b/typer/params.py index b5e86624f3..6093a66df6 100644 --- a/typer/params.py +++ b/typer/params.py @@ -1472,7 +1472,7 @@ def main( bool, Doc( """ - For a CLI Option representing an [Enum (choice)](https://typer.tiangolo.com/tutorial/parameter-types/enum), + For a CLI Argument representing an [Enum (choice)](https://typer.tiangolo.com/tutorial/parameter-types/enum), accept names from the command line instead of values. **Example** From ced68ca68f47c36a43c2b440bbe2f674de5dbec2 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Tue, 17 Feb 2026 09:09:00 +0100 Subject: [PATCH 42/44] refactor to py310 --- docs/tutorial/parameter-types/enum.md | 6 +++--- .../{tutorial005_an_py39.py => tutorial005_an_py310.py} | 0 .../enum/{tutorial005_py39.py => tutorial005_py310.py} | 0 .../{tutorial006_an_py39.py => tutorial006_an_py310.py} | 0 .../enum/{tutorial006_py39.py => tutorial006_py310.py} | 0 .../{tutorial007_an_py39.py => tutorial007_an_py310.py} | 0 .../enum/{tutorial007_py39.py => tutorial007_py310.py} | 0 pyproject.toml | 2 +- .../test_parameter_types/test_enum/test_tutorial005.py | 4 ++-- .../test_parameter_types/test_enum/test_tutorial006.py | 4 ++-- .../test_parameter_types/test_enum/test_tutorial007.py | 4 ++-- 11 files changed, 10 insertions(+), 10 deletions(-) rename docs_src/parameter_types/enum/{tutorial005_an_py39.py => tutorial005_an_py310.py} (100%) rename docs_src/parameter_types/enum/{tutorial005_py39.py => tutorial005_py310.py} (100%) rename docs_src/parameter_types/enum/{tutorial006_an_py39.py => tutorial006_an_py310.py} (100%) rename docs_src/parameter_types/enum/{tutorial006_py39.py => tutorial006_py310.py} (100%) rename docs_src/parameter_types/enum/{tutorial007_an_py39.py => tutorial007_an_py310.py} (100%) rename docs_src/parameter_types/enum/{tutorial007_py39.py => tutorial007_py310.py} (100%) diff --git a/docs/tutorial/parameter-types/enum.md b/docs/tutorial/parameter-types/enum.md index 488b4c093d..012db6bfa8 100644 --- a/docs/tutorial/parameter-types/enum.md +++ b/docs/tutorial/parameter-types/enum.md @@ -80,7 +80,7 @@ Sometimes you want to accept `Enum` names from the command line and convert that into `Enum` values in the command handler. You can enable this by setting `enum_by_name=True`: -{* docs_src/parameter_types/enum/tutorial007_an_py39.py hl[18] *} +{* docs_src/parameter_types/enum/tutorial007_an_py310.py hl[18] *} And then the names of the `Enum` can be used instead of values: @@ -96,7 +96,7 @@ Log level set to DEBUG This can be particularly useful if the enum values are not strings: -{* docs_src/parameter_types/enum/tutorial005_an_py39.py hl[8:11,18] *} +{* docs_src/parameter_types/enum/tutorial005_an_py310.py hl[8:11,18] *} ```console $ python main.py --access protected @@ -145,7 +145,7 @@ Buying groceries: Eggs, Bacon You can also combine `enum_by_name=True` with a list of enums: -{* docs_src/parameter_types/enum/tutorial006_an_py39.py hl[18] *} +{* docs_src/parameter_types/enum/tutorial006_an_py310.py hl[18] *} This works exactly the same, but you're using the enum names instead of values: diff --git a/docs_src/parameter_types/enum/tutorial005_an_py39.py b/docs_src/parameter_types/enum/tutorial005_an_py310.py similarity index 100% rename from docs_src/parameter_types/enum/tutorial005_an_py39.py rename to docs_src/parameter_types/enum/tutorial005_an_py310.py diff --git a/docs_src/parameter_types/enum/tutorial005_py39.py b/docs_src/parameter_types/enum/tutorial005_py310.py similarity index 100% rename from docs_src/parameter_types/enum/tutorial005_py39.py rename to docs_src/parameter_types/enum/tutorial005_py310.py diff --git a/docs_src/parameter_types/enum/tutorial006_an_py39.py b/docs_src/parameter_types/enum/tutorial006_an_py310.py similarity index 100% rename from docs_src/parameter_types/enum/tutorial006_an_py39.py rename to docs_src/parameter_types/enum/tutorial006_an_py310.py diff --git a/docs_src/parameter_types/enum/tutorial006_py39.py b/docs_src/parameter_types/enum/tutorial006_py310.py similarity index 100% rename from docs_src/parameter_types/enum/tutorial006_py39.py rename to docs_src/parameter_types/enum/tutorial006_py310.py diff --git a/docs_src/parameter_types/enum/tutorial007_an_py39.py b/docs_src/parameter_types/enum/tutorial007_an_py310.py similarity index 100% rename from docs_src/parameter_types/enum/tutorial007_an_py39.py rename to docs_src/parameter_types/enum/tutorial007_an_py310.py diff --git a/docs_src/parameter_types/enum/tutorial007_py39.py b/docs_src/parameter_types/enum/tutorial007_py310.py similarity index 100% rename from docs_src/parameter_types/enum/tutorial007_py39.py rename to docs_src/parameter_types/enum/tutorial007_py310.py diff --git a/pyproject.toml b/pyproject.toml index 918a054d8e..a70d01734a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -231,7 +231,7 @@ ignore = [ "docs_src/options_autocompletion/tutorial007_an_py310.py" = ["B006"] "docs_src/options_autocompletion/tutorial008_an_py310.py" = ["B006"] "docs_src/options_autocompletion/tutorial009_an_py310.py" = ["B006"] -"docs_src/parameter_types/enum/tutorial003_an_py310.py" = ["B006"]"docs_src/parameter_types/enum/tutorial006_an_py39.py" = ["B006"] +"docs_src/parameter_types/enum/tutorial003_an_py310.py" = ["B006"] "docs_src/parameter_types/enum/tutorial006_an_py310.py" = ["B006"] # Loop control variable `value` not used within loop body "docs_src/progressbar/tutorial001_py310.py" = ["B007"] diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py index e4963b4855..4f09eb1824 100644 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py @@ -12,8 +12,8 @@ @pytest.fixture( name="mod", params=[ - pytest.param("tutorial005_py39"), - pytest.param("tutorial005_an_py39"), + pytest.param("tutorial005_py310"), + pytest.param("tutorial005_an_py310"), ], ) def get_mod(request: pytest.FixtureRequest) -> ModuleType: diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006.py index c884fa3e08..0e7308275b 100644 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006.py +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006.py @@ -12,8 +12,8 @@ @pytest.fixture( name="mod", params=[ - pytest.param("tutorial006_py39"), - pytest.param("tutorial006_an_py39"), + pytest.param("tutorial006_py310"), + pytest.param("tutorial006_an_py310"), ], ) def get_mod(request: pytest.FixtureRequest) -> ModuleType: diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007.py index 8e9f670576..51322b0ade 100644 --- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007.py +++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007.py @@ -12,8 +12,8 @@ @pytest.fixture( name="mod", params=[ - pytest.param("tutorial007_py39"), - pytest.param("tutorial007_an_py39"), + pytest.param("tutorial007_py310"), + pytest.param("tutorial007_an_py310"), ], ) def get_mod(request: pytest.FixtureRequest) -> ModuleType: From cacc67f45155908953d4d4a0d42a29c94e2cc645 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 08:10:04 +0000 Subject: [PATCH 43/44] =?UTF-8?q?=F0=9F=8E=A8=20Auto=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- typer/main.py | 134 +++++++++++---------- typer/models.py | 199 +++++++++++++++---------------- typer/params.py | 307 ++++++++++++++++++++++++------------------------ 3 files changed, 314 insertions(+), 326 deletions(-) diff --git a/typer/main.py b/typer/main.py index b05abe4e49..a334de1163 100644 --- a/typer/main.py +++ b/typer/main.py @@ -5,14 +5,14 @@ import subprocess import sys import traceback -from collections.abc import Sequence +from collections.abc import Callable, Sequence from datetime import datetime from enum import Enum from functools import update_wrapper from pathlib import Path from traceback import FrameSummary, StackSummary from types import TracebackType -from typing import Annotated, Any, Callable, Optional, Union +from typing import Annotated, Any from uuid import UUID import click @@ -57,9 +57,9 @@ def except_hook( - exc_type: type[BaseException], exc_value: BaseException, tb: Optional[TracebackType] + exc_type: type[BaseException], exc_value: BaseException, tb: TracebackType | None ) -> None: - exception_config: Union[DeveloperExceptionConfig, None] = getattr( + exception_config: DeveloperExceptionConfig | None = getattr( exc_value, _typer_developer_exception_attr_name, None ) standard_traceback = os.getenv( @@ -134,7 +134,7 @@ def __init__( self, *, name: Annotated[ - Optional[str], + str | None, Doc( """ The name of this application. @@ -151,7 +151,7 @@ def __init__( ), ] = Default(None), cls: Annotated[ - Optional[type[TyperGroup]], + type[TyperGroup] | None, Doc( """ The class of this app. Mainly used when [using the Click library underneath](https://typer.tiangolo.com/tutorial/using-click/). Can usually be left at the default value `None`. @@ -200,7 +200,7 @@ def __init__( ), ] = Default(False), subcommand_metavar: Annotated[ - Optional[str], + str | None, Doc( """ **Note**: you probably shouldn't use this parameter, it is inherited @@ -226,7 +226,7 @@ def __init__( ), ] = Default(False), result_callback: Annotated[ - Optional[Callable[..., Any]], + Callable[..., Any] | None, Doc( """ **Note**: you probably shouldn't use this parameter, it is inherited @@ -240,7 +240,7 @@ def __init__( ] = Default(None), # Command context_settings: Annotated[ - Optional[dict[Any, Any]], + dict[Any, Any] | None, Doc( """ Pass configurations for the [context](https://typer.tiangolo.com/tutorial/commands/context/). @@ -257,7 +257,7 @@ def __init__( ), ] = Default(None), callback: Annotated[ - Optional[Callable[..., Any]], + Callable[..., Any] | None, Doc( """ Add a callback to the main Typer app. Can be overridden with `@app.callback()`. @@ -277,7 +277,7 @@ def callback(): ), ] = Default(None), help: Annotated[ - Optional[str], + str | None, Doc( """ Help text for the main Typer app. @@ -295,7 +295,7 @@ def callback(): ), ] = Default(None), epilog: Annotated[ - Optional[str], + str | None, Doc( """ Text that will be printed right after the help text. @@ -311,7 +311,7 @@ def callback(): ), ] = Default(None), short_help: Annotated[ - Optional[str], + str | None, Doc( """ A shortened version of the help text that can be used e.g. in the help table listing subcommands. @@ -426,7 +426,7 @@ def callback(): ), ] = DEFAULT_MARKUP_MODE, rich_help_panel: Annotated[ - Union[str, None], + str | None, Doc( """ Set the panel name of the command when the help is printed with Rich. @@ -542,13 +542,13 @@ def callback(): ) self.registered_groups: list[TyperInfo] = [] self.registered_commands: list[CommandInfo] = [] - self.registered_callback: Optional[TyperInfo] = None + self.registered_callback: TyperInfo | None = None def callback( self, *, cls: Annotated[ - Optional[type[TyperGroup]], + type[TyperGroup] | None, Doc( """ The class of this app. Mainly used when [using the Click library underneath](https://typer.tiangolo.com/tutorial/using-click/). Can usually be left at the default value `None`. @@ -573,7 +573,7 @@ def callback( ), ] = Default(False), subcommand_metavar: Annotated[ - Optional[str], + str | None, Doc( """ **Note**: you probably shouldn't use this parameter, it is inherited @@ -599,7 +599,7 @@ def callback( ), ] = Default(False), result_callback: Annotated[ - Optional[Callable[..., Any]], + Callable[..., Any] | None, Doc( """ **Note**: you probably shouldn't use this parameter, it is inherited @@ -613,7 +613,7 @@ def callback( ] = Default(None), # Command context_settings: Annotated[ - Optional[dict[Any, Any]], + dict[Any, Any] | None, Doc( """ Pass configurations for the [context](https://typer.tiangolo.com/tutorial/commands/context/). @@ -622,7 +622,7 @@ def callback( ), ] = Default(None), help: Annotated[ - Optional[str], + str | None, Doc( """ Help text for the command. @@ -632,7 +632,7 @@ def callback( ), ] = Default(None), epilog: Annotated[ - Optional[str], + str | None, Doc( """ Text that will be printed right after the help text. @@ -640,7 +640,7 @@ def callback( ), ] = Default(None), short_help: Annotated[ - Optional[str], + str | None, Doc( """ A shortened version of the help text that can be used e.g. in the help table listing subcommands. @@ -649,7 +649,7 @@ def callback( ), ] = Default(None), options_metavar: Annotated[ - Optional[str], + str | None, Doc( """ In the example usage string of the help text for a command, the default placeholder for various arguments is `[OPTIONS]`. @@ -688,7 +688,7 @@ def callback( ] = Default(False), # Rich settings rich_help_panel: Annotated[ - Union[str, None], + str | None, Doc( """ Set the panel name of the command when the help is printed with Rich. @@ -751,7 +751,7 @@ def decorator(f: CommandFunctionType) -> CommandFunctionType: def command( self, name: Annotated[ - Optional[str], + str | None, Doc( """ The name of this command. @@ -760,7 +760,7 @@ def command( ] = None, *, cls: Annotated[ - Optional[type[TyperCommand]], + type[TyperCommand] | None, Doc( """ The class of this command. Mainly used when [using the Click library underneath](https://typer.tiangolo.com/tutorial/using-click/). Can usually be left at the default value `None`. @@ -769,7 +769,7 @@ def command( ), ] = None, context_settings: Annotated[ - Optional[dict[Any, Any]], + dict[Any, Any] | None, Doc( """ Pass configurations for the [context](https://typer.tiangolo.com/tutorial/commands/context/). @@ -778,7 +778,7 @@ def command( ), ] = None, help: Annotated[ - Optional[str], + str | None, Doc( """ Help text for the command. @@ -788,7 +788,7 @@ def command( ), ] = None, epilog: Annotated[ - Optional[str], + str | None, Doc( """ Text that will be printed right after the help text. @@ -796,7 +796,7 @@ def command( ), ] = None, short_help: Annotated[ - Optional[str], + str | None, Doc( """ A shortened version of the help text that can be used e.g. in the help table listing subcommands. @@ -805,7 +805,7 @@ def command( ), ] = None, options_metavar: Annotated[ - Optional[str], + str | None, Doc( """ In the example usage string of the help text for a command, the default placeholder for various arguments is `[OPTIONS]`. @@ -852,7 +852,7 @@ def command( ] = False, # Rich settings rich_help_panel: Annotated[ - Union[str, None], + str | None, Doc( """ Set the panel name of the command when the help is printed with Rich. @@ -915,7 +915,7 @@ def add_typer( typer_instance: "Typer", *, name: Annotated[ - Optional[str], + str | None, Doc( """ The name of this subcommand. @@ -925,7 +925,7 @@ def add_typer( ), ] = Default(None), cls: Annotated[ - Optional[type[TyperGroup]], + type[TyperGroup] | None, Doc( """ The class of this subcommand. Mainly used when [using the Click library underneath](https://typer.tiangolo.com/tutorial/using-click/). Can usually be left at the default value `None`. @@ -950,7 +950,7 @@ def add_typer( ), ] = Default(False), subcommand_metavar: Annotated[ - Optional[str], + str | None, Doc( """ **Note**: you probably shouldn't use this parameter, it is inherited @@ -976,7 +976,7 @@ def add_typer( ), ] = Default(False), result_callback: Annotated[ - Optional[Callable[..., Any]], + Callable[..., Any] | None, Doc( """ **Note**: you probably shouldn't use this parameter, it is inherited @@ -990,7 +990,7 @@ def add_typer( ] = Default(None), # Command context_settings: Annotated[ - Optional[dict[Any, Any]], + dict[Any, Any] | None, Doc( """ Pass configurations for the [context](https://typer.tiangolo.com/tutorial/commands/context/). @@ -999,7 +999,7 @@ def add_typer( ), ] = Default(None), callback: Annotated[ - Optional[Callable[..., Any]], + Callable[..., Any] | None, Doc( """ Add a callback to this app. @@ -1008,7 +1008,7 @@ def add_typer( ), ] = Default(None), help: Annotated[ - Optional[str], + str | None, Doc( """ Help text for the subcommand. @@ -1018,7 +1018,7 @@ def add_typer( ), ] = Default(None), epilog: Annotated[ - Optional[str], + str | None, Doc( """ Text that will be printed right after the help text. @@ -1026,7 +1026,7 @@ def add_typer( ), ] = Default(None), short_help: Annotated[ - Optional[str], + str | None, Doc( """ A shortened version of the help text that can be used e.g. in the help table listing subcommands. @@ -1035,7 +1035,7 @@ def add_typer( ), ] = Default(None), options_metavar: Annotated[ - Optional[str], + str | None, Doc( """ In the example usage string of the help text for a command, the default placeholder for various arguments is `[OPTIONS]`. @@ -1074,7 +1074,7 @@ def add_typer( ] = False, # Rich settings rich_help_panel: Annotated[ - Union[str, None], + str | None, Doc( """ Set the panel name of the command when the help is printed with Rich. @@ -1361,8 +1361,8 @@ def get_command_name(name: str) -> str: def get_params_convertors_ctx_param_name_from_function( - callback: Optional[Callable[..., Any]], -) -> tuple[list[Union[click.Argument, click.Option]], dict[str, Any], Optional[str]]: + callback: Callable[..., Any] | None, +) -> tuple[list[click.Argument | click.Option], dict[str, Any], str | None]: params = [] convertors = {} context_param_name = None @@ -1426,8 +1426,8 @@ def get_command_from_info( def determine_type_convertor( type_: Any, enum_by_name: bool -) -> Optional[Callable[[Any], Any]]: - convertor: Optional[Callable[[Any], Any]] = None +) -> Callable[[Any], Any] | None: + convertor: Callable[[Any], Any] | None = None if lenient_issubclass(type_, Path): convertor = param_path_convertor if lenient_issubclass(type_, Enum): @@ -1438,7 +1438,7 @@ def determine_type_convertor( return convertor -def param_path_convertor(value: Optional[str] = None) -> Optional[Path]: +def param_path_convertor(value: str | None = None) -> Path | None: if value is not None: # allow returning any subclass of Path created by an annotated parser without converting # it back to a Path @@ -1472,9 +1472,9 @@ def convertor(value: Any) -> Any: def generate_list_convertor( - convertor: Optional[Callable[[Any], Any]], default_value: Optional[Any] -) -> Callable[[Optional[Sequence[Any]]], Optional[list[Any]]]: - def internal_convertor(value: Optional[Sequence[Any]]) -> Optional[list[Any]]: + convertor: Callable[[Any], Any] | None, default_value: Any | None +) -> Callable[[Sequence[Any] | None], list[Any] | None]: + def internal_convertor(value: Sequence[Any] | None) -> list[Any] | None: if (value is None) or (default_value is None and len(value) == 0): return None return [convertor(v) if convertor else v for v in value] @@ -1485,12 +1485,12 @@ def internal_convertor(value: Optional[Sequence[Any]]) -> Optional[list[Any]]: def generate_tuple_convertor( types: Sequence[Any], enum_by_name: bool, -) -> Callable[[Optional[tuple[Any, ...]]], Optional[tuple[Any, ...]]]: +) -> Callable[[tuple[Any, ...] | None], tuple[Any, ...] | None]: convertors = [determine_type_convertor(type_, enum_by_name) for type_ in types] def internal_convertor( - param_args: Optional[tuple[Any, ...]], - ) -> Optional[tuple[Any, ...]]: + param_args: tuple[Any, ...] | None, + ) -> tuple[Any, ...] | None: if param_args is None: return None return tuple( @@ -1503,12 +1503,12 @@ def internal_convertor( def get_callback( *, - callback: Optional[Callable[..., Any]] = None, + callback: Callable[..., Any] | None = None, params: Sequence[click.Parameter] = [], - convertors: Optional[dict[str, Callable[[str], Any]]] = None, - context_param_name: Optional[str] = None, + convertors: dict[str, Callable[[str], Any]] | None = None, + context_param_name: str | None = None, pretty_exceptions_short: bool, -) -> Optional[Callable[..., Any]]: +) -> Callable[..., Any] | None: use_convertors = convertors or {} if not callback: return None @@ -1639,15 +1639,13 @@ def get_click_type( raise RuntimeError(f"Type not yet supported: {annotation}") # pragma: no cover -def lenient_issubclass( - cls: Any, class_or_tuple: Union[AnyType, tuple[AnyType, ...]] -) -> bool: +def lenient_issubclass(cls: Any, class_or_tuple: AnyType | tuple[AnyType, ...]) -> bool: return isinstance(cls, type) and issubclass(cls, class_or_tuple) def get_click_param( param: ParamMeta, -) -> tuple[Union[click.Argument, click.Option], Any]: +) -> tuple[click.Argument | click.Option, Any]: # First, find out what will be: # * ParamInfo (ArgumentInfo or OptionInfo) # * default_value @@ -1811,9 +1809,9 @@ def get_click_param( def get_param_callback( *, - callback: Optional[Callable[..., Any]] = None, - convertor: Optional[Callable[..., Any]] = None, -) -> Optional[Callable[..., Any]]: + callback: Callable[..., Any] | None = None, + convertor: Callable[..., Any] | None = None, +) -> Callable[..., Any] | None: if not callback: return None parameters = get_params_from_function(callback) @@ -1862,8 +1860,8 @@ def wrapper(ctx: click.Context, param: click.Parameter, value: Any) -> Any: def get_param_completion( - callback: Optional[Callable[..., Any]] = None, -) -> Optional[Callable[..., Any]]: + callback: Callable[..., Any] | None = None, +) -> Callable[..., Any] | None: if not callback: return None parameters = get_params_from_function(callback) @@ -1900,7 +1898,7 @@ def get_param_completion( f"Invalid autocompletion callback parameters: {show_params}" ) - def wrapper(ctx: click.Context, args: list[str], incomplete: Optional[str]) -> Any: + def wrapper(ctx: click.Context, args: list[str], incomplete: str | None) -> Any: use_params: dict[str, Any] = {} if ctx_name: use_params[ctx_name] = ctx diff --git a/typer/models.py b/typer/models.py index 45dab74340..448bbef561 100644 --- a/typer/models.py +++ b/typer/models.py @@ -1,13 +1,11 @@ import inspect import io -from collections.abc import Sequence +from collections.abc import Callable, Sequence from typing import ( TYPE_CHECKING, Any, - Callable, Optional, TypeVar, - Union, ) import click @@ -197,21 +195,21 @@ def Default(value: DefaultType) -> DefaultType: class CommandInfo: def __init__( self, - name: Optional[str] = None, + name: str | None = None, *, - cls: Optional[type["TyperCommand"]] = None, - context_settings: Optional[dict[Any, Any]] = None, - callback: Optional[Callable[..., Any]] = None, - help: Optional[str] = None, - epilog: Optional[str] = None, - short_help: Optional[str] = None, + cls: type["TyperCommand"] | None = None, + context_settings: dict[Any, Any] | None = None, + callback: Callable[..., Any] | None = None, + help: str | None = None, + epilog: str | None = None, + short_help: str | None = None, options_metavar: str = "[OPTIONS]", add_help_option: bool = True, no_args_is_help: bool = False, hidden: bool = False, deprecated: bool = False, # Rich settings - rich_help_panel: Union[str, None] = None, + rich_help_panel: str | None = None, ): self.name = name self.cls = cls @@ -234,25 +232,25 @@ def __init__( self, typer_instance: Optional["Typer"] = Default(None), *, - name: Optional[str] = Default(None), - cls: Optional[type["TyperGroup"]] = Default(None), + name: str | None = Default(None), + cls: type["TyperGroup"] | None = Default(None), invoke_without_command: bool = Default(False), no_args_is_help: bool = Default(False), - subcommand_metavar: Optional[str] = Default(None), + subcommand_metavar: str | None = Default(None), chain: bool = Default(False), - result_callback: Optional[Callable[..., Any]] = Default(None), + result_callback: Callable[..., Any] | None = Default(None), # Command - context_settings: Optional[dict[Any, Any]] = Default(None), - callback: Optional[Callable[..., Any]] = Default(None), - help: Optional[str] = Default(None), - epilog: Optional[str] = Default(None), - short_help: Optional[str] = Default(None), + context_settings: dict[Any, Any] | None = Default(None), + callback: Callable[..., Any] | None = Default(None), + help: str | None = Default(None), + epilog: str | None = Default(None), + short_help: str | None = Default(None), options_metavar: str = Default("[OPTIONS]"), add_help_option: bool = Default(True), hidden: bool = Default(False), deprecated: bool = Default(False), # Rich settings - rich_help_panel: Union[str, None] = Default(None), + rich_help_panel: str | None = Default(None), ): self.typer_instance = typer_instance self.name = name @@ -278,46 +276,45 @@ class ParameterInfo: def __init__( self, *, - default: Optional[Any] = None, - param_decls: Optional[Sequence[str]] = None, - callback: Optional[Callable[..., Any]] = None, - metavar: Optional[str] = None, + default: Any | None = None, + param_decls: Sequence[str] | None = None, + callback: Callable[..., Any] | None = None, + metavar: str | None = None, expose_value: bool = True, is_eager: bool = False, - envvar: Optional[Union[str, list[str]]] = None, + envvar: str | list[str] | None = None, # Note that shell_complete is not fully supported and will be removed in future versions # TODO: Remove shell_complete in a future version (after 0.16.0) - shell_complete: Optional[ - Callable[ - [click.Context, click.Parameter, str], - Union[list["click.shell_completion.CompletionItem"], list[str]], - ] - ] = None, - autocompletion: Optional[Callable[..., Any]] = None, - default_factory: Optional[Callable[[], Any]] = None, + shell_complete: Callable[ + [click.Context, click.Parameter, str], + list["click.shell_completion.CompletionItem"] | list[str], + ] + | None = None, + autocompletion: Callable[..., Any] | None = None, + default_factory: Callable[[], Any] | None = None, # Custom type - parser: Optional[Callable[[str], Any]] = None, - click_type: Optional[click.ParamType] = None, + parser: Callable[[str], Any] | None = None, + click_type: click.ParamType | None = None, # TyperArgument - show_default: Union[bool, str] = True, + show_default: bool | str = True, show_choices: bool = True, show_envvar: bool = True, - help: Optional[str] = None, + help: str | None = None, hidden: bool = False, # Choice case_sensitive: bool = True, enum_by_name: bool = False, # Numbers - min: Optional[Union[int, float]] = None, - max: Optional[Union[int, float]] = None, + min: int | float | None = None, + max: int | float | None = None, clamp: bool = False, # DateTime - formats: Optional[list[str]] = None, + formats: list[str] | None = None, # File - mode: Optional[str] = None, - encoding: Optional[str] = None, - errors: Optional[str] = "strict", - lazy: Optional[bool] = None, + mode: str | None = None, + encoding: str | None = None, + errors: str | None = "strict", + lazy: bool | None = None, atomic: bool = False, # Path exists: bool = False, @@ -327,9 +324,9 @@ def __init__( readable: bool = True, resolve_path: bool = False, allow_dash: bool = False, - path_type: Union[None, type[str], type[bytes]] = None, + path_type: None | type[str] | type[bytes] = None, # Rich settings - rich_help_panel: Union[str, None] = None, + rich_help_panel: str | None = None, ): # Check if user has provided multiple custom parsers if parser and click_type: @@ -390,38 +387,37 @@ def __init__( self, *, # ParameterInfo - default: Optional[Any] = None, - param_decls: Optional[Sequence[str]] = None, - callback: Optional[Callable[..., Any]] = None, - metavar: Optional[str] = None, + default: Any | None = None, + param_decls: Sequence[str] | None = None, + callback: Callable[..., Any] | None = None, + metavar: str | None = None, expose_value: bool = True, is_eager: bool = False, - envvar: Optional[Union[str, list[str]]] = None, + envvar: str | list[str] | None = None, # Note that shell_complete is not fully supported and will be removed in future versions # TODO: Remove shell_complete in a future version (after 0.16.0) - shell_complete: Optional[ - Callable[ - [click.Context, click.Parameter, str], - Union[list["click.shell_completion.CompletionItem"], list[str]], - ] - ] = None, - autocompletion: Optional[Callable[..., Any]] = None, - default_factory: Optional[Callable[[], Any]] = None, + shell_complete: Callable[ + [click.Context, click.Parameter, str], + list["click.shell_completion.CompletionItem"] | list[str], + ] + | None = None, + autocompletion: Callable[..., Any] | None = None, + default_factory: Callable[[], Any] | None = None, # Custom type - parser: Optional[Callable[[str], Any]] = None, - click_type: Optional[click.ParamType] = None, + parser: Callable[[str], Any] | None = None, + click_type: click.ParamType | None = None, # Option - show_default: Union[bool, str] = True, - prompt: Union[bool, str] = False, + show_default: bool | str = True, + prompt: bool | str = False, confirmation_prompt: bool = False, prompt_required: bool = True, hide_input: bool = False, # TODO: remove is_flag and flag_value in a future release - is_flag: Optional[bool] = None, - flag_value: Optional[Any] = None, + is_flag: bool | None = None, + flag_value: Any | None = None, count: bool = False, allow_from_autoenv: bool = True, - help: Optional[str] = None, + help: str | None = None, hidden: bool = False, show_choices: bool = True, show_envvar: bool = True, @@ -429,16 +425,16 @@ def __init__( case_sensitive: bool = True, enum_by_name: bool = False, # Numbers - min: Optional[Union[int, float]] = None, - max: Optional[Union[int, float]] = None, + min: int | float | None = None, + max: int | float | None = None, clamp: bool = False, # DateTime - formats: Optional[list[str]] = None, + formats: list[str] | None = None, # File - mode: Optional[str] = None, - encoding: Optional[str] = None, - errors: Optional[str] = "strict", - lazy: Optional[bool] = None, + mode: str | None = None, + encoding: str | None = None, + errors: str | None = "strict", + lazy: bool | None = None, atomic: bool = False, # Path exists: bool = False, @@ -448,9 +444,9 @@ def __init__( readable: bool = True, resolve_path: bool = False, allow_dash: bool = False, - path_type: Union[None, type[str], type[bytes]] = None, + path_type: None | type[str] | type[bytes] = None, # Rich settings - rich_help_panel: Union[str, None] = None, + rich_help_panel: str | None = None, ): super().__init__( default=default, @@ -521,46 +517,45 @@ def __init__( self, *, # ParameterInfo - default: Optional[Any] = None, - param_decls: Optional[Sequence[str]] = None, - callback: Optional[Callable[..., Any]] = None, - metavar: Optional[str] = None, + default: Any | None = None, + param_decls: Sequence[str] | None = None, + callback: Callable[..., Any] | None = None, + metavar: str | None = None, expose_value: bool = True, is_eager: bool = False, - envvar: Optional[Union[str, list[str]]] = None, + envvar: str | list[str] | None = None, # Note that shell_complete is not fully supported and will be removed in future versions # TODO: Remove shell_complete in a future version (after 0.16.0) - shell_complete: Optional[ - Callable[ - [click.Context, click.Parameter, str], - Union[list["click.shell_completion.CompletionItem"], list[str]], - ] - ] = None, - autocompletion: Optional[Callable[..., Any]] = None, - default_factory: Optional[Callable[[], Any]] = None, + shell_complete: Callable[ + [click.Context, click.Parameter, str], + list["click.shell_completion.CompletionItem"] | list[str], + ] + | None = None, + autocompletion: Callable[..., Any] | None = None, + default_factory: Callable[[], Any] | None = None, # Custom type - parser: Optional[Callable[[str], Any]] = None, - click_type: Optional[click.ParamType] = None, + parser: Callable[[str], Any] | None = None, + click_type: click.ParamType | None = None, # TyperArgument - show_default: Union[bool, str] = True, + show_default: bool | str = True, show_choices: bool = True, show_envvar: bool = True, - help: Optional[str] = None, + help: str | None = None, hidden: bool = False, # Choice case_sensitive: bool = True, enum_by_name: bool = False, # Numbers - min: Optional[Union[int, float]] = None, - max: Optional[Union[int, float]] = None, + min: int | float | None = None, + max: int | float | None = None, clamp: bool = False, # DateTime - formats: Optional[list[str]] = None, + formats: list[str] | None = None, # File - mode: Optional[str] = None, - encoding: Optional[str] = None, - errors: Optional[str] = "strict", - lazy: Optional[bool] = None, + mode: str | None = None, + encoding: str | None = None, + errors: str | None = "strict", + lazy: bool | None = None, atomic: bool = False, # Path exists: bool = False, @@ -570,9 +565,9 @@ def __init__( readable: bool = True, resolve_path: bool = False, allow_dash: bool = False, - path_type: Union[None, type[str], type[bytes]] = None, + path_type: None | type[str] | type[bytes] = None, # Rich settings - rich_help_panel: Union[str, None] = None, + rich_help_panel: str | None = None, ): super().__init__( default=default, diff --git a/typer/params.py b/typer/params.py index 23e5ddf22f..3b4e1dc7ac 100644 --- a/typer/params.py +++ b/typer/params.py @@ -1,4 +1,5 @@ -from typing import TYPE_CHECKING, Annotated, Any, Callable, Optional, Union, overload +from collections.abc import Callable +from typing import TYPE_CHECKING, Annotated, Any, overload import click from annotated_doc import Doc @@ -13,37 +14,36 @@ @overload def Option( # Parameter - default: Optional[Any] = ..., + default: Any | None = ..., *param_decls: str, - callback: Optional[Callable[..., Any]] = None, - metavar: Optional[str] = None, + callback: Callable[..., Any] | None = None, + metavar: str | None = None, expose_value: bool = True, is_eager: bool = False, - envvar: Optional[Union[str, list[str]]] = None, + envvar: str | list[str] | None = None, # Note that shell_complete is not fully supported and will be removed in future versions # TODO: Remove shell_complete in a future version (after 0.16.0) - shell_complete: Optional[ - Callable[ - [click.Context, click.Parameter, str], - Union[list["click.shell_completion.CompletionItem"], list[str]], - ] - ] = None, - autocompletion: Optional[Callable[..., Any]] = None, - default_factory: Optional[Callable[[], Any]] = None, + shell_complete: Callable[ + [click.Context, click.Parameter, str], + list["click.shell_completion.CompletionItem"] | list[str], + ] + | None = None, + autocompletion: Callable[..., Any] | None = None, + default_factory: Callable[[], Any] | None = None, # Custom type - parser: Optional[Callable[[str], Any]] = None, + parser: Callable[[str], Any] | None = None, # Option - show_default: Union[bool, str] = True, - prompt: Union[bool, str] = False, + show_default: bool | str = True, + prompt: bool | str = False, confirmation_prompt: bool = False, prompt_required: bool = True, hide_input: bool = False, # TODO: remove is_flag and flag_value in a future release - is_flag: Optional[bool] = None, - flag_value: Optional[Any] = None, + is_flag: bool | None = None, + flag_value: Any | None = None, count: bool = False, allow_from_autoenv: bool = True, - help: Optional[str] = None, + help: str | None = None, hidden: bool = False, show_choices: bool = True, show_envvar: bool = True, @@ -51,16 +51,16 @@ def Option( case_sensitive: bool = True, enum_by_name: bool = False, # Numbers - min: Optional[Union[int, float]] = None, - max: Optional[Union[int, float]] = None, + min: int | float | None = None, + max: int | float | None = None, clamp: bool = False, # DateTime - formats: Optional[list[str]] = None, + formats: list[str] | None = None, # File - mode: Optional[str] = None, - encoding: Optional[str] = None, - errors: Optional[str] = "strict", - lazy: Optional[bool] = None, + mode: str | None = None, + encoding: str | None = None, + errors: str | None = "strict", + lazy: bool | None = None, atomic: bool = False, # Path exists: bool = False, @@ -70,9 +70,9 @@ def Option( readable: bool = True, resolve_path: bool = False, allow_dash: bool = False, - path_type: Union[None, type[str], type[bytes]] = None, + path_type: None | type[str] | type[bytes] = None, # Rich settings - rich_help_panel: Union[str, None] = None, + rich_help_panel: str | None = None, ) -> Any: ... @@ -80,37 +80,36 @@ def Option( @overload def Option( # Parameter - default: Optional[Any] = ..., + default: Any | None = ..., *param_decls: str, - callback: Optional[Callable[..., Any]] = None, - metavar: Optional[str] = None, + callback: Callable[..., Any] | None = None, + metavar: str | None = None, expose_value: bool = True, is_eager: bool = False, - envvar: Optional[Union[str, list[str]]] = None, + envvar: str | list[str] | None = None, # Note that shell_complete is not fully supported and will be removed in future versions # TODO: Remove shell_complete in a future version (after 0.16.0) - shell_complete: Optional[ - Callable[ - [click.Context, click.Parameter, str], - Union[list["click.shell_completion.CompletionItem"], list[str]], - ] - ] = None, - autocompletion: Optional[Callable[..., Any]] = None, - default_factory: Optional[Callable[[], Any]] = None, + shell_complete: Callable[ + [click.Context, click.Parameter, str], + list["click.shell_completion.CompletionItem"] | list[str], + ] + | None = None, + autocompletion: Callable[..., Any] | None = None, + default_factory: Callable[[], Any] | None = None, # Custom type - click_type: Optional[click.ParamType] = None, + click_type: click.ParamType | None = None, # Option - show_default: Union[bool, str] = True, - prompt: Union[bool, str] = False, + show_default: bool | str = True, + prompt: bool | str = False, confirmation_prompt: bool = False, prompt_required: bool = True, hide_input: bool = False, # TODO: remove is_flag and flag_value in a future release - is_flag: Optional[bool] = None, - flag_value: Optional[Any] = None, + is_flag: bool | None = None, + flag_value: Any | None = None, count: bool = False, allow_from_autoenv: bool = True, - help: Optional[str] = None, + help: str | None = None, hidden: bool = False, show_choices: bool = True, show_envvar: bool = True, @@ -118,16 +117,16 @@ def Option( case_sensitive: bool = True, enum_by_name: bool = False, # Numbers - min: Optional[Union[int, float]] = None, - max: Optional[Union[int, float]] = None, + min: int | float | None = None, + max: int | float | None = None, clamp: bool = False, # DateTime - formats: Optional[list[str]] = None, + formats: list[str] | None = None, # File - mode: Optional[str] = None, - encoding: Optional[str] = None, - errors: Optional[str] = "strict", - lazy: Optional[bool] = None, + mode: str | None = None, + encoding: str | None = None, + errors: str | None = "strict", + lazy: bool | None = None, atomic: bool = False, # Path exists: bool = False, @@ -137,16 +136,16 @@ def Option( readable: bool = True, resolve_path: bool = False, allow_dash: bool = False, - path_type: Union[None, type[str], type[bytes]] = None, + path_type: None | type[str] | type[bytes] = None, # Rich settings - rich_help_panel: Union[str, None] = None, + rich_help_panel: str | None = None, ) -> Any: ... def Option( # Parameter default: Annotated[ - Optional[Any], + Any | None, Doc( """ Usually, [CLI options](https://typer.tiangolo.com/tutorial/options/) are optional and have a default value, passed on like this: @@ -189,7 +188,7 @@ def main(user_name: Annotated[str, typer.Option("--user", "-u", "-x")]): ), ], callback: Annotated[ - Optional[Callable[..., Any]], + Callable[..., Any] | None, Doc( """ Add a callback to this CLI Option, to execute additional logic after its value was received from the terminal. @@ -211,7 +210,7 @@ def main(name: Annotated[str, typer.Option(callback=name_callback)]): ), ] = None, metavar: Annotated[ - Optional[str], + str | None, Doc( """ Customize the name displayed in the [help text](https://typer.tiangolo.com/tutorial/options/help/) to represent this CLI option. @@ -249,7 +248,7 @@ def main(user: Annotated[str, typer.Option(metavar="User name")]): ), ] = False, envvar: Annotated[ - Optional[Union[str, list[str]]], + str | list[str] | None, Doc( """ Configure a CLI Option to read its value from an environment variable if it is not provided in the command line. @@ -267,12 +266,11 @@ def main(user: Annotated[str, typer.Option(envvar="ME")]): ] = None, # TODO: Remove shell_complete in a future version (after 0.16.0) shell_complete: Annotated[ - Optional[ - Callable[ - [click.Context, click.Parameter, str], - Union[list["click.shell_completion.CompletionItem"], list[str]], - ] - ], + Callable[ + [click.Context, click.Parameter, str], + list["click.shell_completion.CompletionItem"] | list[str], + ] + | None, Doc( """ **Note**: you probably shouldn't use this parameter, it is inherited from Click and supported for compatibility. @@ -281,7 +279,7 @@ def main(user: Annotated[str, typer.Option(envvar="ME")]): ), ] = None, autocompletion: Annotated[ - Optional[Callable[..., Any]], + Callable[..., Any] | None, Doc( """ Provide a custom function that helps to autocomplete the values of this CLI Option. @@ -301,7 +299,7 @@ def main(name: Annotated[str, typer.Option(autocompletion=complete)]): ), ] = None, default_factory: Annotated[ - Optional[Callable[[], Any]], + Callable[[], Any] | None, Doc( """ Provide a custom function that dynamically generates a [default](https://typer.tiangolo.com/tutorial/arguments/default) for this CLI Option. @@ -321,7 +319,7 @@ def main(name: Annotated[str, typer.Option(default_factory=get_name)]): ] = None, # Custom type parser: Annotated[ - Optional[Callable[[str], Any]], + Callable[[str], Any] | None, Doc( """ Use your own custom types in Typer applications by defining a `parser` function that parses input into your own types: @@ -347,7 +345,7 @@ def main(opt: Annotated[CustomClass, typer.Option(parser=my_parser)] = "Foo"): ), ] = None, click_type: Annotated[ - Optional[click.ParamType], + click.ParamType | None, Doc( """ Define this parameter to use a [custom Click type](https://click.palletsprojects.com/en/stable/parameters/#implementing-custom-types) in your Typer applications. @@ -377,7 +375,7 @@ def main(opt: Annotated[MyClass, typer.Option(click_type=MyParser())] = "Foo"): ] = None, # Option show_default: Annotated[ - Union[bool, str], + bool | str, Doc( """ When set to `False`, don't show the default value of this CLI Option in the [help text](https://typer.tiangolo.com/tutorial/options/help/). @@ -393,7 +391,7 @@ def main(name: Annotated[str, typer.Option(show_default=False)] = "Rick"): ), ] = True, prompt: Annotated[ - Union[bool, str], + bool | str, Doc( """ When set to `True`, a prompt will appear to ask for the value of this CLI Option if it was not provided: @@ -458,7 +456,7 @@ def login( ] = False, # TODO: remove is_flag and flag_value in a future release is_flag: Annotated[ - Optional[bool], + bool | None, Doc( """ **Note**: you probably shouldn't use this parameter, it is inherited from Click and supported for compatibility. @@ -467,7 +465,7 @@ def login( ), ] = None, flag_value: Annotated[ - Optional[Any], + Any | None, Doc( """ **Note**: you probably shouldn't use this parameter, it is inherited from Click and supported for compatibility. @@ -505,7 +503,7 @@ def main(verbose: Annotated[int, typer.Option("--verbose", "-v", count=True)] = ), ] = True, help: Annotated[ - Optional[str], + str | None, Doc( """ Help text for this CLI Option. @@ -622,7 +620,7 @@ def main( ] = False, # Numbers min: Annotated[ - Optional[Union[int, float]], + int | float | None, Doc( """ For a CLI Option representing a [number](https://typer.tiangolo.com/tutorial/parameter-types/number/) (`int` or `float`), @@ -644,7 +642,7 @@ def main( ), ] = None, max: Annotated[ - Optional[Union[int, float]], + int | float | None, Doc( """ For a CLI Option representing a [number](https://typer.tiangolo.com/tutorial/parameter-types/number/) (`int` or `float`), @@ -689,7 +687,7 @@ def main( ] = False, # DateTime formats: Annotated[ - Optional[list[str]], + list[str] | None, Doc( """ For a CLI Option representing a [DateTime object](https://typer.tiangolo.com/tutorial/parameter-types/datetime), @@ -716,7 +714,7 @@ def main( ] = None, # File mode: Annotated[ - Optional[str], + str | None, Doc( """ For a CLI Option representing a [File object](https://typer.tiangolo.com/tutorial/parameter-types/file/), @@ -734,7 +732,7 @@ def main(config: Annotated[typer.FileText, typer.Option(mode="a")]): ), ] = None, encoding: Annotated[ - Optional[str], + str | None, Doc( """ Customize the encoding of this CLI Option represented by a [File object](https://typer.tiangolo.com/tutorial/parameter-types/file/). @@ -750,7 +748,7 @@ def main(config: Annotated[typer.FileText, typer.Option(encoding="utf-8")]): ), ] = None, errors: Annotated[ - Optional[str], + str | None, Doc( """ **Note**: you probably shouldn't use this parameter, it is inherited from Click and supported for compatibility. @@ -762,7 +760,7 @@ def main(config: Annotated[typer.FileText, typer.Option(encoding="utf-8")]): ), ] = "strict", lazy: Annotated[ - Optional[bool], + bool | None, Doc( """ For a CLI Option representing a [File object](https://typer.tiangolo.com/tutorial/parameter-types/file/), @@ -924,7 +922,7 @@ def main(config: Annotated[Path, typer.Option(resolve_path=True)]): ), ] = False, path_type: Annotated[ - Union[None, type[str], type[bytes]], + None | type[str] | type[bytes], Doc( """ A string type that will be used to represent this [`Path` argument](https://typer.tiangolo.com/tutorial/parameter-types/path/). @@ -935,7 +933,7 @@ def main(config: Annotated[Path, typer.Option(resolve_path=True)]): ] = None, # Rich settings rich_help_panel: Annotated[ - Union[str, None], + str | None, Doc( """ Set the panel name where you want this CLI Option to be shown in the [help text](https://typer.tiangolo.com/tutorial/arguments/help). @@ -1036,45 +1034,44 @@ def register( @overload def Argument( # Parameter - default: Optional[Any] = ..., + default: Any | None = ..., *, - callback: Optional[Callable[..., Any]] = None, - metavar: Optional[str] = None, + callback: Callable[..., Any] | None = None, + metavar: str | None = None, expose_value: bool = True, is_eager: bool = False, - envvar: Optional[Union[str, list[str]]] = None, + envvar: str | list[str] | None = None, # Note that shell_complete is not fully supported and will be removed in future versions # TODO: Remove shell_complete in a future version (after 0.16.0) - shell_complete: Optional[ - Callable[ - [click.Context, click.Parameter, str], - Union[list["click.shell_completion.CompletionItem"], list[str]], - ] - ] = None, - autocompletion: Optional[Callable[..., Any]] = None, - default_factory: Optional[Callable[[], Any]] = None, + shell_complete: Callable[ + [click.Context, click.Parameter, str], + list["click.shell_completion.CompletionItem"] | list[str], + ] + | None = None, + autocompletion: Callable[..., Any] | None = None, + default_factory: Callable[[], Any] | None = None, # Custom type - parser: Optional[Callable[[str], Any]] = None, + parser: Callable[[str], Any] | None = None, # TyperArgument - show_default: Union[bool, str] = True, + show_default: bool | str = True, show_choices: bool = True, show_envvar: bool = True, - help: Optional[str] = None, + help: str | None = None, hidden: bool = False, # Choice case_sensitive: bool = True, enum_by_name: bool = False, # Numbers - min: Optional[Union[int, float]] = None, - max: Optional[Union[int, float]] = None, + min: int | float | None = None, + max: int | float | None = None, clamp: bool = False, # DateTime - formats: Optional[list[str]] = None, + formats: list[str] | None = None, # File - mode: Optional[str] = None, - encoding: Optional[str] = None, - errors: Optional[str] = "strict", - lazy: Optional[bool] = None, + mode: str | None = None, + encoding: str | None = None, + errors: str | None = "strict", + lazy: bool | None = None, atomic: bool = False, # Path exists: bool = False, @@ -1084,9 +1081,9 @@ def Argument( readable: bool = True, resolve_path: bool = False, allow_dash: bool = False, - path_type: Union[None, type[str], type[bytes]] = None, + path_type: None | type[str] | type[bytes] = None, # Rich settings - rich_help_panel: Union[str, None] = None, + rich_help_panel: str | None = None, ) -> Any: ... @@ -1094,45 +1091,44 @@ def Argument( @overload def Argument( # Parameter - default: Optional[Any] = ..., + default: Any | None = ..., *, - callback: Optional[Callable[..., Any]] = None, - metavar: Optional[str] = None, + callback: Callable[..., Any] | None = None, + metavar: str | None = None, expose_value: bool = True, is_eager: bool = False, - envvar: Optional[Union[str, list[str]]] = None, + envvar: str | list[str] | None = None, # Note that shell_complete is not fully supported and will be removed in future versions # TODO: Remove shell_complete in a future version (after 0.16.0) - shell_complete: Optional[ - Callable[ - [click.Context, click.Parameter, str], - Union[list["click.shell_completion.CompletionItem"], list[str]], - ] - ] = None, - autocompletion: Optional[Callable[..., Any]] = None, - default_factory: Optional[Callable[[], Any]] = None, + shell_complete: Callable[ + [click.Context, click.Parameter, str], + list["click.shell_completion.CompletionItem"] | list[str], + ] + | None = None, + autocompletion: Callable[..., Any] | None = None, + default_factory: Callable[[], Any] | None = None, # Custom type - click_type: Optional[click.ParamType] = None, + click_type: click.ParamType | None = None, # TyperArgument - show_default: Union[bool, str] = True, + show_default: bool | str = True, show_choices: bool = True, show_envvar: bool = True, - help: Optional[str] = None, + help: str | None = None, hidden: bool = False, # Choice case_sensitive: bool = True, enum_by_name: bool = False, # Numbers - min: Optional[Union[int, float]] = None, - max: Optional[Union[int, float]] = None, + min: int | float | None = None, + max: int | float | None = None, clamp: bool = False, # DateTime - formats: Optional[list[str]] = None, + formats: list[str] | None = None, # File - mode: Optional[str] = None, - encoding: Optional[str] = None, - errors: Optional[str] = "strict", - lazy: Optional[bool] = None, + mode: str | None = None, + encoding: str | None = None, + errors: str | None = "strict", + lazy: bool | None = None, atomic: bool = False, # Path exists: bool = False, @@ -1142,16 +1138,16 @@ def Argument( readable: bool = True, resolve_path: bool = False, allow_dash: bool = False, - path_type: Union[None, type[str], type[bytes]] = None, + path_type: None | type[str] | type[bytes] = None, # Rich settings - rich_help_panel: Union[str, None] = None, + rich_help_panel: str | None = None, ) -> Any: ... def Argument( # Parameter default: Annotated[ - Optional[Any], + Any | None, Doc( """ By default, CLI arguments are required. However, by giving them a default value they become [optional](https://typer.tiangolo.com/tutorial/arguments/optional): @@ -1175,7 +1171,7 @@ def main(name: Annotated[str, typer.Argument()] = "World"): ] = ..., *, callback: Annotated[ - Optional[Callable[..., Any]], + Callable[..., Any] | None, Doc( """ Add a callback to this CLI Argument, to execute additional logic with the value received from the terminal. @@ -1197,7 +1193,7 @@ def main(name: Annotated[str, typer.Argument(callback=name_callback)]): ), ] = None, metavar: Annotated[ - Optional[str], + str | None, Doc( """ Customize the name displayed in the help text to represent this CLI Argument. @@ -1236,7 +1232,7 @@ def main(name: Annotated[str, typer.Argument(metavar="✨username✨")]): ), ] = False, envvar: Annotated[ - Optional[Union[str, list[str]]], + str | list[str] | None, Doc( """ Configure an argument to read a value from an environment variable if it is not provided in the command line as a CLI argument. @@ -1254,12 +1250,11 @@ def main(name: Annotated[str, typer.Argument(envvar="ME")]): ] = None, # TODO: Remove shell_complete in a future version (after 0.16.0) shell_complete: Annotated[ - Optional[ - Callable[ - [click.Context, click.Parameter, str], - Union[list["click.shell_completion.CompletionItem"], list[str]], - ] - ], + Callable[ + [click.Context, click.Parameter, str], + list["click.shell_completion.CompletionItem"] | list[str], + ] + | None, Doc( """ **Note**: you probably shouldn't use this parameter, it is inherited from Click and supported for compatibility. @@ -1268,7 +1263,7 @@ def main(name: Annotated[str, typer.Argument(envvar="ME")]): ), ] = None, autocompletion: Annotated[ - Optional[Callable[..., Any]], + Callable[..., Any] | None, Doc( """ Provide a custom function that helps to autocomplete the values of this CLI Argument. @@ -1288,7 +1283,7 @@ def main(name: Annotated[str, typer.Argument(autocompletion=complete)]): ), ] = None, default_factory: Annotated[ - Optional[Callable[[], Any]], + Callable[[], Any] | None, Doc( """ Provide a custom function that dynamically generates a [default](https://typer.tiangolo.com/tutorial/arguments/default) for this CLI Argument. @@ -1308,7 +1303,7 @@ def main(name: Annotated[str, typer.Argument(default_factory=get_name)]): ] = None, # Custom type parser: Annotated[ - Optional[Callable[[str], Any]], + Callable[[str], Any] | None, Doc( """ Use your own custom types in Typer applications by defining a `parser` function that parses input into your own types: @@ -1334,7 +1329,7 @@ def main(arg: Annotated[CustomClass, typer.Argument(parser=my_parser): ), ] = None, click_type: Annotated[ - Optional[click.ParamType], + click.ParamType | None, Doc( """ Define this parameter to use a [custom Click type](https://click.palletsprojects.com/en/stable/parameters/#implementing-custom-types) in your Typer applications. @@ -1364,7 +1359,7 @@ def main(arg: Annotated[MyClass, typer.Argument(click_type=MyParser())]): ] = None, # TyperArgument show_default: Annotated[ - Union[bool, str], + bool | str, Doc( """ When set to `False`, don't show the default value of this CLI Argument in the [help text](https://typer.tiangolo.com/tutorial/arguments/help/). @@ -1408,7 +1403,7 @@ def main(name: Annotated[str, typer.Argument(envvar="ME", show_envvar=False)]): ), ] = True, help: Annotated[ - Optional[str], + str | None, Doc( """ Help text for this CLI Argument. @@ -1496,7 +1491,7 @@ def main( ] = False, # Numbers min: Annotated[ - Optional[Union[int, float]], + int | float | None, Doc( """ For a CLI Argument representing a [number](https://typer.tiangolo.com/tutorial/parameter-types/number/) (`int` or `float`), @@ -1518,7 +1513,7 @@ def main( ), ] = None, max: Annotated[ - Optional[Union[int, float]], + int | float | None, Doc( """ For a CLI Argument representing a [number](https://typer.tiangolo.com/tutorial/parameter-types/number/) (`int` or `float`), @@ -1563,7 +1558,7 @@ def main( ] = False, # DateTime formats: Annotated[ - Optional[list[str]], + list[str] | None, Doc( """ For a CLI Argument representing a [DateTime object](https://typer.tiangolo.com/tutorial/parameter-types/datetime), @@ -1590,7 +1585,7 @@ def main( ] = None, # File mode: Annotated[ - Optional[str], + str | None, Doc( """ For a CLI Argument representing a [File object](https://typer.tiangolo.com/tutorial/parameter-types/file/), @@ -1608,7 +1603,7 @@ def main(config: Annotated[typer.FileText, typer.Argument(mode="a")]): ), ] = None, encoding: Annotated[ - Optional[str], + str | None, Doc( """ Customize the encoding of this CLI Argument represented by a [File object](https://typer.tiangolo.com/tutorial/parameter-types/file/). @@ -1624,7 +1619,7 @@ def main(config: Annotated[typer.FileText, typer.Argument(encoding="utf-8")]): ), ] = None, errors: Annotated[ - Optional[str], + str | None, Doc( """ **Note**: you probably shouldn't use this parameter, it is inherited from Click and supported for compatibility. @@ -1636,7 +1631,7 @@ def main(config: Annotated[typer.FileText, typer.Argument(encoding="utf-8")]): ), ] = "strict", lazy: Annotated[ - Optional[bool], + bool | None, Doc( """ For a CLI Argument representing a [File object](https://typer.tiangolo.com/tutorial/parameter-types/file/), @@ -1798,7 +1793,7 @@ def main(config: Annotated[Path, typer.Argument(resolve_path=True)]): ), ] = False, path_type: Annotated[ - Union[None, type[str], type[bytes]], + None | type[str] | type[bytes], Doc( """ A string type that will be used to represent this [`Path` argument](https://typer.tiangolo.com/tutorial/parameter-types/path/). @@ -1809,7 +1804,7 @@ def main(config: Annotated[Path, typer.Argument(resolve_path=True)]): ] = None, # Rich settings rich_help_panel: Annotated[ - Union[str, None], + str | None, Doc( """ Set the panel name where you want this CLI Argument to be shown in the [help text](https://typer.tiangolo.com/tutorial/arguments/help). From 406e291ae84746e7a1c2529872464dfffe317c91 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Tue, 17 Feb 2026 12:19:58 +0100 Subject: [PATCH 44/44] format --- typer/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typer/main.py b/typer/main.py index 2f2c497289..3a0c7b5750 100644 --- a/typer/main.py +++ b/typer/main.py @@ -1425,7 +1425,7 @@ def get_command_from_info( def determine_type_convertor( - type_: Any, enum_by_name: bool + type_: Any, enum_by_name: bool ) -> Callable[[Any], Any] | None: convertor: Callable[[Any], Any] | None = None if lenient_issubclass(type_, Path):