Coverage for tests/test_others.py: 100%
184 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
1import os 1abcdefghi
2import subprocess 1abcdefghi
3import sys 1abcdefghi
4import typing 1abcdefghi
5from pathlib import Path 1abcdefghi
6from unittest import mock 1abcdefghi
8import click 1abcdefghi
9import pytest 1abcdefghi
10import typer 1abcdefghi
11import typer._completion_shared 1abcdefghi
12import typer.completion 1abcdefghi
13from typer.core import _split_opt 1abcdefghi
14from typer.main import solve_typer_info_defaults, solve_typer_info_help 1abcdefghi
15from typer.models import ParameterInfo, TyperInfo 1abcdefghi
16from typer.testing import CliRunner 1abcdefghi
17from typing_extensions import Annotated 1abcdefghi
19from .utils import requires_completion_permission 1abcdefghi
21runner = CliRunner() 1abcdefghi
24def test_help_from_info(): 1abcdefghi
25 # Mainly for coverage/completeness
26 value = solve_typer_info_help(TyperInfo()) 1abcdefghi
27 assert value is None 1abcdefghi
30def test_defaults_from_info(): 1abcdefghi
31 # Mainly for coverage/completeness
32 value = solve_typer_info_defaults(TyperInfo()) 1abcdefghi
33 assert value 1abcdefghi
36def test_too_many_parsers(): 1abcdefghi
37 def custom_parser(value: str) -> int: 1abcdefghi
38 return int(value) # pragma: no cover
40 class CustomClickParser(click.ParamType): 1abcdefghi
41 name = "custom_parser" 1abcdefghi
43 def convert( 1abcdefghi
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 = ( 1abcdefghi
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): 1abcdefghi
57 ParameterInfo(parser=custom_parser, click_type=CustomClickParser()) 1abcdefghi
60def test_valid_parser_permutations(): 1abcdefghi
61 def custom_parser(value: str) -> int: 1abcdefghi
62 return int(value) # pragma: no cover
64 class CustomClickParser(click.ParamType): 1abcdefghi
65 name = "custom_parser" 1abcdefghi
67 def convert( 1abcdefghi
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() 1abcdefghi
76 ParameterInfo(parser=custom_parser) 1abcdefghi
77 ParameterInfo(click_type=CustomClickParser()) 1abcdefghi
80@requires_completion_permission 1abcdefghi
81def test_install_invalid_shell(): 1abcdefghi
82 app = typer.Typer() 1abcdefghi
84 @app.command() 1abcdefghi
85 def main(): 1abcdefghi
86 print("Hello World") 1abcdefghi
88 with mock.patch.object( 1abcdefghi
89 typer._completion_shared, "_get_shell_name", return_value="xshell"
90 ):
91 result = runner.invoke(app, ["--install-completion"]) 1abcdefghi
92 assert "Shell xshell is not supported." in result.stdout 1abcdefghi
93 result = runner.invoke(app) 1abcdefghi
94 assert "Hello World" in result.stdout 1abcdefghi
97def test_callback_too_many_parameters(): 1abcdefghi
98 app = typer.Typer() 1abcdefghi
100 def name_callback(ctx, param, val1, val2): 1abcdefghi
101 pass # pragma: no cover
103 @app.command() 1abcdefghi
104 def main(name: str = typer.Option(..., callback=name_callback)): 1abcdefghi
105 pass # pragma: no cover
107 with pytest.raises(click.ClickException) as exc_info: 1abcdefghi
108 runner.invoke(app, ["--name", "Camila"]) 1abcdefghi
109 assert ( 1abcdefghi
110 exc_info.value.message == "Too many CLI parameter callback function parameters"
111 )
114def test_callback_2_untyped_parameters(): 1abcdefghi
115 app = typer.Typer() 1abcdefghi
117 def name_callback(ctx, value): 1abcdefghi
118 print(f"info name is: {ctx.info_name}") 1abcdefghi
119 print(f"value is: {value}") 1abcdefghi
121 @app.command() 1abcdefghi
122 def main(name: str = typer.Option(..., callback=name_callback)): 1abcdefghi
123 print("Hello World") 1abcdefghi
125 result = runner.invoke(app, ["--name", "Camila"]) 1abcdefghi
126 assert "info name is: main" in result.stdout 1abcdefghi
127 assert "value is: Camila" in result.stdout 1abcdefghi
130def test_callback_3_untyped_parameters(): 1abcdefghi
131 app = typer.Typer() 1abcdefghi
133 def name_callback(ctx, param, value): 1abcdefghi
134 print(f"info name is: {ctx.info_name}") 1abcdefghi
135 print(f"param name is: {param.name}") 1abcdefghi
136 print(f"value is: {value}") 1abcdefghi
138 @app.command() 1abcdefghi
139 def main(name: str = typer.Option(..., callback=name_callback)): 1abcdefghi
140 print("Hello World") 1abcdefghi
142 result = runner.invoke(app, ["--name", "Camila"]) 1abcdefghi
143 assert "info name is: main" in result.stdout 1abcdefghi
144 assert "param name is: name" in result.stdout 1abcdefghi
145 assert "value is: Camila" in result.stdout 1abcdefghi
148def test_callback_4_list_none(): 1abcdefghi
149 app = typer.Typer() 1abcdefghi
151 def names_callback(ctx, param, values: typing.Optional[typing.List[str]]): 1abcdefghi
152 if values is None: 1abcdefghi
153 return values 1abcdefghi
154 return [value.upper() for value in values] 1abcdefghi
156 @app.command() 1abcdefghi
157 def main( 1abcdefghi
158 names: typing.Optional[typing.List[str]] = typer.Option(
159 None, "--name", callback=names_callback
160 ),
161 ):
162 if names is None: 1abcdefghi
163 print("Hello World") 1abcdefghi
164 else:
165 print(f"Hello {', '.join(names)}") 1abcdefghi
167 result = runner.invoke(app, ["--name", "Sideshow", "--name", "Bob"]) 1abcdefghi
168 assert "Hello SIDESHOW, BOB" in result.stdout 1abcdefghi
170 result = runner.invoke(app, []) 1abcdefghi
171 assert "Hello World" in result.stdout 1abcdefghi
174def test_empty_list_default_generator(): 1abcdefghi
175 def empty_list() -> typing.List[str]: 1abcdefghi
176 return [] 1abcdefghi
178 app = typer.Typer() 1abcdefghi
180 @app.command() 1abcdefghi
181 def main( 1abcdefghi
182 names: Annotated[typing.List[str], typer.Option(default_factory=empty_list)],
183 ):
184 print(names) 1abcdefghi
186 result = runner.invoke(app) 1abcdefghi
187 assert "[]" in result.output 1abcdefghi
190def test_completion_argument(): 1abcdefghi
191 file_path = Path(__file__).parent / "assets/completion_argument.py" 1abcdefghi
192 result = subprocess.run( 1abcdefghi
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 1abcdefghi
204 assert "ctx: completion_argument" in result.stderr 1abcdefghi
205 assert "arg is: name" in result.stderr 1abcdefghi
206 assert "incomplete is: E" in result.stderr 1abcdefghi
209def test_completion_untyped_parameters(): 1abcdefghi
210 file_path = Path(__file__).parent / "assets/completion_no_types.py" 1abcdefghi
211 result = subprocess.run( 1abcdefghi
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 1abcdefghi
222 assert "args is: []" in result.stderr 1abcdefghi
223 assert "incomplete is: Ca" in result.stderr 1abcdefghi
224 assert '"Camila":"The reader of books."' in result.stdout 1abcdefghi
225 assert '"Carlos":"The writer of scripts."' in result.stdout 1abcdefghi
227 result = subprocess.run( 1abcdefghi
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 1abcdefghi
235def test_completion_untyped_parameters_different_order_correct_names(): 1abcdefghi
236 file_path = Path(__file__).parent / "assets/completion_no_types_order.py" 1abcdefghi
237 result = subprocess.run( 1abcdefghi
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 1abcdefghi
248 assert "args is: []" in result.stderr 1abcdefghi
249 assert "incomplete is: Ca" in result.stderr 1abcdefghi
250 assert '"Camila":"The reader of books."' in result.stdout 1abcdefghi
251 assert '"Carlos":"The writer of scripts."' in result.stdout 1abcdefghi
253 result = subprocess.run( 1abcdefghi
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 1abcdefghi
261def test_autocompletion_too_many_parameters(): 1abcdefghi
262 app = typer.Typer() 1abcdefghi
264 def name_callback(ctx, args, incomplete, val2): 1abcdefghi
265 pass # pragma: no cover
267 @app.command() 1abcdefghi
268 def main(name: str = typer.Option(..., autocompletion=name_callback)): 1abcdefghi
269 pass # pragma: no cover
271 with pytest.raises(click.ClickException) as exc_info: 1abcdefghi
272 runner.invoke(app, ["--name", "Camila"]) 1abcdefghi
273 assert exc_info.value.message == "Invalid autocompletion callback parameters: val2" 1abcdefghi
276def test_forward_references(): 1abcdefghi
277 app = typer.Typer() 1abcdefghi
279 @app.command() 1abcdefghi
280 def main(arg1, arg2: int, arg3: "int", arg4: bool = False, arg5: "bool" = False): 1abcdefghi
281 print(f"arg1: {type(arg1)} {arg1}") 1abcdefghi
282 print(f"arg2: {type(arg2)} {arg2}") 1abcdefghi
283 print(f"arg3: {type(arg3)} {arg3}") 1abcdefghi
284 print(f"arg4: {type(arg4)} {arg4}") 1abcdefghi
285 print(f"arg5: {type(arg5)} {arg5}") 1abcdefghi
287 result = runner.invoke(app, ["Hello", "2", "invalid"]) 1abcdefghi
289 assert "Invalid value for 'ARG3': 'invalid' is not a valid integer" in result.output 1abcdefghi
290 result = runner.invoke(app, ["Hello", "2", "3", "--arg4", "--arg5"]) 1abcdefghi
291 assert ( 1abcdefghi
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(): 1abcdefghi
298 app = typer.Typer(context_settings={"help_option_names": ["-h", "--help"]}) 1abcdefghi
300 @app.command() 1abcdefghi
301 def main(name: str): 1abcdefghi
302 pass # pragma: no cover
304 result = runner.invoke(app, ["main", "-h"]) 1abcdefghi
305 assert "Show this message and exit." in result.stdout 1abcdefghi
308def test_split_opt(): 1abcdefghi
309 prefix, opt = _split_opt("--verbose") 1abcdefghi
310 assert prefix == "--" 1abcdefghi
311 assert opt == "verbose" 1abcdefghi
313 prefix, opt = _split_opt("//verbose") 1abcdefghi
314 assert prefix == "//" 1abcdefghi
315 assert opt == "verbose" 1abcdefghi
317 prefix, opt = _split_opt("-verbose") 1abcdefghi
318 assert prefix == "-" 1abcdefghi
319 assert opt == "verbose" 1abcdefghi
321 prefix, opt = _split_opt("verbose") 1abcdefghi
322 assert prefix == "" 1abcdefghi
323 assert opt == "verbose" 1abcdefghi
326def test_options_metadata_typer_default(): 1abcdefghi
327 app = typer.Typer(options_metavar="[options]") 1abcdefghi
329 @app.command() 1abcdefghi
330 def c1(): 1abcdefghi
331 pass # pragma: no cover
333 @app.command(options_metavar="[OPTS]") 1abcdefghi
334 def c2(): 1abcdefghi
335 pass # pragma: no cover
337 result = runner.invoke(app, ["c1", "--help"]) 1abcdefghi
338 assert "Usage: root c1 [options]" in result.stdout 1abcdefghi
340 result = runner.invoke(app, ["c2", "--help"]) 1abcdefghi
341 assert "Usage: root c2 [OPTS]" in result.stdout 1abcdefghi