py-predicate 0.4__tar.gz → 0.6__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 (68) hide show
  1. {py_predicate-0.4 → py_predicate-0.6}/PKG-INFO +3 -3
  2. {py_predicate-0.4 → py_predicate-0.6}/README.md +2 -2
  3. {py_predicate-0.4 → py_predicate-0.6}/predicate/__init__.py +65 -25
  4. py_predicate-0.6/predicate/all_predicate.py +25 -0
  5. py_predicate-0.6/predicate/any_predicate.py +21 -0
  6. {py_predicate-0.4 → py_predicate-0.6}/predicate/comp_predicate.py +5 -1
  7. py_predicate-0.6/predicate/dict_of_predicate.py +51 -0
  8. py_predicate-0.6/predicate/eq_predicate.py +21 -0
  9. py_predicate-0.6/predicate/explain.py +7 -0
  10. py_predicate-0.6/predicate/fn_predicate.py +21 -0
  11. {py_predicate-0.4 → py_predicate-0.6}/predicate/formatter/format_dot.py +78 -57
  12. {py_predicate-0.4 → py_predicate-0.6}/predicate/formatter/format_json.py +2 -2
  13. py_predicate-0.6/predicate/ge_predicate.py +21 -0
  14. py_predicate-0.6/predicate/generator/generate_false.py +351 -0
  15. py_predicate-0.6/predicate/generator/generate_true.py +411 -0
  16. {py_predicate-0.4 → py_predicate-0.6}/predicate/generator/helpers.py +45 -11
  17. py_predicate-0.6/predicate/gt_predicate.py +21 -0
  18. {py_predicate-0.4 → py_predicate-0.6}/predicate/has_key_predicate.py +6 -5
  19. py_predicate-0.6/predicate/has_length_predicate.py +23 -0
  20. py_predicate-0.6/predicate/implies.py +109 -0
  21. py_predicate-0.6/predicate/ip_address_predicates.py +47 -0
  22. py_predicate-0.6/predicate/is_empty_predicate.py +41 -0
  23. {py_predicate-0.4 → py_predicate-0.6}/predicate/is_instance_predicate.py +5 -3
  24. py_predicate-0.6/predicate/is_none_predicate.py +19 -0
  25. py_predicate-0.6/predicate/is_not_none_predicate.py +19 -0
  26. py_predicate-0.6/predicate/le_predicate.py +21 -0
  27. py_predicate-0.6/predicate/list_of_predicate.py +35 -0
  28. py_predicate-0.6/predicate/lt_predicate.py +21 -0
  29. {py_predicate-0.4 → py_predicate-0.6}/predicate/named_predicate.py +0 -3
  30. py_predicate-0.6/predicate/ne_predicate.py +21 -0
  31. {py_predicate-0.4 → py_predicate-0.6}/predicate/negate.py +2 -1
  32. {py_predicate-0.4 → py_predicate-0.6}/predicate/optimizer/all_optimizer.py +3 -3
  33. {py_predicate-0.4 → py_predicate-0.6}/predicate/optimizer/and_optimizer.py +13 -22
  34. {py_predicate-0.4 → py_predicate-0.6}/predicate/optimizer/any_optimizer.py +2 -2
  35. py_predicate-0.6/predicate/optimizer/in_optimizer.py +26 -0
  36. {py_predicate-0.4 → py_predicate-0.6}/predicate/optimizer/or_optimizer.py +13 -10
  37. {py_predicate-0.4 → py_predicate-0.6}/predicate/optimizer/predicate_optimizer.py +7 -35
  38. {py_predicate-0.4 → py_predicate-0.6}/predicate/optimizer/xor_optimizer.py +7 -2
  39. py_predicate-0.6/predicate/predicate.py +276 -0
  40. py_predicate-0.6/predicate/property_predicate.py +28 -0
  41. {py_predicate-0.4 → py_predicate-0.6}/predicate/range_predicate.py +20 -12
  42. py_predicate-0.6/predicate/set_of_predicate.py +25 -0
  43. py_predicate-0.6/predicate/set_predicates.py +120 -0
  44. {py_predicate-0.4 → py_predicate-0.6}/predicate/standard_predicates.py +32 -35
  45. py_predicate-0.6/predicate/str_predicates.py +56 -0
  46. {py_predicate-0.4 → py_predicate-0.6}/predicate/this_predicate.py +3 -0
  47. py_predicate-0.6/predicate/tuple_of_predicate.py +32 -0
  48. {py_predicate-0.4 → py_predicate-0.6}/pyproject.toml +5 -2
  49. py_predicate-0.4/predicate/all_predicate.py +0 -20
  50. py_predicate-0.4/predicate/any_predicate.py +0 -20
  51. py_predicate-0.4/predicate/generator/generate_false.py +0 -124
  52. py_predicate-0.4/predicate/generator/generate_true.py +0 -241
  53. py_predicate-0.4/predicate/implies.py +0 -64
  54. py_predicate-0.4/predicate/optimizer/in_optimizer.py +0 -13
  55. py_predicate-0.4/predicate/optimizer/rules.py +0 -71
  56. py_predicate-0.4/predicate/predicate.py +0 -427
  57. {py_predicate-0.4 → py_predicate-0.6}/predicate/constructor/__init__.py +0 -0
  58. {py_predicate-0.4 → py_predicate-0.6}/predicate/constructor/construct.py +0 -0
  59. {py_predicate-0.4 → py_predicate-0.6}/predicate/formatter/__init__.py +0 -0
  60. {py_predicate-0.4 → py_predicate-0.6}/predicate/generator/__init__.py +0 -0
  61. {py_predicate-0.4 → py_predicate-0.6}/predicate/lazy_predicate.py +0 -0
  62. {py_predicate-0.4 → py_predicate-0.6}/predicate/optimizer/__init__.py +0 -0
  63. {py_predicate-0.4 → py_predicate-0.6}/predicate/optimizer/not_optimizer.py +0 -0
  64. {py_predicate-0.4 → py_predicate-0.6}/predicate/parser.py +0 -0
  65. {py_predicate-0.4 → py_predicate-0.6}/predicate/regex_predicate.py +0 -0
  66. {py_predicate-0.4 → py_predicate-0.6}/predicate/root_predicate.py +0 -0
  67. {py_predicate-0.4 → py_predicate-0.6}/predicate/tee_predicate.py +0 -0
  68. {py_predicate-0.4 → py_predicate-0.6}/predicate/truth_table.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: py_predicate
