diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/env/lib/python3.9/site-packages/boltons/funcutils.py	Mon Mar 22 18:12:50 2021 +0000
@@ -0,0 +1,1072 @@
+# -*- 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, **kw):
+    """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)
+
+    """
+    _repr = kw.pop('repr', repr)
+    if kw:
+        raise TypeError('unexpected keyword args: %r' % ', '.join(kw.keys()))
+    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=%s' % (k, _repr(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