Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/pandas/_config/config.py : 51%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2The config module holds package-wide configurables and provides
3a uniform API for working with them.
5Overview
6========
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.
30Implementation
31==============
33- Data is stored using nested dictionaries, and should be accessed
34 through the provided API.
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".
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.
46- `config_prefix` is a context_manager (for use with the `with` keyword)
47 which can save developers some typing, see the docstring.
49"""
51from collections import namedtuple
52from contextlib import contextmanager
53import re
54from typing import Any, Dict, Iterable, List
55import warnings
57DeprecatedOption = namedtuple("DeprecatedOption", "key msg rkey removal_ver")
58RegisteredOption = namedtuple("RegisteredOption", "key defval doc validator cb")
60# holds deprecated option metadata
61_deprecated_options: Dict[str, DeprecatedOption] = {}
63# holds registered option metadata
64_registered_options: Dict[str, RegisteredOption] = {}
66# holds the current values for registered options
67_global_config: Dict[str, Any] = {}
69# keys which have a special meaning
70_reserved_keys: List[str] = ["all"]
73class OptionError(AttributeError, KeyError):
74 """Exception for pandas.options, backwards compatible with KeyError
75 checks
76 """
79#
80# User API
83def _get_single_key(pat, silent):
84 keys = _select_options(pat)
85 if len(keys) == 0:
86 if not silent:
87 _warn_if_deprecated(pat)
88 raise OptionError(f"No such keys(s): {repr(pat)}")
89 if len(keys) > 1:
90 raise OptionError("Pattern matched multiple keys")
91 key = keys[0]
93 if not silent:
94 _warn_if_deprecated(key)
96 key = _translate_key(key)
98 return key
101def _get_option(pat, silent=False):
102 key = _get_single_key(pat, silent)
104 # walk the nested dict
105 root, k = _get_root(key)
106 return root[k]
109def _set_option(*args, **kwargs):
110 # must at least 1 arg deal with constraints later
111 nargs = len(args)
112 if not nargs or nargs % 2 != 0:
113 raise ValueError("Must provide an even number of non-keyword arguments")
115 # default to false
116 silent = kwargs.pop("silent", False)
118 if kwargs:
119 kwarg = list(kwargs.keys())[0]
120 raise TypeError(f'_set_option() got an unexpected keyword argument "{kwarg}"')
122 for k, v in zip(args[::2], args[1::2]):
123 key = _get_single_key(k, silent)
125 o = _get_registered_option(key)
126 if o and o.validator:
127 o.validator(v)
129 # walk the nested dict
130 root, k = _get_root(key)
131 root[k] = v
133 if o.cb:
134 if silent:
135 with warnings.catch_warnings(record=True):
136 o.cb(key)
137 else:
138 o.cb(key)
141def _describe_option(pat="", _print_desc=True):
143 keys = _select_options(pat)
144 if len(keys) == 0:
145 raise OptionError("No such keys(s)")
147 s = "\n".join([_build_option_description(k) for k in keys])
149 if _print_desc:
150 print(s)
151 else:
152 return s
155def _reset_option(pat, silent=False):
157 keys = _select_options(pat)
159 if len(keys) == 0:
160 raise OptionError("No such keys(s)")
162 if len(keys) > 1 and len(pat) < 4 and pat != "all":
163 raise ValueError(
164 "You must specify at least 4 characters when "
165 "resetting multiple keys, use the special keyword "
166 '"all" to reset all the options to their default '
167 "value"
168 )
170 for k in keys:
171 _set_option(k, _registered_options[k].defval, silent=silent)
174def get_default_val(pat):
175 key = _get_single_key(pat, silent=True)
176 return _get_registered_option(key).defval
179class DictWrapper:
180 """ provide attribute-style access to a nested dict"""
182 def __init__(self, d, prefix=""):
183 object.__setattr__(self, "d", d)
184 object.__setattr__(self, "prefix", prefix)
186 def __setattr__(self, key, val):
187 prefix = object.__getattribute__(self, "prefix")
188 if prefix:
189 prefix += "."
190 prefix += key
191 # you can't set new keys
192 # can you can't overwrite subtrees
193 if key in self.d and not isinstance(self.d[key], dict):
194 _set_option(prefix, val)
195 else:
196 raise OptionError("You can only set the value of existing options")
198 def __getattr__(self, key: str):
199 prefix = object.__getattribute__(self, "prefix")
200 if prefix:
201 prefix += "."
202 prefix += key
203 try:
204 v = object.__getattribute__(self, "d")[key]
205 except KeyError:
206 raise OptionError("No such option")
207 if isinstance(v, dict):
208 return DictWrapper(v, prefix)
209 else:
210 return _get_option(prefix)
212 def __dir__(self):
213 return list(self.d.keys())
216# For user convenience, we'd like to have the available options described
217# in the docstring. For dev convenience we'd like to generate the docstrings
218# dynamically instead of maintaining them by hand. To this, we use the
219# class below which wraps functions inside a callable, and converts
220# __doc__ into a property function. The doctsrings below are templates
221# using the py2.6+ advanced formatting syntax to plug in a concise list
222# of options, and option descriptions.
225class CallableDynamicDoc:
226 def __init__(self, func, doc_tmpl):
227 self.__doc_tmpl__ = doc_tmpl
228 self.__func__ = func
230 def __call__(self, *args, **kwds):
231 return self.__func__(*args, **kwds)
233 @property
234 def __doc__(self):
235 opts_desc = _describe_option("all", _print_desc=False)
236 opts_list = pp_options_list(list(_registered_options.keys()))
237 return self.__doc_tmpl__.format(opts_desc=opts_desc, opts_list=opts_list)
240_get_option_tmpl = """
241get_option(pat)
243Retrieves the value of the specified option.
245Available options:
247{opts_list}
249Parameters
250----------
251pat : str
252 Regexp which should match a single option.
253 Note: partial matches are supported for convenience, but unless you use the
254 full option name (e.g. x.y.z.option_name), your code may break in future
255 versions if new options with similar names are introduced.
257Returns
258-------
259result : the value of the option
261Raises
262------
263OptionError : if no such option exists
265Notes
266-----
267The available options with its descriptions:
269{opts_desc}
270"""
272_set_option_tmpl = """
273set_option(pat, value)
275Sets the value of the specified option.
277Available options:
279{opts_list}
281Parameters
282----------
283pat : str
284 Regexp which should match a single option.
285 Note: partial matches are supported for convenience, but unless you use the
286 full option name (e.g. x.y.z.option_name), your code may break in future
287 versions if new options with similar names are introduced.
288value : object
289 New value of option.
291Returns
292-------
293None
295Raises
296------
297OptionError if no such option exists
299Notes
300-----
301The available options with its descriptions:
303{opts_desc}
304"""
306_describe_option_tmpl = """
307describe_option(pat, _print_desc=False)
309Prints the description for one or more registered options.
311Call with not arguments to get a listing for all registered options.
313Available options:
315{opts_list}
317Parameters
318----------
319pat : str
320 Regexp pattern. All matching keys will have their description displayed.
321_print_desc : bool, default True
322 If True (default) the description(s) will be printed to stdout.
323 Otherwise, the description(s) will be returned as a unicode string
324 (for testing).
326Returns
327-------
328None by default, the description(s) as a unicode string if _print_desc
329is False
331Notes
332-----
333The available options with its descriptions:
335{opts_desc}
336"""
338_reset_option_tmpl = """
339reset_option(pat)
341Reset one or more options to their default value.
343Pass "all" as argument to reset all options.
345Available options:
347{opts_list}
349Parameters
350----------
351pat : str/regex
352 If specified only options matching `prefix*` will be reset.
353 Note: partial matches are supported for convenience, but unless you
354 use the full option name (e.g. x.y.z.option_name), your code may break
355 in future versions if new options with similar names are introduced.
357Returns
358-------
359None
361Notes
362-----
363The available options with its descriptions:
365{opts_desc}
366"""
368# bind the functions with their docstrings into a Callable
369# and use that as the functions exposed in pd.api
370get_option = CallableDynamicDoc(_get_option, _get_option_tmpl)
371set_option = CallableDynamicDoc(_set_option, _set_option_tmpl)
372reset_option = CallableDynamicDoc(_reset_option, _reset_option_tmpl)
373describe_option = CallableDynamicDoc(_describe_option, _describe_option_tmpl)
374options = DictWrapper(_global_config)
376#
377# Functions for use by pandas developers, in addition to User - api
380class option_context:
381 """
382 Context manager to temporarily set options in the `with` statement context.
384 You need to invoke as ``option_context(pat, val, [(pat, val), ...])``.
386 Examples
387 --------
389 >>> with option_context('display.max_rows', 10, 'display.max_columns', 5):
390 ... ...
391 """
393 def __init__(self, *args):
394 if not (len(args) % 2 == 0 and len(args) >= 2):
395 raise ValueError(
396 "Need to invoke as option_context(pat, val, [(pat, val), ...])."
397 )
399 self.ops = list(zip(args[::2], args[1::2]))
401 def __enter__(self):
402 self.undo = [(pat, _get_option(pat, silent=True)) for pat, val in self.ops]
404 for pat, val in self.ops:
405 _set_option(pat, val, silent=True)
407 def __exit__(self, *args):
408 if self.undo:
409 for pat, val in self.undo:
410 _set_option(pat, val, silent=True)
413def register_option(key: str, defval: object, doc="", validator=None, cb=None):
414 """Register an option in the package-wide pandas config object
416 Parameters
417 ----------
418 key - a fully-qualified key, e.g. "x.y.option - z".
419 defval - the default value of the option
420 doc - a string description of the option
421 validator - a function of a single argument, should raise `ValueError` if
422 called with a value which is not a legal value for the option.
423 cb - a function of a single argument "key", which is called
424 immediately after an option value is set/reset. key is
425 the full name of the option.
427 Returns
428 -------
429 Nothing.
431 Raises
432 ------
433 ValueError if `validator` is specified and `defval` is not a valid value.
435 """
436 import tokenize
437 import keyword
439 key = key.lower()
441 if key in _registered_options:
442 raise OptionError(f"Option '{key}' has already been registered")
443 if key in _reserved_keys:
444 raise OptionError(f"Option '{key}' is a reserved key")
446 # the default value should be legal
447 if validator:
448 validator(defval)
450 # walk the nested dict, creating dicts as needed along the path
451 path = key.split(".")
453 for k in path:
454 # NOTE: tokenize.Name is not a public constant
455 # error: Module has no attribute "Name" [attr-defined]
456 if not re.match("^" + tokenize.Name + "$", k): # type: ignore
457 raise ValueError(f"{k} is not a valid identifier")
458 if keyword.iskeyword(k):
459 raise ValueError(f"{k} is a python keyword")
461 cursor = _global_config
462 msg = "Path prefix to option '{option}' is already an option"
464 for i, p in enumerate(path[:-1]):
465 if not isinstance(cursor, dict):
466 raise OptionError(msg.format(option=".".join(path[:i])))
467 if p not in cursor:
468 cursor[p] = {}
469 cursor = cursor[p]
471 if not isinstance(cursor, dict):
472 raise OptionError(msg.format(option=".".join(path[:-1])))
474 cursor[path[-1]] = defval # initialize
476 # save the option metadata
477 _registered_options[key] = RegisteredOption(
478 key=key, defval=defval, doc=doc, validator=validator, cb=cb
479 )
482def deprecate_option(key, msg=None, rkey=None, removal_ver=None):
483 """
484 Mark option `key` as deprecated, if code attempts to access this option,
485 a warning will be produced, using `msg` if given, or a default message
486 if not.
487 if `rkey` is given, any access to the key will be re-routed to `rkey`.
489 Neither the existence of `key` nor that if `rkey` is checked. If they
490 do not exist, any subsequence access will fail as usual, after the
491 deprecation warning is given.
493 Parameters
494 ----------
495 key - the name of the option to be deprecated. must be a fully-qualified
496 option name (e.g "x.y.z.rkey").
498 msg - (Optional) a warning message to output when the key is referenced.
499 if no message is given a default message will be emitted.
501 rkey - (Optional) the name of an option to reroute access to.
502 If specified, any referenced `key` will be re-routed to `rkey`
503 including set/get/reset.
504 rkey must be a fully-qualified option name (e.g "x.y.z.rkey").
505 used by the default message if no `msg` is specified.
507 removal_ver - (Optional) specifies the version in which this option will
508 be removed. used by the default message if no `msg`
509 is specified.
511 Returns
512 -------
513 Nothing
515 Raises
516 ------
517 OptionError - if key has already been deprecated.
519 """
521 key = key.lower()
523 if key in _deprecated_options:
524 raise OptionError(f"Option '{key}' has already been defined as deprecated.")
526 _deprecated_options[key] = DeprecatedOption(key, msg, rkey, removal_ver)
529#
530# functions internal to the module
533def _select_options(pat):
534 """returns a list of keys matching `pat`
536 if pat=="all", returns all registered options
537 """
539 # short-circuit for exact key
540 if pat in _registered_options:
541 return [pat]
543 # else look through all of them
544 keys = sorted(_registered_options.keys())
545 if pat == "all": # reserved key
546 return keys
548 return [k for k in keys if re.search(pat, k, re.I)]
551def _get_root(key):
552 path = key.split(".")
553 cursor = _global_config
554 for p in path[:-1]:
555 cursor = cursor[p]
556 return cursor, path[-1]
559def _is_deprecated(key):
560 """ Returns True if the given option has been deprecated """
562 key = key.lower()
563 return key in _deprecated_options
566def _get_deprecated_option(key):
567 """
568 Retrieves the metadata for a deprecated option, if `key` is deprecated.
570 Returns
571 -------
572 DeprecatedOption (namedtuple) if key is deprecated, None otherwise
573 """
575 try:
576 d = _deprecated_options[key]
577 except KeyError:
578 return None
579 else:
580 return d
583def _get_registered_option(key):
584 """
585 Retrieves the option metadata if `key` is a registered option.
587 Returns
588 -------
589 RegisteredOption (namedtuple) if key is deprecated, None otherwise
590 """
591 return _registered_options.get(key)
594def _translate_key(key):
595 """
596 if key id deprecated and a replacement key defined, will return the
597 replacement key, otherwise returns `key` as - is
598 """
600 d = _get_deprecated_option(key)
601 if d:
602 return d.rkey or key
603 else:
604 return key
607def _warn_if_deprecated(key):
608 """
609 Checks if `key` is a deprecated option and if so, prints a warning.
611 Returns
612 -------
613 bool - True if `key` is deprecated, False otherwise.
614 """
616 d = _get_deprecated_option(key)
617 if d:
618 if d.msg:
619 print(d.msg)
620 warnings.warn(d.msg, FutureWarning)
621 else:
622 msg = f"'{key}' is deprecated"
623 if d.removal_ver:
624 msg += f" and will be removed in {d.removal_ver}"
625 if d.rkey:
626 msg += f", please use '{d.rkey}' instead."
627 else:
628 msg += ", please refrain from using it."
630 warnings.warn(msg, FutureWarning)
631 return True
632 return False
635def _build_option_description(k):
636 """ Builds a formatted description of a registered option and prints it """
638 o = _get_registered_option(k)
639 d = _get_deprecated_option(k)
641 s = f"{k} "
643 if o.doc:
644 s += "\n".join(o.doc.strip().split("\n"))
645 else:
646 s += "No description available."
648 if o:
649 s += f"\n [default: {o.defval}] [currently: {_get_option(k, True)}]"
651 if d:
652 rkey = d.rkey if d.rkey else ""
653 s += "\n (Deprecated"
654 s += f", use `{rkey}` instead."
655 s += ")"
657 return s
660def pp_options_list(keys, width=80, _print=False):
661 """ Builds a concise listing of available options, grouped by prefix """
663 from textwrap import wrap
664 from itertools import groupby
666 def pp(name: str, ks: Iterable[str]) -> List[str]:
667 pfx = "- " + name + ".[" if name else ""
668 ls = wrap(
669 ", ".join(ks),
670 width,
671 initial_indent=pfx,
672 subsequent_indent=" ",
673 break_long_words=False,
674 )
675 if ls and ls[-1] and name:
676 ls[-1] = ls[-1] + "]"
677 return ls
679 ls: List[str] = []
680 singles = [x for x in sorted(keys) if x.find(".") < 0]
681 if singles:
682 ls += pp("", singles)
683 keys = [x for x in keys if x.find(".") >= 0]
685 for k, g in groupby(sorted(keys), lambda x: x[: x.rfind(".")]):
686 ks = [x[len(k) + 1 :] for x in list(g)]
687 ls += pp(k, ks)
688 s = "\n".join(ls)
689 if _print:
690 print(s)
691 else:
692 return s
695#
696# helpers
699@contextmanager
700def config_prefix(prefix):
701 """contextmanager for multiple invocations of API with a common prefix
703 supported API functions: (register / get / set )__option
705 Warning: This is not thread - safe, and won't work properly if you import
706 the API functions into your module using the "from x import y" construct.
708 Example:
710 import pandas._config.config as cf
711 with cf.config_prefix("display.font"):
712 cf.register_option("color", "red")
713 cf.register_option("size", " 5 pt")
714 cf.set_option(size, " 6 pt")
715 cf.get_option(size)
716 ...
718 etc'
720 will register options "display.font.color", "display.font.size", set the
721 value of "display.font.size"... and so on.
722 """
724 # Note: reset_option relies on set_option, and on key directly
725 # it does not fit in to this monkey-patching scheme
727 global register_option, get_option, set_option, reset_option
729 def wrap(func):
730 def inner(key, *args, **kwds):
731 pkey = f"{prefix}.{key}"
732 return func(pkey, *args, **kwds)
734 return inner
736 _register_option = register_option
737 _get_option = get_option
738 _set_option = set_option
739 set_option = wrap(set_option)
740 get_option = wrap(get_option)
741 register_option = wrap(register_option)
742 yield None
743 set_option = _set_option
744 get_option = _get_option
745 register_option = _register_option
748# These factories and methods are handy for use as the validator
749# arg in register_option
752def is_type_factory(_type):
753 """
755 Parameters
756 ----------
757 `_type` - a type to be compared against (e.g. type(x) == `_type`)
759 Returns
760 -------
761 validator - a function of a single argument x , which raises
762 ValueError if type(x) is not equal to `_type`
764 """
766 def inner(x):
767 if type(x) != _type:
768 raise ValueError(f"Value must have type '{_type}'")
770 return inner
773def is_instance_factory(_type):
774 """
776 Parameters
777 ----------
778 `_type` - the type to be checked against
780 Returns
781 -------
782 validator - a function of a single argument x , which raises
783 ValueError if x is not an instance of `_type`
785 """
787 if isinstance(_type, (tuple, list)):
788 _type = tuple(_type)
789 type_repr = "|".join(map(str, _type))
790 else:
791 type_repr = f"'{_type}'"
793 def inner(x):
794 if not isinstance(x, _type):
795 raise ValueError(f"Value must be an instance of {type_repr}")
797 return inner
800def is_one_of_factory(legal_values):
802 callables = [c for c in legal_values if callable(c)]
803 legal_values = [c for c in legal_values if not callable(c)]
805 def inner(x):
806 if x not in legal_values:
808 if not any(c(x) for c in callables):
809 uvals = [str(lval) for lval in legal_values]
810 pp_values = "|".join(uvals)
811 msg = f"Value must be one of {pp_values}"
812 if len(callables):
813 msg += " or a callable"
814 raise ValueError(msg)
816 return inner
819def is_nonnegative_int(value):
820 """
821 Verify that value is None or a positive int.
823 Parameters
824 ----------
825 value : None or int
826 The `value` to be checked.
828 Raises
829 ------
830 ValueError
831 When the value is not None or is a negative integer
832 """
834 if value is None:
835 return
837 elif isinstance(value, int):
838 if value >= 0:
839 return
841 msg = "Value must be a nonnegative integer or None"
842 raise ValueError(msg)
845# common type validators, for convenience
846# usage: register_option(... , validator = is_int)
847is_int = is_type_factory(int)
848is_bool = is_type_factory(bool)
849is_float = is_type_factory(float)
850is_str = is_type_factory(str)
851is_text = is_instance_factory((str, bytes))
854def is_callable(obj):
855 """
857 Parameters
858 ----------
859 `obj` - the object to be checked
861 Returns
862 -------
863 validator - returns True if object is callable
864 raises ValueError otherwise.
866 """
867 if not callable(obj):
868 raise ValueError("Value must be a callable")
869 return True