Skip to content

Commit

Permalink
Add support for reverse operator methods to _.each to allow expressio…
Browse files Browse the repository at this point in the history
…ns like `3 / _.each` to work.
  • Loading branch information
dwt committed Mar 5, 2021
1 parent 833ea0f commit dc6352f
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 7 deletions.
2 changes: 2 additions & 0 deletions doc/Changelog.md
Expand Up @@ -8,6 +8,8 @@

- Fixed bug that `_._args` behaved differently than documented. The documentation stated that `_(lambda x, y=3: x).curry(_._args)(1, 2)._ == (1, 2)` but it did instead return `tuple(1)`

- Add `__rmul__` and friends support on `_._each`to allow expressions like `4 % _._each` to work.

## Version 2.0

### Breaking changes
Expand Down
6 changes: 5 additions & 1 deletion fluent_test.py
Expand Up @@ -202,7 +202,6 @@ def test_curry_raises_if_handed_too_many_arguments(self):
expect(curried(1,2,3)) == 3
expect(lambda: curried(1,2,3,4)).to_raise(TypeError, r'<lambda>\(\) takes 1 positional argument but 2 were given')


def test_curry_raises_if_handed_too_little_arguments(self):
curried = _(lambda x, y: x+y).curry(x=3)._ # this should now be a functino that takes _one_ argument
expect(lambda: curried()).to_raise(TypeError, r"<lambda>\(\) missing 1 required positional argument: 'y'")
Expand Down Expand Up @@ -560,6 +559,11 @@ def test_should_auto_terminate_chains_when_using_operators(self):
data = dict(foo=5)
expect(operation(data)) == 2

def test_should_auto_terminate_chains_when_using_reverse_operators(self):
operation = 6 / _.each['foo']
data = dict(foo=3)
expect(operation(data)) == 2

def test_should_have_sensible_repr_and_str_output(self):
expect(repr(_.each)) == "fluentpy.wrap(each)"
expect(repr(_.each['foo'])) == "fluentpy.wrap(each['foo'])"
Expand Down
27 changes: 21 additions & 6 deletions fluentpy/wrapper.py
Expand Up @@ -752,15 +752,30 @@ class TextWrapper(IterableWrapper):
sub = wrapped_forward(re.sub, self_index=2)
subn = wrapped_forward(re.subn, self_index=2)

def _make_operator(name):
__op__ = getattr(operator, name)
def _make_operator(operator_name):
__op__ = getattr(operator, operator_name)
@functools.wraps(__op__)
def wrapper(self, *others):
def operation(placeholder):
return __op__(placeholder, *others)
return self._EachWrapper__operation.compose(operation)._
return self._EachWrapper__operation.compose(operation)._ # auto unwraps
return wrapper

reverse_operator_names = [
'__radd__', '__rsub__', '__rmul__', '__rmatmul__', '__rtruediv__',
'__rfloordiv__', '__rmod__', '__rdivmod__', '__rpow__', '__rlshift__',
'__rrshift__', '__rand__', '__rxor__', '__ror_',
]

def _make_reverse_operator(operator_name):
def wrapper(self, other):
def operation(placeholder):
return getattr(placeholder, operator_name)(other)
return self._EachWrapper__operation.compose(operation)._ # auto unwraps
wrapper.__name__ = operator_name
return wrapper


# REFACT consider to inherit from Callable to simplify methods. On the other hand, I want as few methods on this as possible, perhaps even inheriting from Wrapper is a bad idea already.
@protected
class EachWrapper(object):
Expand Down Expand Up @@ -790,9 +805,9 @@ def __repr__(self):

# REFACT is there a way to get a reliable list of these operations from the stdlib somewhere?
# These are currently scraped from the documentation
# for name in ['__radd__', '__rsub__', '__rmul__', '__rmatmul__', '__rtruediv__', '__rfloordiv__', '__rmod__', '__rdivmod__', '__rpow__', '__rlshift__', '__rrshift__', '__rand__', '__rxor__', '__ror_']:
# locals()[name] = _make_operator(name)
# del name
for name in reverse_operator_names:
locals()[name] = _make_reverse_operator(name)
del name

# there is no operator form for x in iterator, such an api is only the wrong way around on iterator which inverts the reading direction
def in_(self, haystack):
Expand Down

0 comments on commit dc6352f

Please sign in to comment.