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.
Files changed (152) hide show
  1. createsonline/__init__.py +46 -0
  2. createsonline/admin/__init__.py +7 -0
  3. createsonline/admin/content.py +526 -0
  4. createsonline/admin/crud.py +805 -0
  5. createsonline/admin/field_builder.py +559 -0
  6. createsonline/admin/integration.py +482 -0
  7. createsonline/admin/interface.py +2562 -0
  8. createsonline/admin/model_creator.py +513 -0
  9. createsonline/admin/model_manager.py +388 -0
  10. createsonline/admin/modern_dashboard.py +498 -0
  11. createsonline/admin/permissions.py +264 -0
  12. createsonline/admin/user_forms.py +594 -0
  13. createsonline/ai/__init__.py +202 -0
  14. createsonline/ai/fields.py +1226 -0
  15. createsonline/ai/orm.py +325 -0
  16. createsonline/ai/services.py +1244 -0
  17. createsonline/app.py +506 -0
  18. createsonline/auth/__init__.py +8 -0
  19. createsonline/auth/management.py +228 -0
  20. createsonline/auth/models.py +552 -0
  21. createsonline/cli/__init__.py +5 -0
  22. createsonline/cli/commands/__init__.py +122 -0
  23. createsonline/cli/commands/database.py +416 -0
  24. createsonline/cli/commands/info.py +173 -0
  25. createsonline/cli/commands/initdb.py +218 -0
  26. createsonline/cli/commands/project.py +545 -0
  27. createsonline/cli/commands/serve.py +173 -0
  28. createsonline/cli/commands/shell.py +93 -0
  29. createsonline/cli/commands/users.py +148 -0
  30. createsonline/cli/main.py +2041 -0
  31. createsonline/cli/manage.py +274 -0
  32. createsonline/config/__init__.py +9 -0
  33. createsonline/config/app.py +2577 -0
  34. createsonline/config/database.py +179 -0
  35. createsonline/config/docs.py +384 -0
  36. createsonline/config/errors.py +160 -0
  37. createsonline/config/orm.py +43 -0
  38. createsonline/config/request.py +93 -0
  39. createsonline/config/settings.py +176 -0
  40. createsonline/data/__init__.py +23 -0
  41. createsonline/data/dataframe.py +925 -0
  42. createsonline/data/io.py +453 -0
  43. createsonline/data/series.py +557 -0
  44. createsonline/database/__init__.py +60 -0
  45. createsonline/database/abstraction.py +440 -0
  46. createsonline/database/assistant.py +585 -0
  47. createsonline/database/fields.py +442 -0
  48. createsonline/database/migrations.py +132 -0
  49. createsonline/database/models.py +604 -0
  50. createsonline/database.py +438 -0
  51. createsonline/http/__init__.py +28 -0
  52. createsonline/http/client.py +535 -0
  53. createsonline/ml/__init__.py +55 -0
  54. createsonline/ml/classification.py +552 -0
  55. createsonline/ml/clustering.py +680 -0
  56. createsonline/ml/metrics.py +542 -0
  57. createsonline/ml/neural.py +560 -0
  58. createsonline/ml/preprocessing.py +784 -0
  59. createsonline/ml/regression.py +501 -0
  60. createsonline/performance/__init__.py +19 -0
  61. createsonline/performance/cache.py +444 -0
  62. createsonline/performance/compression.py +335 -0
  63. createsonline/performance/core.py +419 -0
  64. createsonline/project_init.py +789 -0
  65. createsonline/routing.py +528 -0
  66. createsonline/security/__init__.py +34 -0
  67. createsonline/security/core.py +811 -0
  68. createsonline/security/encryption.py +349 -0
  69. createsonline/server.py +295 -0
  70. createsonline/static/css/admin.css +263 -0
  71. createsonline/static/css/common.css +358 -0
  72. createsonline/static/css/dashboard.css +89 -0
  73. createsonline/static/favicon.ico +0 -0
  74. createsonline/static/icons/icon-128x128.png +0 -0
  75. createsonline/static/icons/icon-128x128.webp +0 -0
  76. createsonline/static/icons/icon-16x16.png +0 -0
  77. createsonline/static/icons/icon-16x16.webp +0 -0
  78. createsonline/static/icons/icon-180x180.png +0 -0
  79. createsonline/static/icons/icon-180x180.webp +0 -0
  80. createsonline/static/icons/icon-192x192.png +0 -0
  81. createsonline/static/icons/icon-192x192.webp +0 -0
  82. createsonline/static/icons/icon-256x256.png +0 -0
  83. createsonline/static/icons/icon-256x256.webp +0 -0
  84. createsonline/static/icons/icon-32x32.png +0 -0
  85. createsonline/static/icons/icon-32x32.webp +0 -0
  86. createsonline/static/icons/icon-384x384.png +0 -0
  87. createsonline/static/icons/icon-384x384.webp +0 -0
  88. createsonline/static/icons/icon-48x48.png +0 -0
  89. createsonline/static/icons/icon-48x48.webp +0 -0
  90. createsonline/static/icons/icon-512x512.png +0 -0
  91. createsonline/static/icons/icon-512x512.webp +0 -0
  92. createsonline/static/icons/icon-64x64.png +0 -0
  93. createsonline/static/icons/icon-64x64.webp +0 -0
  94. createsonline/static/image/android-chrome-192x192.png +0 -0
  95. createsonline/static/image/android-chrome-512x512.png +0 -0
  96. createsonline/static/image/apple-touch-icon.png +0 -0
  97. createsonline/static/image/favicon-16x16.png +0 -0
  98. createsonline/static/image/favicon-32x32.png +0 -0
  99. createsonline/static/image/favicon.ico +0 -0
  100. createsonline/static/image/favicon.svg +17 -0
  101. createsonline/static/image/icon-128x128.png +0 -0
  102. createsonline/static/image/icon-128x128.webp +0 -0
  103. createsonline/static/image/icon-16x16.png +0 -0
  104. createsonline/static/image/icon-16x16.webp +0 -0
  105. createsonline/static/image/icon-180x180.png +0 -0
  106. createsonline/static/image/icon-180x180.webp +0 -0
  107. createsonline/static/image/icon-192x192.png +0 -0
  108. createsonline/static/image/icon-192x192.webp +0 -0
  109. createsonline/static/image/icon-256x256.png +0 -0
  110. createsonline/static/image/icon-256x256.webp +0 -0
  111. createsonline/static/image/icon-32x32.png +0 -0
  112. createsonline/static/image/icon-32x32.webp +0 -0
  113. createsonline/static/image/icon-384x384.png +0 -0
  114. createsonline/static/image/icon-384x384.webp +0 -0
  115. createsonline/static/image/icon-48x48.png +0 -0
  116. createsonline/static/image/icon-48x48.webp +0 -0
  117. createsonline/static/image/icon-512x512.png +0 -0
  118. createsonline/static/image/icon-512x512.webp +0 -0
  119. createsonline/static/image/icon-64x64.png +0 -0
  120. createsonline/static/image/icon-64x64.webp +0 -0
  121. createsonline/static/image/logo-header-h100.png +0 -0
  122. createsonline/static/image/logo-header-h100.webp +0 -0
  123. createsonline/static/image/logo-header-h200@2x.png +0 -0
  124. createsonline/static/image/logo-header-h200@2x.webp +0 -0
  125. createsonline/static/image/logo.png +0 -0
  126. createsonline/static/js/admin.js +274 -0
  127. createsonline/static/site.webmanifest +35 -0
  128. createsonline/static/templates/admin/base.html +87 -0
  129. createsonline/static/templates/admin/dashboard.html +217 -0
  130. createsonline/static/templates/admin/model_form.html +270 -0
  131. createsonline/static/templates/admin/model_list.html +202 -0
  132. createsonline/static/test_script.js +15 -0
  133. createsonline/static/test_styles.css +59 -0
  134. createsonline/static_files.py +365 -0
  135. createsonline/templates/404.html +100 -0
  136. createsonline/templates/admin_login.html +169 -0
  137. createsonline/templates/base.html +102 -0
  138. createsonline/templates/index.html +151 -0
  139. createsonline/templates.py +205 -0
  140. createsonline/testing.py +322 -0
  141. createsonline/utils.py +448 -0
  142. createsonline/validation/__init__.py +49 -0
  143. createsonline/validation/fields.py +598 -0
  144. createsonline/validation/models.py +504 -0
  145. createsonline/validation/validators.py +561 -0
  146. createsonline/views.py +184 -0
  147. createsonline-0.1.26.dist-info/METADATA +46 -0
  148. createsonline-0.1.26.dist-info/RECORD +152 -0
  149. createsonline-0.1.26.dist-info/WHEEL +5 -0
  150. createsonline-0.1.26.dist-info/entry_points.txt +2 -0
  151. createsonline-0.1.26.dist-info/licenses/LICENSE +21 -0
  152. 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