0
# Models and Data Validation
1
2
Flask-RESTX provides a comprehensive schema definition system for complex data structures with JSON Schema validation, inheritance support, and automatic documentation generation. Models enable declarative API contracts with comprehensive validation and serve as the foundation for request/response documentation.
3
4
## Capabilities
5
6
### Model Base Classes
7
8
Foundation classes for all model types with common functionality.
9
10
```python { .api }
11
class ModelBase:
12
def __init__(self, name, *args, **kwargs):
13
"""
14
Base class for all models.
15
16
Parameters:
17
- name: Model name for documentation and references
18
- args, kwargs: Additional parameters for subclasses
19
"""
20
21
def inherit(self, name, *parents):
22
"""
23
Create inherited model using Swagger composition pattern.
24
25
Parameters:
26
- name: New model name
27
- parents: Parent models to inherit from
28
29
Returns:
30
New model instance inheriting from parents
31
"""
32
33
def validate(self, data, resolver=None, format_checker=None):
34
"""
35
Validate data against model schema.
36
37
Parameters:
38
- data: Data to validate
39
- resolver: JSON schema resolver for references
40
- format_checker: JSON schema format checker
41
42
Raises:
43
ValidationError: If validation fails
44
"""
45
46
def get_parent(self, name):
47
"""
48
Get parent model by name.
49
50
Parameters:
51
- name: Parent model name
52
53
Returns:
54
Parent model instance
55
56
Raises:
57
ValueError: If parent not found
58
"""
59
60
@property
61
def ancestors(self):
62
"""Set of all ancestor model names."""
63
64
@property
65
def __schema__(self):
66
"""JSON Schema representation of the model."""
67
68
@property
69
def name(self):
70
"""Model name."""
71
```
72
73
### Model Classes
74
75
Concrete model implementations for different use cases.
76
77
```python { .api }
78
class Model(dict):
79
def __init__(self, name, fields_dict=None, mask=None, strict=False, **kwargs):
80
"""
81
Dictionary-based model with field definitions.
82
83
Parameters:
84
- name: Model name
85
- fields_dict: Dictionary of field name to field definition
86
- mask: Optional field mask for selective serialization
87
- strict: Whether to enforce strict validation
88
- kwargs: Additional model parameters
89
"""
90
91
def __getitem__(self, key):
92
"""Get field definition by name."""
93
94
def __setitem__(self, key, value):
95
"""Set field definition by name."""
96
97
@classmethod
98
def clone(cls, name, *parents):
99
"""
100
Create a clone with additional fields.
101
102
Parameters:
103
- name: New model name
104
- parents: Parent models or field dictionaries
105
106
Returns:
107
New model instance
108
"""
109
110
@classmethod
111
def inherit(cls, name, *parents):
112
"""
113
Create inherited model using composition pattern.
114
115
Parameters:
116
- name: New model name
117
- parents: Parent models to inherit from
118
119
Returns:
120
New model instance
121
"""
122
123
def extend(self, name, fields_dict):
124
"""
125
Extend model with additional fields (deprecated).
126
127
Parameters:
128
- name: New model name
129
- fields_dict: Dictionary of additional fields
130
131
Returns:
132
Extended model instance
133
"""
134
135
def validate(self, data, resolver=None, format_checker=None):
136
"""
137
Validate data against model schema.
138
139
Parameters:
140
- data: Data to validate
141
- resolver: JSON schema resolver
142
- format_checker: JSON schema format checker
143
144
Raises:
145
ValidationError: If validation fails
146
"""
147
148
@property
149
def __schema__(self):
150
"""JSON Schema representation."""
151
152
@property
153
def resolved(self):
154
"""Resolved field definitions."""
155
156
class OrderedModel(Model):
157
def __init__(self, name, fields_dict=None, mask=None, strict=False, **kwargs):
158
"""
159
Ordered dictionary-based model preserving field order.
160
161
Parameters:
162
- name: Model name
163
- fields_dict: Ordered dictionary of field definitions
164
- mask: Optional field mask
165
- strict: Whether to enforce strict validation
166
- kwargs: Additional model parameters
167
"""
168
169
class SchemaModel(ModelBase):
170
def __init__(self, name, schema=None, **kwargs):
171
"""
172
JSON Schema-based model.
173
174
Parameters:
175
- name: Model name
176
- schema: JSON Schema definition
177
- kwargs: Additional model parameters
178
"""
179
180
@property
181
def _schema(self):
182
"""Internal schema representation."""
183
184
@property
185
def __schema__(self):
186
"""JSON Schema representation."""
187
```
188
189
### Mask System
190
191
Field filtering and partial response functionality.
192
193
```python { .api }
194
class Mask:
195
def __init__(self, mask=None, skip=False, **kwargs):
196
"""
197
Field mask for selective serialization.
198
199
Parameters:
200
- mask: Mask definition (string, dict, or Mask instance)
201
- skip: Whether to skip missing fields
202
- kwargs: Additional mask parameters
203
"""
204
205
def parse(self, mask):
206
"""
207
Parse mask string into field selection structure.
208
209
Parameters:
210
- mask: Mask string in format '{field1,field2{nested1,nested2}}'
211
"""
212
213
def apply(self, data, skip=False):
214
"""
215
Apply mask to data structure.
216
217
Parameters:
218
- data: Data to filter
219
- skip: Whether to skip missing fields
220
221
Returns:
222
Filtered data structure
223
"""
224
225
def apply(data, mask, skip=False):
226
"""
227
Apply mask to data structure.
228
229
Parameters:
230
- data: Data to filter
231
- mask: Mask instance or definition
232
- skip: Whether to skip missing fields
233
234
Returns:
235
Filtered data according to mask
236
"""
237
238
def format_error(error):
239
"""
240
Format validation error for display.
241
242
Parameters:
243
- error: Validation error instance
244
245
Returns:
246
Formatted error message
247
"""
248
```
249
250
### Mask Exceptions
251
252
Exception types for mask parsing and application errors.
253
254
```python { .api }
255
class MaskError(RestError):
256
def __init__(self, msg):
257
"""
258
Base exception for mask operations.
259
260
Parameters:
261
- msg: Error message
262
"""
263
264
class ParseError(MaskError):
265
def __init__(self, msg):
266
"""
267
Exception raised when mask parsing fails.
268
269
Parameters:
270
- msg: Parse error details
271
"""
272
```
273
274
## Usage Examples
275
276
### Basic Model Definition
277
278
```python
279
from flask_restx import Api, fields
280
281
api = Api()
282
283
# Define a simple model
284
user_model = api.model('User', {
285
'id': fields.Integer(required=True, description='User ID'),
286
'name': fields.String(required=True, description='Full name'),
287
'email': fields.String(required=True, description='Email address'),
288
'active': fields.Boolean(default=True, description='Account status'),
289
'created_at': fields.DateTime(description='Account creation time')
290
})
291
```
292
293
### Model with Nested Objects
294
295
```python
296
# Address model
297
address_model = api.model('Address', {
298
'street': fields.String(required=True),
299
'city': fields.String(required=True),
300
'state': fields.String,
301
'country': fields.String(required=True),
302
'postal_code': fields.String
303
})
304
305
# User model with nested address
306
user_with_address = api.model('UserWithAddress', {
307
'id': fields.Integer(required=True),
308
'name': fields.String(required=True),
309
'email': fields.String(required=True),
310
'address': fields.Nested(address_model),
311
'billing_address': fields.Nested(address_model, allow_null=True)
312
})
313
```
314
315
### Model Inheritance
316
317
```python
318
# Base person model
319
person_model = api.model('Person', {
320
'name': fields.String(required=True),
321
'email': fields.String(required=True),
322
'phone': fields.String
323
})
324
325
# Employee model inheriting from person
326
employee_model = api.inherit('Employee', person_model, {
327
'employee_id': fields.String(required=True),
328
'department': fields.String(required=True),
329
'hire_date': fields.Date,
330
'salary': fields.Float(min=0)
331
})
332
333
# Manager model inheriting from employee
334
manager_model = api.inherit('Manager', employee_model, {
335
'team_size': fields.Integer(min=0),
336
'reports': fields.List(fields.Nested(employee_model))
337
})
338
```
339
340
### Model Extension and Cloning
341
342
```python
343
# Base product model
344
product_model = api.model('Product', {
345
'id': fields.Integer(required=True),
346
'name': fields.String(required=True),
347
'price': fields.Float(required=True, min=0)
348
})
349
350
# Extend with additional fields
351
detailed_product = api.clone('DetailedProduct', product_model, {
352
'description': fields.String,
353
'category': fields.String,
354
'tags': fields.List(fields.String),
355
'in_stock': fields.Boolean(default=True)
356
})
357
358
# Alternative using extend method
359
extended_product = product_model.extend('ExtendedProduct', {
360
'manufacturer': fields.String,
361
'warranty_months': fields.Integer(min=0)
362
})
363
```
364
365
### Schema-based Models
366
367
```python
368
# JSON Schema definition
369
user_schema = {
370
"type": "object",
371
"properties": {
372
"name": {"type": "string", "minLength": 1},
373
"age": {"type": "integer", "minimum": 0, "maximum": 150},
374
"email": {"type": "string", "format": "email"}
375
},
376
"required": ["name", "email"]
377
}
378
379
# Create schema model
380
schema_user_model = api.schema_model('SchemaUser', user_schema)
381
```
382
383
### Model Validation
384
385
```python
386
from flask_restx import Resource, ValidationError
387
388
@api.route('/users')
389
class UserList(Resource):
390
@api.expect(user_model, validate=True)
391
def post(self):
392
# Request payload automatically validated against model
393
data = api.payload
394
395
try:
396
# Manual validation if needed
397
user_model.validate(data)
398
except ValidationError as e:
399
return {'error': str(e)}, 400
400
401
# Process valid data
402
return {'message': 'User created', 'data': data}, 201
403
```
404
405
### Field Masking
406
407
```python
408
from flask_restx import Mask
409
410
# Create mask for selective fields
411
mask = Mask('name,email,address{city,country}')
412
413
# Apply mask to model data
414
user_data = {
415
'id': 1,
416
'name': 'John Doe',
417
'email': 'john@example.com',
418
'phone': '555-1234',
419
'address': {
420
'street': '123 Main St',
421
'city': 'Anytown',
422
'state': 'CA',
423
'country': 'USA',
424
'postal_code': '12345'
425
}
426
}
427
428
# Result includes only masked fields
429
filtered_data = mask.apply(user_data)
430
# {'name': 'John Doe', 'email': 'john@example.com', 'address': {'city': 'Anytown', 'country': 'USA'}}
431
```
432
433
### Model Documentation Integration
434
435
```python
436
@api.route('/users/<int:user_id>')
437
class User(Resource):
438
@api.marshal_with(user_model)
439
@api.doc('get_user')
440
def get(self, user_id):
441
"""Fetch a user by ID"""
442
return find_user(user_id)
443
444
@api.expect(user_model, validate=True)
445
@api.marshal_with(user_model)
446
@api.doc('update_user')
447
@api.response(200, 'User updated', user_model)
448
@api.response(400, 'Validation error')
449
@api.response(404, 'User not found')
450
def put(self, user_id):
451
"""Update a user"""
452
data = api.payload
453
updated_user = update_user(user_id, data)
454
return updated_user
455
```
456
457
### Ordered Models
458
459
```python
460
from collections import OrderedDict
461
462
# Preserve field order in responses
463
ordered_user = api.model('OrderedUser', OrderedDict([
464
('id', fields.Integer(required=True)),
465
('name', fields.String(required=True)),
466
('email', fields.String(required=True)),
467
('created_at', fields.DateTime),
468
('updated_at', fields.DateTime)
469
]))
470
471
# Or use OrderedModel directly
472
ordered_product = OrderedModel('Product', OrderedDict([
473
('id', fields.Integer),
474
('sku', fields.String),
475
('name', fields.String),
476
('price', fields.Float),
477
('availability', fields.Boolean)
478
]))
479
```
480
481
### Advanced Validation Patterns
482
483
```python
484
# Model with complex validation rules
485
order_model = api.model('Order', {
486
'id': fields.Integer(required=True),
487
'customer_id': fields.Integer(required=True, min=1),
488
'items': fields.List(fields.Nested(api.model('OrderItem', {
489
'product_id': fields.Integer(required=True),
490
'quantity': fields.Integer(required=True, min=1),
491
'unit_price': fields.Float(required=True, min=0)
492
})), required=True, min_items=1),
493
'total_amount': fields.Float(required=True, min=0),
494
'order_date': fields.DateTime(required=True),
495
'status': fields.String(required=True, enum=['pending', 'confirmed', 'shipped', 'delivered'])
496
})
497
498
# Custom validation logic
499
@api.route('/orders')
500
class OrderList(Resource):
501
@api.expect(order_model, validate=True)
502
def post(self):
503
data = api.payload
504
505
# Additional business logic validation
506
calculated_total = sum(item['quantity'] * item['unit_price'] for item in data['items'])
507
if abs(calculated_total - data['total_amount']) > 0.01:
508
return {'error': 'Total amount does not match item calculations'}, 400
509
510
return {'message': 'Order created'}, 201
511
```