Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/pandas/_config/config.py: 63%

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

316 statements  

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 TYPE_CHECKING, 

60 Any, 

61 Callable, 

62 Generic, 

63 NamedTuple, 

64 cast, 

65) 

66import warnings 

67 

68from pandas._typing import ( 

69 F, 

70 T, 

71) 

72from pandas.util._exceptions import find_stack_level 

73 

74if TYPE_CHECKING: 

75 from collections.abc import ( 

76 Generator, 

77 Iterable, 

78 ) 

79 

80 

81class DeprecatedOption(NamedTuple): 

82 key: str 

83 msg: str | None 

84 rkey: str | None 

85 removal_ver: str | None 

86 

87 

88class RegisteredOption(NamedTuple): 

89 key: str 

90 defval: object 

91 doc: str 

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

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

94 

95 

96# holds deprecated option metadata 

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

98 

99# holds registered option metadata 

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

101 

102# holds the current values for registered options 

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

104 

105# keys which have a special meaning 

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

107 

108 

109class OptionError(AttributeError, KeyError): 

110 """ 

111 Exception raised for pandas.options. 

112 

113 Backwards compatible with KeyError checks. 

114 

115 Examples 

116 -------- 

117 >>> pd.options.context 

118 Traceback (most recent call last): 

119 OptionError: No such option 

120 """ 

121 

122 

123# 

124# User API 

125 

126 

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

128 keys = _select_options(pat) 

129 if len(keys) == 0: 

130 if not silent: 

131 _warn_if_deprecated(pat) 

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

133 if len(keys) > 1: 

134 raise OptionError("Pattern matched multiple keys") 

135 key = keys[0] 

136 

137 if not silent: 

138 _warn_if_deprecated(key) 

139 

140 key = _translate_key(key) 

141 

142 return key 

143 

144 

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

146 key = _get_single_key(pat, silent) 

147 

148 # walk the nested dict 

149 root, k = _get_root(key) 

150 return root[k] 

151 

152 

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

154 # must at least 1 arg deal with constraints later 

155 nargs = len(args) 

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

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

158 

159 # default to false 

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

161 

162 if kwargs: 

163 kwarg = next(iter(kwargs.keys())) 

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

165 

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

167 key = _get_single_key(k, silent) 

168 

169 o = _get_registered_option(key) 

170 if o and o.validator: 

171 o.validator(v) 

172 

173 # walk the nested dict 

174 root, k_root = _get_root(key) 

175 root[k_root] = v 

176 

177 if o.cb: 

178 if silent: 

179 with warnings.catch_warnings(record=True): 

180 o.cb(key) 

181 else: 

182 o.cb(key) 

183 

184 

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

186 keys = _select_options(pat) 

187 if len(keys) == 0: 

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

189 

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

191 

192 if _print_desc: 

193 print(s) 

194 return None 

195 return s 

196 

197 

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

199 keys = _select_options(pat) 

200 

201 if len(keys) == 0: 

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

203 

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

205 raise ValueError( 

206 "You must specify at least 4 characters when " 

207 "resetting multiple keys, use the special keyword " 

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

209 ) 

210 

211 for k in keys: 

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

213 

214 

215def get_default_val(pat: str): 

216 key = _get_single_key(pat, silent=True) 

217 return _get_registered_option(key).defval 

218 

219 

220class DictWrapper: 

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

222 

223 d: dict[str, Any] 

224 

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

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

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

228 

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

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

231 if prefix: 

232 prefix += "." 

233 prefix += key 

234 # you can't set new keys 

235 # can you can't overwrite subtrees 

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

237 _set_option(prefix, val) 

238 else: 

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

240 

241 def __getattr__(self, key: str): 

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

243 if prefix: 

244 prefix += "." 

245 prefix += key 

246 try: 

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

248 except KeyError as err: 

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

250 if isinstance(v, dict): 

251 return DictWrapper(v, prefix) 

252 else: 

253 return _get_option(prefix) 

254 

255 def __dir__(self) -> list[str]: 

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

257 

258 

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

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

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

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

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

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

265# of options, and option descriptions. 

266 

267 