3
- Version: 0.4
3
+ Version: 0.6
4
4
  Summary: Module to create composable predicates
5
5
  Author-email: Maurits Rijk <maurits.rijk@gmail.com>
6
6
  Requires-Python: >=3.10
@@ -91,9 +91,9 @@ either a string, or a list of data that can again either be a string or a list o
91
91
  data. Ad infinitum.
92
92
 
93
93
  ```python
94
- from predicate import all_p, is_list_p, is_str_p, lazy_p
94
+ from predicate import all_p, is_list_p, is_str_p, root_p
95
95
 
96
- str_or_list_of_str = is_str_p | (is_list_p & all_p(lazy_p("str_or_list_of_str")))
96
+ str_or_list_of_str = is_str_p | (is_list_p & all_p(root_p))
97
97
  ```
98
98
 
99
99
  Using plain Python, the above one-liner would have to be coded as a (recursive) function.
@@ -48,9 +48,9 @@ either a string, or a list of data that can again either be a string or a list o
48
48
  data. Ad infinitum.
49
49
 
50
50
  ```python
51
- from predicate import all_p, is_list_p, is_str_p, lazy_p
51
+ from predicate import all_p, is_list_p, is_str_p, root_p
52
52
 
53
- str_or_list_of_str = is_str_p | (is_list_p & all_p(lazy_p("str_or_list_of_str")))
53
+ str_or_list_of_str = is_str_p | (is_list_p & all_p(root_p))
54
54
  ```
55
55
 
56
56
  Using plain Python, the above one-liner would have to be coded as a (recursive) function.
@@ -4,34 +4,41 @@ __version__ = "0.0.1"
4
4
 
5
5
  from predicate.all_predicate import AllPredicate
6
6
  from predicate.any_predicate import AnyPredicate
7
+ from predicate.eq_predicate import EqPredicate
8
+ from predicate.explain import explain
7
9
  from predicate.formatter.format_dot import to_dot
8
10
  from predicate.formatter.format_json import to_json
11
+ from predicate.ge_predicate import GePredicate
9
12
  from predicate.generator.generate_false import generate_false
10
13
  from predicate.generator.generate_true import generate_true
