Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pandas/_config/config.py: 52%

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

313 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 Any, 

60 Callable, 

61 Generator, 

62 Generic, 

63 Iterable, 

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: 

119 if not silent: 

120 _warn_if_deprecated(pat) 

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

122 if len(keys) > 1: 

123 raise OptionError("Pattern matched multiple keys") 

124 key = keys[0] 

125 

126 if not silent: 

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 keys = _select_options(pat) 

176 if len(keys) == 0: 

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

178 

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

180 

181 if _print_desc: 

182 print(s) 

183 return None 

184 return s 

185 

186 

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

188 keys = _select_options(pat) 

189 

190 if len(keys) == 0: 

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

192 

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

194 raise ValueError( 

195 "You must specify at least 4 characters when " 

196 "resetting multiple keys, use the special keyword " 

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

198 ) 

199 

200 for k in keys: 

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

202 

203 

204def get_default_val(pat: str): 

205 key = _get_single_key(pat, silent=True) 

206 return _get_registered_option(key).defval 

207 

208 

209class DictWrapper: 

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

211 

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

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

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

215 

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

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

218 if prefix: 

219 prefix += "." 

220 prefix += key 

221 # you can't set new keys 

222 # can you can't overwrite subtrees 

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

224 _set_option(prefix, val) 

225 else: 

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

227 

228 def __getattr__(self, key: str): 

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

230 if prefix: 

231 prefix += "." 

232 prefix += key 

233 try: 

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

235 except KeyError as err: 

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

237 if isinstance(v, dict): 

238 return DictWrapper(v, prefix) 

239 else: 

240 return _get_option(prefix) 

241 

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

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

244 

245 

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

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

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

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

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

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

252# of options, and option descriptions. 

253 

254 

255class CallableDynamicDoc(Generic[T]): 

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

257 self.__doc_tmpl__ = doc_tmpl 

258 self.__func__ = func 

259 

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

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

262 

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

264 @property 

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

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

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

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

269 

270 

271_get_option_tmpl = """ 

272get_option(pat) 

273 

274Retrieves the value of the specified option. 

275 

276Available options: 

277 

278{opts_list} 

279 

280Parameters 

281---------- 

282pat : str 

283 Regexp which should match a single option. 

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

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

286 versions if new options with similar names are introduced. 

287 

288Returns 

289------- 

290result : the value of the option 

291 

292Raises 

293------ 

294OptionError : if no such option exists 

295 

296Notes 

297----- 

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

299 

300The available options with its descriptions: 

301 

302{opts_desc} 

303""" 

304 

305_set_option_tmpl = """ 

306set_option(pat, value) 

307 

308Sets the value of the specified option. 

309 

310Available options: 

311 

312{opts_list} 

313 

314Parameters 

315---------- 

316pat : str 

317 Regexp which should match a single option. 

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

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

320 versions if new options with similar names are introduced. 

321value : object 

322 New value of option. 

323 

324Returns 

325------- 

326None 

327 

328Raises 

329------ 

330OptionError if no such option exists 

331 

332Notes 

333----- 

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

335 

336The available options with its descriptions: 

337 

338{opts_desc} 

339""" 

340 

341_describe_option_tmpl = """ 

342describe_option(pat, _print_desc=False) 

343 

344Prints the description for one or more registered options. 

345 

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

347 

348Available options: 

349 

350{opts_list} 

351 

352Parameters 

353---------- 

354pat : str 

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

356_print_desc : bool, default True 

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

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

359 (for testing). 

360 

361Returns 

362------- 

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

364is False 

365 

366Notes 

367----- 

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

369 

370The available options with its descriptions: 

371 

372{opts_desc} 

373""" 

374 

375_reset_option_tmpl = """ 

376reset_option(pat) 

377 

378Reset one or more options to their default value. 

379 

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

381 

382Available options: 

383 

384{opts_list} 

385 

386Parameters 

387---------- 

388pat : str/regex 

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

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

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

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

393 

394Returns 

395------- 

396None 

397 

398Notes 

399----- 

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

401 

402The available options with its descriptions: 

403 

404{opts_desc} 

405""" 

406 

407# bind the functions with their docstrings into a Callable 

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

409get_option = CallableDynamicDoc(_get_option, _get_option_tmpl) 

410set_option = CallableDynamicDoc(_set_option, _set_option_tmpl) 

