py-predicate 1.3__tar.gz → 1.4__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 (103) hide show
  1. {py_predicate-1.3 → py_predicate-1.4}/PKG-INFO +2 -1
  2. {py_predicate-1.3 → py_predicate-1.4}/predicate/__init__.py +31 -4
  3. {py_predicate-1.3 → py_predicate-1.4}/predicate/all_predicate.py +9 -1
  4. {py_predicate-1.3 → py_predicate-1.4}/predicate/always_false_predicate.py +5 -1
  5. {py_predicate-1.3 → py_predicate-1.4}/predicate/always_true_predicate.py +5 -1
  6. {py_predicate-1.3 → py_predicate-1.4}/predicate/any_predicate.py +14 -1
  7. {py_predicate-1.3 → py_predicate-1.4}/predicate/constructor/mutate.py +1 -1
  8. py_predicate-1.4/predicate/consumes.py +13 -0
  9. {py_predicate-1.3 → py_predicate-1.4}/predicate/count_predicate.py +1 -1
  10. {py_predicate-1.3 → py_predicate-1.4}/predicate/eq_predicate.py +5 -1
  11. py_predicate-1.4/predicate/exactly_predicate.py +48 -0
  12. py_predicate-1.4/predicate/exception_predicate.py +25 -0
  13. {py_predicate-1.3 → py_predicate-1.4}/predicate/generator/generate_false.py +15 -6
  14. {py_predicate-1.3 → py_predicate-1.4}/predicate/generator/generate_true.py +11 -5
  15. {py_predicate-1.3 → py_predicate-1.4}/predicate/has_length_predicate.py +9 -1
  16. {py_predicate-1.3 → py_predicate-1.4}/predicate/has_path_predicate.py +7 -3
  17. {py_predicate-1.3 → py_predicate-1.4}/predicate/helpers.py +1 -1
  18. {py_predicate-1.3 → py_predicate-1.4}/predicate/is_instance_predicate.py +8 -8
  19. {py_predicate-1.3 → py_predicate-1.4}/predicate/is_none_predicate.py +10 -1
  20. {py_predicate-1.3 → py_predicate-1.4}/predicate/is_not_none_predicate.py +5 -1
  21. py_predicate-1.4/predicate/is_predicate.py +30 -0
  22. py_predicate-1.4/predicate/is_same_predicate.py +44 -0
  23. py_predicate-1.4/predicate/match_predicate.py +74 -0
  24. py_predicate-1.4/predicate/mutual_recur_predicate.py +42 -0
  25. py_predicate-1.4/predicate/named_predicate.py +45 -0
  26. py_predicate-1.4/predicate/optional_predicate.py +45 -0
  27. py_predicate-1.4/predicate/plus_predicate.py +39 -0
  28. {py_predicate-1.3 → py_predicate-1.4}/predicate/predicate.py +30 -2
  29. py_predicate-1.4/predicate/predicate_factory.py +21 -0
  30. py_predicate-1.4/predicate/recur_predicate.py +51 -0
  31. py_predicate-1.4/predicate/reduce_predicate.py +25 -0
  32. py_predicate-1.4/predicate/repeat_predicate.py +34 -0
  33. {py_predicate-1.3 → py_predicate-1.4}/predicate/root_predicate.py +9 -11
  34. {py_predicate-1.3 → py_predicate-1.4}/predicate/spec/exercise.py +2 -5
  35. {py_predicate-1.3 → py_predicate-1.4}/predicate/standard_predicates.py +2 -19
  36. py_predicate-1.4/predicate/star_predicate.py +43 -0
  37. {py_predicate-1.3 → py_predicate-1.4}/predicate/str_predicates.py +1 -1
  38. {py_predicate-1.3 → py_predicate-1.4}/predicate/tee_predicate.py +5 -1
  39. py_predicate-1.4/predicate/this_predicate.py +41 -0
  40. {py_predicate-1.3 → py_predicate-1.4}/predicate/truth_table.py +1 -1
  41. {py_predicate-1.3 → py_predicate-1.4}/pyproject.toml +2 -1
  42. py_predicate-1.3/predicate/match_predicate.py +0 -230
  43. py_predicate-1.3/predicate/named_predicate.py +0 -17
  44. py_predicate-1.3/predicate/this_predicate.py +0 -33
  45. {py_predicate-1.3 → py_predicate-1.4}/LICENSE +0 -0
  46. {py_predicate-1.3 → py_predicate-1.4}/README.md +0 -0
  47. {py_predicate-1.3 → py_predicate-1.4}/predicate/comp_predicate.py +0 -0
  48. {py_predicate-1.3 → py_predicate-1.4}/predicate/constructor/__init__.py +0 -0
  49. {py_predicate-1.3 → py_predicate-1.4}/predicate/constructor/construct.py +0 -0
  50. {py_predicate-1.3 → py_predicate-1.4}/predicate/constructor/helpers.py +0 -0
  51. {py_predicate-1.3 → py_predicate-1.4}/predicate/dict_of_predicate.py +0 -0
  52. {py_predicate-1.3 → py_predicate-1.4}/predicate/explain.py +0 -0
  53. {py_predicate-1.3 → py_predicate-1.4}/predicate/fn_predicate.py +0 -0
  54. {py_predicate-1.3 → py_predicate-1.4}/predicate/formatter/__init__.py +0 -0
  55. {py_predicate-1.3 → py_predicate-1.4}/predicate/formatter/format_dot.py +0 -0
  56. {py_predicate-1.3 → py_predicate-1.4}/predicate/formatter/format_json.py +0 -0
  57. {py_predicate-1.3 → py_predicate-1.4}/predicate/formatter/format_latex.py +0 -0
  58. {py_predicate-1.3 → py_predicate-1.4}/predicate/formatter/helpers.py +0 -0
  59. {py_predicate-1.3 → py_predicate-1.4}/predicate/ge_predicate.py +0 -0
  60. {py_predicate-1.3 → py_predicate-1.4}/predicate/generator/__init__.py +0 -0
  61. {py_predicate-1.3 → py_predicate-1.4}/predicate/generator/generate_predicate.py +0 -0
  62. {py_predicate-1.3 → py_predicate-1.4}/predicate/generator/helpers.py +0 -0
  63. {py_predicate-1.3 → py_predicate-1.4}/predicate/gt_predicate.py +0 -0
  64. {py_predicate-1.3 → py_predicate-1.4}/predicate/has_key_predicate.py +0 -0
  65. {py_predicate-1.3 → py_predicate-1.4}/predicate/implies.py +0 -0
  66. {py_predicate-1.3 → py_predicate-1.4}/predicate/implies_predicate.py +0 -0
  67. {py_predicate-1.3 → py_predicate-1.4}/predicate/in_predicate.py +0 -0
  68. {py_predicate-1.3 → py_predicate-1.4}/predicate/in_predicate_predicate.py +0 -0
  69. {py_predicate-1.3 → py_predicate-1.4}/predicate/ip_address_predicates.py +0 -0
  70. {py_predicate-1.3 → py_predicate-1.4}/predicate/is_callable_predicate.py +0 -0
  71. {py_predicate-1.3 → py_predicate-1.4}/predicate/is_falsy_predicate.py +0 -0
  72. {py_predicate-1.3 → py_predicate-1.4}/predicate/is_lambda_predicate.py +0 -0
  73. {py_predicate-1.3 → py_predicate-1.4}/predicate/is_predicate_of_p.py +0 -0
  74. {py_predicate-1.3 → py_predicate-1.4}/predicate/is_subclass_predicate.py +0 -0
  75. {py_predicate-1.3 → py_predicate-1.4}/predicate/is_truthy_predicate.py +0 -0
  76. {py_predicate-1.3 → py_predicate-1.4}/predicate/lazy_predicate.py +0 -0
  77. {py_predicate-1.3 → py_predicate-1.4}/predicate/le_predicate.py +0 -0
  78. {py_predicate-1.3 → py_predicate-1.4}/predicate/list_of_predicate.py +0 -0
  79. {py_predicate-1.3 → py_predicate-1.4}/predicate/lt_predicate.py +0 -0
  80. {py_predicate-1.3 → py_predicate-1.4}/predicate/ne_predicate.py +0 -0
  81. {py_predicate-1.3 → py_predicate-1.4}/predicate/negate.py +0 -0
  82. {py_predicate-1.3 → py_predicate-1.4}/predicate/not_in_predicate.py +0 -0
  83. {py_predicate-1.3 → py_predicate-1.4}/predicate/optimizer/__init__.py +0 -0
  84. {py_predicate-1.3 → py_predicate-1.4}/predicate/optimizer/all_optimizer.py +0 -0
  85. {py_predicate-1.3 → py_predicate-1.4}/predicate/optimizer/and_optimizer.py +0 -0
  86. {py_predicate-1.3 → py_predicate-1.4}/predicate/optimizer/any_optimizer.py +0 -0
  87. {py_predicate-1.3 → py_predicate-1.4}/predicate/optimizer/helpers.py +0 -0
  88. {py_predicate-1.3 → py_predicate-1.4}/predicate/optimizer/in_optimizer.py +0 -0
  89. {py_predicate-1.3 → py_predicate-1.4}/predicate/optimizer/not_in_optimizer.py +0 -0
  90. {py_predicate-1.3 → py_predicate-1.4}/predicate/optimizer/not_optimizer.py +0 -0
  91. {py_predicate-1.3 → py_predicate-1.4}/predicate/optimizer/or_optimizer.py +0 -0
  92. {py_predicate-1.3 → py_predicate-1.4}/predicate/optimizer/predicate_optimizer.py +0 -0
  93. {py_predicate-1.3 → py_predicate-1.4}/predicate/optimizer/xor_optimizer.py +0 -0
  94. {py_predicate-1.3 → py_predicate-1.4}/predicate/parser.py +0 -0
  95. {py_predicate-1.3 → py_predicate-1.4}/predicate/property_predicate.py +0 -0
  96. {py_predicate-1.3 → py_predicate-1.4}/predicate/range_predicate.py +0 -0
  97. {py_predicate-1.3 → py_predicate-1.4}/predicate/regex_predicate.py +0 -0
  98. {py_predicate-1.3 → py_predicate-1.4}/predicate/set_of_predicate.py +0 -0
  99. {py_predicate-1.3 → py_predicate-1.4}/predicate/set_predicates.py +0 -0
  100. {py_predicate-1.3 → py_predicate-1.4}/predicate/spec/__init__.py +0 -0
  101. {py_predicate-1.3 → py_predicate-1.4}/predicate/spec/instrument.py +0 -0
  102. {py_predicate-1.3 → py_predicate-1.4}/predicate/spec/spec.py +0 -0
  103. {py_predicate-1.3 → py_predicate-1.4}/predicate/tuple_of_predicate.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py_predicate
