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.
Files changed (78) hide show
  1. {py_predicate-0.7 → py_predicate-0.9}/PKG-INFO +1 -1
  2. {py_predicate-0.7 → py_predicate-0.9}/predicate/__init__.py +11 -1
  3. {py_predicate-0.7 → py_predicate-0.9}/predicate/all_predicate.py +6 -2
  4. {py_predicate-0.7 → py_predicate-0.9}/predicate/always_false_predicate.py +5 -1
  5. {py_predicate-0.7 → py_predicate-0.9}/predicate/always_true_predicate.py +5 -1
  6. {py_predicate-0.7 → py_predicate-0.9}/predicate/any_predicate.py +6 -2
  7. {py_predicate-0.7 → py_predicate-0.9}/predicate/comp_predicate.py +1 -1
  8. py_predicate-0.9/predicate/constructor/construct.py +84 -0
  9. py_predicate-0.9/predicate/constructor/helpers.py +48 -0
  10. py_predicate-0.9/predicate/constructor/mutate.py +78 -0
  11. {py_predicate-0.7 → py_predicate-0.9}/predicate/eq_predicate.py +6 -2
  12. {py_predicate-0.7 → py_predicate-0.9}/predicate/fn_predicate.py +7 -1
  13. {py_predicate-0.7 → py_predicate-0.9}/predicate/formatter/helpers.py +0 -1
  14. {py_predicate-0.7 → py_predicate-0.9}/predicate/ge_predicate.py +6 -2
  15. {py_predicate-0.7 → py_predicate-0.9}/predicate/generator/generate_false.py +56 -40
  16. py_predicate-0.9/predicate/generator/generate_predicate.py +188 -0
  17. {py_predicate-0.7 → py_predicate-0.9}/predicate/generator/generate_true.py +30 -22
  18. {py_predicate-0.7 → py_predicate-0.9}/predicate/generator/helpers.py +74 -26
  19. {py_predicate-0.7 → py_predicate-0.9}/predicate/gt_predicate.py +6 -2
  20. {py_predicate-0.7 → py_predicate-0.9}/predicate/has_length_predicate.py +4 -4
  21. {py_predicate-0.7 → py_predicate-0.9}/predicate/ip_address_predicates.py +3 -3
  22. {py_predicate-0.7 → py_predicate-0.9}/predicate/is_instance_predicate.py +8 -4
  23. py_predicate-0.9/predicate/is_predicate_of_p.py +30 -0
  24. {py_predicate-0.7 → py_predicate-0.9}/predicate/le_predicate.py +6 -2
  25. {py_predicate-0.7 → py_predicate-0.9}/predicate/list_of_predicate.py +4 -0
  26. {py_predicate-0.7 → py_predicate-0.9}/predicate/lt_predicate.py +6 -2
  27. py_predicate-0.9/predicate/match_predicate.py +119 -0
  28. {py_predicate-0.7 → py_predicate-0.9}/predicate/ne_predicate.py +6 -2
  29. {py_predicate-0.7 → py_predicate-0.9}/predicate/negate.py +10 -10
  30. {py_predicate-0.7 → py_predicate-0.9}/predicate/optimizer/all_optimizer.py +2 -2
  31. {py_predicate-0.7 → py_predicate-0.9}/predicate/optimizer/or_optimizer.py +14 -2
  32. {py_predicate-0.7 → py_predicate-0.9}/predicate/parser.py +18 -3
  33. {py_predicate-0.7 → py_predicate-0.9}/predicate/predicate.py +26 -2
  34. {py_predicate-0.7 → py_predicate-0.9}/predicate/range_predicate.py +28 -22
  35. {py_predicate-0.7 → py_predicate-0.9}/predicate/set_of_predicate.py +3 -0
  36. {py_predicate-0.7 → py_predicate-0.9}/predicate/set_predicates.py +17 -1
  37. {py_predicate-0.7 → py_predicate-0.9}/predicate/standard_predicates.py +26 -12
  38. {py_predicate-0.7 → py_predicate-0.9}/predicate/str_predicates.py +2 -3
  39. {py_predicate-0.7 → py_predicate-0.9}/pyproject.toml +1 -1
  40. py_predicate-0.7/predicate/constructor/construct.py +0 -60
  41. py_predicate-0.7/predicate/is_empty_predicate.py +0 -41
  42. {py_predicate-0.7 → py_predicate-0.9}/README.md +0 -0
  43. {py_predicate-0.7 → py_predicate-0.9}/predicate/constructor/__init__.py +0 -0
  44. {py_predicate-0.7 → py_predicate-0.9}/predicate/dict_of_predicate.py +0 -0
  45. {py_predicate-0.7 → py_predicate-0.9}/predicate/explain.py +0 -0
  46. {py_predicate-0.7 → py_predicate-0.9}/predicate/formatter/__init__.py +0 -0
  47. {py_predicate-0.7 → py_predicate-0.9}/predicate/formatter/format_dot.py +0 -0
  48. {py_predicate-0.7 → py_predicate-0.9}/predicate/formatter/format_json.py +0 -0
  49. {py_predicate-0.7 → py_predicate-0.9}/predicate/formatter/format_latex.py +0 -0
  50. {py_predicate-0.7 → py_predicate-0.9}/predicate/generator/__init__.py +0 -0
  51. {py_predicate-0.7 → py_predicate-0.9}/predicate/has_key_predicate.py +0 -0
  52. {py_predicate-0.7 → py_predicate-0.9}/predicate/has_path_predicate.py +0 -0
  53. {py_predicate-0.7 → py_predicate-0.9}/predicate/helpers.py +0 -0
  54. {py_predicate-0.7 → py_predicate-0.9}/predicate/implies.py +0 -0
  55. {py_predicate-0.7 → py_predicate-0.9}/predicate/implies_predicate.py +0 -0
  56. {py_predicate-0.7 → py_predicate-0.9}/predicate/in_predicate_predicate.py +0 -0
  57. {py_predicate-0.7 → py_predicate-0.9}/predicate/is_callable_predicate.py +0 -0
  58. {py_predicate-0.7 → py_predicate-0.9}/predicate/is_falsy_predicate.py +0 -0
  59. {py_predicate-0.7 → py_predicate-0.9}/predicate/is_lambda_predicate.py +0 -0
  60. {py_predicate-0.7 → py_predicate-0.9}/predicate/is_none_predicate.py +0 -0
  61. {py_predicate-0.7 → py_predicate-0.9}/predicate/is_not_none_predicate.py +0 -0
  62. {py_predicate-0.7 → py_predicate-0.9}/predicate/is_truthy_predicate.py +0 -0
  63. {py_predicate-0.7 → py_predicate-0.9}/predicate/lazy_predicate.py +0 -0
  64. {py_predicate-0.7 → py_predicate-0.9}/predicate/named_predicate.py +0 -0
  65. {py_predicate-0.7 → py_predicate-0.9}/predicate/optimizer/__init__.py +0 -0
  66. {py_predicate-0.7 → py_predicate-0.9}/predicate/optimizer/and_optimizer.py +0 -0
  67. {py_predicate-0.7 → py_predicate-0.9}/predicate/optimizer/any_optimizer.py +0 -0
  68. {py_predicate-0.7 → py_predicate-0.9}/predicate/optimizer/in_optimizer.py +0 -0
  69. {py_predicate-0.7 → py_predicate-0.9}/predicate/optimizer/not_optimizer.py +0 -0
  70. {py_predicate-0.7 → py_predicate-0.9}/predicate/optimizer/predicate_optimizer.py +0 -0
  71. {py_predicate-0.7 → py_predicate-0.9}/predicate/optimizer/xor_optimizer.py +0 -0
  72. {py_predicate-0.7 → py_predicate-0.9}/predicate/property_predicate.py +0 -0
  73. {py_predicate-0.7 → py_predicate-0.9}/predicate/regex_predicate.py +0 -0
  74. {py_predicate-0.7 → py_predicate-0.9}/predicate/root_predicate.py +0 -0
  75. {py_predicate-0.7 → py_predicate-0.9}/predicate/tee_predicate.py +0 -0
  76. {py_predicate-0.7 → py_predicate-0.9}/predicate/this_predicate.py +0 -0
  77. {py_predicate-0.7 → py_predicate-0.9}/predicate/truth_table.py +0 -0
  78. {py_predicate-0.7 → py_predicate-0.9}/predicate/tuple_of_predicate.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: py_predicate
