Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/pandas/_config/config.py: 46%

313 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1""" 

2The config module holds package-wide configurables and provides 

3a uniform API for working with them. 

4 

5Overview 

6======== 

7 

8This module supports the following requirements: 

9- options are referenced using keys in dot.notation, e.g. "x.y.option - z". 

10- keys are case-insensitive. 

11- functions should accept partial/regex keys, when unambiguous. 

12- options can be registered by modules at import time. 

13- options can be registered at init-time (via core.config_init) 

14- options have a default value, and (optionally) a description and 

15 validation function associated with them. 

16- options can be deprecated, in which case referencing them 

17 should produce a warning. 

18- deprecated options can optionally be rerouted to a replacement 

19 so that accessing a deprecated option reroutes to a differently 

20 named option. 

21- options can be reset to their default value. 

22- all option can be reset to their default value at once. 

23- all options in a certain sub - namespace can be reset at once. 

24- the user can set / get / reset or ask for the description of an option. 

25- a developer can register and mark an option as deprecated. 

26- you can register a callback to be invoked when the option value 

27 is set or reset. Changing the stored value is considered misuse, but 

28 is not verboten. 

29 

30Implementation 

31============== 

32 

33- Data is stored using nested dictionaries, and should be accessed 

34 through the provided API. 

35 

36- "Registered options" and "Deprecated options" have metadata associated 

37 with them, which are stored in auxiliary dictionaries keyed on the 

38 fully-qualified key, e.g. "x.y.z.option". 

39 

40- the config_init module is imported by the package's __init__.py file. 

41 placing any register_option() calls there will ensure those options 

42 are available as soon as pandas is loaded. If you use register_option 

43 in a module, it will only be available after that module is imported, 

44 which you should be aware of. 

45 

46- `config_prefix` is a context_manager (for use with the `with` keyword) 

47 which can save developers some typing, see the docstring. 

48 

49""" 

50 

51from __future__ import annotations 

52 

53from contextlib import ( 

54 ContextDecorator, 

55 contextmanager, 

56) 

57import re 

58from typing import ( 

59 Any, 

60 Callable, 

61 Generic, 

62 Iterable, 

63 Iterator, 

64 NamedTuple, 

65 cast, 

66) 

67import warnings 

68 

69from pandas._typing import ( 

70 F, 

71 T, 

72) 

73from pandas.util._exceptions import find_stack_level 

74 

75 

76class DeprecatedOption(NamedTuple): 

77 key: str 

78 msg: str | None 

79 rkey: str | None 

80 removal_ver: str | None 

81 

82 

83class RegisteredOption(NamedTuple): 

84 key: str 

85 defval: object 

86 doc: str 

87 validator: Callable[[object], Any] | None 

88 cb: Callable[[str], Any] | None 

89 

90 

91# holds deprecated option metadata 

92_deprecated_options: dict[str, DeprecatedOption] = {} 

93 

94# holds registered option metadata 

95_registered_options: dict[str, RegisteredOption] = {} 

96 

97# holds the current values for registered options 

98_global_config: dict[str, Any] = {} 

99 

100# keys which have a special meaning 

101_reserved_keys: list[str] = ["all"] 

102 

103 

104class OptionError(AttributeError, KeyError): 

105 """ 

106 Exception raised for pandas.options. 

107 

108 Backwards compatible with KeyError checks. 

109 """ 

110 

111 

112# 

113# User API 

114 

115 

116def _get_single_key(pat: str, silent: bool) -> str: 

117 keys = _select_options(pat) 

118 if len(keys) == 0: 118 ↛ 119line 118 didn't jump to line 119, because the condition on line 118 was never true

119 if not silent: 

120 _warn_if_deprecated(pat) 

121 raise OptionError(f"No such keys(s): {repr(pat)}") 

122 if len(keys) > 1: 122 ↛ 123line 122 didn't jump to line 123, because the condition on line 122 was never true

123 raise OptionError("Pattern matched multiple keys") 

124 key = keys[0] 

125 

126 if not silent: 126 ↛ 129line 126 didn't jump to line 129, because the condition on line 126 was never false

127 _warn_if_deprecated(key) 

128 

129 key = _translate_key(key) 

