Coverage for tests / test_core.py: 100%
233 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-09 00:15 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-09 00:15 +0000
1from typing import Annotated 1abcdefg
3import pytest 1abcdefg
4import typer 1abcdefg
5import typer._completion_shared 1abcdefg
6import typer.completion 1abcdefg
7from typer import _click 1abcdefg
8from typer.core import TyperArgument, TyperCommand, TyperGroup, TyperOption, _split_opt 1abcdefg
9from typer.testing import CliRunner 1abcdefg
11runner = CliRunner() 1abcdefg
14def test_human_readable_name() -> None: 1abcdefg
15 app = typer.Typer() 1abcdefg
17 @app.command() 1abcdefg
18 def main( 1abcdefg
19 my_arg_1: Annotated[str, typer.Argument()],
20 my_arg_2: Annotated[str, typer.Argument(metavar="META_ARG")],
21 my_opt: Annotated[str, typer.Option()],
22 ):
23 pass # pragma: no cover
25 command = typer.main.get_command(app) 1abcdefg
26 params = {param.name: param for param in command.params} 1abcdefg
28 assert params["my_arg_1"].human_readable_name == "my_arg_1" 1abcdefg
29 assert params["my_arg_2"].human_readable_name == "META_ARG" 1abcdefg
30 assert params["my_opt"].human_readable_name == "my_opt" 1abcdefg
33def test_parameter_metavar() -> None: 1abcdefg
34 app = typer.Typer(rich_markup_mode=None) 1abcdefg
36 @app.command() 1abcdefg
37 def cmd(name: Annotated[str, typer.Option(metavar="CUSTOM")]) -> None: 1abcdefg
38 pass # pragma: no cover
40 result = runner.invoke(app, ["--help"]) 1abcdefg
41 assert result.exit_code == 0 1abcdefg
42 assert "--name CUSTOM" in result.output 1abcdefg
45def test_parameter_nargs_gt_1() -> None: 1abcdefg
46 param = TyperArgument(param_decls=["value"], type=str, nargs=2) 1abcdefg
47 ctx = _click.Context(TyperCommand(name="cmd")) 1abcdefg
49 assert param.type_cast_value(ctx, ("one", "two")) == ("one", "two") 1abcdefg
51 with pytest.raises( 1abcdefg
52 _click.exceptions.BadParameter, match="Takes 2 values but 1 given."
53 ):
54 param.type_cast_value(ctx, ("one",)) 1abcdefg
57def test_parameter_constructor() -> None: 1abcdefg
58 # no param_decl and expose_value is False: sets name to None
59 arg = TyperArgument(param_decls=[], expose_value=False) 1abcdefg
60 assert arg.name is None 1abcdefg
61 assert arg.opts == [] 1abcdefg
62 assert arg.secondary_opts == [] 1abcdefg
64 # no param_decl and expose_value is True: raises
65 with pytest.raises(TypeError, match="does not have a name."): 1abcdefg
66 TyperArgument(param_decls=[], expose_value=True) 1abcdefg
68 # len(param_decl) > 1: raises
69 with pytest.raises(TypeError, match="take exactly one parameter declaration"): 1abcdefg
70 TyperArgument(param_decls=["first", "second"]) 1abcdefg
72 # duplicated identifier in option declarations: raises
73 with pytest.raises(TypeError, match="Name 'name' defined twice"): 1abcdefg
74 TyperOption(param_decls=["name", "name"], required=False) 1abcdefg
76 # same true/false flag in boolean option declaration: raises
77 with pytest.raises(ValueError, match="cannot use the same flag for true/false"): 1abcdefg
78 TyperOption(param_decls=["flag", "--flag/--flag"], required=False, is_flag=True) 1abcdefg
80 # inferred name is not a valid identifier: sets name to None
81 unnamed_option = TyperOption(param_decls=["--123"], required=False) 1abcdefg
82 assert unnamed_option.name is None 1abcdefg
84 # no param_decl and prompt=True: raises
85 with pytest.raises(TypeError, match="'name' is required with 'prompt=True'."): 1abcdefg
86 TyperOption(param_decls=[], expose_value=False, prompt=True, required=False) 1abcdefg
88 # count works
89 option = TyperOption( 1abcdefg
90 param_decls=["verbose", "--verbose", "-v"],
91 type=None,
92 default=0,
93 required=False,
94 count=True,
95 )
96 assert isinstance(option.type, _click.types.IntRange) 1abcdefg
97 assert option.type.min == 0 1abcdefg
100def test_option_error_hint() -> None: 1abcdefg
101 option = TyperOption( 1abcdefg
102 param_decls=["name", "--name"],
103 required=False,
104 show_envvar=True,
105 envvar="APP_NAME",
106 )
107 hint = option.get_error_hint(_click.Context(TyperCommand(name="cmd"))) 1abcdefg
108 assert "(env var: 'APP_NAME')" in hint 1abcdefg
111def test_group_init() -> None: 1abcdefg
112 group_no_commands = TyperGroup(name="root", commands=None) 1abcdefg
113 assert group_no_commands.commands == {} 1abcdefg
115 named = TyperCommand(name="named") 1abcdefg
116 unnamed = TyperCommand(name=None) 1abcdefg
117 group_command_sequence = TyperGroup(name="root", commands=[named, unnamed]) 1abcdefg
118 assert group_command_sequence.commands == {"named": named} 1abcdefg
121@pytest.mark.parametrize("with_result_callback", [False, True]) 1abcdefg
122def test_group_result_callback(with_result_callback: bool) -> None: 1abcdefg
123 called = {"child": False, "result_callback": False} 1abcdefg
125 def child_callback() -> None: 1abcdefg
126 called["child"] = True 1abcdefg
127 return None 1abcdefg
129 def result_callback(value, **kwargs): # type: ignore[no-untyped-def] 1abcdefg
130 called["result_callback"] = True 1abcdefg
131 return value 1abcdefg
133 child = TyperCommand(name="child", callback=child_callback) 1abcdefg
134 group = TyperGroup( 1abcdefg
135 name="root",
136 commands={"child": child},
137 result_callback=result_callback if with_result_callback else None,
138 )
139 ctx = group.make_context("root", ["child"]) 1abcdefg
141 result = group.invoke(ctx) 1abcdefg
143 assert result is None 1abcdefg
144 assert called["child"] is True 1abcdefg
145 assert called["result_callback"] is with_result_callback 1abcdefg
146 assert ctx.invoked_subcommand == "child" 1abcdefg
149def test_group_add_command() -> None: 1abcdefg
150 group = TyperGroup(name="root") 1abcdefg
151 unnamed_command = TyperCommand(name=None) 1abcdefg
153 with pytest.raises(TypeError, match="Command has no name."): 1abcdefg
154 group.add_command(unnamed_command) 1abcdefg
157def test_group_click_resolve_command() -> None: 1abcdefg
158 child = TyperCommand(name="child") 1abcdefg
159 group = TyperGroup(name="root", commands={"child": child}) 1abcdefg
160 ctx = group.make_context("root", ["CHILD"], token_normalize_func=str.lower) 1abcdefg
162 cmd_name, cmd, remaining = group._click_resolve_command(ctx, ["CHILD"]) 1abcdefg
164 assert cmd_name == "child" 1abcdefg
165 assert cmd is child 1abcdefg
166 assert remaining == [] 1abcdefg
169@pytest.mark.parametrize( 1abcdefg
170 ("envvar", "auto_prefix", "set_env", "expected"),
171 [
172 ("APP_NAME", None, True, "my-precious"),
173 (None, "APP", True, "my-precious"),
174 (None, None, False, None),
175 ],
176)
177def test_option_resolve_envvar( 1abcdefg
178 monkeypatch: pytest.MonkeyPatch,
179 envvar: str | None,
180 auto_prefix: str | None,
181 set_env: bool,
182 expected: str | None,
183) -> None:
184 option = TyperOption( 1abcdefg
185 param_decls=["name", "--name"],
186 required=False,
187 envvar=envvar,
188 )
189 if set_env: 1abcdefg
190 monkeypatch.setenv("APP_NAME", "my-precious") 1abcdefg
192 ctx = _click.Context(TyperCommand(name="cmd"), auto_envvar_prefix=auto_prefix) 1abcdefg
193 assert option.resolve_envvar_value(ctx) == expected 1abcdefg
196def test_option_resolve_envvar_list( 1abcdefg
197 monkeypatch: pytest.MonkeyPatch,
198) -> None:
199 option = TyperOption( 1abcdefg
200 param_decls=["name", "--name"],
201 required=False,
202 envvar=["APP_NAME_1", "APP_NAME_2"],
203 )
204 monkeypatch.delenv("APP_NAME_1", raising=False) 1abcdefg
205 monkeypatch.delenv("APP_NAME_2", raising=False) 1abcdefg
206 ctx = _click.Context(TyperCommand(name="cmd")) 1abcdefg
208 assert option.resolve_envvar_value(ctx) is None 1abcdefg
211def test_context_auto_envvar() -> None: 1abcdefg
212 app = typer.Typer(context_settings={"auto_envvar_prefix": "APP"}) 1abcdefg
213 sub_app = typer.Typer() 1abcdefg
215 @sub_app.command() 1abcdefg
216 def clone(ctx: typer.Context) -> None: 1abcdefg
217 print(ctx.auto_envvar_prefix) 1abcdefg
219 app.add_typer(sub_app, name="beth") 1abcdefg
221 result = runner.invoke(app, ["beth", "clone"]) 1abcdefg
222 assert result.exit_code == 0 1abcdefg
223 assert "APP_BETH_CLONE" in result.stdout 1abcdefg
226def test_context_with_resource() -> None: 1abcdefg
227 events: list[str] = [] 1abcdefg
229 class DemoResource: 1abcdefg
230 def __enter__(self) -> str: 1abcdefg
231 events.append("enter") 1abcdefg
232 return "pickle-rick" 1abcdefg
234 def __exit__(self, *args: object) -> None: 1abcdefg
235 events.append("exit") 1abcdefg
237 app = typer.Typer() 1abcdefg
239 @app.command() 1abcdefg
240 def cmd(ctx: typer.Context) -> None: 1abcdefg
241 value = ctx.with_resource(DemoResource()) 1abcdefg
242 assert value == "pickle-rick" 1abcdefg
243 assert events == ["enter"] 1abcdefg
244 print("I'm a pickle") 1abcdefg
246 result = runner.invoke(app) 1abcdefg
248 assert result.exit_code == 0 1abcdefg
249 assert "I'm a pickle" in result.stdout 1abcdefg
250 assert events == ["enter", "exit"] 1abcdefg
253def test_context_find_root() -> None: 1abcdefg
254 app = typer.Typer() 1abcdefg
255 sub_app = typer.Typer() 1abcdefg
257 @sub_app.command() 1abcdefg
258 def child(ctx: typer.Context) -> None: 1abcdefg
259 root = ctx.find_root() 1abcdefg
260 assert root.parent is None 1abcdefg
261 assert root is ctx.parent.parent 1abcdefg
262 print("ok") 1abcdefg
264 app.add_typer(sub_app, name="sub") 1abcdefg
266 result = runner.invoke(app, ["sub", "child"]) 1abcdefg
267 assert result.exit_code == 0 1abcdefg
268 assert "ok" in result.stdout 1abcdefg
271def test_context_find_object() -> None: 1abcdefg
272 class Marker: 1abcdefg
273 pass 1abcdefg
275 marker = Marker() 1abcdefg
276 app = typer.Typer() 1abcdefg
278 @app.callback() 1abcdefg
279 def callback(ctx: typer.Context) -> None: 1abcdefg
280 ctx.obj = marker 1abcdefg
282 @app.command() 1abcdefg
283 def child(ctx: typer.Context) -> None: 1abcdefg
284 assert ctx.find_object(Marker) is marker 1abcdefg
285 assert ctx.find_object(str) is None 1abcdefg
286 print("ok") 1abcdefg
288 result = runner.invoke(app, ["child"]) 1abcdefg
289 assert result.exit_code == 0 1abcdefg
290 assert "ok" in result.stdout 1abcdefg
293def test_context_lookup_default_callable() -> None: 1abcdefg
294 app = typer.Typer() 1abcdefg
296 @app.command() 1abcdefg
297 def child(ctx: typer.Context) -> None: 1abcdefg
298 ctx.default_map = {"planet": lambda: "Earth"} 1abcdefg
299 assert ctx.lookup_default("planet") == "Earth" 1abcdefg
300 value = ctx.lookup_default("planet", call=False) 1abcdefg
301 assert callable(value) 1abcdefg
302 print("ok") 1abcdefg
304 result = runner.invoke(app) 1abcdefg
305 assert result.exit_code == 0 1abcdefg
306 assert "ok" in result.stdout 1abcdefg
309def test_context_abort() -> None: 1abcdefg
310 app = typer.Typer() 1abcdefg
312 @app.command() 1abcdefg
313 def cmd(ctx: typer.Context) -> None: 1abcdefg
314 ctx.abort() 1abcdefg
316 result = runner.invoke(app, standalone_mode=False) 1abcdefg
317 assert result.exit_code == 1 1abcdefg
318 assert isinstance(result.exception, _click.core.Abort) 1abcdefg
321def test_command_help_disabled() -> None: 1abcdefg
322 app = typer.Typer() 1abcdefg
324 @app.command(add_help_option=False) 1abcdefg
325 def cmd() -> None: 1abcdefg
326 pass # pragma: no cover
328 result = runner.invoke(app, ["--help"], standalone_mode=False) 1abcdefg
329 assert result.exit_code == 1 1abcdefg
330 assert isinstance(result.exception, _click.exceptions.NoSuchOption) 1abcdefg
331 assert result.exception.option_name == "--help" 1abcdefg
334def test_command_help_deprecated() -> None: 1abcdefg
335 app = typer.Typer(rich_markup_mode=None, epilog="Built with love") 1abcdefg
337 @app.command(short_help="Shorty", help="Regular help text.", deprecated=True) 1abcdefg
338 def one() -> None: 1abcdefg
339 pass # pragma: no cover
341 @app.command() 1abcdefg
342 def two() -> None: 1abcdefg
343 pass # pragma: no cover
345 result = runner.invoke(app, ["one", "--help"]) 1abcdefg
346 assert result.exit_code == 0 1abcdefg
347 assert "Regular help text. (DEPRECATED)" in result.output 1abcdefg
349 result = runner.invoke(app, ["--help"]) 1abcdefg
350 assert result.exit_code == 0 1abcdefg
351 assert "Built with love" in result.output 1abcdefg
352 assert "oneShorty(DEPRECATED)" in result.output.replace(" ", "") 1abcdefg
355@pytest.mark.parametrize( 1abcdefg
356 ("value", "expected_prefix", "expected_opt"),
357 [
358 ("--verbose", "--", "verbose"),
359 ("//verbose", "//", "verbose"),
360 ("-verbose", "-", "verbose"),
361 ("verbose", "", "verbose"),
362 ],
363)
364def test_split_opt(value: str, expected_prefix: str, expected_opt: str) -> None: 1abcdefg
365 prefix, opt = _split_opt(value) 1abcdefg
366 assert prefix == expected_prefix 1abcdefg
367 assert opt == expected_opt 1abcdefg
370def test_nargs_default_map(): 1abcdefg
371 app = typer.Typer() 1abcdefg
373 @app.command() 1abcdefg
374 def main(names: list[str] = typer.Option(None)): 1abcdefg
375 print(names) # pragma: no cover
377 result = runner.invoke(app, [], default_map={"names": "not-a-list"}) 1abcdefg
378 assert result.exit_code == 2 1abcdefg
379 assert "Invalid value" in result.output 1abcdefg
382def test_parameter_name_casing(): 1abcdefg
383 app = typer.Typer() 1abcdefg
385 @app.command() 1abcdefg
386 def main( 1abcdefg
387 arg1: int,
388 arg2: int = 42,
389 arg3: int = typer.Argument(...),
390 ARG4: int = typer.Argument(42),
391 ARG5: int = typer.Option(...),
392 arg6: int = typer.Option(42),
393 arg7: int = typer.Argument(42, metavar="meta7"),
394 arg8: int = typer.Argument(metavar="ARG8"),
395 arg9: int = typer.Option(metavar="ARG9"),
396 ):
397 print( 1abcdefg
398 f"arg1={arg1} arg2={arg2} arg3={arg3} ARG4={ARG4} ARG5={ARG5} "
399 f"arg6={arg6} arg7={arg7} arg8={arg8} arg9={arg9}"
400 )
402 result = runner.invoke( 1abcdefg
403 app,
404 [
405 "1",
406 "3",
407 "4",
408 "7",
409 "8",
410 "--arg2",
411 "2",
412 "--ARG5",
413 "5",
414 "--arg6",
415 "6",
416 "--arg9",
417 "9",
418 ],
419 )
420 assert result.exit_code == 0 1abcdefg
421 assert ( 1abcdefg
422 "arg1=1 arg2=2 arg3=3 ARG4=4 ARG5=5 arg6=6 arg7=7 arg8=8 arg9=9"
423 in result.output
424 )
426 result = runner.invoke(app, ["1", "3", "4", "7", "8", "--ARG5", "5", "--arg9", "9"]) 1abcdefg
427 assert result.exit_code == 0 1abcdefg
428 assert ( 1abcdefg
429 "arg1=1 arg2=42 arg3=3 ARG4=4 ARG5=5 arg6=42 arg7=7 arg8=8 arg9=9"
430 in result.output
431 )
433 result = runner.invoke(app, ["1", "3", "4", "7", "8", "--arg5", "5", "--arg9", "9"]) 1abcdefg
434 assert result.exit_code != 0 1abcdefg
435 assert "No such option: --arg5" in result.output 1abcdefg
437 result = runner.invoke(app, ["1", "3", "4", "7", "8", "--ARG5", "5", "--ARG9", "9"]) 1abcdefg
438 assert result.exit_code != 0 1abcdefg
439 assert "No such option: --ARG9" in result.output 1abcdefg