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