Source code for wex.composed

"""
Wextracto uses `Function composition <http://en.wikipedia.org/wiki/Function_composition_%28computer_science%29>`_
as an easy way to build new functions from existing ones:

.. code-block:: pycon

    >>> from wex.composed import compose
    >>> def add1(x):
    ...     return x + 1
    ...
    >>> def mult2(x):
    ...     return x * 2
    ...
    >>> f = compose(add1, mult2)
    >>> f(2)
    6

Wextracto uses the pipe operator, ``|``, as a shorthand for function composition.

This shorthand can be a powerful technique for reducing boilerplate code when
used in combination with :func:`.named` extractors:

.. code-block:: python

    from wex.etree import css, text
    from wex.extractor import named

    attrs = named(title = css('h1') | text
                  description = css('#description') | text)

"""

from itertools import chain
from functools import WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES, partial as functools_partial
from six.moves import map as six_map


[docs]def compose(*functions): """ Create a :class:`.ComposedCallable` from zero more functions. """ return ComposedCallable(*functions)
[docs]def composable(func): """ Decorates a callable to support function composition using ``|``. For example: .. code-block:: python @Composable.decorate def add1(x): return x + 1 def mult2(x): return x * 2 composed = add1 | mult2 """ return Composable.decorate(func)
class Composable(object): @classmethod def decorate(cls, func, **kw): name = getattr(func, '__name__', str(func)) clsdict = dict( __call__=staticmethod(func), __doc__=getattr(func, '__doc__', None), __name__=getattr(func, '__name__', None), __module__=getattr(func, '__module__', None), ) clsdict.update(kw) return type(name, (cls,), clsdict)() @classmethod def __getattr__(cls, name): return getattr(cls.__call__, name) @classmethod def __compose__(cls): return (cls.__call__,) def __or__(self, rhs): assert hasattr(rhs, '__call__') return compose(self, rhs) def __ror__(self, lhs): assert hasattr(lhs, '__call__') return compose(lhs, self) def __call__(self, arg): raise NotImplementedError def flatten_composed_callables(functions): iterable = (getattr(f, 'functions', (f,)) for f in functions) return tuple(chain.from_iterable(iterable))
[docs]class ComposedCallable(Composable): """ A callable, taking one argument, composed from other callables. .. code-block:: python def mult2(x): return x * 2 def add1(x): return x + 1 composed = ComposedCallable(add1, mult2) for x in (1, 2, 3): assert composed(x) == mult2(add1(x)) ComposedCallable objects are :func:`composable <wex.composed.composable>`. It can be composed of other ComposedCallable objects. """ def __init__(self, *functions): self.functions = flatten_composed_callables(functions) def __call__(self, arg, **kw): res = arg for func in self.functions: res = func(res, **kw) return res def __compose__(self): return self.functions def __repr__(self): return '<%s.%s%r>' % (self.__class__.__module__, self.__class__.__name__, self.functions)
def wraps(wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES): defaults = { '__annotations__': {} } def decorator(wrapper): for attr in assigned: class_ = getattr(wrapped, '__class__', None) try: value = getattr(wrapped, attr) except AttributeError: try: value = getattr(class_, attr) except AttributeError: value = defaults[attr] setattr(wrapper, attr, value) for attr in updated: value = getattr(wrapped, attr, {}) getattr(wrapper, attr).update(value) return wrapper return decorator def partial(func, *args, **kwargs): return composable(functools_partial(func, *args, **kwargs)) def map(func): return partial(six_map, func)