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