Skip to content

Commit 37b74b4

Browse files
committed
🚨 Get full test coverage
1 parent 9e7afc3 commit 37b74b4

File tree

3 files changed

+107
-91
lines changed

3 files changed

+107
-91
lines changed

Diff for: tests/test_annotated.py

-66
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
from enum import Enum
2-
from typing import Union
3-
41
import typer
52
from typer.testing import CliRunner
63
from typing_extensions import Annotated
@@ -79,66 +76,3 @@ def cmd(force: Annotated[bool, typer.Option("--force")] = False):
7976
result = runner.invoke(app, ["--force"])
8077
assert result.exit_code == 0, result.output
8178
assert "Forcing operation" in result.output
82-
83-
84-
class TestAnnotatedOptionAcceptsOptionalValue:
85-
def test_enum(self):
86-
app = typer.Typer()
87-
88-
class OptEnum(str, Enum):
89-
val1 = "val1"
90-
val2 = "val2"
91-
92-
@app.command()
93-
def cmd(opt: Annotated[Union[bool, OptEnum], typer.Option()] = OptEnum.val1):
94-
if opt is False:
95-
print("False")
96-
elif opt is True:
97-
print("True")
98-
else:
99-
print(opt.value)
100-
101-
result = runner.invoke(app)
102-
assert result.exit_code == 0, result.output
103-
assert "False" in result.output
104-
105-
result = runner.invoke(app, ["--opt"])
106-
assert result.exit_code == 0, result.output
107-
assert "val1" in result.output
108-
109-
result = runner.invoke(app, ["--opt", "val1"])
110-
assert result.exit_code == 0, result.output
111-
assert "val1" in result.output
112-
113-
result = runner.invoke(app, ["--opt", "val2"])
114-
assert result.exit_code == 0, result.output
115-
assert "val2" in result.output
116-
117-
result = runner.invoke(app, ["--opt", "val3"])
118-
assert result.exit_code != 0
119-
assert "Invalid value for '--opt': 'val3' is not one of" in result.output
120-
121-
def test_int(self):
122-
app = typer.Typer()
123-
124-
@app.command()
125-
def cmd(opt: Annotated[Union[bool, int], typer.Option()] = 1):
126-
print(opt)
127-
128-
result = runner.invoke(app)
129-
assert result.exit_code == 0, result.output
130-
assert "False" in result.output
131-
132-
result = runner.invoke(app, ["--opt"])
133-
assert result.exit_code == 0, result.output
134-
assert "1" in result.output
135-
136-
result = runner.invoke(app, ["--opt", "2"])
137-
assert result.exit_code == 0, result.output
138-
assert "2" in result.output
139-
140-
result = runner.invoke(app, ["--opt", "test"])
141-
assert result.exit_code != 0
142-
assert (
143-
"Invalid value for '--opt': 'test' is not a valid integer" in result.output
144-
)

Diff for: tests/test_type_conversion.py

+100-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from enum import Enum
22
from pathlib import Path
3-
from typing import Any, List, Optional, Tuple
3+
from typing import Any, List, Optional, Tuple, Union
44

55
import click
66
import pytest
77
import typer
88
from typer.testing import CliRunner
9+
from typing_extensions import Annotated
910

1011
from .utils import needs_py310
1112

