Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/boltons/funcutils.py: 17%
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 re
39import inspect
40import functools
41import itertools
42from inspect import formatannotation
43from types import MethodType, FunctionType
45make_method = lambda desc, obj, obj_type: MethodType(desc, obj)
48try:
49 from .typeutils import make_sentinel
50 NO_DEFAULT = make_sentinel(var_name='NO_DEFAULT')
51except ImportError:
52 NO_DEFAULT = object()
55def inspect_formatargspec(
56 args, varargs=None, varkw=None, defaults=None,
57 kwonlyargs=(), kwonlydefaults={}, annotations={},
58 formatarg=str,
59 formatvarargs=lambda name: '*' + name,
60 formatvarkw=lambda name: '**' + name,
61 formatvalue=lambda value: '=' + repr(value),
62 formatreturns=lambda text: ' -> ' + text,
63 formatannotation=formatannotation):
64 """Copy formatargspec from python 3.7 standard library.
65 Python 3 has deprecated formatargspec and requested that Signature
66 be used instead, however this requires a full reimplementation
67 of formatargspec() in terms of creating Parameter objects and such.
68 Instead of introducing all the object-creation overhead and having
69 to reinvent from scratch, just copy their compatibility routine.
70 """
72 def formatargandannotation(arg):
73 result = formatarg(arg)
74 if arg in annotations:
75 result += ': ' + formatannotation(annotations[arg])
76 return result
77 specs = []
78 if defaults:
79 firstdefault = len(args) - len(defaults)
80 for i, arg in enumerate(args):
81 spec = formatargandannotation(arg)
82 if defaults and i >= firstdefault:
83 spec = spec + formatvalue(defaults[i - firstdefault])
84 specs.append(spec)
85 if varargs is not None:
86 specs.append(formatvarargs(formatargandannotation(varargs)))
87 else:
88 if kwonlyargs:
89 specs.append('*')
90 if kwonlyargs:
91 for kwonlyarg in kwonlyargs:
92 spec = formatargandannotation(kwonlyarg)
93 if kwonlydefaults and kwonlyarg in kwonlydefaults:
94 spec += formatvalue(kwonlydefaults[kwonlyarg])
95 specs.append(spec)
96 if varkw is not None:
97 specs.append(formatvarkw(formatargandannotation(varkw)))
98 result = '(' + ', '.join(specs) + ')'
99 if 'return' in annotations:
100 result += formatreturns(formatannotation(annotations['return']))
101 return result
104def get_module_callables(mod, ignore=None):
105 """Returns two maps of (*types*, *funcs*) from *mod*, optionally
106 ignoring based on the :class:`bool` return value of the *ignore*
107 callable. *mod* can be a string name of a module in
108 :data:`sys.modules` or the module instance itself.
109 """
110 if isinstance(mod, str):
111 mod = sys.modules[mod]
112 types, funcs = {}, {}
113 for attr_name in dir(mod):
114 if ignore and ignore(attr_name):
115 continue
116 try:
117 attr = getattr(mod, attr_name)
118 except Exception:
119 continue
120 try:
121 attr_mod_name = attr.__module__
122 except AttributeError:
123 continue
124 if attr_mod_name != mod.__name__:
125 continue
126 if isinstance(attr, type):
127 types[attr_name] = attr
128 elif callable(attr):
129 funcs[attr_name] = attr
130 return types, funcs
133def mro_items(type_obj):
134 """Takes a type and returns an iterator over all class variables
135 throughout the type hierarchy (respecting the MRO).
137 >>> sorted(set([k for k, v in mro_items(int) if not k.startswith('__') and 'bytes' not in k and not callable(v)]))
138 ['denominator', 'imag', 'numerator', 'real']
139 """
140 # TODO: handle slots?
141 return itertools.chain.from_iterable(ct.__dict__.items()
142 for ct in type_obj.__mro__)
145def dir_dict(obj, raise_exc=False):
146 """Return a dictionary of attribute names to values for a given
147 object. Unlike ``obj.__dict__``, this function returns all
148 attributes on the object, including ones on parent classes.
149 """
150 # TODO: separate function for handling descriptors on types?
151 ret = {}
152 for k in dir(obj):
153 try:
154 ret[k] = getattr(obj, k)
155 except Exception:
156 if raise_exc:
157 raise
158 return ret
161def copy_function(orig, copy_dict=True):
162 """Returns a shallow copy of the function, including code object,
163 globals, closure, etc.
165 >>> func = lambda: func
166 >>> func() is func
167 True
168 >>> func_copy = copy_function(func)
169 >>> func_copy() is func
170 True
171 >>> func_copy is not func
172 True
174 Args:
175 orig (function): The function to be copied. Must be a
176 function, not just any method or callable.
177 copy_dict (bool): Also copy any attributes set on the function
178 instance. Defaults to ``True``.
179 """
180 ret = FunctionType(orig.__code__,
181 orig.__globals__,
182 name=orig.__name__,
183 argdefs=getattr(orig, "__defaults__", None),
184 closure=getattr(orig, "__closure__", None))
185 if hasattr(orig, "__kwdefaults__"):
186 ret.__kwdefaults__ = orig.__kwdefaults__
187 if copy_dict:
188 ret.__dict__.update(orig.__dict__)
189 return ret
192def partial_ordering(cls):
193 """Class decorator, similar to :func:`functools.total_ordering`,
194 except it is used to define `partial orderings`_ (i.e., it is
195 possible that *x* is neither greater than, equal to, or less than
196 *y*). It assumes the presence of the ``__le__()`` and ``__ge__()``
197 method, but nothing else. It will not override any existing
198 additional comparison methods.
200 .. _partial orderings: https://en.wikipedia.org/wiki/Partially_ordered_set
202 >>> @partial_ordering
203 ... class MySet(set):
204 ... def __le__(self, other):
205 ... return self.issubset(other)
206 ... def __ge__(self, other):
207 ... return self.issuperset(other)
208 ...
209 >>> a = MySet([1,2,3])
210 >>> b = MySet([1,2])
211 >>> c = MySet([1,2,4])
212 >>> b < a
213 True
214 >>> b > a
215 False
216 >>> b < c
217 True
218 >>> a < c
219 False
220 >>> c > a
221 False
222 """
223 def __lt__(self, other): return self <= other and not self >= other
224 def __gt__(self, other): return self >= other and not self <= other
225 def __eq__(self, other): return self >= other and self <= other
227 if not hasattr(cls, '__lt__'): cls.__lt__ = __lt__
228 if not hasattr(cls, '__gt__'): cls.__gt__ = __gt__
229 if not hasattr(cls, '__eq__'): cls.__eq__ = __eq__
231 return cls
234class InstancePartial(functools.partial):
235 """:class:`functools.partial` is a huge convenience for anyone
236 working with Python's great first-class functions. It allows
237 developers to curry arguments and incrementally create simpler
238 callables for a variety of use cases.
240 Unfortunately there's one big gap in its usefulness:
241 methods. Partials just don't get bound as methods and
242 automatically handed a reference to ``self``. The
243 ``InstancePartial`` type remedies this by inheriting from
244 :class:`functools.partial` and implementing the necessary
245 descriptor protocol. There are no other differences in
246 implementation or usage. :class:`CachedInstancePartial`, below,
247 has the same ability, but is slightly more efficient.
249 """
250 @property
251 def _partialmethod(self):
252 return functools.partialmethod(self.func, *self.args, **self.keywords)
254 def __get__(self, obj, obj_type):
255 return make_method(self, obj, obj_type)
259class CachedInstancePartial(functools.partial):
260 """The ``CachedInstancePartial`` is virtually the same as
261 :class:`InstancePartial`, adding support for method-usage to
262 :class:`functools.partial`, except that upon first access, it
263 caches the bound method on the associated object, speeding it up
264 for future accesses, and bringing the method call overhead to
265 about the same as non-``partial`` methods.
267 See the :class:`InstancePartial` docstring for more details.
268 """
269 @property
270 def _partialmethod(self):
271 return functools.partialmethod(self.func, *self.args, **self.keywords)
273 def __set_name__(self, obj_type, name):
274 self.__name__ = name
276 def __get__(self, obj, obj_type):
277 # These assignments could've been in __init__, but there was
278 # no simple way to do it without breaking one of PyPy or Py3.
279 self.__name__ = getattr(self, "__name__", None)
280 self.__doc__ = self.func.__doc__
281 self.__module__ = self.func.__module__
283 name = self.__name__
285 if obj is None:
286 return make_method(self, obj, obj_type)
287 try:
288 # since this is a data descriptor, this block
289 # is probably only hit once (per object)
290 return obj.__dict__[name]
291 except KeyError:
292 obj.__dict__[name] = ret = make_method(self, obj, obj_type)
293 return ret
296partial = CachedInstancePartial
299def format_invocation(name='', args=(), kwargs=None, **kw):
300 """Given a name, positional arguments, and keyword arguments, format
301 a basic Python-style function call.
303 >>> print(format_invocation('func', args=(1, 2), kwargs={'c': 3}))
304 func(1, 2, c=3)
305 >>> print(format_invocation('a_func', args=(1,)))
306 a_func(1)
307 >>> print(format_invocation('kw_func', kwargs=[('a', 1), ('b', 2)]))
308 kw_func(a=1, b=2)
310 """
311 _repr = kw.pop('repr', repr)
312 if kw:
313 raise TypeError('unexpected keyword args: %r' % ', '.join(kw.keys()))
314 kwargs = kwargs or {}
315 a_text = ', '.join([_repr(a) for a in args])
316 if isinstance(kwargs, dict):
317 kwarg_items = [(k, kwargs[k]) for k in sorted(kwargs)]
318 else:
319 kwarg_items = kwargs
320 kw_text = ', '.join([f'{k}={_repr(v)}' for k, v in kwarg_items])
322 all_args_text = a_text
323 if all_args_text and kw_text:
324 all_args_text += ', '
325 all_args_text += kw_text
327 return f'{name}({all_args_text})'
330def format_exp_repr(obj, pos_names, req_names=None, opt_names=None, opt_key=None):
331 """Render an expression-style repr of an object, based on attribute
332 names, which are assumed to line up with arguments to an initializer.
334 >>> class Flag(object):
335 ... def __init__(self, length, width, depth=None):
336 ... self.length = length
337 ... self.width = width
338 ... self.depth = depth
339 ...
341 That's our Flag object, here are some example reprs for it:
343 >>> flag = Flag(5, 10)
344 >>> print(format_exp_repr(flag, ['length', 'width'], [], ['depth']))
345 Flag(5, 10)
346 >>> flag2 = Flag(5, 15, 2)
347 >>> print(format_exp_repr(flag2, ['length'], ['width', 'depth']))
348 Flag(5, width=15, depth=2)
350 By picking the pos_names, req_names, opt_names, and opt_key, you
351 can fine-tune how you want the repr to look.
353 Args:
354 obj (object): The object whose type name will be used and
355 attributes will be checked
356 pos_names (list): Required list of attribute names which will be
357 rendered as positional arguments in the output repr.
358 req_names (list): List of attribute names which will always
359 appear in the keyword arguments in the output repr. Defaults to None.
360 opt_names (list): List of attribute names which may appear in
361 the keyword arguments in the output repr, provided they pass
362 the *opt_key* check. Defaults to None.
363 opt_key (callable): A function or callable which checks whether
364 an opt_name should be in the repr. Defaults to a
365 ``None``-check.
367 """
368 cn = type(obj).__name__
369 req_names = req_names or []
370 opt_names = opt_names or []
371 uniq_names, all_names = set(), []
372 for name in req_names + opt_names:
373 if name in uniq_names:
374 continue
375 uniq_names.add(name)
376 all_names.append(name)
378 if opt_key is None:
379 opt_key = lambda v: v is None
380 assert callable(opt_key)
382 args = [getattr(obj, name, None) for name in pos_names]
384 kw_items = [(name, getattr(obj, name, None)) for name in all_names]
385 kw_items = [(name, val) for name, val in kw_items
386 if not (name in opt_names and opt_key(val))]
388 return format_invocation(cn, args, kw_items)
391def format_nonexp_repr(obj, req_names=None, opt_names=None, opt_key=None):
392 """Format a non-expression-style repr
394 Some object reprs look like object instantiation, e.g., App(r=[], mw=[]).
396 This makes sense for smaller, lower-level objects whose state
397 roundtrips. But a lot of objects contain values that don't
398 roundtrip, like types and functions.
400 For those objects, there is the non-expression style repr, which
401 mimic's Python's default style to make a repr like so:
403 >>> class Flag(object):
404 ... def __init__(self, length, width, depth=None):
405 ... self.length = length
406 ... self.width = width
407 ... self.depth = depth
408 ...
409 >>> flag = Flag(5, 10)
410 >>> print(format_nonexp_repr(flag, ['length', 'width'], ['depth']))
411 <Flag length=5 width=10>
413 If no attributes are specified or set, utilizes the id, not unlike Python's
414 built-in behavior.
416 >>> print(format_nonexp_repr(flag))
417 <Flag id=...>
418 """
419 cn = obj.__class__.__name__
420 req_names = req_names or []
421 opt_names = opt_names or []
422 uniq_names, all_names = set(), []
423 for name in req_names + opt_names:
424 if name in uniq_names:
425 continue
426 uniq_names.add(name)
427 all_names.append(name)
429 if opt_key is None:
430 opt_key = lambda v: v is None
431 assert callable(opt_key)
433 items = [(name, getattr(obj, name, None)) for name in all_names]
434 labels = [f'{name}={val!r}' for name, val in items
435 if not (name in opt_names and opt_key(val))]
436 if not labels:
437 labels = ['id=%s' % id(obj)]
438 ret = '<{} {}>'.format(cn, ' '.join(labels))
439 return ret
443# # #
444# # # Function builder
445# # #
448def wraps(func, injected=None, expected=None, **kw):
449 """Decorator factory to apply update_wrapper() to a wrapper function.
451 Modeled after built-in :func:`functools.wraps`. Returns a decorator
452 that invokes update_wrapper() with the decorated function as the wrapper
453 argument and the arguments to wraps() as the remaining arguments.
454 Default arguments are as for update_wrapper(). This is a convenience
455 function to simplify applying partial() to update_wrapper().
457 Same example as in update_wrapper's doc but with wraps:
459 >>> from boltons.funcutils import wraps
460 >>>
461 >>> def print_return(func):
462 ... @wraps(func)
463 ... def wrapper(*args, **kwargs):
464 ... ret = func(*args, **kwargs)
465 ... print(ret)
466 ... return ret
467 ... return wrapper
468 ...
469 >>> @print_return
470 ... def example():
471 ... '''docstring'''
472 ... return 'example return value'
473 >>>
474 >>> val = example()
475 example return value
476 >>> example.__name__
477 'example'
478 >>> example.__doc__
479 'docstring'
480 """
481 return partial(update_wrapper, func=func, build_from=None,
482 injected=injected, expected=expected, **kw)
485def update_wrapper(wrapper, func, injected=None, expected=None, build_from=None, **kw):
486 """Modeled after the built-in :func:`functools.update_wrapper`,
487 this function is used to make your wrapper function reflect the
488 wrapped function's:
490 * Name
491 * Documentation
492 * Module
493 * Signature
495 The built-in :func:`functools.update_wrapper` copies the first three, but
496 does not copy the signature. This version of ``update_wrapper`` can copy
497 the inner function's signature exactly, allowing seamless usage
498 and :mod:`introspection <inspect>`. Usage is identical to the
499 built-in version::
501 >>> from boltons.funcutils import update_wrapper
502 >>>
503 >>> def print_return(func):
504 ... def wrapper(*args, **kwargs):
505 ... ret = func(*args, **kwargs)
506 ... print(ret)
507 ... return ret
508 ... return update_wrapper(wrapper, func)
509 ...
510 >>> @print_return
511 ... def example():
512 ... '''docstring'''
513 ... return 'example return value'
514 >>>
515 >>> val = example()
516 example return value
517 >>> example.__name__
518 'example'
519 >>> example.__doc__
520 'docstring'
522 In addition, the boltons version of update_wrapper supports
523 modifying the outer signature. By passing a list of
524 *injected* argument names, those arguments will be removed from
525 the outer wrapper's signature, allowing your decorator to provide
526 arguments that aren't passed in.
528 Args:
530 wrapper (function) : The callable to which the attributes of
531 *func* are to be copied.
532 func (function): The callable whose attributes are to be copied.
533 injected (list): An optional list of argument names which
534 should not appear in the new wrapper's signature.
535 expected (list): An optional list of argument names (or (name,
536 default) pairs) representing new arguments introduced by
537 the wrapper (the opposite of *injected*). See
538 :meth:`FunctionBuilder.add_arg()` for more details.
539 build_from (function): The callable from which the new wrapper
540 is built. Defaults to *func*, unless *wrapper* is partial object
541 built from *func*, in which case it defaults to *wrapper*.
542 Useful in some specific cases where *wrapper* and *func* have the
543 same arguments but differ on which are keyword-only and positional-only.
544 update_dict (bool): Whether to copy other, non-standard
545 attributes of *func* over to the wrapper. Defaults to True.
546 inject_to_varkw (bool): Ignore missing arguments when a
547 ``**kwargs``-type catch-all is present. Defaults to True.
548 hide_wrapped (bool): Remove reference to the wrapped function(s)
549 in the updated function.
551 In opposition to the built-in :func:`functools.update_wrapper` bolton's
552 version returns a copy of the function and does not modify anything in place.
553 For more in-depth wrapping of functions, see the
554 :class:`FunctionBuilder` type, on which update_wrapper was built.
555 """
556 if injected is None:
557 injected = []
558 elif isinstance(injected, str):
559 injected = [injected]
560 else:
561 injected = list(injected)
563 expected_items = _parse_wraps_expected(expected)
565 if isinstance(func, (classmethod, staticmethod)):
566 raise TypeError('wraps does not support wrapping classmethods and'
567 ' staticmethods, change the order of wrapping to'
568 ' wrap the underlying function: %r'
569 % (getattr(func, '__func__', None),))
571 update_dict = kw.pop('update_dict', True)
572 inject_to_varkw = kw.pop('inject_to_varkw', True)
573 hide_wrapped = kw.pop('hide_wrapped', False)
574 if kw:
575 raise TypeError('unexpected kwargs: %r' % kw.keys())
577 if isinstance(wrapper, functools.partial) and func is wrapper.func:
578 build_from = build_from or wrapper
580 fb = FunctionBuilder.from_func(build_from or func)
582 for arg in injected:
583 try:
584 fb.remove_arg(arg)
585 except MissingArgument:
586 if inject_to_varkw and fb.varkw is not None:
587 continue # keyword arg will be caught by the varkw
588 raise
590 for arg, default in expected_items:
591 fb.add_arg(arg, default) # may raise ExistingArgument
593 if fb.is_async:
594 fb.body = 'return await _call(%s)' % fb.get_invocation_str()
595 else:
596 fb.body = 'return _call(%s)' % fb.get_invocation_str()
598 execdict = dict(_call=wrapper, _func=func)
599 fully_wrapped = fb.get_func(execdict, with_dict=update_dict)
601 if hide_wrapped and hasattr(fully_wrapped, '__wrapped__'):
602 del fully_wrapped.__dict__['__wrapped__']
603 elif not hide_wrapped:
604 fully_wrapped.__wrapped__ = func # ref to the original function (#115)
606 return fully_wrapped
609def _parse_wraps_expected(expected):
610 # expected takes a pretty powerful argument, it's processed
611 # here. admittedly this would be less trouble if I relied on
612 # OrderedDict (there's an impl of that in the commit history if
613 # you look
614 if expected is None:
615 expected = []
616 elif isinstance(expected, str):
617 expected = [(expected, NO_DEFAULT)]
619 expected_items = []
620 try:
621 expected_iter = iter(expected)
622 except TypeError as e:
623 raise ValueError('"expected" takes string name, sequence of string names,'
624 ' iterable of (name, default) pairs, or a mapping of '
625 ' {name: default}, not %r (got: %r)' % (expected, e))
626 for argname in expected_iter:
627 if isinstance(argname, str):
628 # dict keys and bare strings
629 try:
630 default = expected[argname]
631 except TypeError:
632 default = NO_DEFAULT
633 else:
634 # pairs
635 try:
636 argname, default = argname
637 except (TypeError, ValueError):
638 raise ValueError('"expected" takes string name, sequence of string names,'
639 ' iterable of (name, default) pairs, or a mapping of '
640 ' {name: default}, not %r')
641 if not isinstance(argname, str):
642 raise ValueError(f'all "expected" argnames must be strings, not {argname!r}')
644 expected_items.append((argname, default))
646 return expected_items
649class FunctionBuilder:
650 """The FunctionBuilder type provides an interface for programmatically
651 creating new functions, either based on existing functions or from
652 scratch.
654 Values are passed in at construction or set as attributes on the
655 instance. For creating a new function based of an existing one,
656 see the :meth:`~FunctionBuilder.from_func` classmethod. At any
657 point, :meth:`~FunctionBuilder.get_func` can be called to get a
658 newly compiled function, based on the values configured.
660 >>> fb = FunctionBuilder('return_five', doc='returns the integer 5',
661 ... body='return 5')
662 >>> f = fb.get_func()
663 >>> f()
664 5
665 >>> fb.varkw = 'kw'
666 >>> f_kw = fb.get_func()
667 >>> f_kw(ignored_arg='ignored_val')
668 5
670 Note that function signatures themselves changed quite a bit in
671 Python 3, so several arguments are only applicable to
672 FunctionBuilder in Python 3. Except for *name*, all arguments to
673 the constructor are keyword arguments.
675 Args:
676 name (str): Name of the function.
677 doc (str): `Docstring`_ for the function, defaults to empty.
678 module (str): Name of the module from which this function was
679 imported. Defaults to None.
680 body (str): String version of the code representing the body
681 of the function. Defaults to ``'pass'``, which will result
682 in a function which does nothing and returns ``None``.
683 args (list): List of argument names, defaults to empty list,
684 denoting no arguments.
685 varargs (str): Name of the catch-all variable for positional
686 arguments. E.g., "args" if the resultant function is to have
687 ``*args`` in the signature. Defaults to None.
688 varkw (str): Name of the catch-all variable for keyword
689 arguments. E.g., "kwargs" if the resultant function is to have
690 ``**kwargs`` in the signature. Defaults to None.
691 defaults (tuple): A tuple containing default argument values for
692 those arguments that have defaults.
693 kwonlyargs (list): Argument names which are only valid as
694 keyword arguments. **Python 3 only.**
695 kwonlydefaults (dict): A mapping, same as normal *defaults*,
696 but only for the *kwonlyargs*. **Python 3 only.**
697 annotations (dict): Mapping of type hints and so
698 forth. **Python 3 only.**
699 filename (str): The filename that will appear in
700 tracebacks. Defaults to "boltons.funcutils.FunctionBuilder".
701 indent (int): Number of spaces with which to indent the
702 function *body*. Values less than 1 will result in an error.
703 dict (dict): Any other attributes which should be added to the
704 functions compiled with this FunctionBuilder.
706 All of these arguments are also made available as attributes which
707 can be mutated as necessary.
709 .. _Docstring: https://en.wikipedia.org/wiki/Docstring#Python
711 """
713 _argspec_defaults = {'args': list,
714 'varargs': lambda: None,
715 'varkw': lambda: None,
716 'defaults': lambda: None,
717 'kwonlyargs': list,
718 'kwonlydefaults': dict,
719 'annotations': dict}
721 @classmethod
722 def _argspec_to_dict(cls, f):
723 argspec = inspect.getfullargspec(f)
724 return {attr: getattr(argspec, attr)
725 for attr in cls._argspec_defaults}
727 _defaults = {'doc': str,
728 'dict': dict,
729 'is_async': lambda: False,
730 'module': lambda: None,
731 'body': lambda: 'pass',
732 'indent': lambda: 4,
733 "annotations": dict,
734 'filename': lambda: 'boltons.funcutils.FunctionBuilder'}
736 _defaults.update(_argspec_defaults)
738 _compile_count = itertools.count()
740 def __init__(self, name, **kw):
741 self.name = name
742 for a, default_factory in self._defaults.items():
743 val = kw.pop(a, None)
744 if val is None:
745 val = default_factory()
746 setattr(self, a, val)
748 if kw:
749 raise TypeError('unexpected kwargs: %r' % kw.keys())
750 return
752 # def get_argspec(self): # TODO
754 def get_sig_str(self, with_annotations=True):
755 """Return function signature as a string.
757 with_annotations is ignored on Python 2. On Python 3 signature
758 will omit annotations if it is set to False.
759 """
760 if with_annotations:
761 annotations = self.annotations
762 else:
763 annotations = {}
765 return inspect_formatargspec(self.args,
766 self.varargs,
767 self.varkw,
768 [],
769 self.kwonlyargs,
770 {},
771 annotations)
773 _KWONLY_MARKER = re.compile(r"""
774 \* # a star
775 \s* # followed by any amount of whitespace
776 , # followed by a comma
777 \s* # followed by any amount of whitespace
778 """, re.VERBOSE)
780 def get_invocation_str(self):
781 kwonly_pairs = None
782 formatters = {}
783 if self.kwonlyargs:
784 kwonly_pairs = {arg: arg
785 for arg in self.kwonlyargs}
786 formatters['formatvalue'] = lambda value: '=' + value
788 sig = inspect_formatargspec(self.args,
789 self.varargs,
790 self.varkw,
791 [],
792 kwonly_pairs,
793 kwonly_pairs,
794 {},
795 **formatters)
796 sig = self._KWONLY_MARKER.sub('', sig)
797 return sig[1:-1]
799 @classmethod
800 def from_func(cls, func):
801 """Create a new FunctionBuilder instance based on an existing
802 function. The original function will not be stored or
803 modified.
804 """
805 # TODO: copy_body? gonna need a good signature regex.
806 # TODO: might worry about __closure__?
807 if not callable(func):
808 raise TypeError(f'expected callable object, not {func!r}')
810 if isinstance(func, functools.partial):
811 kwargs = {'name': func.func.__name__,
812 'doc': func.func.__doc__,
813 'module': getattr(func.func, '__module__', None), # e.g., method_descriptor
814 'annotations': getattr(func.func, "__annotations__", {}),
815 'dict': getattr(func.func, '__dict__', {})}
816 else:
817 kwargs = {'name': func.__name__,
818 'doc': func.__doc__,
819 'module': getattr(func, '__module__', None), # e.g., method_descriptor
820 'annotations': getattr(func, "__annotations__", {}),
821 'dict': getattr(func, '__dict__', {})}
823 kwargs.update(cls._argspec_to_dict(func))
825 if inspect.iscoroutinefunction(func):
826 kwargs['is_async'] = True
828 return cls(**kwargs)
830 def get_func(self, execdict=None, add_source=True, with_dict=True):
831 """Compile and return a new function based on the current values of
832 the FunctionBuilder.
834 Args:
835 execdict (dict): The dictionary representing the scope in
836 which the compilation should take place. Defaults to an empty
837 dict.
838 add_source (bool): Whether to add the source used to a
839 special ``__source__`` attribute on the resulting
840 function. Defaults to True.
841 with_dict (bool): Add any custom attributes, if
842 applicable. Defaults to True.
844 To see an example of usage, see the implementation of
845 :func:`~boltons.funcutils.wraps`.
846 """
847 execdict = execdict or {}
848 body = self.body or self._default_body
850 tmpl = 'def {name}{sig_str}:'
851 tmpl += '\n{body}'
853 if self.is_async:
854 tmpl = 'async ' + tmpl
856 body = _indent(self.body, ' ' * self.indent)
858 name = self.name.replace('<', '_').replace('>', '_') # lambdas
859 src = tmpl.format(name=name, sig_str=self.get_sig_str(with_annotations=False),
860 doc=self.doc, body=body)
861 self._compile(src, execdict)
862 func = execdict[name]
864 func.__name__ = self.name
865 func.__doc__ = self.doc
866 func.__defaults__ = self.defaults
867 func.__kwdefaults__ = self.kwonlydefaults
868 func.__annotations__ = self.annotations
870 if with_dict:
871 func.__dict__.update(self.dict)
872 func.__module__ = self.module
873 # TODO: caller module fallback?
875 if add_source:
876 func.__source__ = src
878 return func
880 def get_defaults_dict(self):
881 """Get a dictionary of function arguments with defaults and the
882 respective values.
883 """
884 ret = dict(reversed(list(zip(reversed(self.args),
885 reversed(self.defaults or [])))))
886 kwonlydefaults = getattr(self, 'kwonlydefaults', None)
887 if kwonlydefaults:
888 ret.update(kwonlydefaults)
889 return ret
891 def get_arg_names(self, only_required=False):
892 arg_names = tuple(self.args) + tuple(getattr(self, 'kwonlyargs', ()))
893 if only_required:
894 defaults_dict = self.get_defaults_dict()
895 arg_names = tuple([an for an in arg_names if an not in defaults_dict])
896 return arg_names
898 def add_arg(self, arg_name, default=NO_DEFAULT, kwonly=False):
899 """Add an argument with optional *default* (defaults to
900 ``funcutils.NO_DEFAULT``). Pass *kwonly=True* to add a
901 keyword-only argument
902 """
903 if arg_name in self.args:
904 raise ExistingArgument(f'arg {arg_name!r} already in func {self.name} arg list')
905 if arg_name in self.kwonlyargs:
906 raise ExistingArgument(f'arg {arg_name!r} already in func {self.name} kwonly arg list')
907 if not kwonly:
908 self.args.append(arg_name)
909 if default is not NO_DEFAULT:
910 self.defaults = (self.defaults or ()) + (default,)
911 else:
912 self.kwonlyargs.append(arg_name)
913 if default is not NO_DEFAULT:
914 self.kwonlydefaults[arg_name] = default
916 def remove_arg(self, arg_name):
917 """Remove an argument from this FunctionBuilder's argument list. The
918 resulting function will have one less argument per call to
919 this function.
921 Args:
922 arg_name (str): The name of the argument to remove.
924 Raises a :exc:`ValueError` if the argument is not present.
926 """
927 args = self.args
928 d_dict = self.get_defaults_dict()
929 try:
930 args.remove(arg_name)
931 except ValueError:
932 try:
933 self.kwonlyargs.remove(arg_name)
934 except (AttributeError, ValueError):
935 # missing from both
936 exc = MissingArgument('arg %r not found in %s argument list:'
937 ' %r' % (arg_name, self.name, args))
938 exc.arg_name = arg_name
939 raise exc
940 else:
941 self.kwonlydefaults.pop(arg_name, None)
942 else:
943 d_dict.pop(arg_name, None)
944 self.defaults = tuple([d_dict[a] for a in args if a in d_dict])
945 return
947 def _compile(self, src, execdict):
949 filename = ('<%s-%d>'
950 % (self.filename, next(self._compile_count),))
951 try:
952 code = compile(src, filename, 'single')
953 exec(code, execdict)
954 except Exception:
955 raise
956 return execdict
959class MissingArgument(ValueError):
960 pass
963class ExistingArgument(ValueError):
964 pass
967def _indent(text, margin, newline='\n', key=bool):
968 "based on boltons.strutils.indent"
969 indented_lines = [(margin + line if key(line) else line)
970 for line in text.splitlines()]
971 return newline.join(indented_lines)
974from functools import total_ordering
977def noop(*args, **kwargs):
978 """
979 Simple function that should be used when no effect is desired.
980 An alternative to checking for an optional function type parameter.
982 e.g.
983 def decorate(func, pre_func=None, post_func=None):
984 if pre_func:
985 pre_func()
986 func()
987 if post_func:
988 post_func()
990 vs
992 def decorate(func, pre_func=noop, post_func=noop):
993 pre_func()
994 func()
995 post_func()
996 """
997 return None
999# end funcutils.py