From afc9cf445498f7ba9acbbdac0ceeb4b56d986ad9 Mon Sep 17 00:00:00 2001 From: Mantas Date: Thu, 21 Jan 2021 18:57:51 +0200 Subject: [PATCH 01/33] 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/33] 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/33] 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/33] =?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/33] 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/33] 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/33] 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/33] 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/33] =?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/33] 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/33] 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/33] 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/33] =?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/33] 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/33] =?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/33] 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/33] =?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/33] 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/33] 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/33] =?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/33] 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/33] 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/33] 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/33] =?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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] =?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/33] 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: