safeshield 1.2.2__py3-none-any.whl → 1.4.2__py3-none-any.whl

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.
@@ -1,11 +1,11 @@
1
- from .base import ValidationRule
2
- from typing import Any, Dict, List, Optional, Set, Union, Tuple, Type
1
+ from .base import Rule
2
+ from typing import Any, Dict, List, Optional, Set, Union, Tuple, Type, Callable
3
3
 
4
4
  # =============================================
5
5
  # COMPARISON VALIDATION RULES
6
6
  # =============================================
7
7
 
8
- class MinRule(ValidationRule):
8
+ class MinRule(Rule):
9
9
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
10
10
  if not params:
11
11
  return False
@@ -27,9 +27,9 @@ 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
- class MaxRule(ValidationRule):
32
+ class MaxRule(Rule):
33
33
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
34
34
  if not params or len(params) < 1:
35
35
  return False
@@ -85,15 +85,15 @@ 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
- class BetweenRule(ValidationRule):
96
+ class BetweenRule(Rule):
97
97
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
98
98
  if len(params) != 2:
99
99
  return False
@@ -136,10 +136,10 @@ 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
- class SizeRule(ValidationRule):
142
+ class SizeRule(Rule):
143
143
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
144
144
  if not params or len(params) < 1:
145
145
  return False
@@ -182,59 +182,149 @@ 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
- class DigitsRule(ValidationRule):
194
+
195
+ class UniqueRule(Rule):
195
196
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
196
- if not params or not isinstance(value, str):
197
+ if not params or not hasattr(self.validator, 'db_manager') or not self.validator.db_manager:
197
198
  return False
198
199
 
200
+ table = params[0]
201
+ column = field if len(params) == 1 else params[1]
202
+
199
203
  try:
200
- digits = int(params[0])
201
- except ValueError:
204
+ ignore_id = None
205
+ if len(params) > 2 and params[2].startswith('ignore:'):
206
+ ignore_field = params[2].split(':')[1]
207
+ ignore_id = self.get_field_value(ignore_field)
208
+ return self.validator.db_manager.is_unique(table, column, value, ignore_id)
209
+ except Exception as e:
210
+ print(f"Database error in UniqueRule: {e}")
202
211
  return False
203
-
204
- return value.isdigit() and len(value) == digits
205
212
 
206
213
  def message(self, field: str, params: List[str]) -> str:
207
- return f"The :name must be {params[0]} digits."
214
+ return f"The :attribute has already been taken."
208
215
 
209
- class DigitsBetweenRule(ValidationRule):
216
+ class ExistsRule(Rule):
210
217
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
211
- if len(params) < 2 or not isinstance(value, str):
218
+ if not params or not hasattr(self.validator, 'db_manager') or not self.validator.db_manager:
212
219
  return False
213
220
 
221
+ table = params[0]
222
+ column = field if len(params) == 1 else params[1]
223
+
214
224
  try:
215
- min_digits = int(params[0])
216
- max_digits = int(params[1])
217
- except ValueError:
225
+ return self.validator.db_manager.exists(table, column, value)
226
+ except Exception as e:
227
+ print(f"Database error in ExistsRule: {e}")
218
228
  return False
219
-
220
- return value.isdigit() and min_digits <= len(value) <= max_digits
221
229
 
222
230
  def message(self, field: str, params: List[str]) -> str:
223
- return f"The :name must be between {params[0]} and {params[1]} digits."
231
+ return f"The selected :attribute is invalid."
232
+
233
+ class SameRule(Rule):
234
+ def validate(self, field: str, value: Any, params: List[str]) -> bool:
235
+ if not params:
236
+ return False
237
+ return value == self.get_field_value(params[0])
238
+
239
+ def message(self, field: str, params: List[str]) -> str:
240
+ return f"The :attribute and {params[0]} must match."
224
241
 
225
- class MultipleOfRule(ValidationRule):
242
+ class DifferentRule(Rule):
226
243
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
227
244
  if not params:
228
245
  return False
