Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/click/decorators.py: 23%

210 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-09 06:03 +0000

1from __future__ import annotations 

2 

3import inspect 

4import typing as t 

5from functools import update_wrapper 

6from gettext import gettext as _ 

7 

8from .core import Argument 

9from .core import Command 

10from .core import Context 

11from .core import Group 

12from .core import Option 

13from .core import Parameter 

14from .globals import get_current_context 

15from .utils import echo 

16 

17if t.TYPE_CHECKING: 

18 import typing_extensions as te 

19 

20 P = te.ParamSpec("P") 

21 

22R = t.TypeVar("R") 

23T = t.TypeVar("T") 

24_AnyCallable = t.Callable[..., t.Any] 

25FC = t.TypeVar("FC", bound="_AnyCallable | Command") 

26 

27 

28def pass_context(f: t.Callable[te.Concatenate[Context, P], R]) -> t.Callable[P, R]: 

29 """Marks a callback as wanting to receive the current context 

30 object as first argument. 

31 """ 

32 

33 def new_func(*args: P.args, **kwargs: P.kwargs) -> R: 

34 return f(get_current_context(), *args, **kwargs) 

35 

36 return update_wrapper(new_func, f) 

37 

38 

39def pass_obj(f: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]: 

40 """Similar to :func:`pass_context`, but only pass the object on the 

41 context onwards (:attr:`Context.obj`). This is useful if that object 

42 represents the state of a nested system. 

43 """ 

44 

45 def new_func(*args: P.args, **kwargs: P.kwargs) -> R: 

46 return f(get_current_context().obj, *args, **kwargs) 

47 

48 return update_wrapper(new_func, f) 

49 

50 

51def make_pass_decorator( 

52 object_type: type[T], ensure: bool = False 

53) -> t.Callable[[t.Callable[te.Concatenate[T, P], R]], t.Callable[P, R]]: 

54 """Given an object type this creates a decorator that will work 

55 similar to :func:`pass_obj` but instead of passing the object of the 

56 current context, it will find the innermost context of type 

57 :func:`object_type`. 

58 

59 This generates a decorator that works roughly like this:: 

60 

61 from functools import update_wrapper 

62 

63 def decorator(f): 

64 @pass_context 

65 def new_func(ctx, *args, **kwargs): 

66 obj = ctx.find_object(object_type) 

67 return ctx.invoke(f, obj, *args, **kwargs) 

68 return update_wrapper(new_func, f) 

69 return decorator 

70 

71 :param object_type: the type of the object to pass. 

72 :param ensure: if set to `True`, a new object will be created and 

73 remembered on the context if it's not there yet. 

74 """ 

75 

76 def decorator(f: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]: 

77 def new_func(*args: P.args, **kwargs: P.kwargs) -> R: 

78 ctx = get_current_context() 

79 

80 obj: T | None 

81 if ensure: 

82 obj = ctx.ensure_object(object_type) 

83 else: 

84 obj = ctx.find_object(object_type) 

85 

86 if obj is None: 

87 raise RuntimeError( 

88 "Managed to invoke callback without a context" 

89 f" object of type {object_type.__name__!r}" 

90 " existing." 

91 ) 

92 

93 return ctx.invoke(f, obj, *args, **kwargs) 

94 

95 return update_wrapper(new_func, f) 

96 

97 return decorator # type: ignore[return-value] 

98 

99 

100def pass_meta_key( 

101 key: str, *, doc_description: str | None = None 

102) -> t.Callable[[t.Callable[te.Concatenate[T, P], R]], t.Callable[P, R]]: 

103 """Create a decorator that passes a key from 

104 :attr:`click.Context.meta` as the first argument to the decorated 

105 function. 

106 

107 :param key: Key in ``Context.meta`` to pass. 

108 :param doc_description: Description of the object being passed, 

109 inserted into the decorator's docstring. Defaults to "the 'key' 

110 key from Context.meta". 

111 

112 .. versionadded:: 8.0 

113 """ 

114 

115 def decorator(f: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]: 

116 def new_func(*args: P.args, **kwargs: P.kwargs) -> R: 

