Skip to content

Commit bf9f830

Browse files
Copilotpwwang
andauthored
Add generic pipe() function to datar for applying custom functions in piping workflows (#212)
Co-authored-by: pwwang <[email protected]> Co-authored-by: copilot-swe-agent[bot] <[email protected]>
1 parent 5f2c687 commit bf9f830

File tree

3 files changed

+155
-0
lines changed

3 files changed

+155
-0
lines changed

datar/misc.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,48 @@
1+
from typing import Any as _Any, Callable as _Callable
2+
3+
from pipda import register_verb as _register_verb
4+
15
from .core.load_plugins import plugin as _plugin
26

37
locals().update(_plugin.hooks.misc_api())
8+
9+
10+
@_register_verb(object)
11+
def pipe(data: _Any, func: _Callable, *args, **kwargs) -> _Any:
12+
"""Apply a function to the data
13+
14+
This function is similar to pandas.DataFrame.pipe() and allows you to
15+
apply custom functions in a piping workflow. Works with any data type.
16+
17+
Args:
18+
data: The data object (can be any type)
19+
func: Function to apply to the data. ``args`` and ``kwargs`` are
20+
passed into ``func``.
21+
*args: Positional arguments passed into ``func``
22+
**kwargs: Keyword arguments passed into ``func``
23+
24+
Returns:
25+
The return value of ``func``
26+
27+
Examples:
28+
>>> import datar.all as dr
29+
>>> # Works with lists
30+
>>> [1, 2, 3] >> dr.pipe(lambda x: [i * 2 for i in x])
31+
[2, 4, 6]
32+
33+
>>> # Works with dicts
34+
>>> data = {'a': 1, 'b': 2}
35+
>>> data >> dr.pipe(lambda x: {k: v * 2 for k, v in x.items()})
36+
{'a': 2, 'b': 4}
37+
38+
>>> # With additional arguments
39+
>>> def add_value(data, value):
40+
... return [x + value for x in data]
41+
>>> [1, 2, 3] >> dr.pipe(add_value, 10)
42+
[11, 12, 13]
43+
44+
>>> # Chain multiple operations
45+
>>> [1, 2, 3] >> dr.pipe(lambda x: [i * 2 for i in x]) >> dr.pipe(sum)
46+
12
47+
"""
48+
return func(data, *args, **kwargs)

docs/reference-maps/other.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
|---|---|---:|
2727
|[**`get()`**][2]|Extract values from data frames|[:material-notebook:][1]|
2828
|[**`flatten()`**][2]|Flatten values of data frames|[:material-notebook:][1]|
29+
|[**`pipe()`**][9]|Apply a function to data in a piping workflow|[:material-notebook:][1]|
2930

3031
### Functions
3132

@@ -44,3 +45,4 @@
4445
[6]: ../../api/datar.apis.datar/#datar.apis.datar.pd_str
4546
[7]: ../../api/datar.apis.datar/#datar.apis.datar.pd_cat
4647
[8]: ../../api/datar.apis.datar/#datar.apis.datar.pd_dt
48+
[9]: ../../api/datar.misc/#datar.misc.pipe

tests/test_pipe.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import pytest
2+
from datar.all import pipe
3+
4+
5+
def test_pipe_with_list():
6+
"""Test pipe with a list"""
7+
data = [1, 2, 3, 4, 5]
8+
result = data >> pipe(lambda x: [i * 2 for i in x])
9+
expected = [2, 4, 6, 8, 10]
10+
assert result == expected
11+
12+
13+
def test_pipe_with_dict():
14+
"""Test pipe with a dictionary"""
15+
data = {'a': 1, 'b': 2, 'c': 3}
16+
result = data >> pipe(lambda x: {k: v * 2 for k, v in x.items()})
17+
expected = {'a': 2, 'b': 4, 'c': 6}
18+
assert result == expected
19+
20+
21+
def test_pipe_with_args():
22+
"""Test pipe with additional positional arguments"""
23+
data = [1, 2, 3]
24+
25+
def add_value(data, value):
26+
return [x + value for x in data]
27+
28+
result = data >> pipe(add_value, 10)
29+
expected = [11, 12, 13]
30+
assert result == expected
31+
32+
33+
def test_pipe_with_kwargs():
34+
"""Test pipe with keyword arguments"""
35+
data = [1, 2, 3]
36+
37+
def multiply_data(data, factor=1):
38+
return [x * factor for x in data]
39+
40+
result = data >> pipe(multiply_data, factor=10)
41+
expected = [10, 20, 30]
42+
assert result == expected
43+
44+
45+
def test_pipe_with_string():
46+
"""Test pipe with string operations"""
47+
data = "hello"
48+
result = data >> pipe(lambda x: x.upper())
49+
assert result == "HELLO"
50+
51+
52+
def test_pipe_with_tuple():
53+
"""Test pipe with tuple"""
54+
data = (1, 2, 3)
55+
result = data >> pipe(lambda x: tuple(i * 2 for i in x))
56+
expected = (2, 4, 6)
57+
assert result == expected
58+
59+
60+
def test_pipe_returns_different_type():
61+
"""Test that pipe can return different types"""
62+
data = [1, 2, 3, 4, 5]
63+
result = data >> pipe(sum)
64+
assert result == 15
65+
66+
67+
def test_pipe_chain_multiple():
68+
"""Test chaining multiple pipe operations"""
69+
data = [1, 2, 3]
70+
71+
result = (
72+
data
73+
>> pipe(lambda x: [i * 2 for i in x])
74+
>> pipe(lambda x: [i + 1 for i in x])
75+
)
76+
expected = [3, 5, 7]
77+
assert result == expected
78+
79+
80+
def test_pipe_with_custom_class():
81+
"""Test pipe with a custom class"""
82+
class Counter:
83+
def __init__(self, value):
84+
self.value = value
85+
86+
def increment(self, amount):
87+
return Counter(self.value + amount)
88+
89+
def __eq__(self, other):
90+
return self.value == other.value
91+
92+
counter = Counter(5)
93+
result = counter >> pipe(lambda x: x.increment(10))
94+
expected = Counter(15)
95+
assert result == expected
96+
97+
98+
def test_pipe_with_multiple_args_and_kwargs():
99+
"""Test pipe with both args and kwargs"""
100+
data = [1, 2, 3]
101+
102+
def transform(data, multiplier, offset=0):
103+
return [x * multiplier + offset for x in data]
104+
105+
result = data >> pipe(transform, 2, offset=5)
106+
expected = [7, 9, 11]
107+
assert result == expected
108+

0 commit comments

Comments
 (0)