Coverage for tests / test_others.py: 100%

184 statements  

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

1import os 1abcdefg

2import subprocess 1abcdefg

3import sys 1abcdefg

4import typing 1abcdefg

5from pathlib import Path 1abcdefg

6from typing import Annotated 1abcdefg

7from unittest import mock 1abcdefg

8 

9import click 1abcdefg

10import pytest 1abcdefg

11import typer 1abcdefg

12import typer._completion_shared 1abcdefg

13import typer.completion 1abcdefg

14from typer.core import _split_opt 1abcdefg

15from typer.main import solve_typer_info_defaults, solve_typer_info_help 1abcdefg

16from typer.models import ParameterInfo, TyperInfo 1abcdefg

17from typer.testing import CliRunner 1abcdefg

18 

19from .utils import requires_completion_permission 1abcdefg

20 

21runner = CliRunner() 1abcdefg

22 

23 

24def test_help_from_info(): 1abcdefg

25 # Mainly for coverage/completeness 

26 value = solve_typer_info_help(TyperInfo()) 1abcdefg

27 assert value is None 1abcdefg

28 

29 

30def test_defaults_from_info(): 1abcdefg

31 # Mainly for coverage/completeness 

32 value = solve_typer_info_defaults(TyperInfo()) 1abcdefg

33 assert value 1abcdefg

34 

35 

36def test_too_many_parsers(): 1abcdefg

37 def custom_parser(value: str) -> int: 1abcdefg

38 return int(value) # pragma: no cover 

39 

40 class CustomClickParser(click.ParamType): 1abcdefg

41 name = "custom_parser" 1abcdefg

42 

43 def convert( 1abcdefg

44 self, 

45 value: str, 

46 param: click.Parameter | None, 

47 ctx: click.Context | None, 

48 ) -> typing.Any: 

49 return int(value) # pragma: no cover 

50 

51 expected_error = ( 1abcdefg

52 "Multiple custom type parsers provided. " 

53 "`parser` and `click_type` may not both be provided." 

54 ) 

55 

56 with pytest.raises(ValueError, match=expected_error): 1abcdefg

57 ParameterInfo(parser=custom_parser, click_type=CustomClickParser()) 1abcdefg

58 

59 

60def test_valid_parser_permutations(): 1abcdefg

61 def custom_parser(value: str) -> int: 1abcdefg

62 return int(value) # pragma: no cover 

63 

64 class CustomClickParser(click.ParamType): 1abcdefg

65 name = "custom_parser" 1abcdefg

66 

67 def convert( 1abcdefg

68 self, 

69 value: str, 

70 param: click.Parameter | None, 

71 ctx: click.Context | None, 

72 ) -> typing.Any: 

73 return int(value) # pragma: no cover 

74 

75 ParameterInfo() 1abcdefg

76 ParameterInfo(parser=custom_parser) 1abcdefg

77 ParameterInfo(click_type=CustomClickParser()) 1abcdefg

78 

79 

80@requires_completion_permission 1abcdefg

81def test_install_invalid_shell(): 1abcdefg

82 app = typer.Typer() 1abcdefg

83 

84 @app.command() 1abcdefg

85 def main(): 1abcdefg

86 print("Hello World") 1abcdefg

87 

88 with mock.patch.object( 1abcdefg

89 typer._completion_shared, "_get_shell_name", return_value="xshell" 

90 ): 

91 result = runner.invoke(app, ["--install-completion"]) 1abcdefg

92 assert "Shell xshell is not supported." in result.stdout 1abcdefg

93 result = runner.invoke(app) 1abcdefg

94 assert "Hello World" in result.stdout 1abcdefg

95 

96 

97def test_callback_too_many_parameters(): 1abcdefg

98 app = typer.Typer() 1abcdefg

99 

100 def name_callback(ctx, param, val1, val2): 1abcdefg

101 pass # pragma: no cover 

102 

103 @app.command() 1abcdefg

104 def main(name: str = typer.Option(..., callback=name_callback)): 1abcdefg

105 pass # pragma: no cover 

106 

107 with pytest.raises(click.ClickException) as exc_info: 1abcdefg

108 runner.invoke(app, ["--name", "Camila"]) 1abcdefg

109 assert ( 1abcdefg

110 exc_info.value.message == "Too many CLI parameter callback function parameters" 

111 ) 

112 

113 

114def test_callback_2_untyped_parameters(): 1abcdefg

115 app = typer.Typer() 1abcdefg

116 

117 def name_callback(ctx, value): 1abcdefg

118 print(f"info name is: {ctx.info_name}") 1abcdefg

119 print(f"value is: {value}") 1abcdefg

120 

121 @app.command() 1abcdefg

122 def main(name: str = typer.Option(..., callback=name_callback)): 1abcdefg

123 print("Hello World") 1abcdefg

124 

125 result = runner.invoke(app, ["--name", "Camila"]) 1abcdefg

126 assert "info name is: main" in result.stdout 1abcdefg