117 ctx = get_current_context() 

118 obj = ctx.meta[key] 

119 return ctx.invoke(f, obj, *args, **kwargs) 

120 

121 return update_wrapper(new_func, f) 

122 

123 if doc_description is None: 

124 doc_description = f"the {key!r} key from :attr:`click.Context.meta`" 

125 

126 decorator.__doc__ = ( 

127 f"Decorator that passes {doc_description} as the first argument" 

128 " to the decorated function." 

129 ) 

130 return decorator # type: ignore[return-value] 

131 

132 

133CmdType = t.TypeVar("CmdType", bound=Command) 

134 

135 

136# variant: no call, directly as decorator for a function. 

137@t.overload 

138def command(name: _AnyCallable) -> Command: 

139 ... 

140 

141 

142# variant: with positional name and with positional or keyword cls argument: 

143# @command(namearg, CommandCls, ...) or @command(namearg, cls=CommandCls, ...) 

144@t.overload 

145def command( 

146 name: str | None, 

147 cls: type[CmdType], 

148 **attrs: t.Any, 

149) -> t.Callable[[_AnyCallable], CmdType]: 

150 ... 

151 

152 

153# variant: name omitted, cls _must_ be a keyword argument, @command(cls=CommandCls, ...) 

154@t.overload 

155def command( 

156 name: None = None, 

157 *, 

158 cls: type[CmdType], 

159 **attrs: t.Any, 

160) -> t.Callable[[_AnyCallable], CmdType]: 

161 ... 

162 

163 

164# variant: with optional string name, no cls argument provided. 

165@t.overload 

166def command( 

167 name: str | None = ..., cls: None = None, **attrs: t.Any 

168) -> t.Callable[[_AnyCallable], Command]: 

169 ... 

170 

171 

172def command( 

173 name: str | _AnyCallable | None = None, 

174 cls: type[CmdType] | None = None, 

175 **attrs: t.Any, 

176) -> Command | t.Callable[[_AnyCallable], Command | CmdType]: 

177 r"""Creates a new :class:`Command` and uses the decorated function as 

178 callback. This will also automatically attach all decorated 

179 :func:`option`\s and :func:`argument`\s as parameters to the command. 

180 

181 The name of the command defaults to the name of the function, converted to 

182 lowercase, with underscores ``_`` replaced by dashes ``-``, and the suffixes 

183 ``_command``, ``_cmd``, ``_group``, and ``_grp`` are removed. For example, 

184 ``init_data_command`` becomes ``init-data``. 

185 

186 All keyword arguments are forwarded to the underlying command class. 

187 For the ``params`` argument, any decorated params are appended to 

188 the end of the list. 

189 

190 Once decorated the function turns into a :class:`Command` instance 

191 that can be invoked as a command line utility or be attached to a 

192 command :class:`Group`. 

193 

194 :param name: The name of the command. Defaults to modifying the function's 

195 name as described above. 

196 :param cls: The command class to create. Defaults to :class:`Command`. 

197 

198 .. versionchanged:: 8.2 

199 The suffixes ``_command``, ``_cmd``, ``_group``, and ``_grp`` are 

200 removed when generating the name. 

201 

202 .. versionchanged:: 8.1 

203 This decorator can be applied without parentheses. 

204 

205 .. versionchanged:: 8.1 

206 The ``params`` argument can be used. Decorated params are 

207 appended to the end of the list. 

208 """ 

209 

210 func: t.Callable[[_AnyCallable], t.Any] | None = None 

211 

212 if callable(name): 

213 func = name 

214 name = None 

215 assert cls is None, "Use 'command(cls=cls)(callable)' to specify a class." 

216 assert not attrs, "Use 'command(**kwargs)(callable)' to provide arguments." 

217 

218 if cls is None: 

219 cls = t.cast("type[CmdType]", Command) 

220 

221 def decorator(f: _AnyCallable) -> CmdType: 

222 if isinstance(f, Command): 

223 raise TypeError("Attempted to convert a callback into a command twice.") 

224 