130 

131 return key 

132 

133 

134def _get_option(pat: str, silent: bool = False) -> Any: 

135 key = _get_single_key(pat, silent) 

136 

137 # walk the nested dict 

138 root, k = _get_root(key) 

139 return root[k] 

140 

141 

142def _set_option(*args, **kwargs) -> None: 

143 # must at least 1 arg deal with constraints later 

144 nargs = len(args) 

145 if not nargs or nargs % 2 != 0: 

146 raise ValueError("Must provide an even number of non-keyword arguments") 

147 

148 # default to false 

149 silent = kwargs.pop("silent", False) 

150 

151 if kwargs: 

152 kwarg = list(kwargs.keys())[0] 

153 raise TypeError(f'_set_option() got an unexpected keyword argument "{kwarg}"') 

154 

155 for k, v in zip(args[::2], args[1::2]): 

156 key = _get_single_key(k, silent) 

157 

158 o = _get_registered_option(key) 

159 if o and o.validator: 

160 o.validator(v) 

161 

162 # walk the nested dict 

163 root, k = _get_root(key) 

164 root[k] = v 

165 

166 if o.cb: 

167 if silent: 

168 with warnings.catch_warnings(record=True): 

169 o.cb(key) 

170 else: 

171 o.cb(key) 

172 

173 

174def _describe_option(pat: str = "", _print_desc: bool = True) -> str | None: 

175 

176 keys = _select_options(pat) 

177 if len(keys) == 0: 

178 raise OptionError("No such keys(s)") 

179 

180 s = "\n".join([_build_option_description(k) for k in keys]) 

181 

182 if _print_desc: 

183 print(s) 

184 return None 

185 return s 

186 

187 

188def _reset_option(pat: str, silent: bool = False) -> None: 

189 

190 keys = _select_options(pat) 

191 

192 if len(keys) == 0: 

193 raise OptionError("No such keys(s)") 

194 

195 if len(keys) > 1 and len(pat) < 4 and pat != "all": 

196 raise ValueError( 

197 "You must specify at least 4 characters when " 

198 "resetting multiple keys, use the special keyword " 

199 '"all" to reset all the options to their default value' 

200 ) 

201 

202 for k in keys: 

203 _set_option(k, _registered_options[k].defval, silent=silent) 

204 

205 

206def get_default_val(pat: str): 

207 key = _get_single_key(pat, silent=True) 

208 return _get_registered_option(key).defval 

209 

210 

211class DictWrapper: 

212 """provide attribute-style access to a nested dict""" 

213 

214 def __init__(self, d: dict[str, Any], prefix: str = "") -> None: 

215 object.__setattr__(self, "d", d) 

216 object.__setattr__(self, "prefix", prefix) 

217 

218 def __setattr__(self, key: str, val: Any) -> None: 

219 prefix = object.__getattribute__(self, "prefix") 

220 if prefix: 

221 prefix += "." 

222 prefix += key 

223 # you can't set new keys 

224 # can you can't overwrite subtrees 

225 if key in self.d and not isinstance(self.d[key], dict): 

226 _set_option(prefix, val) 

227 else: 

228 raise OptionError("You can only set the value of existing options") 

229 

230 def __getattr__(self, key: str): 

231 prefix = object.__getattribute__(self, "prefix") 

232 if prefix: 

233 prefix += "." 

234 prefix += key 

235 try: 

236 v = object.__getattribute__(self, "d")[key] 

237 except KeyError as err: 

238 raise OptionError("No such option") from err 

239 if isinstance(v, dict): 

240 return DictWrapper(v, prefix) 

241 else: 

242 return _get_option(prefix) 

243 

244 def __dir__(self) -> Iterable[str]: 

245 return list(self.d.keys()) 

246 

247 

248# For user convenience, we'd like to have the available options described 

249# in the docstring. For dev convenience we'd like to generate the docstrings 

250# dynamically instead of maintaining them by hand. To this, we use the 

251# class below which wraps functions inside a callable, and converts 

252# __doc__ into a property function. The doctsrings below are templates 

253# using the py2.6+ advanced formatting syntax to plug in a concise list 

254# of options, and option descriptions. 

255 

256 