268class CallableDynamicDoc(Generic[T]): 

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

270 self.__doc_tmpl__ = doc_tmpl 

271 self.__func__ = func 

272 

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

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

275 

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

277 @property 

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

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

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

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

282 

283 

284_get_option_tmpl = """ 

285get_option(pat) 

286 

287Retrieves the value of the specified option. 

288 

289Available options: 

290 

291{opts_list} 

292 

293Parameters 

294---------- 

295pat : str 

296 Regexp which should match a single option. 

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

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

299 versions if new options with similar names are introduced. 

300 

301Returns 

302------- 

303result : the value of the option 

304 

305Raises 

306------ 

307OptionError : if no such option exists 

308 

309Notes 

310----- 

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

312 

313The available options with its descriptions: 

314 

315{opts_desc} 

316 

317Examples 

318-------- 

319>>> pd.get_option('display.max_columns') # doctest: +SKIP 

3204 

321""" 

322 

323_set_option_tmpl = """ 

324set_option(pat, value) 

325 

326Sets the value of the specified option. 

327 

328Available options: 

329 

330{opts_list} 

331 

332Parameters 

333---------- 

334pat : str 

335 Regexp which should match a single option. 

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

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

338 versions if new options with similar names are introduced. 

339value : object 

340 New value of option. 

341 

342Returns 

343------- 

344None 

345 

346Raises 

347------ 

348OptionError if no such option exists 

349 

350Notes 

351----- 

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

353 

354The available options with its descriptions: 

355 

356{opts_desc} 

357 

358Examples 

359-------- 

360>>> pd.set_option('display.max_columns', 4) 

361>>> df = pd.DataFrame([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]) 

362>>> df 

363 0 1 ... 3 4 

3640 1 2 ... 4 5 

3651 6 7 ... 9 10 

366[2 rows x 5 columns] 

367>>> pd.reset_option('display.max_columns') 

368""" 

369 

370_describe_option_tmpl = """ 

371describe_option(pat, _print_desc=False) 

372 

373Prints the description for one or more registered options. 

374 

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

376 

377Available options: 

378 

379{opts_list} 

380 

381Parameters 

382---------- 

383pat : str 

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

385_print_desc : bool, default True 

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

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

388 (for testing). 

389 

390Returns 

391------- 

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

393is False 

394 

395Notes 

396----- 

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

398 

399The available options with its descriptions: 

400 

401{opts_desc} 

402 

403Examples 

404-------- 

405>>> pd.describe_option('display.max_columns') # doctest: +SKIP 

406display.max_columns : int 

407 If max_cols is exceeded, switch to truncate view... 

408""" 

409 

410_reset_option_tmpl = """ 

411reset_option(pat) 

412 

413Reset one or more options to their default value. 

414 

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

416 

417Available options: 

418 

419{opts_list} 

420 

421Parameters 

422---------- 

423pat : str/regex 

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

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

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

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

428 

429Returns 

430------- 

431None 

432 

433Notes 

434----- 

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

436 

437The available options with its descriptions: 

438 

439{opts_desc} 

440 

441Examples 

442-------- 

443>>> pd.reset_option('display.max_columns') # doctest: +SKIP 

444""" 

445 

446# bind the functions with their docstrings into a Callable 

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

448get_option = CallableDynamicDoc(_get_option, _get_option_tmpl) 

449set_option = CallableDynamicDoc(_set_option, _set_option_tmpl) 

450reset_option = CallableDynamicDoc(_reset_option, _reset_option_tmpl) 

451describe_option = CallableDynamicDoc(_describe_option, _describe_option_tmpl) 

452options = DictWrapper(_global_config) 

453 

454# 

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

456 

457 

458class option_context(ContextDecorator): 

459 """ 

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

461 

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

463 

464 Examples 

465 -------- 

466 >>> from pandas import option_context 

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

468 ... pass 

469 """ 

470 

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

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

473 raise ValueError( 

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

475 ) 

476 

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

478 

479 def __enter__(self) -> None: 

480 self.undo = [(pat, _get_option(pat)) for pat, val in self.ops] 

481 

482 for pat, val in self.ops: 