225 attr_params = attrs.pop("params", None) 

226 params = attr_params if attr_params is not None else [] 

227 

228 try: 

229 decorator_params = f.__click_params__ # type: ignore 

230 except AttributeError: 

231 pass 

232 else: 

233 del f.__click_params__ # type: ignore 

234 params.extend(reversed(decorator_params)) 

235 

236 if attrs.get("help") is None: 

237 attrs["help"] = f.__doc__ 

238 

239 if t.TYPE_CHECKING: 

240 assert cls is not None 

241 assert not callable(name) 

242 

243 if name is not None: 

244 cmd_name = name 

245 else: 

246 cmd_name = f.__name__.lower().replace("_", "-") 

247 cmd_left, sep, suffix = cmd_name.rpartition("-") 

248 

249 if sep and suffix in {"command", "cmd", "group", "grp"}: 

250 cmd_name = cmd_left 

251 

252 cmd = cls(name=cmd_name, callback=f, params=params, **attrs) 

253 cmd.__doc__ = f.__doc__ 

254 return cmd 

255 

256 if func is not None: 

257 return decorator(func) 

258 

259 return decorator 

260 

261 

262GrpType = t.TypeVar("GrpType", bound=Group) 

263 

264 

265# variant: no call, directly as decorator for a function. 

266@t.overload 

267def group(name: _AnyCallable) -> Group: 

268 ... 

269 

270 

271# variant: with positional name and with positional or keyword cls argument: 

272# @group(namearg, GroupCls, ...) or @group(namearg, cls=GroupCls, ...) 

273@t.overload 

274def group( 

275 name: str | None, 

276 cls: type[GrpType], 

277 **attrs: t.Any, 

278) -> t.Callable[[_AnyCallable], GrpType]: 

279 ... 

280 

281 

282# variant: name omitted, cls _must_ be a keyword argument, @group(cmd=GroupCls, ...) 

283@t.overload 

284def group( 

285 name: None = None, 

286 *, 

287 cls: type[GrpType], 

288 **attrs: t.Any, 

289) -> t.Callable[[_AnyCallable], GrpType]: 

290 ... 

291 

292 

293# variant: with optional string name, no cls argument provided. 

294@t.overload 

295def group( 

296 name: str | None = ..., cls: None = None, **attrs: t.Any 

297) -> t.Callable[[_AnyCallable], Group]: 

298 ... 

299 

300 

301def group( 

302 name: str | _AnyCallable | None = None, 

303 cls: type[GrpType] | None = None, 

304 **attrs: t.Any, 

305) -> Group | t.Callable[[_AnyCallable], Group | GrpType]: 

306 """Creates a new :class:`Group` with a function as callback. This 

307 works otherwise the same as :func:`command` just that the `cls` 

308 parameter is set to :class:`Group`. 

309 

310 .. versionchanged:: 8.1 

311 This decorator can be applied without parentheses. 

312 """ 

313 if cls is None: 

314 cls = t.cast("type[GrpType]", Group) 

315 

316 if callable(name): 

317 return command(cls=cls, **attrs)(name) 

318 

319 return command(name, cls, **attrs) 

320 

321 

322def _param_memo(f: t.Callable[..., t.Any], param: Parameter) -> None: 

323 if isinstance(f, Command): 

324 f.params.append(param) 

325 else: 

326 if not hasattr(f, "__click_params__"): 

327 f.__click_params__ = [] # type: ignore 

328 

329 f.__click_params__.append(param) # type: ignore 

330 

331 

332def argument( 

333 *param_decls: str, cls: type[Argument] | None = None, **attrs: t.Any 

334) -> t.Callable[[FC], FC]: 

335 """Attaches an argument to the command. All positional arguments are 

336 passed as parameter declarations to :class:`Argument`; all keyword 

337 arguments are forwarded unchanged (except ``cls``). 

338 This is equivalent to creating an :class:`Argument` instance manually 

339 and attaching it to the :attr:`Command.params` list. 

340 

341 For the default argument class, refer to :class:`Argument` and 

342 :class:`Parameter` for descriptions of parameters. 

343 

344 :param cls: the argument class to instantiate. This defaults to 

345 :class:`Argument`. 

346 :param param_decls: Passed as positional arguments to the constructor of 

347 ``cls``. 

348 :param attrs: Passed as keyword arguments to the constructor of ``cls``. 

349 """ 

