Mercurial > repos > shellac > guppy_basecaller
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/env/lib/python3.7/site-packages/boltons/funcutils.py Sat May 02 07:14:21 2020 -0400 @@ -0,0 +1,1069 @@ +# -*- coding: utf-8 -*- +"""Python's built-in :mod:`functools` module builds several useful +utilities on top of Python's first-class function +support. ``funcutils`` generally stays in the same vein, adding to and +correcting Python's standard metaprogramming facilities. +""" +from __future__ import print_function + +import sys +import re +import inspect +import functools +import itertools +from types import MethodType, FunctionType + +try: + xrange + make_method = MethodType +except NameError: + # Python 3 + make_method = lambda desc, obj, obj_type: MethodType(desc, obj) + basestring = (str, bytes) # Python 3 compat + _IS_PY2 = False +else: + _IS_PY2 = True + + +try: + _inspect_iscoroutinefunction = inspect.iscoroutinefunction +except AttributeError: + # Python 3.4 + _inspect_iscoroutinefunction = lambda func: False + + +try: + from boltons.typeutils import make_sentinel + NO_DEFAULT = make_sentinel(var_name='NO_DEFAULT') +except ImportError: + NO_DEFAULT = object() + +try: + from functools import partialmethod +except ImportError: + partialmethod = None + + +_IS_PY35 = sys.version_info >= (3, 5) +if not _IS_PY35: + # py35+ wants you to use signature instead, but + # inspect_formatargspec is way simpler for what it is. Copied the + # vendoring approach from alembic: + # https://github.com/sqlalchemy/alembic/blob/4cdad6aec32b4b5573a2009cc356cb4b144bd359/alembic/util/compat.py#L92 + from inspect import formatargspec as inspect_formatargspec +else: + from inspect import formatannotation + + def inspect_formatargspec( + args, varargs=None, varkw=None, defaults=None, + kwonlyargs=(), kwonlydefaults={}, annotations={}, + formatarg=str, + formatvarargs=lambda name: '*' + name, + formatvarkw=lambda name: '**' + name, + formatvalue=lambda value: '=' + repr(value), + formatreturns=lambda text: ' -> ' + text, + formatannotation=formatannotation): + """Copy formatargspec from python 3.7 standard library. + Python 3 has deprecated formatargspec and requested that Signature + be used instead, however this requires a full reimplementation + of formatargspec() in terms of creating Parameter objects and such. + Instead of introducing all the object-creation overhead and having + to reinvent from scratch, just copy their compatibility routine. + """ + + def formatargandannotation(arg): + result = formatarg(arg) + if arg in annotations: + result += ': ' + formatannotation(annotations[arg]) + return result + specs = [] + if defaults: + firstdefault = len(args) - len(defaults) + for i, arg in enumerate(args): + spec = formatargandannotation(arg) + if defaults and i >= firstdefault: + spec = spec + formatvalue(defaults[i - firstdefault]) + specs.append(spec) + if varargs is not None: + specs.append(formatvarargs(formatargandannotation(varargs))) + else: + if kwonlyargs: + specs.append('*') + if kwonlyargs: + for kwonlyarg in kwonlyargs: + spec = formatargandannotation(kwonlyarg) + if kwonlydefaults and kwonlyarg in kwonlydefaults: + spec += formatvalue(kwonlydefaults[kwonlyarg]) + specs.append(spec) + if varkw is not None: + specs.append(formatvarkw(formatargandannotation(varkw))) + result = '(' + ', '.join(specs) + ')' + if 'return' in annotations: + result += formatreturns(formatannotation(annotations['return'])) + return result + + +def get_module_callables(mod, ignore=None): + """Returns two maps of (*types*, *funcs*) from *mod*, optionally + ignoring based on the :class:`bool` return value of the *ignore* + callable. *mod* can be a string name of a module in + :data:`sys.modules` or the module instance itself. + """ + if isinstance(mod, basestring): + mod = sys.modules[mod] + types, funcs = {}, {} + for attr_name in dir(mod): + if ignore and ignore(attr_name): + continue + try: + attr = getattr(mod, attr_name) + except Exception: + continue + try: + attr_mod_name = attr.__module__ + except AttributeError: + continue + if attr_mod_name != mod.__name__: + continue + if isinstance(attr, type): + types[attr_name] = attr + elif callable(attr): + funcs[attr_name] = attr + return types, funcs + + +def mro_items(type_obj): + """Takes a type and returns an iterator over all class variables + throughout the type hierarchy (respecting the MRO). + + >>> sorted(set([k for k, v in mro_items(int) if not k.startswith('__') and 'bytes' not in k and not callable(v)])) + ['denominator', 'imag', 'numerator', 'real'] + """ + # TODO: handle slots? + return itertools.chain.from_iterable(ct.__dict__.items() + for ct in type_obj.__mro__) + + +def dir_dict(obj, raise_exc=False): + """Return a dictionary of attribute names to values for a given + object. Unlike ``obj.__dict__``, this function returns all + attributes on the object, including ones on parent classes. + """ + # TODO: separate function for handling descriptors on types? + ret = {} + for k in dir(obj): + try: + ret[k] = getattr(obj, k) + except Exception: + if raise_exc: + raise + return ret + + +def copy_function(orig, copy_dict=True): + """Returns a shallow copy of the function, including code object, + globals, closure, etc. + + >>> func = lambda: func + >>> func() is func + True + >>> func_copy = copy_function(func) + >>> func_copy() is func + True + >>> func_copy is not func + True + + Args: + orig (function): The function to be copied. Must be a + function, not just any method or callable. + copy_dict (bool): Also copy any attributes set on the function + instance. Defaults to ``True``. + """ + ret = FunctionType(orig.__code__, + orig.__globals__, + name=orig.__name__, + argdefs=getattr(orig, "__defaults__", None), + closure=getattr(orig, "__closure__", None)) + if copy_dict: + ret.__dict__.update(orig.__dict__) + return ret + + +def partial_ordering(cls): + """Class decorator, similar to :func:`functools.total_ordering`, + except it is used to define `partial orderings`_ (i.e., it is + possible that *x* is neither greater than, equal to, or less than + *y*). It assumes the presence of the ``__le__()`` and ``__ge__()`` + method, but nothing else. It will not override any existing + additional comparison methods. + + .. _partial orderings: https://en.wikipedia.org/wiki/Partially_ordered_set + + >>> @partial_ordering + ... class MySet(set): + ... def __le__(self, other): + ... return self.issubset(other) + ... def __ge__(self, other): + ... return self.issuperset(other) + ... + >>> a = MySet([1,2,3]) + >>> b = MySet([1,2]) + >>> c = MySet([1,2,4]) + >>> b < a + True + >>> b > a + False + >>> b < c + True + >>> a < c + False + >>> c > a + False + """ + def __lt__(self, other): return self <= other and not self >= other + def __gt__(self, other): return self >= other and not self <= other + def __eq__(self, other): return self >= other and self <= other + + if not hasattr(cls, '__lt__'): cls.__lt__ = __lt__ + if not hasattr(cls, '__gt__'): cls.__gt__ = __gt__ + if not hasattr(cls, '__eq__'): cls.__eq__ = __eq__ + + return cls + + +class InstancePartial(functools.partial): + """:class:`functools.partial` is a huge convenience for anyone + working with Python's great first-class functions. It allows + developers to curry arguments and incrementally create simpler + callables for a variety of use cases. + + Unfortunately there's one big gap in its usefulness: + methods. Partials just don't get bound as methods and + automatically handed a reference to ``self``. The + ``InstancePartial`` type remedies this by inheriting from + :class:`functools.partial` and implementing the necessary + descriptor protocol. There are no other differences in + implementation or usage. :class:`CachedInstancePartial`, below, + has the same ability, but is slightly more efficient. + + """ + if partialmethod is not None: # NB: See https://github.com/mahmoud/boltons/pull/244 + @property + def _partialmethod(self): + return partialmethod(self.func, *self.args, **self.keywords) + + def __get__(self, obj, obj_type): + return make_method(self, obj, obj_type) + + + +class CachedInstancePartial(functools.partial): + """The ``CachedInstancePartial`` is virtually the same as + :class:`InstancePartial`, adding support for method-usage to + :class:`functools.partial`, except that upon first access, it + caches the bound method on the associated object, speeding it up + for future accesses, and bringing the method call overhead to + about the same as non-``partial`` methods. + + See the :class:`InstancePartial` docstring for more details. + """ + if partialmethod is not None: # NB: See https://github.com/mahmoud/boltons/pull/244 + @property + def _partialmethod(self): + return partialmethod(self.func, *self.args, **self.keywords) + + def __get__(self, obj, obj_type): + # These assignments could've been in __init__, but there was + # no simple way to do it without breaking one of PyPy or Py3. + self.__name__ = None + self.__doc__ = self.func.__doc__ + self.__module__ = self.func.__module__ + + name = self.__name__ + if name is None: + for k, v in mro_items(obj_type): + if v is self: + self.__name__ = name = k + if obj is None: + return make_method(self, obj, obj_type) + try: + # since this is a data descriptor, this block + # is probably only hit once (per object) + return obj.__dict__[name] + except KeyError: + obj.__dict__[name] = ret = make_method(self, obj, obj_type) + return ret + + +partial = CachedInstancePartial + + +def format_invocation(name='', args=(), kwargs=None): + """Given a name, positional arguments, and keyword arguments, format + a basic Python-style function call. + + >>> print(format_invocation('func', args=(1, 2), kwargs={'c': 3})) + func(1, 2, c=3) + >>> print(format_invocation('a_func', args=(1,))) + a_func(1) + >>> print(format_invocation('kw_func', kwargs=[('a', 1), ('b', 2)])) + kw_func(a=1, b=2) + + """ + kwargs = kwargs or {} + a_text = ', '.join([repr(a) for a in args]) + if isinstance(kwargs, dict): + kwarg_items = [(k, kwargs[k]) for k in sorted(kwargs)] + else: + kwarg_items = kwargs + kw_text = ', '.join(['%s=%r' % (k, v) for k, v in kwarg_items]) + + all_args_text = a_text + if all_args_text and kw_text: + all_args_text += ', ' + all_args_text += kw_text + + return '%s(%s)' % (name, all_args_text) + + +def format_exp_repr(obj, pos_names, req_names=None, opt_names=None, opt_key=None): + """Render an expression-style repr of an object, based on attribute + names, which are assumed to line up with arguments to an initializer. + + >>> class Flag(object): + ... def __init__(self, length, width, depth=None): + ... self.length = length + ... self.width = width + ... self.depth = depth + ... + + That's our Flag object, here are some example reprs for it: + + >>> flag = Flag(5, 10) + >>> print(format_exp_repr(flag, ['length', 'width'], [], ['depth'])) + Flag(5, 10) + >>> flag2 = Flag(5, 15, 2) + >>> print(format_exp_repr(flag2, ['length'], ['width', 'depth'])) + Flag(5, width=15, depth=2) + + By picking the pos_names, req_names, opt_names, and opt_key, you + can fine-tune how you want the repr to look. + + Args: + obj (object): The object whose type name will be used and + attributes will be checked + pos_names (list): Required list of attribute names which will be + rendered as positional arguments in the output repr. + req_names (list): List of attribute names which will always + appear in the keyword arguments in the output repr. Defaults to None. + opt_names (list): List of attribute names which may appear in + the keyword arguments in the output repr, provided they pass + the *opt_key* check. Defaults to None. + opt_key (callable): A function or callable which checks whether + an opt_name should be in the repr. Defaults to a + ``None``-check. + + """ + cn = obj.__class__.__name__ + req_names = req_names or [] + opt_names = opt_names or [] + uniq_names, all_names = set(), [] + for name in req_names + opt_names: + if name in uniq_names: + continue + uniq_names.add(name) + all_names.append(name) + + if opt_key is None: + opt_key = lambda v: v is None + assert callable(opt_key) + + args = [getattr(obj, name, None) for name in pos_names] + + kw_items = [(name, getattr(obj, name, None)) for name in all_names] + kw_items = [(name, val) for name, val in kw_items + if not (name in opt_names and opt_key(val))] + + return format_invocation(cn, args, kw_items) + + +def format_nonexp_repr(obj, req_names=None, opt_names=None, opt_key=None): + """Format a non-expression-style repr + + Some object reprs look like object instantiation, e.g., App(r=[], mw=[]). + + This makes sense for smaller, lower-level objects whose state + roundtrips. But a lot of objects contain values that don't + roundtrip, like types and functions. + + For those objects, there is the non-expression style repr, which + mimic's Python's default style to make a repr like so: + + >>> class Flag(object): + ... def __init__(self, length, width, depth=None): + ... self.length = length + ... self.width = width + ... self.depth = depth + ... + >>> flag = Flag(5, 10) + >>> print(format_nonexp_repr(flag, ['length', 'width'], ['depth'])) + <Flag length=5 width=10> + + If no attributes are specified or set, utilizes the id, not unlike Python's + built-in behavior. + + >>> print(format_nonexp_repr(flag)) + <Flag id=...> + """ + cn = obj.__class__.__name__ + req_names = req_names or [] + opt_names = opt_names or [] + uniq_names, all_names = set(), [] + for name in req_names + opt_names: + if name in uniq_names: + continue + uniq_names.add(name) + all_names.append(name) + + if opt_key is None: + opt_key = lambda v: v is None + assert callable(opt_key) + + items = [(name, getattr(obj, name, None)) for name in all_names] + labels = ['%s=%r' % (name, val) for name, val in items + if not (name in opt_names and opt_key(val))] + if not labels: + labels = ['id=%s' % id(obj)] + ret = '<%s %s>' % (cn, ' '.join(labels)) + return ret + + + +# # # +# # # Function builder +# # # + + +def wraps(func, injected=None, expected=None, **kw): + """Decorator factory to apply update_wrapper() to a wrapper function. + + Modeled after built-in :func:`functools.wraps`. Returns a decorator + that invokes update_wrapper() with the decorated function as the wrapper + argument and the arguments to wraps() as the remaining arguments. + Default arguments are as for update_wrapper(). This is a convenience + function to simplify applying partial() to update_wrapper(). + + Same example as in update_wrapper's doc but with wraps: + + >>> from boltons.funcutils import wraps + >>> + >>> def print_return(func): + ... @wraps(func) + ... def wrapper(*args, **kwargs): + ... ret = func(*args, **kwargs) + ... print(ret) + ... return ret + ... return wrapper + ... + >>> @print_return + ... def example(): + ... '''docstring''' + ... return 'example return value' + >>> + >>> val = example() + example return value + >>> example.__name__ + 'example' + >>> example.__doc__ + 'docstring' + """ + return partial(update_wrapper, func=func, build_from=None, + injected=injected, expected=expected, **kw) + + +def update_wrapper(wrapper, func, injected=None, expected=None, build_from=None, **kw): + """Modeled after the built-in :func:`functools.update_wrapper`, + this function is used to make your wrapper function reflect the + wrapped function's: + + * Name + * Documentation + * Module + * Signature + + The built-in :func:`functools.update_wrapper` copies the first three, but + does not copy the signature. This version of ``update_wrapper`` can copy + the inner function's signature exactly, allowing seamless usage + and :mod:`introspection <inspect>`. Usage is identical to the + built-in version:: + + >>> from boltons.funcutils import update_wrapper + >>> + >>> def print_return(func): + ... def wrapper(*args, **kwargs): + ... ret = func(*args, **kwargs) + ... print(ret) + ... return ret + ... return update_wrapper(wrapper, func) + ... + >>> @print_return + ... def example(): + ... '''docstring''' + ... return 'example return value' + >>> + >>> val = example() + example return value + >>> example.__name__ + 'example' + >>> example.__doc__ + 'docstring' + + In addition, the boltons version of update_wrapper supports + modifying the outer signature. By passing a list of + *injected* argument names, those arguments will be removed from + the outer wrapper's signature, allowing your decorator to provide + arguments that aren't passed in. + + Args: + + wrapper (function) : The callable to which the attributes of + *func* are to be copied. + func (function): The callable whose attributes are to be copied. + injected (list): An optional list of argument names which + should not appear in the new wrapper's signature. + expected (list): An optional list of argument names (or (name, + default) pairs) representing new arguments introduced by + the wrapper (the opposite of *injected*). See + :meth:`FunctionBuilder.add_arg()` for more details. + build_from (function): The callable from which the new wrapper + is built. Defaults to *func*, unless *wrapper* is partial object + built from *func*, in which case it defaults to *wrapper*. + Useful in some specific cases where *wrapper* and *func* have the + same arguments but differ on which are keyword-only and positional-only. + update_dict (bool): Whether to copy other, non-standard + attributes of *func* over to the wrapper. Defaults to True. + inject_to_varkw (bool): Ignore missing arguments when a + ``**kwargs``-type catch-all is present. Defaults to True. + hide_wrapped (bool): Remove reference to the wrapped function(s) + in the updated function. + + In opposition to the built-in :func:`functools.update_wrapper` bolton's + version returns a copy of the function and does not modifiy anything in place. + For more in-depth wrapping of functions, see the + :class:`FunctionBuilder` type, on which update_wrapper was built. + """ + if injected is None: + injected = [] + elif isinstance(injected, basestring): + injected = [injected] + else: + injected = list(injected) + + expected_items = _parse_wraps_expected(expected) + + if isinstance(func, (classmethod, staticmethod)): + raise TypeError('wraps does not support wrapping classmethods and' + ' staticmethods, change the order of wrapping to' + ' wrap the underlying function: %r' + % (getattr(func, '__func__', None),)) + + update_dict = kw.pop('update_dict', True) + inject_to_varkw = kw.pop('inject_to_varkw', True) + hide_wrapped = kw.pop('hide_wrapped', False) + if kw: + raise TypeError('unexpected kwargs: %r' % kw.keys()) + + if isinstance(wrapper, functools.partial) and func is wrapper.func: + build_from = build_from or wrapper + + fb = FunctionBuilder.from_func(build_from or func) + + for arg in injected: + try: + fb.remove_arg(arg) + except MissingArgument: + if inject_to_varkw and fb.varkw is not None: + continue # keyword arg will be caught by the varkw + raise + + for arg, default in expected_items: + fb.add_arg(arg, default) # may raise ExistingArgument + + if fb.is_async: + fb.body = 'return await _call(%s)' % fb.get_invocation_str() + else: + fb.body = 'return _call(%s)' % fb.get_invocation_str() + + execdict = dict(_call=wrapper, _func=func) + fully_wrapped = fb.get_func(execdict, with_dict=update_dict) + + if hide_wrapped and hasattr(fully_wrapped, '__wrapped__'): + del fully_wrapped.__dict__['__wrapped__'] + elif not hide_wrapped: + fully_wrapped.__wrapped__ = func # ref to the original function (#115) + + return fully_wrapped + + +def _parse_wraps_expected(expected): + # expected takes a pretty powerful argument, it's processed + # here. admittedly this would be less trouble if I relied on + # OrderedDict (there's an impl of that in the commit history if + # you look + if expected is None: + expected = [] + elif isinstance(expected, basestring): + expected = [(expected, NO_DEFAULT)] + + expected_items = [] + try: + expected_iter = iter(expected) + except TypeError as e: + raise ValueError('"expected" takes string name, sequence of string names,' + ' iterable of (name, default) pairs, or a mapping of ' + ' {name: default}, not %r (got: %r)' % (expected, e)) + for argname in expected_iter: + if isinstance(argname, basestring): + # dict keys and bare strings + try: + default = expected[argname] + except TypeError: + default = NO_DEFAULT + else: + # pairs + try: + argname, default = argname + except (TypeError, ValueError): + raise ValueError('"expected" takes string name, sequence of string names,' + ' iterable of (name, default) pairs, or a mapping of ' + ' {name: default}, not %r') + if not isinstance(argname, basestring): + raise ValueError('all "expected" argnames must be strings, not %r' % (argname,)) + + expected_items.append((argname, default)) + + return expected_items + + +class FunctionBuilder(object): + """The FunctionBuilder type provides an interface for programmatically + creating new functions, either based on existing functions or from + scratch. + + Values are passed in at construction or set as attributes on the + instance. For creating a new function based of an existing one, + see the :meth:`~FunctionBuilder.from_func` classmethod. At any + point, :meth:`~FunctionBuilder.get_func` can be called to get a + newly compiled function, based on the values configured. + + >>> fb = FunctionBuilder('return_five', doc='returns the integer 5', + ... body='return 5') + >>> f = fb.get_func() + >>> f() + 5 + >>> fb.varkw = 'kw' + >>> f_kw = fb.get_func() + >>> f_kw(ignored_arg='ignored_val') + 5 + + Note that function signatures themselves changed quite a bit in + Python 3, so several arguments are only applicable to + FunctionBuilder in Python 3. Except for *name*, all arguments to + the constructor are keyword arguments. + + Args: + name (str): Name of the function. + doc (str): `Docstring`_ for the function, defaults to empty. + module (str): Name of the module from which this function was + imported. Defaults to None. + body (str): String version of the code representing the body + of the function. Defaults to ``'pass'``, which will result + in a function which does nothing and returns ``None``. + args (list): List of argument names, defaults to empty list, + denoting no arguments. + varargs (str): Name of the catch-all variable for positional + arguments. E.g., "args" if the resultant function is to have + ``*args`` in the signature. Defaults to None. + varkw (str): Name of the catch-all variable for keyword + arguments. E.g., "kwargs" if the resultant function is to have + ``**kwargs`` in the signature. Defaults to None. + defaults (tuple): A tuple containing default argument values for + those arguments that have defaults. + kwonlyargs (list): Argument names which are only valid as + keyword arguments. **Python 3 only.** + kwonlydefaults (dict): A mapping, same as normal *defaults*, + but only for the *kwonlyargs*. **Python 3 only.** + annotations (dict): Mapping of type hints and so + forth. **Python 3 only.** + filename (str): The filename that will appear in + tracebacks. Defaults to "boltons.funcutils.FunctionBuilder". + indent (int): Number of spaces with which to indent the + function *body*. Values less than 1 will result in an error. + dict (dict): Any other attributes which should be added to the + functions compiled with this FunctionBuilder. + + All of these arguments are also made available as attributes which + can be mutated as necessary. + + .. _Docstring: https://en.wikipedia.org/wiki/Docstring#Python + + """ + + if _IS_PY2: + _argspec_defaults = {'args': list, + 'varargs': lambda: None, + 'varkw': lambda: None, + 'defaults': lambda: None} + + @classmethod + def _argspec_to_dict(cls, f): + args, varargs, varkw, defaults = inspect.getargspec(f) + return {'args': args, + 'varargs': varargs, + 'varkw': varkw, + 'defaults': defaults} + + else: + _argspec_defaults = {'args': list, + 'varargs': lambda: None, + 'varkw': lambda: None, + 'defaults': lambda: None, + 'kwonlyargs': list, + 'kwonlydefaults': dict, + 'annotations': dict} + + @classmethod + def _argspec_to_dict(cls, f): + argspec = inspect.getfullargspec(f) + return dict((attr, getattr(argspec, attr)) + for attr in cls._argspec_defaults) + + _defaults = {'doc': str, + 'dict': dict, + 'is_async': lambda: False, + 'module': lambda: None, + 'body': lambda: 'pass', + 'indent': lambda: 4, + "annotations": dict, + 'filename': lambda: 'boltons.funcutils.FunctionBuilder'} + + _defaults.update(_argspec_defaults) + + _compile_count = itertools.count() + + def __init__(self, name, **kw): + self.name = name + for a, default_factory in self._defaults.items(): + val = kw.pop(a, None) + if val is None: + val = default_factory() + setattr(self, a, val) + + if kw: + raise TypeError('unexpected kwargs: %r' % kw.keys()) + return + + # def get_argspec(self): # TODO + + if _IS_PY2: + def get_sig_str(self, with_annotations=True): + """Return function signature as a string. + + with_annotations is ignored on Python 2. On Python 3 signature + will omit annotations if it is set to False. + """ + return inspect_formatargspec(self.args, self.varargs, + self.varkw, []) + + def get_invocation_str(self): + return inspect_formatargspec(self.args, self.varargs, + self.varkw, [])[1:-1] + else: + def get_sig_str(self, with_annotations=True): + """Return function signature as a string. + + with_annotations is ignored on Python 2. On Python 3 signature + will omit annotations if it is set to False. + """ + if with_annotations: + annotations = self.annotations + else: + annotations = {} + + return inspect_formatargspec(self.args, + self.varargs, + self.varkw, + [], + self.kwonlyargs, + {}, + annotations) + + _KWONLY_MARKER = re.compile(r""" + \* # a star + \s* # followed by any amount of whitespace + , # followed by a comma + \s* # followed by any amount of whitespace + """, re.VERBOSE) + + def get_invocation_str(self): + kwonly_pairs = None + formatters = {} + if self.kwonlyargs: + kwonly_pairs = dict((arg, arg) + for arg in self.kwonlyargs) + formatters['formatvalue'] = lambda value: '=' + value + + sig = inspect_formatargspec(self.args, + self.varargs, + self.varkw, + [], + kwonly_pairs, + kwonly_pairs, + {}, + **formatters) + sig = self._KWONLY_MARKER.sub('', sig) + return sig[1:-1] + + @classmethod + def from_func(cls, func): + """Create a new FunctionBuilder instance based on an existing + function. The original function will not be stored or + modified. + """ + # TODO: copy_body? gonna need a good signature regex. + # TODO: might worry about __closure__? + if not callable(func): + raise TypeError('expected callable object, not %r' % (func,)) + + if isinstance(func, functools.partial): + if _IS_PY2: + raise ValueError('Cannot build FunctionBuilder instances from partials in python 2.') + kwargs = {'name': func.func.__name__, + 'doc': func.func.__doc__, + 'module': getattr(func.func, '__module__', None), # e.g., method_descriptor + 'annotations': getattr(func.func, "__annotations__", {}), + 'dict': getattr(func.func, '__dict__', {})} + else: + kwargs = {'name': func.__name__, + 'doc': func.__doc__, + 'module': getattr(func, '__module__', None), # e.g., method_descriptor + 'annotations': getattr(func, "__annotations__", {}), + 'dict': getattr(func, '__dict__', {})} + + kwargs.update(cls._argspec_to_dict(func)) + + if _inspect_iscoroutinefunction(func): + kwargs['is_async'] = True + + return cls(**kwargs) + + def get_func(self, execdict=None, add_source=True, with_dict=True): + """Compile and return a new function based on the current values of + the FunctionBuilder. + + Args: + execdict (dict): The dictionary representing the scope in + which the compilation should take place. Defaults to an empty + dict. + add_source (bool): Whether to add the source used to a + special ``__source__`` attribute on the resulting + function. Defaults to True. + with_dict (bool): Add any custom attributes, if + applicable. Defaults to True. + + To see an example of usage, see the implementation of + :func:`~boltons.funcutils.wraps`. + """ + execdict = execdict or {} + body = self.body or self._default_body + + tmpl = 'def {name}{sig_str}:' + tmpl += '\n{body}' + + if self.is_async: + tmpl = 'async ' + tmpl + + body = _indent(self.body, ' ' * self.indent) + + name = self.name.replace('<', '_').replace('>', '_') # lambdas + src = tmpl.format(name=name, sig_str=self.get_sig_str(with_annotations=False), + doc=self.doc, body=body) + self._compile(src, execdict) + func = execdict[name] + + func.__name__ = self.name + func.__doc__ = self.doc + func.__defaults__ = self.defaults + if not _IS_PY2: + func.__kwdefaults__ = self.kwonlydefaults + func.__annotations__ = self.annotations + + if with_dict: + func.__dict__.update(self.dict) + func.__module__ = self.module + # TODO: caller module fallback? + + if add_source: + func.__source__ = src + + return func + + def get_defaults_dict(self): + """Get a dictionary of function arguments with defaults and the + respective values. + """ + ret = dict(reversed(list(zip(reversed(self.args), + reversed(self.defaults or []))))) + kwonlydefaults = getattr(self, 'kwonlydefaults', None) + if kwonlydefaults: + ret.update(kwonlydefaults) + return ret + + def get_arg_names(self, only_required=False): + arg_names = tuple(self.args) + tuple(getattr(self, 'kwonlyargs', ())) + if only_required: + defaults_dict = self.get_defaults_dict() + arg_names = tuple([an for an in arg_names if an not in defaults_dict]) + return arg_names + + if _IS_PY2: + def add_arg(self, arg_name, default=NO_DEFAULT): + "Add an argument with optional *default* (defaults to ``funcutils.NO_DEFAULT``)." + if arg_name in self.args: + raise ExistingArgument('arg %r already in func %s arg list' % (arg_name, self.name)) + self.args.append(arg_name) + if default is not NO_DEFAULT: + self.defaults = (self.defaults or ()) + (default,) + return + else: + def add_arg(self, arg_name, default=NO_DEFAULT, kwonly=False): + """Add an argument with optional *default* (defaults to + ``funcutils.NO_DEFAULT``). Pass *kwonly=True* to add a + keyword-only argument + """ + if arg_name in self.args: + raise ExistingArgument('arg %r already in func %s arg list' % (arg_name, self.name)) + if arg_name in self.kwonlyargs: + raise ExistingArgument('arg %r already in func %s kwonly arg list' % (arg_name, self.name)) + if not kwonly: + self.args.append(arg_name) + if default is not NO_DEFAULT: + self.defaults = (self.defaults or ()) + (default,) + else: + self.kwonlyargs.append(arg_name) + if default is not NO_DEFAULT: + self.kwonlydefaults[arg_name] = default + return + + def remove_arg(self, arg_name): + """Remove an argument from this FunctionBuilder's argument list. The + resulting function will have one less argument per call to + this function. + + Args: + arg_name (str): The name of the argument to remove. + + Raises a :exc:`ValueError` if the argument is not present. + + """ + args = self.args + d_dict = self.get_defaults_dict() + try: + args.remove(arg_name) + except ValueError: + try: + self.kwonlyargs.remove(arg_name) + except (AttributeError, ValueError): + # py2, or py3 and missing from both + exc = MissingArgument('arg %r not found in %s argument list:' + ' %r' % (arg_name, self.name, args)) + exc.arg_name = arg_name + raise exc + else: + self.kwonlydefaults.pop(arg_name, None) + else: + d_dict.pop(arg_name, None) + self.defaults = tuple([d_dict[a] for a in args if a in d_dict]) + return + + def _compile(self, src, execdict): + + filename = ('<%s-%d>' + % (self.filename, next(self._compile_count),)) + try: + code = compile(src, filename, 'single') + exec(code, execdict) + except Exception: + raise + return execdict + + +class MissingArgument(ValueError): + pass + + +class ExistingArgument(ValueError): + pass + + +def _indent(text, margin, newline='\n', key=bool): + "based on boltons.strutils.indent" + indented_lines = [(margin + line if key(line) else line) + for line in text.splitlines()] + return newline.join(indented_lines) + + +try: + from functools import total_ordering # 2.7+ +except ImportError: + # python 2.6 + def total_ordering(cls): + """Class decorator that fills in missing comparators/ordering + methods. Backport of :func:`functools.total_ordering` to work + with Python 2.6. + + Code from http://code.activestate.com/recipes/576685/ + """ + convert = { + '__lt__': [ + ('__gt__', + lambda self, other: not (self < other or self == other)), + ('__le__', + lambda self, other: self < other or self == other), + ('__ge__', + lambda self, other: not self < other)], + '__le__': [ + ('__ge__', + lambda self, other: not self <= other or self == other), + ('__lt__', + lambda self, other: self <= other and not self == other), + ('__gt__', + lambda self, other: not self <= other)], + '__gt__': [ + ('__lt__', + lambda self, other: not (self > other or self == other)), + ('__ge__', + lambda self, other: self > other or self == other), + ('__le__', + lambda self, other: not self > other)], + '__ge__': [ + ('__le__', + lambda self, other: (not self >= other) or self == other), + ('__gt__', + lambda self, other: self >= other and not self == other), + ('__lt__', + lambda self, other: not self >= other)] + } + roots = set(dir(cls)) & set(convert) + if not roots: + raise ValueError('must define at least one ordering operation:' + ' < > <= >=') + root = max(roots) # prefer __lt__ to __le__ to __gt__ to __ge__ + for opname, opfunc in convert[root]: + if opname not in roots: + opfunc.__name__ = opname + opfunc.__doc__ = getattr(int, opname).__doc__ + setattr(cls, opname, opfunc) + return cls + +# end funcutils.py