Coverage for tests / test_others.py: 100%
184 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
1import os 1abcdefgh
2import subprocess 1abcdefgh
3import sys 1abcdefgh
4import typing 1abcdefgh
5from pathlib import Path 1abcdefgh
6from typing import Annotated 1abcdefgh
7from unittest import mock 1abcdefgh
9import click 1abcdefgh
10import pytest 1abcdefgh
11import typer 1abcdefgh
12import typer._completion_shared 1abcdefgh
13import typer.completion 1abcdefgh
14from typer.core import _split_opt 1abcdefgh
15from typer.main import solve_typer_info_defaults, solve_typer_info_help 1abcdefgh
16from typer.models import ParameterInfo, TyperInfo 1abcdefgh
17from typer.testing import CliRunner 1abcdefgh
19from .utils import requires_completion_permission 1abcdefgh
21runner = CliRunner() 1abcdefgh
24def test_help_from_info(): 1abcdefgh
25 # Mainly for coverage/completeness
26 value = solve_typer_info_help(TyperInfo()) 1abcdefgh
27 assert value is None 1abcdefgh
30def test_defaults_from_info(): 1abcdefgh
31 # Mainly for coverage/completeness
32 value = solve_typer_info_defaults(TyperInfo()) 1abcdefgh
33 assert value 1abcdefgh
36def test_too_many_parsers(): 1abcdefgh
37 def custom_parser(value: str) -> int: 1abcdefgh
38 return int(value) # pragma: no cover
40 class CustomClickParser(click.ParamType): 1abcdefgh
41 name = "custom_parser" 1abcdefgh
43 def convert( 1abcdefgh
44 self,
45 value: str,
46 param: typing.Optional[click.Parameter],
47 ctx: typing.Optional[click.Context],
48 ) -> typing.Any:
49 return int(value) # pragma: no cover
51 expected_error = ( 1abcdefgh
52 "Multiple custom type parsers provided. "
53 "`parser` and `click_type` may not both be provided."
54 )
56 with pytest.raises(ValueError, match=expected_error): 1abcdefgh
57 ParameterInfo(parser=custom_parser, click_type=CustomClickParser()) 1abcdefgh
60def test_valid_parser_permutations(): 1abcdefgh
61 def custom_parser(value: str) -> int: 1abcdefgh
62 return int(value) # pragma: no cover
64 class CustomClickParser(click.ParamType): 1abcdefgh
65 name = "custom_parser" 1abcdefgh
67 def convert( 1abcdefgh
68 self,
69 value: str,
70 param: typing.Optional[click.Parameter],
71 ctx: typing.Optional[click.Context],
72 ) -> typing.Any:
73 return int(value) # pragma: no cover
75 ParameterInfo() 1abcdefgh
76 ParameterInfo(parser=custom_parser) 1abcdefgh
77 ParameterInfo(click_type=CustomClickParser()) 1abcdefgh
80@requires_completion_permission 1abcdefgh
81def test_install_invalid_shell(): 1abcdefgh
82 app = typer.Typer() 1abcdefgh
84 @app.command() 1abcdefgh
85 def main(): 1abcdefgh
86 print("Hello World") 1abcdefgh
88 with mock.patch.object( 1abcdefgh
89 typer._completion_shared, "_get_shell_name", return_value="xshell"
90 ):
91 result = runner.invoke(app, ["--install-completion"]) 1abcdefgh
92 assert "Shell xshell is not supported." in result.stdout 1abcdefgh
93 result = runner.invoke(app) 1abcdefgh
94 assert "Hello World" in result.stdout 1abcdefgh
97def test_callback_too_many_parameters(): 1abcdefgh
98 app = typer.Typer() 1abcdefgh
100 def name_callback(ctx, param, val1, val2): 1abcdefgh
101 pass # pragma: no cover
103 @app.command() 1abcdefgh
104 def main(name: str = typer.Option(..., callback=name_callback)): 1abcdefgh
105 pass # pragma: no cover
107 with pytest.raises(click.ClickException) as exc_info: 1abcdefgh
108 runner.invoke(app, ["--name", "Camila"]) 1abcdefgh
109 assert ( 1abcdefgh
110 exc_info.value.message == "Too many CLI parameter callback function parameters"
111 )
114def test_callback_2_untyped_parameters(): 1abcdefgh
115 app = typer.Typer() 1abcdefgh
117 def name_callback(ctx, value): 1abcdefgh
118 print(f"info name is: {ctx.info_name}") 1abcdefgh
119 print(f"value is: {value}") 1abcdefgh
121 @app.command() 1abcdefgh
122 def main(name: str = typer.Option(..., callback=name_callback)): 1abcdefgh
123 print("Hello World") 1abcdefgh
125 result = runner.invoke(app, ["--name", "Camila"]) 1abcdefgh
126 assert "info name is: main" in result.stdout 1abcdefgh
127 assert "value is: Camila" in result.stdout 1abcdefgh
130def test_callback_3_untyped_parameters(): 1abcdefgh
131 app = typer.Typer() 1abcdefgh
133 def name_callback(ctx, param, value): 1abcdefgh
134 print(f"info name is: {ctx.info_name}") 1abcdefgh
135 print(f"param name is: {param.name}") 1abcdefgh
136 print(f"value is: {value}") 1abcdefgh
138 @app.command() 1abcdefgh
139 def main(name: str = typer.Option(..., callback=name_callback)): 1abcdefgh
140 print("Hello World") 1abcdefgh
142 result = runner.invoke(app, ["--name", "Camila"]) 1abcdefgh
143 assert "info name is: main" in result.stdout 1abcdefgh
144 assert "param name is: name" in result.stdout 1abcdefgh
145 assert "value is: Camila" in result.stdout 1abcdefgh
148def test_callback_4_list_none(): 1abcdefgh
149 app = typer.Typer() 1abcdefgh
151 def names_callback(ctx, param, values: typing.Optional[list[str]]): 1abcdefgh
152 if values is None: 1abcdefgh
153 return values 1abcdefgh
154 return [value.upper() for value in values] 1abcdefgh
156 @app.command() 1abcdefgh
157 def main( 1abcdefgh
158 names: typing.Optional[list[str]] = typer.Option(
159 None, "--name", callback=names_callback
160 ),
161 ):
162 if names is None: 1abcdefgh
163 print("Hello World") 1abcdefgh
164 else:
165 print(f"Hello {', '.join(names)}") 1abcdefgh
167 result = runner.invoke(app, ["--name", "Sideshow", "--name", "Bob"]) 1abcdefgh
168 assert "Hello SIDESHOW, BOB" in result.stdout 1abcdefgh
170 result = runner.invoke(app, []) 1abcdefgh
171 assert "Hello World" in result.stdout 1abcdefgh
174def test_empty_list_default_generator(): 1abcdefgh
175 def empty_list() -> list[str]: 1abcdefgh
176 return [] 1abcdefgh
178 app = typer.Typer() 1abcdefgh
180 @app.command() 1abcdefgh
181 def main( 1abcdefgh
182 names: Annotated[list[str], typer.Option(default_factory=empty_list)],
183 ):
184 print(names) 1abcdefgh
186 result = runner.invoke(app) 1abcdefgh
187 assert "[]" in result.output 1abcdefgh
190def test_completion_argument(): 1abcdefgh
191 file_path = Path(__file__).parent / "assets/completion_argument.py" 1abcdefgh
192 result = subprocess.run( 1abcdefgh
193 [sys.executable, "-m", "coverage", "run", str(file_path), "E"],
194 capture_output=True,
195 encoding="utf-8",
196 env={
197 **os.environ,
198 "_COMPLETION_ARGUMENT.PY_COMPLETE": "complete_zsh",
199 "_TYPER_COMPLETE_ARGS": "completion_argument.py E",
200 "_TYPER_COMPLETE_TESTING": "True",
201 },
202 )
203 assert "Emma" in result.stdout or "_files" in result.stdout 1abcdefgh
204 assert "ctx: completion_argument" in result.stderr 1abcdefgh
205 assert "arg is: name" in result.stderr 1abcdefgh
206 assert "incomplete is: E" in result.stderr 1abcdefgh
209def test_completion_untyped_parameters(): 1abcdefgh
210 file_path = Path(__file__).parent / "assets/completion_no_types.py" 1abcdefgh
211 result = subprocess.run( 1abcdefgh
212 [sys.executable, "-m", "coverage", "run", str(file_path)],
213 capture_output=True,
214 encoding="utf-8",
215 env={
216 **os.environ,
217 "_COMPLETION_NO_TYPES.PY_COMPLETE": "complete_zsh",
218 "_TYPER_COMPLETE_ARGS": "completion_no_types.py --name Sebastian --name Ca",
219 },
220 )
221 assert "info name is: completion_no_types.py" in result.stderr 1abcdefgh
222 assert "args is: []" in result.stderr 1abcdefgh
223 assert "incomplete is: Ca" in result.stderr 1abcdefgh
224 assert '"Camila":"The reader of books."' in result.stdout 1abcdefgh
225 assert '"Carlos":"The writer of scripts."' in result.stdout 1abcdefgh
227 result = subprocess.run( 1abcdefgh
228 [sys.executable, "-m", "coverage", "run", str(file_path)],
229 capture_output=True,
230 encoding="utf-8",
231 )
232 assert "Hello World" in result.stdout 1abcdefgh
235def test_completion_untyped_parameters_different_order_correct_names(): 1abcdefgh
236 file_path = Path(__file__).parent / "assets/completion_no_types_order.py" 1abcdefgh
237 result = subprocess.run( 1abcdefgh
238 [sys.executable, "-m", "coverage", "run", str(file_path)],
239 capture_output=True,
240 encoding="utf-8",
241 env={
242 **os.environ,
243 "_COMPLETION_NO_TYPES_ORDER.PY_COMPLETE": "complete_zsh",
244 "_TYPER_COMPLETE_ARGS": "completion_no_types_order.py --name Sebastian --name Ca",
245 },
246 )
247 assert "info name is: completion_no_types_order.py" in result.stderr 1abcdefgh
248 assert "args is: []" in result.stderr 1abcdefgh
249 assert "incomplete is: Ca" in result.stderr 1abcdefgh
250 assert '"Camila":"The reader of books."' in result.stdout 1abcdefgh
251 assert '"Carlos":"The writer of scripts."' in result.stdout 1abcdefgh
253 result = subprocess.run( 1abcdefgh
254 [sys.executable, "-m", "coverage", "run", str(file_path)],
255 capture_output=True,
256 encoding="utf-8",
257 )
258 assert "Hello World" in result.stdout 1abcdefgh
261def test_autocompletion_too_many_parameters(): 1abcdefgh
262 app = typer.Typer() 1abcdefgh
264 def name_callback(ctx, args, incomplete, val2): 1abcdefgh
265 pass # pragma: no cover
267 @app.command() 1abcdefgh
268 def main(name: str = typer.Option(..., autocompletion=name_callback)): 1abcdefgh
269 pass # pragma: no cover
271 with pytest.raises(click.ClickException) as exc_info: 1abcdefgh
272 runner.invoke(app, ["--name", "Camila"]) 1abcdefgh
273 assert exc_info.value.message == "Invalid autocompletion callback parameters: val2" 1abcdefgh
276def test_forward_references(): 1abcdefgh
277 app = typer.Typer() 1abcdefgh
279 @app.command() 1abcdefgh
280 def main(arg1, arg2: int, arg3: "int", arg4: bool = False, arg5: "bool" = False): 1abcdefgh
281 print(f"arg1: {type(arg1)} {arg1}") 1abcdefgh
282 print(f"arg2: {type(arg2)} {arg2}") 1abcdefgh
283 print(f"arg3: {type(arg3)} {arg3}") 1abcdefgh
284 print(f"arg4: {type(arg4)} {arg4}") 1abcdefgh
285 print(f"arg5: {type(arg5)} {arg5}") 1abcdefgh
287 result = runner.invoke(app, ["Hello", "2", "invalid"]) 1abcdefgh
289 assert "Invalid value for 'ARG3': 'invalid' is not a valid integer" in result.output 1abcdefgh
290 result = runner.invoke(app, ["Hello", "2", "3", "--arg4", "--arg5"]) 1abcdefgh
291 assert ( 1abcdefgh
292 "arg1: <class 'str'> Hello\narg2: <class 'int'> 2\narg3: <class 'int'> 3\narg4: <class 'bool'> True\narg5: <class 'bool'> True\n"
293 in result.stdout
294 )
297def test_context_settings_inheritance_single_command(): 1abcdefgh
298 app = typer.Typer(context_settings={"help_option_names": ["-h", "--help"]}) 1abcdefgh
300 @app.command() 1abcdefgh
301 def main(name: str): 1abcdefgh
302 pass # pragma: no cover
304 result = runner.invoke(app, ["main", "-h"]) 1abcdefgh
305 assert "Show this message and exit." in result.stdout 1abcdefgh
308def test_split_opt(): 1abcdefgh
309 prefix, opt = _split_opt("--verbose") 1abcdefgh
310 assert prefix == "--" 1abcdefgh
311 assert opt == "verbose" 1abcdefgh
313 prefix, opt = _split_opt("//verbose") 1abcdefgh
314 assert prefix == "//" 1abcdefgh
315 assert opt == "verbose" 1abcdefgh
317 prefix, opt = _split_opt("-verbose") 1abcdefgh
318 assert prefix == "-" 1abcdefgh
319 assert opt == "verbose" 1abcdefgh
321 prefix, opt = _split_opt("verbose") 1abcdefgh
322 assert prefix == "" 1abcdefgh
323 assert opt == "verbose" 1abcdefgh
326def test_options_metadata_typer_default(): 1abcdefgh
327 app = typer.Typer(options_metavar="[options]") 1abcdefgh
329 @app.command() 1abcdefgh
330 def c1(): 1abcdefgh
331 pass # pragma: no cover
333 @app.command(options_metavar="[OPTS]") 1abcdefgh
334 def c2(): 1abcdefgh
335 pass # pragma: no cover
337 result = runner.invoke(app, ["c1", "--help"]) 1abcdefgh
338 assert "Usage: root c1 [options]" in result.stdout 1abcdefgh
340 result = runner.invoke(app, ["c2", "--help"]) 1abcdefgh
341 assert "Usage: root c2 [OPTS]" in result.stdout 1abcdefgh