3
- Version: 1.3
3
+ Version: 1.4
4
4
  Summary: Module to create composable predicates
5
5
  Author-email: Maurits Rijk <maurits.rijk@gmail.com>
6
6
  Requires-Python: >=3.10
@@ -19,6 +19,7 @@ Classifier: Development Status :: 5 - Production/Stable
19
19
  Classifier: Environment :: Web Environment
20
20
  Classifier: License :: OSI Approved :: MIT License
21
21
  Classifier: Programming Language :: Python :: 3 :: Only
22
+ Classifier: Programming Language :: Python :: 3.14
22
23
  Classifier: Programming Language :: Python :: 3.13
23
24
  Classifier: Programming Language :: Python :: 3.12
24
25
  License-File: LICENSE
@@ -7,9 +7,11 @@ from predicate.always_false_predicate import always_false_p, never_p
7
7
  from predicate.always_true_predicate import always_p, always_true_p
8
8
  from predicate.any_predicate import any_p
9
9
  from predicate.comp_predicate import comp_p
10
- from predicate.count_predicate import count_p
10
+ from predicate.count_predicate import count_p, exactly_one_p, exactly_zero_p
11
11
  from predicate.dict_of_predicate import is_dict_of_p
12
12
  from predicate.eq_predicate import eq_false_p, eq_p, eq_true_p, zero_p