483 _set_option(pat, val, silent=True) 

484 

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

486 if self.undo: 

487 for pat, val in self.undo: 

488 _set_option(pat, val, silent=True) 

489 

490 

491def register_option( 

492 key: str, 

493 defval: object, 

494 doc: str = "", 

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

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

497) -> None: 

498 """ 

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

500 

501 Parameters 

502 ---------- 

503 key : str 

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

505 defval : object 

506 Default value of the option. 

507 doc : str 

508 Description of the option. 

509 validator : Callable, optional 

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

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

512 cb 

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

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

515 the full name of the option. 

516 

517 Raises 

518 ------ 

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

520 

521 """ 

522 import keyword 

523 import tokenize 

524 

525 key = key.lower() 

526 

527 if key in _registered_options: 

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

529 if key in _reserved_keys: 

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

531 

532 # the default value should be legal 

533 if validator: 

534 validator(defval) 

535 

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

537 path = key.split(".") 

538 

539 for k in path: 

540 if not re.match("^" + tokenize.Name + "$", k): 

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

542 if keyword.iskeyword(k): 

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

544 

545 cursor = _global_config 

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

547 

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

549 if not isinstance(cursor, dict): 

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

551 if p not in cursor: 

552 cursor[p] = {} 

553 cursor = cursor[p] 

554 

555 if not isinstance(cursor, dict): 

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

557 

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

559 

560 # save the option metadata 

561 _registered_options[key] = RegisteredOption( 

562 key=key, defval=defval, doc=doc, validator=validator, cb=cb 

563 ) 

564 

565 

566def deprecate_option( 

567 key: str, 

568 msg: str | None = None, 

569 rkey: str | None = None, 

570 removal_ver: str | None = None, 

571) -> None: 

572 """ 

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

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

575 if not. 

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

577 

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

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

580 deprecation warning is given. 

581 

582 Parameters 

583 ---------- 

584 key : str 

585 Name of the option to be deprecated. 

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

587 msg : str, optional 

588 Warning message to output when the key is referenced. 

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

590 rkey : str, optional 

591 Name of an option to reroute access to. 

592 If specified, any referenced `key` will be 

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

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

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

596 removal_ver : str, optional 

597 Specifies the version in which this option will 

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

599 

600 Raises 

601 ------ 

602 OptionError 

603 If the specified key has already been deprecated. 

604 """ 

605 key = key.lower() 

606 

607 if key in _deprecated_options: 

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

609 

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

611 

612 

613# 

614# functions internal to the module 

615 

616 

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

618 """ 

619 returns a list of keys matching `pat` 

620 

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

622 """ 

623 # short-circuit for exact key 

624 if pat in _registered_options: 

625 return [pat] 

626 

627 # else look through all of them 

628 keys = sorted(_registered_options.keys()) 

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

630 return keys 

631 

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

633 

634 

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

636 path = key.split(".") 

637 cursor = _global_config 

638 for p in path[:-1]: 

639 cursor = cursor[p] 

640 return cursor, path[-1] 

641 

642 

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

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

645 key = key.lower() 

646 return key in _deprecated_options 

647 

648 

649def _get_deprecated_option(key: str): 

650 """ 

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

652 

653 Returns 

654 ------- 

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

656 """ 

657 try: 

658 d = _deprecated_options[key] 

659 except KeyError: 

660 return None 

661 else: 

662 return d 

663 

664 

665def _get_registered_option(key: str): 

666 """ 

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

668 

669 Returns 

670 ------- 

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

672 """ 

673 return _registered_options.get(key) 

674 

675 

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

677 """ 

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

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

680 """ 

681 d = _get_deprecated_option(key) 

682 if d: 

683 return d.rkey or key 

684 else: 

685 return key 

686 

687 

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

689 """ 

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

691 

692 Returns 

693 ------- 

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

695 """ 

696 d = _get_deprecated_option(key) 

697 if d: 

698 if d.msg: 

699 warnings.warn( 

700 d.msg, 

701 FutureWarning, 

702 stacklevel=find_stack_level(), 

703 ) 

704 else: 

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

706 if d.removal_ver: 

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

708 if d.rkey: 

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

