Skip to content

Commit ccb17c3

Browse files
committed
support for chained pipes using Ellipsis
1 parent 7f33233 commit ccb17c3

3 files changed

Lines changed: 113 additions & 1 deletion

File tree

README.md

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,46 @@ Don't forget they can be aliased:
482482
>>>
483483
```
484484

485+
## Chained pipes
486+
487+
The Pipe class supports chaining multiple transformations using the `|` operator.
488+
489+
### How It Works
490+
491+
* Use `|` between Pipe instances to create a pipeline, and use the Ellipsis (`...`) operator as the first element instead of an iterable.
492+
* Apply the pipeline to data using `data | pipeline`.
493+
494+
### Examples of chained pipes Usage
495+
496+
```python
497+
@Pipe
498+
def double(iterable):
499+
return (x * 2 for x in iterable)
500+
501+
@Pipe
502+
def square(iterable):
503+
return (x ** 2 for x in iterable)
504+
505+
@Pipe
506+
def increment(iterable):
507+
return (x + 1 for x in iterable)
508+
509+
pipeline = double | square | increment # Chain operations
510+
511+
result = [1, 2, 3] | pipeline # Apply to data
512+
print(list(result)) # Output: [5, 17, 37]
513+
```
514+
515+
Other example:
516+
517+
```py
518+
>>> import pipe
519+
>>> pipeline = ... | pipe.skip(2) | pipe.take(3)
520+
>>> list(range(10) | pipeline)
521+
[2, 3, 4]
522+
>>>
523+
```
524+
485525
## Constructing your own
486526

487527
You can construct your pipes using the `Pipe` class like:
@@ -533,7 +573,7 @@ optional arguments after:
533573
>>>
534574
```
535575

536-
### Organizing pipes more effectively using classes
576+
## Organizing pipes more effectively using classes
537577

538578
The `@Pipe` decorator isn't just for functions-it also works with classes. You can use it with instance methods, class methods, and static methods to better structure your code while keeping it pipeable.
539579

pipe.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ def __ror__(self, other):
6565
provided arguments and keyword arguments.
6666
6767
"""
68+
if other is Ellipsis or isinstance(other, Pipe):
69+
return ChainedPipes.chain_with(other, self)
6870
return self.function(other, *self.args, **self.kwargs)
6971

7072
def __call__(self, *args, **kwargs):
@@ -91,6 +93,50 @@ def __get__(self, instance, owner=None):
9193
)
9294

9395

96+
class ChainedPipes(Pipe):
97+
"""
98+
Chain of pipes for sequential application.
99+
100+
This class enables chaining multiple Pipe objects and applying them
101+
sequentially using the `|` operator. It provides a way to define the
102+
pipes sequence and use it later.
103+
104+
Parameters
105+
----------
106+
*pipes : Pipe
107+
A variable number of Pipe objects to be chained together.
108+
109+
Examples
110+
--------
111+
Define a sequence of pipes and use it:
112+
113+
>>> from pipe import take, skip
114+
>>> pipeline = ... | skip(2) take(3))
115+
>>> list([1, 2, 3, 4, 5, 6] | pipeline)
116+
[3, 4, 5]
117+
118+
"""
119+
120+
def __init__(self, *pipes):
121+
self.pipes = pipes
122+
123+
def __ror__(self, other):
124+
result = other
125+
for pipe in self.pipes:
126+
result = result | pipe
127+
return result
128+
129+
@classmethod
130+
def chain_with(cls, other, ref):
131+
if isinstance(other, ChainedPipes):
132+
return ChainedPipes(*other.pipes, ref)
133+
elif other is Ellipsis:
134+
return ChainedPipes(ref)
135+
136+
def __repr__(self):
137+
return "...\n| %s" % "\n| ".join([str(i) for i in self.pipes])
138+
139+
94140
@Pipe
95141
def take(iterable, qte):
96142
"""Yield qte of elements in the given iterable."""

tests/test_pipe.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,29 @@ def sample_pipe_with_args(iterable, factor):
105105
real_repr = repr(pipe_instance)
106106
assert "piped::<sample_pipe_with_args>(" in real_repr
107107
assert "3" in real_repr
108+
109+
110+
def test_chained_pipes():
111+
pipeline = ... | pipe.skip(2) | pipe.take(3)
112+
113+
assert list(range(10) | pipeline) == [2, 3, 4]
114+
115+
@pipe.Pipe
116+
def double(iterable):
117+
return (x * 2 for x in iterable)
118+
119+
extended_pipeline = pipeline | double
120+
assert list(range(10) | extended_pipeline) == [4, 6, 8]
121+
assert list(range(10) | pipeline | double) == [4, 6, 8]
122+
123+
124+
def test_chained_pipes_on_bad_constructor():
125+
assert pipe.ChainedPipes.chain_with(None, None) is None
126+
127+
128+
def test_chained_pipes_reps():
129+
pipeline = ... | pipe.skip(2) | pipe.take(3)
130+
131+
repr_pipeline = repr(pipeline)
132+
assert repr(pipe.skip(2)) in repr_pipeline
133+
assert repr(pipe.take(3)) in repr_pipeline

0 commit comments

Comments
 (0)