257class CallableDynamicDoc(Generic[T]): 

258 def __init__(self, func: Callable[..., T], doc_tmpl: str) -> None: 

259 self.__doc_tmpl__ = doc_tmpl 

260 self.__func__ = func 

261 

262 def __call__(self, *args, **kwds) -> T: 

263 return self.__func__(*args, **kwds) 

264 

265 # error: Signature of "__doc__" incompatible with supertype "object" 

266 @property 

267 def __doc__(self) -> str: # type: ignore[override] 

268 opts_desc = _describe_option("all", _print_desc=False) 

269 opts_list = pp_options_list(list(_registered_options.keys())) 

270 return self.__doc_tmpl__.format(opts_desc=opts_desc, opts_list=opts_list) 

271 

272 

273_get_option_tmpl = """ 

274get_option(pat) 

275 

276Retrieves the value of the specified option. 

277 

278Available options: 

279 

280{opts_list} 

281 

282Parameters 

283---------- 

284pat : str 

285 Regexp which should match a single option. 

286 Note: partial matches are supported for convenience, but unless you use the 

287 full option name (e.g. x.y.z.option_name), your code may break in future 

288 versions if new options with similar names are introduced. 

289 

290Returns 

291------- 

292result : the value of the option 

293 

294Raises 

295------ 

296OptionError : if no such option exists 

297 

298Notes 

299----- 

300Please reference the :ref:`User Guide <options>` for more information. 

301 

302The available options with its descriptions: 

303 

304{opts_desc} 

305""" 

306 

307_set_option_tmpl = """ 

308set_option(pat, value) 

309 

310Sets the value of the specified option. 

311 

312Available options: 

313 

314{opts_list} 

315 

316Parameters 

317---------- 

318pat : str 

319 Regexp which should match a single option. 

320 Note: partial matches are supported for convenience, but unless you use the 

321 full option name (e.g. x.y.z.option_name), your code may break in future 

322 versions if new options with similar names are introduced. 

323value : object 

324 New value of option. 

325 

326Returns 

327------- 

328None 

329 

330Raises 

331------ 

332OptionError if no such option exists 

333 

334Notes 

335----- 

336Please reference the :ref:`User Guide <options>` for more information. 

337 

338The available options with its descriptions: 

339 

340{opts_desc} 

341""" 

342 

343_describe_option_tmpl = """ 

344describe_option(pat, _print_desc=False) 

345 

346Prints the description for one or more registered options. 

347 

348Call with no arguments to get a listing for all registered options. 

349 

350Available options: 

351 

352{opts_list} 

353 

354Parameters 

355---------- 

356pat : str 

357 Regexp pattern. All matching keys will have their description displayed. 

358_print_desc : bool, default True 

359 If True (default) the description(s) will be printed to stdout. 

360 Otherwise, the description(s) will be returned as a unicode string 

361 (for testing). 

362 

363Returns 

364------- 

365None by default, the description(s) as a unicode string if _print_desc 

366is False 

367 

368Notes 

369----- 

370Please reference the :ref:`User Guide <options>` for more information. 

371 

372The available options with its descriptions: 

373 

374{opts_desc} 

375""" 

376 

377_reset_option_tmpl = """ 

378reset_option(pat) 

379 

380Reset one or more options to their default value. 

381 

382Pass "all" as argument to reset all options. 

383 

384Available options: 

385 

386{opts_list} 

387 

388Parameters 

389---------- 

390pat : str/regex 

391 If specified only options matching `prefix*` will be reset. 

392 Note: partial matches are supported for convenience, but unless you 

393 use the full option name (e.g. x.y.z.option_name), your code may break 

394 in future versions if new options with similar names are introduced. 

395 

396Returns 

397------- 

398None 

399 

400Notes 

401----- 

402Please reference the :ref:`User Guide <options>` for more information. 

403 

404The available options with its descriptions: 

405 

406{opts_desc} 

407""" 

408 

409# bind the functions with their docstrings into a Callable 

410# and use that as the functions exposed in pd.api 

411get_option = CallableDynamicDoc(_get_option, _get_option_tmpl) 

412set_option = CallableDynamicDoc(_set_option, _set_option_tmpl) 

