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,504 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CREATESONLINE Validation Models
|
|
3
|
+
|
|
4
|
+
Pure Python model validation.
|
|
5
|
+
Lightweight replacement for Pydantic BaseModel.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import sys
|
|
10
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
11
|
+
from .fields import Field
|
|
12
|
+
from .validators import ValidationError
|
|
13
|
+
|
|
14
|
+
# Handle typing compatibility across Python versions
|
|
15
|
+
if sys.version_info >= (3, 10):
|
|
16
|
+
from typing import get_type_hints
|
|
17
|
+
else:
|
|
18
|
+
try:
|
|
19
|
+
from typing import get_type_hints
|
|
20
|
+
except ImportError:
|
|
21
|
+
def get_type_hints(obj):
|
|
22
|
+
return getattr(obj, '__annotations__', {})
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ModelMeta(type):
|
|
26
|
+
"""Metaclass for BaseModel to collect field definitions"""
|
|
27
|
+
|
|
28
|
+
def __new__(mcs, name, bases, namespace, **kwargs):
|
|
29
|
+
# Collect fields from the class definition
|
|
30
|
+
fields = {}
|
|
31
|
+
|
|
32
|
+
# Inherit fields from base classes
|
|
33
|
+
for base in bases:
|
|
34
|
+
if hasattr(base, '_fields'):
|
|
35
|
+
fields.update(base._fields)
|
|
36
|
+
|
|
37
|
+
# Add fields from current class
|
|
38
|
+
for key, value in list(namespace.items()):
|
|
39
|
+
if isinstance(value, Field):
|
|
40
|
+
fields[key] = value
|
|
41
|
+
# Remove field from namespace to avoid conflicts
|
|
42
|
+
namespace.pop(key)
|
|
43
|
+
|
|
44
|
+
# Create the class
|
|
45
|
+
cls = super().__new__(mcs, name, bases, namespace)
|
|
46
|
+
|
|
47
|
+
# Store fields on the class
|
|
48
|
+
cls._fields = fields
|
|
49
|
+
|
|
50
|
+
# Store field names for easier access
|
|
51
|
+
cls._field_names = list(fields.keys())
|
|
52
|
+
|
|
53
|
+
return cls
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class BaseModel(metaclass=ModelMeta):
|
|
57
|
+
"""
|
|
58
|
+
Base model class for data validation
|
|
59
|
+
|
|
60
|
+
Pure Python implementation similar to Pydantic BaseModel.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
_fields: Dict[str, Field] = {}
|
|
64
|
+
_field_names: List[str] = []
|
|
65
|
+
|
|
66
|
+
def __init__(self, **data):
|
|
67
|
+
"""
|
|
68
|
+
Initialize model with data validation
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
**data: Data to validate and assign
|
|
72
|
+
"""
|
|
73
|
+
self._validated_data = {}
|
|
74
|
+
self._errors = {}
|
|
75
|
+
|
|
76
|
+
# Process each field
|
|
77
|
+
for field_name, field in self._fields.items():
|
|
78
|
+
# Check for alias
|
|
79
|
+
data_key = field.alias if field.alias else field_name
|
|
80
|
+
|
|
81
|
+
# Get value from data
|
|
82
|
+
if data_key in data:
|
|
83
|
+
value = data[data_key]
|
|
84
|
+
elif field.default is not None:
|
|
85
|
+
value = field.default
|
|
86
|
+
elif not field.required:
|
|
87
|
+
value = None
|
|
88
|
+
else:
|
|
89
|
+
self._errors[field_name] = "Field is required"
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
# Validate field
|
|
93
|
+
try:
|
|
94
|
+
validated_value = field.validate(value, field_name)
|
|
95
|
+
self._validated_data[field_name] = validated_value
|
|
96
|
+
# Set as attribute for easy access
|
|
97
|
+
setattr(self, field_name, validated_value)
|
|
98
|
+
except ValidationError as e:
|
|
99
|
+
self._errors[field_name] = str(e)
|
|
100
|
+
|
|
101
|
+
# Check for unknown fields
|
|
102
|
+
known_keys = set(field.alias if field.alias else name for name, field in self._fields.items())
|
|
103
|
+
unknown_keys = set(data.keys()) - known_keys
|
|
104
|
+
if unknown_keys:
|
|
105
|
+
for key in unknown_keys:
|
|
106
|
+
self._errors[key] = f"Unknown field: {key}"
|
|
107
|
+
|
|
108
|
+
# Raise validation error if there are any errors
|
|
109
|
+
if self._errors:
|
|
110
|
+
raise ValidationError(f"Validation failed: {self._errors}")
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def validate(cls, data: Union[Dict[str, Any], 'BaseModel']) -> 'BaseModel':
|
|
114
|
+
"""
|
|
115
|
+
Validate data and return model instance
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
data: Data to validate (dict or BaseModel)
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Validated model instance
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
ValidationError: If validation fails
|
|
125
|
+
"""
|
|
126
|
+
if isinstance(data, cls):
|
|
127
|
+
return data
|
|
128
|
+
elif isinstance(data, dict):
|
|
129
|
+
return cls(**data)
|
|
130
|
+
else:
|
|
131
|
+
raise ValidationError("Data must be a dictionary or model instance")
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def parse_obj(cls, obj: Dict[str, Any]) -> 'BaseModel':
|
|
135
|
+
"""
|
|
136
|
+
Parse object (alias for validate)
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
obj: Object to parse
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Validated model instance
|
|
143
|
+
"""
|
|
144
|
+
return cls.validate(obj)
|
|
145
|
+
|
|
146
|
+
@classmethod
|
|
147
|
+
def parse_raw(cls, raw_data: Union[str, bytes], content_type: str = 'json') -> 'BaseModel':
|
|
148
|
+
"""
|
|
149
|
+
Parse raw data (JSON, etc.)
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
raw_data: Raw data to parse
|
|
153
|
+
content_type: Content type ('json' supported)
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Validated model instance
|
|
157
|
+
"""
|
|
158
|
+
if content_type == 'json':
|
|
159
|
+
if isinstance(raw_data, bytes):
|
|
160
|
+
raw_data = raw_data.decode('utf-8')
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
data = json.loads(raw_data)
|
|
164
|
+
return cls.validate(data)
|
|
165
|
+
except json.JSONDecodeError as e:
|
|
166
|
+
raise ValidationError(f"Invalid JSON: {e}")
|
|
167
|
+
else:
|
|
168
|
+
raise ValidationError(f"Unsupported content type: {content_type}")
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def parse_file(cls, file_path: str, content_type: str = 'json', encoding: str = 'utf-8') -> 'BaseModel':
|
|
172
|
+
"""
|
|
173
|
+
Parse data from file
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
file_path: Path to file
|
|
177
|
+
content_type: Content type ('json' supported)
|
|
178
|
+
encoding: File encoding
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Validated model instance
|
|
182
|
+
"""
|
|
183
|
+
try:
|
|
184
|
+
with open(file_path, 'r', encoding=encoding) as f:
|
|
185
|
+
raw_data = f.read()
|
|
186
|
+
return cls.parse_raw(raw_data, content_type)
|
|
187
|
+
except IOError as e:
|
|
188
|
+
raise ValidationError(f"Error reading file: {e}")
|
|
189
|
+
|
|
190
|
+
def dict(
|
|
191
|
+
self,
|
|
192
|
+
include: Optional[Union[List[str], set]] = None,
|
|
193
|
+
exclude: Optional[Union[List[str], set]] = None,
|
|
194
|
+
by_alias: bool = False
|
|
195
|
+
) -> Dict[str, Any]:
|
|
196
|
+
"""
|
|
197
|
+
Convert model to dictionary
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
include: Fields to include
|
|
201
|
+
exclude: Fields to exclude
|
|
202
|
+
by_alias: Use field aliases as keys
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Dictionary representation
|
|
206
|
+
"""
|
|
207
|
+
result = {}
|
|
208
|
+
|
|
209
|
+
for field_name, value in self._validated_data.items():
|
|
210
|
+
# Check include/exclude filters
|
|
211
|
+
if include is not None and field_name not in include:
|
|
212
|
+
continue
|
|
213
|
+
if exclude is not None and field_name in exclude:
|
|
214
|
+
continue
|
|
215
|
+
|
|
216
|
+
# Use alias if requested and available
|
|
217
|
+
if by_alias and field_name in self._fields:
|
|
218
|
+
field = self._fields[field_name]
|
|
219
|
+
key = field.alias if field.alias else field_name
|
|
220
|
+
else:
|
|
221
|
+
key = field_name
|
|
222
|
+
|
|
223
|
+
result[key] = value
|
|
224
|
+
|
|
225
|
+
return result
|
|
226
|
+
|
|
227
|
+
def json(
|
|
228
|
+
self,
|
|
229
|
+
include: Optional[Union[List[str], set]] = None,
|
|
230
|
+
exclude: Optional[Union[List[str], set]] = None,
|
|
231
|
+
by_alias: bool = False,
|
|
232
|
+
indent: Optional[int] = None
|
|
233
|
+
) -> str:
|
|
234
|
+
"""
|
|
235
|
+
Convert model to JSON string
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
include: Fields to include
|
|
239
|
+
exclude: Fields to exclude
|
|
240
|
+
by_alias: Use field aliases as keys
|
|
241
|
+
indent: JSON indentation
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
JSON string representation
|
|
245
|
+
"""
|
|
246
|
+
data = self.dict(include=include, exclude=exclude, by_alias=by_alias)
|
|
247
|
+
return json.dumps(data, indent=indent, default=str, ensure_ascii=False)
|
|
248
|
+
|
|
249
|
+
def copy(
|
|
250
|
+
self,
|
|
251
|
+
update: Optional[Dict[str, Any]] = None,
|
|
252
|
+
deep: bool = False
|
|
253
|
+
) -> 'BaseModel':
|
|
254
|
+
"""
|
|
255
|
+
Create a copy of the model
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
update: Fields to update in the copy
|
|
259
|
+
deep: Whether to perform deep copy
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
Copied model instance
|
|
263
|
+
"""
|
|
264
|
+
data = self.dict()
|
|
265
|
+
|
|
266
|
+
if update:
|
|
267
|
+
data.update(update)
|
|
268
|
+
|
|
269
|
+
if deep:
|
|
270
|
+
import copy
|
|
271
|
+
data = copy.deepcopy(data)
|
|
272
|
+
|
|
273
|
+
return self.__class__(**data)
|
|
274
|
+
|
|
275
|
+
def update(self, update_data: Dict[str, Any]) -> 'BaseModel':
|
|
276
|
+
"""
|
|
277
|
+
Update model with new data
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
update_data: Data to update
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
Updated model instance
|
|
284
|
+
"""
|
|
285
|
+
current_data = self.dict()
|
|
286
|
+
current_data.update(update_data)
|
|
287
|
+
return self.__class__(**current_data)
|
|
288
|
+
|
|
289
|
+
@classmethod
|
|
290
|
+
def schema(cls, by_alias: bool = True) -> Dict[str, Any]:
|
|
291
|
+
"""
|
|
292
|
+
Generate JSON schema for the model
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
by_alias: Use field aliases in schema
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
JSON schema dictionary
|
|
299
|
+
"""
|
|
300
|
+
properties = {}
|
|
301
|
+
required = []
|
|
302
|
+
|
|
303
|
+
for field_name, field in cls._fields.items():
|
|
304
|
+
# Use alias if requested and available
|
|
305
|
+
schema_name = field.alias if by_alias and field.alias else field_name
|
|
306
|
+
|
|
307
|
+
# Basic field schema
|
|
308
|
+
field_schema = {
|
|
309
|
+
'type': cls._get_json_type(field),
|
|
310
|
+
'description': field.description
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
# Add field-specific constraints
|
|
314
|
+
if hasattr(field, 'min_length') and field.min_length is not None:
|
|
315
|
+
field_schema['minLength'] = field.min_length
|
|
316
|
+
if hasattr(field, 'max_length') and field.max_length is not None:
|
|
317
|
+
field_schema['maxLength'] = field.max_length
|
|
318
|
+
if hasattr(field, 'min_value') and field.min_value is not None:
|
|
319
|
+
field_schema['minimum'] = field.min_value
|
|
320
|
+
if hasattr(field, 'max_value') and field.max_value is not None:
|
|
321
|
+
field_schema['maximum'] = field.max_value
|
|
322
|
+
if hasattr(field, 'pattern') and field.pattern is not None:
|
|
323
|
+
field_schema['pattern'] = field.pattern
|
|
324
|
+
if hasattr(field, 'choices') and field.choices is not None:
|
|
325
|
+
field_schema['enum'] = field.choices
|
|
326
|
+
|
|
327
|
+
# Default value
|
|
328
|
+
if field.default is not None:
|
|
329
|
+
field_schema['default'] = field.default
|
|
330
|
+
|
|
331
|
+
properties[schema_name] = field_schema
|
|
332
|
+
|
|
333
|
+
# Required fields
|
|
334
|
+
if field.required and field.default is None:
|
|
335
|
+
required.append(schema_name)
|
|
336
|
+
|
|
337
|
+
schema = {
|
|
338
|
+
'type': 'object',
|
|
339
|
+
'properties': properties,
|
|
340
|
+
'title': cls.__name__
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if required:
|
|
344
|
+
schema['required'] = required
|
|
345
|
+
|
|
346
|
+
return schema
|
|
347
|
+
|
|
348
|
+
@staticmethod
|
|
349
|
+
def _get_json_type(field: Field) -> str:
|
|
350
|
+
"""Get JSON schema type for a field"""
|
|
351
|
+
from .fields import (
|
|
352
|
+
StringField, IntField, FloatField, BoolField,
|
|
353
|
+
EmailField, URLField, DateField, ListField, DictField
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
if isinstance(field, (StringField, EmailField, URLField, DateField)):
|
|
357
|
+
return 'string'
|
|
358
|
+
elif isinstance(field, IntField):
|
|
359
|
+
return 'integer'
|
|
360
|
+
elif isinstance(field, FloatField):
|
|
361
|
+
return 'number'
|
|
362
|
+
elif isinstance(field, BoolField):
|
|
363
|
+
return 'boolean'
|
|
364
|
+
elif isinstance(field, ListField):
|
|
365
|
+
return 'array'
|
|
366
|
+
elif isinstance(field, DictField):
|
|
367
|
+
return 'object'
|
|
368
|
+
else:
|
|
369
|
+
return 'string' # Default fallback
|
|
370
|
+
|
|
371
|
+
def __repr__(self) -> str:
|
|
372
|
+
"""String representation of the model"""
|
|
373
|
+
fields_repr = ', '.join(f'{k}={v!r}' for k, v in self._validated_data.items())
|
|
374
|
+
return f'{self.__class__.__name__}({fields_repr})'
|
|
375
|
+
|
|
376
|
+
def __str__(self) -> str:
|
|
377
|
+
"""String representation of the model"""
|
|
378
|
+
return self.__repr__()
|
|
379
|
+
|
|
380
|
+
def __eq__(self, other) -> bool:
|
|
381
|
+
"""Check equality with another model"""
|
|
382
|
+
if not isinstance(other, self.__class__):
|
|
383
|
+
return False
|
|
384
|
+
return self._validated_data == other._validated_data
|
|
385
|
+
|
|
386
|
+
def __hash__(self) -> int:
|
|
387
|
+
"""Hash of the model"""
|
|
388
|
+
return hash(tuple(sorted(self._validated_data.items())))
|
|
389
|
+
|
|
390
|
+
def __getitem__(self, item: str) -> Any:
|
|
391
|
+
"""Get field value by name"""
|
|
392
|
+
if item in self._validated_data:
|
|
393
|
+
return self._validated_data[item]
|
|
394
|
+
raise KeyError(f"Field '{item}' not found")
|
|
395
|
+
|
|
396
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
397
|
+
"""Set field value (with validation)"""
|
|
398
|
+
if key in self._fields:
|
|
399
|
+
field = self._fields[key]
|
|
400
|
+
try:
|
|
401
|
+
validated_value = field.validate(value, key)
|
|
402
|
+
self._validated_data[key] = validated_value
|
|
403
|
+
setattr(self, key, validated_value)
|
|
404
|
+
except ValidationError as e:
|
|
405
|
+
raise ValidationError(f"Validation failed for field '{key}': {e}")
|
|
406
|
+
else:
|
|
407
|
+
raise KeyError(f"Unknown field '{key}'")
|
|
408
|
+
|
|
409
|
+
def __contains__(self, item: str) -> bool:
|
|
410
|
+
"""Check if field exists"""
|
|
411
|
+
return item in self._validated_data
|
|
412
|
+
|
|
413
|
+
def __iter__(self):
|
|
414
|
+
"""Iterate over field names"""
|
|
415
|
+
return iter(self._validated_data)
|
|
416
|
+
|
|
417
|
+
def keys(self):
|
|
418
|
+
"""Get field names"""
|
|
419
|
+
return self._validated_data.keys()
|
|
420
|
+
|
|
421
|
+
def values(self):
|
|
422
|
+
"""Get field values"""
|
|
423
|
+
return self._validated_data.values()
|
|
424
|
+
|
|
425
|
+
def items(self):
|
|
426
|
+
"""Get field items"""
|
|
427
|
+
return self._validated_data.items()
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
# Utility functions for creating models dynamically
|
|
431
|
+
def create_model(
|
|
432
|
+
model_name: str,
|
|
433
|
+
fields: Dict[str, Field],
|
|
434
|
+
base_class: Type[BaseModel] = BaseModel
|
|
435
|
+
) -> Type[BaseModel]:
|
|
436
|
+
"""
|
|
437
|
+
Create a model class dynamically
|
|
438
|
+
|
|
439
|
+
Args:
|
|
440
|
+
model_name: Name of the model class
|
|
441
|
+
fields: Dictionary of field definitions
|
|
442
|
+
base_class: Base class to inherit from
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
Dynamically created model class
|
|
446
|
+
"""
|
|
447
|
+
# Create class attributes
|
|
448
|
+
attrs = dict(fields)
|
|
449
|
+
attrs['__module__'] = __name__
|
|
450
|
+
|
|
451
|
+
# Create the class
|
|
452
|
+
model_class = type(model_name, (base_class,), attrs)
|
|
453
|
+
|
|
454
|
+
return model_class
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
# Validation shortcuts
|
|
458
|
+
def validate_data(data: Dict[str, Any], fields: Dict[str, Field]) -> Dict[str, Any]:
|
|
459
|
+
"""
|
|
460
|
+
Validate data against field definitions without creating a model
|
|
461
|
+
|
|
462
|
+
Args:
|
|
463
|
+
data: Data to validate
|
|
464
|
+
fields: Field definitions
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
Validated data dictionary
|
|
468
|
+
|
|
469
|
+
Raises:
|
|
470
|
+
ValidationError: If validation fails
|
|
471
|
+
"""
|
|
472
|
+
validated_data = {}
|
|
473
|
+
errors = {}
|
|
474
|
+
|
|
475
|
+
for field_name, field in fields.items():
|
|
476
|
+
# Get value from data
|
|
477
|
+
if field_name in data:
|
|
478
|
+
value = data[field_name]
|
|
479
|
+
elif field.default is not None:
|
|
480
|
+
value = field.default
|
|
481
|
+
elif not field.required:
|
|
482
|
+
value = None
|
|
483
|
+
else:
|
|
484
|
+
errors[field_name] = "Field is required"
|
|
485
|
+
continue
|
|
486
|
+
|
|
487
|
+
# Validate field
|
|
488
|
+
try:
|
|
489
|
+
validated_value = field.validate(value, field_name)
|
|
490
|
+
validated_data[field_name] = validated_value
|
|
491
|
+
except ValidationError as e:
|
|
492
|
+
errors[field_name] = str(e)
|
|
493
|
+
|
|
494
|
+
# Check for unknown fields
|
|
495
|
+
unknown_keys = set(data.keys()) - set(fields.keys())
|
|
496
|
+
if unknown_keys:
|
|
497
|
+
for key in unknown_keys:
|
|
498
|
+
errors[key] = f"Unknown field: {key}"
|
|
499
|
+
|
|
500
|
+
# Raise validation error if there are any errors
|
|
501
|
+
if errors:
|
|
502
|
+
raise ValidationError(f"Validation failed: {errors}")
|
|
503
|
+
|
|
504
|
+
return validated_data
|