Coverage for tests / test_others.py: 100%

184 statements  

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

1import os 1abcdefgh

2import subprocess 1abcdefgh

3import sys 1abcdefgh

4import typing 1abcdefgh

5from pathlib import Path 1abcdefgh

6from typing import Annotated 1abcdefgh

7from unittest import mock 1abcdefgh

8 

9import click 1abcdefgh

10import pytest 1abcdefgh

11import typer 1abcdefgh

12import typer._completion_shared 1abcdefgh

13import typer.completion 1abcdefgh

14from typer.core import _split_opt 1abcdefgh

15from typer.main import solve_typer_info_defaults, solve_typer_info_help 1abcdefgh

16from typer.models import ParameterInfo, TyperInfo 1abcdefgh

17from typer.testing import CliRunner 1abcdefgh

18 

19from .utils import requires_completion_permission 1abcdefgh

20 

21runner = CliRunner() 1abcdefgh

22 

23 

24def test_help_from_info(): 1abcdefgh

25 # Mainly for coverage/completeness 

26 value = solve_typer_info_help(TyperInfo()) 1abcdefgh

27 assert value is None 1abcdefgh

28 

29 

30def test_defaults_from_info(): 1abcdefgh

31 # Mainly for coverage/completeness 

32 value = solve_typer_info_defaults(TyperInfo()) 1abcdefgh

33 assert value 1abcdefgh

34 

35 

36def test_too_many_parsers(): 1abcdefgh

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

38 return int(value) # pragma: no cover 

39 

40 class CustomClickParser(click.ParamType): 1abcdefgh

41 name = "custom_parser" 1abcdefgh

42 

43 def convert( 1abcdefgh

44 self, 

45 value: str, 

46 param: typing.Optional[click.Parameter], 

47 ctx: typing.Optional[click.Context], 

48 ) -> typing.Any: 

49 return int(value) # pragma: no cover 

50 