229
-
230
- try:
231
- divisor = float(params[0])
232
- if divisor == 0:
233
- return False
234
- num = float(value)
235
- return num % divisor == 0
236
- except (ValueError, TypeError):
246
+
247
+ return str(value) != self.get_field_value(params[0])
248
+
249
+ def message(self, field: str, params: List[str]) -> str:
250
+ return f"The :attribute and {params[0]} must be different."
251
+
252
+ class InRule(Rule):
253
+ def validate(self, field: str, value: Any, params: List[str]) -> bool:
254
+ allowed_values = self._parse_option_values(field, params)
255
+ return (str(value) in allowed_values or value in allowed_values)
256
+
257
+ def message(self, field: str, params: List[str]) -> str:
258
+ allowed_values = self._parse_option_values(field, params)
259
+ return f"The selected :attribute must be in : {', '.join(map(str, allowed_values))}"
260
+
261
+ class NotInRule(Rule):
262
+ def validate(self, field: str, value: Any, params: List[str]) -> bool:
263
+ not_allowed_values = self._parse_option_values(field, params)
264
+ return str(value) not in not_allowed_values
265
+
266
+ def message(self, field: str, params: List[str]) -> str:
267
+ not_allowed_values = self._parse_option_values(field, params)
268
+ return f"The selected :attribute must be not in : {', '.join(map(str, not_allowed_values))}"
269
+
270
+ class EnumRule(Rule):
271
+ def __init__(self, *params):
272
+ super().__init__()
273
+ self._params: List[str] = list(params)
274
+ self.allowed_values = self._parse_option_values(None, self._params, raise_for_error=False)
275
+
276
+ def exclude(self, *options):
277
+ except_values = self._parse_option_values(None, options, raise_for_error=False)
278
+ self.allowed_values = [value for value in self.allowed_values if value not in except_values]
279
+ return self
280
+
281
+ def only(self, *options):
282
+ only_values = self._parse_option_values(None, options, raise_for_error=False)
283
+ self.allowed_values = [value for value in self.allowed_values if value in only_values]
284
+ return self
285
+
286
+ def when(self,
287
+ condition: bool,
288
+ when_true: Callable[['EnumRule'], 'EnumRule'],
289
+ when_false: Optional[Callable[['EnumRule'], 'EnumRule']] = None
290
+ ) -> 'EnumRule':
291
+
292
+ if condition:
293
+ return when_true(self)
294
+ elif when_false:
295
+ return when_false(self)
296
+ return self
297
+
298
+ def validate(self, field: str, value: Any, params: List[str]) -> bool:
299
+ return (str(value) in self.allowed_values or value in self.allowed_values)
300
+
301
+ def message(self, field: str, params: List[str]) -> str:
302
+ allowed_values = self._parse_option_values(field, self.allowed_values)
303
+ return f"The :attribute must be one of: {', '.join(map(str, allowed_values))}"
304
+
305
+ class ContainsRule(Rule):
306
+ def validate(self, field: str, value: Any, params: List[str]) -> bool:
307
+ if not params:
237
308
  return False
309
+
310
+ for search_value in params:
311
+ if isinstance(value, str) and search_value in value:
312
+ return True
313
+
314
+ if isinstance(value, (int, float)) and str(search_value) in str(value):
315
+ return True
316
+
317
+ if isinstance(value, (list, tuple, set)) and search_value in value:
318
+ return True
319
+
320
+ if isinstance(value, dict) and search_value in value.keys():
321
+ return True
322
+
323
+ return False
238
324
 
239
325
  def message(self, field: str, params: List[str]) -> str:
240
- return f"The :name must be a multiple of {params[0]}."
326
+ if len(params) == 1:
327
+ return f"The {field} must contain {params[0]}"
328
+
329
+ joined_params = ", ".join(params[:-1]) + f" or {params[-1]}"
330
+ return f"The {field} must contain at least one of: {joined_params}"
validator/rules/date.py CHANGED
@@ -1,9 +1,10 @@
1
- from .base import ValidationRule
1
+ from .base import Rule
2
2
  from typing import Any, Dict, List, Optional, Set, Union, Tuple, Type
3
3
  from datetime import datetime
4
+ import zoneinfo
4
5
  from dateutil.parser import parse
5
6
 
6
- class DateRule(ValidationRule):
7
+ class DateRule(Rule):
7
8
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
8
9
  if not isinstance(value, str):
9
10
  return False
@@ -15,18 +16,17 @@ class DateRule(ValidationRule):
15
16
  return False
16
17
 
17
18
  def message(self, field: str, params: List[str]) -> str:
18
- return f"The :name is not a valid date."
19
+ return f"The :attribute is not a valid date."
19
20
 
20
- class DateEqualsRule(ValidationRule):
21
+ class DateEqualsRule(Rule):
21
22
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
22
23
  if not params or not isinstance(value, str):
23
24
  return False
24
25
 
25
- value = self.get_field_value(value, value)
26
-
27
26
  try:
28
27
  date1 = parse(value)
29
28
 
29
+ params = list(params)
30
30
  params[0] = self.get_field_value(params[0], params[0])
31
31
 
32
32
  date2 = parse(params[0])
@@ -35,9 +35,9 @@ class DateEqualsRule(ValidationRule):
35
35
  return False