13
+ from predicate.exactly_predicate import exactly_n
14
+ from predicate.exception_predicate import PredicateError, exception_p
13
15
  from predicate.explain import explain
14
16
  from predicate.fn_predicate import fn_p, is_even_p, is_finite_p, is_inf_p, is_nan_p, is_odd_p
15
17
  from predicate.formatter import to_dot, to_json, to_latex
@@ -39,21 +41,30 @@ from predicate.is_instance_predicate import (
39
41
  is_uuid_p,
40
42
  )
41
43
  from predicate.is_lambda_predicate import is_lambda_p, is_lambda_with_signature_p
42
- from predicate.is_none_predicate import is_none_p
43
- from predicate.is_not_none_predicate import is_not_none_p
44
+ from predicate.is_none_predicate import is_none_p, none_is_exception_p, none_is_true_p
45
+ from predicate.is_not_none_predicate import is_not_none_p, none_is_false_p
46
+ from predicate.is_predicate import is_p
44
47
  from predicate.is_predicate_of_p import is_predicate_of_p
48
+ from predicate.is_same_predicate import is_same_p
45
49
  from predicate.is_subclass_predicate import is_enum_p, is_int_enum_p, is_str_enum_p, is_subclass_p
46
50
  from predicate.is_truthy_predicate import is_truthy_p
47
51
  from predicate.lazy_predicate import lazy_p
48
52
  from predicate.le_predicate import le_p
49
53
  from predicate.list_of_predicate import is_list_of_p
50
54
  from predicate.lt_predicate import lt_p, neg_p
51
- from predicate.match_predicate import exactly_n, match_p, optional, plus, repeat, star
55
+ from predicate.match_predicate import match_p
56
+ from predicate.mutual_recur_predicate import mutual_recur_p
52
57
  from predicate.ne_predicate import ne_p
53
58
  from predicate.not_in_predicate import not_in_p
54
59
  from predicate.optimizer.predicate_optimizer import can_optimize, optimize
60
+ from predicate.optional_predicate import optional
61
+ from predicate.plus_predicate import plus
62
+ from predicate.predicate import and_p, or_p, xor_p
55
63
  from predicate.range_predicate import ge_le_p, ge_lt_p, gt_le_p, gt_lt_p
