comparison env/lib/python3.7/site-packages/boltons/funcutils.py @ 0:26e78fe6e8c4 draft

"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author shellac
date Sat, 02 May 2020 07:14:21 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:26e78fe6e8c4
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):
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 kwargs = kwargs or {}
314 a_text = ', '.join([repr(a) for a in args])
315 if isinstance(kwargs, dict):
316 kwarg_items = [(k, kwargs[k]) for k in sorted(kwargs)]
317 else:
318 kwarg_items = kwargs
319 kw_text = ', '.join(['%s=%r' % (k, v) for k, v in kwarg_items])
320
321 all_args_text = a_text
322 if all_args_text and kw_text:
323 all_args_text += ', '
324 all_args_text += kw_text
325
326 return '%s(%s)' % (name, all_args_text)
327
328
329 def format_exp_repr(obj, pos_names, req_names=None, opt_names=None, opt_key=None):
330 """Render an expression-style repr of an object, based on attribute
331 names, which are assumed to line up with arguments to an initializer.
332
333 >>> class Flag(object):
334 ... def __init__(self, length, width, depth=None):
335 ... self.length = length
336 ... self.width = width
337 ... self.depth = depth
338 ...
339
340 That's our Flag object, here are some example reprs for it:
341
342 >>> flag = Flag(5, 10)
343 >>> print(format_exp_repr(flag, ['length', 'width'], [], ['depth']))
344 Flag(5, 10)
345 >>> flag2 = Flag(5, 15, 2)
346 >>> print(format_exp_repr(flag2, ['length'], ['width', 'depth']))
347 Flag(5, width=15, depth=2)
348
349 By picking the pos_names, req_names, opt_names, and opt_key, you
350 can fine-tune how you want the repr to look.
351
352 Args:
353 obj (object): The object whose type name will be used and
354 attributes will be checked
355 pos_names (list): Required list of attribute names which will be
356 rendered as positional arguments in the output repr.
357 req_names (list): List of attribute names which will always
358 appear in the keyword arguments in the output repr. Defaults to None.
359 opt_names (list): List of attribute names which may appear in
360 the keyword arguments in the output repr, provided they pass
361 the *opt_key* check. Defaults to None.
362 opt_key (callable): A function or callable which checks whether
363 an opt_name should be in the repr. Defaults to a
364 ``None``-check.
365
366 """
367 cn = obj.__class__.__name__
368 req_names = req_names or []
369 opt_names = opt_names or []
370 uniq_names, all_names = set(), []
371 for name in req_names + opt_names:
372 if name in uniq_names:
373 continue
374 uniq_names.add(name)
375 all_names.append(name)
376
377 if opt_key is None:
378 opt_key = lambda v: v is None
379 assert callable(opt_key)
380
381 args = [getattr(obj, name, None) for name in pos_names]
382
383 kw_items = [(name, getattr(obj, name, None)) for name in all_names]
384 kw_items = [(name, val) for name, val in kw_items
385 if not (name in opt_names and opt_key(val))]
386
387 return format_invocation(cn, args, kw_items)
388
389
390 def format_nonexp_repr(obj, req_names=None, opt_names=None, opt_key=None):
391 """Format a non-expression-style repr
392
393 Some object reprs look like object instantiation, e.g., App(r=[], mw=[]).
394
395 This makes sense for smaller, lower-level objects whose state
396 roundtrips. But a lot of objects contain values that don't
397 roundtrip, like types and functions.
398
399 For those objects, there is the non-expression style repr, which
400 mimic's Python's default style to make a repr like so:
401
402 >>> class Flag(object):
403 ... def __init__(self, length, width, depth=None):
404 ... self.length = length
405 ... self.width = width
406 ... self.depth = depth
407 ...
408 >>> flag = Flag(5, 10)
409 >>> print(format_nonexp_repr(flag, ['length', 'width'], ['depth']))
410 <Flag length=5 width=10>
411
412 If no attributes are specified or set, utilizes the id, not unlike Python's
413 built-in behavior.
414
415 >>> print(format_nonexp_repr(flag))
416 <Flag id=...>
417 """
418 cn = obj.__class__.__name__
419 req_names = req_names or []
420 opt_names = opt_names or []
421 uniq_names, all_names = set(), []
422 for name in req_names + opt_names:
423 if name in uniq_names:
424 continue
425 uniq_names.add(name)
426 all_names.append(name)
427
428 if opt_key is None:
429 opt_key = lambda v: v is None
430 assert callable(opt_key)
431
432 items = [(name, getattr(obj, name, None)) for name in all_names]
433 labels = ['%s=%r' % (name, val) for name, val in items
434 if not (name in opt_names and opt_key(val))]
435 if not labels:
436 labels = ['id=%s' % id(obj)]
437 ret = '<%s %s>' % (cn, ' '.join(labels))
438 return ret
439
440
441
442 # # #
443 # # # Function builder
444 # # #
445
446
447 def wraps(func, injected=None, expected=None, **kw):
448 """Decorator factory to apply update_wrapper() to a wrapper function.
449
450 Modeled after built-in :func:`functools.wraps`. Returns a decorator
451 that invokes update_wrapper() with the decorated function as the wrapper
452 argument and the arguments to wraps() as the remaining arguments.
453 Default arguments are as for update_wrapper(). This is a convenience
454 function to simplify applying partial() to update_wrapper().
455
456 Same example as in update_wrapper's doc but with wraps:
457
458 >>> from boltons.funcutils import wraps
459 >>>
460 >>> def print_return(func):
461 ... @wraps(func)
462 ... def wrapper(*args, **kwargs):
463 ... ret = func(*args, **kwargs)
464 ... print(ret)
465 ... return ret
466 ... return wrapper
467 ...
468 >>> @print_return
469 ... def example():
470 ... '''docstring'''
471 ... return 'example return value'
472 >>>
473 >>> val = example()
474 example return value
475 >>> example.__name__
476 'example'
477 >>> example.__doc__
478 'docstring'
479 """
480 return partial(update_wrapper, func=func, build_from=None,
481 injected=injected, expected=expected, **kw)
482
483
484 def update_wrapper(wrapper, func, injected=None, expected=None, build_from=None, **kw):
485 """Modeled after the built-in :func:`functools.update_wrapper`,
486 this function is used to make your wrapper function reflect the
487 wrapped function's:
488
489 * Name
490 * Documentation
491 * Module
492 * Signature
493
494 The built-in :func:`functools.update_wrapper` copies the first three, but
495 does not copy the signature. This version of ``update_wrapper`` can copy
496 the inner function's signature exactly, allowing seamless usage
497 and :mod:`introspection <inspect>`. Usage is identical to the
498 built-in version::
499
500 >>> from boltons.funcutils import update_wrapper
501 >>>
502 >>> def print_return(func):
503 ... def wrapper(*args, **kwargs):
504 ... ret = func(*args, **kwargs)
505 ... print(ret)
506 ... return ret
507 ... return update_wrapper(wrapper, func)
508 ...
509 >>> @print_return
510 ... def example():
511 ... '''docstring'''
512 ... return 'example return value'
513 >>>
514 >>> val = example()
515 example return value
516 >>> example.__name__
517 'example'
518 >>> example.__doc__
519 'docstring'
520
521 In addition, the boltons version of update_wrapper supports
522 modifying the outer signature. By passing a list of
523 *injected* argument names, those arguments will be removed from
524 the outer wrapper's signature, allowing your decorator to provide
525 arguments that aren't passed in.
526
527 Args:
528
529 wrapper (function) : The callable to which the attributes of
530 *func* are to be copied.
531 func (function): The callable whose attributes are to be copied.
532 injected (list): An optional list of argument names which
533 should not appear in the new wrapper's signature.
534 expected (list): An optional list of argument names (or (name,
535 default) pairs) representing new arguments introduced by
536 the wrapper (the opposite of *injected*). See
537 :meth:`FunctionBuilder.add_arg()` for more details.
538 build_from (function): The callable from which the new wrapper
539 is built. Defaults to *func*, unless *wrapper* is partial object
540 built from *func*, in which case it defaults to *wrapper*.
541 Useful in some specific cases where *wrapper* and *func* have the
542 same arguments but differ on which are keyword-only and positional-only.
543 update_dict (bool): Whether to copy other, non-standard
544 attributes of *func* over to the wrapper. Defaults to True.
545 inject_to_varkw (bool): Ignore missing arguments when a
546 ``**kwargs``-type catch-all is present. Defaults to True.
547 hide_wrapped (bool): Remove reference to the wrapped function(s)
548 in the updated function.
549
550 In opposition to the built-in :func:`functools.update_wrapper` bolton's
551 version returns a copy of the function and does not modifiy anything in place.
552 For more in-depth wrapping of functions, see the
553 :class:`FunctionBuilder` type, on which update_wrapper was built.
554 """
555 if injected is None:
556 injected = []
557 elif isinstance(injected, basestring):
558 injected = [injected]
559 else:
560 injected = list(injected)
561
562 expected_items = _parse_wraps_expected(expected)
563
564 if isinstance(func, (classmethod, staticmethod)):
565 raise TypeError('wraps does not support wrapping classmethods and'
566 ' staticmethods, change the order of wrapping to'
567 ' wrap the underlying function: %r'
568 % (getattr(func, '__func__', None),))
569
570 update_dict = kw.pop('update_dict', True)
571 inject_to_varkw = kw.pop('inject_to_varkw', True)
572 hide_wrapped = kw.pop('hide_wrapped', False)
573 if kw:
574 raise TypeError('unexpected kwargs: %r' % kw.keys())
575
576 if isinstance(wrapper, functools.partial) and func is wrapper.func:
577 build_from = build_from or wrapper
578
579 fb = FunctionBuilder.from_func(build_from or func)
580
581 for arg in injected:
582 try:
583 fb.remove_arg(arg)
584 except MissingArgument:
585 if inject_to_varkw and fb.varkw is not None:
586 continue # keyword arg will be caught by the varkw
587 raise
588
589 for arg, default in expected_items:
590 fb.add_arg(arg, default) # may raise ExistingArgument
591
592 if fb.is_async:
593 fb.body = 'return await _call(%s)' % fb.get_invocation_str()
594 else:
595 fb.body = 'return _call(%s)' % fb.get_invocation_str()
596
597 execdict = dict(_call=wrapper, _func=func)
598 fully_wrapped = fb.get_func(execdict, with_dict=update_dict)
599
600 if hide_wrapped and hasattr(fully_wrapped, '__wrapped__'):
601 del fully_wrapped.__dict__['__wrapped__']
602 elif not hide_wrapped:
603 fully_wrapped.__wrapped__ = func # ref to the original function (#115)
604
605 return fully_wrapped
606
607
608 def _parse_wraps_expected(expected):
609 # expected takes a pretty powerful argument, it's processed
610 # here. admittedly this would be less trouble if I relied on
611 # OrderedDict (there's an impl of that in the commit history if
612 # you look
613 if expected is None:
614 expected = []
615 elif isinstance(expected, basestring):
616 expected = [(expected, NO_DEFAULT)]
617
618 expected_items = []
619 try:
620 expected_iter = iter(expected)
621 except TypeError as e:
622 raise ValueError('"expected" takes string name, sequence of string names,'
623 ' iterable of (name, default) pairs, or a mapping of '
624 ' {name: default}, not %r (got: %r)' % (expected, e))
625 for argname in expected_iter:
626 if isinstance(argname, basestring):
627 # dict keys and bare strings
628 try:
629 default = expected[argname]
630 except TypeError:
631 default = NO_DEFAULT
632 else:
633 # pairs
634 try:
635 argname, default = argname
636 except (TypeError, ValueError):
637 raise ValueError('"expected" takes string name, sequence of string names,'
638 ' iterable of (name, default) pairs, or a mapping of '
639 ' {name: default}, not %r')
640 if not isinstance(argname, basestring):
641 raise ValueError('all "expected" argnames must be strings, not %r' % (argname,))
642
643 expected_items.append((argname, default))
644
645 return expected_items
646
647
648 class FunctionBuilder(object):
649 """The FunctionBuilder type provides an interface for programmatically
650 creating new functions, either based on existing functions or from
651 scratch.
652
653 Values are passed in at construction or set as attributes on the
654 instance. For creating a new function based of an existing one,
655 see the :meth:`~FunctionBuilder.from_func` classmethod. At any
656 point, :meth:`~FunctionBuilder.get_func` can be called to get a
657 newly compiled function, based on the values configured.
658
659 >>> fb = FunctionBuilder('return_five', doc='returns the integer 5',
660 ... body='return 5')
661 >>> f = fb.get_func()
662 >>> f()
663 5
664 >>> fb.varkw = 'kw'
665 >>> f_kw = fb.get_func()
666 >>> f_kw(ignored_arg='ignored_val')
667 5
668
669 Note that function signatures themselves changed quite a bit in
670 Python 3, so several arguments are only applicable to
671 FunctionBuilder in Python 3. Except for *name*, all arguments to
672 the constructor are keyword arguments.
673
674 Args:
675 name (str): Name of the function.
676 doc (str): `Docstring`_ for the function, defaults to empty.
677 module (str): Name of the module from which this function was
678 imported. Defaults to None.
679 body (str): String version of the code representing the body
680 of the function. Defaults to ``'pass'``, which will result
681 in a function which does nothing and returns ``None``.
682 args (list): List of argument names, defaults to empty list,
683 denoting no arguments.
684 varargs (str): Name of the catch-all variable for positional
685 arguments. E.g., "args" if the resultant function is to have
686 ``*args`` in the signature. Defaults to None.
687 varkw (str): Name of the catch-all variable for keyword
688 arguments. E.g., "kwargs" if the resultant function is to have
689 ``**kwargs`` in the signature. Defaults to None.
690 defaults (tuple): A tuple containing default argument values for
691 those arguments that have defaults.
692 kwonlyargs (list): Argument names which are only valid as
693 keyword arguments. **Python 3 only.**
694 kwonlydefaults (dict): A mapping, same as normal *defaults*,
695 but only for the *kwonlyargs*. **Python 3 only.**
696 annotations (dict): Mapping of type hints and so
697 forth. **Python 3 only.**
698 filename (str): The filename that will appear in
699 tracebacks. Defaults to "boltons.funcutils.FunctionBuilder".
700 indent (int): Number of spaces with which to indent the
701 function *body*. Values less than 1 will result in an error.
702 dict (dict): Any other attributes which should be added to the
703 functions compiled with this FunctionBuilder.
704
705 All of these arguments are also made available as attributes which
706 can be mutated as necessary.
707
708 .. _Docstring: https://en.wikipedia.org/wiki/Docstring#Python
709
710 """
711
712 if _IS_PY2:
713 _argspec_defaults = {'args': list,
714 'varargs': lambda: None,
715 'varkw': lambda: None,
716 'defaults': lambda: None}
717
718 @classmethod
719 def _argspec_to_dict(cls, f):
720 args, varargs, varkw, defaults = inspect.getargspec(f)
721 return {'args': args,
722 'varargs': varargs,
723 'varkw': varkw,
724 'defaults': defaults}
725
726 else:
727 _argspec_defaults = {'args': list,
728 'varargs': lambda: None,
729 'varkw': lambda: None,
730 'defaults': lambda: None,
731 'kwonlyargs': list,
732 'kwonlydefaults': dict,
733 'annotations': dict}
734
735 @classmethod
736 def _argspec_to_dict(cls, f):
737 argspec = inspect.getfullargspec(f)
738 return dict((attr, getattr(argspec, attr))
739 for attr in cls._argspec_defaults)
740
741 _defaults = {'doc': str,
742 'dict': dict,
743 'is_async': lambda: False,
744 'module': lambda: None,
745 'body': lambda: 'pass',
746 'indent': lambda: 4,
747 "annotations": dict,
748 'filename': lambda: 'boltons.funcutils.FunctionBuilder'}
749
750 _defaults.update(_argspec_defaults)
751
752 _compile_count = itertools.count()
753
754 def __init__(self, name, **kw):
755 self.name = name
756 for a, default_factory in self._defaults.items():
757 val = kw.pop(a, None)
758 if val is None:
759 val = default_factory()
760 setattr(self, a, val)
761
762 if kw:
763 raise TypeError('unexpected kwargs: %r' % kw.keys())
764 return
765
766 # def get_argspec(self): # TODO
767
768 if _IS_PY2:
769 def get_sig_str(self, with_annotations=True):
770 """Return function signature as a string.
771
772 with_annotations is ignored on Python 2. On Python 3 signature
773 will omit annotations if it is set to False.
774 """
775 return inspect_formatargspec(self.args, self.varargs,
776 self.varkw, [])
777
778 def get_invocation_str(self):
779 return inspect_formatargspec(self.args, self.varargs,
780 self.varkw, [])[1:-1]
781 else:
782 def get_sig_str(self, with_annotations=True):
783 """Return function signature as a string.
784
785 with_annotations is ignored on Python 2. On Python 3 signature
786 will omit annotations if it is set to False.
787 """
788 if with_annotations:
789 annotations = self.annotations
790 else:
791 annotations = {}
792
793 return inspect_formatargspec(self.args,
794 self.varargs,
795 self.varkw,
796 [],
797 self.kwonlyargs,
798 {},
799 annotations)
800
801 _KWONLY_MARKER = re.compile(r"""
802 \* # a star
803 \s* # followed by any amount of whitespace
804 , # followed by a comma
805 \s* # followed by any amount of whitespace
806 """, re.VERBOSE)
807
808 def get_invocation_str(self):
809 kwonly_pairs = None
810 formatters = {}
811 if self.kwonlyargs:
812 kwonly_pairs = dict((arg, arg)
813 for arg in self.kwonlyargs)
814 formatters['formatvalue'] = lambda value: '=' + value
815
816 sig = inspect_formatargspec(self.args,
817 self.varargs,
818 self.varkw,
819 [],
820 kwonly_pairs,
821 kwonly_pairs,
822 {},
823 **formatters)
824 sig = self._KWONLY_MARKER.sub('', sig)
825 return sig[1:-1]
826
827 @classmethod
828 def from_func(cls, func):
829 """Create a new FunctionBuilder instance based on an existing
830 function. The original function will not be stored or
831 modified.
832 """
833 # TODO: copy_body? gonna need a good signature regex.
834 # TODO: might worry about __closure__?
835 if not callable(func):
836 raise TypeError('expected callable object, not %r' % (func,))
837
838 if isinstance(func, functools.partial):
839 if _IS_PY2:
840 raise ValueError('Cannot build FunctionBuilder instances from partials in python 2.')
841 kwargs = {'name': func.func.__name__,
842 'doc': func.func.__doc__,
843 'module': getattr(func.func, '__module__', None), # e.g., method_descriptor
844 'annotations': getattr(func.func, "__annotations__", {}),
845 'dict': getattr(func.func, '__dict__', {})}
846 else:
847 kwargs = {'name': func.__name__,
848 'doc': func.__doc__,
849 'module': getattr(func, '__module__', None), # e.g., method_descriptor
850 'annotations': getattr(func, "__annotations__", {}),
851 'dict': getattr(func, '__dict__', {})}
852
853 kwargs.update(cls._argspec_to_dict(func))
854
855 if _inspect_iscoroutinefunction(func):
856 kwargs['is_async'] = True
857
858 return cls(**kwargs)
859
860 def get_func(self, execdict=None, add_source=True, with_dict=True):
861 """Compile and return a new function based on the current values of
862 the FunctionBuilder.
863
864 Args:
865 execdict (dict): The dictionary representing the scope in
866 which the compilation should take place. Defaults to an empty
867 dict.
868 add_source (bool): Whether to add the source used to a
869 special ``__source__`` attribute on the resulting
870 function. Defaults to True.
871 with_dict (bool): Add any custom attributes, if
872 applicable. Defaults to True.
873
874 To see an example of usage, see the implementation of
875 :func:`~boltons.funcutils.wraps`.
876 """
877 execdict = execdict or {}
878 body = self.body or self._default_body
879
880 tmpl = 'def {name}{sig_str}:'
881 tmpl += '\n{body}'
882
883 if self.is_async:
884 tmpl = 'async ' + tmpl
885
886 body = _indent(self.body, ' ' * self.indent)
887
888 name = self.name.replace('<', '_').replace('>', '_') # lambdas
889 src = tmpl.format(name=name, sig_str=self.get_sig_str(with_annotations=False),
890 doc=self.doc, body=body)
891 self._compile(src, execdict)
892 func = execdict[name]
893
894 func.__name__ = self.name
895 func.__doc__ = self.doc
896 func.__defaults__ = self.defaults
897 if not _IS_PY2:
898 func.__kwdefaults__ = self.kwonlydefaults
899 func.__annotations__ = self.annotations
900
901 if with_dict:
902 func.__dict__.update(self.dict)
903 func.__module__ = self.module
904 # TODO: caller module fallback?
905
906 if add_source:
907 func.__source__ = src
908
909 return func
910
911 def get_defaults_dict(self):
912 """Get a dictionary of function arguments with defaults and the
913 respective values.
914 """
915 ret = dict(reversed(list(zip(reversed(self.args),
916 reversed(self.defaults or [])))))
917 kwonlydefaults = getattr(self, 'kwonlydefaults', None)
918 if kwonlydefaults:
919 ret.update(kwonlydefaults)
920 return ret
921
922 def get_arg_names(self, only_required=False):
923 arg_names = tuple(self.args) + tuple(getattr(self, 'kwonlyargs', ()))
924 if only_required:
925 defaults_dict = self.get_defaults_dict()
926 arg_names = tuple([an for an in arg_names if an not in defaults_dict])
927 return arg_names
928
929 if _IS_PY2:
930 def add_arg(self, arg_name, default=NO_DEFAULT):
931 "Add an argument with optional *default* (defaults to ``funcutils.NO_DEFAULT``)."
932 if arg_name in self.args:
933 raise ExistingArgument('arg %r already in func %s arg list' % (arg_name, self.name))
934 self.args.append(arg_name)
935 if default is not NO_DEFAULT:
936 self.defaults = (self.defaults or ()) + (default,)
937 return
938 else:
939 def add_arg(self, arg_name, default=NO_DEFAULT, kwonly=False):
940 """Add an argument with optional *default* (defaults to
941 ``funcutils.NO_DEFAULT``). Pass *kwonly=True* to add a
942 keyword-only argument
943 """
944 if arg_name in self.args:
945 raise ExistingArgument('arg %r already in func %s arg list' % (arg_name, self.name))
946 if arg_name in self.kwonlyargs:
947 raise ExistingArgument('arg %r already in func %s kwonly arg list' % (arg_name, self.name))
948 if not kwonly:
949 self.args.append(arg_name)
950 if default is not NO_DEFAULT:
951 self.defaults = (self.defaults or ()) + (default,)
952 else:
953 self.kwonlyargs.append(arg_name)
954 if default is not NO_DEFAULT:
955 self.kwonlydefaults[arg_name] = default
956 return
957
958 def remove_arg(self, arg_name):
959 """Remove an argument from this FunctionBuilder's argument list. The
960 resulting function will have one less argument per call to
961 this function.
962
963 Args:
964 arg_name (str): The name of the argument to remove.
965
966 Raises a :exc:`ValueError` if the argument is not present.
967
968 """
969 args = self.args
970 d_dict = self.get_defaults_dict()
971 try:
972 args.remove(arg_name)
973 except ValueError:
974 try:
975 self.kwonlyargs.remove(arg_name)
976 except (AttributeError, ValueError):
977 # py2, or py3 and missing from both
978 exc = MissingArgument('arg %r not found in %s argument list:'
979 ' %r' % (arg_name, self.name, args))
980 exc.arg_name = arg_name
981 raise exc
982 else:
983 self.kwonlydefaults.pop(arg_name, None)
984 else:
985 d_dict.pop(arg_name, None)
986 self.defaults = tuple([d_dict[a] for a in args if a in d_dict])
987 return
988
989 def _compile(self, src, execdict):
990
991 filename = ('<%s-%d>'
992 % (self.filename, next(self._compile_count),))
993 try:
994 code = compile(src, filename, 'single')
995 exec(code, execdict)
996 except Exception:
997 raise
998 return execdict
999
1000
1001 class MissingArgument(ValueError):
1002 pass
1003
1004
1005 class ExistingArgument(ValueError):
1006 pass
1007
1008
1009 def _indent(text, margin, newline='\n', key=bool):
1010 "based on boltons.strutils.indent"
1011 indented_lines = [(margin + line if key(line) else line)
1012 for line in text.splitlines()]
1013 return newline.join(indented_lines)
1014
1015
1016 try:
1017 from functools import total_ordering # 2.7+
1018 except ImportError:
1019 # python 2.6
1020 def total_ordering(cls):
1021 """Class decorator that fills in missing comparators/ordering
1022 methods. Backport of :func:`functools.total_ordering` to work
1023 with Python 2.6.
1024
1025 Code from http://code.activestate.com/recipes/576685/
1026 """
1027 convert = {
1028 '__lt__': [
1029 ('__gt__',
1030 lambda self, other: not (self < other or self == other)),
1031 ('__le__',
1032 lambda self, other: self < other or self == other),
1033 ('__ge__',
1034 lambda self, other: not self < other)],
1035 '__le__': [
1036 ('__ge__',
1037 lambda self, other: not self <= other or self == other),
1038 ('__lt__',
1039 lambda self, other: self <= other and not self == other),
1040 ('__gt__',
1041 lambda self, other: not self <= other)],
1042 '__gt__': [
1043 ('__lt__',
1044 lambda self, other: not (self > other or self == other)),
1045 ('__ge__',
1046 lambda self, other: self > other or self == other),
1047 ('__le__',
1048 lambda self, other: not self > other)],
1049 '__ge__': [
1050 ('__le__',
1051 lambda self, other: (not self >= other) or self == other),
1052 ('__gt__',
1053 lambda self, other: self >= other and not self == other),
1054 ('__lt__',
1055 lambda self, other: not self >= other)]
1056 }
1057 roots = set(dir(cls)) & set(convert)
1058 if not roots:
1059 raise ValueError('must define at least one ordering operation:'
1060 ' < > <= >=')
1061 root = max(roots) # prefer __lt__ to __le__ to __gt__ to __ge__
1062 for opname, opfunc in convert[root]:
1063 if opname not in roots:
1064 opfunc.__name__ = opname
1065 opfunc.__doc__ = getattr(int, opname).__doc__
1066 setattr(cls, opname, opfunc)
1067 return cls
1068
1069 # end funcutils.py