Coverage for tests / test_ambiguous_params.py: 100%

71 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-03-26 21:46 +0000

1from typing import Annotated 1abecfdg

2 

3import pytest 1abecfdg

4import typer 1abecfdg

5from typer.testing import CliRunner 1abecfdg

6from typer.utils import ( 1abecfdg

7 AnnotatedParamWithDefaultValueError, 

8 DefaultFactoryAndDefaultValueError, 

9 MixedAnnotatedAndDefaultStyleError, 

10 MultipleTyperAnnotationsError, 

11 _split_annotation_from_typer_annotations, 

12) 

13 

14runner = CliRunner() 1abecfdg

15 

16 

17def test_split_annotations_from_typer_annotations_simple(): 1abecfdg

18 # Simple sanity check that this utility works. If this isn't working on a given 

19 # python version, then no other tests for Annotated will work. 

20 given = Annotated[str, typer.Argument()] 1abecfdg

21 base, typer_annotations = _split_annotation_from_typer_annotations(given) 1abecfdg

22 assert base is str 1abecfdg

23 # No equality check on the param types. Checking the length is sufficient. 

24 assert len(typer_annotations) == 1 1abecfdg

25 

26 

27def test_forbid_default_value_in_annotated_argument(): 1abecfdg

28 app = typer.Typer() 1abecfdg

29 

30 # This test case only works with `typer.Argument`. `typer.Option` uses positionals 

31 # for param_decls too. 

32 @app.command() 1abecfdg

33 def cmd(my_param: Annotated[str, typer.Argument("foo")]): ... # pragma: no cover 1abecfdg

34 

35 with pytest.raises(AnnotatedParamWithDefaultValueError) as excinfo: 1abecfdg

36 runner.invoke(app) 1abecfdg

37 

38 assert vars(excinfo.value) == { 1abecfdg

39 "param_type": typer.models.ArgumentInfo, 

40 "argument_name": "my_param", 

41 } 

42 

43 

44def test_allow_options_to_have_names(): 1abecfdg

45 app = typer.Typer() 1abecfdg

46 

47 @app.command() 1abecfdg

48 def cmd(my_param: Annotated[str, typer.Option("--some-opt")]): 1abecfdg

49 print(my_param) 1abecfdg

50 

51 result = runner.invoke(app, ["--some-opt", "hello"]) 1abecfdg

52 assert result.exit_code == 0, result.output 1abecfdg

53 assert "hello" in result.output 1abecfdg

54 

55 

56@pytest.mark.parametrize( 1abecfdg

57 ["param", "param_info_type"], 

58 [ 

59 (typer.Argument, typer.models.ArgumentInfo), 

60 (typer.Option, typer.models.OptionInfo), 

61 ], 

62) 

63def test_forbid_annotated_param_and_default_param(param, param_info_type): 1abecfdg

64 app = typer.Typer() 1abecfdg

65 

66 @app.command() 1abecfdg

67 def cmd(my_param: Annotated[str, param()] = param("foo")): ... # pragma: no cover 1abecfdg

68 

69 with pytest.raises(MixedAnnotatedAndDefaultStyleError) as excinfo: 1abecfdg

70 runner.invoke(app) 1abecfdg

71 

72 assert vars(excinfo.value) == { 1abecfdg

73 "argument_name": "my_param", 

74 "annotated_param_type": param_info_type, 

75 "default_param_type": param_info_type, 

76 } 

77 

78 

79def test_forbid_multiple_typer_params_in_annotated(): 1abecfdg

80 app = typer.Typer() 1abecfdg

81 

82 @app.command() 1abecfdg

83 def cmd( 1abecfdg

84 my_param: Annotated[str, typer.Argument(), typer.Argument()], 1abcd

85 ): ... # pragma: no cover 

86 

87 with pytest.raises(MultipleTyperAnnotationsError) as excinfo: 1abecfdg

88 runner.invoke(app) 1abecfdg

89 

90 assert vars(excinfo.value) == {"argument_name": "my_param"} 1abecfdg

91 

92 

93def test_allow_multiple_non_typer_params_in_annotated(): 1abecfdg

94 app = typer.Typer() 1abecfdg

95 

96 @app.command() 1abecfdg

97 def cmd(my_param: Annotated[str, "someval", typer.Argument(), 4] = "hello"): 1abecfdg

98 print(my_param) 1abecfdg

99 

100 result = runner.invoke(app) 1abecfdg

101 # Should behave like normal 

102 assert result.exit_code == 0, result.output 1abecfdg

103 assert "hello" in result.output 1abecfdg

104 

105 

106@pytest.mark.parametrize( 1abecfdg

107 ["param", "param_info_type"], 

108 [ 

109 (typer.Argument, typer.models.ArgumentInfo), 

110 (typer.Option, typer.models.OptionInfo), 

111 ], 

112) 

