comparison env/lib/python3.9/site-packages/boltons/funcutils.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 # -*- coding: utf-8 -*-
2 """Python's built-in :mod:`functools` module builds several useful
3 utilities on top of Python's first-class function
4 support. ``funcutils`` generally stays in the same vein, adding to and
5 correcting Python's standard metaprogramming facilities.
6 """
7 from __future__ import print_function
8
9 import sys
10 import re
11 import inspect
12 import functools
13 import itertools
14 from types import MethodType, FunctionType
15
16 try:
17 xrange
18 make_method = MethodType
19 except NameError:
20 # Python 3
21 make_method = lambda desc, obj, obj_type: MethodType(desc, obj)
22 basestring = (str, bytes) # Python 3 compat
23 _IS_PY2 = False
24 else:
25 _IS_PY2 = True
26
27
28 try:
29 _inspect_iscoroutinefunction = inspect.iscoroutinefunction
30 except AttributeError:
31 # Python 3.4
32 _inspect_iscoroutinefunction = lambda func: False
33
34
35 try:
36 from boltons.typeutils import make_sentinel
37 NO_DEFAULT = make_sentinel(var_name='NO_DEFAULT')
38 except ImportError:
39 NO_DEFAULT = object()
40
41 try:
42 from functools import partialmethod
43 except ImportError:
44 partialmethod = None
45
46
47 _IS_PY35 = sys.version_info >= (3, 5)
48 if not _IS_PY35:
49 # py35+ wants you to use signature instead, but
50 # inspect_formatargspec is way simpler for what it is. Copied the
51 # vendoring approach from alembic:
52 # https://github.com/sqlalchemy/alembic/blob/4cdad6aec32b4b5573a2009cc356cb4b144bd359/alembic/util/compat.py#L92
53 from inspect import formatargspec as inspect_formatargspec
54 else:
55 from inspect import formatannotation
56
57 def inspect_formatargspec(
58 args, varargs=None, varkw=None, defaults=None,
59 kwonlyargs=(), kwonlydefaults={}, annotations={},
60 formatarg=str,
61 formatvarargs=lambda name: '*' + name,
62 formatvarkw=lambda name: '**' + name,
63 formatvalue=lambda value: '=' + repr(value),
64 formatreturns=lambda text: ' -> ' + text,
65 formatannotation=formatannotation):
66 """Copy formatargspec from python 3.7 standard library.
67 Python 3 has deprecated formatargspec and requested that Signature
68 be used instead, however this requires a full reimplementation
69 of formatargspec() in terms of creating Parameter objects and such.
70 Instead of introducing all the object-creation overhead and having
71 to reinvent from scratch, just copy their compatibility routine.
72 """
73
74 def formatargandannotation(arg):
75 result = formatarg(arg)
76 if arg in annotations:
77 result += ': ' + formatannotation(annotations[arg])
78 return result
79 specs = []
80 if defaults:
81 firstdefault = len(args) - len(defaults)
82 for i, arg in enumerate(args):
83 spec = formatargandannotation(arg)
84 if defaults and i >= firstdefault:
85 spec = spec + formatvalue(defaults[i - firstdefault])
86 specs.append(spec)
87 if varargs is not None:
88 specs.append(formatvarargs(formatargandannotation(varargs)))
89 else:
90 if kwonlyargs:
91 specs.append('*')
92 if kwonlyargs:
93 for kwonlyarg in kwonlyargs:
94 spec = formatargandannotation(kwonlyarg)
95 if kwonlydefaults and kwonlyarg in kwonlydefaults:
96 spec += formatvalue(kwonlydefaults[kwonlyarg])
97 specs.append(spec)
98 if varkw is not None:
99 specs.append(formatvarkw(formatargandannotation(varkw)))
100 result = '(' + ', '.join(specs) + ')'
101 if 'return' in annotations:
102 result += formatreturns(formatannotation(annotations['return']))
103 return result
104
105
106 def get_module_callables(mod, ignore=None):
107 """Returns two maps of (*types*, *funcs*) from *mod*, optionally
108 ignoring based on the :class:`bool` return value of the *ignore*
109 callable. *mod* can be a string name of a module in
110 :data:`sys.modules` or the module instance itself.
111 """
112 if isinstance(mod, basestring):
113 mod = sys.modules[mod]
114 types, funcs = {}, {}
115 for attr_name in dir(mod):
116 if ignore and ignore(attr_name):
117 continue
118 try:
119 attr = getattr(mod, attr_name)
120 except Exception:
121 continue
122 try:
123 attr_mod_name = attr.__module__
124 except AttributeError:
125 continue
126 if attr_mod_name != mod.__name__:
127 continue
128 if isinstance(attr, type):
129 types[attr_name] = attr
130 elif callable(attr):
131 funcs[attr_name] = attr
132 return types, funcs
133
134
135 def mro_items(type_obj):
136 """Takes a type and returns an iterator over all class variables
137 throughout the type hierarchy (respecting the MRO).
138
139 >>> sorted(set([k for k, v in mro_items(int) if not k.startswith('__') and 'bytes' not in k and not callable(v)]))
140 ['denominator', 'imag', 'numerator', 'real']
141 """
142 # TODO: handle slots?
143 return itertools.chain.from_iterable(ct.__dict__.items()
144 for ct in type_obj.__mro__)
145
146
147 def dir_dict(obj, raise_exc=False):
148 """Return a dictionary of attribute names to values for a given
149 object. Unlike ``obj.__dict__``, this function returns all
150 attributes on the object, including ones on parent classes.
151 """
152 # TODO: separate function for handling descriptors on types?
153 ret = {}
154 for k in dir(obj):
155 try:
156 ret[k] = getattr(obj, k)
157 except Exception:
158 if raise_exc:
159 raise
160 return ret
161
162
163 def copy_function(orig, copy_dict=True):
164 """Returns a shallow copy of the function, including code object,
165 globals, closure, etc.
166
167 >>> func = lambda: func
168 >>> func() is func
169 True
170 >>> func_copy = copy_function(func)
171 >>> func_copy() is func
172 True
173 >>> func_copy is not func
174 True
175
176 Args:
177 orig (function): The function to be copied. Must be a
178 function, not just any method or callable.
179 copy_dict (bool): Also copy any attributes set on the function
180 instance. Defaults to ``True``.
181 """
182 ret = FunctionType(orig.__code__,
183 orig.__globals__,
184 name=orig.__name__,
185 argdefs=getattr(orig, "__defaults__", None),
186 closure=getattr(orig, "__closure__", None))
187 if copy_dict:
188 ret.__dict__.update(orig.__dict__)
189 return ret
190
191
192 def 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.
199
200 .. _partial orderings: https://en.wikipedia.org/wiki/Partially_ordered_set
201
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
226
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__
230
231 return cls
232
233
234 class 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.
239
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.
248
249 """
250 if partialmethod is not None: # NB: See https://github.com/mahmoud/boltons/pull/244
251 @property
252 def _partialmethod(self):
253 return partialmethod(self.func, *self.args, **self.keywords)
254
255 def __get__(self, obj, obj_type):
256 return make_method(self, obj, obj_type)
257
258
259
260 class CachedInstancePartial(functools.partial):
261 """The ``CachedInstancePartial`` is virtually the same as
262 :class:`InstancePartial`, adding support for method-usage to
263 :class:`functools.partial`, except that upon first access, it
264 caches the bound method on the associated object, speeding it up
265 for future accesses, and bringing the method call overhead to
266 about the same as non-``partial`` methods.
267
268 See the :class:`InstancePartial` docstring for more details.
269 """
270 if partialmethod is not None: # NB: See https://github.com/mahmoud/boltons/pull/244
271 @property
272 def _partialmethod(self):
273 return partialmethod(self.func, *self.args, **self.keywords)
274
275 def __get__(self, obj, obj_type):
276 # These assignments could've been in __init__, but there was
277 # no simple way to do it without breaking one of PyPy or Py3.
278 self.__name__ = None
279 self.__doc__ = self.func.__doc__
280 self.__module__ = self.func.__module__
281
282 name = self.__name__
283 if name is None:
284 for k, v in mro_items(obj_type):
285 if v is self:
286 self.__name__ = name = k
287 if obj is None:
288 return make_method(self, obj, obj_type)
289 try:
290 # since this is a data descriptor, this block
291 # is probably only hit once (per object)
292 return obj.__dict__[name]
293 except KeyError:
294 obj.__dict__[name] = ret = make_method(self, obj, obj_type)
295 return ret
296
297
298 partial = CachedInstancePartial
299
300
301 def format_invocation(name='', args=(), kwargs=None, **kw):
302 """Given a name, positional arguments, and keyword arguments, format
303 a basic Python-style function call.
304
305 >>> print(format_invocation('func', args=(1, 2), kwargs={'c': 3}))
306 func(1, 2, c=3)
307 >>> print(format_invocation('a_func', args=(1,)))
308 a_func(1)
309 >>> print(format_invocation('kw_func', kwargs=[('a', 1), ('b', 2)]))
310 kw_func(a=1, b=2)
311
312 """
313 _repr = kw.pop('repr', repr)
314 if kw:
315 raise TypeError('unexpected keyword args: %r' % ', '.join(kw.keys()))
316 kwargs = kwargs or {}
317 a_text = ', '.join([_repr(a) for a in args])
318 if isinstance(kwargs, dict):
319 kwarg_items = [(k, kwargs[k]) for k in sorted(kwargs)]
320 else:
321 kwarg_items = kwargs
322 kw_text = ', '.join(['%s=%s' % (k, _repr(v)) for k, v in kwarg_items])
323
324 all_args_text = a_text
325 if all_args_text and kw_text:
326 all_args_text += ', '
327 all_args_text += kw_text
328
329 return '%s(%s)' % (name, all_args_text)
330
331
332 def format_exp_repr(obj, pos_names, req_names=None, opt_names=None, opt_key=None):
333 """Render an expression-style repr of an object, based on attribute
334 names, which are assumed to line up with arguments to an initializer.
335
336 >>> class Flag(object):
337 ... def __init__(self, length, width, depth=None):
338 ... self.length = length
339 ... self.width = width
340 ... self.depth = depth
341 ...
342
343 That's our Flag object, here are some example reprs for it:
344
345 >>> flag = Flag(5, 10)
346 >>> print(format_exp_repr(flag, ['length', 'width'], [], ['depth']))
347 Flag(5, 10)
348 >>> flag2 = Flag(5, 15, 2)
349 >>> print(format_exp_repr(flag2, ['length'], ['width', 'depth']))
350 Flag(5, width=15, depth=2)
351
352 By picking the pos_names, req_names, opt_names, and opt_key, you
353 can fine-tune how you want the repr to look.
354
355 Args:
356 obj (object): The object whose type name will be used and
357 attributes will be checked
358 pos_names (list): Required list of attribute names which will be
359 rendered as positional arguments in the output repr.
360 req_names (list): List of attribute names which will always
361 appear in the keyword arguments in the output repr. Defaults to None.
362 opt_names (list): List of attribute names which may appear in
363 the keyword arguments in the output repr, provided they pass
364 the *opt_key* check. Defaults to None.
365 opt_key (callable): A function or callable which checks whether
366 an opt_name should be in the repr. Defaults to a
367 ``None``-check.
368
369 """
370 cn = obj.__class__.__name__
371 req_names = req_names or []
372 opt_names = opt_names or []
373 uniq_names, all_names = set(), []
374 for name in req_names + opt_names:
375 if name in uniq_names:
376 continue
377 uniq_names.add(name)
378 all_names.append(name)
379
380 if opt_key is None:
381 opt_key = lambda v: v is None
382 assert callable(opt_key)
383
384 args = [getattr(obj, name, None) for name in pos_names]
385
386 kw_items = [(name, getattr(obj, name, None)) for name in all_names]
387 kw_items = [(name, val) for name, val in kw_items
388 if not (name in opt_names and opt_key(val))]
389
390 return format_invocation(cn, args, kw_items)
391
392
393 def format_nonexp_repr(obj, req_names=None, opt_names=None, opt_key=None):
394 """Format a non-expression-style repr
395
396 Some object reprs look like object instantiation, e.g., App(r=[], mw=[]).
397
398 This makes sense for smaller, lower-level objects whose state
399 roundtrips. But a lot of objects contain values that don't
400 roundtrip, like types and functions.
401
402 For those objects, there is the non-expression style repr, which
403 mimic's Python's default style to make a repr like so:
404
405 >>> class Flag(object):
406 ... def __init__(self, length, width, depth=None):
407 ... self.length = length
408 ... self.width = width
409 ... self.depth = depth
410 ...
411 >>> flag = Flag(5, 10)
412 >>> print(format_nonexp_repr(flag, ['length', 'width'], ['depth']))
413 <Flag length=5 width=10>
414
415 If no attributes are specified or set, utilizes the id, not unlike Python's
416 built-in behavior.
417
418 >>> print(format_nonexp_repr(flag))
419 <Flag id=...>
420 """
421 cn = obj.__class__.__name__
422 req_names = req_names or []
423 opt_names = opt_names or []
424 uniq_names, all_names = set(), []
425 for name in req_names + opt_names:
426 if name in uniq_names:
427 continue
428 uniq_names.add(name)
429 all_names.append(name)
430
431 if opt_key is None:
432 opt_key = lambda v: v is None
433 assert callable(opt_key)
434
435 items = [(name, getattr(obj, name, None)) for name in all_names]
436 labels = ['%s=%r' % (name, val) for name, val in items
437 if not (name in opt_names and opt_key(val))]
438 if not labels:
439 labels = ['id=%s' % id(obj)]
440 ret = '<%s %s>' % (cn, ' '.join(labels))
441 return ret
442
443
444
445 # # #
446 # # # Function builder
447 # # #
448
449
450 def wraps(func, injected=None, expected=None, **kw):
451 """Decorator factory to apply update_wrapper() to a wrapper function.
452
453 Modeled after built-in :func:`functools.wraps`. Returns a decorator
454 that invokes update_wrapper() with the decorated function as the wrapper
455 argument and the arguments to wraps() as the remaining arguments.
456 Default arguments are as for update_wrapper(). This is a convenience
457 function to simplify applying partial() to update_wrapper().
458
459 Same example as in update_wrapper's doc but with wraps:
460
461 >>> from boltons.funcutils import wraps
462 >>>
463 >>> def print_return(func):
464 ... @wraps(func)
465 ... def wrapper(*args, **kwargs):
466 ... ret = func(*args, **kwargs)
467 ... print(ret)
468 ... return ret
469 ... return wrapper
470 ...
471 >>> @print_return
472 ... def example():
473 ... '''docstring'''
474 ... return 'example return value'
475 >>>
476 >>> val = example()
477 example return value
478 >>> example.__name__
479 'example'
480 >>> example.__doc__
481 'docstring'
482 """
483 return partial(update_wrapper, func=func, build_from=None,
484 injected=injected, expected=expected, **kw)
485
486
487 def update_wrapper(wrapper, func, injected=None, expected=None, build_from=None, **kw):
488 """Modeled after the built-in :func:`functools.update_wrapper`,
489 this function is used to make your wrapper function reflect the
490 wrapped function's:
491
492 * Name
493 * Documentation
494 * Module
495 * Signature
496
497 The built-in :func:`functools.update_wrapper` copies the first three, but
498 does not copy the signature. This version of ``update_wrapper`` can copy
499 the inner function's signature exactly, allowing seamless usage
500 and :mod:`introspection <inspect>`. Usage is identical to the
501 built-in version::
502
503 >>> from boltons.funcutils import update_wrapper
504 >>>
505 >>> def print_return(func):
506 ... def wrapper(*args, **kwargs):
507 ... ret = func(*args, **kwargs)
508 ... print(ret)
509 ... return ret
510 ... return update_wrapper(wrapper, func)
511 ...
512 >>> @print_return
513 ... def example():
514 ... '''docstring'''
515 ... return 'example return value'
516 >>>
517 >>> val = example()
518 example return value
519 >>> example.__name__
520 'example'
521 >>> example.__doc__
522 'docstring'
523
524 In addition, the boltons version of update_wrapper supports
525 modifying the outer signature. By passing a list of
526 *injected* argument names, those arguments will be removed from
527 the outer wrapper's signature, allowing your decorator to provide
528 arguments that aren't passed in.
529
530 Args:
531
532 wrapper (function) : The callable to which the attributes of
533 *func* are to be copied.
534 func (function): The callable whose attributes are to be copied.
535 injected (list): An optional list of argument names which
536 should not appear in the new wrapper's signature.
537 expected (list): An optional list of argument names (or (name,
538 default) pairs) representing new arguments introduced by
539 the wrapper (the opposite of *injected*). See
540 :meth:`FunctionBuilder.add_arg()` for more details.
541 build_from (function): The callable from which the new wrapper
542 is built. Defaults to *func*, unless *wrapper* is partial object
543 built from *func*, in which case it defaults to *wrapper*.
544 Useful in some specific cases where *wrapper* and *func* have the
545 same arguments but differ on which are keyword-only and positional-only.
546 update_dict (bool): Whether to copy other, non-standard
547 attributes of *func* over to the wrapper. Defaults to True.
548 inject_to_varkw (bool): Ignore missing arguments when a
549 ``**kwargs``-type catch-all is present. Defaults to True.
550 hide_wrapped (bool): Remove reference to the wrapped function(s)
551 in the updated function.
552
553 In opposition to the built-in :func:`functools.update_wrapper` bolton's
554 version returns a copy of the function and does not modifiy anything in place.
555 For more in-depth wrapping of functions, see the
556 :class:`FunctionBuilder` type, on which update_wrapper was built.
557 """
558 if injected is None:
559 injected = []
560 elif isinstance(injected, basestring):
561 injected = [injected]
562 else:
563 injected = list(injected)
564
565 expected_items = _parse_wraps_expected(expected)
566
567 if isinstance(func, (classmethod, staticmethod)):
568 raise TypeError('wraps does not support wrapping classmethods and'
569 ' staticmethods, change the order of wrapping to'
570 ' wrap the underlying function: %r'
571 % (getattr(func, '__func__', None),))
572
573 update_dict = kw.pop('update_dict', True)
574 inject_to_varkw = kw.pop('inject_to_varkw', True)
575 hide_wrapped = kw.pop('hide_wrapped', False)
576 if kw:
577 raise TypeError('unexpected kwargs: %r' % kw.keys())
578
579 if isinstance(wrapper, functools.partial) and func is wrapper.func:
580 build_from = build_from or wrapper
581
582 fb = FunctionBuilder.from_func(build_from or func)
583
584 for arg in injected:
585 try:
586 fb.remove_arg(arg)
587 except MissingArgument:
588 if inject_to_varkw and fb.varkw is not None:
589 continue # keyword arg will be caught by the varkw
590 raise
591
592 for arg, default in expected_items:
593 fb.add_arg(arg, default) # may raise ExistingArgument
594
595 if fb.is_async:
596 fb.body = 'return await _call(%s)' % fb.get_invocation_str()
597 else:
598 fb.body = 'return _call(%s)' % fb.get_invocation_str()
599
600 execdict = dict(_call=wrapper, _func=func)
601 fully_wrapped = fb.get_func(execdict, with_dict=update_dict)
602
603 if hide_wrapped and hasattr(fully_wrapped, '__wrapped__'):
604 del fully_wrapped.__dict__['__wrapped__']
605 elif not hide_wrapped:
606 fully_wrapped.__wrapped__ = func # ref to the original function (#115)
607
608 return fully_wrapped
609
610
611 def _parse_wraps_expected(expected):
612 # expected takes a pretty powerful argument, it's processed
613 # here. admittedly this would be less trouble if I relied on
614 # OrderedDict (there's an impl of that in the commit history if
615 # you look
616 if expected is None:
617 expected = []
618 elif isinstance(expected, basestring):
619 expected = [(expected, NO_DEFAULT)]
620
621 expected_items = []
622 try:
623 expected_iter = iter(expected)
624 except TypeError as e:
625 raise ValueError('"expected" takes string name, sequence of string names,'
626 ' iterable of (name, default) pairs, or a mapping of '
627 ' {name: default}, not %r (got: %r)' % (expected, e))
628 for argname in expected_iter:
629 if isinstance(argname, basestring):
630 # dict keys and bare strings
631 try:
632 default = expected[argname]
633 except TypeError:
634 default = NO_DEFAULT
635 else:
636 # pairs
637 try:
638 argname, default = argname
639 except (TypeError, ValueError):
640 raise ValueError('"expected" takes string name, sequence of string names,'
641 ' iterable of (name, default) pairs, or a mapping of '
642 ' {name: default}, not %r')
643 if not isinstance(argname, basestring):
644 raise ValueError('all "expected" argnames must be strings, not %r' % (argname,))
645
646 expected_items.append((argname, default))
647
648 return expected_items
649
650
651 class FunctionBuilder(object):
652 """The FunctionBuilder type provides an interface for programmatically
653 creating new functions, either based on existing functions or from
654 scratch.
655
656 Values are passed in at construction or set as attributes on the
657 instance. For creating a new function based of an existing one,
658 see the :meth:`~FunctionBuilder.from_func` classmethod. At any
659 point, :meth:`~FunctionBuilder.get_func` can be called to get a
660 newly compiled function, based on the values configured.
661
662 >>> fb = FunctionBuilder('return_five', doc='returns the integer 5',
663 ... body='return 5')
664 >>> f = fb.get_func()
665 >>> f()
666 5
667 >>> fb.varkw = 'kw'
668 >>> f_kw = fb.get_func()
669 >>> f_kw(ignored_arg='ignored_val')
670 5
671
672 Note that function signatures themselves changed quite a bit in
673 Python 3, so several arguments are only applicable to
674 FunctionBuilder in Python 3. Except for *name*, all arguments to
675 the constructor are keyword arguments.
676
677 Args:
678 name (str): Name of the function.
679 doc (str): `Docstring`_ for the function, defaults to empty.
680 module (str): Name of the module from which this function was
681 imported. Defaults to None.
682 body (str): String version of the code representing the body
683 of the function. Defaults to ``'pass'``, which will result
684 in a function which does nothing and returns ``None``.
685 args (list): List of argument names, defaults to empty list,
686 denoting no arguments.
687 varargs (str): Name of the catch-all variable for positional
688 arguments. E.g., "args" if the resultant function is to have
689 ``*args`` in the signature. Defaults to None.
690 varkw (str): Name of the catch-all variable for keyword
691 arguments. E.g., "kwargs" if the resultant function is to have
692 ``**kwargs`` in the signature. Defaults to None.
693 defaults (tuple): A tuple containing default argument values for
694 those arguments that have defaults.
695 kwonlyargs (list): Argument names which are only valid as
696 keyword arguments. **Python 3 only.**
697 kwonlydefaults (dict): A mapping, same as normal *defaults*,
698 but only for the *kwonlyargs*. **Python 3 only.**
699 annotations (dict): Mapping of type hints and so
700 forth. **Python 3 only.**
701 filename (str): The filename that will appear in
702 tracebacks. Defaults to "boltons.funcutils.FunctionBuilder".
703 indent (int): Number of spaces with which to indent the
704 function *body*. Values less than 1 will result in an error.
705 dict (dict): Any other attributes which should be added to the
706 functions compiled with this FunctionBuilder.
707
708 All of these arguments are also made available as attributes which
709 can be mutated as necessary.
710
711 .. _Docstring: https://en.wikipedia.org/wiki/Docstring#Python
712
713 """
714
715 if _IS_PY2:
716 _argspec_defaults = {'args': list,
717 'varargs': lambda: None,
718 'varkw': lambda: None,
719 'defaults': lambda: None}
720
721 @classmethod
722 def _argspec_to_dict(cls, f):
723 args, varargs, varkw, defaults = inspect.getargspec(f)
724 return {'args': args,
725 'varargs': varargs,
726 'varkw': varkw,
727 'defaults': defaults}
728
729 else:
730 _argspec_defaults = {'args': list,
731 'varargs': lambda: None,
732 'varkw': lambda: None,
733 'defaults': lambda: None,
734 'kwonlyargs': list,
735 'kwonlydefaults': dict,
736 'annotations': dict}
737
738 @classmethod
739 def _argspec_to_dict(cls, f):
740 argspec = inspect.getfullargspec(f)
741 return dict((attr, getattr(argspec, attr))
742 for attr in cls._argspec_defaults)
743
744 _defaults = {'doc': str,
745 'dict': dict,
746 'is_async': lambda: False,
747 'module': lambda: None,
748 'body': lambda: 'pass',
749 'indent': lambda: 4,
750 "annotations": dict,
751 'filename': lambda: 'boltons.funcutils.FunctionBuilder'}
752
753 _defaults.update(_argspec_defaults)
754
755 _compile_count = itertools.count()
756
757 def __init__(self, name, **kw):
758 self.name = name
759 for a, default_factory in self._defaults.items():
760 val = kw.pop(a, None)
761 if val is None:
762 val = default_factory()
763 setattr(self, a, val)
764
765 if kw:
766 raise TypeError('unexpected kwargs: %r' % kw.keys())
767 return
768
769 # def get_argspec(self): # TODO
770
771 if _IS_PY2:
772 def get_sig_str(self, with_annotations=True):
773 """Return function signature as a string.
774
775 with_annotations is ignored on Python 2. On Python 3 signature
776 will omit annotations if it is set to False.
777 """
778 return inspect_formatargspec(self.args, self.varargs,
779 self.varkw, [])
780
781 def get_invocation_str(self):
782 return inspect_formatargspec(self.args, self.varargs,
783 self.varkw, [])[1:-1]
784 else:
785 def get_sig_str(self, with_annotations=True):
786 """Return function signature as a string.
787
788 with_annotations is ignored on Python 2. On Python 3 signature
789 will omit annotations if it is set to False.
790 """
791 if with_annotations:
792 annotations = self.annotations
793 else:
794 annotations = {}
795
796 return inspect_formatargspec(self.args,
797 self.varargs,
798 self.varkw,
799 [],
800 self.kwonlyargs,
801 {},
802 annotations)
803
804 _KWONLY_MARKER = re.compile(r"""
805 \* # a star
806 \s* # followed by any amount of whitespace
807 , # followed by a comma
808 \s* # followed by any amount of whitespace
809 """, re.VERBOSE)
810
811 def get_invocation_str(self):
812 kwonly_pairs = None
813 formatters = {}
814 if self.kwonlyargs:
815 kwonly_pairs = dict((arg, arg)
816 for arg in self.kwonlyargs)
817 formatters['formatvalue'] = lambda value: '=' + value
818
819 sig = inspect_formatargspec(self.args,
820 self.varargs,
821 self.varkw,
822 [],
823 kwonly_pairs,
824 kwonly_pairs,
825 {},
826 **formatters)
827 sig = self._KWONLY_MARKER.sub('', sig)
828 return sig[1:-1]
829
830 @classmethod
831 def from_func(cls, func):
832 """Create a new FunctionBuilder instance based on an existing
833 function. The original function will not be stored or
834 modified.
835 """
836 # TODO: copy_body? gonna need a good signature regex.
837 # TODO: might worry about __closure__?
838 if not callable(func):
839 raise TypeError('expected callable object, not %r' % (func,))
840
841 if isinstance(func, functools.partial):
842 if _IS_PY2:
843 raise ValueError('Cannot build FunctionBuilder instances from partials in python 2.')
844 kwargs = {'name': func.func.__name__,
845 'doc': func.func.__doc__,
846 'module': getattr(func.func, '__module__', None), # e.g., method_descriptor
847 'annotations': getattr(func.func, "__annotations__", {}),
848 'dict': getattr(func.func, '__dict__', {})}
849 else:
850 kwargs = {'name': func.__name__,
851 'doc': func.__doc__,
852 'module': getattr(func, '__module__', None), # e.g., method_descriptor
853 'annotations': getattr(func, "__annotations__", {}),
854 'dict': getattr(func, '__dict__', {})}
855
856 kwargs.update(cls._argspec_to_dict(func))
857
858 if _inspect_iscoroutinefunction(func):
859 kwargs['is_async'] = True
860
861 return cls(**kwargs)
862
863 def get_func(self, execdict=None, add_source=True, with_dict=True):
864 """Compile and return a new function based on the current values of
865 the FunctionBuilder.
866
867 Args:
868 execdict (dict): The dictionary representing the scope in
869 which the compilation should take place. Defaults to an empty
870 dict.
871 add_source (bool): Whether to add the source used to a
872 special ``__source__`` attribute on the resulting
873 function. Defaults to True.
874 with_dict (bool): Add any custom attributes, if
875 applicable. Defaults to True.
876
877 To see an example of usage, see the implementation of
878 :func:`~boltons.funcutils.wraps`.
879 """
880 execdict = execdict or {}
881 body = self.body or self._default_body
882
883 tmpl = 'def {name}{sig_str}:'
884 tmpl += '\n{body}'
885
886 if self.is_async:
887 tmpl = 'async ' + tmpl
888
889 body = _indent(self.body, ' ' * self.indent)
890
891 name = self.name.replace('<', '_').replace('>', '_') # lambdas
892 src = tmpl.format(name=name, sig_str=self.get_sig_str(with_annotations=False),
893 doc=self.doc, body=body)
894 self._compile(src, execdict)
895 func = execdict[name]
896
897 func.__name__ = self.name
898 func.__doc__ = self.doc
899 func.__defaults__ = self.defaults
900 if not _IS_PY2:
901 func.__kwdefaults__ = self.kwonlydefaults
902 func.__annotations__ = self.annotations
903
904 if with_dict:
905 func.__dict__.update(self.dict)
906 func.__module__ = self.module
907 # TODO: caller module fallback?
908
909 if add_source:
910 func.__source__ = src
911
912 return func
913
914 def get_defaults_dict(self):
915 """Get a dictionary of function arguments with defaults and the
916 respective values.
917 """
918 ret = dict(reversed(list(zip(reversed(self.args),
919 reversed(self.defaults or [])))))
920 kwonlydefaults = getattr(self, 'kwonlydefaults', None)
921 if kwonlydefaults:
922 ret.update(kwonlydefaults)
923 return ret
924
925 def get_arg_names(self, only_required=False):
926 arg_names = tuple(self.args) + tuple(getattr(self, 'kwonlyargs', ()))
927 if only_required:
928 defaults_dict = self.get_defaults_dict()
929 arg_names = tuple([an for an in arg_names if an not in defaults_dict])
930 return arg_names
931
932 if _IS_PY2:
933 def add_arg(self, arg_name, default=NO_DEFAULT):
934 "Add an argument with optional *default* (defaults to ``funcutils.NO_DEFAULT``)."
935 if arg_name in self.args:
936 raise ExistingArgument('arg %r already in func %s arg list' % (arg_name, self.name))
937 self.args.append(arg_name)
938 if default is not NO_DEFAULT:
939 self.defaults = (self.defaults or ()) + (default,)
940 return
941 else:
942 def add_arg(self, arg_name, default=NO_DEFAULT, kwonly=False):
943 """Add an argument with optional *default* (defaults to
944 ``funcutils.NO_DEFAULT``). Pass *kwonly=True* to add a
945 keyword-only argument
946 """
947 if arg_name in self.args:
948 raise ExistingArgument('arg %r already in func %s arg list' % (arg_name, self.name))
949 if arg_name in self.kwonlyargs:
950 raise ExistingArgument('arg %r already in func %s kwonly arg list' % (arg_name, self.name))
951 if not kwonly:
952 self.args.append(arg_name)
953 if default is not NO_DEFAULT:
954 self.defaults = (self.defaults or ()) + (default,)
955 else:
956 self.kwonlyargs.append(arg_name)
957 if default is not NO_DEFAULT:
958 self.kwonlydefaults[arg_name] = default
959 return
960
961 def remove_arg(self, arg_name):
962 """Remove an argument from this FunctionBuilder's argument list. The
963 resulting function will have one less argument per call to
964 this function.
965
966 Args:
967 arg_name (str): The name of the argument to remove.
968
969 Raises a :exc:`ValueError` if the argument is not present.
970
971 """
972 args = self.args
973 d_dict = self.get_defaults_dict()
974 try:
975 args.remove(arg_name)
976 except ValueError:
977 try:
978 self.kwonlyargs.remove(arg_name)
979 except (AttributeError, ValueError):
980 # py2, or py3 and missing from both
981 exc = MissingArgument('arg %r not found in %s argument list:'
982 ' %r' % (arg_name, self.name, args))
983 exc.arg_name = arg_name
984 raise exc
985 else:
986 self.kwonlydefaults.pop(arg_name, None)
987 else:
988 d_dict.pop(arg_name, None)
989 self.defaults = tuple([d_dict[a] for a in args if a in d_dict])
990 return
991
992 def _compile(self, src, execdict):
993
994 filename = ('<%s-%d>'
995 % (self.filename, next(self._compile_count),))
996 try:
997 code = compile(src, filename, 'single')
998 exec(code, execdict)
999 except Exception:
1000 raise
1001 return execdict
1002
1003
1004 class MissingArgument(ValueError):
1005 pass
1006
1007
1008 class ExistingArgument(ValueError):
1009 pass
1010
1011
1012 def _indent(text, margin, newline='\n', key=bool):
1013 "based on boltons.strutils.indent"
1014 indented_lines = [(margin + line if key(line) else line)
1015 for line in text.splitlines()]
1016 return newline.join(indented_lines)
1017
1018
1019 try:
1020 from functools import total_ordering # 2.7+
1021 except ImportError:
1022 # python 2.6
1023 def total_ordering(cls):
1024 """Class decorator that fills in missing comparators/ordering
1025 methods. Backport of :func:`functools.total_ordering` to work
1026 with Python 2.6.
1027
1028 Code from http://code.activestate.com/recipes/576685/
1029 """
1030 convert = {
1031 '__lt__': [
1032 ('__gt__',
1033 lambda self, other: not (self < other or self == other)),
1034 ('__le__',
1035 lambda self, other: self < other or self == other),
1036 ('__ge__',
1037 lambda self, other: not self < other)],
1038 '__le__': [
1039 ('__ge__',
1040 lambda self, other: not self <= other or self == other),
1041 ('__lt__',
1042 lambda self, other: self <= other and not self == other),
1043 ('__gt__',
1044 lambda self, other: not self <= other)],
1045 '__gt__': [
1046 ('__lt__',
1047 lambda self, other: not (self > other or self == other)),
1048 ('__ge__',
1049 lambda self, other: self > other or self == other),
1050 ('__le__',
1051 lambda self, other: not self > other)],
1052 '__ge__': [
1053 ('__le__',
1054 lambda self, other: (not self >= other) or self == other),
1055 ('__gt__',
1056 lambda self, other: self >= other and not self == other),
1057 ('__lt__',
1058 lambda self, other: not self >= other)]
1059 }
1060 roots = set(dir(cls)) & set(convert)
1061 if not roots:
1062 raise ValueError('must define at least one ordering operation:'
1063 ' < > <= >=')
1064 root = max(roots) # prefer __lt__ to __le__ to __gt__ to __ge__
1065 for opname, opfunc in convert[root]:
1066 if opname not in roots:
1067 opfunc.__name__ = opname
1068 opfunc.__doc__ = getattr(int, opname).__doc__
1069 setattr(cls, opname, opfunc)
1070 return cls
1071
1072 # end funcutils.py