413reset_option = CallableDynamicDoc(_reset_option, _reset_option_tmpl) 

414describe_option = CallableDynamicDoc(_describe_option, _describe_option_tmpl) 

415options = DictWrapper(_global_config) 

416 

417# 

418# Functions for use by pandas developers, in addition to User - api 

419 

420 

421class option_context(ContextDecorator): 

422 """ 

423 Context manager to temporarily set options in the `with` statement context. 

424 

425 You need to invoke as ``option_context(pat, val, [(pat, val), ...])``. 

426 

427 Examples 

428 -------- 

429 >>> with option_context('display.max_rows', 10, 'display.max_columns', 5): 

430 ... pass 

431 """ 

432 

433 def __init__(self, *args) -> None: 

434 if len(args) % 2 != 0 or len(args) < 2: 

435 raise ValueError( 

436 "Need to invoke as option_context(pat, val, [(pat, val), ...])." 

437 ) 

438 

439 self.ops = list(zip(args[::2], args[1::2])) 

440 

441 def __enter__(self) -> None: 

442 self.undo = [(pat, _get_option(pat, silent=True)) for pat, val in self.ops] 

443 

444 for pat, val in self.ops: 

445 _set_option(pat, val, silent=True) 

446 

447 def __exit__(self, *args) -> None: 

448 if self.undo: 

449 for pat, val in self.undo: 

450 _set_option(pat, val, silent=True) 

451 

452 

453def register_option( 

454 key: str, 

455 defval: object, 

456 doc: str = "", 

457 validator: Callable[[object], Any] | None = None, 

458 cb: Callable[[str], Any] | None = None, 

459) -> None: 

460 """ 

461 Register an option in the package-wide pandas config object 

462 

463 Parameters 

464 ---------- 

465 key : str 

466 Fully-qualified key, e.g. "x.y.option - z". 

467 defval : object 

468 Default value of the option. 

469 doc : str 

470 Description of the option. 

471 validator : Callable, optional 

472 Function of a single argument, should raise `ValueError` if 

473 called with a value which is not a legal value for the option. 

474 cb 

475 a function of a single argument "key", which is called 

476 immediately after an option value is set/reset. key is 

477 the full name of the option. 

478 

479 Raises 

480 ------ 

481 ValueError if `validator` is specified and `defval` is not a valid value. 

482 

483 """ 

484 import keyword 

485 import tokenize 

486 

487 key = key.lower() 

488 

489 if key in _registered_options: 489 ↛ 490line 489 didn't jump to line 490, because the condition on line 489 was never true

490 raise OptionError(f"Option '{key}' has already been registered") 

491 if key in _reserved_keys: 491 ↛ 492line 491 didn't jump to line 492, because the condition on line 491 was never true

492 raise OptionError(f"Option '{key}' is a reserved key") 

493 

494 # the default value should be legal 

495 if validator: 

496 validator(defval) 

497 

498 # walk the nested dict, creating dicts as needed along the path 

499 path = key.split(".") 

500 

501 for k in path: 

502 if not re.match("^" + tokenize.Name + "$", k): 502 ↛ 503line 502 didn't jump to line 503, because the condition on line 502 was never true

503 raise ValueError(f"{k} is not a valid identifier") 

504 if keyword.iskeyword(k): 504 ↛ 505line 504 didn't jump to line 505, because the condition on line 504 was never true

505 raise ValueError(f"{k} is a python keyword") 

506 

507 cursor = _global_config 

508 msg = "Path prefix to option '{option}' is already an option" 

509 

510 for i, p in enumerate(path[:-1]): 

511 if not isinstance(cursor, dict): 511 ↛ 512line 511 didn't jump to line 512, because the condition on line 511 was never true

512 raise OptionError(msg.format(option=".".join(path[:i]))) 

513 if p not in cursor: 

514 cursor[p] = {} 

515 cursor = cursor[p] 

516 

517 if not isinstance(cursor, dict): 517 ↛ 518line 517 didn't jump to line 518, because the condition on line 517 was never true

518 raise OptionError(msg.format(option=".".join(path[:-1]))) 

519 

520 cursor[path[-1]] = defval # initialize 

521 

522 # save the option metadata 