113def test_forbid_default_factory_and_default_value_in_annotated(param, param_info_type): 1abecfdg

114 def make_string(): 1abecfdg

115 return "foo" # pragma: no cover 

116 

117 app = typer.Typer() 1abecfdg

118 

119 @app.command() 1abecfdg

120 def cmd( 1abecfdg

121 my_param: Annotated[str, param(default_factory=make_string)] = "hello", 1abcd

122 ): ... # pragma: no cover 

123 

124 with pytest.raises(DefaultFactoryAndDefaultValueError) as excinfo: 1abecfdg

125 runner.invoke(app) 1abecfdg

126 

127 assert vars(excinfo.value) == { 1abecfdg

128 "argument_name": "my_param", 

129 "param_type": param_info_type, 

130 } 

131 

132 

133@pytest.mark.parametrize( 1abecfdg

134 "param", 

135 [ 

136 typer.Argument, 

137 typer.Option, 

138 ], 

139) 

140def test_allow_default_factory_with_default_param(param): 1abecfdg

141 def make_string(): 1abecfdg

142 return "foo" 1abecfdg

143 

144 app = typer.Typer() 1abecfdg

145 

146 @app.command() 1abecfdg

147 def cmd(my_param: str = param(default_factory=make_string)): 1abecfdg

148 print(my_param) 1abecfdg

149 

150 result = runner.invoke(app) 1abecfdg

151 assert result.exit_code == 0, result.output 1abecfdg

152 assert "foo" in result.output 1abecfdg

153 

154 

155@pytest.mark.parametrize( 1abecfdg

156 ["param", "param_info_type"], 

157 [ 

158 (typer.Argument, typer.models.ArgumentInfo), 

159 (typer.Option, typer.models.OptionInfo), 

160 ], 

161) 

162def test_forbid_default_and_default_factory_with_default_param(param, param_info_type): 1abecfdg

163 def make_string(): 1abecfdg

164 return "foo" # pragma: no cover 

165 

166 app = typer.Typer() 1abecfdg

167 

168 @app.command() 1abecfdg

169 def cmd( 1abecfdg

170 my_param: str = param("hi", default_factory=make_string), 1abecfdg

171 ): ... # pragma: no cover 

172 

173 with pytest.raises(DefaultFactoryAndDefaultValueError) as excinfo: 1abecfdg

174 runner.invoke(app) 1abecfdg

175 

176 assert vars(excinfo.value) == { 1abecfdg

177 "argument_name": "my_param", 

178 "param_type": param_info_type, 

179 } 

180 

181 

182@pytest.mark.parametrize( 1abecfdg

183 ["error", "message"], 

184 [ 

185 ( 

186 AnnotatedParamWithDefaultValueError( 

187 argument_name="my_argument", 

188 param_type=typer.models.ArgumentInfo, 

189 ), 

190 "`Argument` default value cannot be set in `Annotated` for 'my_argument'. Set the default value with `=` instead.", 

191 ), 

192 ( 

193 MixedAnnotatedAndDefaultStyleError( 

194 argument_name="my_argument", 

195 annotated_param_type=typer.models.OptionInfo, 

196 default_param_type=typer.models.ArgumentInfo, 

197 ), 

198 "Cannot specify `Option` in `Annotated` and `Argument` as a default value together for 'my_argument'", 

199 ), 

200 ( 

201 MixedAnnotatedAndDefaultStyleError( 

202 argument_name="my_argument", 

203 annotated_param_type=typer.models.OptionInfo, 

204 default_param_type=typer.models.OptionInfo, 

205 ), 

206 "Cannot specify `Option` in `Annotated` and default value together for 'my_argument'", 

207 ), 

208 ( 

209 MixedAnnotatedAndDefaultStyleError( 

210 argument_name="my_argument", 

211 annotated_param_type=typer.models.ArgumentInfo, 

212 default_param_type=typer.models.ArgumentInfo, 

213 ), 

214 "Cannot specify `Argument` in `Annotated` and default value together for 'my_argument'", 

215 ), 

216 ( 

217 MultipleTyperAnnotationsError( 

218 argument_name="my_argument", 

219 ), 

220 "Cannot specify multiple `Annotated` Typer arguments for 'my_argument'", 

221 ), 

222 ( 

223 DefaultFactoryAndDefaultValueError( 

224 argument_name="my_argument", 

225 param_type=typer.models.OptionInfo, 

226 ), 

227 "Cannot specify `default_factory` and a default value together for `Option`", 

228 ), 

229 ], 

230) 

231def test_error_rendering(error, message): 1abecfdg

232 assert str(error) == message 1abecfdg