Coverage for tests / test_type_conversion.py: 100%
101 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-02-09 12:36 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-02-09 12:36 +0000
1from enum import Enum 1abchdefg
2from pathlib import Path 1abchdefg
3from typing import Any, Optional 1abchdefg
5import click 1abchdefg
6import pytest 1abchdefg
7import typer 1abchdefg
8from typer.testing import CliRunner 1abchdefg
10from .utils import needs_py310 1abchdefg
12runner = CliRunner() 1abchdefg
15def test_optional(): 1abchdefg
16 app = typer.Typer() 1abchdefg
18 @app.command() 1abchdefg
19 def opt(user: Optional[str] = None): 1abchdefg
20 if user: 1abchdefg
21 print(f"User: {user}") 1abchdefg
22 else:
23 print("No user") 1abchdefg
25 result = runner.invoke(app) 1abchdefg
26 assert result.exit_code == 0 1abchdefg
27 assert "No user" in result.output 1abchdefg
29 result = runner.invoke(app, ["--user", "Camila"]) 1abchdefg
30 assert result.exit_code == 0 1abchdefg
31 assert "User: Camila" in result.output 1abchdefg
34@needs_py310 1abchdefg
35def test_union_type_optional(): 1abchdefg
36 app = typer.Typer() 1abcdefg
38 @app.command() 1abcdefg
39 def opt(user: str | None = None): 1abcdefg
40 if user: 1abcdefg
41 print(f"User: {user}") 1abcdefg
42 else:
43 print("No user") 1abcdefg
45 result = runner.invoke(app) 1abcdefg
46 assert result.exit_code == 0 1abcdefg
47 assert "No user" in result.output 1abcdefg
49 result = runner.invoke(app, ["--user", "Camila"]) 1abcdefg
50 assert result.exit_code == 0 1abcdefg
51 assert "User: Camila" in result.output 1abcdefg
54def test_optional_tuple(): 1abchdefg
55 app = typer.Typer() 1abchdefg
57 @app.command() 1abchdefg
58 def opt(number: Optional[tuple[int, int]] = None): 1abchdefg
59 if number: 1abchdefg
60 print(f"Number: {number}") 1abchdefg
61 else:
62 print("No number") 1abchdefg
64 result = runner.invoke(app) 1abchdefg
65 assert result.exit_code == 0 1abchdefg
66 assert "No number" in result.output 1abchdefg
68 result = runner.invoke(app, ["--number", "4", "2"]) 1abchdefg
69 assert result.exit_code == 0 1abchdefg
70 assert "Number: (4, 2)" in result.output 1abchdefg
73def test_no_type(): 1abchdefg
74 app = typer.Typer() 1abchdefg
76 @app.command() 1abchdefg
77 def no_type(user): 1abchdefg
78 print(f"User: {user}") 1abchdefg
80 result = runner.invoke(app, ["Camila"]) 1abchdefg
81 assert result.exit_code == 0 1abchdefg
82 assert "User: Camila" in result.output 1abchdefg
85class SomeEnum(Enum): 1abchdefg
86 ONE = "one" 1abchdefg
87 TWO = "two" 1abchdefg
88 THREE = "three" 1abchdefg
91@pytest.mark.parametrize( 1abchdefg
92 "type_annotation",
93 [list[Path], list[SomeEnum], list[str]],
94)
95def test_list_parameters_convert_to_lists(type_annotation): 1abchdefg
96 # Lists containing objects that are converted by Click (i.e. not Path or Enum)
97 # should not be inadvertently converted to tuples
98 expected_element_type = type_annotation.__args__[0] 1abchdefg
99 app = typer.Typer() 1abchdefg
101 @app.command() 1abchdefg
102 def list_conversion(container: type_annotation): 1abchdefg
103 assert isinstance(container, list) 1abchdefg
104 for element in container: 1abchdefg
105 assert isinstance(element, expected_element_type) 1abchdefg
107 result = runner.invoke(app, ["one", "two", "three"]) 1abchdefg
108 assert result.exit_code == 0 1abchdefg
111@pytest.mark.parametrize( 1abchdefg
112 "type_annotation",
113 [
114 tuple[str, str],
115 tuple[str, Path],
116 tuple[Path, Path],
117 tuple[str, SomeEnum],
118 tuple[SomeEnum, SomeEnum],
119 ],
120)
121def test_tuple_parameter_elements_are_converted_recursively(type_annotation): 1abchdefg
122 # Tuple elements that aren't converted by Click (i.e. Path or Enum)
123 # should be recursively converted by Typer
124 expected_element_types = type_annotation.__args__ 1abchdefg
125 app = typer.Typer() 1abchdefg
127 @app.command() 1abchdefg
128 def tuple_recursive_conversion(container: type_annotation): 1abchdefg
129 assert isinstance(container, tuple) 1abchdefg
130 for element, expected_type in zip(container, expected_element_types): 1abchdefg
131 assert isinstance(element, expected_type) 1abchdefg
133 result = runner.invoke(app, ["one", "two"]) 1abchdefg
134 assert result.exit_code == 0 1abchdefg
137def test_custom_parse(): 1abchdefg
138 app = typer.Typer() 1abchdefg
140 @app.command() 1abchdefg
141 def custom_parser( 1abchdefg
142 hex_value: int = typer.Argument(None, parser=lambda x: int(x, 0)),
143 ):
144 assert hex_value == 0x56 1abchdefg
146 result = runner.invoke(app, ["0x56"]) 1abchdefg
147 assert result.exit_code == 0 1abchdefg
150def test_custom_click_type(): 1abchdefg
151 class BaseNumberParamType(click.ParamType): 1abchdefg
152 name = "base_integer" 1abchdefg
154 def convert( 1abchdefg
155 self,
156 value: Any,
157 param: Optional[click.Parameter],
158 ctx: Optional[click.Context],
159 ) -> Any:
160 return int(value, 0) 1abchdefg
162 app = typer.Typer() 1abchdefg
164 @app.command() 1abchdefg
165 def custom_click_type( 1abchdefg
166 hex_value: int = typer.Argument(None, click_type=BaseNumberParamType()),
167 ):
168 assert hex_value == 0x56 1abchdefg
170 result = runner.invoke(app, ["0x56"]) 1abchdefg
171 assert result.exit_code == 0 1abchdefg