Coverage for tests/test_others.py: 100%

184 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-12-19 20:29 +0000

1import os 1abcdefghi

2import subprocess 1abcdefghi

3import sys 1abcdefghi

4import typing 1abcdefghi

5from pathlib import Path 1abcdefghi

6from unittest import mock 1abcdefghi

7 

8import click 1abcdefghi

9import pytest 1abcdefghi

10import typer 1abcdefghi

11import typer._completion_shared 1abcdefghi

12import typer.completion 1abcdefghi

13from typer.core import _split_opt 1abcdefghi

14from typer.main import solve_typer_info_defaults, solve_typer_info_help 1abcdefghi

15from typer.models import ParameterInfo, TyperInfo 1abcdefghi

16from typer.testing import CliRunner 1abcdefghi

17from typing_extensions import Annotated 1abcdefghi

18 

19from .utils import requires_completion_permission 1abcdefghi

20 

21runner = CliRunner() 1abcdefghi

22 

23 

24def test_help_from_info(): 1abcdefghi

25 # Mainly for coverage/completeness 

26 value = solve_typer_info_help(TyperInfo()) 1abcdefghi

27 assert value is None 1abcdefghi

28 

29 

30def test_defaults_from_info(): 1abcdefghi

31 # Mainly for coverage/completeness 

32 value = solve_typer_info_defaults(TyperInfo()) 1abcdefghi

33 assert value 1abcdefghi

34 

35 

36def test_too_many_parsers(): 1abcdefghi

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

38 return int(value) # pragma: no cover 

39 

40 class CustomClickParser(click.ParamType): 1abcdefghi

41 name = "custom_parser" 1abcdefghi

42 

43 def convert( 1abcdefghi

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 = ( 1abcdefghi

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

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

58 

59 

60def test_valid_parser_permutations(): 1abcdefghi

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

62 return int(value) # pragma: no cover 

63 

64 class CustomClickParser(click.ParamType): 1abcdefghi

65 name = "custom_parser" 1abcdefghi

66 

67 def convert( 1abcdefghi

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() 1abcdefghi

76 ParameterInfo(parser=custom_parser) 1abcdefghi

77 ParameterInfo(click_type=CustomClickParser()) 1abcdefghi

78 

79 

80@requires_completion_permission 1abcdefghi

81def test_install_invalid_shell(): 1abcdefghi

82 app = typer.Typer() 1abcdefghi

83 

84 @app.command() 1abcdefghi

85 def main(): 1abcdefghi

86 print("Hello World") 1abcdefghi

87 

88 with mock.patch.object( 1abcdefghi

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

90 ): 

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

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

93 result = runner.invoke(app) 1abcdefghi

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

95 

96 

97def test_callback_too_many_parameters(): 1abcdefghi

98 app = typer.Typer() 1abcdefghi

99 

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

101 pass # pragma: no cover 

102 

103 @app.command() 1abcdefghi

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

105 pass # pragma: no cover 

106 

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

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

109 assert ( 1abcdefghi

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

111 ) 

112 

113 

114def test_callback_2_untyped_parameters(): 1abcdefghi

115 app = typer.Typer() 1abcdefghi

116 

117 def name_callback(ctx, value): 1abcdefghi

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

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

120 

121 @app.command() 1abcdefghi

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

123 print("Hello World") 1abcdefghi

124 

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

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

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

128 

129 

130def test_callback_3_untyped_parameters(): 1abcdefghi

131 app = typer.Typer() 1abcdefghi

132 

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

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

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

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

137 

138 @app.command() 1abcdefghi

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

140 print("Hello World") 1abcdefghi

141 

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

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

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

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

146 

147 

148def test_callback_4_list_none(): 1abcdefghi

149 app = typer.Typer() 1abcdefghi

150 

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

152 if values is None: 1abcdefghi

153 return values 1abcdefghi

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

155 

156 @app.command() 1abcdefghi

157 def main( 1abcdefghi

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

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

160 ), 

161 ): 

162 if names is None: 1abcdefghi

163 print("Hello World") 1abcdefghi

164 else: 

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

166 

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

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

169 

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

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

172 

173 

174def test_empty_list_default_generator(): 1abcdefghi

175 def empty_list() -> typing.List[str]: 1abcdefghi

176 return [] 1abcdefghi

177 

178 app = typer.Typer() 1abcdefghi

179 

180 @app.command() 1abcdefghi

181 def main( 1abcdefghi

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

183 ): 

184 print(names) 1abcdefghi

185 

186 result = runner.invoke(app) 1abcdefghi

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

188 

189 

190def test_completion_argument(): 1abcdefghi

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

192 result = subprocess.run( 1abcdefghi

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 1abcdefghi

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

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

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

207 

208 

209def test_completion_untyped_parameters(): 1abcdefghi

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

211 result = subprocess.run( 1abcdefghi

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 1abcdefghi

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

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

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

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

226 

227 result = subprocess.run( 1abcdefghi

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 1abcdefghi

233 

234 

235def test_completion_untyped_parameters_different_order_correct_names(): 1abcdefghi

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

237 result = subprocess.run( 1abcdefghi

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 1abcdefghi

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

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

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

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

252 

253 result = subprocess.run( 1abcdefghi

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 1abcdefghi

259 

260 

261def test_autocompletion_too_many_parameters(): 1abcdefghi

262 app = typer.Typer() 1abcdefghi

263 

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

265 pass # pragma: no cover 

266 

267 @app.command() 1abcdefghi

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

269 pass # pragma: no cover 

270 

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

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

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

274 

275 

276def test_forward_references(): 1abcdefghi

277 app = typer.Typer() 1abcdefghi

278 

279 @app.command() 1abcdefghi

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

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

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

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

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

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

286 

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

288 

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

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

291 assert ( 1abcdefghi

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

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

299 

300 @app.command() 1abcdefghi

301 def main(name: str): 1abcdefghi

302 pass # pragma: no cover 

303 

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

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

306 

307 

308def test_split_opt(): 1abcdefghi

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

310 assert prefix == "--" 1abcdefghi

311 assert opt == "verbose" 1abcdefghi

312 

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

314 assert prefix == "//" 1abcdefghi

315 assert opt == "verbose" 1abcdefghi

316 

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

318 assert prefix == "-" 1abcdefghi

319 assert opt == "verbose" 1abcdefghi

320 

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

322 assert prefix == "" 1abcdefghi

323 assert opt == "verbose" 1abcdefghi

324 

325 

326def test_options_metadata_typer_default(): 1abcdefghi

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

328 

329 @app.command() 1abcdefghi

330 def c1(): 1abcdefghi

331 pass # pragma: no cover 

332 

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

334 def c2(): 1abcdefghi

335 pass # pragma: no cover 

336 

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

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

339 

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

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