safeshield 1.2.2__tar.gz → 1.3.2__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.

Potentially problematic release.


This version of safeshield might be problematic. Click here for more details.

Files changed (39) hide show
  1. {safeshield-1.2.2 → safeshield-1.3.2}/PKG-INFO +4 -1
  2. {safeshield-1.2.2 → safeshield-1.3.2}/README.md +4 -1
  3. {safeshield-1.2.2 → safeshield-1.3.2}/safeshield.egg-info/PKG-INFO +4 -1
  4. {safeshield-1.2.2 → safeshield-1.3.2}/setup.py +1 -1
  5. {safeshield-1.2.2 → safeshield-1.3.2}/validator/core/validator.py +3 -3
  6. {safeshield-1.2.2 → safeshield-1.3.2}/validator/rules/array.py +6 -6
  7. safeshield-1.2.2/validator/rules/conditional.py → safeshield-1.3.2/validator/rules/basic.py +110 -205
  8. {safeshield-1.2.2 → safeshield-1.3.2}/validator/rules/comparison.py +12 -12
  9. safeshield-1.3.2/validator/rules/conditional.py +165 -0
  10. {safeshield-1.2.2 → safeshield-1.3.2}/validator/rules/date.py +7 -7
  11. {safeshield-1.2.2 → safeshield-1.3.2}/validator/rules/files.py +5 -5
  12. {safeshield-1.2.2 → safeshield-1.3.2}/validator/rules/format.py +8 -8
  13. {safeshield-1.2.2 → safeshield-1.3.2}/validator/rules/string.py +8 -15
  14. {safeshield-1.2.2 → safeshield-1.3.2}/validator/rules/type.py +10 -3
  15. safeshield-1.3.2/validator/rules/utilities.py +138 -0
  16. safeshield-1.3.2/validator/services/rule_error_handler.py +239 -0
  17. safeshield-1.2.2/validator/rules/basic.py +0 -41
  18. safeshield-1.2.2/validator/rules/utilities.py +0 -213
  19. safeshield-1.2.2/validator/services/rule_error_handler.py +0 -42
  20. {safeshield-1.2.2 → safeshield-1.3.2}/LICENSE +0 -0
  21. {safeshield-1.2.2 → safeshield-1.3.2}/safeshield.egg-info/SOURCES.txt +0 -0
  22. {safeshield-1.2.2 → safeshield-1.3.2}/safeshield.egg-info/dependency_links.txt +0 -0
  23. {safeshield-1.2.2 → safeshield-1.3.2}/safeshield.egg-info/requires.txt +0 -0
  24. {safeshield-1.2.2 → safeshield-1.3.2}/safeshield.egg-info/top_level.txt +0 -0
  25. {safeshield-1.2.2 → safeshield-1.3.2}/setup.cfg +0 -0
  26. {safeshield-1.2.2 → safeshield-1.3.2}/validator/__init__.py +0 -0
  27. {safeshield-1.2.2 → safeshield-1.3.2}/validator/core/__init__.py +0 -0
  28. {safeshield-1.2.2 → safeshield-1.3.2}/validator/database/__init__.py +0 -0
  29. {safeshield-1.2.2 → safeshield-1.3.2}/validator/database/detector.py +0 -0
  30. {safeshield-1.2.2 → safeshield-1.3.2}/validator/database/manager.py +0 -0
  31. {safeshield-1.2.2 → safeshield-1.3.2}/validator/exceptions.py +0 -0
  32. {safeshield-1.2.2 → safeshield-1.3.2}/validator/factory.py +0 -0
  33. {safeshield-1.2.2 → safeshield-1.3.2}/validator/rules/__init__.py +0 -0
  34. {safeshield-1.2.2 → safeshield-1.3.2}/validator/rules/base.py +0 -0
  35. {safeshield-1.2.2 → safeshield-1.3.2}/validator/services/__init__.py +0 -0
  36. {safeshield-1.2.2 → safeshield-1.3.2}/validator/services/rule_conflict.py +0 -0
  37. {safeshield-1.2.2 → safeshield-1.3.2}/validator/services/rule_preparer.py +0 -0
  38. {safeshield-1.2.2 → safeshield-1.3.2}/validator/utils/__init__.py +0 -0
  39. {safeshield-1.2.2 → safeshield-1.3.2}/validator/utils/string.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: safeshield
