Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,55 @@ optional arguments after:
>>>
```

### Organizing using class

You can also organize pipes using classes as shown below:

>>> class Factor:
... n: int = 10
Comment thread
AdrianCert marked this conversation as resolved.
Outdated
...
... def __init__(self, n: int):
... self.n = n
...
... @Pipe
... def mul(self, iterable):
... return (x * self.n for x in iterable)
...
>>> fact = Factor(10)
>>> list([1, 2, 3] | fact.mul)
[10, 20, 30]

Supported on classmethod

>>> class Factor:
... n: int = 10
...
... def __init__(self, n: int):
... self.n = n
Comment thread
AdrianCert marked this conversation as resolved.
Outdated
...
... @Pipe
... @classmethod
... def mul(cls, iterable):
... return (x * cls.n for x in iterable)
...
>>> list([1, 2, 3] | Factor.mul)
[10, 20, 30]

Supported on classmethod like functions
Comment thread
AdrianCert marked this conversation as resolved.
Outdated

>>> class Factor:
... n: int = 10
...
... def __init__(self, n: int):
... self.n = n
...
Comment thread
AdrianCert marked this conversation as resolved.
Outdated
... @Pipe
... def mul(cls, iterable):
... return (x * cls.n for x in iterable)
...
>>> list([1, 2, 3] | Factor.mul)
[10, 20, 30]


## One-off pipes

Expand Down
58 changes: 51 additions & 7 deletions pipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,28 @@
import functools
import itertools
import socket
import inspect
import sys
from contextlib import closing
from collections import deque
import builtins


def _is_a_classmethod(func):
Comment thread
JulienPalard marked this conversation as resolved.
Outdated
"""
Check if a given function is a class method.

Args:
func (function): The function to check.

Returns:
bool: True if the function is a class method, False otherwise.
"""
signature = inspect.signature(func)
parameters = list(signature.parameters.values())
return len(parameters) > 0 and parameters[0].name == "cls"


class Pipe:
"""
Represent a Pipeable Element :
Expand All @@ -33,21 +49,49 @@ class Pipe:
"""

def __init__(self, function, *args, **kwargs):
Comment thread
JulienPalard marked this conversation as resolved.
self.function = lambda iterable, *args2, **kwargs2: function(
iterable, *args, *args2, **kwargs, **kwargs2
)
self.args = args
self.kwargs = kwargs
self.function = function
self.instance = None
Comment thread
AdrianCert marked this conversation as resolved.
Outdated
functools.update_wrapper(self, function)

def __ror__(self, other):
return self.function(other)
bound_args = [] if self.instance is None else [self.instance]
return self.function(*bound_args, other, *self.args, **self.kwargs)

def __call__(self, *args, **kwargs):
return Pipe(
lambda iterable, *args2, **kwargs2: self.function(
iterable, *args, *args2, **kwargs, **kwargs2
)
self.function,
*self.args,
*args,
**self.kwargs,
**kwargs,
)

def __repr__(self) -> str:
return "piped::<%s>(*%s, **%s)" % (
self.function.__name__,
self.args,
self.kwargs,
)

def __get__(self, instance, owner):
Comment thread
JulienPalard marked this conversation as resolved.
Outdated
if instance is None:
if owner is None: # pragma: no cover
return self
if isinstance(self.function, classmethod):
self.instance = owner
self.function = self.function.__func__.__get__(None, owner)
return self
if _is_a_classmethod(self.function):
Comment thread
JulienPalard marked this conversation as resolved.
Outdated
# function is like a classmethod, but not a classmethod
# only the first argument is the class, name it cls
self.instance = owner
return self # pragma: no cover
return self
self.instance = instance
return self


@Pipe
def take(iterable, qte):
Expand Down
28 changes: 28 additions & 0 deletions tests/test_pipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,31 @@ def test_enumerate():
data = [4, "abc", {"key": "value"}]
expected = [(5, 4), (6, "abc"), (7, {"key": "value"})]
assert list(data | pipe.enumerate(start=5)) == expected


def test_class_support():
class Factory:
n = 10

@pipe.Pipe
def mul(self, iterable):
return (x * self.n for x in iterable)

assert list([1, 2, 3] | Factory().mul) == [10, 20, 30]


def test_pipe_repr():
@pipe.Pipe
def sample_pipe(iterable):
return (x * 2 for x in iterable)

assert repr(sample_pipe) == "piped::<sample_pipe>(*(), **{})"

@pipe.Pipe
def sample_pipe_with_args(iterable, factor):
return (x * factor for x in iterable)

pipe_instance = sample_pipe_with_args(3)
real_repr = repr(pipe_instance)
assert "piped::<sample_pipe_with_args>(" in real_repr
assert "3" in real_repr