py-predicate 0.3__tar.gz → 0.5__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 (50) hide show
  1. {py_predicate-0.3 → py_predicate-0.5}/PKG-INFO +5 -3
  2. {py_predicate-0.3 → py_predicate-0.5}/README.md +2 -2
  3. {py_predicate-0.3 → py_predicate-0.5}/predicate/__init__.py +74 -13
  4. py_predicate-0.5/predicate/all_predicate.py +17 -0
  5. py_predicate-0.5/predicate/any_predicate.py +17 -0
  6. py_predicate-0.5/predicate/constructor/construct.py +49 -0
  7. {py_predicate-0.3 → py_predicate-0.5}/predicate/formatter/format_dot.py +65 -51
  8. {py_predicate-0.3 → py_predicate-0.5}/predicate/formatter/format_json.py +6 -3
  9. py_predicate-0.5/predicate/generator/__init__.py +4 -0
  10. py_predicate-0.5/predicate/generator/generate_false.py +176 -0
  11. py_predicate-0.5/predicate/generator/generate_true.py +285 -0
  12. py_predicate-0.5/predicate/generator/helpers.py +85 -0
  13. py_predicate-0.5/predicate/has_key_predicate.py +16 -0
  14. py_predicate-0.5/predicate/has_length_predicate.py +19 -0
  15. py_predicate-0.5/predicate/implies.py +109 -0
  16. py_predicate-0.5/predicate/ip_address_predicates.py +47 -0
  17. py_predicate-0.5/predicate/is_instance_predicate.py +17 -0
  18. py_predicate-0.5/predicate/named_predicate.py +17 -0
  19. py_predicate-0.5/predicate/optimizer/__init__.py +0 -0
  20. {py_predicate-0.3 → py_predicate-0.5}/predicate/optimizer/all_optimizer.py +4 -3
  21. {py_predicate-0.3 → py_predicate-0.5}/predicate/optimizer/and_optimizer.py +39 -19
  22. {py_predicate-0.3 → py_predicate-0.5}/predicate/optimizer/any_optimizer.py +6 -4
  23. py_predicate-0.5/predicate/optimizer/in_optimizer.py +24 -0
  24. {py_predicate-0.3 → py_predicate-0.5}/predicate/optimizer/not_optimizer.py +2 -2
  25. {py_predicate-0.3 → py_predicate-0.5}/predicate/optimizer/or_optimizer.py +20 -13
  26. {py_predicate-0.3 → py_predicate-0.5}/predicate/optimizer/predicate_optimizer.py +8 -4
  27. {py_predicate-0.3 → py_predicate-0.5}/predicate/optimizer/xor_optimizer.py +11 -4
  28. {py_predicate-0.3 → py_predicate-0.5}/predicate/parser.py +3 -2
  29. {py_predicate-0.3 → py_predicate-0.5}/predicate/predicate.py +11 -150
  30. py_predicate-0.5/predicate/property_predicate.py +20 -0
  31. py_predicate-0.5/predicate/range_predicate.py +59 -0
  32. {py_predicate-0.3 → py_predicate-0.5}/predicate/regex_predicate.py +1 -0
  33. py_predicate-0.5/predicate/set_of_predicate.py +16 -0
  34. py_predicate-0.5/predicate/set_predicates.py +120 -0
  35. {py_predicate-0.3 → py_predicate-0.5}/predicate/standard_predicates.py +115 -32
  36. py_predicate-0.5/predicate/str_predicates.py +55 -0
  37. py_predicate-0.5/predicate/tee_predicate.py +18 -0
  38. {py_predicate-0.3 → py_predicate-0.5}/predicate/this_predicate.py +5 -1
  39. {py_predicate-0.3 → py_predicate-0.5}/predicate/truth_table.py +2 -1
  40. py_predicate-0.5/predicate/tuple_of_predicate.py +19 -0
  41. {py_predicate-0.3 → py_predicate-0.5}/pyproject.toml +5 -1
  42. py_predicate-0.3/predicate/implies.py +0 -53
  43. py_predicate-0.3/predicate/optimizer/in_optimizer.py +0 -13
  44. {py_predicate-0.3 → py_predicate-0.5}/predicate/comp_predicate.py +0 -0
  45. {py_predicate-0.3/predicate/formatter → py_predicate-0.5/predicate/constructor}/__init__.py +0 -0
  46. {py_predicate-0.3/predicate/optimizer → py_predicate-0.5/predicate/formatter}/__init__.py +0 -0
  47. {py_predicate-0.3 → py_predicate-0.5}/predicate/lazy_predicate.py +0 -0
  48. {py_predicate-0.3 → py_predicate-0.5}/predicate/negate.py +0 -0
  49. {py_predicate-0.3 → py_predicate-0.5}/predicate/optimizer/rules.py +0 -0
  50. {py_predicate-0.3 → py_predicate-0.5}/predicate/root_predicate.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: py_predicate