411reset_option = CallableDynamicDoc(_reset_option, _reset_option_tmpl) 

412describe_option = CallableDynamicDoc(_describe_option, _describe_option_tmpl) 

413options = DictWrapper(_global_config) 

414 

415# 

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

417 

418 

419class option_context(ContextDecorator): 

420 """ 

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

422 

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

424 

425 Examples 

426 -------- 

427 >>> from pandas import option_context 

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

429 ... pass 

430 """ 

431 

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

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

434 raise ValueError( 

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

436 ) 

437 

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

439 

440 def __enter__(self) -> None: 

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

442 

443 for pat, val in self.ops: 

444 _set_option(pat, val, silent=True) 

445 

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

447 if self.undo: 

448 for pat, val in self.undo: 

449 _set_option(pat, val, silent=True) 

450 

451 

452def register_option( 

453 key: str, 

454 defval: object, 

455 doc: str = "", 

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

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

458) -> None: 

459 """ 

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

461 

462 Parameters 

463 ---------- 

464 key : str 

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

466 defval : object 

467 Default value of the option. 

468 doc : str 

469 Description of the option. 

470 validator : Callable, optional 

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

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

473 cb 

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

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

476 the full name of the option. 

477 

478 Raises 

479 ------ 

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

481 

482 """ 

483 import keyword 

484 import tokenize 

485 

486 key = key.lower() 

487 

488 if key in _registered_options: 

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

490 if key in _reserved_keys: 

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

492 

493 # the default value should be legal 

494 if validator: 

495 validator(defval) 

496 

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

498 path = key.split(".") 

499 

500 for k in path: 

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

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

503 if keyword.iskeyword(k): 

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

505 

506 cursor = _global_config 

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

508 

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

510 if not isinstance(cursor, dict): 

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

512 if p not in cursor: 

513 cursor[p] = {} 

514 cursor = cursor[p] 

515 

516 if not isinstance(cursor, dict): 

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

518 

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

520 

521 # save the option metadata 

522 _registered_options[key] = RegisteredOption( 

523 key=key, defval=defval, doc=doc, validator=validator, cb=cb 

524 ) 

525 

526 

527def deprecate_option( 

528 key: str, 

529 msg: str | None = None, 

530 rkey: str | None = None, 

531 removal_ver: str | None = None, 

532) -> None: 

533 """ 

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

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

536 if not. 

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

538 

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

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

541 deprecation warning is given. 

542 

543 Parameters 

544 ---------- 

545 key : str 

546 Name of the option to be deprecated. 

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

548 msg : str, optional 

549 Warning message to output when the key is referenced. 

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

551 rkey : str, optional 

552 Name of an option to reroute access to. 

553 If specified, any referenced `key` will be 

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

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

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

557 removal_ver : str, optional 

558 Specifies the version in which this option will 

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

560 

561 Raises 

562 ------ 

563 OptionError 

564 If the specified key has already been deprecated. 

565 """ 

566 key = key.lower() 

567 

568 if key in _deprecated_options: 

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

570 

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

572 

573 

574# 

575# functions internal to the module 

576 

577 

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

579 """ 

580 returns a list of keys matching `pat` 

581 

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

583 """ 

584 # short-circuit for exact key 

585 if pat in _registered_options: 

586 return [pat] 

587 

588 # else look through all of them 

589 keys = sorted(_registered_options.keys()) 

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

591 return keys 

592 

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

594 

595 

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

597 path = key.split(".") 

598 cursor = _global_config 

599 for p in path[:-1]: 

600 cursor = cursor[p] 

601 return cursor, path[-1] 

602 

603 

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

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

606 key = key.lower() 

607 return key in _deprecated_options 

608 

609 

610def _get_deprecated_option(key: str): 

611 """ 

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

613 

614 Returns 

615 ------- 

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

617 """ 

618 try: 

619 d = _deprecated_options[key] 

620 except KeyError: 

621 return None 

622 else: 

623 return d 

624 

625 

626def _get_registered_option(key: str): 

627 """ 

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

629 

630 Returns 

631 ------- 

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

633 """ 

634 return _registered_options.get(key) 

635 

636 

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

638 """ 

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

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

641 """ 

642 d = _get_deprecated_option(key) 

643 if d: 

644 return d.rkey or key 

645 else: 

646 return key 

647 

648 

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

650 """ 

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

652 

653 Returns 

654 ------- 

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

656 """ 

