0
# REST API System
1
2
Complete REST API framework with automatic CRUD operations, OpenAPI documentation, schema generation, and JSON serialization. The API system provides a comprehensive solution for building REST APIs with minimal code while maintaining full customization capabilities.
3
4
## Capabilities
5
6
### ModelRestApi Class
7
8
Full-featured REST API class providing complete CRUD operations with automatic schema generation, validation, filtering, pagination, and OpenAPI documentation.
9
10
```python { .api }
11
from flask_appbuilder.api import ModelRestApi
12
from flask_appbuilder.models.sqla.interface import SQLAInterface
13
14
class ModelRestApi(BaseModelApi):
15
def __init__(self):
16
"""Initialize REST API with automatic endpoint generation."""
17
18
def info(self, **kwargs):
19
"""
20
Get API metadata and schema information.
21
22
GET /_info
23
24
Returns:
25
Dict with model schema, relationships, permissions, filters
26
"""
27
28
def get_list(self, **kwargs):
29
"""
30
Get paginated list of model instances.
31
32
GET /
33
34
Query parameters:
35
- q: Rison encoded query filters
36
- page_size: Number of items per page (max: max_page_size)
37
- page: Page number (0-based)
38
- order_column: Column to order by
39
- order_direction: asc or desc
40
41
Returns:
42
Dict with count, ids, result array
43
"""
44
45
def get(self, pk, **kwargs):
46
"""
47
Get single model instance by primary key.
48
49
GET /<pk>
50
51
Parameters:
52
- pk: Primary key value
53
54
Returns:
55
Dict with model data or 404 error
56
"""
57
58
def post(self):
59
"""
60
Create new model instance.
61
62
POST /
63
64
Request body: JSON with model data
65
66
Returns:
67
Dict with created model data and 201 status
68
"""
69
70
def put(self, pk):
71
"""
72
Update existing model instance.
73
74
PUT /<pk>
75
76
Parameters:
77
- pk: Primary key value
78
Request body: JSON with updated model data
79
80
Returns:
81
Dict with updated model data or 404/422 error
82
"""
83
84
def delete(self, pk):
85
"""
86
Delete model instance by primary key.
87
88
DELETE /<pk>
89
90
Parameters:
91
- pk: Primary key value
92
93
Returns:
94
Dict with success message or 404 error
95
"""
96
97
# Lifecycle hooks
98
def pre_add(self, item):
99
"""
100
Hook called before adding new item.
101
102
Parameters:
103
- item: Model instance to be added
104
"""
105
106
def post_add(self, item):
107
"""
108
Hook called after successfully adding item.
109
110
Parameters:
111
- item: Added model instance
112
"""
113
114
def pre_update(self, item):
115
"""
116
Hook called before updating item.
117
118
Parameters:
119
- item: Model instance to be updated
120
"""
121
122
def post_update(self, item):
123
"""
124
Hook called after successfully updating item.
125
126
Parameters:
127
- item: Updated model instance
128
"""
129
130
def pre_delete(self, item):
131
"""
132
Hook called before deleting item.
133
134
Parameters:
135
- item: Model instance to be deleted
136
"""
137
138
def post_delete(self, item):
139
"""
140
Hook called after successfully deleting item.
141
142
Parameters:
143
- item: Deleted model instance
144
"""
145
146
def pre_get(self, data):
147
"""
148
Hook called before returning single item data.
149
150
Parameters:
151
- data: Serialized item data dict
152
153
Returns:
154
Modified data dict
155
"""
156
157
def pre_get_list(self, data):
158
"""
159
Hook called before returning list data.
160
161
Parameters:
162
- data: Dict with count, ids, result
163
164
Returns:
165
Modified data dict
166
"""
167
168
# API configuration properties
169
datamodel = None # SQLAlchemy interface (required)
170
list_title = "List" # API list title
171
show_title = "Show" # API show title
172
add_title = "Add" # API add title
173
edit_title = "Edit" # API edit title
174
175
# Column configuration
176
list_columns = [] # Columns returned in list view
177
show_columns = [] # Columns returned in show view
178
add_columns = [] # Columns accepted for creation
179
edit_columns = [] # Columns accepted for updates
180
list_exclude_columns = [] # Columns excluded from list
181
show_exclude_columns = [] # Columns excluded from show
182
add_exclude_columns = [] # Columns excluded from add
183
edit_exclude_columns = [] # Columns excluded from edit
184
185
# SQL optimization
186
list_select_columns = [] # Specific SELECT columns for list
187
show_select_columns = [] # Specific SELECT columns for show
188
list_outer_default_load = [] # Outer join loading for list
189
show_outer_default_load = [] # Outer join loading for show
190
191
# Pagination and ordering
192
page_size = 20 # Default page size
193
max_page_size = 100 # Maximum allowed page size
194
order_columns = [] # Columns available for ordering
195
196
# Validation and schema
197
description_columns = {} # Column descriptions
198
validators_columns = {} # Marshmallow validators dict
199
add_query_rel_fields = {} # Related field queries for add
200
edit_query_rel_fields = {} # Related field queries for edit
201
order_rel_fields = {} # Related field ordering
202
203
# Schema customization
204
list_model_schema = None # Custom list schema class
205
add_model_schema = None # Custom add schema class
206
edit_model_schema = None # Custom edit schema class
207
show_model_schema = None # Custom show schema class
208
model2schemaconverter = None # Custom schema converter class
209
210
# Usage example
211
class PersonApi(ModelRestApi):
212
datamodel = SQLAInterface(Person)
213
list_columns = ['id', 'name', 'email', 'created_on']
214
show_columns = ['id', 'name', 'email', 'phone', 'created_on', 'updated_on']
215
add_columns = ['name', 'email', 'phone']
216
edit_columns = ['name', 'email', 'phone']
217
order_columns = ['name', 'email', 'created_on']
218
description_columns = {
219
'name': 'Person full name',
220
'email': 'Contact email address'
221
}
222
223
# Register API
224
appbuilder.add_api(PersonApi)
225
```
226
227
### BaseApi Class
228
229
Foundation class for building custom API endpoints with security, routing, and response handling capabilities.
230
231
```python { .api }
232
from flask_appbuilder.api import BaseApi, expose, safe
233
234
class BaseApi:
235
def __init__(self):
236
"""Initialize API with base configuration."""
237
238
def create_blueprint(self, appbuilder, endpoint=None, static_folder=None):
239
"""
240
Create Flask blueprint for API.
241
242
Parameters:
243
- appbuilder: AppBuilder instance
244
- endpoint: Custom endpoint name
245
- static_folder: Static files folder
246
247
Returns:
248
Flask Blueprint instance
249
"""
250
251
def add_api_spec(self, api_spec):
252
"""
253
Add OpenAPI specification to API documentation.
254
255
Parameters:
256
- api_spec: APISpec instance
257
"""
258
259
def add_apispec_components(self, api_spec):
260
"""
261
Add custom OpenAPI components.
262
263
Parameters:
264
- api_spec: APISpec instance
265
"""
266
267
def get_method_permission(self, method_name):
268
"""
269
Get permission name for API method.
270
271
Parameters:
272
- method_name: API method name
273
274
Returns:
275
Permission name string
276
"""
277
278
# Standard HTTP responses
279
def response(self, code, **kwargs):
280
"""
281
Generic JSON response with status code.
282
283
Parameters:
284
- code: HTTP status code
285
- **kwargs: Response data
286
287
Returns:
288
Flask Response with JSON content
289
"""
290
291
def response_400(self, message="Bad request"):
292
"""Bad Request (400) response."""
293
294
def response_401(self):
295
"""Unauthorized (401) response."""
296
297
def response_403(self):
298
"""Forbidden (403) response."""
299
300
def response_404(self):
301
"""Not Found (404) response."""
302
303
def response_422(self, message="Unprocessable Entity"):
304
"""Unprocessable Entity (422) response."""
305
306
def response_500(self, message="Internal server error"):
307
"""Internal Server Error (500) response."""
308
309
# API configuration properties
310
endpoint = "" # API endpoint name
311
version = "v1" # API version
312
route_base = "" # API route base (auto-generated)
313
resource_name = "" # Resource name for OpenAPI
314
base_permissions = [] # Base permissions list
315
class_permission_name = "" # Class permission name override
316
method_permission_name = {} # Method permission name overrides
317
allow_browser_login = False # Allow Flask-Login session auth
318
csrf_exempt = [] # CSRF exempt methods list
319
apispec_parameter_schemas = {} # Custom parameter schemas
320
openapi_spec_component_schemas = {} # OpenAPI component schemas
321
responses = {} # Standard HTTP responses dict
322
exclude_route_methods = set() # Methods to exclude from routes
323
include_route_methods = set() # Methods to include in routes
324
openapi_spec_methods = {} # OpenAPI spec method overrides
325
openapi_spec_tag = {} # OpenAPI tag configuration
326
limits = [] # Rate limiting configuration
327
328
# Usage example
329
class CustomApi(BaseApi):
330
route_base = "/custom"
331
332
@expose('/hello/')
333
@safe
334
def hello(self):
335
return self.response(200, message="Hello World")
336
337
@expose('/protected/')
338
@protect()
339
def protected_endpoint(self):
340
return self.response(200, user=g.user.username)
341
342
# Register custom API
343
appbuilder.add_api(CustomApi)
344
```
345
346
### API Decorators
347
348
Decorators for API endpoint exposure, parameter parsing, error handling, and response processing.
349
350
```python { .api }
351
from flask_appbuilder.api import expose, rison, safe, merge_response_func
352
353
@expose("/", methods=["GET", "POST"])
354
def expose(url="/", methods=("GET",)):
355
"""
356
Expose API method as endpoint.
357
358
Parameters:
359
- url: URL pattern for endpoint
360
- methods: HTTP methods list
361
362
Usage:
363
@expose('/users/', methods=['GET', 'POST'])
364
@protect()
365
def users_endpoint(self):
366
if request.method == 'GET':
367
return self.get_users()
368
return self.create_user()
369
"""
370
371
@rison(schema=UserSchema())
372
def rison(schema=None):
373
"""
374
Parse URI Rison arguments using Marshmallow schema.
375
376
Parameters:
377
- schema: Marshmallow schema for validation
378
379
Usage:
380
from marshmallow import Schema, fields
381
382
class FilterSchema(Schema):
383
name = fields.String()
384
active = fields.Boolean()
385
386
@expose('/filtered/')
387
@rison(FilterSchema())
388
@protect()
389
def filtered_data(self, **kwargs):
390
filters = kwargs.get('rison', {})
391
return self.response(200, filters=filters)
392
"""
393
394
@safe
395
def safe(f):
396
"""
397
Catch uncaught exceptions and return JSON error response.
398
399
Usage:
400
@expose('/may-fail/')
401
@safe
402
def risky_endpoint(self):
403
# This will catch any unhandled exceptions
404
# and return proper JSON error response
405
result = risky_operation()
406
return self.response(200, result=result)
407
"""
408
409
@merge_response_func(custom_processor, "data")
410
def merge_response_func(func, key):
411
"""
412
Merge response function results into endpoint response.
413
414
Parameters:
415
- func: Function to call with response data
416
- key: Key name for merged data
417
418
Usage:
419
def add_metadata(data):
420
return {"timestamp": datetime.utcnow().isoformat()}
421
422
@expose('/with-meta/')
423
@merge_response_func(add_metadata, "meta")
424
@protect()
425
def endpoint_with_meta(self):
426
return self.response(200, result="success")
427
# Response includes merged metadata
428
"""
429
```
430
431
### BaseModelApi Class
432
433
Foundation for model-based APIs providing data model integration, filtering, and search capabilities without full CRUD operations.
434
435
```python { .api }
436
from flask_appbuilder.api import BaseModelApi
437
438
class BaseModelApi(BaseApi):
439
"""Base class for model APIs with search and filter capabilities."""
440
441
# Model integration properties
442
datamodel = None # SQLAlchemy interface (required)
443
search_columns = [] # Searchable columns
444
search_filters = {} # Custom search filters
445
search_exclude_columns = [] # Columns excluded from search
446
label_columns = {} # Column label overrides
447
base_filters = [] # Base query filters
448
base_order = [] # Default query ordering
449
450
# Usage example - Read-only API
451
class ReadOnlyPersonApi(BaseModelApi):
452
datamodel = SQLAInterface(Person)
453
search_columns = ['name', 'email']
454
base_filters = [['active', FilterEqual, True]]
455
456
@expose('/search/')
457
@protect()
458
def search_persons(self):
459
# Custom search implementation using datamodel
460
query = self.datamodel.get_query()
461
# Apply search logic
462
return self.response(200, results=query.all())
463
```
464
465
### OpenAPI Integration
466
467
Automatic OpenAPI (Swagger) documentation generation for all API endpoints with customizable schemas and documentation.
468
469
```python { .api }
470
# OpenAPI is automatically enabled for all ModelRestApi endpoints
471
# Access documentation at: /api/v1/openapi.json
472
# Swagger UI available at: /swaggerview/
473
474
# Custom OpenAPI schemas
475
from marshmallow import Schema, fields
476
477
class PersonResponseSchema(Schema):
478
id = fields.Integer()
479
name = fields.String(description="Person's full name")
480
email = fields.Email(description="Contact email address")
481
created_on = fields.DateTime()
482
483
class PersonApi(ModelRestApi):
484
datamodel = SQLAInterface(Person)
485
486
# Custom response schema
487
show_model_schema = PersonResponseSchema
488
489
# Custom OpenAPI tags
490
openapi_spec_tag = {
491
"name": "Persons",
492
"description": "Person management endpoints"
493
}
494
495
# Custom parameter schemas
496
apispec_parameter_schemas = {
497
"person_id": {
498
"in": "path",
499
"schema": {"type": "integer", "minimum": 1},
500
"description": "Person ID"
501
}
502
}
503
504
# Configuration options
505
FAB_API_SWAGGER_UI = True # Enable Swagger UI
506
FAB_API_SWAGGER_TEMPLATE = "appbuilder/swagger/swagger.html"
507
FAB_API_MAX_PAGE_SIZE = 100 # Maximum API page size
508
```
509
510
### API Error Handling
511
512
Comprehensive error handling with structured JSON responses and proper HTTP status codes.
513
514
```python { .api }
515
# Standard error response format
516
{
517
"message": "Error description",
518
"severity": "error", # or "warning", "info"
519
"details": {} # Additional error details
520
}
521
522
# Common API errors and responses:
523
524
# 400 Bad Request - Invalid input
525
{
526
"message": "Invalid request data",
527
"severity": "error",
528
"details": {"field": "validation error message"}
529
}
530
531
# 401 Unauthorized - Authentication required
532
{
533
"message": "Authentication required",
534
"severity": "error"
535
}
536
537
# 403 Forbidden - Insufficient permissions
538
{
539
"message": "Access denied",
540
"severity": "error"
541
}
542
543
# 404 Not Found - Resource not found
544
{
545
"message": "Record not found",
546
"severity": "error"
547
}
548
549
# 422 Unprocessable Entity - Validation errors
550
{
551
"message": "Validation failed",
552
"severity": "error",
553
"details": {
554
"email": ["Invalid email address"],
555
"name": ["Name is required"]
556
}
557
}
558
559
# Custom error handling in APIs
560
class PersonApi(ModelRestApi):
561
def pre_add(self, item):
562
if not item.email:
563
raise ValidationError("Email is required")
564
565
def post_add(self, item):
566
try:
567
send_welcome_email(item.email)
568
except Exception as e:
569
# Log error but don't fail the request
570
log.error(f"Failed to send welcome email: {e}")
571
```