diff --git a/tests/test_annotated.py b/tests/test_annotated.py index 25622d0ebf..09072b3ae1 100644 --- a/tests/test_annotated.py +++ b/tests/test_annotated.py @@ -1,6 +1,3 @@ -from enum import Enum -from typing import Union - import typer from typer.testing import CliRunner from typing_extensions import Annotated @@ -79,66 +76,3 @@ def cmd(force: Annotated[bool, typer.Option("--force")] = False): result = runner.invoke(app, ["--force"]) assert result.exit_code == 0, result.output assert "Forcing operation" in result.output - - -class TestAnnotatedOptionAcceptsOptionalValue: - def test_enum(self): - app = typer.Typer() - - class OptEnum(str, Enum): - val1 = "val1" - val2 = "val2" - - @app.command() - def cmd(opt: Annotated[Union[bool, OptEnum], typer.Option()] = OptEnum.val1): - if opt is False: - print("False") - elif opt is True: - print("True") - else: - print(opt.value) - - result = runner.invoke(app) - assert result.exit_code == 0, result.output - assert "False" in result.output - - result = runner.invoke(app, ["--opt"]) - assert result.exit_code == 0, result.output - assert "val1" in result.output - - result = runner.invoke(app, ["--opt", "val1"]) - assert result.exit_code == 0, result.output - assert "val1" in result.output - - result = runner.invoke(app, ["--opt", "val2"]) - assert result.exit_code == 0, result.output - assert "val2" in result.output - - result = runner.invoke(app, ["--opt", "val3"]) - assert result.exit_code != 0 - assert "Invalid value for '--opt': 'val3' is not one of" in result.output - - def test_int(self): - app = typer.Typer() - - @app.command() - def cmd(opt: Annotated[Union[bool, int], typer.Option()] = 1): - print(opt) - - result = runner.invoke(app) - assert result.exit_code == 0, result.output - assert "False" in result.output - - result = runner.invoke(app, ["--opt"]) - assert result.exit_code == 0, result.output - assert "1" in result.output - - result = runner.invoke(app, ["--opt", "2"]) - assert result.exit_code == 0, result.output - assert "2" in result.output - - result = runner.invoke(app, ["--opt", "test"]) - assert result.exit_code != 0 - assert ( - "Invalid value for '--opt': 'test' is not a valid integer" in result.output - ) diff --git a/tests/test_type_conversion.py b/tests/test_type_conversion.py index 904a686d2e..0102ac1d2d 100644 --- a/tests/test_type_conversion.py +++ b/tests/test_type_conversion.py @@ -1,11 +1,12 @@ from enum import Enum from pathlib import Path -from typing import Any, List, Optional, Tuple +from typing import Any, List, Optional, Tuple, Union import click import pytest import typer from typer.testing import CliRunner +from typing_extensions import Annotated from .utils import needs_py310 @@ -169,3 +170,101 @@ def custom_click_type( result = runner.invoke(app, ["0x56"]) assert result.exit_code == 0 + + +class TestOptionAcceptsOptionalValue: + def test_enum(self): + app = typer.Typer() + + class OptEnum(str, Enum): + val1 = "val1" + val2 = "val2" + + @app.command() + def cmd(opt: Annotated[Union[bool, OptEnum], typer.Option()] = OptEnum.val1): + if opt is False: + print("False") + + else: + print(opt.value) + + result = runner.invoke(app) + assert result.exit_code == 0, result.output + assert "False" in result.output + + result = runner.invoke(app, ["--opt"]) + assert result.exit_code == 0, result.output + assert "val1" in result.output + + result = runner.invoke(app, ["--opt", "val1"]) + assert result.exit_code == 0, result.output + assert "val1" in result.output + + result = runner.invoke(app, ["--opt", "val2"]) + assert result.exit_code == 0, result.output + assert "val2" in result.output + + result = runner.invoke(app, ["--opt", "val3"]) + assert result.exit_code != 0 + assert "Invalid value for '--opt': 'val3' is not one of" in result.output + + result = runner.invoke(app, ["--opt", "0"]) + assert result.exit_code == 0, result.output + assert "False" in result.output + + result = runner.invoke(app, ["--opt", "1"]) + assert result.exit_code == 0, result.output + assert "val1" in result.output + + def test_int(self): + app = typer.Typer() + + @app.command() + def cmd(opt: Annotated[Union[bool, int], typer.Option()] = 1): + print(opt) + + result = runner.invoke(app) + assert result.exit_code == 0, result.output + assert "False" in result.output + + result = runner.invoke(app, ["--opt"]) + assert result.exit_code == 0, result.output + assert "1" in result.output + + result = runner.invoke(app, ["--opt", "2"]) + assert result.exit_code == 0, result.output + assert "2" in result.output + + result = runner.invoke(app, ["--opt", "test"]) + assert result.exit_code != 0 + assert ( + "Invalid value for '--opt': 'test' is not a valid integer" in result.output + ) + + result = runner.invoke(app, ["--opt", "true"]) + assert result.exit_code == 0, result.output + assert "1" in result.output + + result = runner.invoke(app, ["--opt", "off"]) + assert result.exit_code == 0, result.output + assert "False" in result.output + + def test_path(self): + app = typer.Typer() + + @app.command() + def cmd(opt: Annotated[Union[bool, Path], typer.Option()] = Path(".")): + if isinstance(opt, Path): + print((opt / "file.py").as_posix()) + + result = runner.invoke(app, ["--opt"]) + assert result.exit_code == 0, result.output + assert "file.py" in result.output + + result = runner.invoke(app, ["--opt", "/test/path/file.py"]) + assert result.exit_code == 0, result.output + assert "/test/path/file.py" in result.output + + result = runner.invoke(app, ["--opt", "False"]) + assert result.exit_code == 0, result.output + assert "file.py" not in result.output diff --git a/typer/main.py b/typer/main.py index 53270b119e..93bb0b378f 100644 --- a/typer/main.py +++ b/typer/main.py @@ -660,19 +660,21 @@ def generate_enum_convertor( ) -> Callable[[Any], Union[None, bool, Enum]]: val_map = {str(val.value): val for val in enum} - def convertor(value: Any) -> Union[None, bool, Enum]: - if value is None: - return None - + def convertor(value: Any) -> Union[bool, Enum]: if isinstance(value, bool) and skip_bool: return value + if isinstance(value, enum): + return value + val = str(value) if val in val_map: key = val_map[val] return enum(key) - return None + raise click.BadParameter( + f"Invalid value '{value}' for enum '{enum.__name__}'" + ) # pragma: no cover return convertor @@ -845,22 +847,6 @@ def __init__(self, type_: click.ParamType, default: Any) -> None: self._default: Any = default self.name: str = f"BOOLEAN|{type_.name}" - @override - def __repr__(self) -> str: - return f"DefaultOption({self._type})" - - @override - def to_info_dict(self) -> Dict[str, Any]: - return self._type.to_info_dict() - - @override - def get_metavar(self, param: click.Parameter) -> Optional[str]: - return self._type.get_metavar(param) - - @override - def get_missing_message(self, param: click.Parameter) -> Optional[str]: - return self._type.get_missing_message(param) - @override def convert( self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context] @@ -895,9 +881,6 @@ class DefaultFalse: def __init__(self, value: Any) -> None: self._value = value - def __repr__(self) -> str: - return f"False ({repr(self._value)})" - def __str__(self) -> str: return f"False ({str(self._value)})"