127 assert "value is: Camila" in result.stdout 1abcdefg

128 

129 

130def test_callback_3_untyped_parameters(): 1abcdefg

131 app = typer.Typer() 1abcdefg

132 

133 def name_callback(ctx, param, value): 1abcdefg

134 print(f"info name is: {ctx.info_name}") 1abcdefg

135 print(f"param name is: {param.name}") 1abcdefg

136 print(f"value is: {value}") 1abcdefg

137 

138 @app.command() 1abcdefg

139 def main(name: str = typer.Option(..., callback=name_callback)): 1abcdefg

140 print("Hello World") 1abcdefg

141 

142 result = runner.invoke(app, ["--name", "Camila"]) 1abcdefg

143 assert "info name is: main" in result.stdout 1abcdefg

144 assert "param name is: name" in result.stdout 1abcdefg

145 assert "value is: Camila" in result.stdout 1abcdefg

146 

147 

148def test_callback_4_list_none(): 1abcdefg

149 app = typer.Typer() 1abcdefg

150 

151 def names_callback(ctx, param, values: list[str] | None): 1abcdefg

152 if values is None: 1abcdefg

153 return values 1abcdefg

154 return [value.upper() for value in values] 1abcdefg

155 

156 @app.command() 1abcdefg

157 def main( 1abcdefg

158 names: list[str] | None = typer.Option(None, "--name", callback=names_callback), 

159 ): 

160 if names is None: 1abcdefg

161 print("Hello World") 1abcdefg

162 else: 

163 print(f"Hello {', '.join(names)}") 1abcdefg

164 

165 result = runner.invoke(app, ["--name", "Sideshow", "--name", "Bob"]) 1abcdefg

166 assert "Hello SIDESHOW, BOB" in result.stdout 1abcdefg

167 

168 result = runner.invoke(app, []) 1abcdefg

169 assert "Hello World" in result.stdout 1abcdefg

170 

171 

172def test_empty_list_default_generator(): 1abcdefg

173 def empty_list() -> list[str]: 1abcdefg

174 return [] 1abcdefg

175 

176 app = typer.Typer() 1abcdefg

177 

178 @app.command() 1abcdefg

179 def main( 1abcdefg

180 names: Annotated[list[str], typer.Option(default_factory=empty_list)], 

181 ): 

182 print(names) 1abcdefg

183 

184 result = runner.invoke(app) 1abcdefg

185 assert "[]" in result.output 1abcdefg

186 

187 

188def test_completion_argument(): 1abcdefg

189 file_path = Path(__file__).parent / "assets/completion_argument.py" 1abcdefg

190 result = subprocess.run( 1abcdefg

191 [sys.executable, "-m", "coverage", "run", str(file_path), "E"], 

192 capture_output=True, 

193 encoding="utf-8", 

194 env={ 

195 **os.environ, 

196 "_COMPLETION_ARGUMENT.PY_COMPLETE": "complete_zsh", 

197 "_TYPER_COMPLETE_ARGS": "completion_argument.py E", 

198 "_TYPER_COMPLETE_TESTING": "True", 

199 }, 

200 ) 

201 assert "Emma" in result.stdout or "_files" in result.stdout 1abcdefg

202 assert "ctx: completion_argument" in result.stderr 1abcdefg

203 assert "arg is: name" in result.stderr 1abcdefg

204 assert "incomplete is: E" in result.stderr 1abcdefg

205 

206 

207def test_completion_untyped_parameters(): 1abcdefg

208 file_path = Path(__file__).parent / "assets/completion_no_types.py" 1abcdefg

209 result = subprocess.run( 1abcdefg

210 [sys.executable, "-m", "coverage", "run", str(file_path)], 

211 capture_output=True, 

212 encoding="utf-8", 

213 env={ 

214 **os.environ, 

215 "_COMPLETION_NO_TYPES.PY_COMPLETE": "complete_zsh", 

216 "_TYPER_COMPLETE_ARGS": "completion_no_types.py --name Sebastian --name Ca", 

217 }, 

218 ) 

219 assert "info name is: completion_no_types.py" in result.stderr 1abcdefg

220 assert "args is: []" in result.stderr 1abcdefg

221 assert "incomplete is: Ca" in result.stderr 1abcdefg

222 assert '"Camila":"The reader of books."' in result.stdout 1abcdefg

223 assert '"Carlos":"The writer of scripts."' in result.stdout 1abcdefg

224 

225 result = subprocess.run( 1abcdefg

226 [sys.executable, "-m", "coverage", "run", str(file_path)], 

227 capture_output=True, 

228 encoding="utf-8", 

229 ) 

230 assert "Hello World" in result.stdout 1abcdefg

231 

232 

233def test_completion_untyped_parameters_different_order_correct_names(): 1abcdefg

234 file_path = Path(__file__).parent / "assets/completion_no_types_order.py" 1abcdefg

