Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/boltons/funcutils.py: 27%
417 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:13 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:13 +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 copy_dict:
219 ret.__dict__.update(orig.__dict__)
220 return ret
223def partial_ordering(cls):
224 """Class decorator, similar to :func:`functools.total_ordering`,
225 except it is used to define `partial orderings`_ (i.e., it is
226 possible that *x* is neither greater than, equal to, or less than
227 *y*). It assumes the presence of the ``__le__()`` and ``__ge__()``
228 method, but nothing else. It will not override any existing
229 additional comparison methods.
231 .. _partial orderings: https://en.wikipedia.org/wiki/Partially_ordered_set
233 >>> @partial_ordering
234 ... class MySet(set):
235 ... def __le__(self, other):
236 ... return self.issubset(other)
237 ... def __ge__(self, other):
238 ... return self.issuperset(other)
239 ...
240 >>> a = MySet([1,2,3])
241 >>> b = MySet([1,2])
242 >>> c = MySet([1,2,4])
243 >>> b < a
244 True
245 >>> b > a
246 False
247 >>> b < c
248 True
249 >>> a < c
250 False
251 >>> c > a
252 False
253 """
254 def __lt__(self, other): return self <= other and not self >= other
255 def __gt__(self, other): return self >= other and not self <= other
256 def __eq__(self, other): return self >= other and self <= other
258 if not hasattr(cls, '__lt__'): cls.__lt__ = __lt__
259 if not hasattr(cls, '__gt__'): cls.__gt__ = __gt__
260 if not hasattr(cls, '__eq__'): cls.__eq__ = __eq__
262 return cls
265class InstancePartial(functools.partial):
266 """:class:`functools.partial` is a huge convenience for anyone
267 working with Python's great first-class functions. It allows
268 developers to curry arguments and incrementally create simpler
269 callables for a variety of use cases.
271 Unfortunately there's one big gap in its usefulness:
272 methods. Partials just don't get bound as methods and
273 automatically handed a reference to ``self``. The
274 ``InstancePartial`` type remedies this by inheriting from
275 :class:`functools.partial` and implementing the necessary
276 descriptor protocol. There are no other differences in
277 implementation or usage. :class:`CachedInstancePartial`, below,
278 has the same ability, but is slightly more efficient.
280 """
281 if partialmethod is not None: # NB: See https://github.com/mahmoud/boltons/pull/244
282 @property
283 def _partialmethod(self):
284 return partialmethod(self.func, *self.args, **self.keywords)
286 def __get__(self, obj, obj_type):
287 return make_method(self, obj, obj_type)
291class CachedInstancePartial(functools.partial):
292 """The ``CachedInstancePartial`` is virtually the same as
293 :class:`InstancePartial`, adding support for method-usage to
294 :class:`functools.partial`, except that upon first access, it
295 caches the bound method on the associated object, speeding it up
296 for future accesses, and bringing the method call overhead to
297 about the same as non-``partial`` methods.
299 See the :class:`InstancePartial` docstring for more details.
300 """
301 if partialmethod is not None: # NB: See https://github.com/mahmoud/boltons/pull/244
302 @property
303 def _partialmethod(self):
304 return partialmethod(self.func, *self.args, **self.keywords)
306 if sys.version_info >= (3, 6):
307 def __set_name__(self, obj_type, name):
308 self.__name__ = name
310 def __get__(self, obj, obj_type):
311 # These assignments could've been in __init__, but there was
312 # no simple way to do it without breaking one of PyPy or Py3.
313 self.__name__ = getattr(self, "__name__", None)
314 self.__doc__ = self.func.__doc__
315 self.__module__ = self.func.__module__
317 name = self.__name__
319 # if you're on python 3.6+, name will never be `None` bc `__set_name__` sets it when descriptor getting assigned
320 if name is None:
321 for k, v in mro_items(obj_type):
322 if v is self:
323 self.__name__ = name = k
324 if obj is None:
325 return make_method(self, obj, obj_type)
326 try:
327 # since this is a data descriptor, this block
328 # is probably only hit once (per object)
329 return obj.__dict__[name]
330 except KeyError:
331 obj.__dict__[name] = ret = make_method(self, obj, obj_type)
332 return ret
335partial = CachedInstancePartial
338def format_invocation(name='', args=(), kwargs=None, **kw):
339 """Given a name, positional arguments, and keyword arguments, format
340 a basic Python-style function call.
342 >>> print(format_invocation('func', args=(1, 2), kwargs={'c': 3}))
343 func(1, 2, c=3)
344 >>> print(format_invocation('a_func', args=(1,)))
345 a_func(1)
346 >>> print(format_invocation('kw_func', kwargs=[('a', 1), ('b', 2)]))
347 kw_func(a=1, b=2)
349 """
350 _repr = kw.pop('repr', repr)
351 if kw:
352 raise TypeError('unexpected keyword args: %r' % ', '.join(kw.keys()))
353 kwargs = kwargs or {}
354 a_text = ', '.join([_repr(a) for a in args])
355 if isinstance(kwargs, dict):
356 kwarg_items = [(k, kwargs[k]) for k in sorted(kwargs)]
357 else:
358 kwarg_items = kwargs
359 kw_text = ', '.join(['%s=%s' % (k, _repr(v)) for k, v in kwarg_items])
361 all_args_text = a_text
362 if all_args_text and kw_text:
363 all_args_text += ', '
364 all_args_text += kw_text
366 return '%s(%s)' % (name, all_args_text)
369def format_exp_repr(obj, pos_names, req_names=None, opt_names=None, opt_key=None):
370 """Render an expression-style repr of an object, based on attribute
371 names, which are assumed to line up with arguments to an initializer.
373 >>> class Flag(object):
374 ... def __init__(self, length, width, depth=None):
375 ... self.length = length
376 ... self.width = width
377 ... self.depth = depth
378 ...
380 That's our Flag object, here are some example reprs for it:
382 >>> flag = Flag(5, 10)
383 >>> print(format_exp_repr(flag, ['length', 'width'], [], ['depth']))
384 Flag(5, 10)
385 >>> flag2 = Flag(5, 15, 2)
386 >>> print(format_exp_repr(flag2, ['length'], ['width', 'depth']))
387 Flag(5, width=15, depth=2)
389 By picking the pos_names, req_names, opt_names, and opt_key, you
390 can fine-tune how you want the repr to look.
392 Args:
393 obj (object): The object whose type name will be used and
394 attributes will be checked
395 pos_names (list): Required list of attribute names which will be
396 rendered as positional arguments in the output repr.
397 req_names (list): List of attribute names which will always
398 appear in the keyword arguments in the output repr. Defaults to None.
399 opt_names (list): List of attribute names which may appear in
400 the keyword arguments in the output repr, provided they pass
401 the *opt_key* check. Defaults to None.
402 opt_key (callable): A function or callable which checks whether
403 an opt_name should be in the repr. Defaults to a
404 ``None``-check.
406 """
407 cn = type(obj).__name__
408 req_names = req_names or []
409 opt_names = opt_names or []
410 uniq_names, all_names = set(), []
411 for name in req_names + opt_names:
412 if name in uniq_names:
413 continue
414 uniq_names.add(name)
415 all_names.append(name)
417 if opt_key is None:
418 opt_key = lambda v: v is None
419 assert callable(opt_key)
421 args = [getattr(obj, name, None) for name in pos_names]
423 kw_items = [(name, getattr(obj, name, None)) for name in all_names]
424 kw_items = [(name, val) for name, val in kw_items
425 if not (name in opt_names and opt_key(val))]
427 return format_invocation(cn, args, kw_items)
430def format_nonexp_repr(obj, req_names=None, opt_names=None, opt_key=None):
431 """Format a non-expression-style repr
433 Some object reprs look like object instantiation, e.g., App(r=[], mw=[]).
435 This makes sense for smaller, lower-level objects whose state
436 roundtrips. But a lot of objects contain values that don't
437 roundtrip, like types and functions.
439 For those objects, there is the non-expression style repr, which
440 mimic's Python's default style to make a repr like so:
442 >>> class Flag(object):
443 ... def __init__(self, length, width, depth=None):
444 ... self.length = length
445 ... self.width = width
446 ... self.depth = depth
447 ...
448 >>> flag = Flag(5, 10)
449 >>> print(format_nonexp_repr(flag, ['length', 'width'], ['depth']))
450 <Flag length=5 width=10>
452 If no attributes are specified or set, utilizes the id, not unlike Python's
453 built-in behavior.
455 >>> print(format_nonexp_repr(flag))
456 <Flag id=...>
457 """
458 cn = obj.__class__.__name__
459 req_names = req_names or []
460 opt_names = opt_names or []
461 uniq_names, all_names = set(), []
462 for name in req_names + opt_names:
463 if name in uniq_names:
464 continue
465 uniq_names.add(name)
466 all_names.append(name)
468 if opt_key is None:
469 opt_key = lambda v: v is None
470 assert callable(opt_key)
472 items = [(name, getattr(obj, name, None)) for name in all_names]
473 labels = ['%s=%r' % (name, val) for name, val in items
474 if not (name in opt_names and opt_key(val))]
475 if not labels:
476 labels = ['id=%s' % id(obj)]
477 ret = '<%s %s>' % (cn, ' '.join(labels))
478 return ret
482# # #
483# # # Function builder
484# # #
487def wraps(func, injected=None, expected=None, **kw):
488 """Decorator factory to apply update_wrapper() to a wrapper function.
490 Modeled after built-in :func:`functools.wraps`. Returns a decorator
491 that invokes update_wrapper() with the decorated function as the wrapper
492 argument and the arguments to wraps() as the remaining arguments.
493 Default arguments are as for update_wrapper(). This is a convenience
494 function to simplify applying partial() to update_wrapper().
496 Same example as in update_wrapper's doc but with wraps:
498 >>> from boltons.funcutils import wraps
499 >>>
500 >>> def print_return(func):
501 ... @wraps(func)
502 ... def wrapper(*args, **kwargs):
503 ... ret = func(*args, **kwargs)
504 ... print(ret)
505 ... return ret
506 ... return wrapper
507 ...
508 >>> @print_return
509 ... def example():
510 ... '''docstring'''
511 ... return 'example return value'
512 >>>
513 >>> val = example()
514 example return value
515 >>> example.__name__
516 'example'
517 >>> example.__doc__
518 'docstring'
519 """
520 return partial(update_wrapper, func=func, build_from=None,
521 injected=injected, expected=expected, **kw)
524def update_wrapper(wrapper, func, injected=None, expected=None, build_from=None, **kw):
525 """Modeled after the built-in :func:`functools.update_wrapper`,
526 this function is used to make your wrapper function reflect the
527 wrapped function's:
529 * Name
530 * Documentation
531 * Module
532 * Signature
534 The built-in :func:`functools.update_wrapper` copies the first three, but
535 does not copy the signature. This version of ``update_wrapper`` can copy
536 the inner function's signature exactly, allowing seamless usage
537 and :mod:`introspection <inspect>`. Usage is identical to the
538 built-in version::
540 >>> from boltons.funcutils import update_wrapper
541 >>>
542 >>> def print_return(func):
543 ... def wrapper(*args, **kwargs):
544 ... ret = func(*args, **kwargs)
545 ... print(ret)
546 ... return ret
547 ... return update_wrapper(wrapper, func)
548 ...
549 >>> @print_return
550 ... def example():
551 ... '''docstring'''
552 ... return 'example return value'
553 >>>
554 >>> val = example()
555 example return value
556 >>> example.__name__
557 'example'
558 >>> example.__doc__
559 'docstring'
561 In addition, the boltons version of update_wrapper supports
562 modifying the outer signature. By passing a list of
563 *injected* argument names, those arguments will be removed from
564 the outer wrapper's signature, allowing your decorator to provide
565 arguments that aren't passed in.
567 Args:
569 wrapper (function) : The callable to which the attributes of
570 *func* are to be copied.
571 func (function): The callable whose attributes are to be copied.
572 injected (list): An optional list of argument names which
573 should not appear in the new wrapper's signature.
574 expected (list): An optional list of argument names (or (name,
575 default) pairs) representing new arguments introduced by
576 the wrapper (the opposite of *injected*). See
577 :meth:`FunctionBuilder.add_arg()` for more details.
578 build_from (function): The callable from which the new wrapper
579 is built. Defaults to *func*, unless *wrapper* is partial object
580 built from *func*, in which case it defaults to *wrapper*.
581 Useful in some specific cases where *wrapper* and *func* have the
582 same arguments but differ on which are keyword-only and positional-only.
583 update_dict (bool): Whether to copy other, non-standard
584 attributes of *func* over to the wrapper. Defaults to True.
585 inject_to_varkw (bool): Ignore missing arguments when a
586 ``**kwargs``-type catch-all is present. Defaults to True.
587 hide_wrapped (bool): Remove reference to the wrapped function(s)
588 in the updated function.
590 In opposition to the built-in :func:`functools.update_wrapper` bolton's
591 version returns a copy of the function and does not modify anything in place.
592 For more in-depth wrapping of functions, see the
593 :class:`FunctionBuilder` type, on which update_wrapper was built.
594 """
595 if injected is None:
596 injected = []
597 elif isinstance(injected, basestring):
598 injected = [injected]
599 else:
600 injected = list(injected)
602 expected_items = _parse_wraps_expected(expected)
604 if isinstance(func, (classmethod, staticmethod)):
605 raise TypeError('wraps does not support wrapping classmethods and'
606 ' staticmethods, change the order of wrapping to'
607 ' wrap the underlying function: %r'
608 % (getattr(func, '__func__', None),))
610 update_dict = kw.pop('update_dict', True)
611 inject_to_varkw = kw.pop('inject_to_varkw', True)
612 hide_wrapped = kw.pop('hide_wrapped', False)
613 if kw:
614 raise TypeError('unexpected kwargs: %r' % kw.keys())
616 if isinstance(wrapper, functools.partial) and func is wrapper.func:
617 build_from = build_from or wrapper
619 fb = FunctionBuilder.from_func(build_from or func)
621 for arg in injected:
622 try:
623 fb.remove_arg(arg)
624 except MissingArgument:
625 if inject_to_varkw and fb.varkw is not None:
626 continue # keyword arg will be caught by the varkw
627 raise
629 for arg, default in expected_items:
630 fb.add_arg(arg, default) # may raise ExistingArgument
632 if fb.is_async:
633 fb.body = 'return await _call(%s)' % fb.get_invocation_str()
634 else:
635 fb.body = 'return _call(%s)' % fb.get_invocation_str()
637 execdict = dict(_call=wrapper, _func=func)
638 fully_wrapped = fb.get_func(execdict, with_dict=update_dict)
640 if hide_wrapped and hasattr(fully_wrapped, '__wrapped__'):
641 del fully_wrapped.__dict__['__wrapped__']
642 elif not hide_wrapped:
643 fully_wrapped.__wrapped__ = func # ref to the original function (#115)
645 return fully_wrapped
648def _parse_wraps_expected(expected):
649 # expected takes a pretty powerful argument, it's processed
650 # here. admittedly this would be less trouble if I relied on
651 # OrderedDict (there's an impl of that in the commit history if
652 # you look
653 if expected is None:
654 expected = []
655 elif isinstance(expected, basestring):
656 expected = [(expected, NO_DEFAULT)]
658 expected_items = []
659 try:
660 expected_iter = iter(expected)
661 except TypeError as e:
662 raise ValueError('"expected" takes string name, sequence of string names,'
663 ' iterable of (name, default) pairs, or a mapping of '
664 ' {name: default}, not %r (got: %r)' % (expected, e))
665 for argname in expected_iter:
666 if isinstance(argname, basestring):
667 # dict keys and bare strings
668 try:
669 default = expected[argname]
670 except TypeError:
671 default = NO_DEFAULT
672 else:
673 # pairs
674 try:
675 argname, default = argname
676 except (TypeError, ValueError):
677 raise ValueError('"expected" takes string name, sequence of string names,'
678 ' iterable of (name, default) pairs, or a mapping of '
679 ' {name: default}, not %r')
680 if not isinstance(argname, basestring):
681 raise ValueError('all "expected" argnames must be strings, not %r' % (argname,))
683 expected_items.append((argname, default))
685 return expected_items
688class FunctionBuilder(object):
689 """The FunctionBuilder type provides an interface for programmatically
690 creating new functions, either based on existing functions or from
691 scratch.
693 Values are passed in at construction or set as attributes on the
694 instance. For creating a new function based of an existing one,
695 see the :meth:`~FunctionBuilder.from_func` classmethod. At any
696 point, :meth:`~FunctionBuilder.get_func` can be called to get a
697 newly compiled function, based on the values configured.
699 >>> fb = FunctionBuilder('return_five', doc='returns the integer 5',
700 ... body='return 5')
701 >>> f = fb.get_func()
702 >>> f()
703 5
704 >>> fb.varkw = 'kw'
705 >>> f_kw = fb.get_func()
706 >>> f_kw(ignored_arg='ignored_val')
707 5
709 Note that function signatures themselves changed quite a bit in
710 Python 3, so several arguments are only applicable to
711 FunctionBuilder in Python 3. Except for *name*, all arguments to
712 the constructor are keyword arguments.
714 Args:
715 name (str): Name of the function.
716 doc (str): `Docstring`_ for the function, defaults to empty.
717 module (str): Name of the module from which this function was
718 imported. Defaults to None.
719 body (str): String version of the code representing the body
720 of the function. Defaults to ``'pass'``, which will result
721 in a function which does nothing and returns ``None``.
722 args (list): List of argument names, defaults to empty list,
723 denoting no arguments.
724 varargs (str): Name of the catch-all variable for positional
725 arguments. E.g., "args" if the resultant function is to have
726 ``*args`` in the signature. Defaults to None.
727 varkw (str): Name of the catch-all variable for keyword
728 arguments. E.g., "kwargs" if the resultant function is to have
729 ``**kwargs`` in the signature. Defaults to None.
730 defaults (tuple): A tuple containing default argument values for
731 those arguments that have defaults.
732 kwonlyargs (list): Argument names which are only valid as
733 keyword arguments. **Python 3 only.**
734 kwonlydefaults (dict): A mapping, same as normal *defaults*,
735 but only for the *kwonlyargs*. **Python 3 only.**
736 annotations (dict): Mapping of type hints and so
737 forth. **Python 3 only.**
738 filename (str): The filename that will appear in
739 tracebacks. Defaults to "boltons.funcutils.FunctionBuilder".
740 indent (int): Number of spaces with which to indent the
741 function *body*. Values less than 1 will result in an error.
742 dict (dict): Any other attributes which should be added to the
743 functions compiled with this FunctionBuilder.
745 All of these arguments are also made available as attributes which
746 can be mutated as necessary.
748 .. _Docstring: https://en.wikipedia.org/wiki/Docstring#Python
750 """
752 if _IS_PY2:
753 _argspec_defaults = {'args': list,
754 'varargs': lambda: None,
755 'varkw': lambda: None,
756 'defaults': lambda: None}
758 @classmethod
759 def _argspec_to_dict(cls, f):
760 args, varargs, varkw, defaults = inspect.getargspec(f)
761 return {'args': args,
762 'varargs': varargs,
763 'varkw': varkw,
764 'defaults': defaults}
766 else:
767 _argspec_defaults = {'args': list,
768 'varargs': lambda: None,
769 'varkw': lambda: None,
770 'defaults': lambda: None,
771 'kwonlyargs': list,
772 'kwonlydefaults': dict,
773 'annotations': dict}
775 @classmethod
776 def _argspec_to_dict(cls, f):
777 argspec = inspect.getfullargspec(f)
778 return dict((attr, getattr(argspec, attr))
779 for attr in cls._argspec_defaults)
781 _defaults = {'doc': str,
782 'dict': dict,
783 'is_async': lambda: False,
784 'module': lambda: None,
785 'body': lambda: 'pass',
786 'indent': lambda: 4,
787 "annotations": dict,
788 'filename': lambda: 'boltons.funcutils.FunctionBuilder'}
790 _defaults.update(_argspec_defaults)
792 _compile_count = itertools.count()
794 def __init__(self, name, **kw):
795 self.name = name
796 for a, default_factory in self._defaults.items():
797 val = kw.pop(a, None)
798 if val is None:
799 val = default_factory()
800 setattr(self, a, val)
802 if kw:
803 raise TypeError('unexpected kwargs: %r' % kw.keys())
804 return
806 # def get_argspec(self): # TODO
808 if _IS_PY2:
809 def get_sig_str(self, with_annotations=True):
810 """Return function signature as a string.
812 with_annotations is ignored on Python 2. On Python 3 signature
813 will omit annotations if it is set to False.
814 """
815 return inspect_formatargspec(self.args, self.varargs,
816 self.varkw, [])
818 def get_invocation_str(self):
819 return inspect_formatargspec(self.args, self.varargs,
820 self.varkw, [])[1:-1]
821 else:
822 def get_sig_str(self, with_annotations=True):
823 """Return function signature as a string.
825 with_annotations is ignored on Python 2. On Python 3 signature
826 will omit annotations if it is set to False.
827 """
828 if with_annotations:
829 annotations = self.annotations
830 else:
831 annotations = {}
833 return inspect_formatargspec(self.args,
834 self.varargs,
835 self.varkw,
836 [],
837 self.kwonlyargs,
838 {},
839 annotations)
841 _KWONLY_MARKER = re.compile(r"""
842 \* # a star
843 \s* # followed by any amount of whitespace
844 , # followed by a comma
845 \s* # followed by any amount of whitespace
846 """, re.VERBOSE)
848 def get_invocation_str(self):
849 kwonly_pairs = None
850 formatters = {}
851 if self.kwonlyargs:
852 kwonly_pairs = dict((arg, arg)
853 for arg in self.kwonlyargs)
854 formatters['formatvalue'] = lambda value: '=' + value
856 sig = inspect_formatargspec(self.args,
857 self.varargs,
858 self.varkw,
859 [],
860 kwonly_pairs,
861 kwonly_pairs,
862 {},
863 **formatters)
864 sig = self._KWONLY_MARKER.sub('', sig)
865 return sig[1:-1]
867 @classmethod
868 def from_func(cls, func):
869 """Create a new FunctionBuilder instance based on an existing
870 function. The original function will not be stored or
871 modified.
872 """
873 # TODO: copy_body? gonna need a good signature regex.
874 # TODO: might worry about __closure__?
875 if not callable(func):
876 raise TypeError('expected callable object, not %r' % (func,))
878 if isinstance(func, functools.partial):
879 if _IS_PY2:
880 raise ValueError('Cannot build FunctionBuilder instances from partials in python 2.')
881 kwargs = {'name': func.func.__name__,
882 'doc': func.func.__doc__,
883 'module': getattr(func.func, '__module__', None), # e.g., method_descriptor
884 'annotations': getattr(func.func, "__annotations__", {}),
885 'dict': getattr(func.func, '__dict__', {})}
886 else:
887 kwargs = {'name': func.__name__,
888 'doc': func.__doc__,
889 'module': getattr(func, '__module__', None), # e.g., method_descriptor
890 'annotations': getattr(func, "__annotations__", {}),
891 'dict': getattr(func, '__dict__', {})}
893 kwargs.update(cls._argspec_to_dict(func))
895 if _inspect_iscoroutinefunction(func):
896 kwargs['is_async'] = True
898 return cls(**kwargs)
900 def get_func(self, execdict=None, add_source=True, with_dict=True):
901 """Compile and return a new function based on the current values of
902 the FunctionBuilder.
904 Args:
905 execdict (dict): The dictionary representing the scope in
906 which the compilation should take place. Defaults to an empty
907 dict.
908 add_source (bool): Whether to add the source used to a
909 special ``__source__`` attribute on the resulting
910 function. Defaults to True.
911 with_dict (bool): Add any custom attributes, if
912 applicable. Defaults to True.
914 To see an example of usage, see the implementation of
915 :func:`~boltons.funcutils.wraps`.
916 """
917 execdict = execdict or {}
918 body = self.body or self._default_body
920 tmpl = 'def {name}{sig_str}:'
921 tmpl += '\n{body}'
923 if self.is_async:
924 tmpl = 'async ' + tmpl
926 body = _indent(self.body, ' ' * self.indent)
928 name = self.name.replace('<', '_').replace('>', '_') # lambdas
929 src = tmpl.format(name=name, sig_str=self.get_sig_str(with_annotations=False),
930 doc=self.doc, body=body)
931 self._compile(src, execdict)
932 func = execdict[name]
934 func.__name__ = self.name
935 func.__doc__ = self.doc
936 func.__defaults__ = self.defaults
937 if not _IS_PY2:
938 func.__kwdefaults__ = self.kwonlydefaults
939 func.__annotations__ = self.annotations
941 if with_dict:
942 func.__dict__.update(self.dict)
943 func.__module__ = self.module
944 # TODO: caller module fallback?
946 if add_source:
947 func.__source__ = src
949 return func
951 def get_defaults_dict(self):
952 """Get a dictionary of function arguments with defaults and the
953 respective values.
954 """
955 ret = dict(reversed(list(zip(reversed(self.args),
956 reversed(self.defaults or [])))))
957 kwonlydefaults = getattr(self, 'kwonlydefaults', None)
958 if kwonlydefaults:
959 ret.update(kwonlydefaults)
960 return ret
962 def get_arg_names(self, only_required=False):
963 arg_names = tuple(self.args) + tuple(getattr(self, 'kwonlyargs', ()))
964 if only_required:
965 defaults_dict = self.get_defaults_dict()
966 arg_names = tuple([an for an in arg_names if an not in defaults_dict])
967 return arg_names
969 if _IS_PY2:
970 def add_arg(self, arg_name, default=NO_DEFAULT):
971 "Add an argument with optional *default* (defaults to ``funcutils.NO_DEFAULT``)."
972 if arg_name in self.args:
973 raise ExistingArgument('arg %r already in func %s arg list' % (arg_name, self.name))
974 self.args.append(arg_name)
975 if default is not NO_DEFAULT:
976 self.defaults = (self.defaults or ()) + (default,)
977 return
978 else:
979 def add_arg(self, arg_name, default=NO_DEFAULT, kwonly=False):
980 """Add an argument with optional *default* (defaults to
981 ``funcutils.NO_DEFAULT``). Pass *kwonly=True* to add a
982 keyword-only argument
983 """
984 if arg_name in self.args:
985 raise ExistingArgument('arg %r already in func %s arg list' % (arg_name, self.name))
986 if arg_name in self.kwonlyargs:
987 raise ExistingArgument('arg %r already in func %s kwonly arg list' % (arg_name, self.name))
988 if not kwonly:
989 self.args.append(arg_name)
990 if default is not NO_DEFAULT:
991 self.defaults = (self.defaults or ()) + (default,)
992 else:
993 self.kwonlyargs.append(arg_name)
994 if default is not NO_DEFAULT:
995 self.kwonlydefaults[arg_name] = default
996 return
998 def remove_arg(self, arg_name):
999 """Remove an argument from this FunctionBuilder's argument list. The
1000 resulting function will have one less argument per call to
1001 this function.
1003 Args:
1004 arg_name (str): The name of the argument to remove.
1006 Raises a :exc:`ValueError` if the argument is not present.
1008 """
1009 args = self.args
1010 d_dict = self.get_defaults_dict()
1011 try:
1012 args.remove(arg_name)
1013 except ValueError:
1014 try:
1015 self.kwonlyargs.remove(arg_name)
1016 except (AttributeError, ValueError):
1017 # py2, or py3 and missing from both
1018 exc = MissingArgument('arg %r not found in %s argument list:'
1019 ' %r' % (arg_name, self.name, args))
1020 exc.arg_name = arg_name
1021 raise exc
1022 else:
1023 self.kwonlydefaults.pop(arg_name, None)
1024 else:
1025 d_dict.pop(arg_name, None)
1026 self.defaults = tuple([d_dict[a] for a in args if a in d_dict])
1027 return
1029 def _compile(self, src, execdict):
1031 filename = ('<%s-%d>'
1032 % (self.filename, next(self._compile_count),))
1033 try:
1034 code = compile(src, filename, 'single')
1035 exec(code, execdict)
1036 except Exception:
1037 raise
1038 return execdict
1041class MissingArgument(ValueError):
1042 pass
1045class ExistingArgument(ValueError):
1046 pass
1049def _indent(text, margin, newline='\n', key=bool):
1050 "based on boltons.strutils.indent"
1051 indented_lines = [(margin + line if key(line) else line)
1052 for line in text.splitlines()]
1053 return newline.join(indented_lines)
1056try:
1057 from functools import total_ordering # 2.7+
1058except ImportError:
1059 # python 2.6
1060 def total_ordering(cls):
1061 """Class decorator that fills in missing comparators/ordering
1062 methods. Backport of :func:`functools.total_ordering` to work
1063 with Python 2.6.
1065 Code from http://code.activestate.com/recipes/576685/
1066 """
1067 convert = {
1068 '__lt__': [
1069 ('__gt__',
1070 lambda self, other: not (self < other or self == other)),
1071 ('__le__',
1072 lambda self, other: self < other or self == other),
1073 ('__ge__',
1074 lambda self, other: not self < other)],
1075 '__le__': [
1076 ('__ge__',
1077 lambda self, other: not self <= other or self == other),
1078 ('__lt__',
1079 lambda self, other: self <= other and not self == other),
1080 ('__gt__',
1081 lambda self, other: not self <= other)],
1082 '__gt__': [
1083 ('__lt__',
1084 lambda self, other: not (self > other or self == other)),
1085 ('__ge__',
1086 lambda self, other: self > other or self == other),
1087 ('__le__',
1088 lambda self, other: not self > other)],
1089 '__ge__': [
1090 ('__le__',
1091 lambda self, other: (not self >= other) or self == other),
1092 ('__gt__',
1093 lambda self, other: self >= other and not self == other),
1094 ('__lt__',
1095 lambda self, other: not self >= other)]
1096 }
1097 roots = set(dir(cls)) & set(convert)
1098 if not roots:
1099 raise ValueError('must define at least one ordering operation:'
1100 ' < > <= >=')
1101 root = max(roots) # prefer __lt__ to __le__ to __gt__ to __ge__
1102 for opname, opfunc in convert[root]:
1103 if opname not in roots:
1104 opfunc.__name__ = opname
1105 opfunc.__doc__ = getattr(int, opname).__doc__
1106 setattr(cls, opname, opfunc)
1107 return cls
1109def noop(*args, **kwargs):
1110 """
1111 Simple function that should be used when no effect is desired.
1112 An alternative to checking for an optional function type parameter.
1114 e.g.
1115 def decorate(func, pre_func=None, post_func=None):
1116 if pre_func:
1117 pre_func()
1118 func()
1119 if post_func:
1120 post_func()
1122 vs
1124 def decorate(func, pre_func=noop, post_func=noop):
1125 pre_func()
1126 func()
1127 post_func()
1128 """
1129 return None
1131# end funcutils.py