OpenAPI/Swagger spec-first web framework for Python with automatic request validation and response serialization
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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.
Core exception classes for Connexion-specific errors.
class ConnexionException(Exception):
"""Base exception class for all Connexion-specific errors"""
pass
class ResolverError(ConnexionException):
"""Raised when operation resolution fails"""
def __init__(self, reason: str, operation_id: str = None):
"""
Initialize resolver error.
Parameters:
- reason: Description of the resolution failure
- operation_id: Failed operation identifier
"""
class InvalidSpecification(ConnexionException):
"""Raised when OpenAPI specification is invalid"""
def __init__(self, reason: str, spec_path: str = None):
"""
Initialize specification error.
Parameters:
- reason: Description of the specification issue
- spec_path: Path to the invalid specification
"""
class UnsupportedMediaTypeProblem(ConnexionException):
"""Raised when request contains unsupported media type"""
def __init__(self, media_type: str, supported_types: list = None):
"""
Initialize media type error.
Parameters:
- media_type: Unsupported media type
- supported_types: List of supported media types
"""RFC 7807 Problem Details implementation for HTTP API errors.
class ProblemException(Exception):
"""
Base class for RFC 7807 Problem Detail exceptions.
Automatically generates appropriate HTTP error responses.
"""
def __init__(
self,
status: int,
title: str,
detail: str = None,
type: str = None,
instance: str = None,
**kwargs
):
"""
Initialize problem exception.
Parameters:
- status: HTTP status code
- title: Short, human-readable problem summary
- detail: Human-readable explanation specific to this occurrence
- type: URI that identifies the problem type
- instance: URI that identifies the specific occurrence
- **kwargs: Additional problem-specific properties
"""
super().__init__(detail or title)
self.status = status
self.title = title
self.detail = detail
self.type = type
self.instance = instance
self.extra = kwargs
def to_problem(self) -> dict:
"""
Convert exception to RFC 7807 problem dict.
Returns:
dict: Problem details response
"""Exceptions for client-side errors with automatic HTTP status codes.
class BadRequestProblem(ProblemException):
"""400 Bad Request - Client sent invalid request"""
def __init__(self, title: str = "Bad Request", **kwargs):
super().__init__(status=400, title=title, **kwargs)
class UnauthorizedProblem(ProblemException):
"""401 Unauthorized - Authentication required or failed"""
def __init__(self, title: str = "Unauthorized", **kwargs):
super().__init__(status=401, title=title, **kwargs)
class ForbiddenProblem(ProblemException):
"""403 Forbidden - Access denied"""
def __init__(self, title: str = "Forbidden", **kwargs):
super().__init__(status=403, title=title, **kwargs)
class NotFoundProblem(ProblemException):
"""404 Not Found - Resource not found"""
def __init__(self, title: str = "Not Found", **kwargs):
super().__init__(status=404, title=title, **kwargs)
class MethodNotAllowedProblem(ProblemException):
"""405 Method Not Allowed - HTTP method not supported"""
def __init__(self, title: str = "Method Not Allowed", **kwargs):
super().__init__(status=405, title=title, **kwargs)
class NotAcceptableProblem(ProblemException):
"""406 Not Acceptable - Cannot generate acceptable response"""
def __init__(self, title: str = "Not Acceptable", **kwargs):
super().__init__(status=406, title=title, **kwargs)
class ConflictProblem(ProblemException):
"""409 Conflict - Request conflicts with current state"""
def __init__(self, title: str = "Conflict", **kwargs):
super().__init__(status=409, title=title, **kwargs)
class UnsupportedMediaTypeProblem(ProblemException):
"""415 Unsupported Media Type - Request media type not supported"""
def __init__(self, title: str = "Unsupported Media Type", **kwargs):
super().__init__(status=415, title=title, **kwargs)
class UnprocessableEntityProblem(ProblemException):
"""422 Unprocessable Entity - Request is well-formed but semantically incorrect"""
def __init__(self, title: str = "Unprocessable Entity", **kwargs):
super().__init__(status=422, title=title, **kwargs)Exceptions for server-side errors.
class InternalServerErrorProblem(ProblemException):
"""500 Internal Server Error - Unexpected server error"""
def __init__(self, title: str = "Internal Server Error", **kwargs):
super().__init__(status=500, title=title, **kwargs)
class NotImplementedProblem(ProblemException):
"""501 Not Implemented - Functionality not implemented"""
def __init__(self, title: str = "Not Implemented", **kwargs):
super().__init__(status=501, title=title, **kwargs)
class ServiceUnavailableProblem(ProblemException):
"""503 Service Unavailable - Service temporarily unavailable"""
def __init__(self, title: str = "Service Unavailable", **kwargs):
super().__init__(status=503, title=title, **kwargs)Exceptions specifically for validation failures.
class ValidationError(BadRequestProblem):
"""Request validation failure"""
def __init__(self, detail: str, field: str = None, **kwargs):
"""
Initialize validation error.
Parameters:
- detail: Validation failure description
- field: Field that failed validation
- **kwargs: Additional validation context
"""
super().__init__(
title="Validation Error",
detail=detail,
field=field,
**kwargs
)
class TypeValidationError(ValidationError):
"""Type validation failure"""
def __init__(self, field: str, expected_type: str, actual_type: str, **kwargs):
"""
Initialize type validation error.
Parameters:
- field: Field with type error
- expected_type: Expected data type
- actual_type: Actual data type received
"""
super().__init__(
detail=f"Field '{field}' expected {expected_type}, got {actual_type}",
field=field,
expected_type=expected_type,
actual_type=actual_type,
**kwargs
)
class ExtraParameterProblem(ValidationError):
"""Extra parameter in request"""
def __init__(self, param_name: str, **kwargs):
"""
Initialize extra parameter error.
Parameters:
- param_name: Name of the unexpected parameter
"""
super().__init__(
detail=f"Extra parameter '{param_name}' not allowed",
parameter=param_name,
**kwargs
)from connexion.exceptions import BadRequestProblem, NotFoundProblem
def get_user(user_id: int):
"""Get user by ID with proper error handling"""
# Validate input
if user_id <= 0:
raise BadRequestProblem(
detail="User ID must be a positive integer",
user_id=user_id
)
# Look up user
user = find_user_by_id(user_id)
if not user:
raise NotFoundProblem(
detail=f"User with ID {user_id} not found",
user_id=user_id
)
return user.to_dict()from connexion.exceptions import ProblemException
class RateLimitExceededProblem(ProblemException):
"""429 Too Many Requests - Rate limit exceeded"""
def __init__(self, limit: int, window: int, **kwargs):
super().__init__(
status=429,
title="Rate Limit Exceeded",
detail=f"Rate limit of {limit} requests per {window} seconds exceeded",
type="https://api.example.com/problems/rate-limit-exceeded",
limit=limit,
window=window,
**kwargs
)
# Usage
def api_endpoint():
if is_rate_limited(request.remote_addr):
raise RateLimitExceededProblem(
limit=100,
window=3600,
retry_after=calculate_retry_after()
)
# Process request...
return {"result": "success"}from connexion.exceptions import ValidationError, TypeValidationError
def create_user():
"""Create user with comprehensive validation"""
data = request.json
# Required field validation
if not data.get('email'):
raise ValidationError(
detail="Email is required",
field="email"
)
# Format validation
if not is_valid_email(data['email']):
raise ValidationError(
detail="Invalid email format",
field="email",
value=data['email']
)
# Type validation
age = data.get('age')
if age is not None and not isinstance(age, int):
raise TypeValidationError(
field="age",
expected_type="integer",
actual_type=type(age).__name__
)
# Business rule validation
if age is not None and age < 0:
raise ValidationError(
detail="Age cannot be negative",
field="age",
value=age
)
# Create user...
user = create_user_record(data)
return user.to_dict(), 201from connexion.exceptions import ProblemException, ConnexionException
def global_error_handler(exception):
"""Global error handler for all exceptions"""
# Handle Connexion problem exceptions
if isinstance(exception, ProblemException):
return exception.to_problem(), exception.status
# Handle other Connexion exceptions
if isinstance(exception, ConnexionException):
return {
"error": "Internal error",
"type": type(exception).__name__,
"detail": str(exception)
}, 500
# Handle unexpected exceptions
import traceback
logger.error(f"Unexpected error: {exception}", exc_info=True)
return {
"error": "Internal server error",
"type": "UnexpectedError"
}, 500
# Register global error handler
app.add_error_handler(Exception, global_error_handler)from connexion.exceptions import ForbiddenProblem, UnauthorizedProblem
def delete_user(user_id: int):
"""Delete user with authorization checks"""
# Check authentication
current_user = request.context.get('user')
if not current_user:
raise UnauthorizedProblem(
detail="Authentication required to delete users"
)
# Check authorization
if current_user['user_id'] != user_id and 'admin' not in current_user['roles']:
raise ForbiddenProblem(
detail="You can only delete your own account",
current_user_id=current_user['user_id'],
target_user_id=user_id
)
# Check if user exists
if not user_exists(user_id):
raise NotFoundProblem(
detail=f"User {user_id} not found"
)
# Perform deletion
delete_user_record(user_id)
return NoContent, 204from connexion.exceptions import ServiceUnavailableProblem
import random
def external_service_call():
"""Call external service with retry information on failure"""
try:
result = call_external_api()
return result
except ExternalServiceError as e:
# Calculate retry delay
retry_after = random.randint(30, 120) # 30-120 seconds
raise ServiceUnavailableProblem(
detail="External service temporarily unavailable",
type="https://api.example.com/problems/external-service-unavailable",
service="payment-processor",
retry_after=retry_after,
error_code=e.code
)from connexion.exceptions import UnprocessableEntityProblem
def bulk_create_users():
"""Create multiple users with detailed error reporting"""
users_data = request.json.get('users', [])
errors = []
for i, user_data in enumerate(users_data):
user_errors = validate_user_data(user_data)
if user_errors:
errors.append({
'index': i,
'errors': user_errors
})
if errors:
raise UnprocessableEntityProblem(
detail="Validation failed for one or more users",
type="https://api.example.com/problems/bulk-validation-error",
invalid_users=errors,
total_users=len(users_data),
failed_count=len(errors)
)
# Create all users...
created_users = [create_user_record(data) for data in users_data]
return {"created_users": len(created_users)}, 201
def validate_user_data(data):
"""Validate individual user data and return list of errors"""
errors = []
if not data.get('email'):
errors.append({'field': 'email', 'message': 'Email is required'})
elif not is_valid_email(data['email']):
errors.append({'field': 'email', 'message': 'Invalid email format'})
if 'age' in data and (not isinstance(data['age'], int) or data['age'] < 0):
errors.append({'field': 'age', 'message': 'Age must be a non-negative integer'})
return errorsfrom connexion.exceptions import InternalServerErrorProblem
def complex_operation():
"""Complex operation with exception chaining"""
try:
# Step 1: Database operation
result1 = database_operation()
# Step 2: External API call
result2 = external_api_call(result1)
# Step 3: Final processing
return process_results(result1, result2)
except DatabaseError as e:
raise InternalServerErrorProblem(
detail="Database operation failed",
type="https://api.example.com/problems/database-error",
operation="complex_operation",
step="database_operation",
original_error=str(e)
) from e
except ExternalAPIError as e:
raise InternalServerErrorProblem(
detail="External service call failed",
type="https://api.example.com/problems/external-api-error",
operation="complex_operation",
step="external_api_call",
service_url=e.url,
original_error=str(e)
) from eInstall with Tessl CLI
npx tessl i tessl/pypi-connexion