Skip to content

Commit dcae347

Browse files
committed
support for working with class
1 parent fe034cf commit dcae347

3 files changed

Lines changed: 128 additions & 7 deletions

File tree

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,55 @@ optional arguments after:
533533
>>>
534534
```
535535

536+
### Organizing using class
537+
538+
You can also organize pipes using classes as shown below:
539+
540+
>>> class Factor:
541+
... n: int = 10
542+
...
543+
... def __init__(self, n: int):
544+
... self.n = n
545+
...
546+
... @Pipe
547+
... def mul(self, iterable):
548+
... return (x * self.n for x in iterable)
549+
...
550+
>>> fact = Factor(10)
551+
>>> list([1, 2, 3] | fact.mul)
552+
[10, 20, 30]
553+
554+
Supported on classmethod
555+
556+
>>> class Factor:
557+
... n: int = 10
558+
...
559+
... def __init__(self, n: int):
560+
... self.n = n
561+
...
562+
... @Pipe
563+
... @classmethod
564+
... def mul(cls, iterable):
565+
... return (x * cls.n for x in iterable)
566+
...
567+
>>> list([1, 2, 3] | Factor.mul)
568+
[10, 20, 30]
569+
570+
Supported on classmethod like functions
571+
572+
>>> class Factor:
573+
... n: int = 10
574+
...
575+
... def __init__(self, n: int):
576+
... self.n = n
577+
...
578+
... @Pipe
579+
... def mul(cls, iterable):
580+
... return (x * cls.n for x in iterable)
581+
...
582+
>>> list([1, 2, 3] | Factor.mul)
583+
[10, 20, 30]
584+
536585

537586
## One-off pipes
538587

pipe.py

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,28 @@
88
import functools
99
import itertools
1010
import socket
11+
import inspect
1112
import sys
1213
from contextlib import closing
1314
from collections import deque
1415
import builtins
1516

1617

18+
def _is_a_classmethod(func):
19+
"""
20+
Check if a given function is a class method.
21+
22+
Args:
23+
func (function): The function to check.
24+
25+
Returns:
26+
bool: True if the function is a class method, False otherwise.
27+
"""
28+
signature = inspect.signature(func)
29+
parameters = list(signature.parameters.values())
30+
return len(parameters) > 0 and parameters[0].name == "cls"
31+
32+
1733
class Pipe:
1834
"""
1935
Represent a Pipeable Element :
@@ -33,21 +49,49 @@ class Pipe:
3349
"""
3450

3551
def __init__(self, function, *args, **kwargs):
36-
self.function = lambda iterable, *args2, **kwargs2: function(
37-
iterable, *args, *args2, **kwargs, **kwargs2
38-
)
52+
self.args = args
53+
self.kwargs = kwargs
54+
self.function = function
55+
self.instance = None
3956
functools.update_wrapper(self, function)
4057

4158
def __ror__(self, other):
42-
return self.function(other)
59+
bound_args = [] if self.instance is None else [self.instance]
60+
return self.function(*bound_args, other, *self.args, **self.kwargs)
4361

4462
def __call__(self, *args, **kwargs):
4563
return Pipe(
46-
lambda iterable, *args2, **kwargs2: self.function(
47-
iterable, *args, *args2, **kwargs, **kwargs2
48-
)
64+
self.function,
65+
*self.args,
66+
*args,
67+
**self.kwargs,
68+
**kwargs,
4969
)
5070

71+
def __repr__(self) -> str:
72+
return "piped::<%s>(*%s, **%s)" % (
73+
self.function.__name__,
74+
self.args,
75+
self.kwargs,
76+
)
77+
78+
def __get__(self, instance, owner):
79+
if instance is None:
80+
if owner is None: # pragma: no cover
81+
return self
82+
if isinstance(self.function, classmethod):
83+
self.instance = owner
84+
self.function = self.function.__func__.__get__(None, owner)
85+
return self
86+
if _is_a_classmethod(self.function):
87+
# function is like a classmethod, but not a classmethod
88+
# only the first argument is the class, name it cls
89+
self.instance = owner
90+
return self # pragma: no cover
91+
return self
92+
self.instance = instance
93+
return self
94+
5195

5296
@Pipe
5397
def take(iterable, qte):

tests/test_pipe.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,31 @@ def test_enumerate():
3939
data = [4, "abc", {"key": "value"}]
4040
expected = [(5, 4), (6, "abc"), (7, {"key": "value"})]
4141
assert list(data | pipe.enumerate(start=5)) == expected
42+
43+
44+
def test_class_support():
45+
class Factory:
46+
n = 10
47+
48+
@pipe.Pipe
49+
def mul(self, iterable):
50+
return (x * self.n for x in iterable)
51+
52+
assert list([1, 2, 3] | Factory().mul) == [10, 20, 30]
53+
54+
55+
def test_pipe_repr():
56+
@pipe.Pipe
57+
def sample_pipe(iterable):
58+
return (x * 2 for x in iterable)
59+
60+
assert repr(sample_pipe) == "piped::<sample_pipe>(*(), **{})"
61+
62+
@pipe.Pipe
63+
def sample_pipe_with_args(iterable, factor):
64+
return (x * factor for x in iterable)
65+
66+
pipe_instance = sample_pipe_with_args(3)
67+
real_repr = repr(pipe_instance)
68+
assert "piped::<sample_pipe_with_args>(" in real_repr
69+
assert "3" in real_repr

0 commit comments

Comments
 (0)