3
- Version: 0.3
3
+ Version: 0.5
4
4
  Summary: Module to create composable predicates
5
5
  Author-email: Maurits Rijk <maurits.rijk@gmail.com>
6
6
  Requires-Python: >=3.10
@@ -21,6 +21,7 @@ Classifier: License :: OSI Approved :: MIT License
21
21
  Classifier: Programming Language :: Python :: 3 :: Only
22
22
  Classifier: Programming Language :: Python :: 3.13
23
23
  Classifier: Programming Language :: Python :: 3.12
24
+ Requires-Dist: exrex
24
25
  Requires-Dist: graphviz
25
26
  Requires-Dist: lark
26
27
  Requires-Dist: more-itertools
@@ -32,6 +33,7 @@ Requires-Dist: black ; extra == "test"
32
33
  Requires-Dist: mypy ; extra == "test"
33
34
  Requires-Dist: mypy_extensions ; extra == "test"
34
35
  Requires-Dist: pytest ; extra == "test"
36
+ Requires-Dist: pytest-benchmark ; extra == "test"
35
37
  Requires-Dist: pytest-cov ; extra == "test"
36
38
  Requires-Dist: ruff ; extra == "test"
37
39
  Project-URL: Documentation, https://mrijk.github.io/py-predicate/
@@ -89,9 +91,9 @@ either a string, or a list of data that can again either be a string or a list o
89
91
  data. Ad infinitum.
90
92
 
91
93
  ```python
92
- 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
93
95
 
94
- 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))
95
97
  ```
96
98
 
97
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.
@@ -2,28 +2,27 @@
2
2
 
3
3
  __version__ = "0.0.1"
4
4
 
5
+ from predicate.all_predicate import AllPredicate
6
+ from predicate.any_predicate import AnyPredicate
5
7
  from predicate.formatter.format_dot import to_dot
6
8
  from predicate.formatter.format_json import to_json
9
+ from predicate.generator.generate_false import generate_false
10
+ from predicate.generator.generate_true import generate_true
7
11
  from predicate.optimizer.predicate_optimizer import can_optimize, optimize
