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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

221 statements  

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 

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 

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# variant: with positional name and with positional or keyword cls argument: 

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

143@t.overload 

144def command( 

145 name: str | None, 

146 cls: type[CmdType], 

147 **attrs: t.Any, 

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

149 

150 

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

152@t.overload 

153def command( 

154 name: None = None, 

155 *, 

156 cls: type[CmdType], 

157 **attrs: t.Any, 

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

159 

160 

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

162@t.overload 

163def command( 

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

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

166 

167 

168def command( 

169 name: str | _AnyCallable | None = None, 

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

171 **attrs: t.Any, 

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

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

174 callback. This will also automatically attach all decorated 

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

176 

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

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

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

180 ``init_data_command`` becomes ``init-data``. 

181 

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

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

184 the end of the list. 

185 

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

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

188 command :class:`Group`. 

189 

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

191 name as described above. 

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

193 

194 .. versionchanged:: 8.2 

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

196 removed when generating the name. 

197 

198 .. versionchanged:: 8.1 

199 This decorator can be applied without parentheses. 

200 

201 .. versionchanged:: 8.1 

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

203 appended to the end of the list. 

204 """ 

205 

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

207 

208 if callable(name): 

209 func = name 

210 name = None 

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

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

213 

214 if cls is None: 

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

216 

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

218 if isinstance(f, Command): 

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

220 

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

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

223 

224 try: 

225 decorator_params = f.__click_params__ # type: ignore 

226 except AttributeError: 

227 pass 

228 else: 

229 del f.__click_params__ # type: ignore 

230 params.extend(reversed(decorator_params)) 

231 

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

233 attrs["help"] = f.__doc__ 

234 

235 if t.TYPE_CHECKING: 

236 assert cls is not None 

237 assert not callable(name) 

238 

239 if name is not None: 

240 cmd_name = name 

241 else: 

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

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

244 

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

246 cmd_name = cmd_left 

247 

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

249 cmd.__doc__ = f.__doc__ 

250 return cmd 

251 

252 if func is not None: 

253 return decorator(func) 

254 

255 return decorator 

256 

257 

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

259 

260 

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

262@t.overload 

263def group(name: _AnyCallable) -> Group: ... 

264 

265 

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

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

268@t.overload 

269def group( 

270 name: str | None, 

271 cls: type[GrpType], 

272 **attrs: t.Any, 

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

274 

275 

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

277@t.overload 

278def group( 

279 name: None = None, 

280 *, 

281 cls: type[GrpType], 

282 **attrs: t.Any, 

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

284 

285 

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

287@t.overload 

288def group( 

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

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

291 

292 

293def group( 

294 name: str | _AnyCallable | None = None, 

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

296 **attrs: t.Any, 

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

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

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

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

301 

302 .. versionchanged:: 8.1 

303 This decorator can be applied without parentheses. 

304 """ 

305 if cls is None: 

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

307 

308 if callable(name): 

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

310 

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

312 

313 

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

315 if isinstance(f, Command): 

316 f.params.append(param) 

317 else: 

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

319 f.__click_params__ = [] # type: ignore 

320 

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

322 

323 

324def argument( 

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

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

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

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

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

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

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

332 

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

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

335 

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

337 :class:`Argument`. 

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

339 ``cls``. 

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

341 """ 

342 if cls is None: 

343 cls = Argument 

344 

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

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

347 return f 

348 

349 return decorator 

350 

351 

352def option( 

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

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

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

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

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

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

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

360 

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

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

363 

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

365 :class:`Option`. 

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

367 ``cls``. 

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

369 """ 

370 if cls is None: 

371 cls = Option 

372 

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

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

375 return f 

376 

377 return decorator 

378 

379 

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

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

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

383 

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

385 value ``"--yes"``. 

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

387 """ 

388 

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

390 if not value: 

391 ctx.abort() 

392 

393 if not param_decls: 

394 param_decls = ("--yes",) 

395 

396 kwargs.setdefault("is_flag", True) 

397 kwargs.setdefault("callback", callback) 

398 kwargs.setdefault("expose_value", False) 

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

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

401 return option(*param_decls, **kwargs) 

402 

403 

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

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

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

407 

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

409 value ``"--password"``. 

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

411 """ 

412 if not param_decls: 

413 param_decls = ("--password",) 

414 

415 kwargs.setdefault("prompt", True) 

416 kwargs.setdefault("confirmation_prompt", True) 

417 kwargs.setdefault("hide_input", True) 

418 return option(*param_decls, **kwargs) 

419 

420 

421def version_option( 

422 version: str | None = None, 

423 *param_decls: str, 

424 package_name: str | None = None, 

425 prog_name: str | None = None, 

426 message: str | None = None, 

427 **kwargs: t.Any, 

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

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

430 number and exits the program. 

431 

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

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

434 ``package_name``. 

435 

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

437 inspecting the stack frames. If the detected (or given) name does 

438 not match an installed distribution, Click resolves it as an import 

439 (top-level module) name via 

440 :func:`importlib.metadata.packages_distributions`, so e.g. ``PIL`` 

441 resolves to the ``Pillow`` distribution. 

442 

443 .. note:: 

444 The parameters and message variables accepted by this option are frozen: no new 

445 slots will be added, to keep the common case simple and predictable. Extra 

446 values have no single right format: a path can point to the entry point or to 

447 the module, and a Python version can be the full string or only ``X.Y``. If you 

448 need values it does not expose, such as a file path, the Python version, or git 

449 metadata, use :func:`custom_version_option` to render the output yourself. 

450 

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

452 will try to detect it. 

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

454 value ``"--version"``. 

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

456 not provided, Click will try to detect it. 

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

458 provided, it will be detected from the command. 

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

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

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

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

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

464 

465 .. versionchanged:: 8.0 

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

467 value for messages. 

468 

469 .. versionchanged:: 8.0 

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

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

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

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

474 

475 .. versionchanged:: 8.4.2 

476 When ``package_name`` does not match an installed distribution, 

477 Click now resolves it as an import (top-level module). 

478 """ 

479 if message is None: 

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

481 

482 if version is None and package_name is None: 

483 frame = inspect.currentframe() 

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

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

486 # break reference cycle 

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

488 del frame 

489 

490 if f_globals is not None: 

491 package_name = f_globals.get("__name__") 

492 

493 if package_name == "__main__": 

494 package_name = f_globals.get("__package__") 

495 

496 if package_name: 

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

498 

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

500 if not value or ctx.resilient_parsing: 

501 return 

502 

503 nonlocal prog_name 

504 nonlocal version 

505 nonlocal package_name 

506 

507 if prog_name is None: 

508 prog_name = ctx.find_root().info_name 

509 

510 if version is None and package_name is not None: 

511 import importlib.metadata 

512 

513 try: 

514 version = importlib.metadata.version(package_name) 

515 except importlib.metadata.PackageNotFoundError: 

516 # The given name didn't match an installed distribution. 

517 # Try resolving it as an import (top-level module) name, 

518 # e.g. ``PIL`` is provided by the ``Pillow`` distribution. 

519 distributions = importlib.metadata.packages_distributions().get( 

520 package_name, [] 

521 ) 

522 if len(distributions) == 1: 

523 package_name = distributions[0] 

524 version = importlib.metadata.version(package_name) 

525 elif len(distributions) > 1: 

526 raise RuntimeError( 

527 f"{package_name!r} maps to multiple installed" 

528 f" distributions ({', '.join(distributions)})." 

529 " Pass 'package_name' to disambiguate." 

530 ) from None 

531 else: 

532 raise RuntimeError( 

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

534 " 'package_name' instead." 

535 ) from None 

536 

537 if version is None: 

538 raise RuntimeError( 

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

540 ) 

541 

542 echo( 

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

544 color=ctx.color, 

545 ) 

546 ctx.exit() 

547 

548 if not param_decls: 

549 param_decls = ("--version",) 

550 

551 kwargs.setdefault("is_flag", True) 

552 kwargs.setdefault("expose_value", False) 

553 kwargs.setdefault("is_eager", True) 

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

555 kwargs["callback"] = callback 

556 return option(*param_decls, **kwargs) 

557 

558 

559def custom_version_option( 

560 callback: t.Callable[[Context], str], 

561 *param_decls: str, 

562 **kwargs: t.Any, 

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

564 """Add a ``--version`` option whose output is produced by ``callback``. 

565 

566 This is the customizable companion to :func:`version_option`. Where 

567 :func:`version_option` is intentionally limited to a fixed message and 

568 a small set of values, this option calls ``callback`` to build the 

569 whole string to print. Use it when you need values that 

570 :func:`version_option` does not expose, such as a file path, the 

571 Python version, or git metadata. 

572 

573 :param callback: Called with the current :class:`Context` when the 

574 option is invoked. Its return value is printed, then the program 

575 exits. 

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

577 value ``--version``. 

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

579 

580 .. versionadded:: 8.5.0 

581 """ 

582 

583 def show_version(ctx: Context, param: Parameter, value: bool) -> None: 

584 if not value or ctx.resilient_parsing: 

585 return 

586 

587 echo(callback(ctx), color=ctx.color) 

588 ctx.exit() 

589 

590 if not param_decls: 

591 param_decls = ("--version",) 

592 

593 kwargs.setdefault("is_flag", True) 

594 kwargs.setdefault("expose_value", False) 

595 kwargs.setdefault("is_eager", True) 

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

597 kwargs["callback"] = show_version 

598 return option(*param_decls, **kwargs) 

599 

600 

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

602 """Pre-configured ``--help`` option which immediately prints the help page 

603 and exits the program. 

604 

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

606 value ``"--help"``. 

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

608 """ 

609 

610 def show_help(ctx: Context, param: Parameter, value: bool) -> None: 

611 """Callback that print the help page on ``<stdout>`` and exits.""" 

612 if value and not ctx.resilient_parsing: 

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

614 ctx.exit() 

615 

616 if not param_decls: 

617 param_decls = ("--help",) 

618 

619 kwargs.setdefault("is_flag", True) 

620 kwargs.setdefault("expose_value", False) 

621 kwargs.setdefault("is_eager", True) 

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

623 kwargs.setdefault("callback", show_help) 

624 

625 return option(*param_decls, **kwargs)