657 d = _get_deprecated_option(key) 

658 if d: 

659 if d.msg: 

660 warnings.warn( 

661 d.msg, 

662 FutureWarning, 

663 stacklevel=find_stack_level(), 

664 ) 

665 else: 

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

667 if d.removal_ver: 

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

669 if d.rkey: 

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

671 else: 

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

673 

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

675 return True 

676 return False 

677 

678 

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

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

681 o = _get_registered_option(k) 

682 d = _get_deprecated_option(k) 

683 

684 s = f"{k} " 

685 

686 if o.doc: 

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

688 else: 

689 s += "No description available." 

690 

691 if o: 

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

693 

694 if d: 

695 rkey = d.rkey or "" 

696 s += "\n (Deprecated" 

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

698 s += ")" 

699 

700 return s 

701 

702 

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

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

705 from itertools import groupby 

706 from textwrap import wrap 

707 

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

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

710 ls = wrap( 

711 ", ".join(ks), 

712 width, 

713 initial_indent=pfx, 

714 subsequent_indent=" ", 

715 break_long_words=False, 

716 ) 

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

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

719 return ls 

720 

721 ls: list[str] = [] 

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

723 if singles: 

724 ls += pp("", singles) 

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

726 

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

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

729 ls += pp(k, ks) 

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

731 if _print: 

732 print(s) 

733 else: 

734 return s 

735 

736 

737# 

738# helpers 

739 

740 

741@contextmanager 

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

743 """ 

744 contextmanager for multiple invocations of API with a common prefix 

745 

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

747 

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

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

750 

751 Example 

752 ------- 

753 import pandas._config.config as cf 

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

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

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

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

758 cf.get_option(size) 

759 ... 

760 

761 etc' 

762 

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

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

765 """ 

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

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

768 

769 global register_option, get_option, set_option 

770 

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

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

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

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

775 

776 return cast(F, inner) 

777 

778 _register_option = register_option 

779 _get_option = get_option 

780 _set_option = set_option 

781 set_option = wrap(set_option) 

782 get_option = wrap(get_option) 

783 register_option = wrap(register_option) 

784 try: 

785 yield 

786 finally: 

787 set_option = _set_option 

788 get_option = _get_option 

789 register_option = _register_option 

790 

791 

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

793# arg in register_option 

794 

795 

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

797 """ 

798 

799 Parameters 

800 ---------- 

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

802 

803 Returns 

804 ------- 

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

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

807 

808 """ 

809 

810 def inner(x) -> None: 

811 if type(x) != _type: 

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

813 

814 return inner 

815 

816 

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

818 """ 

819 

820 Parameters 

821 ---------- 

822 `_type` - the type to be checked against 

823 

824 Returns 

825 ------- 

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

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

828 

829 """ 

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

831 _type = tuple(_type) 

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

833 else: 

834 type_repr = f"'{_type}'" 

835 

836 def inner(x) -> None: 

837 if not isinstance(x, _type): 

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

839 

840 return inner 

841 

842 

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

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

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

846 

847 def inner(x) -> None: 

848 if x not in legal_values: 

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

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

851 pp_values = "|".join(uvals) 

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

853 if len(callables): 

854 msg += " or a callable" 

855 raise ValueError(msg) 

856 

857 return inner 

858 

859 

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

861 """ 

862 Verify that value is None or a positive int. 

863 

864 Parameters 

865 ---------- 

866 value : None or int 

867 The `value` to be checked. 

868 

869 Raises 

870 ------ 

871 ValueError 

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

873 """ 

874 if value is None: 

875 return 

876 

877 elif isinstance(value, int): 

878 if value >= 0: 

879 return 

880 

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

882 raise ValueError(msg) 

883 

884 

885# common type validators, for convenience 

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

887is_int = is_type_factory(int) 

888is_bool = is_type_factory(bool) 

889is_float = is_type_factory(float) 

890is_str = is_type_factory(str) 

891is_text = is_instance_factory((str, bytes)) 

892 

893 

894def is_callable(obj) -> bool: 

895 """ 

896 

897 Parameters 

898 ---------- 

899 `obj` - the object to be checked 

900 

901 Returns 

902 ------- 

903 validator - returns True if object is callable 

904 raises ValueError otherwise. 

905 

906 """ 

907 if not callable(obj): 

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

909 return True