Coverage for tests / test_type_conversion.py: 100%
99 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-26 21:46 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-26 21:46 +0000
1from enum import Enum 1abcdefg
2from pathlib import Path 1abcdefg
3from typing import Any 1abcdefg
5import click 1abcdefg
6import pytest 1abcdefg
7import typer 1abcdefg
8from typer.testing import CliRunner 1abcdefg
10runner = CliRunner() 1abcdefg
13def test_optional(): 1abcdefg
14 app = typer.Typer() 1abcdefg
16 @app.command() 1abcdefg
17 def opt(user: str | None = None): 1abcdefg
18 if user: 1abcdefg
19 print(f"User: {user}") 1abcdefg
20 else:
21 print("No user") 1abcdefg
23 result = runner.invoke(app) 1abcdefg
24 assert result.exit_code == 0 1abcdefg
25 assert "No user" in result.output 1abcdefg
27 result = runner.invoke(app, ["--user", "Camila"]) 1abcdefg
28 assert result.exit_code == 0 1abcdefg
29 assert "User: Camila" in result.output 1abcdefg
32def test_union_type_optional(): 1abcdefg
33 app = typer.Typer() 1abcdefg
35 @app.command() 1abcdefg
36 def opt(user: str | None = None): 1abcdefg
37 if user: 1abcdefg
38 print(f"User: {user}") 1abcdefg
39 else:
40 print("No user") 1abcdefg
42 result = runner.invoke(app) 1abcdefg
43 assert result.exit_code == 0 1abcdefg
44 assert "No user" in result.output 1abcdefg
46 result = runner.invoke(app, ["--user", "Camila"]) 1abcdefg
47 assert result.exit_code == 0 1abcdefg
48 assert "User: Camila" in result.output 1abcdefg
51def test_optional_tuple(): 1abcdefg
52 app = typer.Typer() 1abcdefg
54 @app.command() 1abcdefg
55 def opt(number: tuple[int, int] | None = None): 1abcdefg
56 if number: 1abcdefg
57 print(f"Number: {number}") 1abcdefg
58 else:
59 print("No number") 1abcdefg
61 result = runner.invoke(app) 1abcdefg
62 assert result.exit_code == 0 1abcdefg
63 assert "No number" in result.output 1abcdefg
65 result = runner.invoke(app, ["--number", "4", "2"]) 1abcdefg
66 assert result.exit_code == 0 1abcdefg
67 assert "Number: (4, 2)" in result.output 1abcdefg
70def test_no_type(): 1abcdefg
71 app = typer.Typer() 1abcdefg
73 @app.command() 1abcdefg
74 def no_type(user): 1abcdefg
75 print(f"User: {user}") 1abcdefg
77 result = runner.invoke(app, ["Camila"]) 1abcdefg
78 assert result.exit_code == 0 1abcdefg
79 assert "User: Camila" in result.output 1abcdefg
82class SomeEnum(Enum): 1abcdefg
83 ONE = "one" 1abcdefg
84 TWO = "two" 1abcdefg
85 THREE = "three" 1abcdefg
88@pytest.mark.parametrize( 1abcdefg
89 "type_annotation",
90 [list[Path], list[SomeEnum], list[str]],
91)
92def test_list_parameters_convert_to_lists(type_annotation): 1abcdefg
93 # Lists containing objects that are converted by Click (i.e. not Path or Enum)
94 # should not be inadvertently converted to tuples
95 expected_element_type = type_annotation.__args__[0] 1abcdefg
96 app = typer.Typer() 1abcdefg
98 @app.command() 1abcdefg
99 def list_conversion(container: type_annotation): 1abcdefg
100 assert isinstance(container, list) 1abcdefg
101 for element in container: 1abcdefg
102 assert isinstance(element, expected_element_type) 1abcdefg
104 result = runner.invoke(app, ["one", "two", "three"]) 1abcdefg
105 assert result.exit_code == 0 1abcdefg
108@pytest.mark.parametrize( 1abcdefg
109 "type_annotation",
110 [
111 tuple[str, str],
112 tuple[str, Path],
113 tuple[Path, Path],
114 tuple[str, SomeEnum],
115 tuple[SomeEnum, SomeEnum],
116 ],
117)
118def test_tuple_parameter_elements_are_converted_recursively(type_annotation): 1abcdefg
119 # Tuple elements that aren't converted by Click (i.e. Path or Enum)
120 # should be recursively converted by Typer
121 expected_element_types = type_annotation.__args__ 1abcdefg
122 app = typer.Typer() 1abcdefg
124 @app.command() 1abcdefg
125 def tuple_recursive_conversion(container: type_annotation): 1abcdefg
126 assert isinstance(container, tuple) 1abcdefg
127 for element, expected_type in zip( 1abcdefg
128 container, expected_element_types, strict=True
129 ):
130 assert isinstance(element, expected_type) 1abcdefg
132 result = runner.invoke(app, ["one", "two"]) 1abcdefg
133 assert result.exit_code == 0 1abcdefg
136def test_custom_parse(): 1abcdefg
137 app = typer.Typer() 1abcdefg
139 @app.command() 1abcdefg
140 def custom_parser( 1abcdefg
141 hex_value: int = typer.Argument(None, parser=lambda x: int(x, 0)),
142 ):
143 assert hex_value == 0x56 1abcdefg
145 result = runner.invoke(app, ["0x56"]) 1abcdefg
146 assert result.exit_code == 0 1abcdefg
149def test_custom_click_type(): 1abcdefg
150 class BaseNumberParamType(click.ParamType): 1abcdefg
151 name = "base_integer" 1abcdefg
153 def convert( 1abcdefg
154 self,
155 value: Any,
156 param: click.Parameter | None,
157 ctx: click.Context | None,
158 ) -> Any:
159 return int(value, 0) 1abcdefg
161 app = typer.Typer() 1abcdefg
163 @app.command() 1abcdefg
164 def custom_click_type( 1abcdefg
165 hex_value: int = typer.Argument(None, click_type=BaseNumberParamType()),
166 ):
167 assert hex_value == 0x56 1abcdefg
169 result = runner.invoke(app, ["0x56"]) 1abcdefg
170 assert result.exit_code == 0 1abcdefg