Coverage for tests / test_ambiguous_params.py: 100%

71 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-02-09 12:36 +0000

1from typing import Annotated 1abfcdgeh

2 

3import pytest 1abfcdgeh

4import typer 1abfcdgeh

5from typer.testing import CliRunner 1abfcdgeh

6from typer.utils import ( 1abfcdgeh

7 AnnotatedParamWithDefaultValueError, 

8 DefaultFactoryAndDefaultValueError, 

9 MixedAnnotatedAndDefaultStyleError, 

10 MultipleTyperAnnotationsError, 

11 _split_annotation_from_typer_annotations, 

12) 

13 

14runner = CliRunner() 1abfcdgeh

15 

16 

17def test_split_annotations_from_typer_annotations_simple(): 1abfcdgeh

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()] 1abfcdgeh

21 base, typer_annotations = _split_annotation_from_typer_annotations(given) 1abfcdgeh

22 assert base is str 1abfcdgeh

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

24 assert len(typer_annotations) == 1 1abfcdgeh

25 

26 

27def test_forbid_default_value_in_annotated_argument(): 1abfcdgeh

28 app = typer.Typer() 1abfcdgeh

29 

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

31 # for param_decls too. 

32 @app.command() 1abfcdgeh

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

34 

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

36 runner.invoke(app) 1abfcdgeh

37 

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

39 "param_type": typer.models.ArgumentInfo, 

40 "argument_name": "my_param", 

41 } 

42 

43 

44def test_allow_options_to_have_names(): 1abfcdgeh

45 app = typer.Typer() 1abfcdgeh

46 

47 @app.command() 1abfcdgeh

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

49 print(my_param) 1abfcdgeh

50 

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

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

53 assert "hello" in result.output 1abfcdgeh

54 

55 

56@pytest.mark.parametrize( 1abfcdgeh

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): 1abfcdgeh

64 app = typer.Typer() 1abfcdgeh

65 

66 @app.command() 1abfcdgeh

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

68 

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

70 runner.invoke(app) 1abfcdgeh

71 

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

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(): 1abfcdgeh

80 app = typer.Typer() 1abfcdgeh

81 

82 @app.command() 1abfcdgeh

83 def cmd( 1abfcdgeh

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

85 ): ... # pragma: no cover 

86 

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

88 runner.invoke(app) 1abfcdgeh

89 

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

91 

92 

93def test_allow_multiple_non_typer_params_in_annotated(): 1abfcdgeh

94 app = typer.Typer() 1abfcdgeh

95 

96 @app.command() 1abfcdgeh

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

98 print(my_param) 1abfcdgeh

99 

100 result = runner.invoke(app) 1abfcdgeh

101 # Should behave like normal 

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

103 assert "hello" in result.output 1abfcdgeh

104 

105 

106@pytest.mark.parametrize( 1abfcdgeh

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): 1abfcdgeh

114 def make_string(): 1abfcdgeh

115 return "foo" # pragma: no cover 

116 

117 app = typer.Typer() 1abfcdgeh

118 

119 @app.command() 1abfcdgeh

120 def cmd( 1abfcdgeh

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

122 ): ... # pragma: no cover 

123 

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

125 runner.invoke(app) 1abfcdgeh

126 

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

128 "argument_name": "my_param", 

129 "param_type": param_info_type, 

130 } 

131 

132 

133@pytest.mark.parametrize( 1abfcdgeh

134 "param", 

135 [ 

136 typer.Argument, 

137 typer.Option, 

138 ], 

139) 

140def test_allow_default_factory_with_default_param(param): 1abfcdgeh

141 def make_string(): 1abfcdgeh

142 return "foo" 1abfcdgeh

143 

144 app = typer.Typer() 1abfcdgeh

145 

146 @app.command() 1abfcdgeh

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

148 print(my_param) 1abfcdgeh

149 

150 result = runner.invoke(app) 1abfcdgeh

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

152 assert "foo" in result.output 1abfcdgeh

153 

154 

155@pytest.mark.parametrize( 1abfcdgeh

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): 1abfcdgeh

163 def make_string(): 1abfcdgeh

164 return "foo" # pragma: no cover 

165 

166 app = typer.Typer() 1abfcdgeh

167 

168 @app.command() 1abfcdgeh

169 def cmd( 1abfcdgeh

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

171 ): ... # pragma: no cover 

172 

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

174 runner.invoke(app) 1abfcdgeh

175 

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

177 "argument_name": "my_param", 

178 "param_type": param_info_type, 

179 } 

180 

181 

182@pytest.mark.parametrize( 1abfcdgeh

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): 1abfcdgeh

232 assert str(error) == message 1abfcdgeh