0
# Error Handling and Utilities
1
2
Flask-RESTX provides comprehensive error handling capabilities with HTTP status code management, custom exception types, and utilities for API responses. It also includes CORS support and Swagger documentation generation for complete API development support.
3
4
## Capabilities
5
6
### Error Handling Functions
7
8
Core functions for handling API errors and exceptions.
9
10
```python { .api }
11
def abort(code=500, message=None, **kwargs):
12
"""
13
Abort the current request with HTTP status code and message.
14
15
Parameters:
16
- code: HTTP status code (default: 500)
17
- message: Optional error message
18
- kwargs: Additional data to include in error response
19
20
Raises:
21
HTTPException: Flask HTTP exception with specified status code
22
"""
23
```
24
25
### Exception Classes
26
27
Custom exception types for different error scenarios.
28
29
```python { .api }
30
class RestError(Exception):
31
def __init__(self, msg):
32
"""
33
Base class for all Flask-RESTX errors.
34
35
Parameters:
36
- msg: Error message
37
"""
38
39
def __str__(self):
40
"""Return error message as string."""
41
42
class ValidationError(RestError):
43
def __init__(self, msg):
44
"""
45
Exception for input validation errors.
46
47
Parameters:
48
- msg: Validation error details
49
"""
50
51
class SpecsError(RestError):
52
def __init__(self, msg):
53
"""
54
Exception for API specification errors.
55
56
Parameters:
57
- msg: Specification error details
58
"""
59
```
60
61
### CORS Support
62
63
Cross-Origin Resource Sharing functionality for API access control.
64
65
```python { .api }
66
def crossdomain(
67
origin=None,
68
methods=None,
69
headers=None,
70
expose_headers=None,
71
max_age=21600,
72
attach_to_all=True,
73
automatic_options=True,
74
credentials=False
75
):
76
"""
77
Decorator for enabling CORS on API endpoints.
78
79
Parameters:
80
- origin: Allowed origins ('*' for all, list for specific origins)
81
- methods: Allowed HTTP methods (list of strings)
82
- headers: Allowed request headers (list of strings)
83
- expose_headers: Headers to expose to client (list of strings)
84
- max_age: Preflight request cache duration in seconds
85
- attach_to_all: Whether to attach CORS headers to all responses
86
- automatic_options: Whether to automatically handle OPTIONS requests
87
- credentials: Whether to allow credentials in CORS requests
88
89
Returns:
90
Decorator function for CORS-enabled endpoints
91
"""
92
```
93
94
### Swagger Documentation
95
96
Automatic API documentation generation with OpenAPI/Swagger specification.
97
98
```python { .api }
99
class Swagger:
100
def __init__(self, api):
101
"""
102
Swagger documentation generator.
103
104
Parameters:
105
- api: Api instance to generate documentation for
106
"""
107
108
def as_dict(self):
109
"""
110
Generate Swagger specification as dictionary.
111
112
Returns:
113
dict: Complete Swagger/OpenAPI specification
114
"""
115
116
@property
117
def paths(self):
118
"""Dictionary of API paths and operations."""
119
120
@property
121
def definitions(self):
122
"""Dictionary of model definitions."""
123
124
@property
125
def tags(self):
126
"""List of API tags."""
127
```
128
129
### HTTP Status Constants
130
131
Comprehensive HTTP status code constants for consistent error handling.
132
133
```python { .api }
134
class HTTPStatus:
135
"""
136
HTTP status code constants with names, phrases, and descriptions.
137
Complete enumeration of all standard HTTP status codes.
138
"""
139
140
# Informational 1xx
141
CONTINUE = 100
142
SWITCHING_PROTOCOLS = 101
143
PROCESSING = 102
144
145
# Success 2xx
146
OK = 200
147
CREATED = 201
148
ACCEPTED = 202
149
NON_AUTHORITATIVE_INFORMATION = 203
150
NO_CONTENT = 204
151
RESET_CONTENT = 205
152
PARTIAL_CONTENT = 206
153
MULTI_STATUS = 207
154
ALREADY_REPORTED = 208
155
IM_USED = 226
156
157
# Redirection 3xx
158
MULTIPLE_CHOICES = 300
159
MOVED_PERMANENTLY = 301
160
FOUND = 302
161
SEE_OTHER = 303
162
NOT_MODIFIED = 304
163
USE_PROXY = 305
164
TEMPORARY_REDIRECT = 307
165
PERMANENT_REDIRECT = 308
166
167
# Client Error 4xx
168
BAD_REQUEST = 400
169
UNAUTHORIZED = 401
170
PAYMENT_REQUIRED = 402
171
FORBIDDEN = 403
172
NOT_FOUND = 404
173
METHOD_NOT_ALLOWED = 405
174
NOT_ACCEPTABLE = 406
175
PROXY_AUTHENTICATION_REQUIRED = 407
176
REQUEST_TIMEOUT = 408
177
CONFLICT = 409
178
GONE = 410
179
LENGTH_REQUIRED = 411
180
PRECONDITION_FAILED = 412
181
REQUEST_ENTITY_TOO_LARGE = 413
182
REQUEST_URI_TOO_LONG = 414
183
UNSUPPORTED_MEDIA_TYPE = 415
184
REQUESTED_RANGE_NOT_SATISFIABLE = 416
185
EXPECTATION_FAILED = 417
186
UNPROCESSABLE_ENTITY = 422
187
LOCKED = 423
188
FAILED_DEPENDENCY = 424
189
UPGRADE_REQUIRED = 426
190
PRECONDITION_REQUIRED = 428
191
TOO_MANY_REQUESTS = 429
192
REQUEST_HEADER_FIELDS_TOO_LARGE = 431
193
194
# Server Error 5xx
195
INTERNAL_SERVER_ERROR = 500
196
NOT_IMPLEMENTED = 501
197
BAD_GATEWAY = 502
198
SERVICE_UNAVAILABLE = 503
199
GATEWAY_TIMEOUT = 504
200
HTTP_VERSION_NOT_SUPPORTED = 505
201
VARIANT_ALSO_NEGOTIATES = 506
202
INSUFFICIENT_STORAGE = 507
203
LOOP_DETECTED = 508
204
NOT_EXTENDED = 510
205
NETWORK_AUTHENTICATION_REQUIRED = 511
206
207
# Utility functions for HTTP status handling
208
def is_informational(status_code):
209
"""Check if status code is informational (1xx)."""
210
211
def is_success(status_code):
212
"""Check if status code indicates success (2xx)."""
213
214
def is_redirect(status_code):
215
"""Check if status code indicates redirection (3xx)."""
216
217
def is_client_error(status_code):
218
"""Check if status code indicates client error (4xx)."""
219
220
def is_server_error(status_code):
221
"""Check if status code indicates server error (5xx)."""
222
```
223
224
### Response Utilities
225
226
Functions for creating and formatting API responses.
227
228
```python { .api }
229
def output_json(data, code, headers=None):
230
"""
231
Create JSON response with proper headers.
232
233
Parameters:
234
- data: Response data to serialize as JSON
235
- code: HTTP status code
236
- headers: Optional additional headers
237
238
Returns:
239
Flask Response object with JSON content
240
"""
241
242
def make_response(data, code=200, headers=None):
243
"""
244
Create Flask response from data.
245
246
Parameters:
247
- data: Response data
248
- code: HTTP status code
249
- headers: Optional response headers
250
251
Returns:
252
Flask Response object
253
"""
254
```
255
256
## Usage Examples
257
258
### Basic Error Handling
259
260
```python
261
from flask_restx import Resource, abort, HTTPStatus
262
263
class UserResource(Resource):
264
def get(self, user_id):
265
user = find_user(user_id)
266
if not user:
267
abort(HTTPStatus.NOT_FOUND, message=f'User {user_id} not found')
268
269
if not user.is_active:
270
abort(HTTPStatus.FORBIDDEN, message='User account is disabled')
271
272
return user
273
274
def delete(self, user_id):
275
if not is_admin():
276
abort(HTTPStatus.UNAUTHORIZED, message='Admin access required')
277
278
try:
279
delete_user(user_id)
280
return '', HTTPStatus.NO_CONTENT
281
except UserNotFound:
282
abort(HTTPStatus.NOT_FOUND, message='User not found')
283
except DatabaseError:
284
abort(HTTPStatus.INTERNAL_SERVER_ERROR, message='Database error occurred')
285
```
286
287
### Custom Error Responses
288
289
```python
290
from flask_restx import Api
291
292
api = Api()
293
294
@api.errorhandler(ValidationError)
295
def handle_validation_error(error):
296
"""Handle validation errors with custom response format."""
297
return {
298
'error': 'Validation failed',
299
'message': str(error),
300
'code': 'VALIDATION_ERROR'
301
}, HTTPStatus.BAD_REQUEST
302
303
@api.errorhandler(RestError)
304
def handle_rest_error(error):
305
"""Handle general REST API errors."""
306
return {
307
'error': 'API Error',
308
'message': str(error),
309
'code': 'REST_ERROR'
310
}, HTTPStatus.INTERNAL_SERVER_ERROR
311
312
@api.errorhandler(404)
313
def handle_not_found(error):
314
"""Handle 404 errors with custom format."""
315
return {
316
'error': 'Resource not found',
317
'message': 'The requested resource could not be found',
318
'code': 'NOT_FOUND'
319
}, HTTPStatus.NOT_FOUND
320
```
321
322
### Detailed Error Information
323
324
```python
325
class OrderResource(Resource):
326
def post(self):
327
try:
328
data = api.payload
329
order = create_order(data)
330
return order, HTTPStatus.CREATED
331
except InsufficientStock as e:
332
abort(
333
HTTPStatus.CONFLICT,
334
message='Insufficient stock',
335
details={
336
'requested_quantity': e.requested,
337
'available_quantity': e.available,
338
'product_id': e.product_id
339
},
340
code='INSUFFICIENT_STOCK'
341
)
342
except PaymentFailed as e:
343
abort(
344
HTTPStatus.PAYMENT_REQUIRED,
345
message='Payment processing failed',
346
details={
347
'payment_method': e.method,
348
'failure_reason': e.reason
349
},
350
code='PAYMENT_FAILED'
351
)
352
```
353
354
### CORS Configuration
355
356
```python
357
from flask_restx import cors
358
359
# Enable CORS for all origins
360
@api.route('/public-data')
361
class PublicData(Resource):
362
@cors.crossdomain(origin='*')
363
def get(self):
364
return {'data': 'public information'}
365
366
# Restrict CORS to specific origins
367
@api.route('/restricted-data')
368
class RestrictedData(Resource):
369
@cors.crossdomain(
370
origin=['https://example.com', 'https://app.example.com'],
371
methods=['GET', 'POST'],
372
headers=['Authorization', 'Content-Type']
373
)
374
def get(self):
375
return {'data': 'restricted information'}
376
377
@cors.crossdomain(
378
origin=['https://example.com'],
379
methods=['POST'],
380
headers=['Authorization', 'Content-Type'],
381
max_age=3600
382
)
383
def post(self):
384
return {'message': 'Data created'}, HTTPStatus.CREATED
385
```
386
387
### API-wide CORS Configuration
388
389
```python
390
from flask import Flask
391
from flask_restx import Api
392
from flask_cors import CORS
393
394
app = Flask(__name__)
395
396
# Enable CORS for entire Flask app
397
CORS(app, resources={
398
r"/api/*": {
399
"origins": ["https://example.com", "https://app.example.com"],
400
"methods": ["GET", "POST", "PUT", "DELETE"],
401
"allow_headers": ["Authorization", "Content-Type"]
402
}
403
})
404
405
api = Api(app, prefix='/api/v1')
406
```
407
408
### Global Error Handlers
409
410
```python
411
from werkzeug.exceptions import HTTPException
412
import logging
413
414
api = Api()
415
416
@api.errorhandler(Exception)
417
def handle_unexpected_error(error):
418
"""Handle all unexpected errors."""
419
logging.error(f"Unexpected error: {str(error)}", exc_info=True)
420
return {
421
'error': 'Internal server error',
422
'message': 'An unexpected error occurred'
423
}, HTTPStatus.INTERNAL_SERVER_ERROR
424
425
@api.errorhandler(HTTPException)
426
def handle_http_error(error):
427
"""Handle HTTP errors with consistent format."""
428
return {
429
'error': error.name,
430
'message': error.description,
431
'code': error.code
432
}, error.code
433
```
434
435
### Validation Error Details
436
437
```python
438
from jsonschema import ValidationError as JSONValidationError
439
440
@api.errorhandler(JSONValidationError)
441
def handle_json_validation_error(error):
442
"""Handle JSON schema validation errors with detailed information."""
443
return {
444
'error': 'Validation failed',
445
'message': error.message,
446
'path': list(error.absolute_path),
447
'invalid_value': error.instance,
448
'schema_path': list(error.schema_path)
449
}, HTTPStatus.BAD_REQUEST
450
451
class ValidationResource(Resource):
452
@api.expect(user_model, validate=True)
453
def post(self):
454
# Validation automatically handled by Flask-RESTX
455
# Custom validation errors can still be raised
456
data = api.payload
457
458
if User.query.filter_by(email=data['email']).first():
459
abort(
460
HTTPStatus.CONFLICT,
461
message='Email already exists',
462
field='email',
463
code='DUPLICATE_EMAIL'
464
)
465
466
return create_user(data), HTTPStatus.CREATED
467
```
468
469
### Swagger Documentation Customization
470
471
```python
472
# Custom Swagger configuration
473
api = Api(
474
app,
475
version='1.0',
476
title='My API',
477
description='Comprehensive REST API with detailed documentation',
478
terms_url='https://example.com/terms',
479
contact='support@example.com',
480
license='MIT',
481
license_url='https://opensource.org/licenses/MIT',
482
authorizations={
483
'Bearer': {
484
'type': 'apiKey',
485
'in': 'header',
486
'name': 'Authorization',
487
'description': 'JWT token in format: Bearer <token>'
488
},
489
'ApiKey': {
490
'type': 'apiKey',
491
'in': 'header',
492
'name': 'X-API-Key',
493
'description': 'API key for authentication'
494
}
495
}
496
)
497
498
# Access Swagger specification
499
@api.route('/swagger-spec')
500
class SwaggerSpec(Resource):
501
def get(self):
502
"""Return the complete Swagger specification."""
503
swagger = Swagger(api)
504
return swagger.as_dict()
505
```
506
507
### Rate Limiting Error Handling
508
509
```python
510
from flask_limiter import Limiter
511
from flask_limiter.util import get_remote_address
512
513
limiter = Limiter(
514
app,
515
key_func=get_remote_address,
516
default_limits=["100 per hour"]
517
)
518
519
@api.errorhandler(429) # Too Many Requests
520
def handle_rate_limit_error(error):
521
"""Handle rate limiting errors."""
522
return {
523
'error': 'Rate limit exceeded',
524
'message': 'Too many requests. Please try again later.',
525
'retry_after': error.retry_after,
526
'code': 'RATE_LIMIT_EXCEEDED'
527
}, HTTPStatus.TOO_MANY_REQUESTS
528
529
class RateLimitedResource(Resource):
530
@limiter.limit("10 per minute")
531
def post(self):
532
return {'message': 'Request processed'}, HTTPStatus.OK
533
```