523 _registered_options[key] = RegisteredOption( 

524 key=key, defval=defval, doc=doc, validator=validator, cb=cb 

525 ) 

526 

527 

528def deprecate_option( 

529 key: str, 

530 msg: str | None = None, 

531 rkey: str | None = None, 

532 removal_ver: str | None = None, 

533) -> None: 

534 """ 

535 Mark option `key` as deprecated, if code attempts to access this option, 

536 a warning will be produced, using `msg` if given, or a default message 

537 if not. 

538 if `rkey` is given, any access to the key will be re-routed to `rkey`. 

539 

540 Neither the existence of `key` nor that if `rkey` is checked. If they 

541 do not exist, any subsequence access will fail as usual, after the 

542 deprecation warning is given. 

543 

544 Parameters 

545 ---------- 

546 key : str 

547 Name of the option to be deprecated. 

548 must be a fully-qualified option name (e.g "x.y.z.rkey"). 

549 msg : str, optional 

550 Warning message to output when the key is referenced. 

551 if no message is given a default message will be emitted. 

552 rkey : str, optional 

553 Name of an option to reroute access to. 

554 If specified, any referenced `key` will be 

555 re-routed to `rkey` including set/get/reset. 

556 rkey must be a fully-qualified option name (e.g "x.y.z.rkey"). 

557 used by the default message if no `msg` is specified. 

558 removal_ver : str, optional 

559 Specifies the version in which this option will 

560 be removed. used by the default message if no `msg` is specified. 

561 

562 Raises 

563 ------ 

564 OptionError 

565 If the specified key has already been deprecated. 

566 """ 

567 key = key.lower() 

568 

569 if key in _deprecated_options: 569 ↛ 570line 569 didn't jump to line 570, because the condition on line 569 was never true

570 raise OptionError(f"Option '{key}' has already been defined as deprecated.") 

571 

572 _deprecated_options[key] = DeprecatedOption(key, msg, rkey, removal_ver) 

573 

574 

575# 

576# functions internal to the module 

577 

578 

579def _select_options(pat: str) -> list[str]: 

580 """ 

581 returns a list of keys matching `pat` 

582 

583 if pat=="all", returns all registered options 

584 """ 

585 # short-circuit for exact key 

586 if pat in _registered_options: 586 ↛ 590line 586 didn't jump to line 590, because the condition on line 586 was never false

587 return [pat] 

588 

589 # else look through all of them 

590 keys = sorted(_registered_options.keys()) 

591 if pat == "all": # reserved key 

592 return keys 

593 

594 return [k for k in keys if re.search(pat, k, re.I)] 

595 

596 

597def _get_root(key: str) -> tuple[dict[str, Any], str]: 

598 path = key.split(".") 

599 cursor = _global_config 

600 for p in path[:-1]: 

601 cursor = cursor[p] 

602 return cursor, path[-1] 

603 

604 

605def _is_deprecated(key: str) -> bool: 

606 """Returns True if the given option has been deprecated""" 

607 key = key.lower() 

608 return key in _deprecated_options 

609 

610 

611def _get_deprecated_option(key: str): 

612 """ 

613 Retrieves the metadata for a deprecated option, if `key` is deprecated. 

614 

615 Returns 

616 ------- 

617 DeprecatedOption (namedtuple) if key is deprecated, None otherwise 

618 """ 

619 try: 

620 d = _deprecated_options[key] 

621 except KeyError: 

622 return None 

623 else: 

624 return d 

625 

626 

627def _get_registered_option(key: str): 

628 """ 

629 Retrieves the option metadata if `key` is a registered option. 

630 

631 Returns 

632 ------- 

633 RegisteredOption (namedtuple) if key is deprecated, None otherwise 

634 """ 

635 return _registered_options.get(key) 

636 

637 

638def _translate_key(key: str) -> str: 

639 """ 

640 if key id deprecated and a replacement key defined, will return the 

641 replacement key, otherwise returns `key` as - is 

642 """ 

643 d = _get_deprecated_option(key) 

644 if d: 644 ↛ 645line 644 didn't jump to line 645, because the condition on line 644 was never true

645 return d.rkey or key 

646 else: 

647 return key 

648 

649 

650def _warn_if_deprecated(key: str) -> bool: 

