Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/boltons/funcutils.py: 25%
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
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
1# Copyright (c) 2013, Mahmoud Hashemi
2#
3# Redistribution and use in source and binary forms, with or without
4# modification, are permitted provided that the following conditions are
5# met:
6#
7# * Redistributions of source code must retain the above copyright
8# notice, this list of conditions and the following disclaimer.
9#
10# * Redistributions in binary form must reproduce the above
11# copyright notice, this list of conditions and the following
12# disclaimer in the documentation and/or other materials provided
13# with the distribution.
14#
15# * The names of the contributors may not be used to endorse or
16# promote products derived from this software without specific
17# prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31"""Python's built-in :mod:`functools` module builds several useful
32utilities on top of Python's first-class function
33support. ``funcutils`` generally stays in the same vein, adding to and
34correcting Python's standard metaprogramming facilities.
35"""
37import sys
38import inspect
39import functools
40import itertools
41import threading
42from inspect import formatannotation
43from types import FunctionType, MethodType
45# For legacy compatibility.
46# boltons used to offer an implementation of total_ordering for Python <2.7
47from functools import total_ordering as total_ordering
49try:
50 from .typeutils import make_sentinel
51 NO_DEFAULT = make_sentinel(var_name='NO_DEFAULT')
52except ImportError:
53 NO_DEFAULT = object()
56def inspect_formatargspec(
57 args, varargs=None, varkw=None, defaults=None,
58 kwonlyargs=(), kwonlydefaults={}, annotations={},
59 formatarg=str,
60 formatvarargs=lambda name: '*' + name,
61 formatvarkw=lambda name: '**' + name,
62 formatvalue=lambda value: '=' + repr(value),
63 formatreturns=lambda text: ' -> ' + text,
64 formatannotation=formatannotation):
65 """Copy formatargspec from python 3.7 standard library.
66 Python 3 has deprecated formatargspec and requested that Signature
67 be used instead, however this requires a full reimplementation
68 of formatargspec() in terms of creating Parameter objects and such.
69 Instead of introducing all the object-creation overhead and having
70 to reinvent from scratch, just copy their compatibility routine.
71 """
73 def formatargandannotation(arg):
74 result = formatarg(arg)
75 if arg in annotations:
76 result += ': ' + formatannotation(annotations[arg])
77 return result
78 specs = []
79 if defaults:
80 firstdefault = len(args) - len(defaults)
81 for i, arg in enumerate(args):
82 spec = formatargandannotation(arg)
83 if defaults and i >= firstdefault:
84 spec = spec + formatvalue(defaults[i - firstdefault])
85 specs.append(spec)
86 if varargs is not None:
87 specs.append(formatvarargs(formatargandannotation(varargs)))
88 else:
89 if kwonlyargs:
90 specs.append('*')
91 if kwonlyargs:
92 for kwonlyarg in kwonlyargs:
93 spec = formatargandannotation(kwonlyarg)
94 if kwonlydefaults and kwonlyarg in kwonlydefaults:
95 spec += formatvalue(kwonlydefaults[kwonlyarg])
96 specs.append(spec)
97 if varkw is not None:
98 specs.append(formatvarkw(formatargandannotation(varkw)))
99 result = '(' + ', '.join(specs) + ')'
100 if 'return' in annotations:
101 result += formatreturns(formatannotation(annotations['return']))
102 return result
105def get_module_callables(mod, ignore=None):
106 """Returns two maps of (*types*, *funcs*) from *mod*, optionally
107 ignoring based on the :class:`bool` return value of the *ignore*
108 callable. *mod* can be a string name of a module in
109 :data:`sys.modules` or the module instance itself.
110 """
111 if isinstance(mod, str):
112 mod = sys.modules[mod]
113 types, funcs = {}, {}
114 for attr_name in dir(mod):
115 if ignore and ignore(attr_name):
116 continue
117 try:
118 attr = getattr(mod, attr_name)
119 except Exception:
120 continue
121 try:
122 attr_mod_name = attr.__module__
123 except AttributeError:
124 continue
125 if attr_mod_name != mod.__name__:
126 continue
127 if isinstance(attr, type):
128 types[attr_name] = attr
129 elif callable(attr):
130 funcs[attr_name] = attr
131 return types, funcs
134def mro_items(type_obj):
135 """Takes a type and returns an iterator over all class variables
136 throughout the type hierarchy (respecting the MRO).
138 >>> sorted(set([k for k, v in mro_items(int) if not k.startswith('__') and 'bytes' not in k and not callable(v)]))
139 ['denominator', 'imag', 'numerator', 'real']
140 """
141 # TODO: handle slots?
142 return itertools.chain.from_iterable(ct.__dict__.items()
143 for ct in type_obj.__mro__)
146def dir_dict(obj, raise_exc=False):
147 """Return a dictionary of attribute names to values for a given
148 object. Unlike ``obj.__dict__``, this function returns all
149 attributes on the object, including ones on parent classes.
150 """
151 # TODO: separate function for handling descriptors on types?
152 ret = {}
153 for k in dir(obj):
154 try:
155 ret[k] = getattr(obj, k)
156 except Exception:
157 if raise_exc:
158 raise
159 return ret
162def copy_function(orig, copy_dict=True):
163 """Returns a shallow copy of the function, including code object,
164 globals, closure, etc.
166 >>> func = lambda: func
167 >>> func() is func
168 True
169 >>> func_copy = copy_function(func)
170 >>> func_copy() is func
171 True
172 >>> func_copy is not func
173 True
175 Args:
176 orig (function): The function to be copied. Must be a
177 function, not just any method or callable.
178 copy_dict (bool): Also copy any attributes set on the function
179 instance. Defaults to ``True``.
180 """
181 ret = FunctionType(orig.__code__,
182 orig.__globals__,
183 name=orig.__name__,
184 argdefs=getattr(orig, "__defaults__", None),
185 closure=getattr(orig, "__closure__", None))
186 if hasattr(orig, "__kwdefaults__"):
187 ret.__kwdefaults__ = orig.__kwdefaults__
188 if copy_dict:
189 ret.__dict__.update(orig.__dict__)
190 return ret
193def partial_ordering(cls):
194 """Class decorator, similar to :func:`functools.total_ordering`,
195 except it is used to define `partial orderings`_ (i.e., it is
196 possible that *x* is neither greater than, equal to, or less than
197 *y*). It assumes the presence of the ``__le__()`` and ``__ge__()``
198 method, but nothing else. It will not override any existing
199 additional comparison methods.
201 .. _partial orderings: https://en.wikipedia.org/wiki/Partially_ordered_set
203 >>> @partial_ordering
204 ... class MySet(set):
205 ... def __le__(self, other):
206 ... return self.issubset(other)
207 ... def __ge__(self, other):
208 ... return self.issuperset(other)
209 ...
210 >>> a = MySet([1,2,3])
211 >>> b = MySet([1,2])
212 >>> c = MySet([1,2,4])
213 >>> b < a
214 True
215 >>> b > a
216 False
217 >>> b < c
218 True
219 >>> a < c
220 False
221 >>> c > a
222 False
223 """
224 def __lt__(self, other): return self <= other and not self >= other
225 def __gt__(self, other): return self >= other and not self <= other
226 def __eq__(self, other): return self >= other and self <= other
228 if not hasattr(cls, '__lt__'): cls.__lt__ = __lt__
229 if not hasattr(cls, '__gt__'): cls.__gt__ = __gt__
230 if not hasattr(cls, '__eq__'): cls.__eq__ = __eq__
232 return cls
235class InstancePartial(functools.partial):
236 """:class:`functools.partial` is a huge convenience for anyone
237 working with Python's great first-class functions. It allows
238 developers to curry arguments and incrementally create simpler
239 callables for a variety of use cases.
241 Unfortunately there's one big gap in its usefulness:
242 methods. Partials just don't get bound as methods and
243 automatically handed a reference to ``self``. The
244 ``InstancePartial`` type remedies this by inheriting from
245 :class:`functools.partial` and implementing the necessary
246 descriptor protocol. There are no other differences in
247 implementation or usage. :class:`CachedInstancePartial`, below,
248 has the same ability, but is slightly more efficient.
250 """
251 @property
252 def _partialmethod(self):
253 # py3.13 switched from _partialmethod to __partialmethod__, this is kept for backwards compat <=py3.12
254 return self.__partialmethod__
256 @property
257 def __partialmethod__(self):
258 return functools.partialmethod(self.func, *self.args, **self.keywords)
260 def __get__(self, obj, obj_type):
261 return MethodType(self, obj)
265class CachedInstancePartial(functools.partial):
266 """The ``CachedInstancePartial`` is virtually the same as
267 :class:`InstancePartial`, adding support for method-usage to
268 :class:`functools.partial`, except that upon first access, it
269 caches the bound method on the associated object, speeding it up
270 for future accesses, and bringing the method call overhead to
271 about the same as non-``partial`` methods.
273 See the :class:`InstancePartial` docstring for more details.
274 """
275 @property
276 def _partialmethod(self):
277 # py3.13 switched from _partialmethod to __partialmethod__, this is kept for backwards compat <=py3.12
278 return self.__partialmethod__
280 @property
281 def __partialmethod__(self):
282 return functools.partialmethod(self.func, *self.args, **self.keywords)
284 def __set_name__(self, obj_type, name):
285 self.__name__ = name
287 def __get__(self, obj, obj_type):
288 # These assignments could've been in __init__, but there was
289 # no simple way to do it without breaking one of PyPy or Py3.
290 self.__name__ = getattr(self, "__name__", None)
291 self.__doc__ = self.func.__doc__
292 self.__module__ = self.func.__module__
294 name = self.__name__
296 if obj is None:
297 return MethodType(self, obj)
298 try:
299 # since this is a data descriptor, this block
300 # is probably only hit once (per object)
301 return obj.__dict__[name]
302 except KeyError:
303 obj.__dict__[name] = ret = MethodType(self, obj)
304 return ret
307partial = CachedInstancePartial
310def format_invocation(name='', args=(), kwargs=None, **kw):
311 """Given a name, positional arguments, and keyword arguments, format
312 a basic Python-style function call.
314 >>> print(format_invocation('func', args=(1, 2), kwargs={'c': 3}))
315 func(1, 2, c=3)
316 >>> print(format_invocation('a_func', args=(1,)))
317 a_func(1)
318 >>> print(format_invocation('kw_func', kwargs=[('a', 1), ('b', 2)]))
319 kw_func(a=1, b=2)
321 """
322 _repr = kw.pop('repr', repr)
323 if kw:
324 raise TypeError('unexpected keyword args: %r' % ', '.join(kw.keys()))
325 kwargs = kwargs or {}
326 a_text = ', '.join([_repr(a) for a in args])
327 if isinstance(kwargs, dict):
328 kwarg_items = [(k, kwargs[k]) for k in sorted(kwargs)]
329 else:
330 kwarg_items = kwargs
331 kw_text = ', '.join([f'{k}={_repr(v)}' for k, v in kwarg_items])
333 all_args_text = a_text
334 if all_args_text and kw_text:
335 all_args_text += ', '
336 all_args_text += kw_text
338 return f'{name}({all_args_text})'
341def format_exp_repr(obj, pos_names, req_names=None, opt_names=None, opt_key=None):
342 """Render an expression-style repr of an object, based on attribute
343 names, which are assumed to line up with arguments to an initializer.
345 >>> class Flag(object):
346 ... def __init__(self, length, width, depth=None):
347 ... self.length = length
348 ... self.width = width
349 ... self.depth = depth
350 ...
352 That's our Flag object, here are some example reprs for it:
354 >>> flag = Flag(5, 10)
355 >>> print(format_exp_repr(flag, ['length', 'width'], [], ['depth']))
356 Flag(5, 10)
357 >>> flag2 = Flag(5, 15, 2)
358 >>> print(format_exp_repr(flag2, ['length'], ['width', 'depth']))
359 Flag(5, width=15, depth=2)
361 By picking the pos_names, req_names, opt_names, and opt_key, you
362 can fine-tune how you want the repr to look.
364 Args:
365 obj (object): The object whose type name will be used and
366 attributes will be checked
367 pos_names (list): Required list of attribute names which will be
368 rendered as positional arguments in the output repr.
369 req_names (list): List of attribute names which will always
370 appear in the keyword arguments in the output repr. Defaults to None.
371 opt_names (list): List of attribute names which may appear in
372 the keyword arguments in the output repr, provided they pass
373 the *opt_key* check. Defaults to None.
374 opt_key (callable): A function or callable which checks whether
375 an opt_name should be in the repr. Defaults to a
376 ``None``-check.
378 """
379 cn = type(obj).__name__
380 req_names = req_names or []
381 opt_names = opt_names or []
382 uniq_names, all_names = set(), []
383 for name in req_names + opt_names:
384 if name in uniq_names:
385 continue
386 uniq_names.add(name)
387 all_names.append(name)
389 if opt_key is None:
390 opt_key = lambda v: v is None
391 assert callable(opt_key)
393 args = [getattr(obj, name, None) for name in pos_names]
395 kw_items = [(name, getattr(obj, name, None)) for name in all_names]
396 kw_items = [(name, val) for name, val in kw_items
397 if not (name in opt_names and opt_key(val))]
399 return format_invocation(cn, args, kw_items)
402def format_nonexp_repr(obj, req_names=None, opt_names=None, opt_key=None):
403 """Format a non-expression-style repr
405 Some object reprs look like object instantiation, e.g., App(r=[], mw=[]).
407 This makes sense for smaller, lower-level objects whose state
408 roundtrips. But a lot of objects contain values that don't
409 roundtrip, like types and functions.
411 For those objects, there is the non-expression style repr, which
412 mimic's Python's default style to make a repr like so:
414 >>> class Flag(object):
415 ... def __init__(self, length, width, depth=None):
416 ... self.length = length
417 ... self.width = width
418 ... self.depth = depth
419 ...
420 >>> flag = Flag(5, 10)
421 >>> print(format_nonexp_repr(flag, ['length', 'width'], ['depth']))
422 <Flag length=5 width=10>
424 If no attributes are specified or set, utilizes the id, not unlike Python's
425 built-in behavior.
427 >>> print(format_nonexp_repr(flag))
428 <Flag id=...>
429 """
430 cn = obj.__class__.__name__
431 req_names = req_names or []
432 opt_names = opt_names or []
433 uniq_names, all_names = set(), []
434 for name in req_names + opt_names:
435 if name in uniq_names:
436 continue
437 uniq_names.add(name)
438 all_names.append(name)
440 if opt_key is None:
441 opt_key = lambda v: v is None
442 assert callable(opt_key)
444 items = [(name, getattr(obj, name, None)) for name in all_names]
445 labels = [f'{name}={val!r}' for name, val in items
446 if not (name in opt_names and opt_key(val))]
447 if not labels:
448 labels = ['id=%s' % id(obj)]
449 ret = '<{} {}>'.format(cn, ' '.join(labels))
450 return ret
454# # #
455# # # Function builder
456# # #
459def wraps(func, injected=None, expected=None, **kw):
460 """Decorator factory to apply update_wrapper() to a wrapper function.
462 Modeled after built-in :func:`functools.wraps`. Returns a decorator
463 that invokes update_wrapper() with the decorated function as the wrapper
464 argument and the arguments to wraps() as the remaining arguments.
465 Default arguments are as for update_wrapper(). This is a convenience
466 function to simplify applying partial() to update_wrapper().
468 Same example as in update_wrapper's doc but with wraps:
470 >>> from boltons.funcutils import wraps
471 >>>
472 >>> def print_return(func):
473 ... @wraps(func)
474 ... def wrapper(*args, **kwargs):
475 ... ret = func(*args, **kwargs)
476 ... print(ret)
477 ... return ret
478 ... return wrapper
479 ...
480 >>> @print_return
481 ... def example():
482 ... '''docstring'''
483 ... return 'example return value'
484 >>>
485 >>> val = example()
486 example return value
487 >>> example.__name__
488 'example'
489 >>> example.__doc__
490 'docstring'
491 """
492 return partial(update_wrapper, func=func, build_from=None,
493 injected=injected, expected=expected, **kw)
496def update_wrapper(wrapper, func, injected=None, expected=None, build_from=None, **kw):
497 """Modeled after the built-in :func:`functools.update_wrapper`,
498 this function is used to make your wrapper function reflect the
499 wrapped function's:
501 * Name
502 * Documentation
503 * Module
504 * Signature
506 The built-in :func:`functools.update_wrapper` copies the first three, but
507 does not copy the signature. This version of ``update_wrapper`` can copy
508 the inner function's signature exactly, allowing seamless usage
509 and :mod:`introspection <inspect>`. Usage is identical to the
510 built-in version::
512 >>> from boltons.funcutils import update_wrapper
513 >>>
514 >>> def print_return(func):
515 ... def wrapper(*args, **kwargs):
516 ... ret = func(*args, **kwargs)
517 ... print(ret)
518 ... return ret
519 ... return update_wrapper(wrapper, func)
520 ...
521 >>> @print_return
522 ... def example():
523 ... '''docstring'''
524 ... return 'example return value'
525 >>>
526 >>> val = example()
527 example return value
528 >>> example.__name__
529 'example'
530 >>> example.__doc__
531 'docstring'
533 In addition, the boltons version of update_wrapper supports
534 modifying the outer signature. By passing a list of
535 *injected* argument names, those arguments will be removed from
536 the outer wrapper's signature, allowing your decorator to provide
537 arguments that aren't passed in.
539 Args:
541 wrapper (function) : The callable to which the attributes of
542 *func* are to be copied.
543 func (function): The callable whose attributes are to be copied.
544 injected (list): An optional list of argument names which
545 should not appear in the new wrapper's signature.
546 expected (list): An optional list of argument names (or (name,
547 default) pairs) representing new arguments introduced by
548 the wrapper (the opposite of *injected*). See
549 :meth:`FunctionBuilder.add_arg()` for more details.
550 build_from (function): The callable from which the new wrapper
551 is built. Defaults to *func*, unless *wrapper* is partial object
552 built from *func*, in which case it defaults to *wrapper*.
553 Useful in some specific cases where *wrapper* and *func* have the
554 same arguments but differ on which are keyword-only and positional-only.
555 update_dict (bool): Whether to copy other, non-standard
556 attributes of *func* over to the wrapper. Defaults to True.
557 inject_to_varkw (bool): Ignore missing arguments when a
558 ``**kwargs``-type catch-all is present. Defaults to True.
559 hide_wrapped (bool): Remove reference to the wrapped function(s)
560 in the updated function.
562 In opposition to the built-in :func:`functools.update_wrapper` bolton's
563 version returns a copy of the function and does not modify anything in place.
564 For more in-depth wrapping of functions, see the
565 :class:`FunctionBuilder` type, on which update_wrapper was built.
566 """
567 if injected is None:
568 injected = []
569 elif isinstance(injected, str):
570 injected = [injected]
571 else:
572 injected = list(injected)
574 expected_items = _parse_wraps_expected(expected)
576 if isinstance(func, (classmethod, staticmethod)):
577 raise TypeError('wraps does not support wrapping classmethods and'
578 ' staticmethods, change the order of wrapping to'
579 ' wrap the underlying function: %r'
580 % (getattr(func, '__func__', None),))
582 update_dict = kw.pop('update_dict', True)
583 inject_to_varkw = kw.pop('inject_to_varkw', True)
584 hide_wrapped = kw.pop('hide_wrapped', False)
585 if kw:
586 raise TypeError('unexpected kwargs: %r' % kw.keys())
588 if isinstance(wrapper, functools.partial) and func is wrapper.func:
589 build_from = build_from or wrapper
591 fb = FunctionBuilder.from_func(build_from or func)
593 for arg in injected:
594 try:
595 fb.remove_arg(arg)
596 except MissingArgument:
597 if inject_to_varkw and fb.varkw is not None:
598 continue # keyword arg will be caught by the varkw
599 raise
601 for arg, default in expected_items:
602 fb.add_arg(arg, default) # may raise ExistingArgument
604 invocation_str = fb.get_invocation_str(target=wrapper)
605 if fb.is_async:
606 fb.body = 'return await _call(%s)' % invocation_str
607 else:
608 fb.body = 'return _call(%s)' % invocation_str
610 execdict = dict(_call=wrapper, _func=func)
611 fully_wrapped = fb.get_func(execdict, with_dict=update_dict)
613 if hide_wrapped and hasattr(fully_wrapped, '__wrapped__'):
614 del fully_wrapped.__dict__['__wrapped__']
615 elif not hide_wrapped:
616 fully_wrapped.__wrapped__ = func # ref to the original function (#115)
618 return fully_wrapped
621def _parse_wraps_expected(expected):
622 # expected takes a pretty powerful argument, it's processed
623 # here. admittedly this would be less trouble if I relied on
624 # OrderedDict (there's an impl of that in the commit history if
625 # you look
626 if expected is None:
627 expected = []
628 elif isinstance(expected, str):
629 expected = [(expected, NO_DEFAULT)]
631 expected_items = []
632 try:
633 expected_iter = iter(expected)
634 except TypeError as e:
635 raise ValueError('"expected" takes string name, sequence of string names,'
636 ' iterable of (name, default) pairs, or a mapping of '
637 ' {name: default}, not %r (got: %r)' % (expected, e))
638 for argname in expected_iter:
639 if isinstance(argname, str):
640 # dict keys and bare strings
641 try:
642 default = expected[argname]
643 except TypeError:
644 default = NO_DEFAULT
645 else:
646 # pairs
647 try:
648 argname, default = argname
649 except (TypeError, ValueError):
650 raise ValueError('"expected" takes string name, sequence of string names,'
651 ' iterable of (name, default) pairs, or a mapping of '
652 ' {name: default}, not %r')
653 if not isinstance(argname, str):
654 raise ValueError(f'all "expected" argnames must be strings, not {argname!r}')
656 expected_items.append((argname, default))
658 return expected_items
661class FunctionBuilder:
662 """The FunctionBuilder type provides an interface for programmatically
663 creating new functions, either based on existing functions or from
664 scratch.
666 Values are passed in at construction or set as attributes on the
667 instance. For creating a new function based of an existing one,
668 see the :meth:`~FunctionBuilder.from_func` classmethod. At any
669 point, :meth:`~FunctionBuilder.get_func` can be called to get a
670 newly compiled function, based on the values configured.
672 >>> fb = FunctionBuilder('return_five', doc='returns the integer 5',
673 ... body='return 5')
674 >>> f = fb.get_func()
675 >>> f()
676 5
677 >>> fb.varkw = 'kw'
678 >>> f_kw = fb.get_func()
679 >>> f_kw(ignored_arg='ignored_val')
680 5
682 Note that function signatures themselves changed quite a bit in
683 Python 3, so several arguments are only applicable to
684 FunctionBuilder in Python 3. Except for *name*, all arguments to
685 the constructor are keyword arguments.
687 Args:
688 name (str): Name of the function.
689 doc (str): `Docstring`_ for the function, defaults to empty.
690 module (str): Name of the module from which this function was
691 imported. Defaults to None.
692 body (str): String version of the code representing the body
693 of the function. Defaults to ``'pass'``, which will result
694 in a function which does nothing and returns ``None``.
695 args (list): List of argument names, defaults to empty list,
696 denoting no arguments.
697 varargs (str): Name of the catch-all variable for positional
698 arguments. E.g., "args" if the resultant function is to have
699 ``*args`` in the signature. Defaults to None.
700 varkw (str): Name of the catch-all variable for keyword
701 arguments. E.g., "kwargs" if the resultant function is to have
702 ``**kwargs`` in the signature. Defaults to None.
703 defaults (tuple): A tuple containing default argument values for
704 those arguments that have defaults.
705 kwonlyargs (list): Argument names which are only valid as
706 keyword arguments. **Python 3 only.**
707 kwonlydefaults (dict): A mapping, same as normal *defaults*,
708 but only for the *kwonlyargs*. **Python 3 only.**
709 annotations (dict): Mapping of type hints and so
710 forth. **Python 3 only.**
711 filename (str): The filename that will appear in
712 tracebacks. Defaults to "boltons.funcutils.FunctionBuilder".
713 indent (int): Number of spaces with which to indent the
714 function *body*. Values less than 1 will result in an error.
715 dict (dict): Any other attributes which should be added to the
716 functions compiled with this FunctionBuilder.
718 All of these arguments are also made available as attributes which
719 can be mutated as necessary.
721 .. _Docstring: https://en.wikipedia.org/wiki/Docstring#Python
723 """
725 _argspec_defaults = {'args': list,
726 'varargs': lambda: None,
727 'varkw': lambda: None,
728 'defaults': lambda: None,
729 'kwonlyargs': list,
730 'kwonlydefaults': dict,
731 'annotations': dict}
733 @classmethod
734 def _argspec_to_dict(cls, f):
735 argspec = inspect.getfullargspec(f)
736 return {attr: getattr(argspec, attr)
737 for attr in cls._argspec_defaults}
739 _defaults = {'doc': str,
740 'dict': dict,
741 'is_async': lambda: False,
742 'module': lambda: None,
743 'body': lambda: 'pass',
744 'indent': lambda: 4,
745 "annotations": dict,
746 'posonlyargs': list,
747 'filename': lambda: 'boltons.funcutils.FunctionBuilder'}
749 _defaults.update(_argspec_defaults)
751 _compile_count = itertools.count()
753 def __init__(self, name, **kw):
754 self.name = name
755 for a, default_factory in self._defaults.items():
756 val = kw.pop(a, None)
757 if val is None:
758 val = default_factory()
759 setattr(self, a, val)
761 if kw:
762 raise TypeError('unexpected kwargs: %r' % kw.keys())
763 return
765 # def get_argspec(self): # TODO
767 def get_sig_str(self, with_annotations=True):
768 """Return function signature as a string.
770 with_annotations is ignored on Python 2. On Python 3 signature
771 will omit annotations if it is set to False.
772 """
773 if with_annotations:
774 annotations = self.annotations
775 else:
776 annotations = {}
778 return inspect_formatargspec(self.args,
779 self.varargs,
780 self.varkw,
781 [],
782 self.kwonlyargs,
783 {},
784 annotations)
786 def get_invocation_str(self, target=None):
787 # Regular args with defaults (and keyword-only args) are forwarded
788 # as keywords (name=name), so values callers pass by keyword reach
789 # the wrapper's **kwargs instead of being silently flattened into
790 # *args (#343). Two classes must stay positional: positional-only
791 # params (keywords are rejected outright), and every regular arg
792 # when *varargs is present (a keyword-forwarded arg that precedes
793 # *varargs collides with non-empty varargs).
794 #
795 # When *target* -- the callable the generated invocation will
796 # actually call -- is provided, any argument that target only
797 # accepts as a keyword is forwarded as a keyword regardless of
798 # the rules above, since positional forwarding could never bind
799 # it (#261). Targets without introspectable signatures fall
800 # back to signature-based forwarding alone.
801 target_kwonly = frozenset()
802 if target is not None:
803 try:
804 target_params = inspect.signature(target).parameters
805 except (ValueError, TypeError):
806 pass
807 else:
808 target_kwonly = frozenset(
809 p.name for p in target_params.values()
810 if p.kind is inspect.Parameter.KEYWORD_ONLY)
811 defaults = self.defaults or ()
812 args = list(self.args or ())
813 n_positional = len(args) - len(defaults)
814 parts, kw_parts = [], []
815 for i, arg in enumerate(args):
816 if arg in target_kwonly:
817 kw_parts.append(arg + '=' + arg)
818 elif (i >= n_positional and not self.varargs
819 and arg not in self.posonlyargs):
820 kw_parts.append(arg + '=' + arg)
821 else:
822 parts.append(arg)
823 if self.varargs:
824 parts.append('*' + self.varargs)
825 parts += kw_parts
826 parts += [arg + '=' + arg for arg in self.kwonlyargs or ()]
827 if self.varkw:
828 parts.append('**' + self.varkw)
829 return ', '.join(parts)
831 @classmethod
832 def from_func(cls, func):
833 """Create a new FunctionBuilder instance based on an existing
834 function. The original function will not be stored or
835 modified.
836 """
837 # TODO: copy_body? gonna need a good signature regex.
838 # TODO: might worry about __closure__?
839 if not callable(func):
840 raise TypeError(f'expected callable object, not {func!r}')
842 if isinstance(func, functools.partial):
843 kwargs = {'name': func.func.__name__,
844 'doc': func.func.__doc__,
845 'module': getattr(func.func, '__module__', None), # e.g., method_descriptor
846 'annotations': getattr(func.func, "__annotations__", {}),
847 'dict': getattr(func.func, '__dict__', {})}
848 else:
849 kwargs = {'name': func.__name__,
850 'doc': func.__doc__,
851 'module': getattr(func, '__module__', None), # e.g., method_descriptor
852 'annotations': getattr(func, "__annotations__", {}),
853 'dict': getattr(func, '__dict__', {})}
855 kwargs.update(cls._argspec_to_dict(func))
857 # getfullargspec merges positional-only params into args; recover
858 # them from the code object so they are never keyword-forwarded.
859 # (functools.partial objects have no __code__; posonly detection
860 # is skipped for them, preserving today's behavior.)
861 n_posonly = getattr(getattr(func, '__code__', None),
862 'co_posonlyargcount', 0)
863 if n_posonly:
864 kwargs['posonlyargs'] = list(kwargs['args'][:n_posonly])
866 if inspect.iscoroutinefunction(func):
867 kwargs['is_async'] = True
869 return cls(**kwargs)
871 def get_func(self, execdict=None, add_source=True, with_dict=True):
872 """Compile and return a new function based on the current values of
873 the FunctionBuilder.
875 Args:
876 execdict (dict): The dictionary representing the scope in
877 which the compilation should take place. Defaults to an empty
878 dict.
879 add_source (bool): Whether to add the source used to a
880 special ``__source__`` attribute on the resulting
881 function. Defaults to True.
882 with_dict (bool): Add any custom attributes, if
883 applicable. Defaults to True.
885 To see an example of usage, see the implementation of
886 :func:`~boltons.funcutils.wraps`.
887 """
888 execdict = execdict or {}
889 body = self.body or self._default_body
891 tmpl = 'def {name}{sig_str}:'
892 tmpl += '\n{body}'
894 if self.is_async:
895 tmpl = 'async ' + tmpl
897 body = _indent(self.body, ' ' * self.indent)
899 name = self.name.replace('<', '_').replace('>', '_') # lambdas
900 src = tmpl.format(name=name, sig_str=self.get_sig_str(with_annotations=False),
901 doc=self.doc, body=body)
902 self._compile(src, execdict)
903 func = execdict[name]
905 func.__name__ = self.name
906 func.__doc__ = self.doc
907 func.__defaults__ = self.defaults
908 func.__kwdefaults__ = self.kwonlydefaults
909 func.__annotations__ = self.annotations
911 if with_dict:
912 func.__dict__.update(self.dict)
913 func.__module__ = self.module
914 # TODO: caller module fallback?
916 if add_source:
917 func.__source__ = src
919 return func
921 def get_defaults_dict(self):
922 """Get a dictionary of function arguments with defaults and the
923 respective values.
924 """
925 ret = dict(reversed(list(zip(reversed(self.args),
926 reversed(self.defaults or [])))))
927 kwonlydefaults = getattr(self, 'kwonlydefaults', None)
928 if kwonlydefaults:
929 ret.update(kwonlydefaults)
930 return ret
932 def get_arg_names(self, only_required=False):
933 arg_names = tuple(self.args) + tuple(getattr(self, 'kwonlyargs', ()))
934 if only_required:
935 defaults_dict = self.get_defaults_dict()
936 arg_names = tuple([an for an in arg_names if an not in defaults_dict])
937 return arg_names
939 def add_arg(self, arg_name, default=NO_DEFAULT, kwonly=False):
940 """Add an argument with optional *default* (defaults to
941 ``funcutils.NO_DEFAULT``). Pass *kwonly=True* to add a
942 keyword-only argument
943 """
944 if arg_name in self.args:
945 raise ExistingArgument(f'arg {arg_name!r} already in func {self.name} arg list')
946 if arg_name in self.kwonlyargs:
947 raise ExistingArgument(f'arg {arg_name!r} already in func {self.name} kwonly arg list')
948 if not kwonly:
949 self.args.append(arg_name)
950 if default is not NO_DEFAULT:
951 self.defaults = (self.defaults or ()) + (default,)
952 else:
953 self.kwonlyargs.append(arg_name)
954 if default is not NO_DEFAULT:
955 self.kwonlydefaults[arg_name] = default
957 def remove_arg(self, arg_name):
958 """Remove an argument from this FunctionBuilder's argument list. The
959 resulting function will have one less argument per call to
960 this function.
962 Args:
963 arg_name (str): The name of the argument to remove.
965 Raises a :exc:`ValueError` if the argument is not present.
967 """
968 args = self.args
969 d_dict = self.get_defaults_dict()
970 try:
971 args.remove(arg_name)
972 except ValueError:
973 try:
974 self.kwonlyargs.remove(arg_name)
975 except (AttributeError, ValueError):
976 # missing from both
977 exc = MissingArgument('arg %r not found in %s argument list:'
978 ' %r' % (arg_name, self.name, args))
979 exc.arg_name = arg_name
980 raise exc
981 else:
982 self.kwonlydefaults.pop(arg_name, None)
983 else:
984 d_dict.pop(arg_name, None)
985 self.defaults = tuple([d_dict[a] for a in args if a in d_dict])
986 return
988 def _compile(self, src, execdict):
990 filename = ('<%s-%d>'
991 % (self.filename, next(self._compile_count),))
992 try:
993 code = compile(src, filename, 'single')
994 exec(code, execdict)
995 except Exception:
996 raise
997 return execdict
1000class MissingArgument(ValueError):
1001 pass
1004class ExistingArgument(ValueError):
1005 pass
1008def _indent(text, margin, newline='\n', key=bool):
1009 "based on boltons.strutils.indent"
1010 indented_lines = [(margin + line if key(line) else line)
1011 for line in text.splitlines()]
1012 return newline.join(indented_lines)
1015def noop(*args, **kwargs):
1016 """
1017 Simple function that should be used when no effect is desired.
1018 An alternative to checking for an optional function type parameter.
1020 e.g.
1021 def decorate(func, pre_func=None, post_func=None):
1022 if pre_func:
1023 pre_func()
1024 func()
1025 if post_func:
1026 post_func()
1028 vs
1030 def decorate(func, pre_func=noop, post_func=noop):
1031 pre_func()
1032 func()
1033 post_func()
1034 """
1035 return None
1041def once(func):
1042 """Decorator that ensures a function is only executed once, caching
1043 the result for all subsequent calls. Thread-safe: concurrent callers
1044 block until the first execution completes, then all receive the
1045 cached result.
1047 This is especially useful in cases like logging, where multiple
1048 initializations can cause problems.
1050 The decorated function must take no arguments.
1052 >>> call_count = 0
1053 >>> @once
1054 ... def expensive_setup():
1055 ... global call_count
1056 ... call_count += 1
1057 ... return 'initialized'
1058 >>> expensive_setup()
1059 'initialized'
1060 >>> expensive_setup()
1061 'initialized'
1062 >>> call_count
1063 1
1064 """
1065 _UNSET = object()
1066 lock = threading.Lock()
1067 result = _UNSET
1069 @functools.wraps(func)
1070 def wrapper():
1071 nonlocal result
1072 if result is not _UNSET:
1073 return result
1074 with lock:
1075 if result is not _UNSET:
1076 return result
1077 result = func()
1078 return result
1080 return wrapper
1082# end funcutils.py