64
+ from predicate.recur_predicate import recur_p
65
+ from predicate.reduce_predicate import reduce_p
56
66
  from predicate.regex_predicate import regex_p
67
+ from predicate.repeat_predicate import repeat
57
68
  from predicate.set_of_predicate import is_set_of_p
58
69
  from predicate.set_predicates import (
59
70
  is_real_subset_p,
@@ -74,15 +85,18 @@ from predicate.standard_predicates import (
74
85
  root_p,
75
86
  this_p,
76
87
  )
88
+ from predicate.star_predicate import star
77
89
  from predicate.tee_predicate import tee_p
78
90
  from predicate.tuple_of_predicate import is_tuple_of_p
79
91
 
80
92
  __all__ = [
93
+ "PredicateError",
81
94
  "Spec",
82
95
  "all_p",
83
96
  "always_false_p",
84
97
  "always_p",
85
98
  "always_true_p",
99
+ "and_p",
86
100
  "any_p",
87
101
  "can_optimize",
88
102
  "comp_p",
@@ -91,6 +105,9 @@ __all__ = [
91
105
  "eq_p",
92
106
  "eq_true_p",
93
107
  "exactly_n",
108
+ "exactly_one_p",
109
+ "exactly_zero_p",
110
+ "exception_p",
94
111
  "exercise",
95
112
  "explain",
96
113
  "fn_p",
@@ -138,11 +155,13 @@ __all__ = [
138
155
  "is_list_of_p",
139
156
  "is_list_p",
140
157
  "is_lower_p",
158
+ "is_same_p",
141
159
  "is_nan_p",
142
160
  "is_none_p",
143
161
  "is_not_empty_p",
144
162
  "is_not_none_p",
145
163
  "is_odd_p",
164
+ "is_p",
146
165
  "is_predicate_of_p",
147
166
  "is_predicate_p",
148
167
  "is_range_p",
@@ -165,14 +184,21 @@ __all__ = [
165
184
  "le_p",
166
185
  "lt_p",
167
186
  "match_p",
187
+ "mutual_recur_p",
168
188
  "ne_p",
169
189
  "neg_p",
170
190
  "never_p",
191
+ "none_is_exception_p",
192
+ "none_is_false_p",
193
+ "none_is_true_p",
171
194
  "not_in_p",
172
195
  "optimize",
173
196
  "optional",
197
+ "or_p",
174
198
  "plus",
175
199
  "pos_p",
200
+ "recur_p",
201
+ "reduce_p",
176
202
  "regex_p",
177
203
  "repeat",
178
204
  "root_p",
@@ -182,6 +208,7 @@ __all__ = [
182
208
  "to_dot",
183
209
  "to_json",
184
210
  "to_latex",
211
+ "xor_p",
185
212
  "zero_p",
186
213
  ]
187
214
 
@@ -1,5 +1,8 @@
1
1
  from dataclasses import dataclass
2
- from typing import Iterable, override
2
+ from itertools import takewhile
3
+ from typing import Any, Iterable, Iterator, override
4
+
5
+ from more_itertools import ilen
3
6
 
4
7
  from predicate.helpers import first_false
5
8
  from predicate.predicate import Predicate, resolve_predicate
@@ -35,6 +38,11 @@ class AllPredicate[T](Predicate[T]):
35
38
 
36
39
  return {"reason": f"Item '{fail}' didn't match predicate {self.predicate!r}"}
37
40
 
41
+ @override
42
+ def consumes(self, iterable: Iterable[Any]) -> Iterator[int]:
43
+ consumed = takewhile(self.predicate, iterable)
44
+ yield from range(0, ilen(consumed) + 1)
45
+
38
46
 
39
47
  def all_p[T](predicate: Predicate[T]) -> AllPredicate[T]:
40
48
  """Return True if the predicate holds for each item in the iterable, otherwise False."""
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass
2
- from typing import Any, Final, override
2
+ from typing import Any, Final, Iterable, Iterator, 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 consumes(self, iterable: Iterable[Any]) -> Iterator[int]:
23
+ yield 0
24
+
21
25
  @override
22
26
  def get_klass(self) -> type:
23
27
  return type(Any)
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass
2
- from typing import Any, Final, override
2
+ from typing import Any, Final, Iterable, Iterator, 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 consumes(self, iterable: Iterable[Any]) -> Iterator[int]:
19
+ yield 1
20
+
17
21
  @override
18
22
  def get_klass(self) -> type:
19
23
  return type(Any)
@@ -1,5 +1,8 @@
1
1
  from dataclasses import dataclass
2
- from typing import Iterable, override
2
+ from itertools import takewhile
3
+ from typing import Any, Iterable, Iterator, override
4
+
5
+ from more_itertools import ilen
3
6
 
4
7
  from predicate.predicate import Predicate, resolve_predicate
5
8
 
@@ -32,6 +35,16 @@ class AnyPredicate[T](Predicate[T]):
32
35
  def explain_failure(self, iterable: Iterable[T]) -> dict:
33
36
  return {"reason": f"No item matches predicate {self.predicate!r}"}
34
37
 
38
+ @override
39
+ def consumes(self, iterable: Iterable[Any]) -> Iterator[int]:
40
+ consumed = takewhile(~self.predicate, iterable)
41
+ len_consumed = ilen(consumed)
42
+ len_iterable = ilen(iterable)
43
+ if len_consumed < len_iterable:
44
+ yield from range(len_consumed, len_iterable + 1)
45
+ else:
46
+ yield 0
47
+
35
48
 
36
49
  def any_p[T](predicate: Predicate[T]) -> AnyPredicate[T]:
37
50
  """Return True if the predicate holds for any item in the iterable, otherwise False."""
@@ -1,6 +1,6 @@
1
- from collections.abc import Iterator
2
1
  from functools import singledispatch
3
2
  from random import choice, randint
3
+ from typing import Iterator
4
4
 
5
5
  from predicate import eq_p, ge_p, gt_p, le_p, lt_p, ne_p
6
6
  from predicate.eq_predicate import EqPredicate
@@ -0,0 +1,13 @@
1
+ from typing import Any, Iterable, Iterator
2
+
3
+ from more_itertools import spy
4
+
5
+ from predicate.predicate import Predicate
6
+
7
+
8
+ def consumes(predicate: Predicate, iterable: Iterable[Any]) -> Iterator[int]:
9
+ head, _ = spy(iterable, 1)
10
+ if head:
11
+ yield from predicate.consumes(iterable)
12
+ else:
13
+ yield 0
@@ -10,7 +10,7 @@ from predicate.predicate import Predicate
10
10
 
11
11
  @dataclass
12
12
  class CountPredicate[T](Predicate[T]):
13
- """A predicate class that models the 'length' predicate."""
13
+ """A predicate class that models the 'count' predicate."""
14
14
 
15
15
  predicate: Predicate[T]
16
16
  length_p: Predicate[int]
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass
2
- from typing import Final, override
2
+ from typing import Any, Final, Iterable, Iterator, override
3
3
 
4
4
  from predicate.predicate import Predicate
5
5
 
@@ -24,6 +24,10 @@ class EqPredicate[T](Predicate[T]):
24
24
  def explain_failure(self, x: T) -> dict:
25
25
  return {"reason": f"{x} is not equal to {self.v!r}"}
26
26
 
27
+ @override
28
+ def consumes(self, iterable: Iterable[Any]) -> Iterator[int]:
29
+ yield from self.consumes_single(iterable)
30
+
27
31
 
28
32
  def eq_p[T](v: T) -> EqPredicate[T]:
29
33
  """Return True if the value is equal to the constant, otherwise False."""
@@ -0,0 +1,48 @@
1
+ from dataclasses import dataclass
2
+ from typing import Iterable, override
3
+
4
+ from predicate.predicate import Predicate
5
+
6
+
7
+ @dataclass
8
+ class ExactlyPredicate[T](Predicate[T]):
9
+ """Match exactly n instances of the given predicate."""
10
+
11
+ n: int
12
+ predicate: Predicate
13
+
14
+ def __call__(self, iterable: Iterable, *, predicates: list[Predicate], full_match: bool) -> bool:
15
+ from predicate.match_predicate import match
16
+
17
+ rest = iterable
18
+ for _ in range(self.n):
19
+ if not rest:
20
+ return False
21
+
22
+ item, *rest = rest
23
+ if not self.predicate(item):
24
+ return False
25
+ return match(rest, predicates=predicates, full_match=full_match) if predicates else True
26
+
27
+ def __repr__(self) -> str:
28
+ return f"exactly({self.n}, {self.predicate!r})"
29
+
30
+ @override
31
+ def explain_failure(self, iterable: Iterable[T], *, predicates: list[Predicate], full_match: bool) -> dict: # type: ignore
32
+ from predicate.match_predicate import reason
33
+
34
+ rest = iterable
35
+ for _ in range(self.n):
36
+ if not rest:
37
+ return {"reason": f"Not enough items in iterable, expected {self.n}"}
38
+
39
+ item, *rest = rest
40
+ if not self.predicate(item):
41
+ return self.predicate.explain(item)
42
+
43
+ return reason(rest, predicates=predicates, full_match=full_match)
44
+
45
+
46
+ def exactly_n(n: int, predicate: Predicate) -> Predicate:
47
+ """Match exactly n instances of the given predicate."""
48
+ return ExactlyPredicate(n=n, predicate=predicate)
@@ -0,0 +1,25 @@
1
+ from dataclasses import dataclass
2
+ from typing import Final
3
+
4
+ from predicate.predicate import Predicate
5
+
6
+
7
+ class PredicateError(Exception):
8
+ """Exception that will be thrown."""
9
+
10
+ pass
11
+
12
+
13
+ @dataclass
14
+ class ExceptionPredicate[T](Predicate[T]):
15
+ """A predicate class always throws an exception."""
16
+
17
+ def __call__(self, _x: T) -> bool:
18
+ raise PredicateError()
19
+
20
+ def __repr__(self) -> str:
21
+ return "exception_p"
22
+
23
+
24
+ exception_p: Final[ExceptionPredicate] = ExceptionPredicate()
25
+ """Always throw an exception."""
@@ -3,7 +3,7 @@ import sys
3
3
  from collections.abc import Iterable, Iterator
4
4
  from datetime import datetime, timedelta
5
5
  from functools import singledispatch
6
- from itertools import repeat
6
+ from itertools import cycle, repeat
7
7
  from types import UnionType
8
8
  from typing import Final, get_args
9
9
  from uuid import UUID
@@ -17,6 +17,7 @@ from predicate.any_predicate import AnyPredicate
17
17
  from predicate.count_predicate import CountPredicate
18
18
  from predicate.dict_of_predicate import DictOfPredicate, is_dict_of_p
19
19
  from predicate.eq_predicate import EqPredicate
20
+ from predicate.exactly_predicate import ExactlyPredicate
20
21
  from predicate.fn_predicate import FnPredicate
21
22
  from predicate.ge_predicate import GePredicate
22
23
  from predicate.generator.helpers import (
@@ -45,26 +46,26 @@ from predicate.is_instance_predicate import IsInstancePredicate
45
46
  from predicate.is_lambda_predicate import IsLambdaPredicate
46
47
  from predicate.is_none_predicate import IsNonePredicate
47
48
  from predicate.is_not_none_predicate import IsNotNonePredicate
49
+ from predicate.is_predicate import IsPredicate
48
50
  from predicate.is_subclass_predicate import IsSubclassPredicate
49
51
  from predicate.is_truthy_predicate import IsTruthyPredicate
50
52
  from predicate.le_predicate import LePredicate
51
53
  from predicate.list_of_predicate import ListOfPredicate, is_list_of_p
52
54
  from predicate.lt_predicate import LtPredicate
53
55
  from predicate.match_predicate import (
54
- ExactlyPredicate,
55
56
  MatchPredicate,
56
- OptionalPredicate,
57
- PlusPredicate,
58
- RepeatPredicate,
59
- StarPredicate,
60
57
  )
61
58
  from predicate.ne_predicate import NePredicate, ne_p
62
59
  from predicate.not_in_predicate import NotInPredicate
63
60
  from predicate.optimizer.predicate_optimizer import optimize
61
+ from predicate.optional_predicate import OptionalPredicate
62
+ from predicate.plus_predicate import PlusPredicate
64
63
  from predicate.predicate import AndPredicate, NotPredicate, OrPredicate, Predicate, XorPredicate
65
64
  from predicate.range_predicate import GeLePredicate, GeLtPredicate, GtLePredicate, GtLtPredicate, ge_le_p
65
+ from predicate.repeat_predicate import RepeatPredicate
66
66
  from predicate.set_of_predicate import SetOfPredicate
67
67
  from predicate.standard_predicates import is_int_p
68
+ from predicate.star_predicate import StarPredicate
68
69
  from predicate.tee_predicate import TeePredicate
69
70
  from predicate.tuple_of_predicate import TupleOfPredicate
70
71
 
@@ -547,3 +548,11 @@ def generate_is_subclass(is_subclass_predicate: IsSubclassPredicate) -> Iterator
547
548
  if non_subclasses := all_sub_classes - subclasses:
548
549
  while True:
549
550
  yield from non_subclasses
551
+
552
+
553
+ @generate_false.register
554
+ def generate_is(is_predicate: IsPredicate) -> Iterator:
555
+ singletons = (True, False, None, Ellipsis, (), NotImplemented)
556
+ selection = [v for v in singletons if v is not is_predicate.v]
557
+
558
+ yield from cycle(selection)
@@ -26,6 +26,7 @@ from predicate.any_predicate import AnyPredicate
26
26
  from predicate.count_predicate import CountPredicate
27
27
  from predicate.dict_of_predicate import DictOfPredicate, is_dict_of_p
28
28
  from predicate.eq_predicate import EqPredicate, eq_p
29
+ from predicate.exactly_predicate import ExactlyPredicate
29
30
  from predicate.fn_predicate import FnPredicate
30
31
  from predicate.ge_predicate import GePredicate, ge_p
31
32
  from predicate.generator.helpers import (
@@ -66,6 +67,7 @@ from predicate.is_instance_predicate import IsInstancePredicate
66
67
  from predicate.is_lambda_predicate import IsLambdaPredicate
67
68
  from predicate.is_none_predicate import IsNonePredicate
68
69
  from predicate.is_not_none_predicate import IsNotNonePredicate
70
+ from predicate.is_predicate import IsPredicate
69
71
  from predicate.is_predicate_of_p import IsPredicateOfPredicate
70
72
  from predicate.is_subclass_predicate import IsSubclassPredicate
71
73
  from predicate.is_truthy_predicate import IsTruthyPredicate
@@ -73,16 +75,13 @@ from predicate.le_predicate import LePredicate
73
75
  from predicate.list_of_predicate import ListOfPredicate, is_list_of_p
74
76
  from predicate.lt_predicate import LtPredicate
75
77
  from predicate.match_predicate import (
76
- ExactlyPredicate,
77
78
  MatchPredicate,
78
- OptionalPredicate,
79
- PlusPredicate,
80
- RepeatPredicate,
81
- StarPredicate,
82
79
  )
83
80
  from predicate.ne_predicate import NePredicate
84
81
  from predicate.not_in_predicate import NotInPredicate
85
82
  from predicate.optimizer.predicate_optimizer import optimize
83
+ from predicate.optional_predicate import OptionalPredicate
84
+ from predicate.plus_predicate import PlusPredicate
86
85
  from predicate.predicate import (
87
86
  AndPredicate,
88
87
  ConstrainedT,
@@ -94,12 +93,14 @@ from predicate.predicate import (
94
93
  from predicate.property_predicate import PropertyPredicate
95
94
  from predicate.range_predicate import GeLePredicate, GeLtPredicate, GtLePredicate, GtLtPredicate, ge_le_p
96
95
  from predicate.regex_predicate import RegexPredicate
96
+ from predicate.repeat_predicate import RepeatPredicate
97
97
  from predicate.set_of_predicate import SetOfPredicate
98
98
  from predicate.set_predicates import IsRealSubsetPredicate, IsSubsetPredicate
99
99
  from predicate.standard_predicates import (
100
100
  is_int_p,
101
101
  is_list_p,
102
102
  )
103
+ from predicate.star_predicate import StarPredicate
103
104
  from predicate.tee_predicate import TeePredicate
104
105
  from predicate.tuple_of_predicate import TupleOfPredicate
105
106
 
@@ -666,3 +667,8 @@ def generate_is_subclass(is_subclass_predicate: IsSubclassPredicate) -> Iterator
666
667
  subclasses = klass.__subclasses__()
667
668
  while True:
668
669
  yield from subclasses
670
+
671
+
672
+ @generate_true.register
673
+ def generate_is(is_predicate: IsPredicate) -> Iterator:
674
+ yield from repeat(is_predicate.v)
@@ -1,5 +1,6 @@
1
1
  from dataclasses import dataclass
2
- from typing import Final, Iterable, override
2
+ from itertools import islice
3
+ from typing import Any, Final, Iterable, Iterator, override
3
4
 
4
5
  from more_itertools import ilen
5
6
 
@@ -24,6 +25,13 @@ class HasLengthPredicate[T](Predicate[T]):
24
25
  def explain_failure(self, iterable: Iterable[T]) -> dict:
25
26
  return {"reason": f"Expected length {self.length_p!r}, actual: {ilen(iterable)}"}
26
27
 
28
+ @override
29
+ def consumes(self, iterable: Iterable[Any]) -> Iterator[int]:
30
+ for end in range(0, ilen(iterable) + 1):
31
+ rest = islice(iterable, 0, end)
32
+ if self(rest):
33
+ yield end
34
+
27
35
 
28
36
  def has_length_p(length_p: Predicate[int]) -> Predicate[Iterable]:
29
37
  """Return True if length of iterable is equal to value, otherwise False."""
@@ -14,7 +14,7 @@ class HasPathPredicate[T](Predicate[T]):
14
14
  def __call__(self, x: Any) -> bool:
15
15
  match x:
16
16
  case dict() as d:
17
- return match_dict(self.path, d)
17
+ return match_dict(d, path=self.path)
18
18
  case _:
19
19
  return False
20
20
 
@@ -31,7 +31,11 @@ class HasPathPredicate[T](Predicate[T]):
31
31
  return {"reason": f"Value {x} is not a dict"}
32
32
 
33
33
 
34
- def match_dict(path: list[Predicate], x: dict) -> bool:
34
+ def match_dict(
35
+ x: dict,
36
+ *,
37
+ path: list[Predicate],
38
+ ) -> bool:
35
39
  first_p, *rest = path
36
40
  found = [v for k, v in x.items() if first_p(k)]
37
41
  return any(match_rest(value, rest) for value in found)
@@ -40,7 +44,7 @@ def match_dict(path: list[Predicate], x: dict) -> bool:
40
44
  def match_rest(value: Any, rest_path: list[Predicate]) -> bool:
41
45
  match value:
42
46
  case dict() as d if rest_path:
43
- return match_dict(rest_path, d)
47
+ return match_dict(d, path=rest_path)
44
48
  case list() as l if rest_path:
45
49
  first_p, *rest = rest_path
46
50
  return first_p(l) and any(match_rest(value, rest) for value in l)
@@ -1,5 +1,5 @@
1
- from collections.abc import Iterable
2
1
  from itertools import filterfalse
2
+ from typing import Iterable
3
3
 
4
4
  from more_itertools import first
5
5
 
@@ -1,7 +1,7 @@
1
1
  from collections.abc import Callable, Container, Iterable
2
2
  from dataclasses import dataclass
3
3
  from datetime import datetime
4
- from typing import Hashable, Iterator, get_origin, override
4
+ from typing import Any, Hashable, Iterator, get_origin, override
5
5
  from uuid import UUID
6
6
 
7
7
  from predicate.helpers import join_with_or
@@ -12,7 +12,7 @@ from predicate.predicate import Predicate
12
12
  class IsInstancePredicate[T](Predicate[T]):
13
13
  """A predicate class that models the 'isinstance' predicate."""
14
14
 
15
- instance_klass: type | tuple
15
+ instance_klass: tuple
16
16
 
17
17
  def __call__(self, x: object) -> bool:
18
18
  # This is different from standard Python behaviour: a False/True value is not an int!
@@ -33,17 +33,17 @@ class IsInstancePredicate[T](Predicate[T]):
33
33
  @override
34
34
  def explain_failure(self, x: T) -> dict:
35
35
  def class_names() -> Iterator[str]:
36
- match self.instance_klass:
37
- case tuple() as klasses:
38
- for klass in klasses:
39
- yield klass.__name__
40
- case _:
41
- yield self.instance_klass.__name__
36
+ for klass in self.instance_klass:
37
+ yield klass.__name__
42
38
 
43
39
  klasses = join_with_or(list(class_names()))
44
40
 
45
41
  return {"reason": f"{x} is not an instance of type {klasses}"}
46
42
 
43
+ @override
44
+ def consumes(self, iterable: Iterable[Any]) -> Iterator[int]:
45
+ yield from self.consumes_single(iterable)
46
+
47
47
 
48
48
  def is_instance_p(*klass: type) -> Predicate:
49
49
  """Return True if value is an instance of one of the classes, otherwise False."""
@@ -1,7 +1,8 @@
1
1
  from dataclasses import dataclass
2
2
  from typing import Final, override
3
3
 
4
- from predicate.predicate import Predicate
4
+ from predicate import exception_p
5
+ from predicate.predicate import Predicate, or_p, predicate_partial
5
6
 
6
7
 
7
8
  @dataclass
@@ -21,3 +22,11 @@ class IsNonePredicate[T](Predicate[T]):
21
22
 
22
23
  is_none_p: Final[IsNonePredicate] = IsNonePredicate()
23
24
  """Return True if value is None, otherwise False."""
25
+
26
+
27
+ none_is_true_p = predicate_partial(or_p, is_none_p)
28
+ """Return True if value is None, otherwise the result of the predicate."""
29
+
30
+
31
+ none_is_exception_p = predicate_partial(or_p, is_none_p & exception_p)
32
+ """Raise an exception if value is None, otherwise returns the result of the predicate."""
@@ -1,7 +1,7 @@
1
1
  from dataclasses import dataclass
2
2
  from typing import Final, override
3
3
 
4
- from predicate.predicate import Predicate
4
+ from predicate.predicate import Predicate, and_p, predicate_partial
5
5
 
6
6
 
7
7
  @dataclass
@@ -21,3 +21,7 @@ class IsNotNonePredicate[T](Predicate[T]):
21
21
 
22
22
  is_not_none_p: Final[IsNotNonePredicate] = IsNotNonePredicate()
23
23
  """Return True if value is not None, otherwise False."""
24
+
25
+
26
+ none_is_false_p = predicate_partial(and_p, is_not_none_p)
27
+ """Return False if value is None, otherwise the result of the predicate."""
@@ -0,0 +1,30 @@
1
+ from dataclasses import dataclass
2
+ from typing import override
3
+
4
+ from predicate.predicate import Predicate
5
+
6
+
7
+ @dataclass
8
+ class IsPredicate[T](Predicate[T]):
9
+ """A predicate class that models the 'is' predicate."""
10
+
11
+ v: T
12
+
13
+ def __call__(self, x: T) -> bool:
14
+ return x is self.v
15
+
16
+ def __repr__(self) -> str:
17
+ return f"is_p({self.v!r})"
18
+
19
+ @override
20
+ def get_klass(self) -> type:
21
+ return type(self.v)
22
+
23
+ @override
24
+ def explain_failure(self, x: T) -> dict:
25
+ return {"reason": f"{x} does not refer to {self.v!r}"}
26
+
27
+
28
+ def is_p[T](v: T) -> IsPredicate[T]:
29
+ """Return True if the value is equal to the constant, otherwise False."""
30
+ return IsPredicate(v=v)