py-predicate 0.7__tar.gz → 0.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {py_predicate-0.7 → py_predicate-0.9}/PKG-INFO +1 -1
- {py_predicate-0.7 → py_predicate-0.9}/predicate/__init__.py +11 -1
- {py_predicate-0.7 → py_predicate-0.9}/predicate/all_predicate.py +6 -2
- {py_predicate-0.7 → py_predicate-0.9}/predicate/always_false_predicate.py +5 -1
- {py_predicate-0.7 → py_predicate-0.9}/predicate/always_true_predicate.py +5 -1
- {py_predicate-0.7 → py_predicate-0.9}/predicate/any_predicate.py +6 -2
- {py_predicate-0.7 → py_predicate-0.9}/predicate/comp_predicate.py +1 -1
- py_predicate-0.9/predicate/constructor/construct.py +84 -0
- py_predicate-0.9/predicate/constructor/helpers.py +48 -0
- py_predicate-0.9/predicate/constructor/mutate.py +78 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/eq_predicate.py +6 -2
- {py_predicate-0.7 → py_predicate-0.9}/predicate/fn_predicate.py +7 -1
- {py_predicate-0.7 → py_predicate-0.9}/predicate/formatter/helpers.py +0 -1
- {py_predicate-0.7 → py_predicate-0.9}/predicate/ge_predicate.py +6 -2
- {py_predicate-0.7 → py_predicate-0.9}/predicate/generator/generate_false.py +56 -40
- py_predicate-0.9/predicate/generator/generate_predicate.py +188 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/generator/generate_true.py +30 -22
- {py_predicate-0.7 → py_predicate-0.9}/predicate/generator/helpers.py +74 -26
- {py_predicate-0.7 → py_predicate-0.9}/predicate/gt_predicate.py +6 -2
- {py_predicate-0.7 → py_predicate-0.9}/predicate/has_length_predicate.py +4 -4
- {py_predicate-0.7 → py_predicate-0.9}/predicate/ip_address_predicates.py +3 -3
- {py_predicate-0.7 → py_predicate-0.9}/predicate/is_instance_predicate.py +8 -4
- py_predicate-0.9/predicate/is_predicate_of_p.py +30 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/le_predicate.py +6 -2
- {py_predicate-0.7 → py_predicate-0.9}/predicate/list_of_predicate.py +4 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/lt_predicate.py +6 -2
- py_predicate-0.9/predicate/match_predicate.py +119 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/ne_predicate.py +6 -2
- {py_predicate-0.7 → py_predicate-0.9}/predicate/negate.py +10 -10
- {py_predicate-0.7 → py_predicate-0.9}/predicate/optimizer/all_optimizer.py +2 -2
- {py_predicate-0.7 → py_predicate-0.9}/predicate/optimizer/or_optimizer.py +14 -2
- {py_predicate-0.7 → py_predicate-0.9}/predicate/parser.py +18 -3
- {py_predicate-0.7 → py_predicate-0.9}/predicate/predicate.py +26 -2
- {py_predicate-0.7 → py_predicate-0.9}/predicate/range_predicate.py +28 -22
- {py_predicate-0.7 → py_predicate-0.9}/predicate/set_of_predicate.py +3 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/set_predicates.py +17 -1
- {py_predicate-0.7 → py_predicate-0.9}/predicate/standard_predicates.py +26 -12
- {py_predicate-0.7 → py_predicate-0.9}/predicate/str_predicates.py +2 -3
- {py_predicate-0.7 → py_predicate-0.9}/pyproject.toml +1 -1
- py_predicate-0.7/predicate/constructor/construct.py +0 -60
- py_predicate-0.7/predicate/is_empty_predicate.py +0 -41
- {py_predicate-0.7 → py_predicate-0.9}/README.md +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/constructor/__init__.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/dict_of_predicate.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/explain.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/formatter/__init__.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/formatter/format_dot.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/formatter/format_json.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/formatter/format_latex.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/generator/__init__.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/has_key_predicate.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/has_path_predicate.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/helpers.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/implies.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/implies_predicate.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/in_predicate_predicate.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/is_callable_predicate.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/is_falsy_predicate.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/is_lambda_predicate.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/is_none_predicate.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/is_not_none_predicate.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/is_truthy_predicate.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/lazy_predicate.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/named_predicate.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/optimizer/__init__.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/optimizer/and_optimizer.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/optimizer/any_optimizer.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/optimizer/in_optimizer.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/optimizer/not_optimizer.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/optimizer/predicate_optimizer.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/optimizer/xor_optimizer.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/property_predicate.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/regex_predicate.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/root_predicate.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/tee_predicate.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/this_predicate.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/truth_table.py +0 -0
- {py_predicate-0.7 → py_predicate-0.9}/predicate/tuple_of_predicate.py +0 -0
|
@@ -8,8 +8,8 @@ from predicate.explain import explain
|
|
|
8
8
|
from predicate.formatter import to_dot, to_json, to_latex
|
|
9
9
|
from predicate.generator.generate_false import generate_false
|
|
10
10
|
from predicate.generator.generate_true import generate_true
|
|
11
|
-
from predicate.is_empty_predicate import is_empty_p, is_not_empty_p
|
|
12
11
|
from predicate.is_lambda_predicate import is_lambda_p, is_lambda_with_signature_p
|
|
12
|
+
from predicate.match_predicate import exactly_n, match_p, optional, plus, repeat, star
|
|
13
13
|
from predicate.optimizer.predicate_optimizer import can_optimize, optimize
|
|
14
14
|
from predicate.set_predicates import (
|
|
15
15
|
in_p,
|
|
@@ -42,6 +42,7 @@ from predicate.standard_predicates import (
|
|
|
42
42
|
is_datetime_p,
|
|
43
43
|
is_dict_of_p,
|
|
44
44
|
is_dict_p,
|
|
45
|
+
is_empty_p,
|
|
45
46
|
is_falsy_p,
|
|
46
47
|
is_finite_p,
|
|
47
48
|
is_float_p,
|
|
@@ -55,7 +56,9 @@ from predicate.standard_predicates import (
|
|
|
55
56
|
is_list_p,
|
|
56
57
|
is_nan_p,
|
|
57
58
|
is_none_p,
|
|
59
|
+
is_not_empty_p,
|
|
58
60
|
is_not_none_p,
|
|
61
|
+
is_predicate_of_p,
|
|
59
62
|
is_predicate_p,
|
|
60
63
|
is_range_p,
|
|
61
64
|
is_set_of_p,
|
|
@@ -89,6 +92,7 @@ __all__ = [
|
|
|
89
92
|
"eq_false_p",
|
|
90
93
|
"eq_p",
|
|
91
94
|
"eq_true_p",
|
|
95
|
+
"exactly_n",
|
|
92
96
|
"explain",
|
|
93
97
|
"fn_p",
|
|
94
98
|
"ge_le_p",
|
|
@@ -133,6 +137,7 @@ __all__ = [
|
|
|
133
137
|
"is_none_p",
|
|
134
138
|
"is_not_empty_p",
|
|
135
139
|
"is_not_none_p",
|
|
140
|
+
"is_predicate_of_p",
|
|
136
141
|
"is_predicate_p",
|
|
137
142
|
"is_range_p",
|
|
138
143
|
"is_set_of_p",
|
|
@@ -151,14 +156,19 @@ __all__ = [
|
|
|
151
156
|
"lazy_p",
|
|
152
157
|
"le_p",
|
|
153
158
|
"lt_p",
|
|
159
|
+
"match_p",
|
|
154
160
|
"ne_p",
|
|
155
161
|
"neg_p",
|
|
156
162
|
"never_p",
|
|
157
163
|
"not_in_p",
|
|
158
164
|
"optimize",
|
|
165
|
+
"optional",
|
|
166
|
+
"plus",
|
|
159
167
|
"pos_p",
|
|
160
168
|
"regex_p",
|
|
169
|
+
"repeat",
|
|
161
170
|
"root_p",
|
|
171
|
+
"star",
|
|
162
172
|
"tee_p",
|
|
163
173
|
"this_p",
|
|
164
174
|
"to_dot",
|
|
@@ -18,10 +18,14 @@ class AllPredicate[T](Predicate[T]):
|
|
|
18
18
|
return predicate in self.predicate
|
|
19
19
|
|
|
20
20
|
def __repr__(self) -> str:
|
|
21
|
-
return f"
|
|
21
|
+
return f"all_p({self.predicate!r})"
|
|
22
|
+
|
|
23
|
+
@override
|
|
24
|
+
def get_klass(self) -> type:
|
|
25
|
+
return self.predicate.klass
|
|
22
26
|
|
|
23
27
|
@override
|
|
24
28
|
def explain_failure(self, iterable: Iterable[T]) -> dict:
|
|
25
29
|
fail = first_false(iterable, self.predicate)
|
|
26
30
|
|
|
27
|
-
return {"reason": f"Item '{fail}' didn't match predicate {
|
|
31
|
+
return {"reason": f"Item '{fail}' didn't match predicate {self.predicate!r}"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from typing import Final, override
|
|
2
|
+
from typing import Any, Final, override
|
|
3
3
|
|
|
4
4
|
from predicate.predicate import Predicate
|
|
5
5
|
|
|
@@ -18,6 +18,10 @@ class AlwaysFalsePredicate(Predicate):
|
|
|
18
18
|
def explain_failure(self, *args, **kwargs) -> dict:
|
|
19
19
|
return {"reason": "Always returns False"}
|
|
20
20
|
|
|
21
|
+
@override
|
|
22
|
+
def get_klass(self) -> type:
|
|
23
|
+
return type(Any)
|
|
24
|
+
|
|
21
25
|
|
|
22
26
|
always_false_p: Final[AlwaysFalsePredicate] = AlwaysFalsePredicate()
|
|
23
27
|
"""Predicate that always evaluates to False."""
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from typing import Final
|
|
2
|
+
from typing import Any, Final, override
|
|
3
3
|
|
|
4
4
|
from predicate.predicate import Predicate
|
|
5
5
|
|
|
@@ -14,6 +14,10 @@ class AlwaysTruePredicate(Predicate):
|
|
|
14
14
|
def __repr__(self) -> str:
|
|
15
15
|
return "always_true_p"
|
|
16
16
|
|
|
17
|
+
@override
|
|
18
|
+
def get_klass(self) -> type:
|
|
19
|
+
return type(Any)
|
|
20
|
+
|
|
17
21
|
|
|
18
22
|
always_true_p: Final[AlwaysTruePredicate] = AlwaysTruePredicate()
|
|
19
23
|
"""Predicate that always evaluates to True."""
|
|
@@ -17,8 +17,12 @@ class AnyPredicate[T](Predicate[T]):
|
|
|
17
17
|
return predicate in self.predicate
|
|
18
18
|
|
|
19
19
|
def __repr__(self) -> str:
|
|
20
|
-
return f"
|
|
20
|
+
return f"any_p({self.predicate!r})"
|
|
21
|
+
|
|
22
|
+
@override
|
|
23
|
+
def get_klass(self) -> type:
|
|
24
|
+
return self.predicate.klass
|
|
21
25
|
|
|
22
26
|
@override
|
|
23
27
|
def explain_failure(self, iterable: Iterable[T]) -> dict:
|
|
24
|
-
return {"reason": f"No item matches predicate {
|
|
28
|
+
return {"reason": f"No item matches predicate {self.predicate!r}"}
|
|
@@ -15,7 +15,7 @@ class CompPredicate[S, T](Predicate[T]):
|
|
|
15
15
|
return self.predicate(self.fn(x))
|
|
16
16
|
|
|
17
17
|
def __repr__(self) -> str:
|
|
18
|
-
return f"comp_p({
|
|
18
|
+
return f"comp_p({self.predicate!r})"
|
|
19
19
|
|
|
20
20
|
def __contains__(self, predicate: Predicate[T]) -> bool:
|
|
21
21
|
return predicate in self.predicate
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from typing import Iterator
|
|
2
|
+
|
|
3
|
+
from more_itertools import first, gray_product, take
|
|
4
|
+
|
|
5
|
+
from predicate import (
|
|
6
|
+
always_false_p,
|
|
7
|
+
always_true_p,
|
|
8
|
+
is_datetime_p,
|
|
9
|
+
is_falsy_p,
|
|
10
|
+
is_float_p,
|
|
11
|
+
is_int_p,
|
|
12
|
+
is_not_none_p,
|
|
13
|
+
is_set_p,
|
|
14
|
+
is_str_p,
|
|
15
|
+
is_truthy_p,
|
|
16
|
+
)
|
|
17
|
+
from predicate.constructor.helpers import perfect_match, sort_by_match
|
|
18
|
+
from predicate.constructor.mutate import mutations
|
|
19
|
+
from predicate.predicate import Predicate
|
|
20
|
+
from predicate.standard_predicates import (
|
|
21
|
+
ge_p,
|
|
22
|
+
gt_p,
|
|
23
|
+
is_bool_p,
|
|
24
|
+
is_dict_p,
|
|
25
|
+
is_list_p,
|
|
26
|
+
is_none_p,
|
|
27
|
+
le_p,
|
|
28
|
+
lt_p,
|
|
29
|
+
ne_p,
|
|
30
|
+
zero_p,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def construct(false_set: list, true_set: list, attempts: int = 30) -> Predicate | None:
|
|
35
|
+
predicates = initial_predicates()
|
|
36
|
+
|
|
37
|
+
while attempts:
|
|
38
|
+
sorted_by_match = sort_by_match(list(predicates), false_set=false_set, true_set=true_set)
|
|
39
|
+
|
|
40
|
+
if perfect_match(matched := first(sorted_by_match), false_set=false_set, true_set=true_set):
|
|
41
|
+
return matched
|
|
42
|
+
|
|
43
|
+
predicates = create_mutations(take(20, sorted_by_match), false_set=false_set, true_set=true_set)
|
|
44
|
+
|
|
45
|
+
attempts -= 1
|
|
46
|
+
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def create_mutations(candidates: list[Predicate], false_set: list, true_set: list) -> Iterator[Predicate]:
|
|
51
|
+
for candidate in candidates:
|
|
52
|
+
yield from mutations(candidate, false_set=false_set, true_set=true_set)
|
|
53
|
+
|
|
54
|
+
pairs = gray_product(candidates, candidates)
|
|
55
|
+
for pair in pairs:
|
|
56
|
+
left, right = pair
|
|
57
|
+
if left != right:
|
|
58
|
+
yield left | right
|
|
59
|
+
yield left & right
|
|
60
|
+
yield left ^ right
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def initial_predicates() -> Iterator[Predicate]:
|
|
64
|
+
# TODO: probably import from __init__
|
|
65
|
+
yield always_false_p
|
|
66
|
+
yield always_true_p
|
|
67
|
+
yield ge_p(0)
|
|
68
|
+
yield gt_p(0)
|
|
69
|
+
yield is_bool_p
|
|
70
|
+
yield is_datetime_p
|
|
71
|
+
yield is_dict_p
|
|
72
|
+
yield is_falsy_p
|
|
73
|
+
yield is_float_p
|
|
74
|
+
yield is_int_p
|
|
75
|
+
yield is_list_p
|
|
76
|
+
yield is_none_p
|
|
77
|
+
yield is_not_none_p
|
|
78
|
+
yield is_set_p
|
|
79
|
+
yield is_str_p
|
|
80
|
+
yield is_truthy_p
|
|
81
|
+
yield le_p(0)
|
|
82
|
+
yield lt_p(0)
|
|
83
|
+
yield ne_p(0)
|
|
84
|
+
yield zero_p
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from predicate.predicate import Predicate
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def safe_call_false(predicate: Predicate, value: Any) -> bool:
|
|
7
|
+
# For performance reasons type checking is not part of the predicate itself
|
|
8
|
+
try:
|
|
9
|
+
return predicate(value)
|
|
10
|
+
except TypeError:
|
|
11
|
+
return True
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def safe_call_true(predicate: Predicate, value: Any) -> bool:
|
|
15
|
+
# For performance reasons type checking is not part of the predicate itself
|
|
16
|
+
try:
|
|
17
|
+
return predicate(value)
|
|
18
|
+
except TypeError:
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def predicate_match(predicate: Predicate, false_set: list, true_set: list) -> dict[str, int]:
|
|
23
|
+
false_misses = sum(safe_call_false(predicate, value) for value in false_set)
|
|
24
|
+
false_matches = len(false_set) - false_misses
|
|
25
|
+
|
|
26
|
+
true_matches = sum(safe_call_true(predicate, value) for value in true_set)
|
|
27
|
+
true_misses = len(true_set) - true_matches
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
"false_matches": false_matches,
|
|
31
|
+
"false_misses": false_misses,
|
|
32
|
+
"true_matches": true_matches,
|
|
33
|
+
"true_misses": true_misses,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def sort_by_match(predicates: list[Predicate], false_set: list, true_set: list) -> list[Predicate]:
|
|
38
|
+
def best_match(predicate) -> int:
|
|
39
|
+
match = predicate_match(predicate, false_set=false_set, true_set=true_set)
|
|
40
|
+
|
|
41
|
+
return match["false_matches"] + match["true_matches"]
|
|
42
|
+
|
|
43
|
+
return sorted(predicates, key=best_match, reverse=True)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def perfect_match(predicate: Predicate, false_set: list, true_set: list) -> bool:
|
|
47
|
+
match = predicate_match(predicate, false_set=false_set, true_set=true_set)
|
|
48
|
+
return match["false_misses"] + match["true_misses"] == 0
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from collections.abc import Iterator
|
|
2
|
+
from functools import singledispatch
|
|
3
|
+
from random import choice, randint
|
|
4
|
+
|
|
5
|
+
from predicate import eq_p, ge_p, gt_p, le_p, lt_p, ne_p
|
|
6
|
+
from predicate.eq_predicate import EqPredicate
|
|
7
|
+
from predicate.ge_predicate import GePredicate
|
|
8
|
+
from predicate.gt_predicate import GtPredicate
|
|
9
|
+
from predicate.le_predicate import LePredicate
|
|
10
|
+
from predicate.lt_predicate import LtPredicate
|
|
11
|
+
from predicate.ne_predicate import NePredicate
|
|
12
|
+
from predicate.predicate import Predicate
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@singledispatch
|
|
16
|
+
def mutations(predicate: Predicate, false_set: list, true_set: list, nr: int = 3) -> Iterator[Predicate]:
|
|
17
|
+
"""Return nr of mutations."""
|
|
18
|
+
yield predicate
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def int_value_mutations(n: int, values: list, nr: int) -> Iterator[int]:
|
|
22
|
+
yield choice(values)
|
|
23
|
+
yield n - randint(0, 10)
|
|
24
|
+
yield n + randint(0, 10)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@mutations.register
|
|
28
|
+
def _(predicate: EqPredicate, false_set: list, true_set: list, nr: int = 3) -> Iterator[Predicate]:
|
|
29
|
+
match predicate.v:
|
|
30
|
+
case int(n):
|
|
31
|
+
yield from (eq_p(v) for v in int_value_mutations(n, true_set, nr))
|
|
32
|
+
case _:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@mutations.register
|
|
37
|
+
def _(predicate: NePredicate, false_set, true_set: list, nr: int = 3) -> Iterator[Predicate]:
|
|
38
|
+
match predicate.v:
|
|
39
|
+
case int(n):
|
|
40
|
+
yield from (ne_p(v) for v in int_value_mutations(n, false_set, nr))
|
|
41
|
+
case _:
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@mutations.register
|
|
46
|
+
def _(predicate: GePredicate, false_set, true_set: list, nr: int = 3) -> Iterator[Predicate]:
|
|
47
|
+
match predicate.v:
|
|
48
|
+
case int(n):
|
|
49
|
+
yield from (ge_p(v) for v in int_value_mutations(n, true_set, nr))
|
|
50
|
+
case _:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@mutations.register
|
|
55
|
+
def _(predicate: GtPredicate, false_set, true_set: list, nr: int = 3) -> Iterator[Predicate]:
|
|
56
|
+
match predicate.v:
|
|
57
|
+
case int(n):
|
|
58
|
+
yield from (gt_p(v) for v in int_value_mutations(n, true_set, nr))
|
|
59
|
+
case _:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@mutations.register
|
|
64
|
+
def _(predicate: LePredicate, false_set, true_set: list, nr: int = 3) -> Iterator[Predicate]:
|
|
65
|
+
match predicate.v:
|
|
66
|
+
case int(n):
|
|
67
|
+
yield from (le_p(v) for v in int_value_mutations(n, true_set, nr))
|
|
68
|
+
case _:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@mutations.register
|
|
73
|
+
def _(predicate: LtPredicate, false_set, true_set: list, nr: int = 3) -> Iterator[Predicate]:
|
|
74
|
+
match predicate.v:
|
|
75
|
+
case int(n):
|
|
76
|
+
yield from (lt_p(v) for v in int_value_mutations(n, true_set, nr))
|
|
77
|
+
case _:
|
|
78
|
+
pass
|
|
@@ -14,8 +14,12 @@ class EqPredicate[T](Predicate[T]):
|
|
|
14
14
|
return x == self.v
|
|
15
15
|
|
|
16
16
|
def __repr__(self) -> str:
|
|
17
|
-
return f"eq_p({self.v})"
|
|
17
|
+
return f"eq_p({self.v!r})"
|
|
18
|
+
|
|
19
|
+
@override
|
|
20
|
+
def get_klass(self) -> type:
|
|
21
|
+
return type(self.v)
|
|
18
22
|
|
|
19
23
|
@override
|
|
20
24
|
def explain_failure(self, x: T) -> dict:
|
|
21
|
-
return {"reason": f"{x} is not equal to {self.v}"}
|
|
25
|
+
return {"reason": f"{x} is not equal to {self.v!r}"}
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from typing import Callable, override
|
|
2
|
+
from typing import Callable, Iterator, override
|
|
3
3
|
|
|
4
4
|
from predicate.predicate import Predicate
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
def undefined() -> Iterator:
|
|
8
|
+
raise ValueError("Please register generator type")
|
|
9
|
+
|
|
10
|
+
|
|
7
11
|
@dataclass
|
|
8
12
|
class FnPredicate[T](Predicate[T]):
|
|
9
13
|
"""A predicate class that can hold a function."""
|
|
10
14
|
|
|
11
15
|
predicate_fn: Callable[[T], bool]
|
|
16
|
+
generate_false_fn: Callable[[], Iterator]
|
|
17
|
+
generate_true_fn: Callable[[], Iterator]
|
|
12
18
|
|
|
13
19
|
def __call__(self, x: T) -> bool:
|
|
14
20
|
return self.predicate_fn(x)
|
|
@@ -14,8 +14,12 @@ class GePredicate[T](Predicate[T]):
|
|
|
14
14
|
return x >= self.v
|
|
15
15
|
|
|
16
16
|
def __repr__(self) -> str:
|
|
17
|
-
return f"ge_p({self.v})"
|
|
17
|
+
return f"ge_p({self.v!r})"
|
|
18
|
+
|
|
19
|
+
@override
|
|
20
|
+
def get_klass(self) -> type:
|
|
21
|
+
return type(self.v)
|
|
18
22
|
|
|
19
23
|
@override
|
|
20
24
|
def explain_failure(self, x: T) -> dict:
|
|
21
|
-
return {"reason": f"{x} is not greater or equal to {self.v}"}
|
|
25
|
+
return {"reason": f"{x} is not greater or equal to {self.v!r}"}
|
|
@@ -6,13 +6,14 @@ from functools import singledispatch
|
|
|
6
6
|
from itertools import repeat
|
|
7
7
|
from uuid import UUID
|
|
8
8
|
|
|
9
|
-
from more_itertools import chunked, flatten, interleave,
|
|
9
|
+
from more_itertools import chunked, first, flatten, interleave, random_permutation, take
|
|
10
10
|
|
|
11
11
|
from predicate.all_predicate import AllPredicate
|
|
12
12
|
from predicate.always_false_predicate import AlwaysFalsePredicate, always_false_p
|
|
13
13
|
from predicate.always_true_predicate import AlwaysTruePredicate, always_true_p
|
|
14
14
|
from predicate.dict_of_predicate import DictOfPredicate
|
|
15
15
|
from predicate.eq_predicate import EqPredicate
|
|
16
|
+
from predicate.fn_predicate import FnPredicate
|
|
16
17
|
from predicate.ge_predicate import GePredicate
|
|
17
18
|
from predicate.generator.helpers import (
|
|
18
19
|
generate_anys,
|
|
@@ -26,11 +27,11 @@ from predicate.generator.helpers import (
|
|
|
26
27
|
random_floats,
|
|
27
28
|
random_ints,
|
|
28
29
|
random_iterables,
|
|
30
|
+
random_values_of_type,
|
|
29
31
|
)
|
|
30
32
|
from predicate.gt_predicate import GtPredicate
|
|
31
33
|
from predicate.has_key_predicate import HasKeyPredicate
|
|
32
34
|
from predicate.has_length_predicate import HasLengthPredicate
|
|
33
|
-
from predicate.is_empty_predicate import IsEmptyPredicate, IsNotEmptyPredicate
|
|
34
35
|
from predicate.is_falsy_predicate import IsFalsyPredicate
|
|
35
36
|
from predicate.is_instance_predicate import IsInstancePredicate
|
|
36
37
|
from predicate.is_none_predicate import IsNonePredicate
|
|
@@ -41,13 +42,7 @@ from predicate.list_of_predicate import ListOfPredicate
|
|
|
41
42
|
from predicate.lt_predicate import LtPredicate
|
|
42
43
|
from predicate.ne_predicate import NePredicate
|
|
43
44
|
from predicate.optimizer.predicate_optimizer import optimize
|
|
44
|
-
from predicate.predicate import
|
|
45
|
-
AndPredicate,
|
|
46
|
-
NotPredicate,
|
|
47
|
-
OrPredicate,
|
|
48
|
-
Predicate,
|
|
49
|
-
XorPredicate,
|
|
50
|
-
)
|
|
45
|
+
from predicate.predicate import AndPredicate, NotPredicate, OrPredicate, Predicate, XorPredicate
|
|
51
46
|
from predicate.range_predicate import GeLePredicate, GeLtPredicate, GtLePredicate, GtLtPredicate
|
|
52
47
|
from predicate.set_of_predicate import SetOfPredicate
|
|
53
48
|
from predicate.set_predicates import InPredicate, NotInPredicate
|
|
@@ -56,21 +51,17 @@ from predicate.tuple_of_predicate import TupleOfPredicate
|
|
|
56
51
|
|
|
57
52
|
|
|
58
53
|
@singledispatch
|
|
59
|
-
def generate_false[T](
|
|
54
|
+
def generate_false[T](predicate: Predicate[T]) -> Iterator[T]:
|
|
60
55
|
"""Generate values that don't satisfy this predicate."""
|
|
61
|
-
raise ValueError("Please register generator for correct predicate type")
|
|
56
|
+
raise ValueError(f"Please register generator for correct predicate type: {predicate!r}")
|
|
62
57
|
|
|
63
58
|
|
|
64
59
|
@generate_false.register
|
|
65
|
-
def generate_all_p(all_predicate: AllPredicate) -> Iterator:
|
|
60
|
+
def generate_all_p(all_predicate: AllPredicate, *, min_size: int = 1, max_size: int = 10) -> Iterator:
|
|
66
61
|
predicate = all_predicate.predicate
|
|
67
62
|
|
|
68
63
|
while True:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
# TODO: combination of some true values, or just rewrite as any(false)
|
|
72
|
-
values = take(max_length, generate_false(predicate))
|
|
73
|
-
yield random_combination_with_replacement(values, max_length)
|
|
64
|
+
yield generate_at_least_one_false(predicate, min_size=min_size, max_size=max_size)
|
|
74
65
|
|
|
75
66
|
|
|
76
67
|
@generate_false.register
|
|
@@ -98,7 +89,7 @@ def generate_always_true(_predicate: AlwaysTruePredicate) -> Iterator:
|
|
|
98
89
|
|
|
99
90
|
@generate_false.register
|
|
100
91
|
def generate_eq(predicate: EqPredicate) -> Iterator:
|
|
101
|
-
yield from
|
|
92
|
+
yield from (value for value in random_values_of_type(klass=predicate.klass) if not predicate(value))
|
|
102
93
|
|
|
103
94
|
|
|
104
95
|
@generate_false.register
|
|
@@ -115,8 +106,13 @@ def generate_has_key(predicate: HasKeyPredicate) -> Iterator:
|
|
|
115
106
|
|
|
116
107
|
@generate_false.register
|
|
117
108
|
def generate_has_length(predicate: HasLengthPredicate) -> Iterator:
|
|
118
|
-
|
|
119
|
-
|
|
109
|
+
length_p = predicate.length_p
|
|
110
|
+
invalid_lengths = (length for length in generate_false(length_p) if length >= 0)
|
|
111
|
+
invalid_length = first(invalid_lengths)
|
|
112
|
+
|
|
113
|
+
# TODO: generate with different invalid lengths
|
|
114
|
+
|
|
115
|
+
yield from random_iterables(min_size=invalid_length, max_size=invalid_length)
|
|
120
116
|
|
|
121
117
|
|
|
122
118
|
@generate_false.register
|
|
@@ -234,6 +230,11 @@ def generate_falsy(_predicate: IsFalsyPredicate) -> Iterator:
|
|
|
234
230
|
yield from generate_anys(IsTruthyPredicate())
|
|
235
231
|
|
|
236
232
|
|
|
233
|
+
@generate_false.register
|
|
234
|
+
def generate_fn_p(predicate: FnPredicate) -> Iterator:
|
|
235
|
+
yield from predicate.generate_false_fn()
|
|
236
|
+
|
|
237
|
+
|
|
237
238
|
@generate_false.register
|
|
238
239
|
def generate_in(predicate: InPredicate) -> Iterator:
|
|
239
240
|
# TODO: combine with generate_not_in true
|
|
@@ -245,16 +246,6 @@ def generate_in(predicate: InPredicate) -> Iterator:
|
|
|
245
246
|
yield from generate_strings(~predicate)
|
|
246
247
|
|
|
247
248
|
|
|
248
|
-
@generate_false.register
|
|
249
|
-
def generate_is_empty(_predicate: IsEmptyPredicate) -> Iterator:
|
|
250
|
-
yield from random_iterables(min_size=1)
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
@generate_false.register
|
|
254
|
-
def generate_is_not_empty(_predicate: IsNotEmptyPredicate) -> Iterator:
|
|
255
|
-
yield from random_iterables(max_size=0)
|
|
256
|
-
|
|
257
|
-
|
|
258
249
|
@generate_false.register
|
|
259
250
|
def generate_le(predicate: LePredicate) -> Iterator:
|
|
260
251
|
match v := predicate.v:
|
|
@@ -331,9 +322,18 @@ def generate_is_instance_p(predicate: IsInstancePredicate) -> Iterator:
|
|
|
331
322
|
|
|
332
323
|
@generate_false.register
|
|
333
324
|
def generate_or(predicate: OrPredicate) -> Iterator:
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
325
|
+
attempts = 100
|
|
326
|
+
|
|
327
|
+
try_left = (item for item in take(attempts, generate_false(predicate.left)) if not predicate.right(item))
|
|
328
|
+
try_right = (item for item in take(attempts, generate_false(predicate.right)) if not predicate.left(item))
|
|
329
|
+
|
|
330
|
+
range_1 = (item for item in generate_false(predicate.left) if not predicate.right(item)) if try_left else ()
|
|
331
|
+
range_2 = (item for item in generate_false(predicate.right) if not predicate.left(item)) if try_right else ()
|
|
332
|
+
|
|
333
|
+
if range_1 or range_2:
|
|
334
|
+
yield from random_first_from_iterables(range_1, range_2)
|
|
335
|
+
|
|
336
|
+
raise ValueError(f"Couldn't generate values that statisfy {predicate}")
|
|
337
337
|
|
|
338
338
|
|
|
339
339
|
@generate_false.register
|
|
@@ -354,9 +354,7 @@ def generate_list_of_p(list_of_predicate: ListOfPredicate, *, min_size: int = 1,
|
|
|
354
354
|
predicate = list_of_predicate.predicate
|
|
355
355
|
|
|
356
356
|
while True:
|
|
357
|
-
|
|
358
|
-
# TODO: generate mix of both false (at least 1) and true
|
|
359
|
-
yield take(length, generate_false(predicate))
|
|
357
|
+
yield list(generate_at_least_one_false(predicate, min_size=min_size, max_size=max_size))
|
|
360
358
|
|
|
361
359
|
|
|
362
360
|
@generate_false.register
|
|
@@ -368,12 +366,14 @@ def generate_tuple_of_p(tuple_of_predicate: TupleOfPredicate) -> Iterator:
|
|
|
368
366
|
|
|
369
367
|
|
|
370
368
|
@generate_false.register
|
|
371
|
-
def generate_set_of_p(set_of_predicate: SetOfPredicate) -> Iterator:
|
|
369
|
+
def generate_set_of_p(set_of_predicate: SetOfPredicate, *, min_size: int = 1, max_size: int = 10) -> Iterator:
|
|
372
370
|
predicate = set_of_predicate.predicate
|
|
373
371
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
372
|
+
while True:
|
|
373
|
+
result = set(generate_at_least_one_false(predicate, min_size=min_size, max_size=max_size))
|
|
374
|
+
# This check is needed because {False, 0} and {True, 1} result in {False} and {True}
|
|
375
|
+
if not set_of_predicate(result):
|
|
376
|
+
yield result
|
|
377
377
|
|
|
378
378
|
|
|
379
379
|
@generate_false.register
|
|
@@ -390,3 +390,19 @@ def generate_xor(predicate: XorPredicate) -> Iterator:
|
|
|
390
390
|
left_and_right = (item for item in generate_true(predicate.left) if predicate.right(item))
|
|
391
391
|
|
|
392
392
|
yield from random_first_from_iterables(left_and_right, not_right_and_not_left)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def generate_at_least_one_false(predicate: Predicate, *, min_size: int, max_size: int) -> tuple:
|
|
396
|
+
from predicate import generate_true
|
|
397
|
+
|
|
398
|
+
length = random.randint(min_size, max_size)
|
|
399
|
+
|
|
400
|
+
nr_false_values = random.randint(1, length) if length > 1 else 1
|
|
401
|
+
nr_true_values = length - nr_false_values
|
|
402
|
+
|
|
403
|
+
false_values = take(nr_false_values, generate_false(predicate))
|
|
404
|
+
true_values = take(nr_true_values, generate_true(predicate))
|
|
405
|
+
|
|
406
|
+
combined_values = false_values + true_values
|
|
407
|
+
|
|
408
|
+
return random_permutation(combined_values)
|