sqlalchemy-serializer-meta 0.1.0__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.
- sqlalchemy_serializer_meta/__init__.py +424 -0
- sqlalchemy_serializer_meta/serializer.py +199 -0
- sqlalchemy_serializer_meta-0.1.0.dist-info/LICENSE +21 -0
- sqlalchemy_serializer_meta-0.1.0.dist-info/METADATA +392 -0
- sqlalchemy_serializer_meta-0.1.0.dist-info/RECORD +7 -0
- sqlalchemy_serializer_meta-0.1.0.dist-info/WHEEL +5 -0
- sqlalchemy_serializer_meta-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
"""
|
|
2
|
+
# Serializer Models
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from sqlalchemy import Table
|
|
6
|
+
from .serializer import (
|
|
7
|
+
AbstractSerializer,
|
|
8
|
+
Validatable,
|
|
9
|
+
Saveable,
|
|
10
|
+
Updatable,
|
|
11
|
+
Deletable,
|
|
12
|
+
Representable,
|
|
13
|
+
Creatable,
|
|
14
|
+
ToInstance,
|
|
15
|
+
ExtendsPydantic,
|
|
16
|
+
)
|
|
17
|
+
from typing import Self, NoReturn, Optional, Any, Union
|
|
18
|
+
from pydantic import BaseModel, ValidationError
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Serializer Exceptions
|
|
22
|
+
class SerializerError(Exception):
|
|
23
|
+
"""
|
|
24
|
+
Serializer Error
|
|
25
|
+
|
|
26
|
+
You must apply excpetion as following otherwise an error will occure
|
|
27
|
+
|
|
28
|
+
```py
|
|
29
|
+
from playstation import serializer
|
|
30
|
+
|
|
31
|
+
raise serializer.SerializerError(TypeError, "Error Message")
|
|
32
|
+
|
|
33
|
+
# Your code will be catched to form a message like this
|
|
34
|
+
try:
|
|
35
|
+
# Code
|
|
36
|
+
except SerializerError as e:
|
|
37
|
+
message: str = e.args[1] # Exception message
|
|
38
|
+
type: Exception = e.args[0] # Exception Type
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Serializer Interface
|
|
45
|
+
class SerializerInterface(
|
|
46
|
+
AbstractSerializer,
|
|
47
|
+
Validatable,
|
|
48
|
+
Saveable,
|
|
49
|
+
Updatable,
|
|
50
|
+
Deletable,
|
|
51
|
+
Representable,
|
|
52
|
+
Creatable,
|
|
53
|
+
ToInstance,
|
|
54
|
+
ExtendsPydantic,
|
|
55
|
+
):
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# Serializer class
|
|
60
|
+
class Serializer(SerializerInterface):
|
|
61
|
+
@property
|
|
62
|
+
def data(self: Self) -> dict:
|
|
63
|
+
"""
|
|
64
|
+
Property to get data
|
|
65
|
+
"""
|
|
66
|
+
# Check instance
|
|
67
|
+
if self.instance:
|
|
68
|
+
return self.to_representation(self.instance)
|
|
69
|
+
# Return self._data
|
|
70
|
+
return self.validated_data
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def validated_data(self: Self) -> dict:
|
|
74
|
+
"""
|
|
75
|
+
Property to get validated data
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
- Python Dictionary of the validated data
|
|
79
|
+
"""
|
|
80
|
+
# Check validated data
|
|
81
|
+
if not hasattr(self, "_validated_data"):
|
|
82
|
+
raise Exception("You Should run .is_valid() method first to validate data.")
|
|
83
|
+
# Return validated_data
|
|
84
|
+
return self._validated_data
|
|
85
|
+
|
|
86
|
+
def is_valid(self: Self) -> bool:
|
|
87
|
+
"""
|
|
88
|
+
Method to validate data
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
- True incase data validation passes otherwise False
|
|
92
|
+
"""
|
|
93
|
+
# Implement validation logic here
|
|
94
|
+
# Grab the data
|
|
95
|
+
data: Optional[dict] = self._data
|
|
96
|
+
|
|
97
|
+
# Check data
|
|
98
|
+
self.__data_checker()
|
|
99
|
+
|
|
100
|
+
# validate data
|
|
101
|
+
if self.validate(data):
|
|
102
|
+
return True
|
|
103
|
+
# Data not valid
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
def __data_checker(self: Self) -> Optional[NoReturn]:
|
|
107
|
+
"""
|
|
108
|
+
Method used to check private attribute _data
|
|
109
|
+
"""
|
|
110
|
+
# Grab data
|
|
111
|
+
data: Optional[dict] = self._data
|
|
112
|
+
# Check if it is not None
|
|
113
|
+
if data is None:
|
|
114
|
+
raise ValueError("data: parameter should not be None")
|
|
115
|
+
# Check datatype for self._data
|
|
116
|
+
if not isinstance(data, dict):
|
|
117
|
+
raise TypeError("data: parameter should be of type dictionary")
|
|
118
|
+
|
|
119
|
+
def validate(self: Self, data: dict) -> bool:
|
|
120
|
+
"""
|
|
121
|
+
Method used to validate data.
|
|
122
|
+
|
|
123
|
+
Creates:
|
|
124
|
+
self._validated_data is created when using this function
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
- bool True in case of validated otherwise False
|
|
128
|
+
"""
|
|
129
|
+
# Implement your validation logic here
|
|
130
|
+
# Check data keys if match the fields of the model or mismatch
|
|
131
|
+
self.__validate_data_fields(data)
|
|
132
|
+
# Get all validate methods
|
|
133
|
+
validate_methods: list[str] = self.__validate_methods()
|
|
134
|
+
# Create validated_data
|
|
135
|
+
self._validated_data: dict = data
|
|
136
|
+
# Get Fields
|
|
137
|
+
fields: set[str] = set(self.fields)
|
|
138
|
+
# Check Validation methods
|
|
139
|
+
if validate_methods:
|
|
140
|
+
for method in validate_methods:
|
|
141
|
+
try:
|
|
142
|
+
# Grab key
|
|
143
|
+
key: str = method.replace("validate_", "")
|
|
144
|
+
# Grab the field
|
|
145
|
+
value: Optional[str] = data.get(key)
|
|
146
|
+
# Check if field is in fields
|
|
147
|
+
if key in fields:
|
|
148
|
+
# Call each validation method
|
|
149
|
+
self._validated_data[key] = getattr(self, method)(value)
|
|
150
|
+
except SerializerError as e:
|
|
151
|
+
# add to error
|
|
152
|
+
self.errors.append(
|
|
153
|
+
{
|
|
154
|
+
"msg": str(e.args[1]),
|
|
155
|
+
"field": key,
|
|
156
|
+
"input": (
|
|
157
|
+
value
|
|
158
|
+
if key != "password"
|
|
159
|
+
else "" if key in self.write_only else ""
|
|
160
|
+
),
|
|
161
|
+
"type": f"{e.args[0].__name__}",
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
except Exception as e:
|
|
165
|
+
self.errors.append(
|
|
166
|
+
{
|
|
167
|
+
"msg": str(e),
|
|
168
|
+
"field": key,
|
|
169
|
+
"input": (
|
|
170
|
+
value
|
|
171
|
+
if key != "password"
|
|
172
|
+
else "" if key in self.write_only else ""
|
|
173
|
+
),
|
|
174
|
+
"type": f"{type(e).__name__}",
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
# Validate using pydantic
|
|
178
|
+
if self.pydantic_model is not None:
|
|
179
|
+
self._validated_data = getattr(self, "validate_pydantic")(
|
|
180
|
+
self.validated_data
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
return not bool(self.errors)
|
|
184
|
+
|
|
185
|
+
def __validate_data_fields(self: Self, data: dict) -> Optional[NoReturn]:
|
|
186
|
+
"""
|
|
187
|
+
Method used to validate data fields
|
|
188
|
+
"""
|
|
189
|
+
# Grab fields
|
|
190
|
+
fields: set = {
|
|
191
|
+
key for key in self.model.__dict__.keys() if not key.startswith("_")
|
|
192
|
+
}
|
|
193
|
+
# Check if data keys match with fields
|
|
194
|
+
for key in data.keys():
|
|
195
|
+
if key not in fields:
|
|
196
|
+
raise ValueError(f"Invalid field: {key}")
|
|
197
|
+
|
|
198
|
+
def __validate_methods(self: Self) -> list[str]:
|
|
199
|
+
"""
|
|
200
|
+
Method to retrieve a list of validation callable methods.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
- list of (callable) methods
|
|
204
|
+
"""
|
|
205
|
+
return [
|
|
206
|
+
method
|
|
207
|
+
for method in dir(self)
|
|
208
|
+
if method.startswith("validate_") # Get all validate methods
|
|
209
|
+
if method.replace("validate_", "")
|
|
210
|
+
in self.fields # Check if validation method matchs name of a field
|
|
211
|
+
if callable(getattr(self, method)) # Check if it is a callable method
|
|
212
|
+
]
|
|
213
|
+
|
|
214
|
+
def validate_pydantic(self: Self, validated_data: dict) -> Optional[BaseModel]:
|
|
215
|
+
"""Validate data using Pydantic."""
|
|
216
|
+
try:
|
|
217
|
+
model: BaseModel = getattr(self, "pydantic_model")
|
|
218
|
+
if not model:
|
|
219
|
+
raise AttributeError("No Pydantic model defined")
|
|
220
|
+
return model(**validated_data).model_dump()
|
|
221
|
+
except ValidationError as e:
|
|
222
|
+
self.errors.extend(e.errors())
|
|
223
|
+
except SerializerError as e:
|
|
224
|
+
# add to error
|
|
225
|
+
self.errors.append(
|
|
226
|
+
{
|
|
227
|
+
"msg": str(
|
|
228
|
+
e.args[1]
|
|
229
|
+
), # error message SerializerError(<class Exception>, error_message)
|
|
230
|
+
"type": f"{e.args[0].__name__}", # Excpetion class name,
|
|
231
|
+
}
|
|
232
|
+
)
|
|
233
|
+
except AttributeError as e:
|
|
234
|
+
# error
|
|
235
|
+
error: dict = {"msg": str(e), "type": f"{type(e).__name__}"}
|
|
236
|
+
self.errors.append(error)
|
|
237
|
+
except Exception as e:
|
|
238
|
+
# error
|
|
239
|
+
error: dict = {"msg": str(e), "type": f"{type(e).__name__}"}
|
|
240
|
+
self.errors.append(error)
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
def save(self: Self) -> object:
|
|
244
|
+
"""
|
|
245
|
+
Method to create new instance of an object or update an existing object
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
- Instance of the model.
|
|
249
|
+
"""
|
|
250
|
+
# Grab the data
|
|
251
|
+
data: Optional[dict] = getattr(self, "validated_data", None)
|
|
252
|
+
# Check data
|
|
253
|
+
if data is None:
|
|
254
|
+
raise ValueError("No validated data found")
|
|
255
|
+
# Check if save method exists
|
|
256
|
+
self.__check_save()
|
|
257
|
+
# Check if object instance exists or not
|
|
258
|
+
# Grab object id
|
|
259
|
+
object_id: Optional[int] = data.get("id", None)
|
|
260
|
+
instance: Optional[None] = None
|
|
261
|
+
# Check if id exists
|
|
262
|
+
if object_id:
|
|
263
|
+
# Check if object instance exists or not
|
|
264
|
+
instance: object = self.model.query.get(object_id)
|
|
265
|
+
if instance:
|
|
266
|
+
# Update instance
|
|
267
|
+
self.instance = self.update(data, instance)
|
|
268
|
+
return self.instance
|
|
269
|
+
# Else perform create new instance method
|
|
270
|
+
self.instance = self.create(data)
|
|
271
|
+
# Return the instance
|
|
272
|
+
return self.instance
|
|
273
|
+
|
|
274
|
+
def __check_save(self: Self) -> Optional[NoReturn]:
|
|
275
|
+
"""
|
|
276
|
+
Checker function to check if model object has a method .save()
|
|
277
|
+
|
|
278
|
+
Raises:
|
|
279
|
+
- Error in case the model does not have a method .save()
|
|
280
|
+
"""
|
|
281
|
+
if not hasattr(self.model, "save"):
|
|
282
|
+
raise AttributeError("Model object must have a method .save()")
|
|
283
|
+
|
|
284
|
+
def update(self: Self, validated_data: dict, instance: object) -> object:
|
|
285
|
+
"""
|
|
286
|
+
Method to update an existing object
|
|
287
|
+
"""
|
|
288
|
+
# Implement update logic here
|
|
289
|
+
for key, value in validated_data.items():
|
|
290
|
+
# Check if model has the key and key is not in read_only fields
|
|
291
|
+
if hasattr(self.model, key) and key not in self.read_only:
|
|
292
|
+
setattr(instance, key, value)
|
|
293
|
+
# Save
|
|
294
|
+
instance.save()
|
|
295
|
+
# Return instance
|
|
296
|
+
return instance
|
|
297
|
+
|
|
298
|
+
def delete(self: Self) -> None:
|
|
299
|
+
"""
|
|
300
|
+
Method to delete an existing object
|
|
301
|
+
"""
|
|
302
|
+
# Implement delete logic here
|
|
303
|
+
instance: object = self.instance
|
|
304
|
+
# Check instance
|
|
305
|
+
if not instance:
|
|
306
|
+
raise ValueError("Instance does not exist")
|
|
307
|
+
# Perform delete function
|
|
308
|
+
if not hasattr(instance, "delete"):
|
|
309
|
+
raise AttributeError("Model object must have a method .delete()")
|
|
310
|
+
# Initiate delete instance
|
|
311
|
+
instance.delete()
|
|
312
|
+
|
|
313
|
+
def create(self, validated_data: dict) -> object:
|
|
314
|
+
"""
|
|
315
|
+
Create a new instance of the model.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
- validated_data (dict): Validated data of the parameters of new instance.
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
- Newly created instance of the model.
|
|
322
|
+
"""
|
|
323
|
+
# Create the model
|
|
324
|
+
instance = self.model(**validated_data)
|
|
325
|
+
# Create instance
|
|
326
|
+
instance.save()
|
|
327
|
+
# Return the instance
|
|
328
|
+
return instance
|
|
329
|
+
|
|
330
|
+
def to_representation(self, instance: object) -> dict:
|
|
331
|
+
"""
|
|
332
|
+
Convert the model instance into a dictionary representation.
|
|
333
|
+
"""
|
|
334
|
+
# Implement representation logic here
|
|
335
|
+
fields: list[str] = self.fields
|
|
336
|
+
# Serialize
|
|
337
|
+
serialized_data: dict = {
|
|
338
|
+
field: self.__grab_field(instance, field)
|
|
339
|
+
for field in fields
|
|
340
|
+
if field not in self.write_only
|
|
341
|
+
}
|
|
342
|
+
# Return Serialized data
|
|
343
|
+
return serialized_data
|
|
344
|
+
|
|
345
|
+
def __grab_field(self, instance: object, field: str) -> Any:
|
|
346
|
+
"""Helper function to grab field for serialization process."""
|
|
347
|
+
# Grab field value from instance_dict
|
|
348
|
+
value: Any = getattr(instance, field)
|
|
349
|
+
# Grab nested serializer
|
|
350
|
+
# If value is None, check if the field is a relationship
|
|
351
|
+
if self.__check_model(value):
|
|
352
|
+
nested_serialization = getattr(self, field, None)
|
|
353
|
+
return self.__serialize_relationship(nested_serialization, value)
|
|
354
|
+
if isinstance(value, list):
|
|
355
|
+
nested_serialization = getattr(self, field, None)
|
|
356
|
+
return self.__serialize_relationship_list(nested_serialization, value)
|
|
357
|
+
# Return Value
|
|
358
|
+
return value
|
|
359
|
+
|
|
360
|
+
def __check_model(self, instance: object) -> bool:
|
|
361
|
+
"""Helper function to check if the model exists."""
|
|
362
|
+
# Check if the model exists
|
|
363
|
+
return hasattr(instance, "__table__") and isinstance(instance.__table__, Table)
|
|
364
|
+
|
|
365
|
+
def __serialize_relationship(
|
|
366
|
+
self,
|
|
367
|
+
nested_serializer: Optional["Serializer"],
|
|
368
|
+
relationship: object,
|
|
369
|
+
) -> Any:
|
|
370
|
+
"""Helper function to serialize a single nested relationship."""
|
|
371
|
+
if nested_serializer and isinstance(nested_serializer, Serializer):
|
|
372
|
+
nested_serializer.instance = relationship
|
|
373
|
+
return nested_serializer.data
|
|
374
|
+
return self.__grab_field(relationship, "id")
|
|
375
|
+
|
|
376
|
+
def __serialize_relationship_list(
|
|
377
|
+
self, nested_serializer: Optional["Serializer"], relationship: list
|
|
378
|
+
) -> list:
|
|
379
|
+
"""Helper function to serialize a list of nested relationships."""
|
|
380
|
+
if nested_serializer and isinstance(nested_serializer, Serializer):
|
|
381
|
+
nested_serializer.instance = relationship
|
|
382
|
+
return nested_serializer.data
|
|
383
|
+
return [
|
|
384
|
+
self.__grab_field(model, "id")
|
|
385
|
+
for model in relationship
|
|
386
|
+
if self.__check_model(model)
|
|
387
|
+
]
|
|
388
|
+
|
|
389
|
+
def to_instance(self, find_by: str = "id") -> Optional[object]:
|
|
390
|
+
"""
|
|
391
|
+
Convert the dictionary representation into a model instance.
|
|
392
|
+
"""
|
|
393
|
+
# Implement instance creation logic here
|
|
394
|
+
if self._data:
|
|
395
|
+
instance: object = self.model.query.get(self._data[find_by])
|
|
396
|
+
return instance
|
|
397
|
+
return None
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
# Model Serializer
|
|
401
|
+
class ModelSerializer(Serializer):
|
|
402
|
+
@property
|
|
403
|
+
def data(self: Self) -> dict:
|
|
404
|
+
"""
|
|
405
|
+
Property to get serialized model
|
|
406
|
+
"""
|
|
407
|
+
# Get the model
|
|
408
|
+
model: object = self.instance
|
|
409
|
+
# Check model
|
|
410
|
+
if model is None:
|
|
411
|
+
|
|
412
|
+
raise ValueError("Model instance is not provided")
|
|
413
|
+
|
|
414
|
+
# Treat instance as many instances
|
|
415
|
+
if self.many:
|
|
416
|
+
if not isinstance(self.instance, list):
|
|
417
|
+
raise TypeError("Expected a list of instances with many set to True")
|
|
418
|
+
return [self.to_representation(instance) for instance in self.instance]
|
|
419
|
+
|
|
420
|
+
if isinstance(model, list):
|
|
421
|
+
model = model[0]
|
|
422
|
+
|
|
423
|
+
# Call to_representation method
|
|
424
|
+
return self.to_representation(model)
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""
|
|
2
|
+
# File that contains abstract classes for how serializer should be implemented.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import Optional, Iterable, Self
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Abstract Serializer
|
|
11
|
+
class AbstractSerializer(ABC):
|
|
12
|
+
"""
|
|
13
|
+
Abstract class for serializer implementation.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
instance: Optional[object] = None,
|
|
19
|
+
data: Optional[dict] = None,
|
|
20
|
+
many: bool = False,
|
|
21
|
+
) -> None:
|
|
22
|
+
self.instance: Optional[object] = instance
|
|
23
|
+
self._data: Optional[dict] = data
|
|
24
|
+
self.many: bool = many
|
|
25
|
+
self._errors = []
|
|
26
|
+
self._meta = self.get_meta() # Access Meta Class
|
|
27
|
+
self._fields: Optional[Iterable[str]] = getattr(self._meta, "fields", None)
|
|
28
|
+
self._write_only: Iterable[str] = getattr(self._meta, "write_only", [])
|
|
29
|
+
self._read_only: Iterable[str] = getattr(self._meta, "read_only", {"id"})
|
|
30
|
+
|
|
31
|
+
self.fields = self._fields # Trigger the fields setter
|
|
32
|
+
self.write_only = self._write_only # Trigger the write_only setter
|
|
33
|
+
self.read_only = self._read_only # Trigger the read_only setter
|
|
34
|
+
|
|
35
|
+
def get_meta(self):
|
|
36
|
+
meta = getattr(self, "Meta", None)
|
|
37
|
+
if meta is None:
|
|
38
|
+
raise AttributeError("Meta class is not defined.")
|
|
39
|
+
return meta
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def data(self) -> dict:
|
|
43
|
+
"""
|
|
44
|
+
Property to get serialized model
|
|
45
|
+
"""
|
|
46
|
+
raise NotImplementedError("data property must be implemented by subclasses")
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def model(self):
|
|
50
|
+
return getattr(self._meta, "model", None)
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def fields(self) -> Optional[Iterable[str]]:
|
|
54
|
+
return self._fields
|
|
55
|
+
|
|
56
|
+
@fields.setter
|
|
57
|
+
def fields(self, serializer_fields: Optional[Iterable[str]]) -> None:
|
|
58
|
+
"""
|
|
59
|
+
Setter property method for fields.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
- serializer_fields (Iterable Python Object): Fields for serializer.
|
|
63
|
+
"""
|
|
64
|
+
if serializer_fields is None:
|
|
65
|
+
raise ValueError("Fields cannot be None.")
|
|
66
|
+
fields = [key for key in self.model.__dict__.keys() if not key.startswith("_")]
|
|
67
|
+
if not self.__check_fields(serializer_fields):
|
|
68
|
+
fields = [
|
|
69
|
+
key for key in serializer_fields if self.__existing_field(key, fields)
|
|
70
|
+
]
|
|
71
|
+
self._fields = fields
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def write_only(self) -> Iterable[str]:
|
|
75
|
+
return self._write_only
|
|
76
|
+
|
|
77
|
+
@write_only.setter
|
|
78
|
+
def write_only(self, serializer_fields: Optional[Iterable[str]]) -> None:
|
|
79
|
+
"""
|
|
80
|
+
Setter property method write_only fields.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
- serializer_fields (Iterable Python Object): Fields for serializer.
|
|
84
|
+
"""
|
|
85
|
+
if serializer_fields is None:
|
|
86
|
+
return
|
|
87
|
+
fields = [key for key in self.model.__dict__.keys()]
|
|
88
|
+
if not self.__check_fields(serializer_fields):
|
|
89
|
+
fields = [
|
|
90
|
+
key for key in serializer_fields if self.__existing_field(key, fields)
|
|
91
|
+
]
|
|
92
|
+
self._write_only = fields
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def read_only(self) -> Iterable[str]:
|
|
96
|
+
return self._read_only
|
|
97
|
+
|
|
98
|
+
@read_only.setter
|
|
99
|
+
def read_only(self, serializer_fields: Optional[Iterable[str]]) -> None:
|
|
100
|
+
"""
|
|
101
|
+
Setter property method read_only fields.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
- serializer_fields (Iterable Python Object): Fields for serializer.
|
|
105
|
+
"""
|
|
106
|
+
if serializer_fields is None:
|
|
107
|
+
return
|
|
108
|
+
fields = [key for key in self.model.__dict__.keys()]
|
|
109
|
+
if not self.__check_fields(serializer_fields):
|
|
110
|
+
fields = [
|
|
111
|
+
key for key in serializer_fields if self.__existing_field(key, fields)
|
|
112
|
+
]
|
|
113
|
+
self._read_only = fields
|
|
114
|
+
|
|
115
|
+
def __existing_field(self, field: str, fields: list[str]) -> bool:
|
|
116
|
+
"""
|
|
117
|
+
Check if field exists in fields.
|
|
118
|
+
"""
|
|
119
|
+
if field not in fields:
|
|
120
|
+
raise ValueError(f"Field '{field}' does not exist in model.")
|
|
121
|
+
return True
|
|
122
|
+
|
|
123
|
+
def __check_fields(self, fields: Optional[Iterable[str]]) -> bool:
|
|
124
|
+
"""
|
|
125
|
+
Helper function to check fields specified fields against '__all__'
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
- fields (Optional[Iterable[str]]): Fields
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
- True in case all fields otherwise False
|
|
132
|
+
"""
|
|
133
|
+
if isinstance(fields, str) and fields == "__all__":
|
|
134
|
+
return True
|
|
135
|
+
if isinstance(fields, list) and len(fields) == 1 and fields[0] == "__all__":
|
|
136
|
+
return True
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def errors(self) -> Optional[list]:
|
|
141
|
+
return self._errors
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class Validatable(ABC):
|
|
145
|
+
@property
|
|
146
|
+
def validated_data(self) -> dict:
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
@abstractmethod
|
|
150
|
+
def is_valid(self) -> bool:
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
@abstractmethod
|
|
154
|
+
def validate(self) -> bool:
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class Saveable(ABC):
|
|
159
|
+
@abstractmethod
|
|
160
|
+
def save(self) -> object:
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class Updatable(ABC):
|
|
165
|
+
@abstractmethod
|
|
166
|
+
def update(self) -> object:
|
|
167
|
+
pass
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class Deletable(ABC):
|
|
171
|
+
@abstractmethod
|
|
172
|
+
def delete(self) -> None:
|
|
173
|
+
pass
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class Representable(ABC):
|
|
177
|
+
@abstractmethod
|
|
178
|
+
def to_representation(self, instance: object) -> dict:
|
|
179
|
+
pass
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class Creatable(ABC):
|
|
183
|
+
@abstractmethod
|
|
184
|
+
def create(self, validated_data: dict) -> dict:
|
|
185
|
+
pass
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class ToInstance(ABC):
|
|
189
|
+
@abstractmethod
|
|
190
|
+
def to_instance(self, data: dict) -> object:
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class ExtendsPydantic(ABC):
|
|
195
|
+
pydantic_model: Optional[BaseModel] = None
|
|
196
|
+
|
|
197
|
+
@abstractmethod
|
|
198
|
+
def validate_pydantic(self: Self, data: dict) -> None:
|
|
199
|
+
pass
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017 Yuri Boiko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: sqlalchemy_serializer_meta
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A simple serializer for SQLAlchemy models and Flask Models
|
|
5
|
+
Home-page: https://github.com/Wehda23/sqlalchemy_serializer_meta
|
|
6
|
+
Author: Waheed Khaled Elhariri
|
|
7
|
+
Author-email: waheedkhaled95@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: sqlalchemy serializer pydantic flask
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: sqlalchemy
|
|
20
|
+
Requires-Dist: pydantic ==2.7.4
|
|
21
|
+
Requires-Dist: pydantic-core ==2.18.4
|
|
22
|
+
|
|
23
|
+
# Serializer Module for Flask Application
|
|
24
|
+
|
|
25
|
+
This module provides a set of serializers for your Flask application, allowing you to easily validate and serialize data. The serializers are built with flexibility and extendability in mind, providing features such as nested serialization and integration with Pydantic models.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
To use the serializers in your project, simply include the necessary files in your application directory.
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
Can be used to Serialize SQLAlchemy Model or Flask-SQLAlchemy model.
|
|
34
|
+
|
|
35
|
+
### Basic Usage
|
|
36
|
+
|
|
37
|
+
Here's an example of how to use the `UserRegisterSerializer` to validate and create a new user:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from your_application.serializers import UserRegisterSerializer
|
|
41
|
+
from your_application.models import User
|
|
42
|
+
|
|
43
|
+
data = {
|
|
44
|
+
"first_name": "John",
|
|
45
|
+
"last_name": "Doe",
|
|
46
|
+
"email": "john.doe@example.com",
|
|
47
|
+
"password": "securepassword123"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
serializer = UserRegisterSerializer(data=data)
|
|
51
|
+
if serializer.is_valid():
|
|
52
|
+
user = serializer.save()
|
|
53
|
+
print("User created:", user)
|
|
54
|
+
else:
|
|
55
|
+
print("Errors:", serializer.errors)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
In this example your custom serializer module/package
|
|
59
|
+
|
|
60
|
+
```py
|
|
61
|
+
from sqlalchemy_serializer_meta import Serializer, ModelSerializer, SerializerError
|
|
62
|
+
from your_application.models import User
|
|
63
|
+
|
|
64
|
+
class UserRegisterSerializer(Serializer):
|
|
65
|
+
class Meta:
|
|
66
|
+
model = User
|
|
67
|
+
fields = ['first_name', 'last_name', 'email', 'password']
|
|
68
|
+
|
|
69
|
+
def validate_first_name(self, value) -> str:
|
|
70
|
+
# Your Code
|
|
71
|
+
return value
|
|
72
|
+
|
|
73
|
+
class UserSerializer(ModelSerializer):
|
|
74
|
+
class Meta:
|
|
75
|
+
model = User
|
|
76
|
+
fields = ["first_name", "last_name", "email", "password"]
|
|
77
|
+
|
|
78
|
+
class UserLoginSerializer(Serializer):
|
|
79
|
+
class Meta:
|
|
80
|
+
model = User
|
|
81
|
+
fields = ['email', 'password']
|
|
82
|
+
|
|
83
|
+
def validate_email(self, email: str) -> str:
|
|
84
|
+
# Your Code
|
|
85
|
+
# Code pass
|
|
86
|
+
self.instance = object # User Object
|
|
87
|
+
return email
|
|
88
|
+
|
|
89
|
+
def validate_password(self, password: str) -> str:
|
|
90
|
+
# Your Code
|
|
91
|
+
return password
|
|
92
|
+
|
|
93
|
+
# Override serializer
|
|
94
|
+
def to_representation(self, instance: object) -> dict:
|
|
95
|
+
# Your Code
|
|
96
|
+
return UserSerializer(instance=instance).data
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Nested Serialization
|
|
100
|
+
|
|
101
|
+
To handle nested relationships, you can define nested serializers and use them in your main serializer.
|
|
102
|
+
|
|
103
|
+
#### Single Nested Object
|
|
104
|
+
|
|
105
|
+
When dealing with a single nested object, you do not need to specify `many=True`. Here's an example:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from your_application.serializers import UserSerializer, ShippingAddressSerializer
|
|
109
|
+
|
|
110
|
+
class UserDetailSerializer(UserSerializer):
|
|
111
|
+
shipping_address = ShippingAddressSerializer()
|
|
112
|
+
|
|
113
|
+
class Meta:
|
|
114
|
+
model = User
|
|
115
|
+
fields = ['id', 'first_name', 'last_name', 'email', 'shipping_address']
|
|
116
|
+
|
|
117
|
+
# Usage
|
|
118
|
+
user = User.query.get(1)
|
|
119
|
+
serializer = UserDetailSerializer(instance=user)
|
|
120
|
+
print(serializer.data)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
This will serialize the `shipping_address` field as a dictionary:
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"id": 1,
|
|
128
|
+
"first_name": "John",
|
|
129
|
+
"last_name": "Doe",
|
|
130
|
+
"email": "john.doe@example.com",
|
|
131
|
+
"shipping_address": {
|
|
132
|
+
"street": "123 Main St",
|
|
133
|
+
"city": "Anytown",
|
|
134
|
+
"zipcode": "12345"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### Multiple Nested Objects
|
|
140
|
+
|
|
141
|
+
When dealing with multiple nested objects, use `many=True` to return a list of dictionaries. Here's an example:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from your_application.serializers import UserSerializer, ShippingAddressSerializer
|
|
145
|
+
|
|
146
|
+
class UserDetailSerializer(UserSerializer):
|
|
147
|
+
shipping_addresses = ShippingAddressSerializer(many=True)
|
|
148
|
+
|
|
149
|
+
class Meta:
|
|
150
|
+
model = User
|
|
151
|
+
fields = ['id', 'first_name', 'last_name', 'email', 'shipping_addresses']
|
|
152
|
+
|
|
153
|
+
# Usage
|
|
154
|
+
user = User.query.get(1)
|
|
155
|
+
serializer = UserDetailSerializer(instance=user)
|
|
156
|
+
print(serializer.data)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
This will serialize the `shipping_addresses` field as a list of dictionaries:
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"id": 1,
|
|
164
|
+
"first_name": "John",
|
|
165
|
+
"last_name": "Doe",
|
|
166
|
+
"email": "john.doe@example.com",
|
|
167
|
+
"shipping_addresses": [
|
|
168
|
+
{
|
|
169
|
+
"street": "123 Main St",
|
|
170
|
+
"city": "Anytown",
|
|
171
|
+
"zipcode": "12345"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"street": "456 Elm St",
|
|
175
|
+
"city": "Othertown",
|
|
176
|
+
"zipcode": "67890"
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Integration with Pydantic Models
|
|
183
|
+
|
|
184
|
+
You can also integrate Pydantic models for additional validation. Here's an example:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from your_application.serializers import UserSerializer, SerializerError
|
|
188
|
+
from pydantic import BaseModel, EmailStr, ValidationError
|
|
189
|
+
|
|
190
|
+
class UserPydanticModel(BaseModel):
|
|
191
|
+
first_name: str
|
|
192
|
+
last_name: str
|
|
193
|
+
email: EmailStr
|
|
194
|
+
|
|
195
|
+
class UserPydanticSerializer(UserSerializer):
|
|
196
|
+
pydantic_model = UserPydanticModel
|
|
197
|
+
|
|
198
|
+
def validate_pydantic(self, data):
|
|
199
|
+
try:
|
|
200
|
+
pydantic_instance = self.pydantic_model(**data)
|
|
201
|
+
return pydantic_instance.model_dump() # Dump the validated model to dictionary
|
|
202
|
+
except ValidationError as e:
|
|
203
|
+
raise SerializerError(e, "Invalid data")
|
|
204
|
+
|
|
205
|
+
# Usage
|
|
206
|
+
data = {
|
|
207
|
+
"first_name": "John",
|
|
208
|
+
"last_name": "Doe",
|
|
209
|
+
"email": "john.doe@example.com"
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
serializer = UserPydanticSerializer(data=data)
|
|
213
|
+
if serializer.is_valid():
|
|
214
|
+
print("Valid data:", serializer.validated_data)
|
|
215
|
+
else:
|
|
216
|
+
print("Errors:", serializer.errors)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Updating Existing Instances
|
|
220
|
+
|
|
221
|
+
The `.save()` method is designed to update an instance if it already exists, rather than creating a new one. If the `instance` attribute is set, `.save()` will call the `.update()` method. If `instance` is `None`, it will call `.create()`.
|
|
222
|
+
|
|
223
|
+
Here's an example:
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
from your_application.serializers import UserSerializer
|
|
227
|
+
from your_application.models import User
|
|
228
|
+
|
|
229
|
+
# Update an existing user
|
|
230
|
+
user_instance = User.query.get(1)
|
|
231
|
+
data = {
|
|
232
|
+
"first_name": "Jane",
|
|
233
|
+
"last_name": "Doe",
|
|
234
|
+
"email": "jane.doe@example.com"
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
serializer = UserSerializer(instance=user_instance, data=data)
|
|
238
|
+
if serializer.is_valid():
|
|
239
|
+
user = serializer.save()
|
|
240
|
+
print("User updated:", user)
|
|
241
|
+
else:
|
|
242
|
+
print("Errors:", serializer.errors)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Custom Serializers
|
|
246
|
+
|
|
247
|
+
You can create custom serializers by inheriting from `Serializer` or `ModelSerializer`. Here's an example:
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
from sqlalchemy_serializer_meta import ModelSerializer
|
|
251
|
+
from your_application.models import CustomModel
|
|
252
|
+
|
|
253
|
+
class CustomModelSerializer(ModelSerializer):
|
|
254
|
+
class Meta:
|
|
255
|
+
model = CustomModel
|
|
256
|
+
fields = ['id', 'name', 'description']
|
|
257
|
+
|
|
258
|
+
# Usage
|
|
259
|
+
instance = CustomModel.query.get(1)
|
|
260
|
+
serializer = CustomModelSerializer(instance=instance)
|
|
261
|
+
print(serializer.data)
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Serializer Validated Fields
|
|
265
|
+
|
|
266
|
+
You can customize your field validation by adding `validate_<field_name>` methods in your `Serializer class`.
|
|
267
|
+
|
|
268
|
+
Can be used in SQLAlchemy and Flask API
|
|
269
|
+
|
|
270
|
+
```py
|
|
271
|
+
from sqlalchemy_serializer_meta import Serializer
|
|
272
|
+
from your_application.models import CustomModel
|
|
273
|
+
from your_application.validators import CustomValidator
|
|
274
|
+
from typing import Union
|
|
275
|
+
|
|
276
|
+
class CustomPydantic(BaseModel):
|
|
277
|
+
id: str
|
|
278
|
+
name: str
|
|
279
|
+
description: EmailStr
|
|
280
|
+
|
|
281
|
+
class CustomModelSerializer(Serializer):
|
|
282
|
+
# If not specified the validate_pydantic method won't be called hence no errors will occure
|
|
283
|
+
pydantic_model: BaseModel = CustomPydantic
|
|
284
|
+
|
|
285
|
+
class Meta:
|
|
286
|
+
model = CustomModel
|
|
287
|
+
fields = ['id', 'name', 'description']
|
|
288
|
+
|
|
289
|
+
# Custom Validation for name
|
|
290
|
+
def validate_name(self, value):
|
|
291
|
+
# This will trigger Custome made Validator or pass
|
|
292
|
+
CustomValidator(SerializerError).validate(value)
|
|
293
|
+
return value
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def serialize(data: dict) -> Union[dict, list[dict]]:
|
|
297
|
+
# To trigger validators you have to initiate .is_valid() method
|
|
298
|
+
serializer = CustomModelSerializer(data=data)
|
|
299
|
+
|
|
300
|
+
try:
|
|
301
|
+
# Trigger validation
|
|
302
|
+
if serializer.is_valid():
|
|
303
|
+
serializer.save()
|
|
304
|
+
return serializer.data
|
|
305
|
+
# If validation did not pass custom validation Grab Errors
|
|
306
|
+
return serializer.errors
|
|
307
|
+
# if validation did not pass Pydantic Validation
|
|
308
|
+
except ValidationError as e: # If pydanic model not defined this error will not occure because validate_pydantic wont be called.
|
|
309
|
+
return e.errors()
|
|
310
|
+
# other unexpected or unhandled errors.
|
|
311
|
+
except Exception as e:
|
|
312
|
+
return e
|
|
313
|
+
|
|
314
|
+
serialize({'name': 'test', 'description': 'test@test.com',"id":1})
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Reusable Serializer Design with SQLAlchemy and Flask-SQLAlchemy
|
|
318
|
+
|
|
319
|
+
The design of the `CustomModelSerializer` class in our Flask application leverages the flexibility and robustness of SQLAlchemy and Flask-SQLAlchemy, allowing it to be highly reusable across different projects. This approach ensures that the main views or functions in your API can remain focused on returning data or handling errors, rather than getting bogged down in validation logic.
|
|
320
|
+
|
|
321
|
+
#### Key Features of the Design:
|
|
322
|
+
|
|
323
|
+
1. **Modular Validation:**
|
|
324
|
+
- By using Pydantic for model validation, we ensure that data is consistently validated before it is processed or saved.
|
|
325
|
+
- Custom validation methods like `validate_name` allow for specific field-level validation rules, making the serializer adaptable to different models and requirements.
|
|
326
|
+
|
|
327
|
+
2. **Separation of Concerns:**
|
|
328
|
+
- The serializer handles data validation, deserialization, and serialization, keeping these concerns separate from the view logic.
|
|
329
|
+
- This separation allows the view functions to focus on API response generation, improving readability and maintainability.
|
|
330
|
+
|
|
331
|
+
3. **Custom Error Handling:**
|
|
332
|
+
- The design includes robust error handling, catching validation errors from Pydantic and custom validators, and other exceptions.
|
|
333
|
+
- This ensures that the API can provide meaningful error messages to the client, improving the user experience and making debugging easier.
|
|
334
|
+
|
|
335
|
+
4. **Ease of Integration:**
|
|
336
|
+
- The `CustomModelSerializer` can be easily integrated with Flask routes, as demonstrated in the example.
|
|
337
|
+
- This flexibility makes it simple to reuse the serializer across different endpoints or even different projects, as long as they use SQLAlchemy and Flask-SQLAlchemy.
|
|
338
|
+
|
|
339
|
+
#### Example Usage in Flask API:
|
|
340
|
+
|
|
341
|
+
```py
|
|
342
|
+
from flask import Flask, request, jsonify
|
|
343
|
+
from sqlalchemy_serializer_meta import Serializer
|
|
344
|
+
from your_application.models import CustomModel
|
|
345
|
+
from your_application.validators import CustomValidator
|
|
346
|
+
from pydantic import BaseModel, EmailStr, ValidationError
|
|
347
|
+
from typing import Union
|
|
348
|
+
|
|
349
|
+
class CustomPydantic(BaseModel):
|
|
350
|
+
id: str
|
|
351
|
+
name: str
|
|
352
|
+
description: EmailStr
|
|
353
|
+
|
|
354
|
+
class CustomModelSerializer(Serializer):
|
|
355
|
+
pydantic_model: BaseModel = CustomPydantic
|
|
356
|
+
|
|
357
|
+
class Meta:
|
|
358
|
+
model = CustomModel
|
|
359
|
+
fields = ['id', 'name', 'description']
|
|
360
|
+
|
|
361
|
+
def validate_name(self, value):
|
|
362
|
+
CustomValidator(SerializerError).validate(value)
|
|
363
|
+
return value
|
|
364
|
+
|
|
365
|
+
app = Flask(__name__)
|
|
366
|
+
|
|
367
|
+
@app.route('/serialize', methods=['POST'])
|
|
368
|
+
def serialize_data():
|
|
369
|
+
data = request.json
|
|
370
|
+
serializer = CustomModelSerializer(data=data)
|
|
371
|
+
|
|
372
|
+
try:
|
|
373
|
+
if serializer.is_valid():
|
|
374
|
+
serializer.save()
|
|
375
|
+
return jsonify(serializer.data), 201
|
|
376
|
+
return jsonify(serializer.errors), 400
|
|
377
|
+
except ValidationError as e:
|
|
378
|
+
return jsonify(e.errors()), 422
|
|
379
|
+
except Exception as e:
|
|
380
|
+
return jsonify({"error": str(e)}), 500
|
|
381
|
+
|
|
382
|
+
if __name__ == '__main__':
|
|
383
|
+
app.run(debug=True)
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
#### Benefits for Different Projects:
|
|
387
|
+
|
|
388
|
+
- **Consistency:** Ensures consistent data validation across various projects, reducing the likelihood of errors and inconsistencies.
|
|
389
|
+
- **Reusability:** The modular design means the same serializer class can be used across different models and endpoints, minimizing duplicate code.
|
|
390
|
+
- **Focus on Business Logic:** By handling validation and serialization within the serializer, the main view functions can focus on business logic and API response handling, making the codebase cleaner and more maintainable.
|
|
391
|
+
|
|
392
|
+
By adopting this design, you can create scalable, maintainable, and robust APIs that leverage the power of SQLAlchemy and Flask-SQLAlchemy, ensuring that your application remains flexible and adaptable to changing requirements.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
sqlalchemy_serializer_meta/__init__.py,sha256=R0tgVeUHxXSuV8ET8KTKDKFstS2h6iCaE7JvVMFpmF4,14503
|
|
2
|
+
sqlalchemy_serializer_meta/serializer.py,sha256=lr0MOZmMT5Ynf89oLUJtLKQYrId674xRbfAVdaWb2DY,5782
|
|
3
|
+
sqlalchemy_serializer_meta-0.1.0.dist-info/LICENSE,sha256=c1dx0gfbut5pHGHayRypYfy9dQjP5zGcINW3OvzNIQo,1086
|
|
4
|
+
sqlalchemy_serializer_meta-0.1.0.dist-info/METADATA,sha256=W7COlzr2Qvqlz1P6ZpW2KaXVaFYml-2UYNLyCoAD_8Q,13204
|
|
5
|
+
sqlalchemy_serializer_meta-0.1.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
6
|
+
sqlalchemy_serializer_meta-0.1.0.dist-info/top_level.txt,sha256=rlJUoEf220tqtRa1uvL41fVE9MrRuenX6nYjyTEAbYM,27
|
|
7
|
+
sqlalchemy_serializer_meta-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sqlalchemy_serializer_meta
|