createsonline 0.1.26__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.
- createsonline/__init__.py +46 -0
- createsonline/admin/__init__.py +7 -0
- createsonline/admin/content.py +526 -0
- createsonline/admin/crud.py +805 -0
- createsonline/admin/field_builder.py +559 -0
- createsonline/admin/integration.py +482 -0
- createsonline/admin/interface.py +2562 -0
- createsonline/admin/model_creator.py +513 -0
- createsonline/admin/model_manager.py +388 -0
- createsonline/admin/modern_dashboard.py +498 -0
- createsonline/admin/permissions.py +264 -0
- createsonline/admin/user_forms.py +594 -0
- createsonline/ai/__init__.py +202 -0
- createsonline/ai/fields.py +1226 -0
- createsonline/ai/orm.py +325 -0
- createsonline/ai/services.py +1244 -0
- createsonline/app.py +506 -0
- createsonline/auth/__init__.py +8 -0
- createsonline/auth/management.py +228 -0
- createsonline/auth/models.py +552 -0
- createsonline/cli/__init__.py +5 -0
- createsonline/cli/commands/__init__.py +122 -0
- createsonline/cli/commands/database.py +416 -0
- createsonline/cli/commands/info.py +173 -0
- createsonline/cli/commands/initdb.py +218 -0
- createsonline/cli/commands/project.py +545 -0
- createsonline/cli/commands/serve.py +173 -0
- createsonline/cli/commands/shell.py +93 -0
- createsonline/cli/commands/users.py +148 -0
- createsonline/cli/main.py +2041 -0
- createsonline/cli/manage.py +274 -0
- createsonline/config/__init__.py +9 -0
- createsonline/config/app.py +2577 -0
- createsonline/config/database.py +179 -0
- createsonline/config/docs.py +384 -0
- createsonline/config/errors.py +160 -0
- createsonline/config/orm.py +43 -0
- createsonline/config/request.py +93 -0
- createsonline/config/settings.py +176 -0
- createsonline/data/__init__.py +23 -0
- createsonline/data/dataframe.py +925 -0
- createsonline/data/io.py +453 -0
- createsonline/data/series.py +557 -0
- createsonline/database/__init__.py +60 -0
- createsonline/database/abstraction.py +440 -0
- createsonline/database/assistant.py +585 -0
- createsonline/database/fields.py +442 -0
- createsonline/database/migrations.py +132 -0
- createsonline/database/models.py +604 -0
- createsonline/database.py +438 -0
- createsonline/http/__init__.py +28 -0
- createsonline/http/client.py +535 -0
- createsonline/ml/__init__.py +55 -0
- createsonline/ml/classification.py +552 -0
- createsonline/ml/clustering.py +680 -0
- createsonline/ml/metrics.py +542 -0
- createsonline/ml/neural.py +560 -0
- createsonline/ml/preprocessing.py +784 -0
- createsonline/ml/regression.py +501 -0
- createsonline/performance/__init__.py +19 -0
- createsonline/performance/cache.py +444 -0
- createsonline/performance/compression.py +335 -0
- createsonline/performance/core.py +419 -0
- createsonline/project_init.py +789 -0
- createsonline/routing.py +528 -0
- createsonline/security/__init__.py +34 -0
- createsonline/security/core.py +811 -0
- createsonline/security/encryption.py +349 -0
- createsonline/server.py +295 -0
- createsonline/static/css/admin.css +263 -0
- createsonline/static/css/common.css +358 -0
- createsonline/static/css/dashboard.css +89 -0
- createsonline/static/favicon.ico +0 -0
- createsonline/static/icons/icon-128x128.png +0 -0
- createsonline/static/icons/icon-128x128.webp +0 -0
- createsonline/static/icons/icon-16x16.png +0 -0
- createsonline/static/icons/icon-16x16.webp +0 -0
- createsonline/static/icons/icon-180x180.png +0 -0
- createsonline/static/icons/icon-180x180.webp +0 -0
- createsonline/static/icons/icon-192x192.png +0 -0
- createsonline/static/icons/icon-192x192.webp +0 -0
- createsonline/static/icons/icon-256x256.png +0 -0
- createsonline/static/icons/icon-256x256.webp +0 -0
- createsonline/static/icons/icon-32x32.png +0 -0
- createsonline/static/icons/icon-32x32.webp +0 -0
- createsonline/static/icons/icon-384x384.png +0 -0
- createsonline/static/icons/icon-384x384.webp +0 -0
- createsonline/static/icons/icon-48x48.png +0 -0
- createsonline/static/icons/icon-48x48.webp +0 -0
- createsonline/static/icons/icon-512x512.png +0 -0
- createsonline/static/icons/icon-512x512.webp +0 -0
- createsonline/static/icons/icon-64x64.png +0 -0
- createsonline/static/icons/icon-64x64.webp +0 -0
- createsonline/static/image/android-chrome-192x192.png +0 -0
- createsonline/static/image/android-chrome-512x512.png +0 -0
- createsonline/static/image/apple-touch-icon.png +0 -0
- createsonline/static/image/favicon-16x16.png +0 -0
- createsonline/static/image/favicon-32x32.png +0 -0
- createsonline/static/image/favicon.ico +0 -0
- createsonline/static/image/favicon.svg +17 -0
- createsonline/static/image/icon-128x128.png +0 -0
- createsonline/static/image/icon-128x128.webp +0 -0
- createsonline/static/image/icon-16x16.png +0 -0
- createsonline/static/image/icon-16x16.webp +0 -0
- createsonline/static/image/icon-180x180.png +0 -0
- createsonline/static/image/icon-180x180.webp +0 -0
- createsonline/static/image/icon-192x192.png +0 -0
- createsonline/static/image/icon-192x192.webp +0 -0
- createsonline/static/image/icon-256x256.png +0 -0
- createsonline/static/image/icon-256x256.webp +0 -0
- createsonline/static/image/icon-32x32.png +0 -0
- createsonline/static/image/icon-32x32.webp +0 -0
- createsonline/static/image/icon-384x384.png +0 -0
- createsonline/static/image/icon-384x384.webp +0 -0
- createsonline/static/image/icon-48x48.png +0 -0
- createsonline/static/image/icon-48x48.webp +0 -0
- createsonline/static/image/icon-512x512.png +0 -0
- createsonline/static/image/icon-512x512.webp +0 -0
- createsonline/static/image/icon-64x64.png +0 -0
- createsonline/static/image/icon-64x64.webp +0 -0
- createsonline/static/image/logo-header-h100.png +0 -0
- createsonline/static/image/logo-header-h100.webp +0 -0
- createsonline/static/image/logo-header-h200@2x.png +0 -0
- createsonline/static/image/logo-header-h200@2x.webp +0 -0
- createsonline/static/image/logo.png +0 -0
- createsonline/static/js/admin.js +274 -0
- createsonline/static/site.webmanifest +35 -0
- createsonline/static/templates/admin/base.html +87 -0
- createsonline/static/templates/admin/dashboard.html +217 -0
- createsonline/static/templates/admin/model_form.html +270 -0
- createsonline/static/templates/admin/model_list.html +202 -0
- createsonline/static/test_script.js +15 -0
- createsonline/static/test_styles.css +59 -0
- createsonline/static_files.py +365 -0
- createsonline/templates/404.html +100 -0
- createsonline/templates/admin_login.html +169 -0
- createsonline/templates/base.html +102 -0
- createsonline/templates/index.html +151 -0
- createsonline/templates.py +205 -0
- createsonline/testing.py +322 -0
- createsonline/utils.py +448 -0
- createsonline/validation/__init__.py +49 -0
- createsonline/validation/fields.py +598 -0
- createsonline/validation/models.py +504 -0
- createsonline/validation/validators.py +561 -0
- createsonline/views.py +184 -0
- createsonline-0.1.26.dist-info/METADATA +46 -0
- createsonline-0.1.26.dist-info/RECORD +152 -0
- createsonline-0.1.26.dist-info/WHEEL +5 -0
- createsonline-0.1.26.dist-info/entry_points.txt +2 -0
- createsonline-0.1.26.dist-info/licenses/LICENSE +21 -0
- createsonline-0.1.26.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CREATESONLINE Validation Validators
|
|
3
|
+
|
|
4
|
+
Pure Python validation functions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from typing import Any, Callable, Pattern, Union, List
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ValidationError(Exception):
|
|
12
|
+
"""Custom validation error"""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def validator(func: Callable[[Any], bool]) -> Callable[[Any], bool]:
|
|
17
|
+
"""
|
|
18
|
+
Decorator to mark a function as a validator
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
func: Validator function
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Decorated validator function
|
|
25
|
+
"""
|
|
26
|
+
def wrapper(value: Any) -> bool:
|
|
27
|
+
try:
|
|
28
|
+
result = func(value)
|
|
29
|
+
if not result:
|
|
30
|
+
raise ValidationError("Validation failed")
|
|
31
|
+
return result
|
|
32
|
+
except ValidationError:
|
|
33
|
+
raise
|
|
34
|
+
except Exception as e:
|
|
35
|
+
raise ValidationError(f"Validator error: {e}")
|
|
36
|
+
|
|
37
|
+
wrapper._is_validator = True
|
|
38
|
+
return wrapper
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@validator
|
|
42
|
+
def required(value: Any) -> bool:
|
|
43
|
+
"""Validate that value is not None or empty"""
|
|
44
|
+
if value is None:
|
|
45
|
+
raise ValidationError("Field is required")
|
|
46
|
+
if isinstance(value, (str, list, dict)) and len(value) == 0:
|
|
47
|
+
raise ValidationError("Field cannot be empty")
|
|
48
|
+
return True
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def min_length(length: int) -> Callable[[Any], bool]:
|
|
52
|
+
"""
|
|
53
|
+
Create validator for minimum length
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
length: Minimum length
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Validator function
|
|
60
|
+
"""
|
|
61
|
+
@validator
|
|
62
|
+
def _min_length(value: Any) -> bool:
|
|
63
|
+
if hasattr(value, '__len__'):
|
|
64
|
+
if len(value) < length:
|
|
65
|
+
raise ValidationError(f"Must be at least {length} characters/items long")
|
|
66
|
+
return True
|
|
67
|
+
raise ValidationError("Value must have length")
|
|
68
|
+
|
|
69
|
+
return _min_length
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def max_length(length: int) -> Callable[[Any], bool]:
|
|
73
|
+
"""
|
|
74
|
+
Create validator for maximum length
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
length: Maximum length
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Validator function
|
|
81
|
+
"""
|
|
82
|
+
@validator
|
|
83
|
+
def _max_length(value: Any) -> bool:
|
|
84
|
+
if hasattr(value, '__len__'):
|
|
85
|
+
if len(value) > length:
|
|
86
|
+
raise ValidationError(f"Must be at most {length} characters/items long")
|
|
87
|
+
return True
|
|
88
|
+
raise ValidationError("Value must have length")
|
|
89
|
+
|
|
90
|
+
return _max_length
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def min_value(minimum: Union[int, float]) -> Callable[[Any], bool]:
|
|
94
|
+
"""
|
|
95
|
+
Create validator for minimum value
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
minimum: Minimum value
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Validator function
|
|
102
|
+
"""
|
|
103
|
+
@validator
|
|
104
|
+
def _min_value(value: Any) -> bool:
|
|
105
|
+
if not isinstance(value, (int, float)):
|
|
106
|
+
raise ValidationError("Value must be numeric")
|
|
107
|
+
if value < minimum:
|
|
108
|
+
raise ValidationError(f"Must be at least {minimum}")
|
|
109
|
+
return True
|
|
110
|
+
|
|
111
|
+
return _min_value
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def max_value(maximum: Union[int, float]) -> Callable[[Any], bool]:
|
|
115
|
+
"""
|
|
116
|
+
Create validator for maximum value
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
maximum: Maximum value
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Validator function
|
|
123
|
+
"""
|
|
124
|
+
@validator
|
|
125
|
+
def _max_value(value: Any) -> bool:
|
|
126
|
+
if not isinstance(value, (int, float)):
|
|
127
|
+
raise ValidationError("Value must be numeric")
|
|
128
|
+
if value > maximum:
|
|
129
|
+
raise ValidationError(f"Must be at most {maximum}")
|
|
130
|
+
return True
|
|
131
|
+
|
|
132
|
+
return _max_value
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def regex_validator(pattern: Union[str, Pattern], message: str = None) -> Callable[[str], bool]:
|
|
136
|
+
"""
|
|
137
|
+
Create validator for regex pattern matching
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
pattern: Regex pattern (string or compiled pattern)
|
|
141
|
+
message: Custom error message
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Validator function
|
|
145
|
+
"""
|
|
146
|
+
if isinstance(pattern, str):
|
|
147
|
+
compiled_pattern = re.compile(pattern)
|
|
148
|
+
else:
|
|
149
|
+
compiled_pattern = pattern
|
|
150
|
+
|
|
151
|
+
@validator
|
|
152
|
+
def _regex_validator(value: str) -> bool:
|
|
153
|
+
if not isinstance(value, str):
|
|
154
|
+
raise ValidationError("Value must be a string")
|
|
155
|
+
if not compiled_pattern.match(value):
|
|
156
|
+
error_msg = message or f"Value does not match pattern: {pattern}"
|
|
157
|
+
raise ValidationError(error_msg)
|
|
158
|
+
return True
|
|
159
|
+
|
|
160
|
+
return _regex_validator
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@validator
|
|
164
|
+
def email_validator(value: str) -> bool:
|
|
165
|
+
"""Validate email format"""
|
|
166
|
+
if not isinstance(value, str):
|
|
167
|
+
raise ValidationError("Email must be a string")
|
|
168
|
+
|
|
169
|
+
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
|
|
170
|
+
if not re.match(email_pattern, value):
|
|
171
|
+
raise ValidationError("Invalid email format")
|
|
172
|
+
return True
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@validator
|
|
176
|
+
def url_validator(value: str) -> bool:
|
|
177
|
+
"""Validate URL format"""
|
|
178
|
+
if not isinstance(value, str):
|
|
179
|
+
raise ValidationError("URL must be a string")
|
|
180
|
+
|
|
181
|
+
url_pattern = r'^https?://(?:[-\w.])+(?:[:\d]+)?(?:/(?:[\w/_.])*(?:\?(?:[\w&=%.])*)?(?:#(?:\w*))?)?$'
|
|
182
|
+
if not re.match(url_pattern, value):
|
|
183
|
+
raise ValidationError("Invalid URL format")
|
|
184
|
+
return True
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def in_range(min_val: Union[int, float], max_val: Union[int, float]) -> Callable[[Any], bool]:
|
|
188
|
+
"""
|
|
189
|
+
Create validator for value within range
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
min_val: Minimum value (inclusive)
|
|
193
|
+
max_val: Maximum value (inclusive)
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Validator function
|
|
197
|
+
"""
|
|
198
|
+
@validator
|
|
199
|
+
def _in_range(value: Any) -> bool:
|
|
200
|
+
if not isinstance(value, (int, float)):
|
|
201
|
+
raise ValidationError("Value must be numeric")
|
|
202
|
+
if not (min_val <= value <= max_val):
|
|
203
|
+
raise ValidationError(f"Value must be between {min_val} and {max_val}")
|
|
204
|
+
return True
|
|
205
|
+
|
|
206
|
+
return _in_range
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def one_of(choices: List[Any]) -> Callable[[Any], bool]:
|
|
210
|
+
"""
|
|
211
|
+
Create validator for value in choices
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
choices: List of valid choices
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Validator function
|
|
218
|
+
"""
|
|
219
|
+
@validator
|
|
220
|
+
def _one_of(value: Any) -> bool:
|
|
221
|
+
if value not in choices:
|
|
222
|
+
raise ValidationError(f"Value must be one of: {choices}")
|
|
223
|
+
return True
|
|
224
|
+
|
|
225
|
+
return _one_of
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def not_empty(value: Any) -> bool:
|
|
229
|
+
"""Validate that value is not empty"""
|
|
230
|
+
if value is None:
|
|
231
|
+
raise ValidationError("Value cannot be None")
|
|
232
|
+
if isinstance(value, (str, list, dict, tuple, set)) and len(value) == 0:
|
|
233
|
+
raise ValidationError("Value cannot be empty")
|
|
234
|
+
return True
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def positive(value: Union[int, float]) -> bool:
|
|
238
|
+
"""Validate that numeric value is positive"""
|
|
239
|
+
if not isinstance(value, (int, float)):
|
|
240
|
+
raise ValidationError("Value must be numeric")
|
|
241
|
+
if value <= 0:
|
|
242
|
+
raise ValidationError("Value must be positive")
|
|
243
|
+
return True
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def negative(value: Union[int, float]) -> bool:
|
|
247
|
+
"""Validate that numeric value is negative"""
|
|
248
|
+
if not isinstance(value, (int, float)):
|
|
249
|
+
raise ValidationError("Value must be numeric")
|
|
250
|
+
if value >= 0:
|
|
251
|
+
raise ValidationError("Value must be negative")
|
|
252
|
+
return True
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def non_negative(value: Union[int, float]) -> bool:
|
|
256
|
+
"""Validate that numeric value is non-negative"""
|
|
257
|
+
if not isinstance(value, (int, float)):
|
|
258
|
+
raise ValidationError("Value must be numeric")
|
|
259
|
+
if value < 0:
|
|
260
|
+
raise ValidationError("Value must be non-negative")
|
|
261
|
+
return True
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def alpha_validator(value: str) -> bool:
|
|
265
|
+
"""Validate that string contains only alphabetic characters"""
|
|
266
|
+
if not isinstance(value, str):
|
|
267
|
+
raise ValidationError("Value must be a string")
|
|
268
|
+
if not value.isalpha():
|
|
269
|
+
raise ValidationError("Value must contain only alphabetic characters")
|
|
270
|
+
return True
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def alphanumeric_validator(value: str) -> bool:
|
|
274
|
+
"""Validate that string contains only alphanumeric characters"""
|
|
275
|
+
if not isinstance(value, str):
|
|
276
|
+
raise ValidationError("Value must be a string")
|
|
277
|
+
if not value.isalnum():
|
|
278
|
+
raise ValidationError("Value must contain only alphanumeric characters")
|
|
279
|
+
return True
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def numeric_validator(value: str) -> bool:
|
|
283
|
+
"""Validate that string contains only numeric characters"""
|
|
284
|
+
if not isinstance(value, str):
|
|
285
|
+
raise ValidationError("Value must be a string")
|
|
286
|
+
if not value.isdigit():
|
|
287
|
+
raise ValidationError("Value must contain only numeric characters")
|
|
288
|
+
return True
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def no_whitespace(value: str) -> bool:
|
|
292
|
+
"""Validate that string contains no whitespace"""
|
|
293
|
+
if not isinstance(value, str):
|
|
294
|
+
raise ValidationError("Value must be a string")
|
|
295
|
+
if ' ' in value or '\t' in value or '\n' in value:
|
|
296
|
+
raise ValidationError("Value cannot contain whitespace")
|
|
297
|
+
return True
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def starts_with(prefix: str) -> Callable[[str], bool]:
|
|
301
|
+
"""
|
|
302
|
+
Create validator for string starting with prefix
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
prefix: Required prefix
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
Validator function
|
|
309
|
+
"""
|
|
310
|
+
@validator
|
|
311
|
+
def _starts_with(value: str) -> bool:
|
|
312
|
+
if not isinstance(value, str):
|
|
313
|
+
raise ValidationError("Value must be a string")
|
|
314
|
+
if not value.startswith(prefix):
|
|
315
|
+
raise ValidationError(f"Value must start with '{prefix}'")
|
|
316
|
+
return True
|
|
317
|
+
|
|
318
|
+
return _starts_with
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def ends_with(suffix: str) -> Callable[[str], bool]:
|
|
322
|
+
"""
|
|
323
|
+
Create validator for string ending with suffix
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
suffix: Required suffix
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
Validator function
|
|
330
|
+
"""
|
|
331
|
+
@validator
|
|
332
|
+
def _ends_with(value: str) -> bool:
|
|
333
|
+
if not isinstance(value, str):
|
|
334
|
+
raise ValidationError("Value must be a string")
|
|
335
|
+
if not value.endswith(suffix):
|
|
336
|
+
raise ValidationError(f"Value must end with '{suffix}'")
|
|
337
|
+
return True
|
|
338
|
+
|
|
339
|
+
return _ends_with
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def contains(substring: str) -> Callable[[str], bool]:
|
|
343
|
+
"""
|
|
344
|
+
Create validator for string containing substring
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
substring: Required substring
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
Validator function
|
|
351
|
+
"""
|
|
352
|
+
@validator
|
|
353
|
+
def _contains(value: str) -> bool:
|
|
354
|
+
if not isinstance(value, str):
|
|
355
|
+
raise ValidationError("Value must be a string")
|
|
356
|
+
if substring not in value:
|
|
357
|
+
raise ValidationError(f"Value must contain '{substring}'")
|
|
358
|
+
return True
|
|
359
|
+
|
|
360
|
+
return _contains
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def unique_items(value: List[Any]) -> bool:
|
|
364
|
+
"""Validate that list contains only unique items"""
|
|
365
|
+
if not isinstance(value, list):
|
|
366
|
+
raise ValidationError("Value must be a list")
|
|
367
|
+
if len(value) != len(set(value)):
|
|
368
|
+
raise ValidationError("List must contain only unique items")
|
|
369
|
+
return True
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def all_same_type(value: List[Any]) -> bool:
|
|
373
|
+
"""Validate that all items in list are of the same type"""
|
|
374
|
+
if not isinstance(value, list):
|
|
375
|
+
raise ValidationError("Value must be a list")
|
|
376
|
+
if len(value) == 0:
|
|
377
|
+
return True
|
|
378
|
+
|
|
379
|
+
first_type = type(value[0])
|
|
380
|
+
if not all(isinstance(item, first_type) for item in value):
|
|
381
|
+
raise ValidationError("All items in list must be of the same type")
|
|
382
|
+
return True
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def password_strength(
|
|
386
|
+
min_length: int = 8,
|
|
387
|
+
require_uppercase: bool = True,
|
|
388
|
+
require_lowercase: bool = True,
|
|
389
|
+
require_digits: bool = True,
|
|
390
|
+
require_special: bool = True
|
|
391
|
+
) -> Callable[[str], bool]:
|
|
392
|
+
"""
|
|
393
|
+
Create validator for password strength
|
|
394
|
+
|
|
395
|
+
Args:
|
|
396
|
+
min_length: Minimum password length
|
|
397
|
+
require_uppercase: Require uppercase letters
|
|
398
|
+
require_lowercase: Require lowercase letters
|
|
399
|
+
require_digits: Require digits
|
|
400
|
+
require_special: Require special characters
|
|
401
|
+
|
|
402
|
+
Returns:
|
|
403
|
+
Validator function
|
|
404
|
+
"""
|
|
405
|
+
@validator
|
|
406
|
+
def _password_strength(value: str) -> bool:
|
|
407
|
+
if not isinstance(value, str):
|
|
408
|
+
raise ValidationError("Password must be a string")
|
|
409
|
+
|
|
410
|
+
if len(value) < min_length:
|
|
411
|
+
raise ValidationError(f"Password must be at least {min_length} characters long")
|
|
412
|
+
|
|
413
|
+
if require_uppercase and not any(c.isupper() for c in value):
|
|
414
|
+
raise ValidationError("Password must contain at least one uppercase letter")
|
|
415
|
+
|
|
416
|
+
if require_lowercase and not any(c.islower() for c in value):
|
|
417
|
+
raise ValidationError("Password must contain at least one lowercase letter")
|
|
418
|
+
|
|
419
|
+
if require_digits and not any(c.isdigit() for c in value):
|
|
420
|
+
raise ValidationError("Password must contain at least one digit")
|
|
421
|
+
|
|
422
|
+
if require_special and not any(c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in value):
|
|
423
|
+
raise ValidationError("Password must contain at least one special character")
|
|
424
|
+
|
|
425
|
+
return True
|
|
426
|
+
|
|
427
|
+
return _password_strength
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def json_validator(value: str) -> bool:
|
|
431
|
+
"""Validate that string is valid JSON"""
|
|
432
|
+
import json
|
|
433
|
+
|
|
434
|
+
if not isinstance(value, str):
|
|
435
|
+
raise ValidationError("Value must be a string")
|
|
436
|
+
|
|
437
|
+
try:
|
|
438
|
+
json.loads(value)
|
|
439
|
+
return True
|
|
440
|
+
except json.JSONDecodeError:
|
|
441
|
+
raise ValidationError("Value must be valid JSON")
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def ipv4_validator(value: str) -> bool:
|
|
445
|
+
"""Validate IPv4 address format"""
|
|
446
|
+
if not isinstance(value, str):
|
|
447
|
+
raise ValidationError("IP address must be a string")
|
|
448
|
+
|
|
449
|
+
parts = value.split('.')
|
|
450
|
+
if len(parts) != 4:
|
|
451
|
+
raise ValidationError("Invalid IPv4 address format")
|
|
452
|
+
|
|
453
|
+
for part in parts:
|
|
454
|
+
try:
|
|
455
|
+
num = int(part)
|
|
456
|
+
if not (0 <= num <= 255):
|
|
457
|
+
raise ValidationError("Invalid IPv4 address - parts must be 0-255")
|
|
458
|
+
except ValueError:
|
|
459
|
+
raise ValidationError("Invalid IPv4 address - parts must be integers")
|
|
460
|
+
|
|
461
|
+
return True
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def credit_card_validator(value: str) -> bool:
|
|
465
|
+
"""Validate credit card number using Luhn algorithm"""
|
|
466
|
+
if not isinstance(value, str):
|
|
467
|
+
raise ValidationError("Credit card number must be a string")
|
|
468
|
+
|
|
469
|
+
# Remove spaces and dashes
|
|
470
|
+
cleaned = value.replace(' ', '').replace('-', '')
|
|
471
|
+
|
|
472
|
+
if not cleaned.isdigit():
|
|
473
|
+
raise ValidationError("Credit card number must contain only digits")
|
|
474
|
+
|
|
475
|
+
if len(cleaned) < 13 or len(cleaned) > 19:
|
|
476
|
+
raise ValidationError("Credit card number must be 13-19 digits long")
|
|
477
|
+
|
|
478
|
+
# Luhn algorithm
|
|
479
|
+
def luhn_checksum(card_num):
|
|
480
|
+
def digits_of(n):
|
|
481
|
+
return [int(d) for d in str(n)]
|
|
482
|
+
|
|
483
|
+
digits = digits_of(card_num)
|
|
484
|
+
odd_digits = digits[-1::-2]
|
|
485
|
+
even_digits = digits[-2::-2]
|
|
486
|
+
checksum = sum(odd_digits)
|
|
487
|
+
for d in even_digits:
|
|
488
|
+
checksum += sum(digits_of(d * 2))
|
|
489
|
+
return checksum % 10
|
|
490
|
+
|
|
491
|
+
if luhn_checksum(cleaned) != 0:
|
|
492
|
+
raise ValidationError("Invalid credit card number")
|
|
493
|
+
|
|
494
|
+
return True
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
# Composite validators
|
|
498
|
+
def and_validator(*validators: Callable[[Any], bool]) -> Callable[[Any], bool]:
|
|
499
|
+
"""
|
|
500
|
+
Create validator that requires all validators to pass
|
|
501
|
+
|
|
502
|
+
Args:
|
|
503
|
+
*validators: Validator functions
|
|
504
|
+
|
|
505
|
+
Returns:
|
|
506
|
+
Combined validator function
|
|
507
|
+
"""
|
|
508
|
+
@validator
|
|
509
|
+
def _and_validator(value: Any) -> bool:
|
|
510
|
+
for val in validators:
|
|
511
|
+
if not val(value):
|
|
512
|
+
return False
|
|
513
|
+
return True
|
|
514
|
+
|
|
515
|
+
return _and_validator
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def or_validator(*validators: Callable[[Any], bool]) -> Callable[[Any], bool]:
|
|
519
|
+
"""
|
|
520
|
+
Create validator that requires at least one validator to pass
|
|
521
|
+
|
|
522
|
+
Args:
|
|
523
|
+
*validators: Validator functions
|
|
524
|
+
|
|
525
|
+
Returns:
|
|
526
|
+
Combined validator function
|
|
527
|
+
"""
|
|
528
|
+
@validator
|
|
529
|
+
def _or_validator(value: Any) -> bool:
|
|
530
|
+
for val in validators:
|
|
531
|
+
try:
|
|
532
|
+
if val(value):
|
|
533
|
+
return True
|
|
534
|
+
except ValidationError:
|
|
535
|
+
continue
|
|
536
|
+
raise ValidationError("None of the alternative validations passed")
|
|
537
|
+
|
|
538
|
+
return _or_validator
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def not_validator(validator_func: Callable[[Any], bool]) -> Callable[[Any], bool]:
|
|
542
|
+
"""
|
|
543
|
+
Create validator that inverts another validator
|
|
544
|
+
|
|
545
|
+
Args:
|
|
546
|
+
validator_func: Validator to invert
|
|
547
|
+
|
|
548
|
+
Returns:
|
|
549
|
+
Inverted validator function
|
|
550
|
+
"""
|
|
551
|
+
@validator
|
|
552
|
+
def _not_validator(value: Any) -> bool:
|
|
553
|
+
try:
|
|
554
|
+
result = validator_func(value)
|
|
555
|
+
if result:
|
|
556
|
+
raise ValidationError("Validation should have failed")
|
|
557
|
+
return True
|
|
558
|
+
except ValidationError:
|
|
559
|
+
return True # Original validator failed, so NOT validator passes
|
|
560
|
+
|
|
561
|
+
return _not_validator
|