350 if cls is None: 

351 cls = Argument 

352 

353 def decorator(f: FC) -> FC: 

354 _param_memo(f, cls(param_decls, **attrs)) 

355 return f 

356 

357 return decorator 

358 

359 

360def option( 

361 *param_decls: str, cls: type[Option] | None = None, **attrs: t.Any 

362) -> t.Callable[[FC], FC]: 

363 """Attaches an option to the command. All positional arguments are 

364 passed as parameter declarations to :class:`Option`; all keyword 

365 arguments are forwarded unchanged (except ``cls``). 

366 This is equivalent to creating an :class:`Option` instance manually 

367 and attaching it to the :attr:`Command.params` list. 

368 

369 For the default option class, refer to :class:`Option` and 

370 :class:`Parameter` for descriptions of parameters. 

371 

372 :param cls: the option class to instantiate. This defaults to 

373 :class:`Option`. 

374 :param param_decls: Passed as positional arguments to the constructor of 

375 ``cls``. 

376 :param attrs: Passed as keyword arguments to the constructor of ``cls``. 

377 """ 

378 if cls is None: 

379 cls = Option 

380 

381 def decorator(f: FC) -> FC: 

382 _param_memo(f, cls(param_decls, **attrs)) 

383 return f 

384 

385 return decorator 

386 

387 

388def confirmation_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: 

389 """Add a ``--yes`` option which shows a prompt before continuing if 

390 not passed. If the prompt is declined, the program will exit. 

391 

392 :param param_decls: One or more option names. Defaults to the single 

393 value ``"--yes"``. 

394 :param kwargs: Extra arguments are passed to :func:`option`. 

395 """ 

396 

397 def callback(ctx: Context, param: Parameter, value: bool) -> None: 

398 if not value: 

399 ctx.abort() 

400 

401 if not param_decls: 

402 param_decls = ("--yes",) 

403 

404 kwargs.setdefault("is_flag", True) 

405 kwargs.setdefault("callback", callback) 

406 kwargs.setdefault("expose_value", False) 

407 kwargs.setdefault("prompt", "Do you want to continue?") 

408 kwargs.setdefault("help", "Confirm the action without prompting.") 

409 return option(*param_decls, **kwargs) 

410 

411 

412def password_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: 

413 """Add a ``--password`` option which prompts for a password, hiding 

414 input and asking to enter the value again for confirmation. 

415 

416 :param param_decls: One or more option names. Defaults to the single 

417 value ``"--password"``. 

418 :param kwargs: Extra arguments are passed to :func:`option`. 

419 """ 

420 if not param_decls: 

421 param_decls = ("--password",) 

422 

423 kwargs.setdefault("prompt", True) 

424 kwargs.setdefault("confirmation_prompt", True) 

425 kwargs.setdefault("hide_input", True) 

426 return option(*param_decls, **kwargs) 

427 

428 

429def version_option( 

430 version: str | None = None, 

431 *param_decls: str, 

432 package_name: str | None = None, 

433 prog_name: str | None = None, 

434 message: str | None = None, 

435 **kwargs: t.Any, 

436) -> t.Callable[[FC], FC]: 