3
- Version: 0.7
3
+ Version: 0.9
4
4
  Summary: Module to create composable predicates
5
5
  Author-email: Maurits Rijk <maurits.rijk@gmail.com>
6
6
  Requires-Python: >=3.10
@@ -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"all({repr(self.predicate)})"
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 {repr(self.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"any({repr(self.predicate)})"
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 {repr(self.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({repr(self.predicate)})"
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)
@@ -1,4 +1,3 @@
1
1
  def set_to_str(v: set) -> str:
2
- # TODO: truncate if too many items.
3
2
  items = ", ".join(str(item) for item in v)
4
3
  return f"{{{items}}}"
@@ -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, random_combination_with_replacement, random_permutation, take
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](_predicate: Predicate[T]) -> Iterator[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
- max_length = random.randint(1, 10)
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 generate_anys(~predicate)
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
- length = predicate.length
119
- yield from random_iterables(max_size=length - 1)
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
- iterable_1 = (item for item in generate_false(predicate.left) if not predicate.right(item))
335
- iterable_2 = (item for item in generate_false(predicate.right) if not predicate.left(item))
336
- yield from random_first_from_iterables(iterable_1, iterable_2)
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
- length = random.randint(min_size, max_size)
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
- values = take(10, generate_false(predicate))
375
-
376
- yield set(random_combination_with_replacement(values, 5))
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)