You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ORPA-pyOpenRPA/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pydash/chaining.py

321 lines
9.0 KiB

# -*- coding: utf-8 -*-
"""
Method chaining interface.
.. versionadded:: 1.0.0
"""
from __future__ import absolute_import, print_function
import pydash as pyd
from .helpers import NoValue
__all__ = (
"chain",
"tap",
"thru",
)
class Chain(object):
"""Enables chaining of :attr:`module` functions."""
#: Object that contains attribute references to available methods.
module = pyd
def __init__(self, value=NoValue):
self._value = value
def value(self):
"""
Return current value of the chain operations.
Returns:
mixed: Current value of chain operations.
"""
return self(self._value)
def to_string(self):
"""
Return current value as string.
Returns:
str: Current value of chain operations casted to ``str``.
"""
return self.module.to_string(self.value())
def commit(self):
"""
Executes the chained sequence and returns the wrapped result.
Returns:
Chain: New instance of :class:`Chain` with resolved value from
previous :class:`Class`.
"""
return Chain(self.value())
def plant(self, value):
"""
Return a clone of the chained sequence planting `value` as the wrapped value.
Args:
value (mixed): Value to plant as the initial chain value.
"""
# pylint: disable=no-member,maybe-no-member
wrapper = self._value
wrappers = []
if hasattr(wrapper, "_value"):
wrappers = [wrapper]
while isinstance(wrapper._value, ChainWrapper):
wrapper = wrapper._value
wrappers.insert(0, wrapper)
clone = Chain(value)
for wrap in wrappers:
clone = ChainWrapper(clone._value, wrap.method)(*wrap.args, **wrap.kwargs)
return clone
@classmethod
def get_method(cls, name):
"""
Return valid :attr:`module` method.
Args:
name (str): Name of pydash method to get.
Returns:
function: :attr:`module` callable.
Raises:
InvalidMethod: Raised if `name` is not a valid :attr:`module` method.
"""
# Python 3.5 issue with pytest doctest call where inspect module tries
# to unwrap this class. If we don't return here, we get an
# InvalidMethod exception.
if name in ("__wrapped__",): # pragma: no cover
return cls
method = getattr(cls.module, 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(cls.module, name + "_", None)
if not callable(method):
raise cls.module.InvalidMethod(("Invalid pydash method: {0}".format(name)))
return method
def __getattr__(self, attr):
"""
Proxy attribute access to :attr:`module`.
Args:
attr (str): Name of :attr:`module` function to chain.
Returns:
ChainWrapper: New instance of :class:`ChainWrapper` with value passed on.
Raises:
InvalidMethod: Raised if `attr` is not a valid function.
"""
return ChainWrapper(self._value, self.get_method(attr))
def __call__(self, value):
"""
Return result of passing `value` through chained methods.
Args:
value (mixed): Initial value to pass through chained methods.
Returns:
mixed: Result of method chain evaluation of `value`.
"""
if isinstance(self._value, ChainWrapper):
# pylint: disable=maybe-no-member
value = self._value.unwrap(value)
return value
class ChainWrapper(object):
"""Wrap :class:`Chain` method call within a :class:`ChainWrapper` context."""
def __init__(self, value, method):
self._value = value
self.method = method
self.args = ()
self.kwargs = {}
def _generate(self):
"""Generate a copy of this instance."""
# pylint: disable=attribute-defined-outside-init
new = self.__class__.__new__(self.__class__)
new.__dict__ = self.__dict__.copy()
return new
def unwrap(self, value=NoValue):
"""
Execute :meth:`method` with :attr:`_value`, :attr:`args`, and :attr:`kwargs`.
If :attr:`_value` is an instance of :class:`ChainWrapper`, then unwrap it before calling
:attr:`method`.
"""
# Generate a copy of ourself so that we don't modify the chain wrapper
# _value directly. This way if we are late passing a value, we don't
# "freeze" the chain wrapper value when a value is first passed.
# Otherwise, we'd locked the chain wrapper value permanently and not be
# able to reuse it.
wrapper = self._generate()
if isinstance(wrapper._value, ChainWrapper):
# pylint: disable=no-member,maybe-no-member
wrapper._value = wrapper._value.unwrap(value)
elif not isinstance(value, ChainWrapper) and value is not NoValue:
# Override wrapper's initial value.
wrapper._value = value
if wrapper._value is not NoValue:
value = wrapper._value
return wrapper.method(value, *wrapper.args, **wrapper.kwargs)
def __call__(self, *args, **kwargs):
"""
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.kwargs = kwargs
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=NoValue):
"""Return a new instance of :class:`Chain` with `value` as the seed."""
return Chain(value)
def chain(value=NoValue):
"""
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`.
Example:
>>> chain([1, 2, 3, 4]).map(lambda x: x * 2).sum().value()
20
>>> chain().map(lambda x: x * 2).sum()([1, 2, 3, 4])
20
>>> summer = chain([1, 2, 3, 4]).sum()
>>> new_summer = summer.plant([1, 2])
>>> new_summer.value()
3
>>> summer.value()
10
>>> def echo(item): print(item)
>>> summer = chain([1, 2, 3, 4]).for_each(echo).sum()
>>> committed = summer.commit()
1
2
3
4
>>> committed.value()
10
>>> summer.value()
1
2
3
4
10
.. versionadded:: 1.0.0
.. versionchanged:: 2.0.0
Made chaining lazy.
.. versionchanged:: 3.0.0
- Added support for late passing of `value`.
- Added :meth:`Chain.plant` for replacing initial chain value.
- Added :meth:`Chain.commit` for returning a new :class:`Chain` instance initialized with
the results from calling :meth:`Chain.value`.
"""
return Chain(value)
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 (callable): Function called on `value`.
Returns:
mixed: `value` after `interceptor` call.
Example:
>>> data = []
>>> def log(value): data.append(value)
>>> chain([1, 2, 3, 4]).map(lambda x: x * 2).tap(log).value()
[2, 4, 6, 8]
>>> data
[[2, 4, 6, 8]]
.. versionadded:: 1.0.0
"""
interceptor(value)
return value
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 (callable): Function called with `value`.
Returns:
mixed: Results of ``interceptor(value)``.
Example:
>>> chain([1, 2, 3, 4]).thru(lambda x: x * 2).value()
[1, 2, 3, 4, 1, 2, 3, 4]
.. versionadded:: 2.0.0
"""
return interceptor(value)