8
12
  from predicate.predicate import (
9
- AllPredicate,
10
13
  AlwaysFalsePredicate,
11
14
  AlwaysTruePredicate,
12
15
  AndPredicate,
13
- AnyPredicate,
14
16
  EqPredicate,
15
17
  FnPredicate,
16
18
  GePredicate,
17
19
  GtPredicate,
18
- InPredicate,
19
20
  IsEmptyPredicate,
20
- IsInstancePredicate,
21
21
  IsNonePredicate,
22
22
  IsNotNonePredicate,
23
23
  LePredicate,
24
24
  LtPredicate,
25
25
  NePredicate,
26
- NotInPredicate,
27
26
  NotPredicate,
28
27
  OrPredicate,
29
28
  Predicate,
@@ -32,6 +31,16 @@ from predicate.predicate import (
32
31
  always_true_p,
33
32
  is_empty_p,
34
33
  )
34
+ from predicate.set_predicates import (
35
+ InPredicate,
36
+ NotInPredicate,
37
+ in_p,
38
+ is_real_subset_p,
39
+ is_real_superset_p,
40
+ is_subset_p,
41
+ is_superset_p,
42
+ not_in_p,
43
+ )
35
44
  from predicate.standard_predicates import (
36
45
  all_p,
37
46
  any_p,
@@ -40,16 +49,24 @@ from predicate.standard_predicates import (
40
49
  eq_p,
41
50
  eq_true_p,
42
51
  fn_p,
52
+ ge_le_p,
53
+ ge_lt_p,
43
54
  ge_p,
55
+ gt_le_p,
56
+ gt_lt_p,
44
57
  gt_p,
45
- in_p,
58
+ has_length_p,
46
59
  is_bool_p,
47
60
  is_callable_p,
48
61
  is_complex_p,
62
+ is_container_p,
49
63
  is_datetime_p,
50
64
  is_dict_p,
51
65
  is_falsy_p,
66
+ is_finite_p,
52
67
  is_float_p,
68
+ is_hashable_p,
69
+ is_inf_p,
53
70
  is_instance_p,
54
71
  is_int_p,
55
72
  is_iterable_of_p,
@@ -59,9 +76,11 @@ from predicate.standard_predicates import (
59
76
  is_none_p,
60
77
  is_not_none_p,
61
78
  is_predicate_p,
79
+ is_range_p,
62
80
  is_set_of_p,
63
81
  is_set_p,
64
82
  is_str_p,
83
+ is_truthy_p,
65
84
  is_tuple_of_p,
66
85
  is_tuple_p,
67
86
  is_uuid_p,
@@ -69,10 +88,13 @@ from predicate.standard_predicates import (
69
88
  le_p,
70
89
  lt_p,
71
90
  ne_p,
72
- not_in_p,
91
+ neg_p,
92
+ pos_p,
73
93
  regex_p,
74
94
  root_p,
95
+ tee_p,
75
96
  this_p,
97
+ zero_p,
76
98
  )
77
99
 
78
100
  __all__ = [
@@ -87,7 +109,6 @@ __all__ = [
87
109
  "GtPredicate",
88
110
  "InPredicate",
89
111
  "IsEmptyPredicate",
90
- "IsInstancePredicate",
91
112
  "IsNonePredicate",
92
113
  "IsNotNonePredicate",
93
114
  "LePredicate",
@@ -107,42 +128,82 @@ __all__ = [
107
128
  "eq_false_p",
108
129
  "eq_p",
109
130
  "eq_true_p",
110
- "is_falsy_p",
111
131
  "fn_p",
132
+ "ge_le_p",
133
+ "ge_lt_p",
112
134
  "ge_p",
135
+ "generate_false",
136
+ "generate_true",
137
+ "gt_le_p",
138
+ "gt_lt_p",
113
139
  "gt_p",
140
+ "has_length_p",
114
141
  "in_p",
142
+ "is_alnum_p",
143
+ "is_alpha_p",
144
+ "is_ascii_p",
115
145
  "is_bool_p",
116
146
  "is_callable_p",
117
147
  "is_complex_p",
148
+ "is_container_p",
118
149
  "is_datetime_p",
150
+ "is_decimal_p",
119
151
  "is_dict_p",
120
152
  "is_empty_p",
153
+ "is_falsy_p",
154
+ "is_finite_p",
121
155
  "is_float_p",
156
+ "is_hashable_p",
157
+ "is_identifier_p",
158
+ "is_inf_p",
122
159
  "is_instance_p",
123
160
  "is_int_p",
124
- "is_iterable_p",
125
161
  "is_iterable_of_p",
126
- "is_list_p",
162
+ "is_iterable_p",
127
163
  "is_list_of_p",
164
+ "is_list_p",
165
+ "is_lower_p",
128
166
  "is_none_p",
129
167
  "is_not_none_p",
130
168
  "is_predicate_p",
131
- "is_set_p",
169
+ "is_range_p",
132
170
  "is_set_of_p",
171
+ "is_set_p",
133
172
  "is_str_p",
134
- "is_tuple_p",
173
+ "is_subset_p",
174
+ "is_superset_p",
175
+ "is_real_subset_p",
176
+ "is_real_superset_p",
177
+ "is_title_p",
178
+ "is_truthy_p",
135
179
  "is_tuple_of_p",
180
+ "is_tuple_p",
181
+ "is_upper_p",
136
182
  "is_uuid_p",
137
183
  "lazy_p",
138
184
  "le_p",
139
185
  "lt_p",
140
186
  "ne_p",
187
+ "neg_p",
141
188
  "not_in_p",
142
189
  "optimize",
190
+ "pos_p",
143
191
  "regex_p",
144
192
  "root_p",
193
+ "tee_p",
145
194
  "this_p",
146
195
  "to_dot",
147
196
  "to_json",
197
+ "zero_p",
148
198
  ]
199
+
200
+ from predicate.str_predicates import (
201
+ is_alnum_p,
202
+ is_alpha_p,
203
+ is_ascii_p,
204
+ is_decimal_p,
205
+ is_identifier_p,
206
+ is_lower_p,
207
+ is_title_p,
208
+ is_upper_p,
209
+ )
@@ -0,0 +1,17 @@
1
+ from dataclasses import dataclass
2
+ from typing import Iterable
3
+
4
+ from predicate.predicate import Predicate
5
+
6
+
7
+ @dataclass
8
+ class AllPredicate[T](Predicate[T]):
9
+ """A predicate class that models the 'all' predicate."""
10
+
11
+ predicate: Predicate[T]
12
+
13
+ def __call__(self, iterable: Iterable[T]) -> bool:
14
+ return all(self.predicate(x) for x in iterable)
15
+
16
+ def __repr__(self) -> str:
17
+ return f"all({repr(self.predicate)})"
@@ -0,0 +1,17 @@
1
+ from dataclasses import dataclass
2
+ from typing import Iterable
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)})"
@@ -0,0 +1,49 @@
1
+ from typing import Iterator
2
+
3
+ from more_itertools import gray_product
4
+
5
+ from predicate import is_datetime_p, is_falsy_p, is_float_p, is_int_p, is_not_none_p, is_set_p, is_str_p, is_truthy_p
6
+ from predicate.predicate import Predicate, always_false_p, always_true_p
7
+ from predicate.standard_predicates import all_p, is_bool_p, is_dict_p, is_list_p, is_none_p
8
+
9
+ # TODO: this is very much work under construction (pun intended) and not ready for public consumption
10
+
11
+
12
+ def construct(false_set: list, true_set: list) -> Iterator[Predicate]:
13
+ predicates = list(initial_predicates())
14
+
15
+ while True:
16
+ for predicate in predicates:
17
+ all_true = all_p(predicate)
18
+ all_false = all_p(~predicate)
19
+ if all_true(true_set) and all_false(false_set):
20
+ yield predicate
21
+
22
+ predicates = list(create_mutations(predicates))
23
+
24
+
25
+ def create_mutations(candidates: list[Predicate]) -> Iterator[Predicate]:
26
+ pairs = gray_product(candidates, candidates)
27
+ for pair in pairs:
28
+ left, right = pair
29
+ if left != right:
30
+ yield left | right
31
+ yield left & right
32
+
33
+
34
+ def initial_predicates() -> Iterator[Predicate]:
35
+ # TODO: probably import from __init__
36
+ yield always_false_p
37
+ yield always_true_p
38
+ yield is_bool_p
39
+ yield is_datetime_p
40
+ yield is_dict_p
41
+ yield is_falsy_p
42
+ yield is_float_p
43
+ yield is_int_p
44
+ yield is_list_p
45
+ yield is_none_p
46
+ yield is_not_none_p
47
+ yield is_set_p
48
+ yield is_str_p
49
+ yield is_truthy_p
@@ -5,8 +5,12 @@ from itertools import count
5
5
  import graphviz # type: ignore
6
6
  from more_itertools import first
7
7
 
8
+ from predicate.all_predicate import AllPredicate
9
+ from predicate.any_predicate import AnyPredicate
8
10
  from predicate.comp_predicate import CompPredicate
11
+ from predicate.is_instance_predicate import IsInstancePredicate
9
12
  from predicate.lazy_predicate import LazyPredicate, find_predicate_by_ref
13
+ from predicate.named_predicate import NamedPredicate
10
14
  from predicate.optimizer.predicate_optimizer import optimize
11
15
  from predicate.predicate import (
12
16
  AlwaysFalsePredicate,
@@ -14,29 +18,32 @@ from predicate.predicate import (
14
18
  AndPredicate,
15
19
  IsFalsyPredicate,
16
20
  IsTruthyPredicate,
17
- NamedPredicate,
18
21
  NotPredicate,
19
22
  OrPredicate,
20
23
  Predicate,
21
24
  XorPredicate,
22
25
  )
26
+ from predicate.range_predicate import GeLePredicate, GeLtPredicate, GtLePredicate, GtLtPredicate
23
27
  from predicate.root_predicate import RootPredicate, find_root_predicate
28
+ from predicate.set_predicates import (
29
+ InPredicate,
30
+ IsRealSubsetPredicate,
31
+ IsRealSupersetPredicate,
32
+ IsSubsetPredicate,
33
+ IsSupersetPredicate,
34
+ NotInPredicate,
35
+ )
24
36
  from predicate.standard_predicates import (
25
- AllPredicate,
26
- AnyPredicate,
27
37
  EqPredicate,
28
38
  FnPredicate,
29
39
  GePredicate,
30
40
  GtPredicate,
31
- InPredicate,
32
- IsInstancePredicate,
33
41
  IsNonePredicate,
34
42
  LePredicate,
35
43
  LtPredicate,
36
44
  NePredicate,
37
- NotInPredicate,
38
- PredicateFactory,
39
45
  )
46
+ from predicate.tee_predicate import TeePredicate
40
47
  from predicate.this_predicate import ThisPredicate, find_this_predicate
41
48
 
42
49
 
@@ -60,46 +67,52 @@ def to_dot(predicate: Predicate, predicate_string: str = "", show_optimized: boo
60
67
  return dot
61
68
 
62
69
 
70
+ def set_to_str(v: set) -> str:
71
+ # TODO: truncate if too many items.
72
+ items = ", ".join(str(item) for item in v)
73
+ return f"{{{items}}}"
74
+
75
+
63
76
  def render(dot, predicate: Predicate, node_nr):
64
77
  node_predicate_mapping: dict[str, Predicate] = {}
65
78
 
66
- def _add_node(name: str, *, label: str, predicate: Predicate):
79
+ def _add_node(name: str, *, label: str, predicate: Predicate) -> str:
67
80
  node = next(node_nr)
68
81
  unique_name = f"{name}_{node}"
69
82
  dot.node(unique_name, label=label)
70
83
  node_predicate_mapping[unique_name] = predicate
71
84
  return unique_name
72
85
 
86
+ def _add_node_left_right(name: str, *, label: str, predicate: Predicate, left: Predicate, right: Predicate) -> str:
87
+ node = _add_node(name, label=label, predicate=predicate)
88
+ dot.edge(node, to_value(left))
89
+ dot.edge(node, to_value(right))
90
+
91
+ return node
92
+
93
+ def _add_node_with_child(name: str, *, label: str, predicate: Predicate, child: Predicate) -> str:
94
+ node = _add_node(name, label=label, predicate=predicate)
95
+ dot.edge(node, to_value(child))
96
+ return node
97
+
73
98
  def to_value(predicate: Predicate):
74
99
  add_node = partial(_add_node, predicate=predicate)
100
+ add_node_left_right = partial(_add_node_left_right, predicate=predicate)
101
+ add_node_with_child = partial(_add_node_with_child, predicate=predicate)
75
102
 
76
103
  match predicate:
77
104
  case AllPredicate(all_predicate):
78
- node = add_node("all", label="∀")
79
- child = to_value(all_predicate)
80
- dot.edge(node, child)
81
- return node
105
+ return add_node_with_child("all", label="∀", child=all_predicate)
82
106
  case AlwaysFalsePredicate():
83
107
  return add_node("F", label="false")
84
108
  case AlwaysTruePredicate():
85
109
  return add_node("T", label="true")
86
110
  case AndPredicate(left, right):
87
- node = add_node("and", label="∧")
88
- left_node = to_value(left)
89
- right_node = to_value(right)
90
- dot.edge(node, left_node)
91
- dot.edge(node, right_node)
92
- return node
111
+ return add_node_left_right("and", label="∧", left=left, right=right)
93
112
  case AnyPredicate(any_predicate):
94
- node = add_node("any", label="∃")
95
- child = to_value(any_predicate)
96
- dot.edge(node, child)
97
- return node
113
+ return add_node_with_child("any", label="∃", child=any_predicate)
98
114
  case CompPredicate(_fn, comp_predicate):
99
- node = add_node("comp", label="f")
100
- child = to_value(comp_predicate)
101
- dot.edge(node, child)
102
- return node
115
+ return add_node_with_child("comp", label="f", child=comp_predicate)
103
116
  case EqPredicate(v):
104
117
  return add_node("eq", label=f"x = {v}")
105
118
  case IsFalsyPredicate():
@@ -111,16 +124,31 @@ def render(dot, predicate: Predicate, node_nr):
111
124
  return add_node("fn", label=f"fn: {name}")
112
125
  case GePredicate(v):
113
126
  return add_node("ge", label=f"x ≥ {v}")
127
+ case GeLePredicate(upper, lower):
128
+ return add_node("gele", label=f"{lower} ≤ x ≤ {upper}")
129
+ case GeLtPredicate(upper, lower):
130
+ return add_node("gelt", label=f"{lower} ≤ x < {upper}")
114
131
  case GtPredicate(v):
115
132
  return add_node("gt", label=f"x > {v}")
133
+ case GtLePredicate(upper, lower):
134
+ return add_node("gtle", label=f"{lower} ≤ x ≤ {upper}")
135
+ case GtLtPredicate(upper, lower):
136
+ return add_node("gtlt", label=f"{lower} ≤ x < {upper}")
116
137
  case InPredicate(v):
117
- items = ", ".join(str(item) for item in v)
118
- return add_node("in", label=f"x ∈ {{{items}}}")
138
+ return add_node("in", label=f"x {set_to_str(v)}")
119
139
  case IsInstancePredicate(klass):
120
140
  name = klass[0].__name__ # type: ignore
121
141
  return add_node("instance", label=f"is_{name}_p")
122
142
  case IsNonePredicate():
123
143
  return add_node("none", label="x = None")
144
+ case IsRealSubsetPredicate(v):
145
+ return add_node("real_subset", label=f"x ⊂ {set_to_str(v)}")
146
+ case IsSubsetPredicate(v):
147
+ return add_node("subset", label=f"x ⊆ {set_to_str(v)}")
148
+ case IsRealSupersetPredicate(v):
149
+ return add_node("real_superset", label=f"x ⊃ {set_to_str(v)}")
150
+ case IsSupersetPredicate(v):
151
+ return add_node("superset", label=f"x ⊇ {set_to_str(v)}")
124
152
  case LazyPredicate(ref):
125
153
  return add_node("lazy", label=ref)
126
154
  case LePredicate(v):
@@ -130,35 +158,21 @@ def render(dot, predicate: Predicate, node_nr):
130
158
  case NamedPredicate(name):
131
159
  return add_node("named", label=name)
132
160
  case NotInPredicate(v):
133
- items = ", ".join(str(item) for item in v)
134
- return add_node("in", label=f"x ∉ {{{items}}}")
161
+ return add_node("in", label=f"x {set_to_str(v)}")
135
162
  case NePredicate(v):
136
163
  return add_node("ne", label=f"x ≠ {v}")
137
164
  case NotPredicate(not_predicate):
138
- child = to_value(not_predicate)
139
- node = add_node("not", label="¬")
140
- dot.edge(node, child)
141
- return node
165
+ return add_node_with_child("not", label="¬", child=not_predicate)
142
166
  case OrPredicate(left, right):
143
- node = add_node("or", label="∨")
144
- left_node = to_value(left)
145
- right_node = to_value(right)
146
- dot.edge(node, left_node)
147
- dot.edge(node, right_node)
148
- return node
149
- case PredicateFactory() as factory:
150
- return to_value(factory.predicate)
167
+ return add_node_left_right("or", label="∨", left=left, right=right)
151
168
  case RootPredicate():
152
169
  return add_node("root", label="root")
170
+ case TeePredicate():
171
+ return add_node("tee", label="tee")
153
172
  case ThisPredicate():
154
173
  return add_node("this", label="this")
155
174
  case XorPredicate(left, right):
156
- node = add_node("xor", label="⊻")
157
- left_node = to_value(left)
158
- right_node = to_value(right)
159
- dot.edge(node, left_node)
160
- dot.edge(node, right_node)
161
- return node
175
+ return add_node_left_right("xor", label="⊻", left=left, right=right)
162
176
  case _:
163
177
  raise ValueError(f"Unknown predicate type {predicate}")
164
178
 
@@ -167,7 +181,7 @@ def render(dot, predicate: Predicate, node_nr):
167
181
  render_lazy_references(dot, node_predicate_mapping)
168
182
 
169
183
 
170
- def render_lazy_references(dot, node_predicate_mapping):
184
+ def render_lazy_references(dot, node_predicate_mapping) -> None:
171
185
  def find_in_mapping(lookup: Predicate) -> str:
172
186
  return first(node for node, predicate in node_predicate_mapping.items() if predicate == lookup)
173
187
 
@@ -190,14 +204,14 @@ def render_lazy_references(dot, node_predicate_mapping):
190
204
  add_dashed_line(node, this)
191
205
 
192
206
 
193
- def render_original(dot, predicate: Predicate, node_nr):
207
+ def render_original(dot, predicate: Predicate, node_nr) -> None:
194
208
  with dot.subgraph(name="cluster_original") as original:
195
209
  original.attr(style="filled", color="lightgrey")
196
210
  original.attr(label="Original predicate")
197
211
  render(original, predicate, node_nr)
198
212
 
199
213
 
200
- def render_optimized(dot, predicate: Predicate, node_nr):
214
+ def render_optimized(dot, predicate: Predicate, node_nr) -> None:
201
215
  optimized_predicate = optimize(predicate)
202
216
 
203
217
  with dot.subgraph(name="cluster_optimized") as optimized:
@@ -1,21 +1,22 @@
1
1
  from typing import Any
2
2
 
3
+ from predicate.all_predicate import AllPredicate
4
+ from predicate.any_predicate import AnyPredicate
5
+ from predicate.named_predicate import NamedPredicate
3
6
  from predicate.predicate import (
4
- AllPredicate,
5
7
  AlwaysFalsePredicate,
6
8
  AlwaysTruePredicate,
7
9
  AndPredicate,
8
- AnyPredicate,
9
10
  FnPredicate,
10
11
  IsFalsyPredicate,
11
12
  IsTruthyPredicate,
12
- NamedPredicate,
13
13
  NePredicate,
14
14
  NotPredicate,
15
15
  OrPredicate,
16
16
  Predicate,
17
17
  XorPredicate,
18
18
  )
19
+ from predicate.tee_predicate import TeePredicate
19
20
 
20
21
 
21
22
  def to_json(predicate: Predicate) -> dict[str, Any]:
@@ -48,6 +49,8 @@ def to_json(predicate: Predicate) -> dict[str, Any]:
48
49
  return "not", {"predicate": to_json(not_predicate)}
49
50
  case OrPredicate(left, right):
50
51
  return "or", {"left": to_json(left), "right": to_json(right)}
52
+ case TeePredicate():
53
+ return "tee", None
51
54
  case XorPredicate(left, right):
52
55
  return "xor", {"left": to_json(left), "right": to_json(right)}
53
56
  case _:
@@ -0,0 +1,4 @@
1
+ __all__ = [
2
+ "generate_false",
3
+ "generate_true",
4
+ ]