3
- Version: 1.2.2
3
+ Version: 1.3.2
4
4
  Summary: Library for Help Validation Control
5
5
  Home-page: https://github.com/WunsunTarniho/py-guard
6
6
  Author: Wunsun Tarniho
@@ -58,3 +58,6 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
58
58
 
59
59
  ## v1.2.2 (2024-07-20)
60
60
  - Fixed: Error when connect use postgres.
61
+
62
+ ## v1.3.2 (2024-07-20)
63
+ - Refinement: Groups Inheritance Rule Class.
@@ -38,4 +38,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
38
38
  - Fixed: Bug unique and exist rule.
39
39
 
40
40
  ## v1.2.2 (2024-07-20)
41
- - Fixed: Error when connect use postgres.
41
+ - Fixed: Error when connect use postgres.
42
+
43
+ ## v1.3.2 (2024-07-20)
44
+ - Refinement: Groups Inheritance Rule Class.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: safeshield
3
- Version: 1.2.2
3
+ Version: 1.3.2
4
4
  Summary: Library for Help Validation Control
5
5
  Home-page: https://github.com/WunsunTarniho/py-guard
6
6
  Author: Wunsun Tarniho
@@ -58,3 +58,6 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
58
58
 
59
59
  ## v1.2.2 (2024-07-20)
60
60
  - Fixed: Error when connect use postgres.
61
+
62
+ ## v1.3.2 (2024-07-20)
63
+ - Refinement: Groups Inheritance Rule Class.
@@ -7,7 +7,7 @@ def get_requirements():
7
7
 
