Coverage for tests / test_annotated.py: 100%

71 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-11-09 00:15 +0000

1import sys 1acdebfg

2from pathlib import Path 1acdebfg

3from typing import Annotated 1acdebfg

4 

5import pytest 1acdebfg

6import typer 1acdebfg

7from typer.testing import CliRunner 1acdebfg

8 

9runner = CliRunner() 1acdebfg

10 

11 

12def test_annotated_argument_with_default(): 1acdebfg

13 app = typer.Typer() 1acdebfg

14 

15 @app.command() 1acdebfg

16 def cmd(val: Annotated[int, typer.Argument()] = 0): 1acdebfg

17 print(f"hello {val}") 1acdebfg

18 

19 result = runner.invoke(app) 1acdebfg

20 assert result.exit_code == 0, result.output 1acdebfg

21 assert "hello 0" in result.output 1acdebfg

22 

23 result = runner.invoke(app, ["42"]) 1acdebfg

24 assert result.exit_code == 0, result.output 1acdebfg

25 assert "hello 42" in result.output 1acdebfg

26 

27 

28def test_annotated_argument_in_string_type_with_default(): 1acdebfg

29 app = typer.Typer() 1acdebfg

30 

31 @app.command() 1acdebfg

32 def cmd(val: "Annotated[int, typer.Argument()]" = 0): 1acdebfg

33 print(f"hello {val}") 1acdebfg

34 

35 result = runner.invoke(app) 1acdebfg

36 assert result.exit_code == 0, result.output 1acdebfg

37 assert "hello 0" in result.output 1acdebfg

38 

39 result = runner.invoke(app, ["42"]) 1acdebfg

40 assert result.exit_code == 0, result.output 1acdebfg

41 assert "hello 42" in result.output 1acdebfg

42 

43 

44def test_annotated_argument_with_default_factory(): 1acdebfg

45 app = typer.Typer() 1acdebfg

46 

47 def make_string(): 1acdebfg

48 return "I made it" 1acdebfg

49 

50 @app.command() 1acdebfg

51 def cmd(val: Annotated[str, typer.Argument(default_factory=make_string)]): 1acdebfg

52 print(val) 1acdebfg

53 

54 result = runner.invoke(app) 1acdebfg

55 assert result.exit_code == 0, result.output 1acdebfg

56 assert "I made it" in result.output 1acdebfg

57 

58 result = runner.invoke(app, ["overridden"]) 1acdebfg

59 assert result.exit_code == 0, result.output 1acdebfg

60 assert "overridden" in result.output 1acdebfg

61 

62 

63def test_annotated_option_with_argname_doesnt_mutate_multiple_calls(): 1acdebfg

64 app = typer.Typer() 1acdebfg

65 

66 @app.command() 1acdebfg

67 def cmd(force: Annotated[bool, typer.Option("--force")] = False): 1acdebfg

68 if force: 1acdebfg

69 print("Forcing operation") 1acdebfg

70 else: 

71 print("Not forcing") 1acdebfg

72 

73 result = runner.invoke(app) 1acdebfg

74 assert result.exit_code == 0, result.output 1acdebfg

75 assert "Not forcing" in result.output 1acdebfg

76 

77 result = runner.invoke(app, ["--force"]) 1acdebfg

78 assert result.exit_code == 0, result.output 1acdebfg

79 assert "Forcing operation" in result.output 1acdebfg

80 

81 

82def test_annotated_custom_path(): 1acdebfg

83 app = typer.Typer() 1acdebfg

84 

85 class CustomPath(Path): 1acdebfg

86 # Subclassing Path was not fully supported before 3.12 

87 # https://docs.python.org/3.12/whatsnew/3.12.html 

88 if sys.version_info < (3, 12): 1acdebfg

89 _flavour = type(Path())._flavour 1ab

90 

91 @app.command() 1acdebfg

92 def custom_parser( 1acdebfg

93 my_path: Annotated[CustomPath, typer.Argument(parser=CustomPath)], 

94 ): 

95 assert isinstance(my_path, CustomPath) 1acdebfg

96 

97 result = runner.invoke(app, "/some/quirky/path/implementation") 1acdebfg

98 assert result.exit_code == 0 1acdebfg

99 

100 

101def test_annotated_option_invalid(): 1acdebfg

102 app = typer.Typer() 1acdebfg

103 

104 @app.command() 1acdebfg

105 def cmd(value: Annotated[str, typer.Option(..., "foo-bar")]): 1acdebfg

106 print(value) # pragma: no cover 

107 

108 with pytest.raises(ValueError, match="Invalid start character for option"): 1acdebfg

109 runner.invoke(app, ["--help"], catch_exceptions=False) 1acdebfg