14
+ from predicate.gt_predicate import GtPredicate
15
+ from predicate.is_empty_predicate import IsEmptyPredicate, IsNotEmptyPredicate, is_empty_p, is_not_empty_p
16
+ from predicate.is_none_predicate import IsNonePredicate
17
+ from predicate.is_not_none_predicate import IsNotNonePredicate
18
+ from predicate.le_predicate import LePredicate
19
+ from predicate.lt_predicate import LtPredicate
20
+ from predicate.ne_predicate import NePredicate
11
21
  from predicate.optimizer.predicate_optimizer import can_optimize, optimize
12
22
  from predicate.predicate import (
13
23
  AlwaysFalsePredicate,
14
24
  AlwaysTruePredicate,
15
25
  AndPredicate,
16
- EqPredicate,
17
- FnPredicate,
18
- GePredicate,
19
- GtPredicate,
20
- InPredicate,
21
- IsEmptyPredicate,
22
- IsNonePredicate,
23
- IsNotNonePredicate,
24
- LePredicate,
25
- LtPredicate,
26
- NePredicate,
27
- NotInPredicate,
28
26
  NotPredicate,
29
27
  OrPredicate,
30
28
  Predicate,
31
29
  XorPredicate,
32
30
  always_false_p,
33
31
  always_true_p,
34
- is_empty_p,
32
+ )
33
+ from predicate.set_predicates import (
34
+ InPredicate,
35
+ NotInPredicate,
36
+ in_p,
37
+ is_real_subset_p,
38
+ is_real_superset_p,
39
+ is_subset_p,
40
+ is_superset_p,
41
+ not_in_p,
35
42
  )