8
8
  setup(
9
9
  name="safeshield", # Nama package di PyPI
10
- version="1.2.2",
10
+ version="1.3.2",
11
11
  packages=find_packages(),
12
12
  install_requires=get_requirements(),
13
13
  author="Wunsun Tarniho",
@@ -221,18 +221,18 @@ class Validator:
221
221
  try:
222
222
  rule.set_validator(self)
223
223
 
224
- # Format field name untuk pesan error (otomatis tangkap path aktual)
225
224
  display_field = getattr(self, '_current_actual_path', field)
226
225
 
227
226
  if not rule.validate(field, value, getattr(rule, 'params', [])):
228
227
  msg = rule.message(display_field, getattr(rule, 'params', []))
229
- self.error_handler.add_error(display_field, rule.rule_name, msg, value)
228
+ self.error_handler.add_error(display_field, rule.rule_name, getattr(rule, 'params', []), msg, value)
229
+
230
230
  return False
231
231
  else:
232
232
  return True
233
233
  except Exception as e:
234
234
  display_field = getattr(self, '_current_actual_path', field)
235
- self.error_handler.add_error(display_field, rule.rule_name, str(e), value)
235
+ self.error_handler.add_error(display_field, rule.rule_name, getattr(rule, 'params', []), str(e), value)
236
236
  return False
237
237
 
238
238
  def _get_nested_value(self, path: str) -> Any:
@@ -19,8 +19,8 @@ class ArrayRule(ValidationRule):
19
19
 
20
20
  def message(self, field: str, params: List[str]) -> str:
21
21
  if params:
22
- return f"The {field} must contain the keys: {', '.join(self._missing_keys)}."
23
- return f"The {field} must be an array."
22
+ return f"The :attribute must contain the keys: {', '.join(self._missing_keys)}."
23
+ return f"The :attribute must be an array."
24
24
 
25
25
  class ContainsRule(ValidationRule):
26
26
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
@@ -44,7 +44,7 @@ class ContainsRule(ValidationRule):
44
44
  return False
45
45
 
46
46
  def message(self, field: str, params: List[str]) -> str:
47
- return f"The :name must contain {params[0]}"
47
+ return f"The :attribute must contain {params[0]}"
48
48
 
49
49
  class DistinctRule(ValidationRule):
50
50
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
@@ -54,7 +54,7 @@ class DistinctRule(ValidationRule):
54
54
  return len(value) == len(set(value))
55
55
 
56
56
  def message(self, field: str, params: List[str]) -> str:
57
- return f"The :name must contain unique values"
57
+ return f"The :attribute must contain unique values"
58
58
 
59
59
  class InArrayRule(ValidationRule):
60
60
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
@@ -64,7 +64,7 @@ class InArrayRule(ValidationRule):
64
64
  return str(value) in params
65
65
 
66
66
  def message(self, field: str, params: List[str]) -> str:
67
- return f"The :name must be one of: {', '.join(params)}"
67
+ return f"The :attribute must be one of: {', '.join(params)}"
68
68
 
69
69
  class InArrayKeysRule(ValidationRule):
70
70
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
@@ -74,4 +74,4 @@ class InArrayKeysRule(ValidationRule):
74
74
  return any(key in value for key in params)
75
75
 
76
76
  def message(self, field: str, params: List[str]) -> str:
77
- return f"The :name must contain at least one of these keys: {', '.join(params)}"
77
+ return f"The :attribute must contain at least one of these keys: {', '.join(params)}"
@@ -1,234 +1,123 @@
1
1
  from .base import ValidationRule
2
2
  from typing import Any, Dict, List, Optional, Set, Union, Tuple, Type
3
- from enum import Enum
4
- import re
5
- import inspect
6
- from collections.abc import Iterable
7
3
 
8
4
  # =============================================
9
- # CONDITIONAL VALIDATION RULES
5
+ # BASIC VALIDATION RULES
10
6
  # =============================================
11
7
 
12
- class RequiredIfRule(ValidationRule):
8
+ class AnyOfRule(ValidationRule):
13
9
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
14
- if len(params) < 2 or len(params) % 2 != 0:
15
- return True
16
-
17
- conditions = list(zip(params[::2], params[1::2]))
18
-
19
- condition_met = False
20
- for other_field, expected_value in conditions:
21
- if not other_field or expected_value is None:
22
- continue
23
-
24
- actual_value = self.get_field_value(other_field, '')
25
- if actual_value == expected_value:
26
- condition_met = True
27
- break
28
-
29
- if not condition_met:
30
- return True
31
-
32
- return not self.is_empty(value)
33
-
34
- def message(self, field: str, params: List[str]) -> str:
35
- valid_conditions = []
36
- if len(params) >= 2 and len(params) % 2 == 0:
37
- valid_conditions = [
38
- f"{params[i]} = {params[i+1]}"
39
- for i in range(0, len(params), 2)
40
- if params[i] and params[i+1] is not None
41
- ]
42
-
43
- if not valid_conditions:
44
- return f"Invalid required_if rule configuration for {field}"
45
-
46
- return f"The :name field is required when any of these are true: {', '.join(valid_conditions)}"
47
-
48
- class RequiredAllIfRule(ValidationRule):
49
- def validate(self, field: str, value: Any, params: List[str]) -> bool:
50
- if len(params) < 2:
10
+ if not params:
51
11
  return False
52
-
53
- conditions = [(f.strip(), v.strip()) for f, v in zip(params[::2], params[1::2])]
54
-
55
- all_conditions_met = all(
56
- self.get_field_value(f) == v
57
- for f, v in conditions
58
- )
59
-
60
- if not all_conditions_met:
61
- return True
62
-
63
- return not self.is_empty(value)
12
+ return any(self.get_field_value(param, param) == value for param in params)
64
13
 
65
14
  def message(self, field: str, params: List[str]) -> str:
66
- conditions = " AND ".join(f"{f} = {v}" for f, v in zip(params[::2], params[1::2]))
67
- return f"The :name field is required when ALL conditions are met: {conditions}"
15
+ return f"The :attribute must be one of: {', '.join(params)}"
68
16
 
69
- class RequiredUnlessRule(ValidationRule):
17
+ class BailRule(ValidationRule):
70
18
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
71
- if len(params) < 2:
72
- return False
73
-
74
- other_field, other_value = params[0], params[1]
75
- actual_value = self.get_field_value(other_field, '')
76
-
77
- if actual_value == other_value:
78
- return True
79
-
80
- return not self.is_empty(value)
19
+ self.validator._stop_on_first_failure = True
20
+ return True
81
21
 
82
22
  def message(self, field: str, params: List[str]) -> str:
83
- return f"The :name field is required unless {params[0]} is {params[1]}."
23
+ return ""
84
24
 
85
- class RequiredWithRule(ValidationRule):
25
+ class RequiredRule(ValidationRule):
86
26
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
87
- if not params:
88
- return False
89
-
90
- if any(f in self.validator.data for f in params):
91
- return not self.is_empty(value)
92
- return True
27
+ return not self.is_empty(value)
93
28
 
94
29
  def message(self, field: str, params: List[str]) -> str:
95
- return f"The :name field is required when {', '.join(params)} is present."
30
+ return f"The :attribute field is required."
96
31
 
97
- class RequiredWithAllRule(ValidationRule):
32
+ class ProhibitedRule(ValidationRule):
98
33
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
99
- if not params:
100
- return False
101
-
102
- if all(f in self.validator.data for f in params):
103
- return not self.is_empty(value)
104
- return True
34
+ return self.is_empty(value)
105
35
 
106
36
  def message(self, field: str, params: List[str]) -> str:
107
- return f"The :name field is required when all of {', '.join(params)} are present."
108
-
109
- class RequiredWithoutRule(ValidationRule):
37
+ return "The :attribute field is must be empty."
38
+
39
+ class NullableRule(ValidationRule):
110
40
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
111
- if not params:
112
- return False
113
-
114
- if any(f not in self.validator.data for f in params):
115
- return not self.is_empty(value)
116
41
  return True
117
42
 
118
43
  def message(self, field: str, params: List[str]) -> str:
119
- return f"The :name field is required when {', '.join(params)} is not present."
44
+ return f"The :attribute may be null."
120
45
 
121
- class RequiredWithoutAllRule(ValidationRule):
46
+ class FilledRule(ValidationRule):
122
47
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
123
- if not params:
124
- return False
125
-
126
- if all(f not in self.validator.data for f in params):
127
- return not self.is_empty(value)
128
- return True
48
+ return value not in ('', None)
129
49
 
130
50
  def message(self, field: str, params: List[str]) -> str:
131
- return f"The :name field is required when none of {', '.join(params)} are present."
51
+ return f"The :attribute field must have a value."
132
52
 
133
- class ProhibitedIfRule(ValidationRule):
53
+ class PresentRule(ValidationRule):
134
54
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
135
- if len(params) < 2:
136
- return False
137
-
138
- other_field, other_value = params[0], params[1]
139
- actual_value = self.get_field_value(other_field, '')
140
-
141
- if actual_value != other_value:
142
- return True
143
-
144
- return self.is_empty(value)
55
+ return field in self.validator.data
145
56
 
146
57
  def message(self, field: str, params: List[str]) -> str:
147
- return f"The :name field is prohibited when {params[0]} is {params[1]}."
148
-
149
- class ProhibitedUnlessRule(ValidationRule):
58
+ return f"The :attribute field must be present."
59
+
60
+ class MissingRule(ValidationRule):
150
61
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
151
- if len(params) < 2:
152
- return False
153
-
154
- other_field, other_value = params[0], params[1]
155
- actual_value = self.get_field_value(other_field, '')
156
-
157
- if actual_value == other_value:
158
- return True
159
-
160
- return self.is_empty(value)
62
+ return value is None
161
63
 
162
64
  def message(self, field: str, params: List[str]) -> str:
163
- return f"The :name field is prohibited unless {params[0]} is {params[1]}."
164
-
165
- class FilledIfRule(ValidationRule):
65
+ return f"The :attribute field must be missing."
66
+
67
+ class ProhibitsRule(ValidationRule):
166
68
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
167
- if len(params) < 2:
168
- return False
169
-
170
- other_field, other_value = params[0], params[1]
171
- actual_value = self.get_field_value(other_field, '')
172
-
173
- if actual_value != other_value:
69
+ if not params or value is None:
174
70
  return True
175
-
176
- return value not in ('', None)
71
+ return all(self.get_field_value(param, param) in (None, 'None') for param in params)
177
72
 
178
73
  def message(self, field: str, params: List[str]) -> str:
179
- return f"The :name field must be filled when {params[0]} is {params[1]}."
180
-
181
- class RegexRule(ValidationRule):
182
- def validate(self, field: str, value: Any, params: List[str]) -> bool:
183
- if not params or not isinstance(value, str):
184
- return False
185
- try:
186
- return bool(re.fullmatch(params[0], value))
187
- except re.error:
188
- return False
74
+ return f"When :attribute is present, {', '.join(params)} must be absent."
189
75
 
190
- def message(self, field: str, params: List[str]) -> str:
191
- return f"The :name format is invalid."
192
-
193
- class NotRegexRule(ValidationRule):
76
+ class AcceptedRule(ValidationRule):
194
77
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
195
- if not params or not isinstance(value, str):
196
- return True
197
- print(not bool(re.search(params[0], value)))
198
- try:
199
- return not bool(re.search(params[0], value))
200
- except re.error:
201
- return True
78
+ value = self.get_field_value(value, value)
79
+
80
+ if isinstance(value, str):
81
+ return value.lower() in ['yes', 'on', '1', 1, True, 'true', 'True']
82
+ if isinstance(value, int):
83
+ return value == 1
84
+ if isinstance(value, bool):
85
+ return value
86
+ return False
202
87
 
203
88
  def message(self, field: str, params: List[str]) -> str:
204
- return f"The :name format is invalid."
89
+ return f"The :attribute must be accepted."
205
90
 
206
- class InRule(ValidationRule):
91
+ class DeclinedRule(ValidationRule):
207
92
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
208
- allowed_values = self._parse_option_values(field, params)
209
- return (str(value) in allowed_values or value in allowed_values)
210
-
93
+ value = self.get_field_value(value, value)
94
+
95
+ if isinstance(value, str):
96
+ return value.lower() in ['no', 'off', '0', 0, False, 'false', 'False']
97
+ if isinstance(value, int):
98
+ return value == 0
99
+ if isinstance(value, bool):
100
+ return not value
101
+ return False
102
+
211
103
  def message(self, field: str, params: List[str]) -> str:
212
- allowed_values = self._parse_option_values(field, params)
213
- return f"The selected :name must be in : {', '.join(map(str, allowed_values))}"
104
+ return f"The :attribute must be declined."
214
105
 
215
- class NotInRule(ValidationRule):
106
+ class SometimesRule(ValidationRule):
216
107
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
217
- not_allowed_values = self._parse_option_values(field, params)
218
- return str(value) not in not_allowed_values
108
+ return True
219
109
 
220
110
  def message(self, field: str, params: List[str]) -> str:
221
- not_allowed_values = self._parse_option_values(field, params)
222
- return f"The selected :name must be not in : {', '.join(map(str, not_allowed_values))}"
111
+ return ""
223
112
 
224
- class EnumRule(ValidationRule):
113
+ class ExcludeRule(ValidationRule):
225
114
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
226
- allowed_values = self._parse_option_values(field, params)
227
- return (str(value) in allowed_values or value in allowed_values)
228
-
115
+ self.validator._is_exclude = True
116
+
117
+ return True
118
+
229
119
  def message(self, field: str, params: List[str]) -> str:
230
- allowed_values = self._parse_option_values(field, params)
231
- return f"The :name must be one of: {', '.join(map(str, allowed_values))}"
120
+ return ""
232
121
 
233
122
  class UniqueRule(ValidationRule):
234
123
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
@@ -239,7 +128,6 @@ class UniqueRule(ValidationRule):
239
128
  column = field if len(params) == 1 else params[1]
240
129
 
241
130
  try:
242
- # Optional: handle ignore case (id)
243
131
  ignore_id = None
244
132
  if len(params) > 2 and params[2].startswith('ignore:'):
245
133
  ignore_field = params[2].split(':')[1]
@@ -250,7 +138,7 @@ class UniqueRule(ValidationRule):
250
138
  return False
251
139
 
252
140
  def message(self, field: str, params: List[str]) -> str:
253
- return f"The :name has already been taken."
141
+ return f"The :attribute has already been taken."
254
142
 
255
143
  class ExistsRule(ValidationRule):
256
144
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
@@ -267,7 +155,7 @@ class ExistsRule(ValidationRule):
267
155
  return False
268
156
 
269
157
  def message(self, field: str, params: List[str]) -> str:
270
- return f"The selected :name is invalid."
158
+ return f"The selected :attribute is invalid."
271
159
 
272
160
  class ConfirmedRule(ValidationRule):
273
161
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
@@ -276,7 +164,7 @@ class ConfirmedRule(ValidationRule):
276
164
  return value == self.get_field_value(confirmation_field, '')
277
165
 
278
166
  def message(self, field: str, params: List[str]) -> str:
279
- return f"The :name confirmation does not match."
167
+ return f"The :attribute confirmation does not match."
280
168
 
281
169
  class SameRule(ValidationRule):
282
170
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
@@ -285,7 +173,7 @@ class SameRule(ValidationRule):
285
173
  return value == self.get_field_value(params[0])
286
174
 
287
175
  def message(self, field: str, params: List[str]) -> str:
288
- return f"The :name and {params[0]} must match."
176
+ return f"The :attribute and {params[0]} must match."
289
177
 
290
178
  class DifferentRule(ValidationRule):
291
179
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
@@ -294,39 +182,56 @@ class DifferentRule(ValidationRule):
294
182
  return value != self.get_field_value(params[0])
295
183
 
296
184
  def message(self, field: str, params: List[str]) -> str:
297
- return f"The :name and {params[0]} must be different."
298
-
299
- class AcceptedRule(ValidationRule):
185
+ return f"The :attribute and {params[0]} must be different."
186
+
187
+ class RegexRule(ValidationRule):
300
188
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
301
- if isinstance(value, str):
302
- return value.lower() in ['yes', 'on', '1', 'true']
303
- if isinstance(value, int):
304
- return value == 1
305
- if isinstance(value, bool):
306
- return value
307
- return False
189
+ if not params or not isinstance(value, str):
190
+ return False
191
+ try:
192
+ return bool(re.fullmatch(params[0], value))
193
+ except re.error:
194
+ return False
308
195
 
309
196
  def message(self, field: str, params: List[str]) -> str:
310
- return f"The :name must be accepted."
197
+ return f"The :attribute format is invalid."
311
198
 
312
- class DeclinedRule(ValidationRule):
199
+ class NotRegexRule(ValidationRule):
313
200
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
314
- if isinstance(value, str):
315
- return value.lower() in ['no', 'off', '0', 'false']
316
- if isinstance(value, int):
317
- return value == 0
318
- if isinstance(value, bool):
319
- return not value
320
- return False
201
+ if not params or not isinstance(value, str):
202
+ return True
203
+ print(not bool(re.search(params[0], value)))
204
+ try:
205
+ return not bool(re.search(params[0], value))
206
+ except re.error:
207
+ return True
321
208
 
322
209
  def message(self, field: str, params: List[str]) -> str:
323
- return f"The :name must be declined."
210
+ return f"The :attribute format is invalid."
324
211
 
212
+ class InRule(ValidationRule):
213
+ def validate(self, field: str, value: Any, params: List[str]) -> bool:
214
+ allowed_values = self._parse_option_values(field, params)
215
+ return (str(value) in allowed_values or value in allowed_values)
325
216
 
326
- class BailRule(ValidationRule):
217
+ def message(self, field: str, params: List[str]) -> str:
218
+ allowed_values = self._parse_option_values(field, params)
219
+ return f"The selected :attribute must be in : {', '.join(map(str, allowed_values))}"
220
+
221
+ class NotInRule(ValidationRule):
327
222
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
328
- self.validator._stop_on_first_failure = True
329
- return True
223
+ not_allowed_values = self._parse_option_values(field, params)
224
+ return str(value) not in not_allowed_values
330
225
 
331
226
  def message(self, field: str, params: List[str]) -> str:
332
- return ""
227
+ not_allowed_values = self._parse_option_values(field, params)
228
+ return f"The selected :attribute must be not in : {', '.join(map(str, not_allowed_values))}"
229
+
230
+ class EnumRule(ValidationRule):
231
+ def validate(self, field: str, value: Any, params: List[str]) -> bool:
232
+ allowed_values = self._parse_option_values(field, params)
233
+ return (str(value) in allowed_values or value in allowed_values)
234
+
235
+ def message(self, field: str, params: List[str]) -> str:
236
+ allowed_values = self._parse_option_values(field, params)
237
+ return f"The :attribute must be one of: {', '.join(map(str, allowed_values))}"
@@ -27,7 +27,7 @@ class MinRule(ValidationRule):
27
27
  return False
28
28
 
29
29
  def message(self, field: str, params: List[str]) -> str:
30
- return f"The :name must be at least {params[0]}."
30
+ return f"The :attribute must be at least {params[0]}."
31
31
 
32
32
  class MaxRule(ValidationRule):
33
33
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
@@ -85,13 +85,13 @@ class MaxRule(ValidationRule):
85
85
  def message(self, field: str, params: List[str]) -> str:
86
86
  value = self.get_field_value(field)
87
87
  if value is None:
88
- return f"The {field} must not exceed {params[0]}"
88
+ return f"The :attribute must not exceed {params[0]}"
89
89
 
90
90
  # Check all possible file size attributes
91
91
  file_attrs = ['content_length', 'size', 'fileno']
92
92
  if any(hasattr(value, attr) for attr in file_attrs):
93
- return f"File {field} must not exceed {params[0]} bytes"
94
- return f"The {field} must not exceed {params[0]}"
93
+ return f"File :attribute must not exceed {params[0]} bytes"
94
+ return f"The :attribute must not exceed {params[0]}"
95
95
 
96
96
  class BetweenRule(ValidationRule):
97
97
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
@@ -136,8 +136,8 @@ class BetweenRule(ValidationRule):
136
136
  def message(self, field: str, params: List[str]) -> str:
137
137
  value = self.get_field_value(field)
138
138
  if hasattr(value, 'content_length') or hasattr(value, 'size'):
139
- return f"File {field} must be between {params[0]} and {params[1]} bytes"
140
- return f"The {field} must be between {params[0]} and {params[1]}"
139
+ return f"File :attribute must be between {params[0]} and {params[1]} bytes"
140
+ return f"The :attribute must be between {params[0]} and {params[1]}"
141
141
 
142
142
  class SizeRule(ValidationRule):
143
143
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
@@ -182,14 +182,14 @@ class SizeRule(ValidationRule):
182
182
  def message(self, field: str, params: List[str]) -> str:
183
183
  value = self.get_field_value(field)
184
184
  if value is None:
185
- return f"The {field} must be exactly {params[0]}"
185
+ return f"The :attribute must be exactly {params[0]}"
186
186
 
187
187
  # Check for file attributes
188
188
  file_attrs = ['content_length', 'size', 'fileno']
189
189
  if any(hasattr(value, attr) for attr in file_attrs):
190
- return f"File {field} must be exactly {params[0]} bytes"
190
+ return f"File :attribute must be exactly {params[0]} bytes"
191
191
 
192
- return f"The {field} must be exactly {params[0]}"
192
+ return f"The :attribute must be exactly {params[0]}"
193
193
 
194
194
  class DigitsRule(ValidationRule):
195
195
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
@@ -204,7 +204,7 @@ class DigitsRule(ValidationRule):
204
204
  return value.isdigit() and len(value) == digits
205
205
 
206
206
  def message(self, field: str, params: List[str]) -> str:
207
- return f"The :name must be {params[0]} digits."
207
+ return f"The :attribute must be {params[0]} digits."
208
208
 
209
209
  class DigitsBetweenRule(ValidationRule):
210
210
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
@@ -220,7 +220,7 @@ class DigitsBetweenRule(ValidationRule):
220
220
  return value.isdigit() and min_digits <= len(value) <= max_digits
221
221
 
222
222
  def message(self, field: str, params: List[str]) -> str:
223
- return f"The :name must be between {params[0]} and {params[1]} digits."
223
+ return f"The :attribute must be between {params[0]} and {params[1]} digits."
224
224
 
225
225
  class MultipleOfRule(ValidationRule):
226
226
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
@@ -237,4 +237,4 @@ class MultipleOfRule(ValidationRule):
237
237
  return False
238
238
 
239
239
  def message(self, field: str, params: List[str]) -> str:
240
- return f"The :name must be a multiple of {params[0]}."
240
+ return f"The :attribute must be a multiple of {params[0]}."