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

207 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 07:07 +0000

1import inspect 

2import types 

3import typing as t 

4from functools import update_wrapper 

5from gettext import gettext as _ 

6 

7from .core import Argument 

8from .core import Command 

9from .core import Context 

10from .core import Group 

11from .core import Option 

12from .core import Parameter 

13from .globals import get_current_context 

14from .utils import echo 

15 

16if t.TYPE_CHECKING: 

17 import typing_extensions as te 

18 

19 P = te.ParamSpec("P") 

20 

21R = t.TypeVar("R") 

22T = t.TypeVar("T") 

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

24_Decorator: "te.TypeAlias" = t.Callable[[T], T] 

25FC = t.TypeVar("FC", bound=t.Union[_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.Any, 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: t.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.Optional[T] 

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: t.Optional[str] = None 

102) -> "t.Callable[[t.Callable[te.Concatenate[t.Any, 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.Any, 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: t.Optional[str], 

147 cls: t.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(cmd=CommandCls, ...) 

154# The correct way to spell this overload is to use keyword-only argument syntax: 

155# def command(*, cls: t.Type[CmdType], **attrs: t.Any) -> ... 

156# However, mypy thinks this doesn't fit the overloaded function. Pyright does 

157# accept that spelling, and the following work-around makes pyright issue a 

158# warning that CmdType could be left unsolved, but mypy sees it as fine. *shrug* 

159@t.overload 

160def command( 

161 name: None = None, 

162 cls: t.Type[CmdType] = ..., 

163 **attrs: t.Any, 

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

165 ... 

166 

167 

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

169@t.overload 

170def command( 

171 name: t.Optional[str] = ..., cls: None = None, **attrs: t.Any 

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

173 ... 

174 

175 

176def command( 

177 name: t.Union[t.Optional[str], _AnyCallable] = None, 

178 cls: t.Optional[t.Type[CmdType]] = None, 

179 **attrs: t.Any, 

180) -> t.Union[Command, t.Callable[[_AnyCallable], t.Union[Command, CmdType]]]: 

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

182 callback. This will also automatically attach all decorated 

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

184 

185 The name of the command defaults to the name of the function with 

186 underscores replaced by dashes. If you want to change that, you can 

187 pass the intended name as the first argument. 

188 

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

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

191 the end of the list. 

192 

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

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

195 command :class:`Group`. 

196 

197 :param name: the name of the command. This defaults to the function 

198 name with underscores replaced by dashes. 

199 :param cls: the command class to instantiate. This defaults to 

200 :class:`Command`. 

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.Optional[t.Callable[[_AnyCallable], t.Any]] = 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(t.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 cmd = cls( 

244 name=name or f.__name__.lower().replace("_", "-"), 

245 callback=f, 

246 params=params, 

247 **attrs, 

248 ) 

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 

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

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

269@t.overload 

270def group( 

271 name: t.Optional[str], 

272 cls: t.Type[GrpType], 

273 **attrs: t.Any, 

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

275 ... 

276 

277 

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

279# The _correct_ way to spell this overload is to use keyword-only argument syntax: 

280# def group(*, cls: t.Type[GrpType], **attrs: t.Any) -> ... 

281# However, mypy thinks this doesn't fit the overloaded function. Pyright does 

282# accept that spelling, and the following work-around makes pyright issue a 

283# warning that GrpType could be left unsolved, but mypy sees it as fine. *shrug* 

284@t.overload 

285def group( 

286 name: None = None, 

287 cls: t.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: t.Optional[str] = ..., cls: None = None, **attrs: t.Any 

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

298 ... 

299 

300 

301def group( 

302 name: t.Union[str, _AnyCallable, None] = None, 

303 cls: t.Optional[t.Type[GrpType]] = None, 

304 **attrs: t.Any, 

305) -> t.Union[Group, t.Callable[[_AnyCallable], t.Union[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(t.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(*param_decls: str, **attrs: t.Any) -> _Decorator[FC]: 

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

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

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

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

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

338 

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

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

341 

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

343 :class:`Argument`. 

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

345 ``cls``. 

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

347 """ 

348 

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

350 ArgumentClass = attrs.pop("cls", None) or Argument 

351 _param_memo(f, ArgumentClass(param_decls, **attrs)) 

352 return f 

353 

354 return decorator 

355 

356 

357def option(*param_decls: str, **attrs: t.Any) -> _Decorator[FC]: 

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

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

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

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

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

363 

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

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

366 

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

368 :class:`Option`. 

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

370 ``cls``. 

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

372 """ 

373 

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

375 # Issue 926, copy attrs, so pre-defined options can re-use the same cls= 

376 option_attrs = attrs.copy() 

377 OptionClass = option_attrs.pop("cls", None) or Option 

378 _param_memo(f, OptionClass(param_decls, **option_attrs)) 

379 return f 

380 

381 return decorator 

382 

383 

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

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

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

387 

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

389 value ``"--yes"``. 

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

391 """ 

392 

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

394 if not value: 

395 ctx.abort() 

396 

397 if not param_decls: 

398 param_decls = ("--yes",) 

399 

400 kwargs.setdefault("is_flag", True) 

401 kwargs.setdefault("callback", callback) 

402 kwargs.setdefault("expose_value", False) 

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

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

405 return option(*param_decls, **kwargs) 

406 

407 

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

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

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

411 

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

413 value ``"--password"``. 

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

415 """ 

416 if not param_decls: 

417 param_decls = ("--password",) 

418 

419 kwargs.setdefault("prompt", True) 

420 kwargs.setdefault("confirmation_prompt", True) 

421 kwargs.setdefault("hide_input", True) 

422 return option(*param_decls, **kwargs) 

423 

424 

425def version_option( 

426 version: t.Optional[str] = None, 

427 *param_decls: str, 

428 package_name: t.Optional[str] = None, 

429 prog_name: t.Optional[str] = None, 

430 message: t.Optional[str] = None, 

431 **kwargs: t.Any, 

432) -> _Decorator[FC]: 

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

434 number and exits the program. 

435 

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

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

438 ``package_name``. On Python < 3.8, the ``importlib_metadata`` 

439 backport must be installed. 

440 

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

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

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

444 

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

446 will try to detect it. 

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

448 value ``"--version"``. 

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

450 not provided, Click will try to detect it. 

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

452 provided, it will be detected from the command. 

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

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

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

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

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

458 

459 .. versionchanged:: 8.0 

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

461 value for messages. 

462 

463 .. versionchanged:: 8.0 

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

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

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

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

468 """ 

469 if message is None: 

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

471 

472 if version is None and package_name is None: 

473 frame = inspect.currentframe() 

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

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

476 # break reference cycle 

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

478 del frame 

479 

480 if f_globals is not None: 

481 package_name = f_globals.get("__name__") 

482 

483 if package_name == "__main__": 

484 package_name = f_globals.get("__package__") 

485 

486 if package_name: 

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

488 

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

490 if not value or ctx.resilient_parsing: 

491 return 

492 

493 nonlocal prog_name 

494 nonlocal version 

495 

496 if prog_name is None: 

497 prog_name = ctx.find_root().info_name 

498 

499 if version is None and package_name is not None: 

500 metadata: t.Optional[types.ModuleType] 

501 

502 try: 

503 from importlib import metadata # type: ignore 

504 except ImportError: 

505 # Python < 3.8 

506 import importlib_metadata as metadata # type: ignore 

507 

508 try: 

509 version = metadata.version(package_name) # type: ignore 

510 except metadata.PackageNotFoundError: # type: ignore 

511 raise RuntimeError( 

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

513 " 'package_name' instead." 

514 ) from None 

515 

516 if version is None: 

517 raise RuntimeError( 

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

519 ) 

520 

521 echo( 

522 t.cast(str, message) 

523 % {"prog": prog_name, "package": package_name, "version": version}, 

524 color=ctx.color, 

525 ) 

526 ctx.exit() 

527 

528 if not param_decls: 

529 param_decls = ("--version",) 

530 

531 kwargs.setdefault("is_flag", True) 

532 kwargs.setdefault("expose_value", False) 

533 kwargs.setdefault("is_eager", True) 

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

535 kwargs["callback"] = callback 

536 return option(*param_decls, **kwargs) 

537 

538 

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

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

541 and exits the program. 

542 

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

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

545 passed. 

546 

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

548 value ``"--help"``. 

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

550 """ 

551 

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

553 if not value or ctx.resilient_parsing: 

554 return 

555 

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

557 ctx.exit() 

558 

559 if not param_decls: 

560 param_decls = ("--help",) 

561 

562 kwargs.setdefault("is_flag", True) 

563 kwargs.setdefault("expose_value", False) 

564 kwargs.setdefault("is_eager", True) 

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

566 kwargs["callback"] = callback 

567 return option(*param_decls, **kwargs)