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,598 @@
1
+ """
2
+ CREATESONLINE Validation Fields
3
+
4
+ Pure Python field validation.
5
+ """
6
+
7
+ import re
8
+ import sys
9
+ from datetime import datetime, date
10
+ from .validators import ValidationError
11
+
12
+ # Handle typing compatibility
13
+ if sys.version_info >= (3, 9):
14
+ from typing import Any, Optional, List, Dict, Union, Callable
15
+ else:
16
+ # Fallback for older Python versions (shouldn't happen since we require 3.9+)
17
+ from typing import Any, List, Dict, Union, Callable
18
+ Optional = Union
19
+
20
+
21
+ class Field:
22
+ """
23
+ Base validation field
24
+
25
+ Pure Python field validation with custom validators.
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ default: Any = None,
31
+ required: bool = True,
32
+ validators: Optional[List[Callable[[Any], bool]]] = None,
33
+ description: Optional[str] = None,
34
+ alias: Optional[str] = None
35
+ ):
36
+ """
37
+ Initialize field
38
+
39
+ Args:
40
+ default: Default value if not provided
41
+ required: Whether field is required
42
+ validators: List of validator functions
43
+ description: Field description
44
+ alias: Alternative field name
45
+ """
46
+ self.default = default
47
+ self.required = required
48
+ self.validators = validators or []
49
+ self.description = description
50
+ self.alias = alias
51
+
52
+ # Add required validator if field is required
53
+ if self.required and default is None:
54
+ self.validators.insert(0, self._required_validator)
55
+
56
+ def _required_validator(self, value: Any) -> bool:
57
+ """Check if value is not None/empty"""
58
+ if value is None:
59
+ raise ValidationError("Field is required")
60
+ if isinstance(value, (str, list, dict)) and len(value) == 0:
61
+ raise ValidationError("Field cannot be empty")
62
+ return True
63
+
64
+ def validate(self, value: Any, field_name: str = "field") -> Any:
65
+ """
66
+ Validate field value
67
+
68
+ Args:
69
+ value: Value to validate
70
+ field_name: Name of the field for error messages
71
+
72
+ Returns:
73
+ Validated and converted value
74
+
75
+ Raises:
76
+ ValidationError: If validation fails
77
+ """
78
+ # Handle None values
79
+ if value is None:
80
+ if not self.required and self.default is not None:
81
+ return self.default
82
+ elif not self.required:
83
+ return None
84
+
85
+ # Type conversion
86
+ try:
87
+ value = self.convert_type(value)
88
+ except (ValueError, TypeError) as e:
89
+ raise ValidationError(f"Invalid type for {field_name}: {e}")
90
+
91
+ # Run validators
92
+ for validator in self.validators:
93
+ try:
94
+ if not validator(value):
95
+ raise ValidationError(f"Validation failed for {field_name}")
96
+ except ValidationError:
97
+ raise
98
+ except Exception as e:
99
+ raise ValidationError(f"Validator error for {field_name}: {e}")
100
+
101
+ return value
102
+
103
+ def convert_type(self, value: Any) -> Any:
104
+ """Convert value to appropriate type (override in subclasses)"""
105
+ return value
106
+
107
+ def to_dict(self) -> Dict[str, Any]:
108
+ """Convert field definition to dictionary"""
109
+ return {
110
+ 'type': self.__class__.__name__,
111
+ 'default': self.default,
112
+ 'required': self.required,
113
+ 'description': self.description,
114
+ 'alias': self.alias
115
+ }
116
+
117
+
118
+ class StringField(Field):
119
+ """String field with length validation"""
120
+
121
+ def __init__(
122
+ self,
123
+ min_length: Optional[int] = None,
124
+ max_length: Optional[int] = None,
125
+ pattern: Optional[str] = None,
126
+ **kwargs
127
+ ):
128
+ """
129
+ Initialize string field
130
+
131
+ Args:
132
+ min_length: Minimum string length
133
+ max_length: Maximum string length
134
+ pattern: Regex pattern to match
135
+ **kwargs: Base field arguments
136
+ """
137
+ super().__init__(**kwargs)
138
+
139
+ self.min_length = min_length
140
+ self.max_length = max_length
141
+ self.pattern = pattern
142
+
143
+ # Add length validators
144
+ if min_length is not None:
145
+ self.validators.append(lambda x: self._validate_min_length(x))
146
+ if max_length is not None:
147
+ self.validators.append(lambda x: self._validate_max_length(x))
148
+ if pattern is not None:
149
+ self.validators.append(lambda x: self._validate_pattern(x))
150
+
151
+ def _validate_min_length(self, value: str) -> bool:
152
+ if len(value) < self.min_length:
153
+ raise ValidationError(f"String too short, minimum length is {self.min_length}")
154
+ return True
155
+
156
+ def _validate_max_length(self, value: str) -> bool:
157
+ if len(value) > self.max_length:
158
+ raise ValidationError(f"String too long, maximum length is {self.max_length}")
159
+ return True
160
+
161
+ def _validate_pattern(self, value: str) -> bool:
162
+ if not re.match(self.pattern, value):
163
+ raise ValidationError(f"String does not match pattern: {self.pattern}")
164
+ return True
165
+
166
+ def convert_type(self, value: Any) -> str:
167
+ """Convert value to string"""
168
+ if isinstance(value, str):
169
+ return value
170
+ return str(value)
171
+
172
+
173
+ class IntField(Field):
174
+ """Integer field with range validation"""
175
+
176
+ def __init__(
177
+ self,
178
+ min_value: Optional[int] = None,
179
+ max_value: Optional[int] = None,
180
+ **kwargs
181
+ ):
182
+ """
183
+ Initialize integer field
184
+
185
+ Args:
186
+ min_value: Minimum value
187
+ max_value: Maximum value
188
+ **kwargs: Base field arguments
189
+ """
190
+ super().__init__(**kwargs)
191
+
192
+ self.min_value = min_value
193
+ self.max_value = max_value
194
+
195
+ # Add range validators
196
+ if min_value is not None:
197
+ self.validators.append(lambda x: self._validate_min_value(x))
198
+ if max_value is not None:
199
+ self.validators.append(lambda x: self._validate_max_value(x))
200
+
201
+ def _validate_min_value(self, value: int) -> bool:
202
+ if value < self.min_value:
203
+ raise ValidationError(f"Value too small, minimum is {self.min_value}")
204
+ return True
205
+
206
+ def _validate_max_value(self, value: int) -> bool:
207
+ if value > self.max_value:
208
+ raise ValidationError(f"Value too large, maximum is {self.max_value}")
209
+ return True
210
+
211
+ def convert_type(self, value: Any) -> int:
212
+ """Convert value to integer"""
213
+ if isinstance(value, int):
214
+ return value
215
+ if isinstance(value, float) and value.is_integer():
216
+ return int(value)
217
+ if isinstance(value, str):
218
+ try:
219
+ return int(value)
220
+ except ValueError:
221
+ raise ValueError("Cannot convert to integer")
222
+ raise ValueError("Cannot convert to integer")
223
+
224
+
225
+ class FloatField(Field):
226
+ """Float field with range validation"""
227
+
228
+ def __init__(
229
+ self,
230
+ min_value: Optional[float] = None,
231
+ max_value: Optional[float] = None,
232
+ **kwargs
233
+ ):
234
+ """
235
+ Initialize float field
236
+
237
+ Args:
238
+ min_value: Minimum value
239
+ max_value: Maximum value
240
+ **kwargs: Base field arguments
241
+ """
242
+ super().__init__(**kwargs)
243
+
244
+ self.min_value = min_value
245
+ self.max_value = max_value
246
+
247
+ # Add range validators
248
+ if min_value is not None:
249
+ self.validators.append(lambda x: self._validate_min_value(x))
250
+ if max_value is not None:
251
+ self.validators.append(lambda x: self._validate_max_value(x))
252
+
253
+ def _validate_min_value(self, value: float) -> bool:
254
+ if value < self.min_value:
255
+ raise ValidationError(f"Value too small, minimum is {self.min_value}")
256
+ return True
257
+
258
+ def _validate_max_value(self, value: float) -> bool:
259
+ if value > self.max_value:
260
+ raise ValidationError(f"Value too large, maximum is {self.max_value}")
261
+ return True
262
+
263
+ def convert_type(self, value: Any) -> float:
264
+ """Convert value to float"""
265
+ if isinstance(value, (int, float)):
266
+ return float(value)
267
+ if isinstance(value, str):
268
+ try:
269
+ return float(value)
270
+ except ValueError:
271
+ raise ValueError("Cannot convert to float")
272
+ raise ValueError("Cannot convert to float")
273
+
274
+
275
+ class BoolField(Field):
276
+ """Boolean field"""
277
+
278
+ def convert_type(self, value: Any) -> bool:
279
+ """Convert value to boolean"""
280
+ if isinstance(value, bool):
281
+ return value
282
+ if isinstance(value, str):
283
+ if value.lower() in ('true', '1', 'yes', 'on'):
284
+ return True
285
+ elif value.lower() in ('false', '0', 'no', 'off'):
286
+ return False
287
+ else:
288
+ raise ValueError("Cannot convert string to boolean")
289
+ if isinstance(value, (int, float)):
290
+ return bool(value)
291
+ raise ValueError("Cannot convert to boolean")
292
+
293
+
294
+ class EmailField(StringField):
295
+ """Email field with email validation"""
296
+
297
+ def __init__(self, **kwargs):
298
+ """Initialize email field"""
299
+ super().__init__(**kwargs)
300
+ self.validators.append(self._validate_email)
301
+
302
+ def _validate_email(self, value: str) -> bool:
303
+ """Validate email format"""
304
+ email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
305
+ if not re.match(email_pattern, value):
306
+ raise ValidationError("Invalid email format")
307
+ return True
308
+
309
+
310
+ class URLField(StringField):
311
+ """URL field with URL validation"""
312
+
313
+ def __init__(self, **kwargs):
314
+ """Initialize URL field"""
315
+ super().__init__(**kwargs)
316
+ self.validators.append(self._validate_url)
317
+
318
+ def _validate_url(self, value: str) -> bool:
319
+ """Validate URL format"""
320
+ url_pattern = r'^https?://(?:[-\w.])+(?:[:\d]+)?(?:/(?:[\w/_.])*(?:\?(?:[\w&=%.])*)?(?:#(?:\w*))?)?$'
321
+ if not re.match(url_pattern, value):
322
+ raise ValidationError("Invalid URL format")
323
+ return True
324
+
325
+
326
+ class DateField(Field):
327
+ """Date field with date parsing"""
328
+
329
+ def __init__(self, format: str = "%Y-%m-%d", **kwargs):
330
+ """
331
+ Initialize date field
332
+
333
+ Args:
334
+ format: Date format string
335
+ **kwargs: Base field arguments
336
+ """
337
+ super().__init__(**kwargs)
338
+ self.format = format
339
+
340
+ def convert_type(self, value: Any) -> date:
341
+ """Convert value to date"""
342
+ if isinstance(value, date):
343
+ return value
344
+ if isinstance(value, datetime):
345
+ return value.date()
346
+ if isinstance(value, str):
347
+ try:
348
+ return datetime.strptime(value, self.format).date()
349
+ except ValueError:
350
+ raise ValueError(f"Date does not match format {self.format}")
351
+ raise ValueError("Cannot convert to date")
352
+
353
+
354
+ class ListField(Field):
355
+ """List field with item validation"""
356
+
357
+ def __init__(
358
+ self,
359
+ item_field: Optional[Field] = None,
360
+ min_items: Optional[int] = None,
361
+ max_items: Optional[int] = None,
362
+ **kwargs
363
+ ):
364
+ """
365
+ Initialize list field
366
+
367
+ Args:
368
+ item_field: Field for validating list items
369
+ min_items: Minimum number of items
370
+ max_items: Maximum number of items
371
+ **kwargs: Base field arguments
372
+ """
373
+ super().__init__(**kwargs)
374
+
375
+ self.item_field = item_field
376
+ self.min_items = min_items
377
+ self.max_items = max_items
378
+
379
+ # Add list size validators
380
+ if min_items is not None:
381
+ self.validators.append(lambda x: self._validate_min_items(x))
382
+ if max_items is not None:
383
+ self.validators.append(lambda x: self._validate_max_items(x))
384
+
385
+ def _validate_min_items(self, value: list) -> bool:
386
+ if len(value) < self.min_items:
387
+ raise ValidationError(f"List too short, minimum items is {self.min_items}")
388
+ return True
389
+
390
+ def _validate_max_items(self, value: list) -> bool:
391
+ if len(value) > self.max_items:
392
+ raise ValidationError(f"List too long, maximum items is {self.max_items}")
393
+ return True
394
+
395
+ def convert_type(self, value: Any) -> list:
396
+ """Convert value to list"""
397
+ if isinstance(value, list):
398
+ return value
399
+ if isinstance(value, (tuple, set)):
400
+ return list(value)
401
+ # Single item becomes list of one item
402
+ return [value]
403
+
404
+ def validate(self, value: Any, field_name: str = "field") -> list:
405
+ """Validate list and its items"""
406
+ # Validate the list itself
407
+ validated_list = super().validate(value, field_name)
408
+
409
+ if validated_list is None:
410
+ return None
411
+
412
+ # Validate each item if item_field is provided
413
+ if self.item_field:
414
+ validated_items = []
415
+ for i, item in enumerate(validated_list):
416
+ try:
417
+ validated_item = self.item_field.validate(item, f"{field_name}[{i}]")
418
+ validated_items.append(validated_item)
419
+ except ValidationError as e:
420
+ raise ValidationError(f"Item {i} in {field_name}: {e}")
421
+ return validated_items
422
+
423
+ return validated_list
424
+
425
+
426
+ class DictField(Field):
427
+ """Dictionary field with key/value validation"""
428
+
429
+ def __init__(
430
+ self,
431
+ key_field: Optional[Field] = None,
432
+ value_field: Optional[Field] = None,
433
+ **kwargs
434
+ ):
435
+ """
436
+ Initialize dictionary field
437
+
438
+ Args:
439
+ key_field: Field for validating dictionary keys
440
+ value_field: Field for validating dictionary values
441
+ **kwargs: Base field arguments
442
+ """
443
+ super().__init__(**kwargs)
444
+
445
+ self.key_field = key_field
446
+ self.value_field = value_field
447
+
448
+ def convert_type(self, value: Any) -> dict:
449
+ """Convert value to dictionary"""
450
+ if isinstance(value, dict):
451
+ return value
452
+ raise ValueError("Cannot convert to dictionary")
453
+
454
+ def validate(self, value: Any, field_name: str = "field") -> dict:
455
+ """Validate dictionary and its keys/values"""
456
+ # Validate the dictionary itself
457
+ validated_dict = super().validate(value, field_name)
458
+
459
+ if validated_dict is None:
460
+ return None
461
+
462
+ # Validate keys and values if field validators are provided
463
+ if self.key_field or self.value_field:
464
+ validated_items = {}
465
+ for key, val in validated_dict.items():
466
+ # Validate key
467
+ if self.key_field:
468
+ try:
469
+ validated_key = self.key_field.validate(key, f"{field_name} key")
470
+ except ValidationError as e:
471
+ raise ValidationError(f"Key '{key}' in {field_name}: {e}")
472
+ else:
473
+ validated_key = key
474
+
475
+ # Validate value
476
+ if self.value_field:
477
+ try:
478
+ validated_value = self.value_field.validate(val, f"{field_name}[{key}]")
479
+ except ValidationError as e:
480
+ raise ValidationError(f"Value for key '{key}' in {field_name}: {e}")
481
+ else:
482
+ validated_value = val
483
+
484
+ validated_items[validated_key] = validated_value
485
+
486
+ return validated_items
487
+
488
+ return validated_dict
489
+
490
+
491
+ class OptionalField(Field):
492
+ """Optional field wrapper"""
493
+
494
+ def __init__(self, inner_field: Field, **kwargs):
495
+ """
496
+ Initialize optional field
497
+
498
+ Args:
499
+ inner_field: The field to make optional
500
+ **kwargs: Base field arguments
501
+ """
502
+ # Override required to False
503
+ kwargs['required'] = False
504
+ super().__init__(**kwargs)
505
+
506
+ self.inner_field = inner_field
507
+ # Copy inner field properties but make it non-required
508
+ self.inner_field.required = False
509
+
510
+ def validate(self, value: Any, field_name: str = "field") -> Any:
511
+ """Validate optional field"""
512
+ if value is None:
513
+ return self.default
514
+
515
+ return self.inner_field.validate(value, field_name)
516
+
517
+ def convert_type(self, value: Any) -> Any:
518
+ """Use inner field's type conversion"""
519
+ return self.inner_field.convert_type(value)
520
+
521
+
522
+ class ChoiceField(Field):
523
+ """Field that validates against a set of choices"""
524
+
525
+ def __init__(self, choices: List[Any], **kwargs):
526
+ """
527
+ Initialize choice field
528
+
529
+ Args:
530
+ choices: List of valid choices
531
+ **kwargs: Base field arguments
532
+ """
533
+ super().__init__(**kwargs)
534
+
535
+ self.choices = choices
536
+ self.validators.append(self._validate_choice)
537
+
538
+ def _validate_choice(self, value: Any) -> bool:
539
+ if value not in self.choices:
540
+ raise ValidationError(f"Value must be one of: {self.choices}")
541
+ return True
542
+
543
+
544
+ # Utility function for creating optional fields
545
+ def Optional(field: Field, default: Any = None) -> OptionalField:
546
+ """Create an optional version of a field"""
547
+ return OptionalField(field, default=default)
548
+
549
+
550
+ # Common field factory functions
551
+ def String(min_length=None, max_length=None, pattern=None, **kwargs):
552
+ """Create a string field"""
553
+ return StringField(min_length=min_length, max_length=max_length, pattern=pattern, **kwargs)
554
+
555
+
556
+ def Int(min_value=None, max_value=None, **kwargs):
557
+ """Create an integer field"""
558
+ return IntField(min_value=min_value, max_value=max_value, **kwargs)
559
+
560
+
561
+ def Float(min_value=None, max_value=None, **kwargs):
562
+ """Create a float field"""
563
+ return FloatField(min_value=min_value, max_value=max_value, **kwargs)
564
+
565
+
566
+ def Bool(**kwargs) -> BoolField:
567
+ """Create a boolean field"""
568
+ return BoolField(**kwargs)
569
+
570
+
571
+ def Email(**kwargs) -> EmailField:
572
+ """Create an email field"""
573
+ return EmailField(**kwargs)
574
+
575
+
576
+ def URL(**kwargs) -> URLField:
577
+ """Create a URL field"""
578
+ return URLField(**kwargs)
579
+
580
+
581
+ def Date(format: str = "%Y-%m-%d", **kwargs) -> DateField:
582
+ """Create a date field"""
583
+ return DateField(format=format, **kwargs)
584
+
585
+
586
+ def List(item_field=None, min_items=None, max_items=None, **kwargs):
587
+ """Create a list field"""
588
+ return ListField(item_field=item_field, min_items=min_items, max_items=max_items, **kwargs)
589
+
590
+
591
+ def Dict(key_field=None, value_field=None, **kwargs):
592
+ """Create a dictionary field"""
593
+ return DictField(key_field=key_field, value_field=value_field, **kwargs)
594
+
595
+
596
+ def Choice(choices, **kwargs):
597
+ """Create a choice field"""
598
+ return ChoiceField(choices=choices, **kwargs)