651 """ 

652 Checks if `key` is a deprecated option and if so, prints a warning. 

653 

654 Returns 

655 ------- 

656 bool - True if `key` is deprecated, False otherwise. 

657 """ 

658 d = _get_deprecated_option(key) 

659 if d: 659 ↛ 660line 659 didn't jump to line 660, because the condition on line 659 was never true

660 if d.msg: 

661 warnings.warn( 

662 d.msg, 

663 FutureWarning, 

664 stacklevel=find_stack_level(), 

665 ) 

666 else: 

667 msg = f"'{key}' is deprecated" 

668 if d.removal_ver: 

669 msg += f" and will be removed in {d.removal_ver}" 

670 if d.rkey: 

671 msg += f", please use '{d.rkey}' instead." 

672 else: 

673 msg += ", please refrain from using it." 

674 

675 warnings.warn(msg, FutureWarning, stacklevel=find_stack_level()) 

676 return True 

677 return False 

678 

679 

680def _build_option_description(k: str) -> str: 

681 """Builds a formatted description of a registered option and prints it""" 

682 o = _get_registered_option(k) 

683 d = _get_deprecated_option(k) 

684 

685 s = f"{k} " 

686 

687 if o.doc: 

688 s += "\n".join(o.doc.strip().split("\n")) 

689 else: 

690 s += "No description available." 

691 

692 if o: 

693 s += f"\n [default: {o.defval}] [currently: {_get_option(k, True)}]" 

694 

695 if d: 

696 rkey = d.rkey or "" 

697 s += "\n (Deprecated" 

698 s += f", use `{rkey}` instead." 

699 s += ")" 

700 

701 return s 

702 

703 

704def pp_options_list(keys: Iterable[str], width=80, _print: bool = False): 

705 """Builds a concise listing of available options, grouped by prefix""" 

706 from itertools import groupby 

707 from textwrap import wrap 

708 

709 def pp(name: str, ks: Iterable[str]) -> list[str]: 

710 pfx = "- " + name + ".[" if name else "" 

711 ls = wrap( 

712 ", ".join(ks), 

713 width, 

714 initial_indent=pfx, 

715 subsequent_indent=" ", 

716 break_long_words=False, 

717 ) 

718 if ls and ls[-1] and name: 

719 ls[-1] = ls[-1] + "]" 

720 return ls 

721 

722 ls: list[str] = [] 

723 singles = [x for x in sorted(keys) if x.find(".") < 0] 

724 if singles: 

725 ls += pp("", singles) 

726 keys = [x for x in keys if x.find(".") >= 0] 

727 

728 for k, g in groupby(sorted(keys), lambda x: x[: x.rfind(".")]): 

729 ks = [x[len(k) + 1 :] for x in list(g)] 

730 ls += pp(k, ks) 

731 s = "\n".join(ls) 

732 if _print: 

733 print(s) 

734 else: 

735 return s 

736 

737 

738# 

739# helpers 

740 

741 

742@contextmanager 

743def config_prefix(prefix) -> Iterator[None]: 

744 """ 

745 contextmanager for multiple invocations of API with a common prefix 

746 

747 supported API functions: (register / get / set )__option 

748 

749 Warning: This is not thread - safe, and won't work properly if you import 

750 the API functions into your module using the "from x import y" construct. 

751 

752 Example 

753 ------- 

754 import pandas._config.config as cf 

755 with cf.config_prefix("display.font"): 

756 cf.register_option("color", "red") 

757 cf.register_option("size", " 5 pt") 

758 cf.set_option(size, " 6 pt") 

759 cf.get_option(size) 

760 ... 

761 

762 etc' 

763 

764 will register options "display.font.color", "display.font.size", set the 

765 value of "display.font.size"... and so on. 

766 """ 

767 # Note: reset_option relies on set_option, and on key directly 

768 # it does not fit in to this monkey-patching scheme 

769 

770 global register_option, get_option, set_option, reset_option 

771 

772 def wrap(func: F) -> F: 

773 def inner(key: str, *args, **kwds): 

774 pkey = f"{prefix}.{key}" 

775 return func(pkey, *args, **kwds) 

776 

777 return cast(F, inner) 

