Skip to content
This repository was archived by the owner on Jan 31, 2026. It is now read-only.

Commit 54e8c50

Browse files
authored
Merge pull request #75 from katunilya/73-add-predicate-function-builder
73 add predicate function builder
2 parents a0f96c9 + 883d69f commit 54e8c50

3 files changed

Lines changed: 177 additions & 0 deletions

File tree

tests/test_predicate.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import pytest
2+
3+
from tibia.predicate import all_, any_, not_, when, when_or, where
4+
from tibia.value import Value
5+
6+
7+
@pytest.mark.parametrize(
8+
("value", "target"),
9+
[
10+
("", False),
11+
("abc", False),
12+
("abc123", True),
13+
],
14+
)
15+
def test_all(value: str, target: bool) -> None:
16+
pfn = all_(
17+
lambda s: "a" in s,
18+
lambda s: "1" in s,
19+
)
20+
21+
assert pfn(value) == target
22+
23+
24+
@pytest.mark.parametrize(
25+
("value", "target"),
26+
[
27+
("", False),
28+
("abc", True),
29+
("abc123", True),
30+
],
31+
)
32+
def test_any(value: str, target: bool) -> None:
33+
pfn = any_(
34+
lambda s: "a" in s,
35+
lambda s: "1" in s,
36+
)
37+
38+
assert pfn(value) == target
39+
40+
41+
@pytest.mark.parametrize(
42+
("value", "target", "target_not"),
43+
[
44+
("", False, True),
45+
("abc", True, False),
46+
("123", False, True),
47+
],
48+
)
49+
def test_not(value: str, target: bool, target_not: bool) -> None:
50+
not_pnf = not_(str.__contains__, "abc")
51+
52+
assert ("abc" in value) == target
53+
assert not_pnf(value) == target_not
54+
55+
56+
@pytest.mark.parametrize(
57+
("value", "target"),
58+
[
59+
("", False),
60+
("abc", True),
61+
("abc123", True),
62+
],
63+
)
64+
def test_where(value: str, target: bool) -> None:
65+
pfn = (
66+
where(str.__contains__, "a")
67+
.and_(str.__contains__, "b")
68+
.or_(str.__contains__, "c12")
69+
.unwrap()
70+
)
71+
72+
assert pfn(value) == target
73+
74+
75+
@pytest.mark.parametrize(
76+
("value", "target"),
77+
[
78+
(0, 0),
79+
(1, 2),
80+
],
81+
)
82+
def test_when(value: int, target: int) -> None:
83+
result = Value(value).map(when, lambda i: i > 0, lambda i: i + 1).unwrap()
84+
85+
assert result == target
86+
87+
88+
@pytest.mark.parametrize(
89+
("value", "target"),
90+
[
91+
(0, "no"),
92+
(1, "1"),
93+
],
94+
)
95+
def test_when_or(value: int, target: str) -> None:
96+
result = Value(value).map(when_or, lambda i: i > 0, "no", str).unwrap()
97+
98+
assert result == target

tibia/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from .future_maybe import FutureMaybe
33
from .future_result import FutureResult
44
from .maybe import Empty, Maybe, Some
5+
from .predicate import all_, any_, not_, where
56
from .result import Err, Ok, Result
67
from .value import Value
78

@@ -12,6 +13,10 @@
1213
"Empty",
1314
"Maybe",
1415
"Some",
16+
"all_",
17+
"any_",
18+
"not_",
19+
"where",
1520
"Err",
1621
"Ok",
1722
"Result",

tibia/predicate.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from __future__ import annotations
2+
3+
from typing import Callable, Concatenate
4+
5+
6+
def all_[T](*pfns: Callable[[T], bool]) -> Callable[[T], bool]:
7+
return lambda t: all(fn(t) for fn in pfns)
8+
9+
10+
def any_[T](*pfns: Callable[[T], bool]) -> Callable[[T], bool]:
11+
return lambda t: any(fn(t) for fn in pfns)
12+
13+
14+
def not_[T, **P](
15+
pfn: Callable[Concatenate[T, P], bool],
16+
*args: P.args,
17+
**kwargs: P.kwargs,
18+
) -> Callable[[T], bool]:
19+
return lambda t: not pfn(t, *args, **kwargs)
20+
21+
22+
class where[T, **P]:
23+
pfn: Callable[[T], bool]
24+
25+
def __init__(
26+
self,
27+
pfn: Callable[Concatenate[T, P], bool],
28+
*args: P.args,
29+
**kwargs: P.kwargs,
30+
):
31+
self.pfn = lambda t: pfn(t, *args, **kwargs)
32+
33+
def __call__(self, value: T) -> bool:
34+
return self.pfn(value)
35+
36+
def or_[**P_](
37+
self,
38+
pfn: Callable[Concatenate[T, P_], bool],
39+
*args: P_.args,
40+
**kwargs: P_.kwargs,
41+
) -> where[T]:
42+
return where(lambda t: self.pfn(t) or pfn(t, *args, **kwargs))
43+
44+
def and_[**P_](
45+
self,
46+
pfn: Callable[Concatenate[T, P_], bool],
47+
*args: P_.args,
48+
**kwargs: P_.kwargs,
49+
) -> where[T]:
50+
return where(lambda t: self.pfn(t) and pfn(t, *args, **kwargs))
51+
52+
def unwrap(self) -> Callable[[T], bool]:
53+
return self
54+
55+
56+
def when[T, **P](
57+
value: T,
58+
pfn: Callable[[T], bool],
59+
fn: Callable[Concatenate[T, P], T],
60+
*args: P.args,
61+
**kwargs: P.kwargs,
62+
) -> T:
63+
return fn(value, *args, **kwargs) if pfn(value) else value
64+
65+
66+
def when_or[T, **P, R](
67+
value: T,
68+
pfn: Callable[[T], bool],
69+
other: R,
70+
fn: Callable[Concatenate[T, P], R],
71+
*args: P.args,
72+
**kwargs: P.kwargs,
73+
) -> R:
74+
return fn(value, *args, **kwargs) if pfn(value) else other

0 commit comments

Comments
 (0)