Coverage for tests/test_ambiguous_params.py: 100%
71 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 pytest 1abgcdhefi
2import typer 1abgcdhefi
3from typer.testing import CliRunner 1abgcdhefi
4from typer.utils import ( 1abgcdhefi
5 AnnotatedParamWithDefaultValueError,
6 DefaultFactoryAndDefaultValueError,
7 MixedAnnotatedAndDefaultStyleError,
8 MultipleTyperAnnotationsError,
9 _split_annotation_from_typer_annotations,
10)
11from typing_extensions import Annotated 1abgcdhefi
13runner = CliRunner() 1abgcdhefi
16def test_split_annotations_from_typer_annotations_simple(): 1abgcdhefi
17 # Simple sanity check that this utility works. If this isn't working on a given
18 # python version, then no other tests for Annotated will work.
19 given = Annotated[str, typer.Argument()] 1abgcdhefi
20 base, typer_annotations = _split_annotation_from_typer_annotations(given) 1abgcdhefi
21 assert base is str 1abgcdhefi
22 # No equality check on the param types. Checking the length is sufficient.
23 assert len(typer_annotations) == 1 1abgcdhefi
26def test_forbid_default_value_in_annotated_argument(): 1abgcdhefi
27 app = typer.Typer() 1abgcdhefi
29 # This test case only works with `typer.Argument`. `typer.Option` uses positionals
30 # for param_decls too.
31 @app.command() 1abgcdhefi
32 def cmd(my_param: Annotated[str, typer.Argument("foo")]): ... # pragma: no cover 1abgcdhefi
34 with pytest.raises(AnnotatedParamWithDefaultValueError) as excinfo: 1abgcdhefi
35 runner.invoke(app) 1abgcdhefi
37 assert vars(excinfo.value) == { 1abgcdhefi
38 "param_type": typer.models.ArgumentInfo,
39 "argument_name": "my_param",
40 }
43def test_allow_options_to_have_names(): 1abgcdhefi
44 app = typer.Typer() 1abgcdhefi
46 @app.command() 1abgcdhefi
47 def cmd(my_param: Annotated[str, typer.Option("--some-opt")]): 1abgcdhefi
48 print(my_param) 1abgcdhefi
50 result = runner.invoke(app, ["--some-opt", "hello"]) 1abgcdhefi
51 assert result.exit_code == 0, result.output 1abgcdhefi
52 assert "hello" in result.output 1abgcdhefi
55@pytest.mark.parametrize( 1abgcdhefi
56 ["param", "param_info_type"],
57 [
58 (typer.Argument, typer.models.ArgumentInfo),
59 (typer.Option, typer.models.OptionInfo),
60 ],
61)
62def test_forbid_annotated_param_and_default_param(param, param_info_type): 1abgcdhefi
63 app = typer.Typer() 1abgcdhefi
65 @app.command() 1abgcdhefi
66 def cmd(my_param: Annotated[str, param()] = param("foo")): ... # pragma: no cover 1abgcdhefi
68 with pytest.raises(MixedAnnotatedAndDefaultStyleError) as excinfo: 1abgcdhefi
69 runner.invoke(app) 1abgcdhefi
71 assert vars(excinfo.value) == { 1abgcdhefi
72 "argument_name": "my_param",
73 "annotated_param_type": param_info_type,
74 "default_param_type": param_info_type,
75 }
78def test_forbid_multiple_typer_params_in_annotated(): 1abgcdhefi
79 app = typer.Typer() 1abgcdhefi
81 @app.command() 1abgcdhefi
82 def cmd( 1abgcdhefi
83 my_param: Annotated[str, typer.Argument(), typer.Argument()], 1abcdef
84 ): ... # pragma: no cover
86 with pytest.raises(MultipleTyperAnnotationsError) as excinfo: 1abgcdhefi
87 runner.invoke(app) 1abgcdhefi
89 assert vars(excinfo.value) == {"argument_name": "my_param"} 1abgcdhefi
92def test_allow_multiple_non_typer_params_in_annotated(): 1abgcdhefi
93 app = typer.Typer() 1abgcdhefi
95 @app.command() 1abgcdhefi
96 def cmd(my_param: Annotated[str, "someval", typer.Argument(), 4] = "hello"): 1abgcdhefi
97 print(my_param) 1abgcdhefi
99 result = runner.invoke(app) 1abgcdhefi
100 # Should behave like normal
101 assert result.exit_code == 0, result.output 1abgcdhefi
102 assert "hello" in result.output 1abgcdhefi
105@pytest.mark.parametrize( 1abgcdhefi
106 ["param", "param_info_type"],
107 [
108 (typer.Argument, typer.models.ArgumentInfo),
109 (typer.Option, typer.models.OptionInfo),
110 ],
111)
112def test_forbid_default_factory_and_default_value_in_annotated(param, param_info_type): 1abgcdhefi
113 def make_string(): 1abgcdhefi
114 return "foo" # pragma: no cover
116 app = typer.Typer() 1abgcdhefi
118 @app.command() 1abgcdhefi
119 def cmd( 1abgcdhefi
120 my_param: Annotated[str, param(default_factory=make_string)] = "hello", 1abcdef
121 ): ... # pragma: no cover
123 with pytest.raises(DefaultFactoryAndDefaultValueError) as excinfo: 1abgcdhefi
124 runner.invoke(app) 1abgcdhefi
126 assert vars(excinfo.value) == { 1abgcdhefi
127 "argument_name": "my_param",
128 "param_type": param_info_type,
129 }
132@pytest.mark.parametrize( 1abgcdhefi
133 "param",
134 [
135 typer.Argument,
136 typer.Option,
137 ],
138)
139def test_allow_default_factory_with_default_param(param): 1abgcdhefi
140 def make_string(): 1abgcdhefi
141 return "foo" 1abgcdhefi
143 app = typer.Typer() 1abgcdhefi
145 @app.command() 1abgcdhefi
146 def cmd(my_param: str = param(default_factory=make_string)): 1abgcdhefi
147 print(my_param) 1abgcdhefi
149 result = runner.invoke(app) 1abgcdhefi
150 assert result.exit_code == 0, result.output 1abgcdhefi
151 assert "foo" in result.output 1abgcdhefi
154@pytest.mark.parametrize( 1abgcdhefi
155 ["param", "param_info_type"],
156 [
157 (typer.Argument, typer.models.ArgumentInfo),
158 (typer.Option, typer.models.OptionInfo),
159 ],
160)
161def test_forbid_default_and_default_factory_with_default_param(param, param_info_type): 1abgcdhefi
162 def make_string(): 1abgcdhefi
163 return "foo" # pragma: no cover
165 app = typer.Typer() 1abgcdhefi
167 @app.command() 1abgcdhefi
168 def cmd( 1abgcdhefi
169 my_param: str = param("hi", default_factory=make_string), 1abgcdhefi
170 ): ... # pragma: no cover
172 with pytest.raises(DefaultFactoryAndDefaultValueError) as excinfo: 1abgcdhefi
173 runner.invoke(app) 1abgcdhefi
175 assert vars(excinfo.value) == { 1abgcdhefi
176 "argument_name": "my_param",
177 "param_type": param_info_type,
178 }
181@pytest.mark.parametrize( 1abgcdhefi
182 ["error", "message"],
183 [
184 (
185 AnnotatedParamWithDefaultValueError(
186 argument_name="my_argument",
187 param_type=typer.models.ArgumentInfo,
188 ),
189 "`Argument` default value cannot be set in `Annotated` for 'my_argument'. Set the default value with `=` instead.",
190 ),
191 (
192 MixedAnnotatedAndDefaultStyleError(
193 argument_name="my_argument",
194 annotated_param_type=typer.models.OptionInfo,
195 default_param_type=typer.models.ArgumentInfo,
196 ),
197 "Cannot specify `Option` in `Annotated` and `Argument` as a default value together for 'my_argument'",
198 ),
199 (
200 MixedAnnotatedAndDefaultStyleError(
201 argument_name="my_argument",
202 annotated_param_type=typer.models.OptionInfo,
203 default_param_type=typer.models.OptionInfo,
204 ),
205 "Cannot specify `Option` in `Annotated` and default value together for 'my_argument'",
206 ),
207 (
208 MixedAnnotatedAndDefaultStyleError(
209 argument_name="my_argument",
210 annotated_param_type=typer.models.ArgumentInfo,
211 default_param_type=typer.models.ArgumentInfo,
212 ),
213 "Cannot specify `Argument` in `Annotated` and default value together for 'my_argument'",
214 ),
215 (
216 MultipleTyperAnnotationsError(
217 argument_name="my_argument",
218 ),
219 "Cannot specify multiple `Annotated` Typer arguments for 'my_argument'",
220 ),
221 (
222 DefaultFactoryAndDefaultValueError(
223 argument_name="my_argument",
224 param_type=typer.models.OptionInfo,
225 ),
226 "Cannot specify `default_factory` and a default value together for `Option`",
227 ),
228 ],
229)
230def test_error_rendering(error, message): 1abgcdhefi
231 assert str(error) == message 1abgcdhefi