51 expected_error = ( 1abcdefgh

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

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

58 

59 

60def test_valid_parser_permutations(): 1abcdefgh

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

62 return int(value) # pragma: no cover 

63 

64 class CustomClickParser(click.ParamType): 1abcdefgh

65 name = "custom_parser" 1abcdefgh

66 

67 def convert( 1abcdefgh

68 self, 

69 value: str, 

70 param: typing.Optional[click.Parameter], 

71 ctx: typing.Optional[click.Context], 

72 ) -> typing.Any: 

73 return int(value) # pragma: no cover 

74 

75 ParameterInfo() 1abcdefgh

76 ParameterInfo(parser=custom_parser) 1abcdefgh

77 ParameterInfo(click_type=CustomClickParser()) 1abcdefgh

78 

79 

80@requires_completion_permission 1abcdefgh

81def test_install_invalid_shell(): 1abcdefgh

82 app = typer.Typer() 1abcdefgh

83 

84 @app.command() 1abcdefgh

85 def main(): 1abcdefgh

86 print("Hello World") 1abcdefgh

87 

88 with mock.patch.object( 1abcdefgh

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

90 ): 

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

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

93 result = runner.invoke(app) 1abcdefgh

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

95 

96 

97def test_callback_too_many_parameters(): 1abcdefgh

98 app = typer.Typer() 1abcdefgh

99 

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

101 pass # pragma: no cover 

102 

103 @app.command() 1abcdefgh

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

105 pass # pragma: no cover 

106 

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

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

109 assert ( 1abcdefgh

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

111 ) 

112 

113 

114def test_callback_2_untyped_parameters(): 1abcdefgh

115 app = typer.Typer() 1abcdefgh

116 

117 def name_callback(ctx, value): 1abcdefgh

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

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

120 

121 @app.command() 1abcdefgh

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

123 print("Hello World") 1abcdefgh

124 

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

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

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

128 

129 

130def test_callback_3_untyped_parameters(): 1abcdefgh

131 app = typer.Typer() 1abcdefgh

132 

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

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

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

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

137 

138 @app.command() 1abcdefgh

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

140 print("Hello World") 1abcdefgh

141 

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

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

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

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

146 

147 

148def test_callback_4_list_none(): 1abcdefgh

149 app = typer.Typer() 1abcdefgh

150 

151 def names_callback(ctx, param, values: typing.Optional[list[str]]): 1abcdefgh

152 if values is None: 1abcdefgh

153 return values 1abcdefgh

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

155 

156 @app.command() 1abcdefgh

157 def main( 1abcdefgh

158 names: typing.Optional[list[str]] = typer.Option( 

159 None, "--name", callback=names_callback 

160 ), 

161 ): 

162 if names is None: 1abcdefgh

163 print("Hello World") 1abcdefgh

164 else: 

165 print(f"Hello {', '.join(names)}") 1abcdefgh

166 

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

168 assert "Hello SIDESHOW, BOB" in result.stdout 1abcdefgh

169 

170 result = runner.invoke(app, []) 1abcdefgh

171 assert "Hello World" in result.stdout 1abcdefgh

172 

173 

174def test_empty_list_default_generator(): 1abcdefgh

175 def empty_list() -> list[str]: 1abcdefgh

176 return [] 1abcdefgh

177 

178 app = typer.Typer() 1abcdefgh

179 

180 @app.command() 1abcdefgh

181 def main( 1abcdefgh

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

183 ): 

184 print(names) 1abcdefgh

185 

186 result = runner.invoke(app) 1abcdefgh

187 assert "[]" in result.output 1abcdefgh

188 

189 

190def test_completion_argument(): 1abcdefgh

191 file_path = Path(__file__).parent / "assets/completion_argument.py" 1abcdefgh

192 result = subprocess.run( 1abcdefgh

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

194 capture_output=True, 

195 encoding="utf-8", 

196 env={ 

197 **os.environ, 

198 "_COMPLETION_ARGUMENT.PY_COMPLETE": "complete_zsh", 

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

200 "_TYPER_COMPLETE_TESTING": "True", 

201 }, 

202 ) 

203 assert "Emma" in result.stdout or "_files" in result.stdout 1abcdefgh

204 assert "ctx: completion_argument" in result.stderr 1abcdefgh

205 assert "arg is: name" in result.stderr 1abcdefgh

206 assert "incomplete is: E" in result.stderr 1abcdefgh

207 

208 

209def test_completion_untyped_parameters(): 1abcdefgh

210 file_path = Path(__file__).parent / "assets/completion_no_types.py" 1abcdefgh

211 result = subprocess.run( 1abcdefgh

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

213 capture_output=True, 

214 encoding="utf-8", 

215 env={ 

216 **os.environ, 

217 "_COMPLETION_NO_TYPES.PY_COMPLETE": "complete_zsh", 

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

219 }, 

220 ) 

221 assert "info name is: completion_no_types.py" in result.stderr 1abcdefgh

222 assert "args is: []" in result.stderr 1abcdefgh

223 assert "incomplete is: Ca" in result.stderr 1abcdefgh

224 assert '"Camila":"The reader of books."' in result.stdout 1abcdefgh

225 assert '"Carlos":"The writer of scripts."' in result.stdout 1abcdefgh

226 

227 result = subprocess.run( 1abcdefgh

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

229 capture_output=True, 

230 encoding="utf-8", 

231 ) 

232 assert "Hello World" in result.stdout 1abcdefgh

233 

234 

235def test_completion_untyped_parameters_different_order_correct_names(): 1abcdefgh

236 file_path = Path(__file__).parent / "assets/completion_no_types_order.py" 1abcdefgh

237 result = subprocess.run( 1abcdefgh

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

239 capture_output=True, 

240 encoding="utf-8", 

241 env={ 

242 **os.environ, 

243 "_COMPLETION_NO_TYPES_ORDER.PY_COMPLETE": "complete_zsh", 

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

245 }, 

246 ) 

247 assert "info name is: completion_no_types_order.py" in result.stderr 1abcdefgh

248 assert "args is: []" in result.stderr 1abcdefgh

249 assert "incomplete is: Ca" in result.stderr 1abcdefgh

250 assert '"Camila":"The reader of books."' in result.stdout 1abcdefgh

251 assert '"Carlos":"The writer of scripts."' in result.stdout 1abcdefgh

252 

253 result = subprocess.run( 1abcdefgh

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

255 capture_output=True, 

256 encoding="utf-8", 

257 ) 

258 assert "Hello World" in result.stdout 1abcdefgh

259 

260 

261def test_autocompletion_too_many_parameters(): 1abcdefgh

262 app = typer.Typer() 1abcdefgh

263 

264 def name_callback(ctx, args, incomplete, val2): 1abcdefgh

265 pass # pragma: no cover 

266 

267 @app.command() 1abcdefgh

268 def main(name: str = typer.Option(..., autocompletion=name_callback)): 1abcdefgh

269 pass # pragma: no cover 

270 

271 with pytest.raises(click.ClickException) as exc_info: 1abcdefgh

272 runner.invoke(app, ["--name", "Camila"]) 1abcdefgh

273 assert exc_info.value.message == "Invalid autocompletion callback parameters: val2" 1abcdefgh

274 

275 

276def test_forward_references(): 1abcdefgh

277 app = typer.Typer() 1abcdefgh

278 

279 @app.command() 1abcdefgh

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

281 print(f"arg1: {type(arg1)} {arg1}") 1abcdefgh

282 print(f"arg2: {type(arg2)} {arg2}") 1abcdefgh

283 print(f"arg3: {type(arg3)} {arg3}") 1abcdefgh

284 print(f"arg4: {type(arg4)} {arg4}") 1abcdefgh

285 print(f"arg5: {type(arg5)} {arg5}") 1abcdefgh

286 

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

288 

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

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

291 assert ( 1abcdefgh

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

293 in result.stdout 

294 ) 

295 

296 

297def test_context_settings_inheritance_single_command(): 1abcdefgh

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

299 

300 @app.command() 1abcdefgh

301 def main(name: str): 1abcdefgh

302 pass # pragma: no cover 

303 

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

305 assert "Show this message and exit." in result.stdout 1abcdefgh

306 

307 

308def test_split_opt(): 1abcdefgh

309 prefix, opt = _split_opt("--verbose") 1abcdefgh

310 assert prefix == "--" 1abcdefgh

311 assert opt == "verbose" 1abcdefgh

312 

313 prefix, opt = _split_opt("//verbose") 1abcdefgh

314 assert prefix == "//" 1abcdefgh

315 assert opt == "verbose" 1abcdefgh

316 

317 prefix, opt = _split_opt("-verbose") 1abcdefgh

318 assert prefix == "-" 1abcdefgh

319 assert opt == "verbose" 1abcdefgh

320 

321 prefix, opt = _split_opt("verbose") 1abcdefgh

322 assert prefix == "" 1abcdefgh

323 assert opt == "verbose" 1abcdefgh

324 

325 

326def test_options_metadata_typer_default(): 1abcdefgh

327 app = typer.Typer(options_metavar="[options]") 1abcdefgh

328 

329 @app.command() 1abcdefgh

330 def c1(): 1abcdefgh

331 pass # pragma: no cover 

332 

333 @app.command(options_metavar="[OPTS]") 1abcdefgh

334 def c2(): 1abcdefgh

335 pass # pragma: no cover 

336 

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

338 assert "Usage: root c1 [options]" in result.stdout 1abcdefgh

339 

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

341 assert "Usage: root c2 [OPTS]" in result.stdout 1abcdefgh