778 

779 _register_option = register_option 

780 _get_option = get_option 

781 _set_option = set_option 

782 set_option = wrap(set_option) 

783 get_option = wrap(get_option) 

784 register_option = wrap(register_option) 

785 try: 

786 yield 

787 finally: 

788 set_option = _set_option 

789 get_option = _get_option 

790 register_option = _register_option 

791 

792 

793# These factories and methods are handy for use as the validator 

794# arg in register_option 

795 

796 

797def is_type_factory(_type: type[Any]) -> Callable[[Any], None]: 

798 """ 

799 

800 Parameters 

801 ---------- 

802 `_type` - a type to be compared against (e.g. type(x) == `_type`) 

803 

804 Returns 

805 ------- 

806 validator - a function of a single argument x , which raises 

807 ValueError if type(x) is not equal to `_type` 

808 

809 """ 

810 

811 def inner(x) -> None: 

812 if type(x) != _type: 812 ↛ 813line 812 didn't jump to line 813, because the condition on line 812 was never true

813 raise ValueError(f"Value must have type '{_type}'") 

814 

815 return inner 

816 

817 

818def is_instance_factory(_type) -> Callable[[Any], None]: 

819 """ 

820 

821 Parameters 

822 ---------- 

823 `_type` - the type to be checked against 

824 

825 Returns 

826 ------- 

827 validator - a function of a single argument x , which raises 

828 ValueError if x is not an instance of `_type` 

829 

830 """ 

831 if isinstance(_type, (tuple, list)): 831 ↛ 835line 831 didn't jump to line 835, because the condition on line 831 was never false

832 _type = tuple(_type) 

833 type_repr = "|".join(map(str, _type)) 

834 else: 

835 type_repr = f"'{_type}'" 

836 

837 def inner(x) -> None: 

838 if not isinstance(x, _type): 838 ↛ 839line 838 didn't jump to line 839, because the condition on line 838 was never true

839 raise ValueError(f"Value must be an instance of {type_repr}") 

840 

841 return inner 

842 

843 

844def is_one_of_factory(legal_values) -> Callable[[Any], None]: 

845 

846 callables = [c for c in legal_values if callable(c)] 

847 legal_values = [c for c in legal_values if not callable(c)] 

848 

849 def inner(x) -> None: 

850 if x not in legal_values: 850 ↛ 852line 850 didn't jump to line 852, because the condition on line 850 was never true

851 

852 if not any(c(x) for c in callables): 

853 uvals = [str(lval) for lval in legal_values] 

854 pp_values = "|".join(uvals) 

855 msg = f"Value must be one of {pp_values}" 

856 if len(callables): 

857 msg += " or a callable" 

858 raise ValueError(msg) 

859 

860 return inner 

861 

862 

863def is_nonnegative_int(value: object) -> None: 

864 """ 

865 Verify that value is None or a positive int. 

866 

867 Parameters 

868 ---------- 

869 value : None or int 

870 The `value` to be checked. 

871 

872 Raises 

873 ------ 

874 ValueError 

875 When the value is not None or is a negative integer 

876 """ 

877 if value is None: 

878 return 

879 

880 elif isinstance(value, int): 880 ↛ 884line 880 didn't jump to line 884, because the condition on line 880 was never false

881 if value >= 0: 881 ↛ 884line 881 didn't jump to line 884, because the condition on line 881 was never false

882 return 

883 

884 msg = "Value must be a nonnegative integer or None" 

885 raise ValueError(msg) 

886 

887 

888# common type validators, for convenience 

889# usage: register_option(... , validator = is_int) 

890is_int = is_type_factory(int) 

891is_bool = is_type_factory(bool) 

892is_float = is_type_factory(float) 

893is_str = is_type_factory(str) 

894is_text = is_instance_factory((str, bytes)) 

895 

896 

897def is_callable(obj) -> bool: 

898 """ 

899 

900 Parameters 

901 ---------- 

902 `obj` - the object to be checked 

903 

904 Returns 

905 ------- 

906 validator - returns True if object is callable 

907 raises ValueError otherwise. 

908 

909 """ 

910 if not callable(obj): 

911 raise ValueError("Value must be a callable") 

912 return True