Skip to content

Commit e78c9e4

Browse files
authored
support for working with class (#106)
With this commit I also include usage of the ruff as linter and formatter
1 parent fe034cf commit e78c9e4

File tree

6 files changed

+277
-96
lines changed

6 files changed

+277
-96
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ pip-log.txt
2020
.mypy_cache/
2121
.venv/
2222
.envrc
23+
.build

README.md

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

536+
### Organizing pipes more effectively using classes
537+
538+
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.
539+
540+
1. Using an Instance Method
541+
542+
```python
543+
>>> class Factor:
544+
... def __init__(self, n: int):
545+
... self.n = n
546+
... @Pipe
547+
... def mul(self, iterable):
548+
... return (x * self.n for x in iterable)
549+
>>> fact = Factor(10)
550+
>>> list([1, 2, 3] | fact.mul)
551+
[10, 20, 30]
552+
>>>
553+
```
554+
555+
2. Using a Class Method
556+
557+
```python
558+
>>> class Factor:
559+
... n: int = 10
560+
... @Pipe
561+
... @classmethod
562+
... def mul(cls, iterable):
563+
... return (x * cls.n for x in iterable)
564+
>>> list([1, 2, 3] | Factor.mul)
565+
[10, 20, 30]
566+
>>>
567+
```
568+
569+
3. Using a Static Method
570+
571+
```python
572+
>>> class Factor:
573+
... @Pipe
574+
... @staticmethod
575+
... def mul(iterable):
576+
... return (x * 10 for x in iterable)
577+
>>> list([1, 2, 3] | Factor.mul)
578+
[10, 20, 30]
579+
>>>
580+
```
536581

537582
## One-off pipes
538583

pipe.py

Lines changed: 68 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,53 +5,95 @@
55
__credits__ = """Jérôme Schneider for teaching me the Python datamodel,
66
and all contributors."""
77

8+
import builtins
89
import functools
910
import itertools
1011
import socket
1112
import sys
12-
from contextlib import closing
1313
from collections import deque
14-
import builtins
14+
from contextlib import closing
1515

1616

1717
class Pipe:
1818
"""
19-
Represent a Pipeable Element :
20-
Described as :
21-
first = Pipe(lambda iterable: next(iter(iterable)))
22-
and used as :
23-
print [1, 2, 3] | first
24-
printing 1
25-
26-
Or represent a Pipeable Function :
27-
It's a function returning a Pipe
28-
Described as :
29-
select = Pipe(lambda iterable, pred: (pred(x) for x in iterable))
30-
and used as :
31-
print [1, 2, 3] | select(lambda x: x * 2)
32-
# 2, 4, 6
19+
Pipe class enable a sh like infix syntax.
20+
21+
This class allows you to create a pipeline of operations by chaining functions
22+
together using the `|` operator. It wraps a function and its arguments, enabling
23+
you to apply the function to an input in a clean and readable manner.
24+
25+
Examples
26+
--------
27+
Create a new Pipe operation:
28+
29+
>>> from pipe import Pipe
30+
>>> @Pipe
31+
... def double(iterable):
32+
... return (x * 2 for x in iterable)
33+
34+
Use the Pipe operation:
35+
36+
>>> result = [1, 2, 3] | double
37+
>>> list(result)
38+
[2, 4, 6]
39+
40+
Notes
41+
-----
42+
...
43+
3344
"""
3445

3546
def __init__(self, function, *args, **kwargs):
36-
self.function = lambda iterable, *args2, **kwargs2: function(
37-
iterable, *args, *args2, **kwargs, **kwargs2
38-
)
47+
self.args = args
48+
self.kwargs = kwargs
49+
self.function = function
3950
functools.update_wrapper(self, function)
4051

4152
def __ror__(self, other):
42-
return self.function(other)
53+
"""
54+
Implement the reverse pipe operator (`|`) for the object.
55+
56+
Parameters
57+
----------
58+
other : Any
59+
The left-hand operand of the `|` operator.
60+
61+
Returns
62+
-------
63+
Any
64+
The result of applying the stored function to `other` with the
65+
provided arguments and keyword arguments.
66+
67+
"""
68+
return self.function(other, *self.args, **self.kwargs)
4369

4470
def __call__(self, *args, **kwargs):
4571
return Pipe(
46-
lambda iterable, *args2, **kwargs2: self.function(
47-
iterable, *args, *args2, **kwargs, **kwargs2
48-
)
72+
self.function,
73+
*self.args,
74+
*args,
75+
**self.kwargs,
76+
**kwargs,
77+
)
78+
79+
def __repr__(self) -> str:
80+
return "piped::<%s>(*%s, **%s)" % (
81+
self.function.__name__,
82+
self.args,
83+
self.kwargs,
84+
)
85+
86+
def __get__(self, instance, owner=None):
87+
return Pipe(
88+
function=self.function.__get__(instance, owner),
89+
*self.args,
90+
**self.kwargs,
4991
)
5092

5193

5294
@Pipe
5395
def take(iterable, qte):
54-
"Yield qte of elements in the given iterable."
96+
"""Yield qte of elements in the given iterable."""
5597
if not qte:
5698
return
5799
for item in iterable:
@@ -63,13 +105,13 @@ def take(iterable, qte):
63105

64106
@Pipe
65107
def tail(iterable, qte):
66-
"Yield qte of elements in the given iterable."
108+
"""Yield qte of elements in the given iterable."""
67109
return deque(iterable, maxlen=qte)
68110

69111

70112
@Pipe
71113
def skip(iterable, qte):
72-
"Skip qte elements in the given iterable, then yield others."
114+
"""Skip qte elements in the given iterable, then yield others."""
73115
for item in iterable:
74116
if qte == 0:
75117
yield item
@@ -213,8 +255,3 @@ def batched(iterable, n):
213255
chain_with = Pipe(itertools.chain)
214256
islice = Pipe(itertools.islice)
215257
izip = Pipe(zip)
216-
217-
if __name__ == "__main__":
218-
import doctest
219-
220-
doctest.testfile("README.md")

pyproject.toml

Lines changed: 97 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,117 @@ build-backend = "setuptools.build_meta"
66
name = "pipe"
77
description = "Module enabling a sh like infix syntax (using pipes)"
88
readme = "README.md"
9-
license = {text = "MIT License"}
9+
license = { text = "MIT License" }
1010
dynamic = ["version"]
11-
authors = [
12-
{name = "Julien Palard", email = "julien@palard.fr"},
13-
]
11+
authors = [{ name = "Julien Palard", email = "julien@palard.fr" }]
1412
classifiers = [
1513
"Programming Language :: Python",
1614
"Programming Language :: Python :: 3",
17-
"Development Status :: 5 - Production/Stable",
18-
"Intended Audience :: Developers",
15+
"Programming Language :: Python :: 3.8",
16+
"Programming Language :: Python :: 3.10",
17+
"Programming Language :: Python :: 3.11",
18+
"Programming Language :: Python :: 3.12",
19+
"Programming Language :: Python :: 3.13",
1920
"License :: OSI Approved :: MIT License",
21+
"Intended Audience :: Developers",
22+
"Development Status :: 5 - Production/Stable",
2023
"Operating System :: OS Independent",
2124
"Topic :: Software Development",
2225
"Topic :: Software Development :: Libraries :: Python Modules",
2326
]
2427
requires-python = ">= 3.8"
28+
keywords = ["pipe"]
2529

2630
[project.urls]
2731
repository = "https://github.com/JulienPalard/Pipe"
2832

33+
[dependency-groups]
34+
ruff = ["ruff>=0.10.0"]
35+
coverage = ["coverage[toml]", "covdefaults"]
36+
dev = [{ include-group = "ruff" }, "pre-commit>=2.21.0", "tox>=4.23.2"]
37+
test = [{ include-group = "coverage" }, "mock", "pytest", "pytest-cov"]
38+
2939
[tool.setuptools]
30-
py-modules = [
31-
"pipe",
32-
]
40+
py-modules = ["pipe"]
3341
include-package-data = false
42+
dynamic.version.attr = "pipe.__version__"
3443

35-
[tool.setuptools.dynamic.version]
36-
attr = "pipe.__version__"
44+
[tool.ruff]
45+
lint.pydocstyle.convention = "numpy"
46+
lint.extend-select = ["C", "D", "FURB", "I", "N", "PL", "PTH", "SIM"]
47+
lint.extend-ignore = [
48+
"D100", # Missing docstring in public module
49+
"D101", # Missing docstring in public class
50+
"D102", #
51+
"D103", # Missing docstring in public function
52+
"D105",
53+
]
54+
55+
[tool.coverage]
56+
html.show_contexts = true
57+
html.skip_covered = false
58+
run.parallel = true
59+
run.omit = [
60+
"tests/*.py",
61+
# add others
62+
]
63+
run.plugins = [
64+
# all plugins
65+
"covdefaults",
66+
]
67+
68+
69+
[tool.tox]
70+
requires = ["tox>=4.23.2", "tox-uv>=1.13"]
71+
isolated_build = true
72+
env_list = ["ruff", "cov-report", "py38", "py39", "py311", "py312", "py313"]
73+
74+
[tool.tox.env_run_base]
75+
commands = [
76+
[
77+
"coverage",
78+
"run",
79+
"-m",
80+
"pytest",
81+
"--doctest-glob=README.md",
82+
"--junit-xml=.build/tests/pytest_{env_name}_junit.xml",
83+
],
84+
]
85+
set_env.COVERAGE_FILE = "{toxworkdir}/.coverage"
86+
dependency_groups = ["test"]
87+
package = "wheel"
88+
wheel_build_env = ".pkg"
89+
90+
[tool.tox.env.ruff]
91+
recreate = false
92+
skip_install = true
93+
dependency_groups = ["ruff"]
94+
commands = [
95+
# commands
96+
["ruff", "format", "--check", "."],
97+
["ruff", "check", "."],
98+
]
99+
100+
[tool.tox.env.cov-report]
101+
parallel_show_output = true
102+
recreate = false
103+
set_env.COVERAGE_FILE = "{toxworkdir}/.coverage"
104+
skip_install = true
105+
dependency_groups = ["coverage"]
106+
depends = ["py38", "py39", "py311", "py312", "py313"]
107+
commands = [
108+
[
109+
"coverage",
110+
"combine",
111+
],
112+
[
113+
"coverage",
114+
"html",
115+
"-d",
116+
".build/coverage",
117+
],
118+
[
119+
"coverage",
120+
"report",
121+
],
122+
]

0 commit comments

Comments
 (0)