aws-python-helper 0.23.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aws_python_helper/__init__.py +45 -0
- aws_python_helper/api/__init__.py +11 -0
- aws_python_helper/api/auth_middleware.py +108 -0
- aws_python_helper/api/auth_validators.py +143 -0
- aws_python_helper/api/base.py +272 -0
- aws_python_helper/api/dispatcher.py +213 -0
- aws_python_helper/api/exceptions.py +43 -0
- aws_python_helper/api/fetcher.py +210 -0
- aws_python_helper/api/handler.py +106 -0
- aws_python_helper/database/__init__.py +11 -0
- aws_python_helper/database/database_proxy.py +50 -0
- aws_python_helper/database/external_database_proxy.py +66 -0
- aws_python_helper/database/external_mongo_manager.py +212 -0
- aws_python_helper/database/mongo_manager.py +214 -0
- aws_python_helper/fargate/__init__.py +9 -0
- aws_python_helper/fargate/executor.py +226 -0
- aws_python_helper/fargate/fetcher.py +108 -0
- aws_python_helper/fargate/handler.py +101 -0
- aws_python_helper/fargate/task_base.py +165 -0
- aws_python_helper/lambda_standalone/__init__.py +8 -0
- aws_python_helper/lambda_standalone/base.py +171 -0
- aws_python_helper/lambda_standalone/fetcher.py +122 -0
- aws_python_helper/lambda_standalone/handler.py +117 -0
- aws_python_helper/sns/__init__.py +6 -0
- aws_python_helper/sns/publisher.py +245 -0
- aws_python_helper/sqs/__init__.py +10 -0
- aws_python_helper/sqs/consumer_base.py +416 -0
- aws_python_helper/sqs/fetcher.py +111 -0
- aws_python_helper/sqs/handler.py +138 -0
- aws_python_helper/utils/__init__.py +9 -0
- aws_python_helper/utils/json_encoder.py +108 -0
- aws_python_helper/utils/response.py +145 -0
- aws_python_helper/utils/serializer.py +103 -0
- aws_python_helper-0.23.0.dist-info/METADATA +712 -0
- aws_python_helper-0.23.0.dist-info/RECORD +37 -0
- aws_python_helper-0.23.0.dist-info/WHEEL +5 -0
- aws_python_helper-0.23.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AWS Python Framework
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "0.1.0"
|
|
6
|
+
|
|
7
|
+
# All classes
|
|
8
|
+
from .api.base import API
|
|
9
|
+
from .sqs.consumer_base import SQSConsumer
|
|
10
|
+
from .database.mongo_manager import MongoManager
|
|
11
|
+
from .database.database_proxy import DatabaseProxy
|
|
12
|
+
from .sns.publisher import SNSPublisher
|
|
13
|
+
from .fargate.task_base import FargateTask
|
|
14
|
+
from .fargate.executor import FargateExecutor
|
|
15
|
+
from .lambda_standalone.base import Lambda
|
|
16
|
+
|
|
17
|
+
# All handlers
|
|
18
|
+
from .fargate.handler import fargate_handler
|
|
19
|
+
from .api.handler import api_handler
|
|
20
|
+
from .sqs.handler import sqs_handler
|
|
21
|
+
from .lambda_standalone.handler import lambda_handler
|
|
22
|
+
|
|
23
|
+
# Utils
|
|
24
|
+
from .utils.json_encoder import MongoJSONEncoder, mongo_json_dumps
|
|
25
|
+
from .utils.serializer import serialize_mongo_types
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
'API',
|
|
30
|
+
'SQSConsumer',
|
|
31
|
+
'MongoManager',
|
|
32
|
+
'DatabaseProxy',
|
|
33
|
+
'SNSPublisher',
|
|
34
|
+
'FargateTask',
|
|
35
|
+
'FargateExecutor',
|
|
36
|
+
'Lambda',
|
|
37
|
+
'fargate_handler',
|
|
38
|
+
'api_handler',
|
|
39
|
+
'sqs_handler',
|
|
40
|
+
'lambda_handler',
|
|
41
|
+
'MongoJSONEncoder',
|
|
42
|
+
'mongo_json_dumps',
|
|
43
|
+
'serialize_mongo_types',
|
|
44
|
+
]
|
|
45
|
+
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Auth Middleware - Handles authentication for API requests
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict, Any
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
from .auth_validators import AuthValidator
|
|
9
|
+
from .exceptions import UnauthorizedError
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AuthMiddleware:
|
|
15
|
+
"""
|
|
16
|
+
Middleware to handle authentication for API requests
|
|
17
|
+
|
|
18
|
+
This middleware:
|
|
19
|
+
1. Extracts token from Authorization header
|
|
20
|
+
2. Validates token using configured validator
|
|
21
|
+
3. Injects user information into API instance
|
|
22
|
+
4. Raises UnauthorizedError if authentication fails
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, validator: AuthValidator):
|
|
26
|
+
"""
|
|
27
|
+
Initialize middleware with an auth validator
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
validator: AuthValidator instance to use for token validation
|
|
31
|
+
"""
|
|
32
|
+
self.validator = validator
|
|
33
|
+
|
|
34
|
+
def _extract_token(self, headers: Dict[str, str]) -> str:
|
|
35
|
+
"""
|
|
36
|
+
Extract token from Authorization header
|
|
37
|
+
|
|
38
|
+
Supports two formats:
|
|
39
|
+
- Authorization: Bearer <token>
|
|
40
|
+
- authorization: Bearer <token> (case insensitive)
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
headers: Request headers dictionary
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
The extracted token string
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
UnauthorizedError: If Authorization header is missing or invalid
|
|
50
|
+
"""
|
|
51
|
+
# Try to get Authorization header (case-insensitive)
|
|
52
|
+
auth_header = None
|
|
53
|
+
for key, value in headers.items():
|
|
54
|
+
if key.lower() == 'authorization':
|
|
55
|
+
auth_header = value
|
|
56
|
+
break
|
|
57
|
+
|
|
58
|
+
if not auth_header:
|
|
59
|
+
raise UnauthorizedError("Authorization header is required")
|
|
60
|
+
|
|
61
|
+
# Check Bearer format
|
|
62
|
+
if not auth_header.startswith('Bearer '):
|
|
63
|
+
raise UnauthorizedError("Authorization header must use Bearer scheme")
|
|
64
|
+
|
|
65
|
+
# Extract token
|
|
66
|
+
token = auth_header.replace('Bearer ', '').strip()
|
|
67
|
+
|
|
68
|
+
if not token:
|
|
69
|
+
raise UnauthorizedError("Token cannot be empty")
|
|
70
|
+
|
|
71
|
+
return token
|
|
72
|
+
|
|
73
|
+
async def authenticate(self, headers: Dict[str, str], api: Any):
|
|
74
|
+
"""
|
|
75
|
+
Authenticate the request and inject user data into API instance
|
|
76
|
+
|
|
77
|
+
This method:
|
|
78
|
+
1. Extracts token from headers
|
|
79
|
+
2. Validates token using the configured validator
|
|
80
|
+
3. Injects authentication data into the API instance
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
headers: Request headers dictionary
|
|
84
|
+
api: API instance to inject authentication data into
|
|
85
|
+
|
|
86
|
+
Raises:
|
|
87
|
+
UnauthorizedError: If authentication fails
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
# 1. Extract token from headers
|
|
92
|
+
token = self._extract_token(headers)
|
|
93
|
+
|
|
94
|
+
# 2. Validate token
|
|
95
|
+
auth_data = await self.validator.validate_token(token)
|
|
96
|
+
|
|
97
|
+
# 3. Inject authentication data into API instance
|
|
98
|
+
api._current_user = auth_data.get('user')
|
|
99
|
+
api._auth_data = auth_data
|
|
100
|
+
api._is_authenticated = True
|
|
101
|
+
except UnauthorizedError:
|
|
102
|
+
# Re-raise UnauthorizedError as is
|
|
103
|
+
raise
|
|
104
|
+
|
|
105
|
+
except Exception as e:
|
|
106
|
+
# Catch any other error and convert to UnauthorizedError
|
|
107
|
+
logger.error(f"Authentication error: {e}", exc_info=True)
|
|
108
|
+
raise UnauthorizedError(f"Authentication failed: {str(e)}")
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Auth Validators - Validators for different authentication strategies
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import Dict, Any, Optional
|
|
7
|
+
import os
|
|
8
|
+
import logging
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from .exceptions import UnauthorizedError
|
|
11
|
+
from ..database.mongo_manager import MongoManager
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AuthValidator(ABC):
|
|
17
|
+
"""
|
|
18
|
+
Abstract base class for authentication validators
|
|
19
|
+
|
|
20
|
+
Each validator implements a different strategy to validate tokens.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
async def validate_token(self, token: str) -> Dict[str, Any]:
|
|
25
|
+
"""
|
|
26
|
+
Validate a token and return user information
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
token: The authentication token to validate
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Dict with user information: {'user_id': ..., 'user': {...}, ...}
|
|
33
|
+
|
|
34
|
+
Raises:
|
|
35
|
+
UnauthorizedError: If token is invalid
|
|
36
|
+
"""
|
|
37
|
+
raise NotImplementedError("Subclasses must implement validate_token()")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TokenValidator(AuthValidator):
|
|
41
|
+
"""
|
|
42
|
+
Validates authentication tokens
|
|
43
|
+
|
|
44
|
+
Simple unified validator that:
|
|
45
|
+
1. First checks if token matches AUTH_BYPASS_TOKEN (if set)
|
|
46
|
+
2. If not bypass, searches token in MongoDB 'tokens' collection
|
|
47
|
+
3. Validates token is not expired and is active
|
|
48
|
+
4. Loads complete user data from 'users' collection
|
|
49
|
+
5. Updates last_used_at timestamp for auditing
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
async def validate_token(self, token: str) -> Dict[str, Any]:
|
|
53
|
+
"""
|
|
54
|
+
Validate token against MongoDB or bypass token
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
token: The authentication token to validate
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Dict with user_id, user object, and token_data
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
UnauthorizedError: If token is invalid or expired
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
# 1. Check bypass token first (for development/testing)
|
|
67
|
+
bypass_token = os.getenv('AUTH_BYPASS_TOKEN')
|
|
68
|
+
if bypass_token and token == bypass_token:
|
|
69
|
+
logger.info("Bypass token used - skipping DB validation")
|
|
70
|
+
return {
|
|
71
|
+
'user_id': 'bypass',
|
|
72
|
+
'user': {
|
|
73
|
+
'email': 'bypass@system',
|
|
74
|
+
'role': 'admin',
|
|
75
|
+
'name': 'Bypass User',
|
|
76
|
+
'_id': 'bypass'
|
|
77
|
+
},
|
|
78
|
+
'is_bypass': True,
|
|
79
|
+
'token_data': None
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# 2. Get database name from environment
|
|
83
|
+
db_name = os.getenv('AUTH_DB_NAME') or 'core'
|
|
84
|
+
if not db_name:
|
|
85
|
+
raise ValueError(
|
|
86
|
+
"AUTH_DB_NAME environment variable not set. "
|
|
87
|
+
"This is required for MongoDB authentication."
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# 3. Get database reference
|
|
91
|
+
if not MongoManager.is_initialized():
|
|
92
|
+
raise RuntimeError("MongoManager not initialized")
|
|
93
|
+
|
|
94
|
+
db = MongoManager.get_database(db_name)
|
|
95
|
+
|
|
96
|
+
# 4. Search for token in database
|
|
97
|
+
token_doc = await db.tokens.find_one({
|
|
98
|
+
'token': token,
|
|
99
|
+
'is_active': True
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
if not token_doc:
|
|
103
|
+
logger.warning(f"Token not found or inactive")
|
|
104
|
+
raise UnauthorizedError("Invalid or revoked token")
|
|
105
|
+
|
|
106
|
+
# 5. Check if token is expired
|
|
107
|
+
if 'expires_at' in token_doc:
|
|
108
|
+
if token_doc['expires_at'] < datetime.utcnow():
|
|
109
|
+
logger.warning(f"Token expired at {token_doc['expires_at']}")
|
|
110
|
+
raise UnauthorizedError("Token has expired")
|
|
111
|
+
|
|
112
|
+
# 6. Load user from database
|
|
113
|
+
user = await db.users.find_one({'_id': token_doc['user_id']})
|
|
114
|
+
|
|
115
|
+
if not user:
|
|
116
|
+
logger.error(f"User {token_doc['user_id']} not found for valid token")
|
|
117
|
+
raise UnauthorizedError("User not found")
|
|
118
|
+
|
|
119
|
+
# 7. Check if user is active
|
|
120
|
+
if 'is_active' in user and not user['is_active']:
|
|
121
|
+
logger.warning(f"User {user['_id']} is inactive")
|
|
122
|
+
raise UnauthorizedError("User account is inactive")
|
|
123
|
+
|
|
124
|
+
# 8. Update last_used_at for auditing
|
|
125
|
+
try:
|
|
126
|
+
await db.tokens.update_one(
|
|
127
|
+
{'_id': token_doc['_id']},
|
|
128
|
+
{'$set': {'last_used_at': datetime.utcnow()}}
|
|
129
|
+
)
|
|
130
|
+
except Exception as e:
|
|
131
|
+
# Non-critical, just log
|
|
132
|
+
logger.warning(f"Failed to update last_used_at: {e}")
|
|
133
|
+
|
|
134
|
+
# 9. Return user data (remove password from response)
|
|
135
|
+
user_data = dict(user)
|
|
136
|
+
user_data.pop('password', None) # Never return password
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
'user_id': str(token_doc['user_id']),
|
|
140
|
+
'user': user_data,
|
|
141
|
+
'token_data': token_doc,
|
|
142
|
+
'is_bypass': False
|
|
143
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""
|
|
2
|
+
API Base Class - Base class for all REST APIs
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Dict, Any, Optional, List
|
|
8
|
+
from ..database.mongo_manager import MongoManager
|
|
9
|
+
from ..database.database_proxy import DatabaseProxy
|
|
10
|
+
from ..database.external_mongo_manager import ExternalMongoManager
|
|
11
|
+
from ..database.external_database_proxy import ExternalDatabaseProxy
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class API(ABC):
|
|
15
|
+
"""
|
|
16
|
+
Base class for all REST APIs
|
|
17
|
+
|
|
18
|
+
Base class that provides the basic structure
|
|
19
|
+
to handle requests and responses from API Gateway.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self):
|
|
23
|
+
# Request properties
|
|
24
|
+
self._endpoint: str = ""
|
|
25
|
+
self._http_method: str = ""
|
|
26
|
+
self._data: Dict[str, Any] = {}
|
|
27
|
+
self._headers: Dict[str, str] = {}
|
|
28
|
+
self._path_parameters: List[str] = []
|
|
29
|
+
self._query_parameters: Dict[str, Any] = {}
|
|
30
|
+
|
|
31
|
+
# Response properties
|
|
32
|
+
self._response_code: Optional[int] = None
|
|
33
|
+
self._response_body: Any = None
|
|
34
|
+
self._response_headers: Dict[str, str] = {}
|
|
35
|
+
|
|
36
|
+
# Database proxy
|
|
37
|
+
self._db = None
|
|
38
|
+
self._external_db = None
|
|
39
|
+
|
|
40
|
+
# Authentication properties
|
|
41
|
+
self._current_user: Optional[Dict[str, Any]] = None
|
|
42
|
+
self._auth_data: Optional[Dict[str, Any]] = None
|
|
43
|
+
self._is_authenticated: bool = False
|
|
44
|
+
self.logger = logging.getLogger(self.__class__.__name__)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def endpoint(self) -> str:
|
|
48
|
+
"""Endpoint of the request"""
|
|
49
|
+
return self._endpoint
|
|
50
|
+
|
|
51
|
+
@endpoint.setter
|
|
52
|
+
def endpoint(self, value: str):
|
|
53
|
+
self._endpoint = value
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def http_method(self) -> str:
|
|
57
|
+
"""HTTP method (get, post, put, delete, etc.)"""
|
|
58
|
+
return self._http_method
|
|
59
|
+
|
|
60
|
+
@http_method.setter
|
|
61
|
+
def http_method(self, value: str):
|
|
62
|
+
self._http_method = value
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def data(self) -> Dict[str, Any]:
|
|
66
|
+
"""Data of the request (body for POST/PUT, query params for GET)"""
|
|
67
|
+
return self._data
|
|
68
|
+
|
|
69
|
+
@data.setter
|
|
70
|
+
def data(self, value: Dict[str, Any]):
|
|
71
|
+
self._data = value or {}
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def headers(self) -> Dict[str, str]:
|
|
75
|
+
"""Headers of the request"""
|
|
76
|
+
return self._headers
|
|
77
|
+
|
|
78
|
+
@headers.setter
|
|
79
|
+
def headers(self, value: Dict[str, str]):
|
|
80
|
+
self._headers = value or {}
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def path_parameters(self) -> List[str]:
|
|
84
|
+
"""Path parameters extracted from the URL"""
|
|
85
|
+
return self._path_parameters
|
|
86
|
+
|
|
87
|
+
@path_parameters.setter
|
|
88
|
+
def path_parameters(self, value: List[str]):
|
|
89
|
+
self._path_parameters = value or []
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def query_parameters(self) -> Dict[str, Any]:
|
|
93
|
+
"""Query parameters of the URL"""
|
|
94
|
+
return self._query_parameters
|
|
95
|
+
|
|
96
|
+
@query_parameters.setter
|
|
97
|
+
def query_parameters(self, value: Dict[str, Any]):
|
|
98
|
+
self._query_parameters = value or {}
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def db(self):
|
|
102
|
+
"""
|
|
103
|
+
Access to MongoDB databases (main cluster)
|
|
104
|
+
"""
|
|
105
|
+
if self._db is None:
|
|
106
|
+
self._db = DatabaseProxy(MongoManager)
|
|
107
|
+
return self._db
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def external_db(self):
|
|
111
|
+
"""
|
|
112
|
+
Access to external MongoDB clusters
|
|
113
|
+
|
|
114
|
+
Returns None if EXTERNAL_MONGODB_CONNECTIONS environment variable is not set.
|
|
115
|
+
|
|
116
|
+
Usage:
|
|
117
|
+
if self.external_db:
|
|
118
|
+
result = await self.external_db.ClusterDockets.smart_data.addresses.find_one({...})
|
|
119
|
+
await self.external_db.ClusterDockets.core.users.insert_one({...})
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
ExternalDatabaseProxy instance for accessing external clusters, or None if not configured
|
|
123
|
+
"""
|
|
124
|
+
if self._external_db is None:
|
|
125
|
+
# Initialize external connections if not already done
|
|
126
|
+
if not ExternalMongoManager.is_initialized():
|
|
127
|
+
has_connections = ExternalMongoManager.initialize()
|
|
128
|
+
if not has_connections:
|
|
129
|
+
# No external connections available, return None
|
|
130
|
+
return None
|
|
131
|
+
else:
|
|
132
|
+
# Check if there are any connections available
|
|
133
|
+
if len(ExternalMongoManager.get_available_clusters()) == 0:
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
self._external_db = ExternalDatabaseProxy()
|
|
137
|
+
return self._external_db
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def current_user(self) -> Optional[Dict[str, Any]]:
|
|
141
|
+
"""
|
|
142
|
+
Current authenticated user or None if not authenticated
|
|
143
|
+
|
|
144
|
+
This property is populated by the authentication middleware
|
|
145
|
+
when REQUIRE_AUTH=true.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Dict with user data (email, role, name, etc.) or None
|
|
149
|
+
|
|
150
|
+
Example:
|
|
151
|
+
if self.is_authenticated:
|
|
152
|
+
user_email = self.current_user['email']
|
|
153
|
+
user_role = self.current_user.get('role', 'user')
|
|
154
|
+
"""
|
|
155
|
+
return self._current_user
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def is_authenticated(self) -> bool:
|
|
159
|
+
"""
|
|
160
|
+
True if the request has been authenticated
|
|
161
|
+
|
|
162
|
+
This is set to True by the authentication middleware
|
|
163
|
+
when a valid token is provided.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
True if authenticated, False otherwise
|
|
167
|
+
|
|
168
|
+
Example:
|
|
169
|
+
if not self.is_authenticated:
|
|
170
|
+
raise ValueError("This operation requires authentication")
|
|
171
|
+
"""
|
|
172
|
+
return self._is_authenticated
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def auth_data(self) -> Optional[Dict[str, Any]]:
|
|
176
|
+
"""
|
|
177
|
+
Complete authentication data from the middleware
|
|
178
|
+
|
|
179
|
+
This includes user data, token data, and other metadata
|
|
180
|
+
provided by the authentication validator.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Dict with authentication data or None
|
|
184
|
+
"""
|
|
185
|
+
return self._auth_data
|
|
186
|
+
|
|
187
|
+
def set_code(self, code: int):
|
|
188
|
+
"""
|
|
189
|
+
Set the HTTP response code
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
code: HTTP code (200, 404, 500, etc.)
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
self to chain calls
|
|
196
|
+
"""
|
|
197
|
+
self._response_code = code
|
|
198
|
+
return self
|
|
199
|
+
|
|
200
|
+
def set_body(self, body: Any):
|
|
201
|
+
"""
|
|
202
|
+
Set the body of the response
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
body: Any serializable object to JSON
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
self to chain calls
|
|
209
|
+
"""
|
|
210
|
+
self._response_body = body
|
|
211
|
+
return self
|
|
212
|
+
|
|
213
|
+
def set_header(self, key: str, value: str):
|
|
214
|
+
"""
|
|
215
|
+
Add a header to the response
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
key: Name of the header
|
|
219
|
+
value: Value of the header
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
self to chain calls
|
|
223
|
+
"""
|
|
224
|
+
self._response_headers[key] = value
|
|
225
|
+
return self
|
|
226
|
+
|
|
227
|
+
def set_headers(self, headers: Dict[str, str]):
|
|
228
|
+
"""
|
|
229
|
+
Set multiple headers
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
headers: Dictionary of headers
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
self to chain calls
|
|
236
|
+
"""
|
|
237
|
+
self._response_headers.update(headers)
|
|
238
|
+
return self
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def response(self) -> Dict[str, Any]:
|
|
242
|
+
"""
|
|
243
|
+
Return the complete response object
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
Dict with code, body and headers
|
|
247
|
+
"""
|
|
248
|
+
return {
|
|
249
|
+
'code': self._response_code,
|
|
250
|
+
'body': self._response_body,
|
|
251
|
+
'headers': self._response_headers
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async def validate(self):
|
|
255
|
+
"""
|
|
256
|
+
Hook for data validation
|
|
257
|
+
|
|
258
|
+
Override this method to implement custom validations.
|
|
259
|
+
If the validation fails, raise an exception.
|
|
260
|
+
"""
|
|
261
|
+
pass
|
|
262
|
+
|
|
263
|
+
@abstractmethod
|
|
264
|
+
async def process(self):
|
|
265
|
+
"""
|
|
266
|
+
Main method that must be implemented by each class
|
|
267
|
+
|
|
268
|
+
This is the method where you implement the logic of your API.
|
|
269
|
+
You must use set_body() and optionally set_code() and set_header().
|
|
270
|
+
"""
|
|
271
|
+
raise NotImplementedError("You must implement the process() method")
|
|
272
|
+
|