235 result = subprocess.run( 1abcdefg

236 [sys.executable, "-m", "coverage", "run", str(file_path)], 

237 capture_output=True, 

238 encoding="utf-8", 

239 env={ 

240 **os.environ, 

241 "_COMPLETION_NO_TYPES_ORDER.PY_COMPLETE": "complete_zsh", 

242 "_TYPER_COMPLETE_ARGS": "completion_no_types_order.py --name Sebastian --name Ca", 

243 }, 

244 ) 

245 assert "info name is: completion_no_types_order.py" in result.stderr 1abcdefg

246 assert "args is: []" in result.stderr 1abcdefg

247 assert "incomplete is: Ca" in result.stderr 1abcdefg

248 assert '"Camila":"The reader of books."' in result.stdout 1abcdefg

249 assert '"Carlos":"The writer of scripts."' in result.stdout 1abcdefg

250 

251 result = subprocess.run( 1abcdefg

252 [sys.executable, "-m", "coverage", "run", str(file_path)], 

253 capture_output=True, 

254 encoding="utf-8", 

255 ) 

256 assert "Hello World" in result.stdout 1abcdefg

257 

258 

259def test_autocompletion_too_many_parameters(): 1abcdefg

260 app = typer.Typer() 1abcdefg

261 

262 def name_callback(ctx, args, incomplete, val2): 1abcdefg

263 pass # pragma: no cover 

264 

265 @app.command() 1abcdefg

266 def main(name: str = typer.Option(..., autocompletion=name_callback)): 1abcdefg

267 pass # pragma: no cover 

268 

269 with pytest.raises(click.ClickException) as exc_info: 1abcdefg

270 runner.invoke(app, ["--name", "Camila"]) 1abcdefg

271 assert exc_info.value.message == "Invalid autocompletion callback parameters: val2" 1abcdefg

272 

273 

274def test_forward_references(): 1abcdefg

275 app = typer.Typer() 1abcdefg

276 

277 @app.command() 1abcdefg

278 def main(arg1, arg2: int, arg3: "int", arg4: bool = False, arg5: "bool" = False): 1abcdefg

279 print(f"arg1: {type(arg1)} {arg1}") 1abcdefg

280 print(f"arg2: {type(arg2)} {arg2}") 1abcdefg

281 print(f"arg3: {type(arg3)} {arg3}") 1abcdefg

282 print(f"arg4: {type(arg4)} {arg4}") 1abcdefg

283 print(f"arg5: {type(arg5)} {arg5}") 1abcdefg

284 

285 result = runner.invoke(app, ["Hello", "2", "invalid"]) 1abcdefg

286 

287 assert "Invalid value for 'ARG3': 'invalid' is not a valid integer" in result.output 1abcdefg

288 result = runner.invoke(app, ["Hello", "2", "3", "--arg4", "--arg5"]) 1abcdefg

289 assert ( 1abcdefg

290 "arg1: <class 'str'> Hello\narg2: <class 'int'> 2\narg3: <class 'int'> 3\narg4: <class 'bool'> True\narg5: <class 'bool'> True\n" 

291 in result.stdout 

292 ) 

293 

294 

295def test_context_settings_inheritance_single_command(): 1abcdefg

296 app = typer.Typer(context_settings={"help_option_names": ["-h", "--help"]}) 1abcdefg

297 

298 @app.command() 1abcdefg

299 def main(name: str): 1abcdefg

300 pass # pragma: no cover 

301 

302 result = runner.invoke(app, ["main", "-h"]) 1abcdefg

303 assert "Show this message and exit." in result.stdout 1abcdefg

304 

305 

306def test_split_opt(): 1abcdefg

307 prefix, opt = _split_opt("--verbose") 1abcdefg

308 assert prefix == "--" 1abcdefg

309 assert opt == "verbose" 1abcdefg

310 

311 prefix, opt = _split_opt("//verbose") 1abcdefg

312 assert prefix == "//" 1abcdefg

313 assert opt == "verbose" 1abcdefg

314 

315 prefix, opt = _split_opt("-verbose") 1abcdefg

316 assert prefix == "-" 1abcdefg

317 assert opt == "verbose" 1abcdefg

318 

319 prefix, opt = _split_opt("verbose") 1abcdefg

320 assert prefix == "" 1abcdefg

321 assert opt == "verbose" 1abcdefg

322 

323 

324def test_options_metadata_typer_default(): 1abcdefg

325 app = typer.Typer(options_metavar="[options]") 1abcdefg

326 

327 @app.command() 1abcdefg

328 def c1(): 1abcdefg

329 pass # pragma: no cover 

330 

331 @app.command(options_metavar="[OPTS]") 1abcdefg

332 def c2(): 1abcdefg

333 pass # pragma: no cover 

334 

335 result = runner.invoke(app, ["c1", "--help"]) 1abcdefg

336 assert "Usage: root c1 [options]" in result.stdout 1abcdefg

337 

338 result = runner.invoke(app, ["c2", "--help"]) 1abcdefg

339 assert "Usage: root c2 [OPTS]" in result.stdout 1abcdefg