710 else: 

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

712 

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

714 return True 

715 return False 

716 

717 

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

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

720 o = _get_registered_option(k) 

721 d = _get_deprecated_option(k) 

722 

723 s = f"{k} " 

724 

725 if o.doc: 

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

727 else: 

728 s += "No description available." 

729 

730 if o: 

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

732 

733 if d: 

734 rkey = d.rkey or "" 

735 s += "\n (Deprecated" 

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

737 s += ")" 

738 

739 return s 

740 

741 

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

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

744 from itertools import groupby 

745 from textwrap import wrap 

746 

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

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

749 ls = wrap( 

750 ", ".join(ks), 

751 width, 

752 initial_indent=pfx, 

753 subsequent_indent=" ", 

754 break_long_words=False, 

755 ) 

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

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

758 return ls 

759 

760 ls: list[str] = [] 

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

762 if singles: 

763 ls += pp("", singles) 

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

765 

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

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

768 ls += pp(k, ks) 

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

770 if _print: 

771 print(s) 

772 else: 

773 return s 

774 

775 

776# 

777# helpers 

778 

779 

780@contextmanager 

781def config_prefix(prefix: str) -> Generator[None, None, None]: 

782 """ 

783 contextmanager for multiple invocations of API with a common prefix 

784 

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

786 

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

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

789 

790 Example 

791 ------- 

792 import pandas._config.config as cf 

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

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

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

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

797 cf.get_option(size) 

798 ... 

799 

800 etc' 

801 

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

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

804 """ 

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

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

807 

808 global register_option, get_option, set_option 

809 

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

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

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

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

814 

815 return cast(F, inner) 

816 

817 _register_option = register_option 

818 _get_option = get_option 

819 _set_option = set_option 

820 set_option = wrap(set_option) 

821 get_option = wrap(get_option) 

822 register_option = wrap(register_option) 

823 try: 

824 yield 

825 finally: 

826 set_option = _set_option 

827 get_option = _get_option 

828 register_option = _register_option 

829 

830 

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

832# arg in register_option 

833 

834 

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

836 """ 

837 

838 Parameters 

839 ---------- 

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

841 

842 Returns 

843 ------- 

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

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

846 

847 """ 

848 

849 def inner(x) -> None: 

850 if type(x) != _type: 

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

852 

853 return inner 

854 

855 

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

857 """ 

858 

859 Parameters 

860 ---------- 

861 `_type` - the type to be checked against 

862 

863 Returns 

864 ------- 

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

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

867 

868 """ 

869 if isinstance(_type, (tuple, list)): 

870 _type = tuple(_type) 

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

872 else: 

873 type_repr = f"'{_type}'" 

874 

875 def inner(x) -> None: 

876 if not isinstance(x, _type): 

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

878 

879 return inner 

880 

881 

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

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

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

885 

886 def inner(x) -> None: 

887 if x not in legal_values: 

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

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

890 pp_values = "|".join(uvals) 

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

892 if len(callables): 

893 msg += " or a callable" 

894 raise ValueError(msg) 

895 

896 return inner 

897 

898 

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

900 """ 

901 Verify that value is None or a positive int. 

902 

903 Parameters 

904 ---------- 

905 value : None or int 

906 The `value` to be checked. 

907 

908 Raises 

909 ------ 

910 ValueError 

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

912 """ 

913 if value is None: 

914 return 

915 

916 elif isinstance(value, int): 

917 if value >= 0: 

918 return 

919 

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

921 raise ValueError(msg) 

922 

923 

924# common type validators, for convenience 

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

926is_int = is_type_factory(int) 

927is_bool = is_type_factory(bool) 

928is_float = is_type_factory(float) 

929is_str = is_type_factory(str) 

930is_text = is_instance_factory((str, bytes)) 

931 

932 

933def is_callable(obj) -> bool: 

934 """ 

935 

936 Parameters 

937 ---------- 

938 `obj` - the object to be checked 

939 

940 Returns 

941 ------- 

942 validator - returns True if object is callable 

943 raises ValueError otherwise. 

944 

945 """ 

946 if not callable(obj): 

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

948 return True