Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/boltons/funcutils.py: 27%
419 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-11 06:58 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-11 06:58 +0000
1# -*- coding: utf-8 -*-
3# Copyright (c) 2013, Mahmoud Hashemi
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11#
12# * Redistributions in binary form must reproduce the above
13# copyright notice, this list of conditions and the following
14# disclaimer in the documentation and/or other materials provided
15# with the distribution.
16#
17# * The names of the contributors may not be used to endorse or
18# promote products derived from this software without specific
19# prior written permission.
20#
21# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33"""Python's built-in :mod:`functools` module builds several useful
34utilities on top of Python's first-class function
35support. ``funcutils`` generally stays in the same vein, adding to and
36correcting Python's standard metaprogramming facilities.
37"""
38from __future__ import print_function
40import sys
41import re
42import inspect
43import functools
44import itertools
45from types import MethodType, FunctionType
47try:
48 xrange
49 make_method = MethodType
50except NameError:
51 # Python 3
52 make_method = lambda desc, obj, obj_type: MethodType(desc, obj)
53 basestring = (str, bytes) # Python 3 compat
54 _IS_PY2 = False
55else:
56 _IS_PY2 = True
59try:
60 _inspect_iscoroutinefunction = inspect.iscoroutinefunction
61except AttributeError:
62 # Python 3.4
63 _inspect_iscoroutinefunction = lambda func: False
66try:
67 from .typeutils import make_sentinel
68 NO_DEFAULT = make_sentinel(var_name='NO_DEFAULT')
69except ImportError:
70 NO_DEFAULT = object()
72try:
73 from functools import partialmethod
74except ImportError:
75 partialmethod = None
78_IS_PY35 = sys.version_info >= (3, 5)
79if not _IS_PY35:
80 # py35+ wants you to use signature instead, but
81 # inspect_formatargspec is way simpler for what it is. Copied the
82 # vendoring approach from alembic:
83 # https://github.com/sqlalchemy/alembic/blob/4cdad6aec32b4b5573a2009cc356cb4b144bd359/alembic/util/compat.py#L92
84 from inspect import formatargspec as inspect_formatargspec
85else:
86 from inspect import formatannotation
88 def inspect_formatargspec(
89 args, varargs=None, varkw=None, defaults=None,
90 kwonlyargs=(), kwonlydefaults={}, annotations={},
91 formatarg=str,
92 formatvarargs=lambda name: '*' + name,
93 formatvarkw=lambda name: '**' + name,
94 formatvalue=lambda value: '=' + repr(value),
95 formatreturns=lambda text: ' -> ' + text,
96 formatannotation=formatannotation):
97 """Copy formatargspec from python 3.7 standard library.
98 Python 3 has deprecated formatargspec and requested that Signature
99 be used instead, however this requires a full reimplementation
100 of formatargspec() in terms of creating Parameter objects and such.
101 Instead of introducing all the object-creation overhead and having
102 to reinvent from scratch, just copy their compatibility routine.
103 """
105 def formatargandannotation(arg):
106 result = formatarg(arg)
107 if arg in annotations:
108 result += ': ' + formatannotation(annotations[arg])
109 return result
110 specs = []
111 if defaults:
112 firstdefault = len(args) - len(defaults)
113 for i, arg in enumerate(args):
114 spec = formatargandannotation(arg)
115 if defaults and i >= firstdefault:
116 spec = spec + formatvalue(defaults[i - firstdefault])
117 specs.append(spec)
118 if varargs is not None:
119 specs.append(formatvarargs(formatargandannotation(varargs)))
120 else:
121 if kwonlyargs:
122 specs.append('*')
123 if kwonlyargs:
124 for kwonlyarg in kwonlyargs:
125 spec = formatargandannotation(kwonlyarg)
126 if kwonlydefaults and kwonlyarg in kwonlydefaults:
127 spec += formatvalue(kwonlydefaults[kwonlyarg])
128 specs.append(spec)
129 if varkw is not None:
130 specs.append(formatvarkw(formatargandannotation(varkw)))
131 result = '(' + ', '.join(specs) + ')'
132 if 'return' in annotations:
133 result += formatreturns(formatannotation(annotations['return']))
134 return result
137def get_module_callables(mod, ignore=None):
138 """Returns two maps of (*types*, *funcs*) from *mod*, optionally
139 ignoring based on the :class:`bool` return value of the *ignore*
140 callable. *mod* can be a string name of a module in
141 :data:`sys.modules` or the module instance itself.
142 """
143 if isinstance(mod, basestring):
144 mod = sys.modules[mod]
145 types, funcs = {}, {}
146 for attr_name in dir(mod):
147 if ignore and ignore(attr_name):
148 continue
149 try:
150 attr = getattr(mod, attr_name)
151 except Exception:
152 continue
153 try:
154 attr_mod_name = attr.__module__
155 except AttributeError:
156 continue
157 if attr_mod_name != mod.__name__:
158 continue
159 if isinstance(attr, type):
160 types[attr_name] = attr
161 elif callable(attr):
162 funcs[attr_name] = attr
163 return types, funcs
166def mro_items(type_obj):
167 """Takes a type and returns an iterator over all class variables
168 throughout the type hierarchy (respecting the MRO).
170 >>> sorted(set([k for k, v in mro_items(int) if not k.startswith('__') and 'bytes' not in k and not callable(v)]))
171 ['denominator', 'imag', 'numerator', 'real']
172 """
173 # TODO: handle slots?
174 return itertools.chain.from_iterable(ct.__dict__.items()
175 for ct in type_obj.__mro__)
178def dir_dict(obj, raise_exc=False):
179 """Return a dictionary of attribute names to values for a given
180 object. Unlike ``obj.__dict__``, this function returns all
181 attributes on the object, including ones on parent classes.
182 """
183 # TODO: separate function for handling descriptors on types?
184 ret = {}
185 for k in dir(obj):
186 try:
187 ret[k] = getattr(obj, k)
188 except Exception:
189 if raise_exc:
190 raise
191 return ret
194def copy_function(orig, copy_dict=True):
195 """Returns a shallow copy of the function, including code object,
196 globals, closure, etc.
198 >>> func = lambda: func
199 >>> func() is func
200 True
201 >>> func_copy = copy_function(func)
202 >>> func_copy() is func
203 True
204 >>> func_copy is not func
205 True
207 Args:
208 orig (function): The function to be copied. Must be a
209 function, not just any method or callable.
210 copy_dict (bool): Also copy any attributes set on the function
211 instance. Defaults to ``True``.
212 """
213 ret = FunctionType(orig.__code__,
214 orig.__globals__,
215 name=orig.__name__,
216 argdefs=getattr(orig, "__defaults__", None),
217 closure=getattr(orig, "__closure__", None))
218 if hasattr(orig, "__kwdefaults__"):
219 ret.__kwdefaults__ = orig.__kwdefaults__
220 if copy_dict:
221 ret.__dict__.update(orig.__dict__)
222 return ret
225def partial_ordering(cls):
226 """Class decorator, similar to :func:`functools.total_ordering`,
227 except it is used to define `partial orderings`_ (i.e., it is
228 possible that *x* is neither greater than, equal to, or less than
229 *y*). It assumes the presence of the ``__le__()`` and ``__ge__()``
230 method, but nothing else. It will not override any existing
231 additional comparison methods.
233 .. _partial orderings: https://en.wikipedia.org/wiki/Partially_ordered_set
235 >>> @partial_ordering
236 ... class MySet(set):
237 ... def __le__(self, other):
238 ... return self.issubset(other)
239 ... def __ge__(self, other):
240 ... return self.issuperset(other)
241 ...
242 >>> a = MySet([1,2,3])
243 >>> b = MySet([1,2])
244 >>> c = MySet([1,2,4])
245 >>> b < a
246 True
247 >>> b > a
248 False
249 >>> b < c
250 True
251 >>> a < c
252 False
253 >>> c > a
254 False
255 """
256 def __lt__(self, other): return self <= other and not self >= other
257 def __gt__(self, other): return self >= other and not self <= other
258 def __eq__(self, other): return self >= other and self <= other
260 if not hasattr(cls, '__lt__'): cls.__lt__ = __lt__
261 if not hasattr(cls, '__gt__'): cls.__gt__ = __gt__
262 if not hasattr(cls, '__eq__'): cls.__eq__ = __eq__
264 return cls
267class InstancePartial(functools.partial):
268 """:class:`functools.partial` is a huge convenience for anyone
269 working with Python's great first-class functions. It allows
270 developers to curry arguments and incrementally create simpler
271 callables for a variety of use cases.
273 Unfortunately there's one big gap in its usefulness:
274 methods. Partials just don't get bound as methods and
275 automatically handed a reference to ``self``. The
276 ``InstancePartial`` type remedies this by inheriting from
277 :class:`functools.partial` and implementing the necessary
278 descriptor protocol. There are no other differences in
279 implementation or usage. :class:`CachedInstancePartial`, below,
280 has the same ability, but is slightly more efficient.
282 """
283 if partialmethod is not None: # NB: See https://github.com/mahmoud/boltons/pull/244
284 @property
285 def _partialmethod(self):
286 return partialmethod(self.func, *self.args, **self.keywords)
288 def __get__(self, obj, obj_type):
289 return make_method(self, obj, obj_type)
293class CachedInstancePartial(functools.partial):
294 """The ``CachedInstancePartial`` is virtually the same as
295 :class:`InstancePartial`, adding support for method-usage to
296 :class:`functools.partial`, except that upon first access, it
297 caches the bound method on the associated object, speeding it up
298 for future accesses, and bringing the method call overhead to
299 about the same as non-``partial`` methods.
301 See the :class:`InstancePartial` docstring for more details.
302 """
303 if partialmethod is not None: # NB: See https://github.com/mahmoud/boltons/pull/244
304 @property
305 def _partialmethod(self):
306 return partialmethod(self.func, *self.args, **self.keywords)
308 if sys.version_info >= (3, 6):
309 def __set_name__(self, obj_type, name):
310 self.__name__ = name
312 def __get__(self, obj, obj_type):
313 # These assignments could've been in __init__, but there was
314 # no simple way to do it without breaking one of PyPy or Py3.
315 self.__name__ = getattr(self, "__name__", None)
316 self.__doc__ = self.func.__doc__
317 self.__module__ = self.func.__module__
319 name = self.__name__
321 # if you're on python 3.6+, name will never be `None` bc `__set_name__` sets it when descriptor getting assigned
322 if name is None:
323 for k, v in mro_items(obj_type):
324 if v is self:
325 self.__name__ = name = k
326 if obj is None:
327 return make_method(self, obj, obj_type)
328 try:
329 # since this is a data descriptor, this block
330 # is probably only hit once (per object)
331 return obj.__dict__[name]
332 except KeyError:
333 obj.__dict__[name] = ret = make_method(self, obj, obj_type)
334 return ret
337partial = CachedInstancePartial
340def format_invocation(name='', args=(), kwargs=None, **kw):
341 """Given a name, positional arguments, and keyword arguments, format
342 a basic Python-style function call.
344 >>> print(format_invocation('func', args=(1, 2), kwargs={'c': 3}))
345 func(1, 2, c=3)
346 >>> print(format_invocation('a_func', args=(1,)))
347 a_func(1)
348 >>> print(format_invocation('kw_func', kwargs=[('a', 1), ('b', 2)]))
349 kw_func(a=1, b=2)
351 """
352 _repr = kw.pop('repr', repr)
353 if kw:
354 raise TypeError('unexpected keyword args: %r' % ', '.join(kw.keys()))
355 kwargs = kwargs or {}
356 a_text = ', '.join([_repr(a) for a in args])
357 if isinstance(kwargs, dict):
358 kwarg_items = [(k, kwargs[k]) for k in sorted(kwargs)]
359 else:
360 kwarg_items = kwargs
361 kw_text = ', '.join(['%s=%s' % (k, _repr(v)) for k, v in kwarg_items])
363 all_args_text = a_text
364 if all_args_text and kw_text:
365 all_args_text += ', '
366 all_args_text += kw_text
368 return '%s(%s)' % (name, all_args_text)
371def format_exp_repr(obj, pos_names, req_names=None, opt_names=None, opt_key=None):
372 """Render an expression-style repr of an object, based on attribute
373 names, which are assumed to line up with arguments to an initializer.
375 >>> class Flag(object):
376 ... def __init__(self, length, width, depth=None):
377 ... self.length = length
378 ... self.width = width
379 ... self.depth = depth
380 ...
382 That's our Flag object, here are some example reprs for it:
384 >>> flag = Flag(5, 10)
385 >>> print(format_exp_repr(flag, ['length', 'width'], [], ['depth']))
386 Flag(5, 10)
387 >>> flag2 = Flag(5, 15, 2)
388 >>> print(format_exp_repr(flag2, ['length'], ['width', 'depth']))
389 Flag(5, width=15, depth=2)
391 By picking the pos_names, req_names, opt_names, and opt_key, you
392 can fine-tune how you want the repr to look.
394 Args:
395 obj (object): The object whose type name will be used and
396 attributes will be checked
397 pos_names (list): Required list of attribute names which will be
398 rendered as positional arguments in the output repr.
399 req_names (list): List of attribute names which will always
400 appear in the keyword arguments in the output repr. Defaults to None.
401 opt_names (list): List of attribute names which may appear in
402 the keyword arguments in the output repr, provided they pass
403 the *opt_key* check. Defaults to None.
404 opt_key (callable): A function or callable which checks whether
405 an opt_name should be in the repr. Defaults to a
406 ``None``-check.
408 """
409 cn = type(obj).__name__
410 req_names = req_names or []
411 opt_names = opt_names or []
412 uniq_names, all_names = set(), []
413 for name in req_names + opt_names:
414 if name in uniq_names:
415 continue
416 uniq_names.add(name)
417 all_names.append(name)
419 if opt_key is None:
420 opt_key = lambda v: v is None
421 assert callable(opt_key)
423 args = [getattr(obj, name, None) for name in pos_names]
425 kw_items = [(name, getattr(obj, name, None)) for name in all_names]
426 kw_items = [(name, val) for name, val in kw_items
427 if not (name in opt_names and opt_key(val))]
429 return format_invocation(cn, args, kw_items)
432def format_nonexp_repr(obj, req_names=None, opt_names=None, opt_key=None):
433 """Format a non-expression-style repr
435 Some object reprs look like object instantiation, e.g., App(r=[], mw=[]).
437 This makes sense for smaller, lower-level objects whose state
438 roundtrips. But a lot of objects contain values that don't
439 roundtrip, like types and functions.
441 For those objects, there is the non-expression style repr, which
442 mimic's Python's default style to make a repr like so:
444 >>> class Flag(object):
445 ... def __init__(self, length, width, depth=None):
446 ... self.length = length
447 ... self.width = width
448 ... self.depth = depth
449 ...
450 >>> flag = Flag(5, 10)
451 >>> print(format_nonexp_repr(flag, ['length', 'width'], ['depth']))
452 <Flag length=5 width=10>
454 If no attributes are specified or set, utilizes the id, not unlike Python's
455 built-in behavior.
457 >>> print(format_nonexp_repr(flag))
458 <Flag id=...>
459 """
460 cn = obj.__class__.__name__
461 req_names = req_names or []
462 opt_names = opt_names or []
463 uniq_names, all_names = set(), []
464 for name in req_names + opt_names:
465 if name in uniq_names:
466 continue
467 uniq_names.add(name)
468 all_names.append(name)
470 if opt_key is None:
471 opt_key = lambda v: v is None
472 assert callable(opt_key)
474 items = [(name, getattr(obj, name, None)) for name in all_names]
475 labels = ['%s=%r' % (name, val) for name, val in items
476 if not (name in opt_names and opt_key(val))]
477 if not labels:
478 labels = ['id=%s' % id(obj)]
479 ret = '<%s %s>' % (cn, ' '.join(labels))
480 return ret
484# # #
485# # # Function builder
486# # #
489def wraps(func, injected=None, expected=None, **kw):
490 """Decorator factory to apply update_wrapper() to a wrapper function.
492 Modeled after built-in :func:`functools.wraps`. Returns a decorator
493 that invokes update_wrapper() with the decorated function as the wrapper
494 argument and the arguments to wraps() as the remaining arguments.
495 Default arguments are as for update_wrapper(). This is a convenience
496 function to simplify applying partial() to update_wrapper().
498 Same example as in update_wrapper's doc but with wraps:
500 >>> from boltons.funcutils import wraps
501 >>>
502 >>> def print_return(func):
503 ... @wraps(func)
504 ... def wrapper(*args, **kwargs):
505 ... ret = func(*args, **kwargs)
506 ... print(ret)
507 ... return ret
508 ... return wrapper
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'
521 """
522 return partial(update_wrapper, func=func, build_from=None,
523 injected=injected, expected=expected, **kw)
526def update_wrapper(wrapper, func, injected=None, expected=None, build_from=None, **kw):
527 """Modeled after the built-in :func:`functools.update_wrapper`,
528 this function is used to make your wrapper function reflect the
529 wrapped function's:
531 * Name
532 * Documentation
533 * Module
534 * Signature
536 The built-in :func:`functools.update_wrapper` copies the first three, but
537 does not copy the signature. This version of ``update_wrapper`` can copy
538 the inner function's signature exactly, allowing seamless usage
539 and :mod:`introspection <inspect>`. Usage is identical to the
540 built-in version::
542 >>> from boltons.funcutils import update_wrapper
543 >>>
544 >>> def print_return(func):
545 ... def wrapper(*args, **kwargs):
546 ... ret = func(*args, **kwargs)
547 ... print(ret)
548 ... return ret
549 ... return update_wrapper(wrapper, func)
550 ...
551 >>> @print_return
552 ... def example():
553 ... '''docstring'''
554 ... return 'example return value'
555 >>>
556 >>> val = example()
557 example return value
558 >>> example.__name__
559 'example'
560 >>> example.__doc__
561 'docstring'
563 In addition, the boltons version of update_wrapper supports
564 modifying the outer signature. By passing a list of
565 *injected* argument names, those arguments will be removed from
566 the outer wrapper's signature, allowing your decorator to provide
567 arguments that aren't passed in.
569 Args:
571 wrapper (function) : The callable to which the attributes of
572 *func* are to be copied.
573 func (function): The callable whose attributes are to be copied.
574 injected (list): An optional list of argument names which
575 should not appear in the new wrapper's signature.
576 expected (list): An optional list of argument names (or (name,
577 default) pairs) representing new arguments introduced by
578 the wrapper (the opposite of *injected*). See
579 :meth:`FunctionBuilder.add_arg()` for more details.
580 build_from (function): The callable from which the new wrapper
581 is built. Defaults to *func*, unless *wrapper* is partial object
582 built from *func*, in which case it defaults to *wrapper*.
583 Useful in some specific cases where *wrapper* and *func* have the
584 same arguments but differ on which are keyword-only and positional-only.
585 update_dict (bool): Whether to copy other, non-standard
586 attributes of *func* over to the wrapper. Defaults to True.
587 inject_to_varkw (bool): Ignore missing arguments when a
588 ``**kwargs``-type catch-all is present. Defaults to True.
589 hide_wrapped (bool): Remove reference to the wrapped function(s)
590 in the updated function.
592 In opposition to the built-in :func:`functools.update_wrapper` bolton's
593 version returns a copy of the function and does not modify anything in place.
594 For more in-depth wrapping of functions, see the
595 :class:`FunctionBuilder` type, on which update_wrapper was built.
596 """
597 if injected is None:
598 injected = []
599 elif isinstance(injected, basestring):
600 injected = [injected]
601 else:
602 injected = list(injected)
604 expected_items = _parse_wraps_expected(expected)
606 if isinstance(func, (classmethod, staticmethod)):
607 raise TypeError('wraps does not support wrapping classmethods and'
608 ' staticmethods, change the order of wrapping to'
609 ' wrap the underlying function: %r'
610 % (getattr(func, '__func__', None),))
612 update_dict = kw.pop('update_dict', True)
613 inject_to_varkw = kw.pop('inject_to_varkw', True)
614 hide_wrapped = kw.pop('hide_wrapped', False)
615 if kw:
616 raise TypeError('unexpected kwargs: %r' % kw.keys())
618 if isinstance(wrapper, functools.partial) and func is wrapper.func:
619 build_from = build_from or wrapper
621 fb = FunctionBuilder.from_func(build_from or func)
623 for arg in injected:
624 try:
625 fb.remove_arg(arg)
626 except MissingArgument:
627 if inject_to_varkw and fb.varkw is not None:
628 continue # keyword arg will be caught by the varkw
629 raise
631 for arg, default in expected_items:
632 fb.add_arg(arg, default) # may raise ExistingArgument
634 if fb.is_async:
635 fb.body = 'return await _call(%s)' % fb.get_invocation_str()
636 else:
637 fb.body = 'return _call(%s)' % fb.get_invocation_str()
639 execdict = dict(_call=wrapper, _func=func)
640 fully_wrapped = fb.get_func(execdict, with_dict=update_dict)
642 if hide_wrapped and hasattr(fully_wrapped, '__wrapped__'):
643 del fully_wrapped.__dict__['__wrapped__']
644 elif not hide_wrapped:
645 fully_wrapped.__wrapped__ = func # ref to the original function (#115)
647 return fully_wrapped
650def _parse_wraps_expected(expected):
651 # expected takes a pretty powerful argument, it's processed
652 # here. admittedly this would be less trouble if I relied on
653 # OrderedDict (there's an impl of that in the commit history if
654 # you look
655 if expected is None:
656 expected = []
657 elif isinstance(expected, basestring):
658 expected = [(expected, NO_DEFAULT)]
660 expected_items = []
661 try:
662 expected_iter = iter(expected)
663 except TypeError as e:
664 raise ValueError('"expected" takes string name, sequence of string names,'
665 ' iterable of (name, default) pairs, or a mapping of '
666 ' {name: default}, not %r (got: %r)' % (expected, e))
667 for argname in expected_iter:
668 if isinstance(argname, basestring):
669 # dict keys and bare strings
670 try:
671 default = expected[argname]
672 except TypeError:
673 default = NO_DEFAULT
674 else:
675 # pairs
676 try:
677 argname, default = argname
678 except (TypeError, ValueError):
679 raise ValueError('"expected" takes string name, sequence of string names,'
680 ' iterable of (name, default) pairs, or a mapping of '
681 ' {name: default}, not %r')
682 if not isinstance(argname, basestring):
683 raise ValueError('all "expected" argnames must be strings, not %r' % (argname,))
685 expected_items.append((argname, default))
687 return expected_items
690class FunctionBuilder(object):
691 """The FunctionBuilder type provides an interface for programmatically
692 creating new functions, either based on existing functions or from
693 scratch.
695 Values are passed in at construction or set as attributes on the
696 instance. For creating a new function based of an existing one,
697 see the :meth:`~FunctionBuilder.from_func` classmethod. At any
698 point, :meth:`~FunctionBuilder.get_func` can be called to get a
699 newly compiled function, based on the values configured.
701 >>> fb = FunctionBuilder('return_five', doc='returns the integer 5',
702 ... body='return 5')
703 >>> f = fb.get_func()
704 >>> f()
705 5
706 >>> fb.varkw = 'kw'
707 >>> f_kw = fb.get_func()
708 >>> f_kw(ignored_arg='ignored_val')
709 5
711 Note that function signatures themselves changed quite a bit in
712 Python 3, so several arguments are only applicable to
713 FunctionBuilder in Python 3. Except for *name*, all arguments to
714 the constructor are keyword arguments.
716 Args:
717 name (str): Name of the function.
718 doc (str): `Docstring`_ for the function, defaults to empty.
719 module (str): Name of the module from which this function was
720 imported. Defaults to None.
721 body (str): String version of the code representing the body
722 of the function. Defaults to ``'pass'``, which will result
723 in a function which does nothing and returns ``None``.
724 args (list): List of argument names, defaults to empty list,
725 denoting no arguments.
726 varargs (str): Name of the catch-all variable for positional
727 arguments. E.g., "args" if the resultant function is to have
728 ``*args`` in the signature. Defaults to None.
729 varkw (str): Name of the catch-all variable for keyword
730 arguments. E.g., "kwargs" if the resultant function is to have
731 ``**kwargs`` in the signature. Defaults to None.
732 defaults (tuple): A tuple containing default argument values for
733 those arguments that have defaults.
734 kwonlyargs (list): Argument names which are only valid as
735 keyword arguments. **Python 3 only.**
736 kwonlydefaults (dict): A mapping, same as normal *defaults*,
737 but only for the *kwonlyargs*. **Python 3 only.**
738 annotations (dict): Mapping of type hints and so
739 forth. **Python 3 only.**
740 filename (str): The filename that will appear in
741 tracebacks. Defaults to "boltons.funcutils.FunctionBuilder".
742 indent (int): Number of spaces with which to indent the
743 function *body*. Values less than 1 will result in an error.
744 dict (dict): Any other attributes which should be added to the
745 functions compiled with this FunctionBuilder.
747 All of these arguments are also made available as attributes which
748 can be mutated as necessary.
750 .. _Docstring: https://en.wikipedia.org/wiki/Docstring#Python
752 """
754 if _IS_PY2:
755 _argspec_defaults = {'args': list,
756 'varargs': lambda: None,
757 'varkw': lambda: None,
758 'defaults': lambda: None}
760 @classmethod
761 def _argspec_to_dict(cls, f):
762 args, varargs, varkw, defaults = inspect.getargspec(f)
763 return {'args': args,
764 'varargs': varargs,
765 'varkw': varkw,
766 'defaults': defaults}
768 else:
769 _argspec_defaults = {'args': list,
770 'varargs': lambda: None,
771 'varkw': lambda: None,
772 'defaults': lambda: None,
773 'kwonlyargs': list,
774 'kwonlydefaults': dict,
775 'annotations': dict}
777 @classmethod
778 def _argspec_to_dict(cls, f):
779 argspec = inspect.getfullargspec(f)
780 return dict((attr, getattr(argspec, attr))
781 for attr in cls._argspec_defaults)
783 _defaults = {'doc': str,
784 'dict': dict,
785 'is_async': lambda: False,
786 'module': lambda: None,
787 'body': lambda: 'pass',
788 'indent': lambda: 4,
789 "annotations": dict,
790 'filename': lambda: 'boltons.funcutils.FunctionBuilder'}
792 _defaults.update(_argspec_defaults)
794 _compile_count = itertools.count()
796 def __init__(self, name, **kw):
797 self.name = name
798 for a, default_factory in self._defaults.items():
799 val = kw.pop(a, None)
800 if val is None:
801 val = default_factory()
802 setattr(self, a, val)
804 if kw:
805 raise TypeError('unexpected kwargs: %r' % kw.keys())
806 return
808 # def get_argspec(self): # TODO
810 if _IS_PY2:
811 def get_sig_str(self, with_annotations=True):
812 """Return function signature as a string.
814 with_annotations is ignored on Python 2. On Python 3 signature
815 will omit annotations if it is set to False.
816 """
817 return inspect_formatargspec(self.args, self.varargs,
818 self.varkw, [])
820 def get_invocation_str(self):
821 return inspect_formatargspec(self.args, self.varargs,
822 self.varkw, [])[1:-1]
823 else:
824 def get_sig_str(self, with_annotations=True):
825 """Return function signature as a string.
827 with_annotations is ignored on Python 2. On Python 3 signature
828 will omit annotations if it is set to False.
829 """
830 if with_annotations:
831 annotations = self.annotations
832 else:
833 annotations = {}
835 return inspect_formatargspec(self.args,
836 self.varargs,
837 self.varkw,
838 [],
839 self.kwonlyargs,
840 {},
841 annotations)
843 _KWONLY_MARKER = re.compile(r"""
844 \* # a star
845 \s* # followed by any amount of whitespace
846 , # followed by a comma
847 \s* # followed by any amount of whitespace
848 """, re.VERBOSE)
850 def get_invocation_str(self):
851 kwonly_pairs = None
852 formatters = {}
853 if self.kwonlyargs:
854 kwonly_pairs = dict((arg, arg)
855 for arg in self.kwonlyargs)
856 formatters['formatvalue'] = lambda value: '=' + value
858 sig = inspect_formatargspec(self.args,
859 self.varargs,
860 self.varkw,
861 [],
862 kwonly_pairs,
863 kwonly_pairs,
864 {},
865 **formatters)
866 sig = self._KWONLY_MARKER.sub('', sig)
867 return sig[1:-1]
869 @classmethod
870 def from_func(cls, func):
871 """Create a new FunctionBuilder instance based on an existing
872 function. The original function will not be stored or
873 modified.
874 """
875 # TODO: copy_body? gonna need a good signature regex.
876 # TODO: might worry about __closure__?
877 if not callable(func):
878 raise TypeError('expected callable object, not %r' % (func,))
880 if isinstance(func, functools.partial):
881 if _IS_PY2:
882 raise ValueError('Cannot build FunctionBuilder instances from partials in python 2.')
883 kwargs = {'name': func.func.__name__,
884 'doc': func.func.__doc__,
885 'module': getattr(func.func, '__module__', None), # e.g., method_descriptor
886 'annotations': getattr(func.func, "__annotations__", {}),
887 'dict': getattr(func.func, '__dict__', {})}
888 else:
889 kwargs = {'name': func.__name__,
890 'doc': func.__doc__,
891 'module': getattr(func, '__module__', None), # e.g., method_descriptor
892 'annotations': getattr(func, "__annotations__", {}),
893 'dict': getattr(func, '__dict__', {})}
895 kwargs.update(cls._argspec_to_dict(func))
897 if _inspect_iscoroutinefunction(func):
898 kwargs['is_async'] = True
900 return cls(**kwargs)
902 def get_func(self, execdict=None, add_source=True, with_dict=True):
903 """Compile and return a new function based on the current values of
904 the FunctionBuilder.
906 Args:
907 execdict (dict): The dictionary representing the scope in
908 which the compilation should take place. Defaults to an empty
909 dict.
910 add_source (bool): Whether to add the source used to a
911 special ``__source__`` attribute on the resulting
912 function. Defaults to True.
913 with_dict (bool): Add any custom attributes, if
914 applicable. Defaults to True.
916 To see an example of usage, see the implementation of
917 :func:`~boltons.funcutils.wraps`.
918 """
919 execdict = execdict or {}
920 body = self.body or self._default_body
922 tmpl = 'def {name}{sig_str}:'
923 tmpl += '\n{body}'
925 if self.is_async:
926 tmpl = 'async ' + tmpl
928 body = _indent(self.body, ' ' * self.indent)
930 name = self.name.replace('<', '_').replace('>', '_') # lambdas
931 src = tmpl.format(name=name, sig_str=self.get_sig_str(with_annotations=False),
932 doc=self.doc, body=body)
933 self._compile(src, execdict)
934 func = execdict[name]
936 func.__name__ = self.name
937 func.__doc__ = self.doc
938 func.__defaults__ = self.defaults
939 if not _IS_PY2:
940 func.__kwdefaults__ = self.kwonlydefaults
941 func.__annotations__ = self.annotations
943 if with_dict:
944 func.__dict__.update(self.dict)
945 func.__module__ = self.module
946 # TODO: caller module fallback?
948 if add_source:
949 func.__source__ = src
951 return func
953 def get_defaults_dict(self):
954 """Get a dictionary of function arguments with defaults and the
955 respective values.
956 """
957 ret = dict(reversed(list(zip(reversed(self.args),
958 reversed(self.defaults or [])))))
959 kwonlydefaults = getattr(self, 'kwonlydefaults', None)
960 if kwonlydefaults:
961 ret.update(kwonlydefaults)
962 return ret
964 def get_arg_names(self, only_required=False):
965 arg_names = tuple(self.args) + tuple(getattr(self, 'kwonlyargs', ()))
966 if only_required:
967 defaults_dict = self.get_defaults_dict()
968 arg_names = tuple([an for an in arg_names if an not in defaults_dict])
969 return arg_names
971 if _IS_PY2:
972 def add_arg(self, arg_name, default=NO_DEFAULT):
973 "Add an argument with optional *default* (defaults to ``funcutils.NO_DEFAULT``)."
974 if arg_name in self.args:
975 raise ExistingArgument('arg %r already in func %s arg list' % (arg_name, self.name))
976 self.args.append(arg_name)
977 if default is not NO_DEFAULT:
978 self.defaults = (self.defaults or ()) + (default,)
979 return
980 else:
981 def add_arg(self, arg_name, default=NO_DEFAULT, kwonly=False):
982 """Add an argument with optional *default* (defaults to
983 ``funcutils.NO_DEFAULT``). Pass *kwonly=True* to add a
984 keyword-only argument
985 """
986 if arg_name in self.args:
987 raise ExistingArgument('arg %r already in func %s arg list' % (arg_name, self.name))
988 if arg_name in self.kwonlyargs:
989 raise ExistingArgument('arg %r already in func %s kwonly arg list' % (arg_name, self.name))
990 if not kwonly:
991 self.args.append(arg_name)
992 if default is not NO_DEFAULT:
993 self.defaults = (self.defaults or ()) + (default,)
994 else:
995 self.kwonlyargs.append(arg_name)
996 if default is not NO_DEFAULT:
997 self.kwonlydefaults[arg_name] = default
998 return
1000 def remove_arg(self, arg_name):
1001 """Remove an argument from this FunctionBuilder's argument list. The
1002 resulting function will have one less argument per call to
1003 this function.
1005 Args:
1006 arg_name (str): The name of the argument to remove.
1008 Raises a :exc:`ValueError` if the argument is not present.
1010 """
1011 args = self.args
1012 d_dict = self.get_defaults_dict()
1013 try:
1014 args.remove(arg_name)
1015 except ValueError:
1016 try:
1017 self.kwonlyargs.remove(arg_name)
1018 except (AttributeError, ValueError):
1019 # py2, or py3 and missing from both
1020 exc = MissingArgument('arg %r not found in %s argument list:'
1021 ' %r' % (arg_name, self.name, args))
1022 exc.arg_name = arg_name
1023 raise exc
1024 else:
1025 self.kwonlydefaults.pop(arg_name, None)
1026 else:
1027 d_dict.pop(arg_name, None)
1028 self.defaults = tuple([d_dict[a] for a in args if a in d_dict])
1029 return
1031 def _compile(self, src, execdict):
1033 filename = ('<%s-%d>'
1034 % (self.filename, next(self._compile_count),))
1035 try:
1036 code = compile(src, filename, 'single')
1037 exec(code, execdict)
1038 except Exception:
1039 raise
1040 return execdict
1043class MissingArgument(ValueError):
1044 pass
1047class ExistingArgument(ValueError):
1048 pass
1051def _indent(text, margin, newline='\n', key=bool):
1052 "based on boltons.strutils.indent"
1053 indented_lines = [(margin + line if key(line) else line)
1054 for line in text.splitlines()]
1055 return newline.join(indented_lines)
1058try:
1059 from functools import total_ordering # 2.7+
1060except ImportError:
1061 # python 2.6
1062 def total_ordering(cls):
1063 """Class decorator that fills in missing comparators/ordering
1064 methods. Backport of :func:`functools.total_ordering` to work
1065 with Python 2.6.
1067 Code from http://code.activestate.com/recipes/576685/
1068 """
1069 convert = {
1070 '__lt__': [
1071 ('__gt__',
1072 lambda self, other: not (self < other or self == other)),
1073 ('__le__',
1074 lambda self, other: self < other or self == other),
1075 ('__ge__',
1076 lambda self, other: not self < other)],
1077 '__le__': [
1078 ('__ge__',
1079 lambda self, other: not self <= other or self == other),
1080 ('__lt__',
1081 lambda self, other: self <= other and not self == other),
1082 ('__gt__',
1083 lambda self, other: not self <= other)],
1084 '__gt__': [
1085 ('__lt__',
1086 lambda self, other: not (self > other or self == other)),
1087 ('__ge__',
1088 lambda self, other: self > other or self == other),
1089 ('__le__',
1090 lambda self, other: not self > other)],
1091 '__ge__': [
1092 ('__le__',
1093 lambda self, other: (not self >= other) or self == other),
1094 ('__gt__',
1095 lambda self, other: self >= other and not self == other),
1096 ('__lt__',
1097 lambda self, other: not self >= other)]
1098 }
1099 roots = set(dir(cls)) & set(convert)
1100 if not roots:
1101 raise ValueError('must define at least one ordering operation:'
1102 ' < > <= >=')
1103 root = max(roots) # prefer __lt__ to __le__ to __gt__ to __ge__
1104 for opname, opfunc in convert[root]:
1105 if opname not in roots:
1106 opfunc.__name__ = opname
1107 opfunc.__doc__ = getattr(int, opname).__doc__
1108 setattr(cls, opname, opfunc)
1109 return cls
1111def noop(*args, **kwargs):
1112 """
1113 Simple function that should be used when no effect is desired.
1114 An alternative to checking for an optional function type parameter.
1116 e.g.
1117 def decorate(func, pre_func=None, post_func=None):
1118 if pre_func:
1119 pre_func()
1120 func()
1121 if post_func:
1122 post_func()
1124 vs
1126 def decorate(func, pre_func=noop, post_func=noop):
1127 pre_func()
1128 func()
1129 post_func()
1130 """
1131 return None
1133# end funcutils.py