437 """Add a ``--version`` option which immediately prints the version 

438 number and exits the program. 

439 

440 If ``version`` is not provided, Click will try to detect it using 

441 :func:`importlib.metadata.version` to get the version for the 

442 ``package_name``. 

443 

444 If ``package_name`` is not provided, Click will try to detect it by 

445 inspecting the stack frames. This will be used to detect the 

446 version, so it must match the name of the installed package. 

447 

448 :param version: The version number to show. If not provided, Click 

449 will try to detect it. 

450 :param param_decls: One or more option names. Defaults to the single 

451 value ``"--version"``. 

452 :param package_name: The package name to detect the version from. If 

453 not provided, Click will try to detect it. 

454 :param prog_name: The name of the CLI to show in the message. If not 

455 provided, it will be detected from the command. 

456 :param message: The message to show. The values ``%(prog)s``, 

457 ``%(package)s``, and ``%(version)s`` are available. Defaults to 

458 ``"%(prog)s, version %(version)s"``. 

459 :param kwargs: Extra arguments are passed to :func:`option`. 

460 :raise RuntimeError: ``version`` could not be detected. 

461 

462 .. versionchanged:: 8.0 

463 Add the ``package_name`` parameter, and the ``%(package)s`` 

464 value for messages. 

465 

466 .. versionchanged:: 8.0 

467 Use :mod:`importlib.metadata` instead of ``pkg_resources``. The 

468 version is detected based on the package name, not the entry 

469 point name. The Python package name must match the installed 

470 package name, or be passed with ``package_name=``. 

471 """ 

472 if message is None: 

473 message = _("%(prog)s, version %(version)s") 

474 

475 if version is None and package_name is None: 

476 frame = inspect.currentframe() 

477 f_back = frame.f_back if frame is not None else None 

478 f_globals = f_back.f_globals if f_back is not None else None 

479 # break reference cycle 

480 # https://docs.python.org/3/library/inspect.html#the-interpreter-stack 

481 del frame 

482 

483 if f_globals is not None: 

484 package_name = f_globals.get("__name__") 

485 

486 if package_name == "__main__": 

487 package_name = f_globals.get("__package__") 

488 

489 if package_name: 

490 package_name = package_name.partition(".")[0] 

491 

492 def callback(ctx: Context, param: Parameter, value: bool) -> None: 

493 if not value or ctx.resilient_parsing: 

494 return 

495 

496 nonlocal prog_name 

497 nonlocal version 

498 

499 if prog_name is None: 

500 prog_name = ctx.find_root().info_name 

501 

502 if version is None and package_name is not None: 

503 import importlib.metadata 

504 

505 try: 

506 version = importlib.metadata.version(package_name) 

507 except importlib.metadata.PackageNotFoundError: 

508 raise RuntimeError( 

509 f"{package_name!r} is not installed. Try passing" 

510 " 'package_name' instead." 

511 ) from None 

512 

513 if version is None: 

514 raise RuntimeError( 

515 f"Could not determine the version for {package_name!r} automatically." 

516 ) 

517 

518 echo( 

519 message % {"prog": prog_name, "package": package_name, "version": version}, 

520 color=ctx.color, 

521 ) 

522 ctx.exit() 

523 

524 if not param_decls: 

525 param_decls = ("--version",) 

526 

527 kwargs.setdefault("is_flag", True) 

528 kwargs.setdefault("expose_value", False) 

529 kwargs.setdefault("is_eager", True) 

530 kwargs.setdefault("help", _("Show the version and exit.")) 

531 kwargs["callback"] = callback 

532 return option(*param_decls, **kwargs) 

533 

534 

535def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: 

536 """Add a ``--help`` option which immediately prints the help page 

537 and exits the program. 

538 

539 This is usually unnecessary, as the ``--help`` option is added to 

540 each command automatically unless ``add_help_option=False`` is 

541 passed. 

542 

543 :param param_decls: One or more option names. Defaults to the single 

544 value ``"--help"``. 

545 :param kwargs: Extra arguments are passed to :func:`option`. 

546 """ 

547 

548 def callback(ctx: Context, param: Parameter, value: bool) -> None: 

549 if not value or ctx.resilient_parsing: 

550 return 

551 

552 echo(ctx.get_help(), color=ctx.color) 

553 ctx.exit() 

554 

555 if not param_decls: 

556 param_decls = ("--help",) 

557 

558 kwargs.setdefault("is_flag", True) 

559 kwargs.setdefault("expose_value", False) 

560 kwargs.setdefault("is_eager", True) 

561 kwargs.setdefault("help", _("Show this message and exit.")) 

562 kwargs["callback"] = callback 

563 return option(*param_decls, **kwargs)