36
36
 
37
37
  def message(self, field: str, params: List[str]) -> str:
38
- return f"The :name must be equal to {params[0]}."
38
+ return f"The :attribute must be equal to {params[0]}."
39
39
 
40
- class AfterRule(ValidationRule):
40
+ class AfterRule(Rule):
41
41
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
42
42
  if not params or len(params) < 1:
43
43
  return False
@@ -51,7 +51,8 @@ class AfterRule(ValidationRule):
51
51
  else:
52
52
  return False
53
53
 
54
- # Parse comparison date
54
+
55
+ params = list(params)# Parse comparison date
55
56
  params[0] = self.get_field_value(params[0], params[0])
56
57
  compare_date = parse(params[0])
57
58
 
@@ -61,13 +62,12 @@ class AfterRule(ValidationRule):
61
62
  return False
62
63
 
63
64
  def message(self, field: str, params: List[str]) -> str:
64
- return f"The :name must be after {params[0]}"
65
+ return f"The :attribute must be after {params[0]}"
65
66
 
66
- class AfterOrEqualRule(ValidationRule):
67
+ class AfterOrEqualRule(Rule):
67
68
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
68
69
  if not params or len(params) < 1:
69
70
  return False
70
-
71
71
  try:
72
72
  if isinstance(value, str):
73
73
  date_value = parse(value)
@@ -76,18 +76,19 @@ class AfterOrEqualRule(ValidationRule):
76
76
  else:
77
77
  return False
78
78
 
79
+ params = list(params)
79
80
  params[0] = self.get_field_value(params[0], params[0])
80
81
  compare_date = parse(params[0])
81
82
 
82
83
  return date_value >= compare_date
83
84
 
84
- except (ValueError, TypeError):
85
+ except (ValueError, TypeError) as e:
85
86
  return False
86
87
 
87
88
  def message(self, field: str, params: List[str]) -> str:
88
- return f"The :name must be after or equal to {params[0]}"
89
+ return f"The :attribute must be after or equal to {params[0]}"
89
90
 
90
- class BeforeRule(ValidationRule):
91
+ class BeforeRule(Rule):
91
92
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
92
93
  if not params or len(params) < 1:
93
94
  return False
@@ -102,6 +103,7 @@ class BeforeRule(ValidationRule):
102
103
  else:
103
104
  return False
104
105
 
106
+ params = list(params)
105
107
  params[0] = self.get_field_value(params[0], params[0])
106
108
  compare_date = parse(params[0])
107
109
 
@@ -111,9 +113,9 @@ class BeforeRule(ValidationRule):
111
113
  return False
112
114
 
113
115
  def message(self, field: str, params: List[str]) -> str:
114
- return f"The :name must be before {params[0]}"
116
+ return f"The :attribute must be before {params[0]}"
115
117
 
116
- class BeforeOrEqualRule(ValidationRule):
118
+ class BeforeOrEqualRule(Rule):
117
119
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
118
120
  if not params or len(params) < 1:
119
121
  return False
@@ -128,6 +130,7 @@ class BeforeOrEqualRule(ValidationRule):
128
130
  else:
129
131
  return False
130
132
 
133
+ params = list(params)
131
134
  params[0] = self.get_field_value(params[0], params[0])
132
135
  compare_date = parse(params[0])
133
136
 
@@ -137,9 +140,9 @@ class BeforeOrEqualRule(ValidationRule):
137
140
  return False
138
141
 
139
142
  def message(self, field: str, params: List[str]) -> str:
140
- return f"The :name must be before or equal to {params[0]}"
143
+ return f"The :attribute must be before or equal to {params[0]}"
141
144
 
142
- class DateFormatRule(ValidationRule):
145
+ class DateFormatRule(Rule):
143
146
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
144
147
  if not params or len(params) < 1 or not isinstance(value, str):
145
148
  return False
@@ -151,4 +154,17 @@ class DateFormatRule(ValidationRule):
151
154
  return False
152
155
 
153
156
  def message(self, field: str, params: List[str]) -> str:
154
- return f"The :name must match the format {params[0]}"
157
+ return f"The :attribute must match the format {params[0]}"
158
+
159
+ class TimezoneRule(Rule):
160
+ def validate(self, field: str, value: Any, params: List[str]) -> bool:
161
+ if not isinstance(value, str):
162
+ return False
163
+ try:
164
+ zoneinfo.ZoneInfo(value)
165
+ return True
166
+ except zoneinfo.ZoneInfoNotFoundError:
167
+ return False
168
+
169
+ def message(self, field: str, params: List[str]) -> str:
170
+ return f"The :attribute must be a valid timezone."