0
# Exception Handling
1
2
Comprehensive exception system following RFC 7807 Problem Details standard for consistent error responses and debugging. Connexion provides both generic exceptions and HTTP-specific problem exceptions.
3
4
## Capabilities
5
6
### Base Exceptions
7
8
Core exception classes for Connexion-specific errors.
9
10
```python { .api }
11
class ConnexionException(Exception):
12
"""Base exception class for all Connexion-specific errors"""
13
pass
14
15
class ResolverError(ConnexionException):
16
"""Raised when operation resolution fails"""
17
def __init__(self, reason: str, operation_id: str = None):
18
"""
19
Initialize resolver error.
20
21
Parameters:
22
- reason: Description of the resolution failure
23
- operation_id: Failed operation identifier
24
"""
25
26
class InvalidSpecification(ConnexionException):
27
"""Raised when OpenAPI specification is invalid"""
28
def __init__(self, reason: str, spec_path: str = None):
29
"""
30
Initialize specification error.
31
32
Parameters:
33
- reason: Description of the specification issue
34
- spec_path: Path to the invalid specification
35
"""
36
37
class UnsupportedMediaTypeProblem(ConnexionException):
38
"""Raised when request contains unsupported media type"""
39
def __init__(self, media_type: str, supported_types: list = None):
40
"""
41
Initialize media type error.
42
43
Parameters:
44
- media_type: Unsupported media type
45
- supported_types: List of supported media types
46
"""
47
```
48
49
### Problem Exception Base
50
51
RFC 7807 Problem Details implementation for HTTP API errors.
52
53
```python { .api }
54
class ProblemException(Exception):
55
"""
56
Base class for RFC 7807 Problem Detail exceptions.
57
Automatically generates appropriate HTTP error responses.
58
"""
59
60
def __init__(
61
self,
62
status: int,
63
title: str,
64
detail: str = None,
65
type: str = None,
66
instance: str = None,
67
**kwargs
68
):
69
"""
70
Initialize problem exception.
71
72
Parameters:
73
- status: HTTP status code
74
- title: Short, human-readable problem summary
75
- detail: Human-readable explanation specific to this occurrence
76
- type: URI that identifies the problem type
77
- instance: URI that identifies the specific occurrence
78
- **kwargs: Additional problem-specific properties
79
"""
80
super().__init__(detail or title)
81
self.status = status
82
self.title = title
83
self.detail = detail
84
self.type = type
85
self.instance = instance
86
self.extra = kwargs
87
88
def to_problem(self) -> dict:
89
"""
90
Convert exception to RFC 7807 problem dict.
91
92
Returns:
93
dict: Problem details response
94
"""
95
```
96
97
### HTTP Client Error Exceptions (4xx)
98
99
Exceptions for client-side errors with automatic HTTP status codes.
100
101
```python { .api }
102
class BadRequestProblem(ProblemException):
103
"""400 Bad Request - Client sent invalid request"""
104
def __init__(self, title: str = "Bad Request", **kwargs):
105
super().__init__(status=400, title=title, **kwargs)
106
107
class UnauthorizedProblem(ProblemException):
108
"""401 Unauthorized - Authentication required or failed"""
109
def __init__(self, title: str = "Unauthorized", **kwargs):
110
super().__init__(status=401, title=title, **kwargs)
111
112
class ForbiddenProblem(ProblemException):
113
"""403 Forbidden - Access denied"""
114
def __init__(self, title: str = "Forbidden", **kwargs):
115
super().__init__(status=403, title=title, **kwargs)
116
117
class NotFoundProblem(ProblemException):
118
"""404 Not Found - Resource not found"""
119
def __init__(self, title: str = "Not Found", **kwargs):
120
super().__init__(status=404, title=title, **kwargs)
121
122
class MethodNotAllowedProblem(ProblemException):
123
"""405 Method Not Allowed - HTTP method not supported"""
124
def __init__(self, title: str = "Method Not Allowed", **kwargs):
125
super().__init__(status=405, title=title, **kwargs)
126
127
class NotAcceptableProblem(ProblemException):
128
"""406 Not Acceptable - Cannot generate acceptable response"""
129
def __init__(self, title: str = "Not Acceptable", **kwargs):
130
super().__init__(status=406, title=title, **kwargs)
131
132
class ConflictProblem(ProblemException):
133
"""409 Conflict - Request conflicts with current state"""
134
def __init__(self, title: str = "Conflict", **kwargs):
135
super().__init__(status=409, title=title, **kwargs)
136
137
class UnsupportedMediaTypeProblem(ProblemException):
138
"""415 Unsupported Media Type - Request media type not supported"""
139
def __init__(self, title: str = "Unsupported Media Type", **kwargs):
140
super().__init__(status=415, title=title, **kwargs)
141
142
class UnprocessableEntityProblem(ProblemException):
143
"""422 Unprocessable Entity - Request is well-formed but semantically incorrect"""
144
def __init__(self, title: str = "Unprocessable Entity", **kwargs):
145
super().__init__(status=422, title=title, **kwargs)
146
```
147
148
### HTTP Server Error Exceptions (5xx)
149
150
Exceptions for server-side errors.
151
152
```python { .api }
153
class InternalServerErrorProblem(ProblemException):
154
"""500 Internal Server Error - Unexpected server error"""
155
def __init__(self, title: str = "Internal Server Error", **kwargs):
156
super().__init__(status=500, title=title, **kwargs)
157
158
class NotImplementedProblem(ProblemException):
159
"""501 Not Implemented - Functionality not implemented"""
160
def __init__(self, title: str = "Not Implemented", **kwargs):
161
super().__init__(status=501, title=title, **kwargs)
162
163
class ServiceUnavailableProblem(ProblemException):
164
"""503 Service Unavailable - Service temporarily unavailable"""
165
def __init__(self, title: str = "Service Unavailable", **kwargs):
166
super().__init__(status=503, title=title, **kwargs)
167
```
168
169
### Validation Exceptions
170
171
Exceptions specifically for validation failures.
172
173
```python { .api }
174
class ValidationError(BadRequestProblem):
175
"""Request validation failure"""
176
def __init__(self, detail: str, field: str = None, **kwargs):
177
"""
178
Initialize validation error.
179
180
Parameters:
181
- detail: Validation failure description
182
- field: Field that failed validation
183
- **kwargs: Additional validation context
184
"""
185
super().__init__(
186
title="Validation Error",
187
detail=detail,
188
field=field,
189
**kwargs
190
)
191
192
class TypeValidationError(ValidationError):
193
"""Type validation failure"""
194
def __init__(self, field: str, expected_type: str, actual_type: str, **kwargs):
195
"""
196
Initialize type validation error.
197
198
Parameters:
199
- field: Field with type error
200
- expected_type: Expected data type
201
- actual_type: Actual data type received
202
"""
203
super().__init__(
204
detail=f"Field '{field}' expected {expected_type}, got {actual_type}",
205
field=field,
206
expected_type=expected_type,
207
actual_type=actual_type,
208
**kwargs
209
)
210
211
class ExtraParameterProblem(ValidationError):
212
"""Extra parameter in request"""
213
def __init__(self, param_name: str, **kwargs):
214
"""
215
Initialize extra parameter error.
216
217
Parameters:
218
- param_name: Name of the unexpected parameter
219
"""
220
super().__init__(
221
detail=f"Extra parameter '{param_name}' not allowed",
222
parameter=param_name,
223
**kwargs
224
)
225
```
226
227
## Usage Examples
228
229
### Basic Exception Handling
230
231
```python
232
from connexion.exceptions import BadRequestProblem, NotFoundProblem
233
234
def get_user(user_id: int):
235
"""Get user by ID with proper error handling"""
236
237
# Validate input
238
if user_id <= 0:
239
raise BadRequestProblem(
240
detail="User ID must be a positive integer",
241
user_id=user_id
242
)
243
244
# Look up user
245
user = find_user_by_id(user_id)
246
if not user:
247
raise NotFoundProblem(
248
detail=f"User with ID {user_id} not found",
249
user_id=user_id
250
)
251
252
return user.to_dict()
253
```
254
255
### Custom Problem Types
256
257
```python
258
from connexion.exceptions import ProblemException
259
260
class RateLimitExceededProblem(ProblemException):
261
"""429 Too Many Requests - Rate limit exceeded"""
262
def __init__(self, limit: int, window: int, **kwargs):
263
super().__init__(
264
status=429,
265
title="Rate Limit Exceeded",
266
detail=f"Rate limit of {limit} requests per {window} seconds exceeded",
267
type="https://api.example.com/problems/rate-limit-exceeded",
268
limit=limit,
269
window=window,
270
**kwargs
271
)
272
273
# Usage
274
def api_endpoint():
275
if is_rate_limited(request.remote_addr):
276
raise RateLimitExceededProblem(
277
limit=100,
278
window=3600,
279
retry_after=calculate_retry_after()
280
)
281
282
# Process request...
283
return {"result": "success"}
284
```
285
286
### Validation Error Handling
287
288
```python
289
from connexion.exceptions import ValidationError, TypeValidationError
290
291
def create_user():
292
"""Create user with comprehensive validation"""
293
data = request.json
294
295
# Required field validation
296
if not data.get('email'):
297
raise ValidationError(
298
detail="Email is required",
299
field="email"
300
)
301
302
# Format validation
303
if not is_valid_email(data['email']):
304
raise ValidationError(
305
detail="Invalid email format",
306
field="email",
307
value=data['email']
308
)
309
310
# Type validation
311
age = data.get('age')
312
if age is not None and not isinstance(age, int):
313
raise TypeValidationError(
314
field="age",
315
expected_type="integer",
316
actual_type=type(age).__name__
317
)
318
319
# Business rule validation
320
if age is not None and age < 0:
321
raise ValidationError(
322
detail="Age cannot be negative",
323
field="age",
324
value=age
325
)
326
327
# Create user...
328
user = create_user_record(data)
329
return user.to_dict(), 201
330
```
331
332
### Global Error Handler
333
334
```python
335
from connexion.exceptions import ProblemException, ConnexionException
336
337
def global_error_handler(exception):
338
"""Global error handler for all exceptions"""
339
340
# Handle Connexion problem exceptions
341
if isinstance(exception, ProblemException):
342
return exception.to_problem(), exception.status
343
344
# Handle other Connexion exceptions
345
if isinstance(exception, ConnexionException):
346
return {
347
"error": "Internal error",
348
"type": type(exception).__name__,
349
"detail": str(exception)
350
}, 500
351
352
# Handle unexpected exceptions
353
import traceback
354
logger.error(f"Unexpected error: {exception}", exc_info=True)
355
356
return {
357
"error": "Internal server error",
358
"type": "UnexpectedError"
359
}, 500
360
361
# Register global error handler
362
app.add_error_handler(Exception, global_error_handler)
363
```
364
365
### Context-Aware Error Handling
366
367
```python
368
from connexion.exceptions import ForbiddenProblem, UnauthorizedProblem
369
370
def delete_user(user_id: int):
371
"""Delete user with authorization checks"""
372
373
# Check authentication
374
current_user = request.context.get('user')
375
if not current_user:
376
raise UnauthorizedProblem(
377
detail="Authentication required to delete users"
378
)
379
380
# Check authorization
381
if current_user['user_id'] != user_id and 'admin' not in current_user['roles']:
382
raise ForbiddenProblem(
383
detail="You can only delete your own account",
384
current_user_id=current_user['user_id'],
385
target_user_id=user_id
386
)
387
388
# Check if user exists
389
if not user_exists(user_id):
390
raise NotFoundProblem(
391
detail=f"User {user_id} not found"
392
)
393
394
# Perform deletion
395
delete_user_record(user_id)
396
return NoContent, 204
397
```
398
399
### Exception with Retry Information
400
401
```python
402
from connexion.exceptions import ServiceUnavailableProblem
403
import random
404
405
def external_service_call():
406
"""Call external service with retry information on failure"""
407
408
try:
409
result = call_external_api()
410
return result
411
except ExternalServiceError as e:
412
# Calculate retry delay
413
retry_after = random.randint(30, 120) # 30-120 seconds
414
415
raise ServiceUnavailableProblem(
416
detail="External service temporarily unavailable",
417
type="https://api.example.com/problems/external-service-unavailable",
418
service="payment-processor",
419
retry_after=retry_after,
420
error_code=e.code
421
)
422
```
423
424
### Detailed Validation Errors
425
426
```python
427
from connexion.exceptions import UnprocessableEntityProblem
428
429
def bulk_create_users():
430
"""Create multiple users with detailed error reporting"""
431
users_data = request.json.get('users', [])
432
errors = []
433
434
for i, user_data in enumerate(users_data):
435
user_errors = validate_user_data(user_data)
436
if user_errors:
437
errors.append({
438
'index': i,
439
'errors': user_errors
440
})
441
442
if errors:
443
raise UnprocessableEntityProblem(
444
detail="Validation failed for one or more users",
445
type="https://api.example.com/problems/bulk-validation-error",
446
invalid_users=errors,
447
total_users=len(users_data),
448
failed_count=len(errors)
449
)
450
451
# Create all users...
452
created_users = [create_user_record(data) for data in users_data]
453
return {"created_users": len(created_users)}, 201
454
455
def validate_user_data(data):
456
"""Validate individual user data and return list of errors"""
457
errors = []
458
459
if not data.get('email'):
460
errors.append({'field': 'email', 'message': 'Email is required'})
461
elif not is_valid_email(data['email']):
462
errors.append({'field': 'email', 'message': 'Invalid email format'})
463
464
if 'age' in data and (not isinstance(data['age'], int) or data['age'] < 0):
465
errors.append({'field': 'age', 'message': 'Age must be a non-negative integer'})
466
467
return errors
468
```
469
470
### Exception Chaining
471
472
```python
473
from connexion.exceptions import InternalServerErrorProblem
474
475
def complex_operation():
476
"""Complex operation with exception chaining"""
477
478
try:
479
# Step 1: Database operation
480
result1 = database_operation()
481
482
# Step 2: External API call
483
result2 = external_api_call(result1)
484
485
# Step 3: Final processing
486
return process_results(result1, result2)
487
488
except DatabaseError as e:
489
raise InternalServerErrorProblem(
490
detail="Database operation failed",
491
type="https://api.example.com/problems/database-error",
492
operation="complex_operation",
493
step="database_operation",
494
original_error=str(e)
495
) from e
496
497
except ExternalAPIError as e:
498
raise InternalServerErrorProblem(
499
detail="External service call failed",
500
type="https://api.example.com/problems/external-api-error",
501
operation="complex_operation",
502
step="external_api_call",
503
service_url=e.url,
504
original_error=str(e)
505
) from e
506
```