Coverage for tests/test_type_conversion.py: 100%
101 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-12-19 20:29 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2025-12-19 20:29 +0000
1from enum import Enum 1abchdeifg
2from pathlib import Path 1abchdeifg
3from typing import Any, List, Optional, Tuple 1abchdeifg
5import click 1abchdeifg
6import pytest 1abchdeifg
7import typer 1abchdeifg
8from typer.testing import CliRunner 1abchdeifg
10from .utils import needs_py310 1abchdeifg
12runner = CliRunner() 1abchdeifg
15def test_optional(): 1abchdeifg
16 app = typer.Typer() 1abchdeifg
18 @app.command() 1abchdeifg
19 def opt(user: Optional[str] = None): 1abchdeifg
20 if user: 1abchdeifg
21 print(f"User: {user}") 1abchdeifg
22 else:
23 print("No user") 1abchdeifg
25 result = runner.invoke(app) 1abchdeifg
26 assert result.exit_code == 0 1abchdeifg
27 assert "No user" in result.output 1abchdeifg
29 result = runner.invoke(app, ["--user", "Camila"]) 1abchdeifg
30 assert result.exit_code == 0 1abchdeifg
31 assert "User: Camila" in result.output 1abchdeifg
34@needs_py310 1abchdeifg
35def test_union_type_optional(): 1abchdeifg
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(): 1abchdeifg
55 app = typer.Typer() 1abchdeifg
57 @app.command() 1abchdeifg
58 def opt(number: Optional[Tuple[int, int]] = None): 1abchdeifg
59 if number: 1abchdeifg
60 print(f"Number: {number}") 1abchdeifg
61 else:
62 print("No number") 1abchdeifg
64 result = runner.invoke(app) 1abchdeifg
65 assert result.exit_code == 0 1abchdeifg
66 assert "No number" in result.output 1abchdeifg
68 result = runner.invoke(app, ["--number", "4", "2"]) 1abchdeifg
69 assert result.exit_code == 0 1abchdeifg
70 assert "Number: (4, 2)" in result.output 1abchdeifg
73def test_no_type(): 1abchdeifg
74 app = typer.Typer() 1abchdeifg
76 @app.command() 1abchdeifg
77 def no_type(user): 1abchdeifg
78 print(f"User: {user}") 1abchdeifg
80 result = runner.invoke(app, ["Camila"]) 1abchdeifg
81 assert result.exit_code == 0 1abchdeifg
82 assert "User: Camila" in result.output 1abchdeifg
85class SomeEnum(Enum): 1abchdeifg
86 ONE = "one" 1abchdeifg
87 TWO = "two" 1abchdeifg
88 THREE = "three" 1abchdeifg
91@pytest.mark.parametrize( 1abchdeifg
92 "type_annotation",
93 [List[Path], List[SomeEnum], List[str]],
94)
95def test_list_parameters_convert_to_lists(type_annotation): 1abchdeifg
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] 1abchdeifg
99 app = typer.Typer() 1abchdeifg
101 @app.command() 1abchdeifg
102 def list_conversion(container: type_annotation): 1abchdeifg
103 assert isinstance(container, list) 1abchdeifg
104 for element in container: 1abchdeifg
105 assert isinstance(element, expected_element_type) 1abchdeifg
107 result = runner.invoke(app, ["one", "two", "three"]) 1abchdeifg
108 assert result.exit_code == 0 1abchdeifg
111@pytest.mark.parametrize( 1abchdeifg
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): 1abchdeifg
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__ 1abchdeifg
125 app = typer.Typer() 1abchdeifg
127 @app.command() 1abchdeifg
128 def tuple_recursive_conversion(container: type_annotation): 1abchdeifg
129 assert isinstance(container, tuple) 1abchdeifg
130 for element, expected_type in zip(container, expected_element_types): 1abchdeifg
131 assert isinstance(element, expected_type) 1abchdeifg
133 result = runner.invoke(app, ["one", "two"]) 1abchdeifg
134 assert result.exit_code == 0 1abchdeifg
137def test_custom_parse(): 1abchdeifg
138 app = typer.Typer() 1abchdeifg
140 @app.command() 1abchdeifg
141 def custom_parser( 1abchdeifg
142 hex_value: int = typer.Argument(None, parser=lambda x: int(x, 0)),
143 ):
144 assert hex_value == 0x56 1abchdeifg
146 result = runner.invoke(app, ["0x56"]) 1abchdeifg
147 assert result.exit_code == 0 1abchdeifg
150def test_custom_click_type(): 1abchdeifg
151 class BaseNumberParamType(click.ParamType): 1abchdeifg
152 name = "base_integer" 1abchdeifg
154 def convert( 1abchdeifg
155 self,
156 value: Any,
157 param: Optional[click.Parameter],
158 ctx: Optional[click.Context],
159 ) -> Any:
160 return int(value, 0) 1abchdeifg
162 app = typer.Typer() 1abchdeifg
164 @app.command() 1abchdeifg
165 def custom_click_type( 1abchdeifg
166 hex_value: int = typer.Argument(None, click_type=BaseNumberParamType()),
167 ):
168 assert hex_value == 0x56 1abchdeifg
170 result = runner.invoke(app, ["0x56"]) 1abchdeifg
171 assert result.exit_code == 0 1abchdeifg