Source code for pydash.chaining

"""Method chaining interface.

.. versionadded:: 1.0.0
"""

from __future__ import absolute_import

import pydash as pyd


__all__ = (
    'chain',
    'tap',
    'thru',
)


class Chain(object):
    """Enables chaining of pydash functions."""

    def __init__(self, value):
        self._value = value

    def value(self):
        """Return current value of the chain operations.

        Returns:
            mixed: Current value of chain operations.

        See Also:
            - :meth:`value` (main definition)
            - :meth:`value_of` (alias)
        """
        if isinstance(self._value, ChainWrapper):
            # pylint: disable=maybe-no-member
            self._value = self._value.unwrap()
        return self._value

    value_of = value

    def to_string(self):
        """Return current value as string.

        Returns:
            str: Current value of chain operations casted to ``str``.
        """
        return pyd.to_string(self.value())

    @staticmethod
    def get_method(name):
        """Return valid pydash method.

        Args:
            name (str): Name of pydash method to get.

        Returns:
            function: pydash callable.

        Raises:
            InvalidMethod: Raised if `name` is not a valid :mod:`pydash`
                method.
        """
        method = getattr(pyd, name, None)

        if not callable(method) and not name.endswith('_'):
            # Alias method names not ending in underscore to their underscore
            # counterpart. This allows chaining of functions like "map_()"
            # using "map()" instead.
            method = getattr(pyd, name + '_', None)

        if not callable(method):
            raise pyd.InvalidMethod('Invalid pydash method: {0}'.format(name))

        return method

    def __getattr__(self, attr):
        """Proxy attribute access to :mod:`pydash`.

        Args:
            attr (str): Name of :mod:`pydash` function to chain.

        Returns:
            ChainWrapper: New instance of :class:`ChainWrapper` with value
                passed on.

        Raises:
            InvalidMethod: Raised if `attr` is not a valid :mod:`pydash`
                function.
        """
        return ChainWrapper(self._value, self.get_method(attr))


class ChainWrapper(object):
    """Wrap :mod:`pydash` method call within a :class:`ChainWrapper` context.
    """
    def __init__(self, value, method):
        self._value = value
        self.method = method
        self.args = ()
        self.kargs = {}

    def unwrap(self):
        """Execute :meth:`method` with :attr:`_value`, :attr:`args`, and
        :attr:`kargs`. If :attr:`_value` is an instance of
        :class:`ChainWrapper`, then unwrap it before calling :attr:`method`.
        """
        if isinstance(self._value, ChainWrapper):
            self._value = self._value.unwrap()
        return self.method(self._value, *self.args, **self.kargs)

    def __call__(self, *args, **kargs):
        """Invoke the :attr:`method` with :attr:`value` as the first argument
        and return a new :class:`Chain` object with the return value.

        Returns:
            Chain: New instance of :class:`Chain` with the results of
                :attr:`method` passed in as value.
        """
        self.args = args
        self.kargs = kargs
        return Chain(self)


class _Dash(object):
    """Class that provides attribute access to valid :mod:`pydash` methods and
    callable access to :mod:`pydash` method chaining.
    """

    def __getattr__(self, attr):
        """Proxy to :meth:`Chain.get_method`."""
        return Chain.get_method(attr)

    def __call__(self, value):
        """Return a new instance of :class:`Chain` with `value` as the seed."""
        return Chain(value)


[docs]def chain(value): """Creates a :class:`Chain` object which wraps the given value to enable intuitive method chaining. Chaining is lazy and won't compute a final value until :meth:`Chain.value` is called. Args: value (mixed): Value to initialize chain operations with. Returns: :class:`Chain`: Instance of :class:`Chain` initialized with `value`. .. versionadded:: 1.0.0 .. versionchanged:: 2.0.0 Made chaining lazy. """ return Chain(value)
[docs]def tap(value, interceptor): """Invokes `interceptor` with the `value` as the first argument and then returns `value`. The purpose of this method is to "tap into" a method chain in order to perform operations on intermediate results within the chain. Args: value (mixed): Current value of chain operation. interceptor (function): Function called on `value`. Returns: mixed: `value` after `interceptor` call. .. versionadded:: 1.0.0 """ interceptor(value) return value
[docs]def thru(value, interceptor): """Returns the result of calling `interceptor` on `value`. The purpose of this method is to pass `value` through a function during a method chain. Args: value (mixed): Current value of chain operation. interceptor (function): Function called with `value`. Returns: mixed: Results of ``interceptor(value)``. .. versionadded:: 2.0.0 """ return interceptor(value)