36
43
  from predicate.standard_predicates import (
37
44
  all_p,
@@ -47,12 +54,14 @@ from predicate.standard_predicates import (
47
54
  gt_le_p,
48
55
  gt_lt_p,
49
56
  gt_p,
50
- in_p,
57
+ has_key_p,
58
+ has_length_p,
51
59
  is_bool_p,
52
60
  is_callable_p,
53
61
  is_complex_p,
54
62
  is_container_p,
55
63
  is_datetime_p,
64
+ is_dict_of_p,
56
65
  is_dict_p,
57
66
  is_falsy_p,
58
67
  is_finite_p,
@@ -65,9 +74,11 @@ from predicate.standard_predicates import (
65
74
  is_iterable_p,
66
75
  is_list_of_p,
67
76
  is_list_p,
77
+ is_nan_p,
68
78
  is_none_p,
69
79
  is_not_none_p,
70
80
  is_predicate_p,
81
+ is_range_p,
71
82
  is_set_of_p,
72
83
  is_set_p,
73
84
  is_str_p,
@@ -80,7 +91,6 @@ from predicate.standard_predicates import (
80
91
  lt_p,
81
92
  ne_p,
82
93
  neg_p,
83
- not_in_p,
84
94
  pos_p,
85
95
  regex_p,
86
96
  root_p,
@@ -90,21 +100,21 @@ from predicate.standard_predicates import (
90
100
  )
91
101
 
92
102
  __all__ = [
103
+ "AllPredicate",
93
104
  "AlwaysFalsePredicate",
94
105
  "AlwaysTruePredicate",
95
- "AllPredicate",
96
106
  "AndPredicate",
97
107
  "AnyPredicate",
98
108
  "EqPredicate",
99
- "FnPredicate",
100
109
  "GePredicate",
101
110
  "GtPredicate",
111
+ "LePredicate",
112
+ "LtPredicate",
102
113
  "InPredicate",
103
114
  "IsEmptyPredicate",
104
115
  "IsNonePredicate",
116
+ "IsNotEmptyPredicate",
105
117
  "IsNotNonePredicate",
106
- "LePredicate",
107
- "LtPredicate",
108
118
  "NePredicate",
109
119
  "NotInPredicate",
110
120
  "NotPredicate",
@@ -120,7 +130,7 @@ __all__ = [
120
130
  "eq_false_p",
121
131
  "eq_p",
122
132
  "eq_true_p",
123
- "is_falsy_p",
133
+ "explain",
124
134
  "fn_p",
125
135
  "ge_le_p",
126
136
  "ge_lt_p",
@@ -130,33 +140,52 @@ __all__ = [
130
140
  "gt_le_p",
131
141
  "gt_lt_p",
132
142
  "gt_p",
143
+ "has_key_p",
144
+ "has_length_p",
133
145
  "in_p",
146
+ "is_alnum_p",
147
+ "is_alpha_p",
148
+ "is_ascii_p",
134
149
  "is_bool_p",
135
150
  "is_callable_p",
136
151
  "is_complex_p",
137
152
  "is_container_p",
138
153
  "is_datetime_p",
154
+ "is_decimal_p",
155
+ "is_dict_of_p",
139
156
  "is_dict_p",
140
157
  "is_empty_p",
158
+ "is_falsy_p",
141
159
  "is_finite_p",
142
160
  "is_float_p",
143
161
  "is_hashable_p",
144
- "is_instance_p",
162
+ "is_identifier_p",
145
163
  "is_inf_p",
164
+ "is_instance_p",
146
165
  "is_int_p",
147
- "is_iterable_p",
148
166
  "is_iterable_of_p",
149
- "is_list_p",
167
+ "is_iterable_p",
150
168
  "is_list_of_p",
169
+ "is_list_p",
170
+ "is_lower_p",
171
+ "is_nan_p",
151
172
  "is_none_p",
173
+ "is_not_empty_p",
152
174
  "is_not_none_p",
153
175
  "is_predicate_p",
154
- "is_set_p",
176
+ "is_range_p",
155
177
  "is_set_of_p",
178
+ "is_set_p",
156
179
  "is_str_p",
180
+ "is_subset_p",
181
+ "is_superset_p",
182
+ "is_real_subset_p",
183
+ "is_real_superset_p",
184
+ "is_title_p",
157
185
  "is_truthy_p",
158
- "is_tuple_p",
159
186
  "is_tuple_of_p",
187
+ "is_tuple_p",
188
+ "is_upper_p",
160
189
  "is_uuid_p",
161
190
  "lazy_p",
162
191
  "le_p",
@@ -174,3 +203,14 @@ __all__ = [
174
203
  "to_json",
175
204
  "zero_p",
176
205
  ]
206
+
207
+ from predicate.str_predicates import (
208
+ is_alnum_p,
209
+ is_alpha_p,
210
+ is_ascii_p,
211
+ is_decimal_p,
212
+ is_identifier_p,
213
+ is_lower_p,
214
+ is_title_p,
215
+ is_upper_p,
216
+ )
@@ -0,0 +1,25 @@
1
+ from dataclasses import dataclass
2
+ from typing import Iterable, override
3
+
4
+ from more_itertools import first
5
+
6
+ from predicate.predicate import Predicate
7
+
8
+
9
+ @dataclass
10
+ class AllPredicate[T](Predicate[T]):
11
+ """A predicate class that models the 'all' predicate."""
12
+
13
+ predicate: Predicate[T]
14
+
15
+ def __call__(self, iterable: Iterable[T]) -> bool:
16
+ return all(self.predicate(x) for x in iterable)
17
+
18
+ def __repr__(self) -> str:
19
+ return f"all({repr(self.predicate)})"
20
+
21
+ @override
22
+ def explain_failure(self, iterable: Iterable[T]) -> dict:
23
+ fail = first(item for item in iterable if not self.predicate(item))
24
+
25
+ return {"result": False, "reason": f"Item '{fail}' didn't match predicate {repr(self.predicate)}"}
@@ -0,0 +1,21 @@
1
+ from dataclasses import dataclass
2
+ from typing import Iterable, override
3
+
4
+ from predicate.predicate import Predicate
5
+
6
+
7
+ @dataclass
8
+ class AnyPredicate[T](Predicate[T]):
9
+ """A predicate class that models the 'any' predicate."""
10
+
11
+ predicate: Predicate[T]
12
+
13
+ def __call__(self, iterable: Iterable[T]) -> bool:
14
+ return any(self.predicate(x) for x in iterable)
15
+
16
+ def __repr__(self) -> str:
17
+ return f"any({repr(self.predicate)})"
18
+
19
+ @override
20
+ def explain_failure(self, iterable: Iterable[T]) -> dict:
21
+ return {"result": False, "reason": f"No item matches predicate {repr(self.predicate)}"}
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass
2
- from typing import Callable
2
+ from typing import Callable, override
3
3
 
4
4
  from predicate.predicate import Predicate
5
5
 
@@ -16,3 +16,7 @@ class CompPredicate[S, T](Predicate[T]):
16
16
 
17
17
  def __repr__(self) -> str:
18
18
  return f"comp_p({repr(self.predicate)})"
19
+
20
+ @override
21
+ def explain_failure(self, x: S) -> dict:
22
+ return {"result": False, "predicate": self.predicate.explain(x)}
@@ -0,0 +1,51 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any, override
3
+
4
+ from predicate.predicate import Predicate
5
+
6
+
7
+ @dataclass
8
+ class DictOfPredicate[T](Predicate[T]):
9
+ """A predicate class that models the dict_of predicate."""
10
+
11
+ key_value_predicates: list[tuple[Predicate, Predicate]]
12
+
13
+ def __init__(self, key_value_predicates: list[tuple[Predicate | str, Predicate]]):
14
+ def to_key_p(key_p: Predicate | str) -> Predicate:
15
+ from predicate.standard_predicates import eq_p
16
+
17
+ match key_p:
18
+ case str(s):
19
+ return eq_p(s)
20
+ case _:
21
+ return key_p
22
+
23
+ self.key_value_predicates = [(to_key_p(key_p), value_p) for key_p, value_p in key_value_predicates]
24
+
25
+ def __call__(self, x: Any) -> bool:
26
+ if not isinstance(x, dict):
27
+ return False
28
+
29
+ if not x:
30
+ return False
31
+
32
+ # For all values, a predicate must be True
33
+ for key, value in x.items():
34
+ if not any(key_p(key) and value_p(value) for key_p, value_p in self.key_value_predicates):
35
+ return False
36
+
37
+ # All predicates must be True
38
+ for key_p, value_p in self.key_value_predicates:
39
+ if any(key_p(key) and not value_p(value) for key, value in x.items()):
40
+ return False
41
+
42
+ return True
43
+
44
+ def __repr__(self) -> str:
45
+ # TODO: show predicates
46
+ return "is_dict_of_p"
47
+
48
+ @override
49
+ def explain_failure(self, x: Any) -> dict:
50
+ # TODO: finish
51
+ return {"result": False, "key_value_predicates": []}
@@ -0,0 +1,21 @@
1
+ from dataclasses import dataclass
2
+ from typing import override
3
+
4
+ from predicate.predicate import Predicate
5
+
6
+
7
+ @dataclass
8
+ class EqPredicate[T](Predicate[T]):
9
+ """A predicate class that models the 'eq' (=) predicate."""
10
+
11
+ v: T
12
+
13
+ def __call__(self, x: T) -> bool:
14
+ return x == self.v
15
+
16
+ def __repr__(self) -> str:
17
+ return f"eq_p({self.v})"
18
+
19
+ @override
20
+ def explain_failure(self, x: T) -> dict:
21
+ return {"result": False, "reason": f"{x} is not equal to {self.v}"}
@@ -0,0 +1,7 @@
1
+ from typing import Any
2
+
3
+ from predicate.predicate import Predicate
4
+
5
+
6
+ def explain(predicate: Predicate, value: Any) -> dict:
7
+ return predicate.explain(value)
@@ -0,0 +1,21 @@
1
+ from dataclasses import dataclass
2
+ from typing import Callable, override
3
+
4
+ from predicate.predicate import Predicate
5
+
6
+
7
+ @dataclass
8
+ class FnPredicate[T](Predicate[T]):
9
+ """A predicate class that can hold a function."""
10
+
11
+ predicate_fn: Callable[[T], bool]
12
+
13
+ def __call__(self, x: T) -> bool:
14
+ return self.predicate_fn(x)
15
+
16
+ def __repr__(self) -> str:
17
+ return "fn_p"
18
+
19
+ @override
20
+ def explain_failure(self, x: T) -> dict:
21
+ return {"result": False, "reason": f"Function returned False for value {x}"}
@@ -8,9 +8,18 @@ from more_itertools import first
8
8
  from predicate.all_predicate import AllPredicate
9
9
  from predicate.any_predicate import AnyPredicate
10
10
  from predicate.comp_predicate import CompPredicate
11
+ from predicate.dict_of_predicate import DictOfPredicate
12
+ from predicate.eq_predicate import EqPredicate
13
+ from predicate.fn_predicate import FnPredicate
14
+ from predicate.ge_predicate import GePredicate
15
+ from predicate.gt_predicate import GtPredicate
11
16
  from predicate.is_instance_predicate import IsInstancePredicate
17
+ from predicate.is_none_predicate import IsNonePredicate
12
18
  from predicate.lazy_predicate import LazyPredicate, find_predicate_by_ref
19
+ from predicate.le_predicate import LePredicate
20
+ from predicate.lt_predicate import LtPredicate
13
21
  from predicate.named_predicate import NamedPredicate
22
+ from predicate.ne_predicate import NePredicate
14
23
  from predicate.optimizer.predicate_optimizer import optimize
15
24
  from predicate.predicate import (
16
25
  AlwaysFalsePredicate,
@@ -25,25 +34,24 @@ from predicate.predicate import (
25
34
  )
26
35
  from predicate.range_predicate import GeLePredicate, GeLtPredicate, GtLePredicate, GtLtPredicate
27
36
  from predicate.root_predicate import RootPredicate, find_root_predicate
28
- from predicate.standard_predicates import (
29
- EqPredicate,
30
- FnPredicate,
31
- GePredicate,
32
- GtPredicate,
37
+ from predicate.set_predicates import (
33
38
  InPredicate,
34
- IsNonePredicate,
35
- LePredicate,
36
- LtPredicate,
37
- NePredicate,
39
+ IsRealSubsetPredicate,
40
+ IsRealSupersetPredicate,
41
+ IsSubsetPredicate,
42
+ IsSupersetPredicate,
38
43
  NotInPredicate,
39
44
  )
40
45
  from predicate.tee_predicate import TeePredicate
41
46
  from predicate.this_predicate import ThisPredicate, find_this_predicate
47
+ from predicate.tuple_of_predicate import TupleOfPredicate
42
48
 
43
49
 
44
- def to_dot(predicate: Predicate, predicate_string: str = "", show_optimized: bool = False):
50
+ def to_dot(predicate: Predicate, predicate_string: str | None = None, show_optimized: bool = False):
45
51
  """Format predicate as a .dot file."""
46
- graph_attr = {"label": predicate_string, "labelloc": "t"}
52
+ label = predicate_string if predicate_string else repr(predicate)
53
+
54
+ graph_attr = {"label": label, "labelloc": "t"}
47
55
 
48
56
  node_attr = {"shape": "rectangle", "style": "filled", "fillcolor": "#B7D7A8"}
49
57
 
@@ -61,46 +69,53 @@ def to_dot(predicate: Predicate, predicate_string: str = "", show_optimized: boo
61
69
  return dot
62
70
 
63
71
 
64
- def render(dot, predicate: Predicate, node_nr):
72
+ def set_to_str(v: set) -> str:
73
+ # TODO: truncate if too many items.
74
+ items = ", ".join(str(item) for item in v)
75
+ return f"{{{items}}}"
76
+
77
+
78
+ def render(dot, predicate: Predicate, node_nr: count):
65
79
  node_predicate_mapping: dict[str, Predicate] = {}
66
80
 
67
- def _add_node(name: str, *, label: str, predicate: Predicate):
81
+ def _add_node(name: str, *, label: str, predicate: Predicate | None) -> str:
68
82
  node = next(node_nr)
69
83
  unique_name = f"{name}_{node}"
70
84
  dot.node(unique_name, label=label)
71
- node_predicate_mapping[unique_name] = predicate
85
+ if predicate:
86
+ node_predicate_mapping[unique_name] = predicate
72
87
  return unique_name
73
88
 
89
+ def _add_node_left_right(name: str, *, label: str, predicate: Predicate, left: Predicate, right: Predicate) -> str:
90
+ node = _add_node(name, label=label, predicate=predicate)
91
+ dot.edge(node, to_value(left))
92
+ dot.edge(node, to_value(right))
93
+
94
+ return node
95
+
96
+ def _add_node_with_child(name: str, *, label: str, predicate: Predicate, child: Predicate) -> str:
97
+ node = _add_node(name, label=label, predicate=predicate)
98
+ dot.edge(node, to_value(child))
99
+ return node
100
+
74
101
  def to_value(predicate: Predicate):
75
102
  add_node = partial(_add_node, predicate=predicate)
103
+ add_node_left_right = partial(_add_node_left_right, predicate=predicate)
104
+ add_node_with_child = partial(_add_node_with_child, predicate=predicate)
76
105
 
77
106
  match predicate:
78
107
  case AllPredicate(all_predicate):
79
- node = add_node("all", label="∀")
80
- child = to_value(all_predicate)
81
- dot.edge(node, child)
82
- return node
108
+ return add_node_with_child("all", label="∀", child=all_predicate)
83
109
  case AlwaysFalsePredicate():
84
110
  return add_node("F", label="false")
85
111
  case AlwaysTruePredicate():
86
112
  return add_node("T", label="true")
87
113
  case AndPredicate(left, right):
88
- node = add_node("and", label="∧")
89
- left_node = to_value(left)
90
- right_node = to_value(right)
91
- dot.edge(node, left_node)
92
- dot.edge(node, right_node)
93
- return node
114
+ return add_node_left_right("and", label="∧", left=left, right=right)
94
115
  case AnyPredicate(any_predicate):
95
- node = add_node("any", label="∃")
96
- child = to_value(any_predicate)
97
- dot.edge(node, child)
98
- return node
116
+ return add_node_with_child("any", label="∃", child=any_predicate)
99
117
  case CompPredicate(_fn, comp_predicate):
100
- node = add_node("comp", label="f")
101
- child = to_value(comp_predicate)
102
- dot.edge(node, child)
103
- return node
118
+ return add_node_with_child("comp", label="f", child=comp_predicate)
104
119
  case EqPredicate(v):
105
120
  return add_node("eq", label=f"x = {v}")
106
121
  case IsFalsyPredicate():
@@ -119,17 +134,32 @@ def render(dot, predicate: Predicate, node_nr):
119
134
  case GtPredicate(v):
120
135
  return add_node("gt", label=f"x > {v}")
121
136
  case GtLePredicate(upper, lower):
122
- return add_node("gele", label=f"{lower} ≤ x ≤ {upper}")
137
+ return add_node("gtle", label=f"{lower} ≤ x ≤ {upper}")
123
138
  case GtLtPredicate(upper, lower):
124
- return add_node("gelt", label=f"{lower} ≤ x < {upper}")
139
+ return add_node("gtlt", label=f"{lower} ≤ x < {upper}")
125
140
  case InPredicate(v):
126
- items = ", ".join(str(item) for item in v)
127
- return add_node("in", label=f"x ∈ {{{items}}}")
141
+ return add_node("in", label=f"x {set_to_str(v)}")
142
+ case DictOfPredicate(key_value_predicates):
143
+ node = add_node("dict_of", label="is_dict_of")
144
+ for key, value in key_value_predicates:
145
+ kv = _add_node("kv", label="kv", predicate=None)
146
+ dot.edge(node, kv)
147
+ dot.edge(kv, to_value(key), label="key")
148
+ dot.edge(kv, to_value(value), label="value")
149
+ return node
128
150
  case IsInstancePredicate(klass):
129
151
  name = klass[0].__name__ # type: ignore
130
152
  return add_node("instance", label=f"is_{name}_p")
131
153
  case IsNonePredicate():
132
154
  return add_node("none", label="x = None")
155
+ case IsRealSubsetPredicate(v):
156
+ return add_node("real_subset", label=f"x ⊂ {set_to_str(v)}")
157
+ case IsSubsetPredicate(v):
158
+ return add_node("subset", label=f"x ⊆ {set_to_str(v)}")
159
+ case IsRealSupersetPredicate(v):
160
+ return add_node("real_superset", label=f"x ⊃ {set_to_str(v)}")
161
+ case IsSupersetPredicate(v):
162
+ return add_node("superset", label=f"x ⊇ {set_to_str(v)}")
133
163
  case LazyPredicate(ref):
134
164
  return add_node("lazy", label=ref)
135
165
  case LePredicate(v):
@@ -139,35 +169,26 @@ def render(dot, predicate: Predicate, node_nr):
139
169
  case NamedPredicate(name):
140
170
  return add_node("named", label=name)
141
171
  case NotInPredicate(v):
142
- items = ", ".join(str(item) for item in v)
143
- return add_node("in", label=f"x ∉ {{{items}}}")
172
+ return add_node("in", label=f"x {set_to_str(v)}")
144
173
  case NePredicate(v):
145
174
  return add_node("ne", label=f"x ≠ {v}")
146
175
  case NotPredicate(not_predicate):
147
- child = to_value(not_predicate)
148
- node = add_node("not", label="¬")
149
- dot.edge(node, child)
150
- return node
176
+ return add_node_with_child("not", label="¬", child=not_predicate)
151
177
  case OrPredicate(left, right):
152
- node = add_node("or", label="∨")
153
- left_node = to_value(left)
154
- right_node = to_value(right)
155
- dot.edge(node, left_node)
156
- dot.edge(node, right_node)
157
- return node
178
+ return add_node_left_right("or", label="∨", left=left, right=right)
158
179
  case RootPredicate():
159
180
  return add_node("root", label="root")
160
181
  case TeePredicate():
161
182
  return add_node("tee", label="tee")
162
183
  case ThisPredicate():
163
184
  return add_node("this", label="this")
164
- case XorPredicate(left, right):
165
- node = add_node("xor", label="")
166
- left_node = to_value(left)
167
- right_node = to_value(right)
168
- dot.edge(node, left_node)
169
- dot.edge(node, right_node)
185
+ case TupleOfPredicate(predicates):
186
+ node = add_node("tuple_of", label="is_tuple_of")
187
+ for tuple_predicate in predicates:
188
+ dot.edge(node, to_value(tuple_predicate))
170
189
  return node
190
+ case XorPredicate(left, right):
191
+ return add_node_left_right("xor", label="⊻", left=left, right=right)
171
192
  case _:
172
193
  raise ValueError(f"Unknown predicate type {predicate}")
173
194
 
@@ -176,7 +197,7 @@ def render(dot, predicate: Predicate, node_nr):
176
197
  render_lazy_references(dot, node_predicate_mapping)
177
198
 
178
199
 
179
- def render_lazy_references(dot, node_predicate_mapping):
200
+ def render_lazy_references(dot, node_predicate_mapping) -> None:
180
201
  def find_in_mapping(lookup: Predicate) -> str:
181
202
  return first(node for node, predicate in node_predicate_mapping.items() if predicate == lookup)
182
203
 
@@ -199,14 +220,14 @@ def render_lazy_references(dot, node_predicate_mapping):
199
220
  add_dashed_line(node, this)
200
221
 
201
222
 
202
- def render_original(dot, predicate: Predicate, node_nr):
223
+ def render_original(dot, predicate: Predicate, node_nr) -> None:
203
224
  with dot.subgraph(name="cluster_original") as original:
204
225
  original.attr(style="filled", color="lightgrey")
205
226
  original.attr(label="Original predicate")
206
227
  render(original, predicate, node_nr)
207
228
 
208
229
 
209
- def render_optimized(dot, predicate: Predicate, node_nr):
230
+ def render_optimized(dot, predicate: Predicate, node_nr) -> None:
210
231
  optimized_predicate = optimize(predicate)
211
232
 
212
233
  with dot.subgraph(name="cluster_optimized") as optimized:
@@ -2,15 +2,15 @@ from typing import Any
2
2
 
3
3
  from predicate.all_predicate import AllPredicate
4
4
  from predicate.any_predicate import AnyPredicate
5
+ from predicate.fn_predicate import FnPredicate
5
6
  from predicate.named_predicate import NamedPredicate
7
+ from predicate.ne_predicate import NePredicate
6
8
  from predicate.predicate import (
7
9
  AlwaysFalsePredicate,
8
10
  AlwaysTruePredicate,
9
11
  AndPredicate,
10
- FnPredicate,
11
12
  IsFalsyPredicate,
12
13
  IsTruthyPredicate,
13
- NePredicate,
14
14
  NotPredicate,
15
15
  OrPredicate,
16
16
  Predicate,
@@ -0,0 +1,21 @@
1
+ from dataclasses import dataclass
2
+ from typing import override
3
+
4
+ from predicate.predicate import ConstrainedT, Predicate
5
+
6
+
7
+ @dataclass
8
+ class GePredicate[T](Predicate[T]):
9
+ """A predicate class that models the 'ge' (>=) predicate."""
10
+
11
+ v: ConstrainedT
12
+
13
+ def __call__(self, x: T) -> bool:
14
+ return x >= self.v
15
+
16
+ def __repr__(self) -> str:
17
+ return f"ge_p({self.v})"
18
+
19
+ @override
20
+ def explain_failure(self, x: T) -> dict:
21
+ return {"result": False, "reason": f"{x} is not greater or equal to {self.v}"}