From a6a797dc7958ac2cbfbae4c70bab2ee6d34fc353 Mon Sep 17 00:00:00 2001 From: Patrick Sean Klein Date: Wed, 17 Apr 2024 23:04:41 +0100 Subject: [PATCH 01/17] Implement list parsing from string with separators. --- .../multiple_options/tutorial003.py | 11 +++++ .../multiple_options/tutorial003_an.py | 12 ++++++ tests/test_others.py | 27 ++++++++++++ .../test_multiple_options/test_tutorial003.py | 42 ++++++++++++++++++ .../test_tutorial003_an.py | 43 +++++++++++++++++++ typer/__init__.py | 1 + typer/core.py | 26 +++++++++++ typer/main.py | 4 ++ typer/models.py | 4 ++ typer/params.py | 16 +++---- typer/utils.py | 26 +++++++++++ 11 files changed, 204 insertions(+), 8 deletions(-) create mode 100644 docs_src/multiple_values/multiple_options/tutorial003.py create mode 100644 docs_src/multiple_values/multiple_options/tutorial003_an.py create mode 100644 tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003.py create mode 100644 tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003_an.py diff --git a/docs_src/multiple_values/multiple_options/tutorial003.py b/docs_src/multiple_values/multiple_options/tutorial003.py new file mode 100644 index 0000000000..6c4f1f10da --- /dev/null +++ b/docs_src/multiple_values/multiple_options/tutorial003.py @@ -0,0 +1,11 @@ +from typing import List + +import typer + + +def main(number: List[float] = typer.Option([], multiple_separator=",")): + print(f"The sum is {sum(number)}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/docs_src/multiple_values/multiple_options/tutorial003_an.py b/docs_src/multiple_values/multiple_options/tutorial003_an.py new file mode 100644 index 0000000000..6d261f39a0 --- /dev/null +++ b/docs_src/multiple_values/multiple_options/tutorial003_an.py @@ -0,0 +1,12 @@ +from typing import List + +import typer +from typing_extensions import Annotated + + +def main(number: Annotated[List[float], typer.Option(multiple_separator=",")] = []): + print(f"The sum is {sum(number)}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/tests/test_others.py b/tests/test_others.py index 8c78520029..4afd858b1c 100644 --- a/tests/test_others.py +++ b/tests/test_others.py @@ -256,3 +256,30 @@ def test_split_opt(): prefix, opt = _split_opt("verbose") assert prefix == "" assert opt == "verbose" + +def test_multiple_options_separator_1_unsupported_separator(): + app = typer.Typer() + + @app.command() + def main(names: list[str] = typer.Option(..., multiple_separator="\t \n")): + print("Hello World") + + with pytest.raises(typer.UnsupportedMultipleSeparatorError) as exc_info: + runner.invoke(app, []) + assert ( + str(exc_info.value) == "Error in definition of Option 'names'. Separator \"\t \n\" is not supported for multiple value splitting." + ) + +def test_multiple_options_separator_2_non_list_type(): + app = typer.Typer() + + @app.command() + def main(names: str = typer.Option(..., multiple_separator=",")): + print("Hello World") + + with pytest.raises(typer.MultipleSeparatorForNonListTypeError) as exc_info: + runner.invoke(app, []) + assert ( + str(exc_info.value) == "Multiple values are supported for list[T] types only. Wrap type in list to" + " support multiple values for 'names'." + ) diff --git a/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003.py b/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003.py new file mode 100644 index 0000000000..dd4a1329d5 --- /dev/null +++ b/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003.py @@ -0,0 +1,42 @@ +import subprocess +import sys + +import typer +from typer.testing import CliRunner + +from docs_src.multiple_values.multiple_options 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 "The sum is 0" in result.output + + +def test_1_number(): + result = runner.invoke(app, ["--number", "2"]) + assert result.exit_code == 0 + assert "The sum is 2.0" in result.output + + +def test_2_number(): + result = runner.invoke(app, ["--number", "2, 3, 4.5"], catch_exceptions=False) + assert result.exit_code == 0 + assert "The sum is 9.5" in result.output + +def test_3_number(): + result = runner.invoke(app, ["--number", "2, 3, 4.5", "--number", "5"]) + assert result.exit_code == 0 + assert "The sum is 14.5" 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_multiple_options/test_tutorial003_an.py b/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003_an.py new file mode 100644 index 0000000000..07161ce1fd --- /dev/null +++ b/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003_an.py @@ -0,0 +1,43 @@ +import subprocess +import sys + +import typer +from typer.testing import CliRunner + +from docs_src.multiple_values.multiple_options 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 "The sum is 0" in result.output + + +def test_1_number(): + result = runner.invoke(app, ["--number", "2"]) + assert result.exit_code == 0 + assert "The sum is 2.0" in result.output + + +def test_2_number(): + result = runner.invoke(app, ["--number", "2, 3, 4.5"]) + assert result.exit_code == 0 + assert "The sum is 9.5" in result.output + +def test_3_number(): + result = runner.invoke(app, ["--number", "2, 3, 4.5", "--number", "5"]) + assert result.exit_code == 0 + assert "The sum is 14.5" 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/typer/__init__.py b/typer/__init__.py index d4ac56d0ba..2cdbe5b197 100644 --- a/typer/__init__.py +++ b/typer/__init__.py @@ -37,3 +37,4 @@ from .models import FileTextWrite as FileTextWrite from .params import Argument as Argument from .params import Option as Option +from .utils import UnsupportedMultipleSeparatorError, MultipleSeparatorForNonListTypeError diff --git a/typer/core.py b/typer/core.py index 31fece5a76..8539d4fe10 100644 --- a/typer/core.py +++ b/typer/core.py @@ -2,6 +2,7 @@ import inspect import os import sys +import typing as t from enum import Enum from gettext import gettext as _ from typing import ( @@ -25,6 +26,9 @@ import click.shell_completion import click.types import click.utils +from click import OptionParser, Context + +from .utils import UnsupportedMultipleSeparatorError, MultipleSeparatorForNonListTypeError if sys.version_info >= (3, 8): from typing import Literal @@ -419,6 +423,7 @@ def __init__( show_envvar: bool = False, # Rich settings rich_help_panel: Union[str, None] = None, + multiple_separator: Optional[str] = None, ): super().__init__( param_decls=param_decls, @@ -449,6 +454,27 @@ def __init__( ) _typer_param_setup_autocompletion_compat(self, autocompletion=autocompletion) self.rich_help_panel = rich_help_panel + self.original_type = type + self.multiple_separator = multiple_separator + + if self.multiple_separator is not None: + if self.multiple_separator.strip() == '': + raise UnsupportedMultipleSeparatorError(self.name, self.multiple_separator) + + if not isinstance(self.type, list): + raise MultipleSeparatorForNonListTypeError(self.name) + + def _parse_separated_parameter_list(self, parameter_values: list[str]) -> list[str]: + values = [] + for param_str_list in parameter_values: + values.extend(param_str_list.split(self.multiple_separator)) + return values + + def process_value(self, ctx: Context, value: t.Any) -> t.Any: + if self.multiple_separator is not None: + value = self._parse_separated_parameter_list(value) + return super().process_value(ctx, value) + def _get_default_string( self, diff --git a/typer/main.py b/typer/main.py index 9db26975ca..fe5532b6e8 100644 --- a/typer/main.py +++ b/typer/main.py @@ -884,6 +884,9 @@ def get_click_param( param_decls.extend(parameter_info.param_decls) else: param_decls.append(default_option_declaration) + + # Check the multiple separator option for validity + return ( TyperOption( # Option @@ -917,6 +920,7 @@ def get_click_param( autocompletion=get_param_completion(parameter_info.autocompletion), # Rich settings rich_help_panel=parameter_info.rich_help_panel, + multiple_separator=parameter_info.multiple_separator, ), convertor, ) diff --git a/typer/models.py b/typer/models.py index 9bbe2a36d2..fe5e6e6db9 100644 --- a/typer/models.py +++ b/typer/models.py @@ -331,6 +331,7 @@ def __init__( path_type: Union[None, Type[str], Type[bytes]] = None, # Rich settings rich_help_panel: Union[str, None] = None, + multiple_separator: Optional[str] = None, ): super().__init__( default=default, @@ -386,6 +387,9 @@ def __init__( self.flag_value = flag_value self.count = count self.allow_from_autoenv = allow_from_autoenv + self.multiple_separator = multiple_separator + + class ArgumentInfo(ParameterInfo): diff --git a/typer/params.py b/typer/params.py index 710a4cf136..1f3ac7b63b 100644 --- a/typer/params.py +++ b/typer/params.py @@ -68,8 +68,7 @@ def Option( path_type: Union[None, Type[str], Type[bytes]] = None, # Rich settings rich_help_panel: Union[str, None] = None, -) -> Any: - ... +) -> Any: ... # Overload for Option created with custom type 'click_type' @@ -132,8 +131,7 @@ def Option( path_type: Union[None, Type[str], Type[bytes]] = None, # Rich settings rich_help_panel: Union[str, None] = None, -) -> Any: - ... +) -> Any: ... def Option( @@ -195,7 +193,10 @@ def Option( path_type: Union[None, Type[str], Type[bytes]] = None, # Rich settings rich_help_panel: Union[str, None] = None, + # Multiple values + multiple_separator: Optional[str] = None, ) -> Any: + return OptionInfo( # Parameter default=default, @@ -250,6 +251,7 @@ def Option( path_type=path_type, # Rich settings rich_help_panel=rich_help_panel, + multiple_separator=multiple_separator, ) @@ -305,8 +307,7 @@ def Argument( path_type: Union[None, Type[str], Type[bytes]] = None, # Rich settings rich_help_panel: Union[str, None] = None, -) -> Any: - ... +) -> Any: ... # Overload for Argument created with custom type 'click_type' @@ -361,8 +362,7 @@ def Argument( path_type: Union[None, Type[str], Type[bytes]] = None, # Rich settings rich_help_panel: Union[str, None] = None, -) -> Any: - ... +) -> Any: ... def Argument( diff --git a/typer/utils.py b/typer/utils.py index 2ba7bace45..8d6ae81a70 100644 --- a/typer/utils.py +++ b/typer/utils.py @@ -190,3 +190,29 @@ def get_params_from_function(func: Callable[..., Any]) -> Dict[str, ParamMeta]: name=param.name, default=default, annotation=annotation ) return params + +class MultipleSeparatorForNonListTypeError(Exception): + argument_name: str + + def __init__(self, argument_name: str): + self.argument_name = argument_name + + def __str__(self) -> str: + return ( + f"Multiple values are supported for list[T] types only. Wrap type in list to support multiple " + f"values for {self.argument_name!r}." + ) + +class UnsupportedMultipleSeparatorError(Exception): + argument_name: str + separator: str + + def __init__(self, argument_name: str, separator: str): + self.argument_name = argument_name + self.separator = separator + + def __str__(self) -> str: + return ( + f"Error in definition of Option {self.argument_name!r}. " + f"Separator \"{self.separator}\" is not supported for multiple value splitting." + ) From 7eb3fd2c04bcd5ff34aecb1ce83a6f786d1a0f95 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 17 Apr 2024 22:22:09 +0000 Subject: [PATCH 02/17] =?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 --- tests/test_others.py | 10 +++++++--- .../test_multiple_options/test_tutorial003.py | 2 ++ .../test_multiple_options/test_tutorial003_an.py | 1 + typer/__init__.py | 1 - typer/core.py | 14 +++++++++----- typer/models.py | 2 -- typer/params.py | 13 ++++++++----- typer/utils.py | 4 +++- 8 files changed, 30 insertions(+), 17 deletions(-) diff --git a/tests/test_others.py b/tests/test_others.py index 4afd858b1c..3335a5af17 100644 --- a/tests/test_others.py +++ b/tests/test_others.py @@ -257,6 +257,7 @@ def test_split_opt(): assert prefix == "" assert opt == "verbose" + def test_multiple_options_separator_1_unsupported_separator(): app = typer.Typer() @@ -267,9 +268,11 @@ def main(names: list[str] = typer.Option(..., multiple_separator="\t \n")): with pytest.raises(typer.UnsupportedMultipleSeparatorError) as exc_info: runner.invoke(app, []) assert ( - str(exc_info.value) == "Error in definition of Option 'names'. Separator \"\t \n\" is not supported for multiple value splitting." + str(exc_info.value) + == "Error in definition of Option 'names'. Separator \"\t \n\" is not supported for multiple value splitting." ) + def test_multiple_options_separator_2_non_list_type(): app = typer.Typer() @@ -280,6 +283,7 @@ def main(names: str = typer.Option(..., multiple_separator=",")): with pytest.raises(typer.MultipleSeparatorForNonListTypeError) as exc_info: runner.invoke(app, []) assert ( - str(exc_info.value) == "Multiple values are supported for list[T] types only. Wrap type in list to" - " support multiple values for 'names'." + str(exc_info.value) + == "Multiple values are supported for list[T] types only. Wrap type in list to" + " support multiple values for 'names'." ) diff --git a/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003.py b/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003.py index dd4a1329d5..02258bb957 100644 --- a/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003.py +++ b/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003.py @@ -28,11 +28,13 @@ def test_2_number(): assert result.exit_code == 0 assert "The sum is 9.5" in result.output + def test_3_number(): result = runner.invoke(app, ["--number", "2, 3, 4.5", "--number", "5"]) assert result.exit_code == 0 assert "The sum is 14.5" in result.output + def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], diff --git a/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003_an.py b/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003_an.py index 07161ce1fd..6b398da2ee 100644 --- a/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003_an.py +++ b/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003_an.py @@ -28,6 +28,7 @@ def test_2_number(): assert result.exit_code == 0 assert "The sum is 9.5" in result.output + def test_3_number(): result = runner.invoke(app, ["--number", "2, 3, 4.5", "--number", "5"]) assert result.exit_code == 0 diff --git a/typer/__init__.py b/typer/__init__.py index 2cdbe5b197..d4ac56d0ba 100644 --- a/typer/__init__.py +++ b/typer/__init__.py @@ -37,4 +37,3 @@ from .models import FileTextWrite as FileTextWrite from .params import Argument as Argument from .params import Option as Option -from .utils import UnsupportedMultipleSeparatorError, MultipleSeparatorForNonListTypeError diff --git a/typer/core.py b/typer/core.py index 8539d4fe10..52ad048853 100644 --- a/typer/core.py +++ b/typer/core.py @@ -26,9 +26,12 @@ import click.shell_completion import click.types import click.utils -from click import OptionParser, Context +from click import Context -from .utils import UnsupportedMultipleSeparatorError, MultipleSeparatorForNonListTypeError +from .utils import ( + MultipleSeparatorForNonListTypeError, + UnsupportedMultipleSeparatorError, +) if sys.version_info >= (3, 8): from typing import Literal @@ -458,8 +461,10 @@ def __init__( self.multiple_separator = multiple_separator if self.multiple_separator is not None: - if self.multiple_separator.strip() == '': - raise UnsupportedMultipleSeparatorError(self.name, self.multiple_separator) + if self.multiple_separator.strip() == "": + raise UnsupportedMultipleSeparatorError( + self.name, self.multiple_separator + ) if not isinstance(self.type, list): raise MultipleSeparatorForNonListTypeError(self.name) @@ -475,7 +480,6 @@ def process_value(self, ctx: Context, value: t.Any) -> t.Any: value = self._parse_separated_parameter_list(value) return super().process_value(ctx, value) - def _get_default_string( self, *, diff --git a/typer/models.py b/typer/models.py index fe5e6e6db9..86376c9f27 100644 --- a/typer/models.py +++ b/typer/models.py @@ -390,8 +390,6 @@ def __init__( self.multiple_separator = multiple_separator - - class ArgumentInfo(ParameterInfo): def __init__( self, diff --git a/typer/params.py b/typer/params.py index 1f3ac7b63b..29e84e0804 100644 --- a/typer/params.py +++ b/typer/params.py @@ -68,7 +68,8 @@ def Option( path_type: Union[None, Type[str], Type[bytes]] = None, # Rich settings rich_help_panel: Union[str, None] = None, -) -> Any: ... +) -> Any: + ... # Overload for Option created with custom type 'click_type' @@ -131,7 +132,8 @@ def Option( path_type: Union[None, Type[str], Type[bytes]] = None, # Rich settings rich_help_panel: Union[str, None] = None, -) -> Any: ... +) -> Any: + ... def Option( @@ -196,7 +198,6 @@ def Option( # Multiple values multiple_separator: Optional[str] = None, ) -> Any: - return OptionInfo( # Parameter default=default, @@ -307,7 +308,8 @@ def Argument( path_type: Union[None, Type[str], Type[bytes]] = None, # Rich settings rich_help_panel: Union[str, None] = None, -) -> Any: ... +) -> Any: + ... # Overload for Argument created with custom type 'click_type' @@ -362,7 +364,8 @@ def Argument( path_type: Union[None, Type[str], Type[bytes]] = None, # Rich settings rich_help_panel: Union[str, None] = None, -) -> Any: ... +) -> Any: + ... def Argument( diff --git a/typer/utils.py b/typer/utils.py index 8d6ae81a70..166b0b75d9 100644 --- a/typer/utils.py +++ b/typer/utils.py @@ -191,6 +191,7 @@ def get_params_from_function(func: Callable[..., Any]) -> Dict[str, ParamMeta]: ) return params + class MultipleSeparatorForNonListTypeError(Exception): argument_name: str @@ -203,6 +204,7 @@ def __str__(self) -> str: f"values for {self.argument_name!r}." ) + class UnsupportedMultipleSeparatorError(Exception): argument_name: str separator: str @@ -214,5 +216,5 @@ def __init__(self, argument_name: str, separator: str): def __str__(self) -> str: return ( f"Error in definition of Option {self.argument_name!r}. " - f"Separator \"{self.separator}\" is not supported for multiple value splitting." + f'Separator "{self.separator}" is not supported for multiple value splitting.' ) From cec52c953e60c29e8fac466b9a604295a71fcb31 Mon Sep 17 00:00:00 2001 From: Patrick Sean Klein Date: Wed, 17 Apr 2024 23:25:34 +0100 Subject: [PATCH 03/17] Do not allow multi-character separators for List[T] parsing. --- typer/core.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/typer/core.py b/typer/core.py index 52ad048853..209c6cbdf1 100644 --- a/typer/core.py +++ b/typer/core.py @@ -461,10 +461,9 @@ def __init__( self.multiple_separator = multiple_separator if self.multiple_separator is not None: - if self.multiple_separator.strip() == "": - raise UnsupportedMultipleSeparatorError( - self.name, self.multiple_separator - ) + self.multiple_separator = self.multiple_separator.strip() + if len(self.multiple_separator) != 1: + raise UnsupportedMultipleSeparatorError(self.name, multiple_separator) if not isinstance(self.type, list): raise MultipleSeparatorForNonListTypeError(self.name) From f1ca3bd760981b76863912ccb862bf00481c064d Mon Sep 17 00:00:00 2001 From: Patrick Sean Klein Date: Thu, 18 Apr 2024 20:08:48 +0100 Subject: [PATCH 04/17] Move multiple separator error checking to option construction. --- tests/test_others.py | 3 +-- typer/__init__.py | 3 +++ typer/core.py | 15 +-------------- typer/main.py | 19 +++++++++++++++++-- typer/utils.py | 9 ++++----- 5 files changed, 26 insertions(+), 23 deletions(-) diff --git a/tests/test_others.py b/tests/test_others.py index 3335a5af17..b4ea739b66 100644 --- a/tests/test_others.py +++ b/tests/test_others.py @@ -284,6 +284,5 @@ def main(names: str = typer.Option(..., multiple_separator=",")): runner.invoke(app, []) assert ( str(exc_info.value) - == "Multiple values are supported for list[T] types only. Wrap type in list to" - " support multiple values for 'names'." + == "Multiple values are supported for List[T] types only. Annotate 'names' as List[str] to support multiple values." ) diff --git a/typer/__init__.py b/typer/__init__.py index d4ac56d0ba..be9e1d73d9 100644 --- a/typer/__init__.py +++ b/typer/__init__.py @@ -37,3 +37,6 @@ from .models import FileTextWrite as FileTextWrite from .params import Argument as Argument from .params import Option as Option + +from .utils import MultipleSeparatorForNonListTypeError +from .utils import UnsupportedMultipleSeparatorError diff --git a/typer/core.py b/typer/core.py index 209c6cbdf1..f0990d64d9 100644 --- a/typer/core.py +++ b/typer/core.py @@ -28,11 +28,6 @@ import click.utils from click import Context -from .utils import ( - MultipleSeparatorForNonListTypeError, - UnsupportedMultipleSeparatorError, -) - if sys.version_info >= (3, 8): from typing import Literal else: @@ -460,15 +455,7 @@ def __init__( self.original_type = type self.multiple_separator = multiple_separator - if self.multiple_separator is not None: - self.multiple_separator = self.multiple_separator.strip() - if len(self.multiple_separator) != 1: - raise UnsupportedMultipleSeparatorError(self.name, multiple_separator) - - if not isinstance(self.type, list): - raise MultipleSeparatorForNonListTypeError(self.name) - - def _parse_separated_parameter_list(self, parameter_values: list[str]) -> list[str]: + def _parse_separated_parameter_list(self, parameter_values: List[str]) -> List[str]: values = [] for param_str_list in parameter_values: values.extend(param_str_list.split(self.multiple_separator)) diff --git a/typer/main.py b/typer/main.py index fe5532b6e8..46bd81d8c3 100644 --- a/typer/main.py +++ b/typer/main.py @@ -34,7 +34,11 @@ Required, TyperInfo, ) -from .utils import get_params_from_function +from .utils import ( + get_params_from_function, + UnsupportedMultipleSeparatorError, + MultipleSeparatorForNonListTypeError, +) try: import rich @@ -886,6 +890,17 @@ def get_click_param( param_decls.append(default_option_declaration) # Check the multiple separator option for validity + multiple_separator = None + if parameter_info.multiple_separator: + multiple_separator = parameter_info.multiple_separator.strip() + + if not is_list: + raise MultipleSeparatorForNonListTypeError(param.name, main_type) + + if len(multiple_separator) != 1: + raise UnsupportedMultipleSeparatorError( + param.name, parameter_info.multiple_separator + ) return ( TyperOption( @@ -920,7 +935,7 @@ def get_click_param( autocompletion=get_param_completion(parameter_info.autocompletion), # Rich settings rich_help_panel=parameter_info.rich_help_panel, - multiple_separator=parameter_info.multiple_separator, + multiple_separator=multiple_separator, ), convertor, ) diff --git a/typer/utils.py b/typer/utils.py index 166b0b75d9..e2f4bb8826 100644 --- a/typer/utils.py +++ b/typer/utils.py @@ -194,15 +194,14 @@ def get_params_from_function(func: Callable[..., Any]) -> Dict[str, ParamMeta]: class MultipleSeparatorForNonListTypeError(Exception): argument_name: str + argument_type: Type - def __init__(self, argument_name: str): + def __init__(self, argument_name: str, argument_type: Type): self.argument_name = argument_name + self.argument_type = argument_type def __str__(self) -> str: - return ( - f"Multiple values are supported for list[T] types only. Wrap type in list to support multiple " - f"values for {self.argument_name!r}." - ) + return f"Multiple values are supported for List[T] types only. Annotate {self.argument_name!r} as List[{self.argument_type.__name__}] to support multiple values." class UnsupportedMultipleSeparatorError(Exception): From aca33a5cb1cd8491cba785fd8acaf98855869057 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 18 Apr 2024 19:08:58 +0000 Subject: [PATCH 05/17] =?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/__init__.py | 3 --- typer/main.py | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/typer/__init__.py b/typer/__init__.py index be9e1d73d9..d4ac56d0ba 100644 --- a/typer/__init__.py +++ b/typer/__init__.py @@ -37,6 +37,3 @@ from .models import FileTextWrite as FileTextWrite from .params import Argument as Argument from .params import Option as Option - -from .utils import MultipleSeparatorForNonListTypeError -from .utils import UnsupportedMultipleSeparatorError diff --git a/typer/main.py b/typer/main.py index 46bd81d8c3..a11832a968 100644 --- a/typer/main.py +++ b/typer/main.py @@ -35,9 +35,9 @@ TyperInfo, ) from .utils import ( - get_params_from_function, - UnsupportedMultipleSeparatorError, MultipleSeparatorForNonListTypeError, + UnsupportedMultipleSeparatorError, + get_params_from_function, ) try: From 46b4690419052cdc4853407e2e08f861e3e0a8f5 Mon Sep 17 00:00:00 2001 From: Patrick Sean Klein Date: Thu, 18 Apr 2024 20:25:23 +0100 Subject: [PATCH 06/17] Fix linter errors. --- typer/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/typer/utils.py b/typer/utils.py index e2f4bb8826..3553cb0ec7 100644 --- a/typer/utils.py +++ b/typer/utils.py @@ -194,9 +194,9 @@ def get_params_from_function(func: Callable[..., Any]) -> Dict[str, ParamMeta]: class MultipleSeparatorForNonListTypeError(Exception): argument_name: str - argument_type: Type + argument_type: Type[Any] - def __init__(self, argument_name: str, argument_type: Type): + def __init__(self, argument_name: str, argument_type: Type[Any]): self.argument_name = argument_name self.argument_type = argument_type From 7c78c51cb4d64530d839a590bbe85103dcfcc6b9 Mon Sep 17 00:00:00 2001 From: Patrick Sean Klein Date: Thu, 18 Apr 2024 20:31:36 +0100 Subject: [PATCH 07/17] Ignore mutable data structures error for added tutorial file. --- pyproject.toml | 1 + typer/__init__.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index c9e793e1ed..8af0c70fdc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -175,6 +175,7 @@ ignore = [ # Default mutable data structure "docs_src/options_autocompletion/tutorial006_an.py" = ["B006"] "docs_src/multiple_values/multiple_options/tutorial002_an.py" = ["B006"] +"docs_src/multiple_values/multiple_options/tutorial003_an.py" = ["B006"] "docs_src/options_autocompletion/tutorial007_an.py" = ["B006"] "docs_src/options_autocompletion/tutorial008_an.py" = ["B006"] "docs_src/options_autocompletion/tutorial009_an.py" = ["B006"] diff --git a/typer/__init__.py b/typer/__init__.py index d4ac56d0ba..1fb0452210 100644 --- a/typer/__init__.py +++ b/typer/__init__.py @@ -37,3 +37,6 @@ from .models import FileTextWrite as FileTextWrite from .params import Argument as Argument from .params import Option as Option + +from .utils import MultipleSeparatorForNonListTypeError as MultipleSeparatorForNonListTypeError +from .utils import UnsupportedMultipleSeparatorError as UnsupportedMultipleSeparatorError From c0aba28c94cd2ef2fd57c3c5141131cb14af6f2d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 18 Apr 2024 19:32:55 +0000 Subject: [PATCH 08/17] =?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/__init__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/typer/__init__.py b/typer/__init__.py index 1fb0452210..2d04cef669 100644 --- a/typer/__init__.py +++ b/typer/__init__.py @@ -37,6 +37,9 @@ from .models import FileTextWrite as FileTextWrite from .params import Argument as Argument from .params import Option as Option - -from .utils import MultipleSeparatorForNonListTypeError as MultipleSeparatorForNonListTypeError -from .utils import UnsupportedMultipleSeparatorError as UnsupportedMultipleSeparatorError +from .utils import ( + MultipleSeparatorForNonListTypeError as MultipleSeparatorForNonListTypeError, +) +from .utils import ( + UnsupportedMultipleSeparatorError as UnsupportedMultipleSeparatorError, +) From 6233289c96b18b7903bbe2625b502c28866f77c9 Mon Sep 17 00:00:00 2001 From: Patrick Sean Klein Date: Thu, 18 Apr 2024 20:37:10 +0100 Subject: [PATCH 09/17] Use typing.List instead of list in test_others. --- tests/test_others.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_others.py b/tests/test_others.py index b4ea739b66..d894f81b18 100644 --- a/tests/test_others.py +++ b/tests/test_others.py @@ -262,7 +262,7 @@ def test_multiple_options_separator_1_unsupported_separator(): app = typer.Typer() @app.command() - def main(names: list[str] = typer.Option(..., multiple_separator="\t \n")): + def main(names: typing.List[str] = typer.Option(..., multiple_separator="\t \n")): print("Hello World") with pytest.raises(typer.UnsupportedMultipleSeparatorError) as exc_info: From f09da736a4e4633b52cbb1205e52d8c866dbf850 Mon Sep 17 00:00:00 2001 From: Patrick Sean Klein Date: Thu, 18 Apr 2024 20:45:06 +0100 Subject: [PATCH 10/17] Do not collect coverage on unused command in test_others. --- tests/test_others.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_others.py b/tests/test_others.py index d894f81b18..b4e58f3460 100644 --- a/tests/test_others.py +++ b/tests/test_others.py @@ -263,7 +263,7 @@ def test_multiple_options_separator_1_unsupported_separator(): @app.command() def main(names: typing.List[str] = typer.Option(..., multiple_separator="\t \n")): - print("Hello World") + pass # pragma: no cover with pytest.raises(typer.UnsupportedMultipleSeparatorError) as exc_info: runner.invoke(app, []) @@ -278,7 +278,7 @@ def test_multiple_options_separator_2_non_list_type(): @app.command() def main(names: str = typer.Option(..., multiple_separator=",")): - print("Hello World") + pass # pragma: no cover with pytest.raises(typer.MultipleSeparatorForNonListTypeError) as exc_info: runner.invoke(app, []) From 3cdd682244358cf9108291ce8695a93878c46f92 Mon Sep 17 00:00:00 2001 From: Patrick Sean Klein Date: Thu, 18 Apr 2024 21:08:45 +0100 Subject: [PATCH 11/17] Add documentation. --- .../multiple-values/multiple-options.md | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/docs/tutorial/multiple-values/multiple-options.md b/docs/tutorial/multiple-values/multiple-options.md index a7c5c88fb6..fd0d791bdf 100644 --- a/docs/tutorial/multiple-values/multiple-options.md +++ b/docs/tutorial/multiple-values/multiple-options.md @@ -87,3 +87,51 @@ The sum is 9.5 ``` + +## Passing multiple values in a single argument + +Many users expect to be able to pass multiple arguments with a single . +**Typer** supports this with the `multiple_separator` option for `typing.List[T]` types: + +=== "Python 3.7+" + + ```Python hl_lines="7" + {!> ../docs_src/multiple_values/multiple_options/tutorial003_an.py!} + ``` + +=== "Python 3.7+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="6" + {!> ../docs_src/multiple_values/multiple_options/tutorial003.py!} + ``` + +Check it: + +
+ +```console +$ python main.py + +The sum is 0 + +// Behaves +$ python main.py --number 2 + +The sum is 2.0 + +// Values to the argument are split using the passed separator +$ python main.py --number "2, 3, 4.5" + +// Supports passing the option multiple times. This joins all values to a single list +$ python main.py --number "2, 3, 4.5" --number 5 + +The sum is 14.5 +``` + +
+ +!!! warning + Only single-character non-whitespace separators are supported. Note that passing `--number 2, 3, 4.5` (without wrapping the value in "") does not work. From 03558cb210b2c22837ef644a8f671cf575876860 Mon Sep 17 00:00:00 2001 From: Patrick Sean Klein Date: Thu, 25 Apr 2024 22:53:22 +0100 Subject: [PATCH 12/17] Rename `multiple_separator` to `separator`. --- .../multiple-values/multiple-options.md | 2 +- .../multiple_options/tutorial003.py | 2 +- .../multiple_options/tutorial003_an.py | 2 +- tests/test_others.py | 8 ++++---- typer/__init__.py | 4 ++-- typer/core.py | 8 ++++---- typer/main.py | 20 +++++++++---------- typer/models.py | 4 ++-- typer/params.py | 4 ++-- typer/utils.py | 4 ++-- 10 files changed, 28 insertions(+), 30 deletions(-) diff --git a/docs/tutorial/multiple-values/multiple-options.md b/docs/tutorial/multiple-values/multiple-options.md index fd0d791bdf..d3da73ffba 100644 --- a/docs/tutorial/multiple-values/multiple-options.md +++ b/docs/tutorial/multiple-values/multiple-options.md @@ -91,7 +91,7 @@ The sum is 9.5 ## Passing multiple values in a single argument Many users expect to be able to pass multiple arguments with a single . -**Typer** supports this with the `multiple_separator` option for `typing.List[T]` types: +**Typer** supports this with the `separator` option for `typing.List[T]` types: === "Python 3.7+" diff --git a/docs_src/multiple_values/multiple_options/tutorial003.py b/docs_src/multiple_values/multiple_options/tutorial003.py index 6c4f1f10da..a8dcfc6927 100644 --- a/docs_src/multiple_values/multiple_options/tutorial003.py +++ b/docs_src/multiple_values/multiple_options/tutorial003.py @@ -3,7 +3,7 @@ import typer -def main(number: List[float] = typer.Option([], multiple_separator=",")): +def main(number: List[float] = typer.Option([], separator=",")): print(f"The sum is {sum(number)}") diff --git a/docs_src/multiple_values/multiple_options/tutorial003_an.py b/docs_src/multiple_values/multiple_options/tutorial003_an.py index 6d261f39a0..d29e01c514 100644 --- a/docs_src/multiple_values/multiple_options/tutorial003_an.py +++ b/docs_src/multiple_values/multiple_options/tutorial003_an.py @@ -4,7 +4,7 @@ from typing_extensions import Annotated -def main(number: Annotated[List[float], typer.Option(multiple_separator=",")] = []): +def main(number: Annotated[List[float], typer.Option(separator=",")] = []): print(f"The sum is {sum(number)}") diff --git a/tests/test_others.py b/tests/test_others.py index b4e58f3460..cf25670a5c 100644 --- a/tests/test_others.py +++ b/tests/test_others.py @@ -262,10 +262,10 @@ def test_multiple_options_separator_1_unsupported_separator(): app = typer.Typer() @app.command() - def main(names: typing.List[str] = typer.Option(..., multiple_separator="\t \n")): + def main(names: typing.List[str] = typer.Option(..., separator="\t \n")): pass # pragma: no cover - with pytest.raises(typer.UnsupportedMultipleSeparatorError) as exc_info: + with pytest.raises(typer.UnsupportedSeparatorError) as exc_info: runner.invoke(app, []) assert ( str(exc_info.value) @@ -277,10 +277,10 @@ def test_multiple_options_separator_2_non_list_type(): app = typer.Typer() @app.command() - def main(names: str = typer.Option(..., multiple_separator=",")): + def main(names: str = typer.Option(..., separator=",")): pass # pragma: no cover - with pytest.raises(typer.MultipleSeparatorForNonListTypeError) as exc_info: + with pytest.raises(typer.SeparatorForNonListTypeError) as exc_info: runner.invoke(app, []) assert ( str(exc_info.value) diff --git a/typer/__init__.py b/typer/__init__.py index 2d04cef669..e11ce98efc 100644 --- a/typer/__init__.py +++ b/typer/__init__.py @@ -38,8 +38,8 @@ from .params import Argument as Argument from .params import Option as Option from .utils import ( - MultipleSeparatorForNonListTypeError as MultipleSeparatorForNonListTypeError, + SeparatorForNonListTypeError as SeparatorForNonListTypeError, ) from .utils import ( - UnsupportedMultipleSeparatorError as UnsupportedMultipleSeparatorError, + UnsupportedSeparatorError as UnsupportedSeparatorError, ) diff --git a/typer/core.py b/typer/core.py index f0990d64d9..a6e8dadd0c 100644 --- a/typer/core.py +++ b/typer/core.py @@ -421,7 +421,7 @@ def __init__( show_envvar: bool = False, # Rich settings rich_help_panel: Union[str, None] = None, - multiple_separator: Optional[str] = None, + separator: Optional[str] = None, ): super().__init__( param_decls=param_decls, @@ -453,16 +453,16 @@ def __init__( _typer_param_setup_autocompletion_compat(self, autocompletion=autocompletion) self.rich_help_panel = rich_help_panel self.original_type = type - self.multiple_separator = multiple_separator + self.separator = separator def _parse_separated_parameter_list(self, parameter_values: List[str]) -> List[str]: values = [] for param_str_list in parameter_values: - values.extend(param_str_list.split(self.multiple_separator)) + values.extend(param_str_list.split(self.separator)) return values def process_value(self, ctx: Context, value: t.Any) -> t.Any: - if self.multiple_separator is not None: + if self.separator is not None: value = self._parse_separated_parameter_list(value) return super().process_value(ctx, value) diff --git a/typer/main.py b/typer/main.py index a11832a968..cb5dda7738 100644 --- a/typer/main.py +++ b/typer/main.py @@ -35,8 +35,8 @@ TyperInfo, ) from .utils import ( - MultipleSeparatorForNonListTypeError, - UnsupportedMultipleSeparatorError, + SeparatorForNonListTypeError, + UnsupportedSeparatorError, get_params_from_function, ) @@ -890,17 +890,15 @@ def get_click_param( param_decls.append(default_option_declaration) # Check the multiple separator option for validity - multiple_separator = None - if parameter_info.multiple_separator: - multiple_separator = parameter_info.multiple_separator.strip() + separator = None + if parameter_info.separator: + separator = parameter_info.separator.strip() if not is_list: - raise MultipleSeparatorForNonListTypeError(param.name, main_type) + raise SeparatorForNonListTypeError(param.name, main_type) - if len(multiple_separator) != 1: - raise UnsupportedMultipleSeparatorError( - param.name, parameter_info.multiple_separator - ) + if len(separator) != 1: + raise UnsupportedSeparatorError(param.name, parameter_info.separator) return ( TyperOption( @@ -935,7 +933,7 @@ def get_click_param( autocompletion=get_param_completion(parameter_info.autocompletion), # Rich settings rich_help_panel=parameter_info.rich_help_panel, - multiple_separator=multiple_separator, + separator=separator, ), convertor, ) diff --git a/typer/models.py b/typer/models.py index 86376c9f27..271f8a38ad 100644 --- a/typer/models.py +++ b/typer/models.py @@ -331,7 +331,7 @@ def __init__( path_type: Union[None, Type[str], Type[bytes]] = None, # Rich settings rich_help_panel: Union[str, None] = None, - multiple_separator: Optional[str] = None, + separator: Optional[str] = None, ): super().__init__( default=default, @@ -387,7 +387,7 @@ def __init__( self.flag_value = flag_value self.count = count self.allow_from_autoenv = allow_from_autoenv - self.multiple_separator = multiple_separator + self.separator = separator class ArgumentInfo(ParameterInfo): diff --git a/typer/params.py b/typer/params.py index 29e84e0804..d44a6158ff 100644 --- a/typer/params.py +++ b/typer/params.py @@ -196,7 +196,7 @@ def Option( # Rich settings rich_help_panel: Union[str, None] = None, # Multiple values - multiple_separator: Optional[str] = None, + separator: Optional[str] = None, ) -> Any: return OptionInfo( # Parameter @@ -252,7 +252,7 @@ def Option( path_type=path_type, # Rich settings rich_help_panel=rich_help_panel, - multiple_separator=multiple_separator, + separator=separator, ) diff --git a/typer/utils.py b/typer/utils.py index 3553cb0ec7..9843f77784 100644 --- a/typer/utils.py +++ b/typer/utils.py @@ -192,7 +192,7 @@ def get_params_from_function(func: Callable[..., Any]) -> Dict[str, ParamMeta]: return params -class MultipleSeparatorForNonListTypeError(Exception): +class SeparatorForNonListTypeError(Exception): argument_name: str argument_type: Type[Any] @@ -204,7 +204,7 @@ def __str__(self) -> str: return f"Multiple values are supported for List[T] types only. Annotate {self.argument_name!r} as List[{self.argument_type.__name__}] to support multiple values." -class UnsupportedMultipleSeparatorError(Exception): +class UnsupportedSeparatorError(Exception): argument_name: str separator: str From e54193b385614cbd99b9ac4da710374fb8687704 Mon Sep 17 00:00:00 2001 From: Patrick Sean Klein Date: Fri, 26 Apr 2024 20:22:48 +0100 Subject: [PATCH 13/17] Fix missing word in documentation --- docs/tutorial/multiple-values/multiple-options.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/multiple-values/multiple-options.md b/docs/tutorial/multiple-values/multiple-options.md index d3da73ffba..75dec9b79c 100644 --- a/docs/tutorial/multiple-values/multiple-options.md +++ b/docs/tutorial/multiple-values/multiple-options.md @@ -90,8 +90,11 @@ The sum is 9.5 ## Passing multiple values in a single argument -Many users expect to be able to pass multiple arguments with a single . -**Typer** supports this with the `separator` option for `typing.List[T]` types: +Many users expect to be able to pass multiple arguments with a single option. +**Typer** supports this with the `separator` option for `typing.List[T]` types. +This feature makes it easy to parse multiple values from a single command-line argument into a list in your application. + +To use this feature, define a command-line option that accepts multiple values separated by a specific character (such as a comma). Here's an example of how to implement this: === "Python 3.7+" From e4781dd8d1958cc2a43431e0e6f409920cc0617c Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 24 Jul 2024 15:09:57 +0200 Subject: [PATCH 14/17] update documentation --- .../multiple-values/multiple-options.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/tutorial/multiple-values/multiple-options.md b/docs/tutorial/multiple-values/multiple-options.md index 75dec9b79c..386a098ccc 100644 --- a/docs/tutorial/multiple-values/multiple-options.md +++ b/docs/tutorial/multiple-values/multiple-options.md @@ -90,8 +90,7 @@ The sum is 9.5 ## Passing multiple values in a single argument -Many users expect to be able to pass multiple arguments with a single option. -**Typer** supports this with the `separator` option for `typing.List[T]` types. +**Typer** supports passing multiple arguments with a single option, by using the `separator` parameter in combination with `typing.List[T]` types. This feature makes it easy to parse multiple values from a single command-line argument into a list in your application. To use this feature, define a command-line option that accepts multiple values separated by a specific character (such as a comma). Here's an example of how to implement this: @@ -116,20 +115,28 @@ Check it:
```console +// With no optional CLI argument $ python main.py The sum is 0 -// Behaves +// With one number argument $ python main.py --number 2 The sum is 2.0 -// Values to the argument are split using the passed separator +// With several number arguments, split using the separator defined by the Option argument $ python main.py --number "2, 3, 4.5" +The sum is 9.5 + +// You can remove the quotes if no whitespace is added between the numbers +$ python main.py --number 2,3,4.5 + +The sum is 9.5 + // Supports passing the option multiple times. This joins all values to a single list -$ python main.py --number "2, 3, 4.5" --number 5 +$ python main.py --number 2,3,4.5 --number 5 The sum is 14.5 ``` @@ -137,4 +144,4 @@ The sum is 14.5
!!! warning - Only single-character non-whitespace separators are supported. Note that passing `--number 2, 3, 4.5` (without wrapping the value in "") does not work. + Only single-character non-whitespace separators are supported. From 1793f0fcfb3904a5e00b5f3c4ba408c2b9a16024 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 24 Jul 2024 15:10:53 +0200 Subject: [PATCH 15/17] Extend error message to point to valid separators --- typer/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typer/utils.py b/typer/utils.py index 9843f77784..e114b273cf 100644 --- a/typer/utils.py +++ b/typer/utils.py @@ -215,5 +215,5 @@ def __init__(self, argument_name: str, separator: str): def __str__(self) -> str: return ( f"Error in definition of Option {self.argument_name!r}. " - f'Separator "{self.separator}" is not supported for multiple value splitting.' + f'Only single-character non-whitespace separators are supported, but got "{self.separator}".' ) From ae11002c487bf202146e5a9f5a746fc26c0a3753 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 24 Jul 2024 15:15:57 +0200 Subject: [PATCH 16/17] Update error msg in test --- tests/test_others.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_others.py b/tests/test_others.py index cf25670a5c..6e5bee2386 100644 --- a/tests/test_others.py +++ b/tests/test_others.py @@ -269,7 +269,7 @@ def main(names: typing.List[str] = typer.Option(..., separator="\t \n")): runner.invoke(app, []) assert ( str(exc_info.value) - == "Error in definition of Option 'names'. Separator \"\t \n\" is not supported for multiple value splitting." + == "Error in definition of Option 'names'. Only single-character non-whitespace separators are supported, but got \"\t \n\"." ) From d5328fa7213e07227ffb0cc3c836f2a4679c1153 Mon Sep 17 00:00:00 2001 From: Patrick Sean Klein Date: Sat, 30 Nov 2024 18:14:26 +0000 Subject: [PATCH 17/17] Update tests to clarify that whitespace between options is unsupported. --- .../test_multiple_options/test_tutorial003.py | 4 ++-- .../test_multiple_options/test_tutorial003_an.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003.py b/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003.py index 02258bb957..638b80b4c5 100644 --- a/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003.py +++ b/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003.py @@ -24,13 +24,13 @@ def test_1_number(): def test_2_number(): - result = runner.invoke(app, ["--number", "2, 3, 4.5"], catch_exceptions=False) + result = runner.invoke(app, ["--number", "2,3,4.5"], catch_exceptions=False) assert result.exit_code == 0 assert "The sum is 9.5" in result.output def test_3_number(): - result = runner.invoke(app, ["--number", "2, 3, 4.5", "--number", "5"]) + result = runner.invoke(app, ["--number", "2,3,4.5", "--number", "5"]) assert result.exit_code == 0 assert "The sum is 14.5" in result.output diff --git a/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003_an.py b/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003_an.py index 6b398da2ee..45dd8c7242 100644 --- a/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003_an.py +++ b/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial003_an.py @@ -24,13 +24,13 @@ def test_1_number(): def test_2_number(): - result = runner.invoke(app, ["--number", "2, 3, 4.5"]) + result = runner.invoke(app, ["--number", "2,3,4.5"]) assert result.exit_code == 0 assert "The sum is 9.5" in result.output def test_3_number(): - result = runner.invoke(app, ["--number", "2, 3, 4.5", "--number", "5"]) + result = runner.invoke(app, ["--number", "2,3,4.5", "--number", "5"]) assert result.exit_code == 0 assert "The sum is 14.5" in result.output