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
« 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.
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 __future__ import annotations
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
69from pandas._typing import (
70 F,
71 T,
72)
73from pandas.util._exceptions import find_stack_level
76class DeprecatedOption(NamedTuple):
77 key: str
78 msg: str | None
79 rkey: str | None
80 removal_ver: str | None
83class RegisteredOption(NamedTuple):
84 key: str
85 defval: object
86 doc: str
87 validator: Callable[[object], Any] | None
88 cb: Callable[[str], Any] | None
91# holds deprecated option metadata
92_deprecated_options: dict[str, DeprecatedOption] = {}
94# holds registered option metadata
95_registered_options: dict[str, RegisteredOption] = {}
97# holds the current values for registered options
98_global_config: dict[str, Any] = {}
100# keys which have a special meaning
101_reserved_keys: list[str] = ["all"]
104class OptionError(AttributeError, KeyError):
105 """
106 Exception raised for pandas.options.
108 Backwards compatible with KeyError checks.
109 """
112#
113# User API
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]
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)
129 key = _translate_key(key)
131 return key
134def _get_option(pat: str, silent: bool = False) -> Any:
135 key = _get_single_key(pat, silent)
137 # walk the nested dict
138 root, k = _get_root(key)
139 return root[k]
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")
148 # default to false
149 silent = kwargs.pop("silent", False)
151 if kwargs:
152 kwarg = list(kwargs.keys())[0]
153 raise TypeError(f'_set_option() got an unexpected keyword argument "{kwarg}"')
155 for k, v in zip(args[::2], args[1::2]):
156 key = _get_single_key(k, silent)
158 o = _get_registered_option(key)
159 if o and o.validator:
160 o.validator(v)
162 # walk the nested dict
163 root, k = _get_root(key)
164 root[k] = v
166 if o.cb:
167 if silent:
168 with warnings.catch_warnings(record=True):
169 o.cb(key)
170 else:
171 o.cb(key)
174def _describe_option(pat: str = "", _print_desc: bool = True) -> str | None:
176 keys = _select_options(pat)
177 if len(keys) == 0:
178 raise OptionError("No such keys(s)")
180 s = "\n".join([_build_option_description(k) for k in keys])
182 if _print_desc:
183 print(s)
184 return None
185 return s
188def _reset_option(pat: str, silent: bool = False) -> None:
190 keys = _select_options(pat)
192 if len(keys) == 0:
193 raise OptionError("No such keys(s)")
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 )
202 for k in keys:
203 _set_option(k, _registered_options[k].defval, silent=silent)
206def get_default_val(pat: str):
207 key = _get_single_key(pat, silent=True)
208 return _get_registered_option(key).defval
211class DictWrapper:
212 """provide attribute-style access to a nested dict"""
214 def __init__(self, d: dict[str, Any], prefix: str = "") -> None:
215 object.__setattr__(self, "d", d)
216 object.__setattr__(self, "prefix", prefix)
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")
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)
244 def __dir__(self) -> Iterable[str]:
245 return list(self.d.keys())
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.
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
262 def __call__(self, *args, **kwds) -> T:
263 return self.__func__(*args, **kwds)
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)
273_get_option_tmpl = """
274get_option(pat)
276Retrieves the value of the specified option.
278Available options:
280{opts_list}
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.
290Returns
291-------
292result : the value of the option
294Raises
295------
296OptionError : if no such option exists
298Notes
299-----
300Please reference the :ref:`User Guide <options>` for more information.
302The available options with its descriptions:
304{opts_desc}
305"""
307_set_option_tmpl = """
308set_option(pat, value)
310Sets the value of the specified option.
312Available options:
314{opts_list}
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.
326Returns
327-------
328None
330Raises
331------
332OptionError if no such option exists
334Notes
335-----
336Please reference the :ref:`User Guide <options>` for more information.
338The available options with its descriptions:
340{opts_desc}
341"""
343_describe_option_tmpl = """
344describe_option(pat, _print_desc=False)
346Prints the description for one or more registered options.
348Call with no arguments to get a listing for all registered options.
350Available options:
352{opts_list}
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).
363Returns
364-------
365None by default, the description(s) as a unicode string if _print_desc
366is False
368Notes
369-----
370Please reference the :ref:`User Guide <options>` for more information.
372The available options with its descriptions:
374{opts_desc}
375"""
377_reset_option_tmpl = """
378reset_option(pat)
380Reset one or more options to their default value.
382Pass "all" as argument to reset all options.
384Available options:
386{opts_list}
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.
396Returns
397-------
398None
400Notes
401-----
402Please reference the :ref:`User Guide <options>` for more information.
404The available options with its descriptions:
406{opts_desc}
407"""
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)
417#
418# Functions for use by pandas developers, in addition to User - api
421class option_context(ContextDecorator):
422 """
423 Context manager to temporarily set options in the `with` statement context.
425 You need to invoke as ``option_context(pat, val, [(pat, val), ...])``.
427 Examples
428 --------
429 >>> with option_context('display.max_rows', 10, 'display.max_columns', 5):
430 ... pass
431 """
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 )
439 self.ops = list(zip(args[::2], args[1::2]))
441 def __enter__(self) -> None:
442 self.undo = [(pat, _get_option(pat, silent=True)) for pat, val in self.ops]
444 for pat, val in self.ops:
445 _set_option(pat, val, silent=True)
447 def __exit__(self, *args) -> None:
448 if self.undo:
449 for pat, val in self.undo:
450 _set_option(pat, val, silent=True)
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
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.
479 Raises
480 ------
481 ValueError if `validator` is specified and `defval` is not a valid value.
483 """
484 import keyword
485 import tokenize
487 key = key.lower()
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")
494 # the default value should be legal
495 if validator:
496 validator(defval)
498 # walk the nested dict, creating dicts as needed along the path
499 path = key.split(".")
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")
507 cursor = _global_config
508 msg = "Path prefix to option '{option}' is already an option"
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]
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])))
520 cursor[path[-1]] = defval # initialize
522 # save the option metadata
523 _registered_options[key] = RegisteredOption(
524 key=key, defval=defval, doc=doc, validator=validator, cb=cb
525 )
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`.
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.
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.
562 Raises
563 ------
564 OptionError
565 If the specified key has already been deprecated.
566 """
567 key = key.lower()
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.")
572 _deprecated_options[key] = DeprecatedOption(key, msg, rkey, removal_ver)
575#
576# functions internal to the module
579def _select_options(pat: str) -> list[str]:
580 """
581 returns a list of keys matching `pat`
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]
589 # else look through all of them
590 keys = sorted(_registered_options.keys())
591 if pat == "all": # reserved key
592 return keys
594 return [k for k in keys if re.search(pat, k, re.I)]
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]
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
611def _get_deprecated_option(key: str):
612 """
613 Retrieves the metadata for a deprecated option, if `key` is deprecated.
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
627def _get_registered_option(key: str):
628 """
629 Retrieves the option metadata if `key` is a registered option.
631 Returns
632 -------
633 RegisteredOption (namedtuple) if key is deprecated, None otherwise
634 """
635 return _registered_options.get(key)
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
650def _warn_if_deprecated(key: str) -> bool:
651 """
652 Checks if `key` is a deprecated option and if so, prints a warning.
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."
675 warnings.warn(msg, FutureWarning, stacklevel=find_stack_level())
676 return True
677 return False
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)
685 s = f"{k} "
687 if o.doc:
688 s += "\n".join(o.doc.strip().split("\n"))
689 else:
690 s += "No description available."
692 if o:
693 s += f"\n [default: {o.defval}] [currently: {_get_option(k, True)}]"
695 if d:
696 rkey = d.rkey or ""
697 s += "\n (Deprecated"
698 s += f", use `{rkey}` instead."
699 s += ")"
701 return s
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
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
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]
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
738#
739# helpers
742@contextmanager
743def config_prefix(prefix) -> Iterator[None]:
744 """
745 contextmanager for multiple invocations of API with a common prefix
747 supported API functions: (register / get / set )__option
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.
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 ...
762 etc'
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
770 global register_option, get_option, set_option, reset_option
772 def wrap(func: F) -> F:
773 def inner(key: str, *args, **kwds):
774 pkey = f"{prefix}.{key}"
775 return func(pkey, *args, **kwds)
777 return cast(F, inner)
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
793# These factories and methods are handy for use as the validator
794# arg in register_option
797def is_type_factory(_type: type[Any]) -> Callable[[Any], None]:
798 """
800 Parameters
801 ----------
802 `_type` - a type to be compared against (e.g. type(x) == `_type`)
804 Returns
805 -------
806 validator - a function of a single argument x , which raises
807 ValueError if type(x) is not equal to `_type`
809 """
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}'")
815 return inner
818def is_instance_factory(_type) -> Callable[[Any], None]:
819 """
821 Parameters
822 ----------
823 `_type` - the type to be checked against
825 Returns
826 -------
827 validator - a function of a single argument x , which raises
828 ValueError if x is not an instance of `_type`
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}'"
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}")
841 return inner
844def is_one_of_factory(legal_values) -> Callable[[Any], None]:
846 callables = [c for c in legal_values if callable(c)]
847 legal_values = [c for c in legal_values if not callable(c)]
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
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)
860 return inner
863def is_nonnegative_int(value: object) -> None:
864 """
865 Verify that value is None or a positive int.
867 Parameters
868 ----------
869 value : None or int
870 The `value` to be checked.
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
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
884 msg = "Value must be a nonnegative integer or None"
885 raise ValueError(msg)
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))
897def is_callable(obj) -> bool:
898 """
900 Parameters
901 ----------
902 `obj` - the object to be checked
904 Returns
905 -------
906 validator - returns True if object is callable
907 raises ValueError otherwise.
909 """
910 if not callable(obj):
911 raise ValueError("Value must be a callable")
912 return True