@@ -169,3 +170,101 @@ def custom_click_type(
169170

170171
result = runner.invoke(app, ["0x56"])
171172
assert result.exit_code == 0
173+
174+
175+
class TestOptionAcceptsOptionalValue:
176+
def test_enum(self):
177+
app = typer.Typer()
178+
179+
class OptEnum(str, Enum):
180+
val1 = "val1"
181+
val2 = "val2"
182+
183+
@app.command()
184+
def cmd(opt: Annotated[Union[bool, OptEnum], typer.Option()] = OptEnum.val1):
185+
if opt is False:
186+
print("False")
187+
188+
else:
189+
print(opt.value)
190+
191+
result = runner.invoke(app)
192+
assert result.exit_code == 0, result.output
193+
assert "False" in result.output
194+
195+
result = runner.invoke(app, ["--opt"])
196+
assert result.exit_code == 0, result.output
197+
assert "val1" in result.output
198+
199+
result = runner.invoke(app, ["--opt", "val1"])
200+
assert result.exit_code == 0, result.output
201+
assert "val1" in result.output
202+
203+
result = runner.invoke(app, ["--opt", "val2"])
204+
assert result.exit_code == 0, result.output
205+
assert "val2" in result.output
206+
207+
result = runner.invoke(app, ["--opt", "val3"])
208+
assert result.exit_code != 0
209+
assert "Invalid value for '--opt': 'val3' is not one of" in result.output
210+
211+
result = runner.invoke(app, ["--opt", "0"])
212+
assert result.exit_code == 0, result.output
213+
assert "False" in result.output
214+
215+
result = runner.invoke(app, ["--opt", "1"])
216+
assert result.exit_code == 0, result.output
217+
assert "val1" in result.output
218+
219+
def test_int(self):
220+
app = typer.Typer()
221+
222+
@app.command()
223+
def cmd(opt: Annotated[Union[bool, int], typer.Option()] = 1):
224+
print(opt)
225+
226+
result = runner.invoke(app)
227+
assert result.exit_code == 0, result.output
228+
assert "False" in result.output
229+
230+
result = runner.invoke(app, ["--opt"])
231+
assert result.exit_code == 0, result.output
232+
assert "1" in result.output
233+
234+
result = runner.invoke(app, ["--opt", "2"])
235+
assert result.exit_code == 0, result.output
236+
assert "2" in result.output
237+
238+
result = runner.invoke(app, ["--opt", "test"])
239+
assert result.exit_code != 0
240+
assert (
241+
"Invalid value for '--opt': 'test' is not a valid integer" in result.output
242+
)
243+
244+
result = runner.invoke(app, ["--opt", "true"])
245+
assert result.exit_code == 0, result.output
246+
assert "1" in result.output
247+
248+
result = runner.invoke(app, ["--opt", "off"])
249+
assert result.exit_code == 0, result.output
250+
assert "False" in result.output
251+
252+
def test_path(self):
253+
app = typer.Typer()
254+
255+
@app.command()
256+
def cmd(opt: Annotated[Union[bool, Path], typer.Option()] = Path(".")):
257+
if isinstance(opt, Path):
258+
print((opt / "file.py").as_posix())
259+
260+
result = runner.invoke(app, ["--opt"])
261+
assert result.exit_code == 0, result.output
262+
assert "file.py" in result.output
263+
264+
result = runner.invoke(app, ["--opt", "/test/path/file.py"])
265+
assert result.exit_code == 0, result.output
266+
assert "/test/path/file.py" in result.output
267+
268+
result = runner.invoke(app, ["--opt", "False"])
269+
assert result.exit_code == 0, result.output
270+
assert "file.py" not in result.output

Diff for: typer/main.py

+7-24
Original file line numberDiff line numberDiff line change
@@ -660,19 +660,21 @@ def generate_enum_convertor(
660660
) -> Callable[[Any], Union[None, bool, Enum]]:
661661
val_map = {str(val.value): val for val in enum}
662662

663-
def convertor(value: Any) -> Union[None, bool, Enum]:
664-
if value is None:
665-
return None
666-
663+
def convertor(value: Any) -> Union[bool, Enum]:
667664
if isinstance(value, bool) and skip_bool:
668665
return value
669666

667+
if isinstance(value, enum):
668+
return value
669+
670670
val = str(value)
671671
if val in val_map:
672672
key = val_map[val]
673673
return enum(key)
674674

675-
return None
675+
raise click.BadParameter(
676+
f"Invalid value '{value}' for enum '{enum.__name__}'"
677+
) # pragma: no cover
676678

677679
return convertor
678680

@@ -845,22 +847,6 @@ def __init__(self, type_: click.ParamType, default: Any) -> None:
845847
self._default: Any = default
846848
self.name: str = f"BOOLEAN|{type_.name}"
847849

848-
@override
849-
def __repr__(self) -> str:
850-
return f"DefaultOption({self._type})"
851-
852-
@override
853-
def to_info_dict(self) -> Dict[str, Any]:
854-
return self._type.to_info_dict()
855-
856-
@override
857-
def get_metavar(self, param: click.Parameter) -> Optional[str]:
858-
return self._type.get_metavar(param)
859-
860-
@override
861-
def get_missing_message(self, param: click.Parameter) -> Optional[str]:
862-
return self._type.get_missing_message(param)
863-
864850
@override
865851
def convert(
866852
self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
@@ -895,9 +881,6 @@ class DefaultFalse:
895881
def __init__(self, value: Any) -> None:
896882
self._value = value
897883

898-
def __repr__(self) -> str:
899-
return f"False ({repr(self._value)})"
900-
901884
def __str__(self) -> str:
902885
return f"False ({str(self._value)})"
903886

0 commit comments

Comments
 (0)