api-foundry-query-engine 0.8.53__tar.gz → 0.8.55__tar.gz
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.
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/PKG-INFO +1 -1
- api_foundry_query_engine-0.8.55/api_foundry_query_engine/__init__.py +1 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/adapters/gateway_adapter.py +12 -3
- api_foundry_query_engine-0.8.53/api_foundry_query_engine/__init__.py +0 -1
- api_foundry_query_engine-0.8.53/api_foundry_query_engine/utils/gateway_operation.py +0 -279
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/.gitignore +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/LICENSE +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/.pre-commit-config.yaml +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/adapters/adapter.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/adapters/case_change_adapter.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/adapters/security_adapter.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/connectors/connection.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/connectors/connection_factory.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/connectors/oracle_connector.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/connectors/postgres_connection.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/dao/batch_operation_handler.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/dao/dao.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/dao/operation_dao.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/dao/sql_custom_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/dao/sql_delete_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/dao/sql_insert_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/dao/sql_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/dao/sql_restore_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/dao/sql_select_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/dao/sql_subselect_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/dao/sql_update_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/lambda_handler.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/operation.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/services/service.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/services/transactional_service.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/utils/api_model.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/utils/app_exception.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/utils/claims_check.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/utils/dependency_resolver.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/utils/logger.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/utils/reference_resolver.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/utils/token_decoder.py +0 -0
- {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: api-foundry-query-engine
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.55
|
|
4
4
|
Summary: The AWS lambda service handler use by the `api_foundry` project is a powerful tool designed to automate the deployment of REST APIs on AWS using Lambda services to access and interact with relational databases (RDBMS). This project leverages the OpenAPI specification to define and manage the APIs
|
|
5
5
|
Project-URL: Documentation, https://github.com/DanRepik/api-foundry
|
|
6
6
|
Project-URL: Source, https://github.com/DanRepik/api-foundry
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.8.55"
|
|
@@ -57,8 +57,16 @@ class GatewayAdapter(Adapter):
|
|
|
57
57
|
|
|
58
58
|
# Handle different authorizer types
|
|
59
59
|
if isinstance(claims, dict):
|
|
60
|
+
# Parse JSON-stringified fields (roles, permissions) from token validator
|
|
61
|
+
for key in ['roles', 'permissions']:
|
|
62
|
+
if key in claims and isinstance(claims[key], str):
|
|
63
|
+
try:
|
|
64
|
+
claims[key] = json.loads(claims[key])
|
|
65
|
+
except (json.JSONDecodeError, ValueError):
|
|
66
|
+
pass # Keep as string if not valid JSON
|
|
67
|
+
|
|
60
68
|
# TOKEN authorizer puts claims directly in authorizer object
|
|
61
|
-
if "sub" in claims or "iss" in claims:
|
|
69
|
+
if "sub" in claims or "iss" in claims or "roles" in claims:
|
|
62
70
|
# Already have JWT claims at top level
|
|
63
71
|
pass
|
|
64
72
|
elif "claims" in claims:
|
|
@@ -71,8 +79,8 @@ class GatewayAdapter(Adapter):
|
|
|
71
79
|
# Lambda authorizer fallback
|
|
72
80
|
claims = claims["lambda"]
|
|
73
81
|
else:
|
|
74
|
-
# Empty or unknown format
|
|
75
|
-
|
|
82
|
+
# Empty or unknown format - keep what we have
|
|
83
|
+
pass
|
|
76
84
|
else:
|
|
77
85
|
# Non-dict authorizer context
|
|
78
86
|
claims = {}
|
|
@@ -108,6 +116,7 @@ class GatewayAdapter(Adapter):
|
|
|
108
116
|
if body is not None and len(body) > 0:
|
|
109
117
|
store_params = json.loads(body)
|
|
110
118
|
scope_str = claims.get("scope")
|
|
119
|
+
log.info(f"scope_str: {scope_str}")
|
|
111
120
|
|
|
112
121
|
# Enforce OAuth scopes (simulating API Gateway authorizer behavior)
|
|
113
122
|
# Required scope pattern: read|write|delete:<entity>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.8.53"
|
|
@@ -1,279 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Gateway Operation Decorator for Lambda Functions
|
|
3
|
-
|
|
4
|
-
This decorator handles the marshalling and unmarshalling of API Gateway events
|
|
5
|
-
into Operation objects, similar to what GatewayAdapter does but as a decorator.
|
|
6
|
-
It extracts the operation details and adds them to the event for downstream processing.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import functools
|
|
10
|
-
import json
|
|
11
|
-
import logging
|
|
12
|
-
from typing import Any, Dict, List, Optional, Tuple, Callable
|
|
13
|
-
|
|
14
|
-
from api_foundry_query_engine.operation import Operation
|
|
15
|
-
from api_foundry_query_engine.utils.app_exception import ApplicationException
|
|
16
|
-
|
|
17
|
-
log = logging.getLogger(__name__)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def gateway_operation(validate_scopes: bool = True, auto_marshal_response: bool = True):
|
|
21
|
-
"""
|
|
22
|
-
Decorator to handle API Gateway event marshalling and unmarshalling.
|
|
23
|
-
|
|
24
|
-
This decorator:
|
|
25
|
-
1. Unmarshals API Gateway events into Operation objects
|
|
26
|
-
2. Adds the Operation to the event for downstream processing
|
|
27
|
-
3. Validates OAuth scopes based on the operation
|
|
28
|
-
4. Marshals response data back to API Gateway format
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
validate_scopes: Whether to validate OAuth scopes (default: True)
|
|
32
|
-
auto_marshal_response: Whether to automatically format response (default: True)
|
|
33
|
-
|
|
34
|
-
Returns:
|
|
35
|
-
Decorated function that processes API Gateway events
|
|
36
|
-
|
|
37
|
-
Example:
|
|
38
|
-
@token_decoder()
|
|
39
|
-
@claims_check()
|
|
40
|
-
@gateway_operation()
|
|
41
|
-
def handler(event, context):
|
|
42
|
-
# event now contains 'operation' key with Operation object
|
|
43
|
-
operation = event['operation']
|
|
44
|
-
return {"data": [{"id": 1, "name": "Test"}]}
|
|
45
|
-
"""
|
|
46
|
-
|
|
47
|
-
def decorator(func: Callable) -> Callable:
|
|
48
|
-
@functools.wraps(func)
|
|
49
|
-
def wrapper(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
|
|
50
|
-
try:
|
|
51
|
-
log.debug("Gateway operation decorator starting")
|
|
52
|
-
|
|
53
|
-
# Unmarshal API Gateway event into Operation
|
|
54
|
-
operation = _unmarshal_gateway_event(event, validate_scopes)
|
|
55
|
-
|
|
56
|
-
# Add operation to event for downstream processing
|
|
57
|
-
event["operation"] = operation
|
|
58
|
-
|
|
59
|
-
log.debug(
|
|
60
|
-
"Created operation: entity=%s, action=%s",
|
|
61
|
-
operation.entity,
|
|
62
|
-
operation.action,
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
# Execute the original function
|
|
66
|
-
result = func(event, context)
|
|
67
|
-
|
|
68
|
-
# Auto-marshal response if requested
|
|
69
|
-
if auto_marshal_response and isinstance(result, (list, dict)):
|
|
70
|
-
return _marshal_response(result)
|
|
71
|
-
else:
|
|
72
|
-
return result
|
|
73
|
-
|
|
74
|
-
except ApplicationException:
|
|
75
|
-
raise
|
|
76
|
-
except Exception as e:
|
|
77
|
-
log.error("Gateway operation error: %s", str(e))
|
|
78
|
-
raise ApplicationException(
|
|
79
|
-
status_code=500,
|
|
80
|
-
message="Internal server error during operation processing",
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
return wrapper
|
|
84
|
-
|
|
85
|
-
return decorator
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def _unmarshal_gateway_event(event: Dict[str, Any], validate_scopes: bool) -> Operation:
|
|
89
|
-
"""
|
|
90
|
-
Unmarshal API Gateway event into Operation object.
|
|
91
|
-
|
|
92
|
-
This replicates the logic from GatewayAdapter.unmarshal() method.
|
|
93
|
-
"""
|
|
94
|
-
# Extract entity from resource path
|
|
95
|
-
resource = event.get("resource")
|
|
96
|
-
if resource is not None and "/" in resource:
|
|
97
|
-
parts = resource.split("/")
|
|
98
|
-
entity = parts[1] if len(parts) > 1 else None
|
|
99
|
-
else:
|
|
100
|
-
entity = None
|
|
101
|
-
|
|
102
|
-
# Map HTTP method to action
|
|
103
|
-
method = str(event.get("httpMethod", "")).upper()
|
|
104
|
-
actions_map = {
|
|
105
|
-
"GET": "read",
|
|
106
|
-
"POST": "create",
|
|
107
|
-
"PUT": "update",
|
|
108
|
-
"DELETE": "delete",
|
|
109
|
-
}
|
|
110
|
-
action = actions_map.get(method, "read")
|
|
111
|
-
|
|
112
|
-
# Collect parameters from path and query string
|
|
113
|
-
event_params = {}
|
|
114
|
-
|
|
115
|
-
path_parameters = _convert_parameters(event.get("pathParameters"))
|
|
116
|
-
if path_parameters is not None:
|
|
117
|
-
event_params.update(path_parameters)
|
|
118
|
-
|
|
119
|
-
query_string_parameters = _convert_parameters(event.get("queryStringParameters"))
|
|
120
|
-
if query_string_parameters is not None:
|
|
121
|
-
event_params.update(query_string_parameters)
|
|
122
|
-
|
|
123
|
-
query_params, metadata_params = _split_params(event_params)
|
|
124
|
-
|
|
125
|
-
# Extract store params from request body
|
|
126
|
-
store_params = {}
|
|
127
|
-
body = event.get("body")
|
|
128
|
-
if body is not None and len(body) > 0:
|
|
129
|
-
store_params = json.loads(body)
|
|
130
|
-
|
|
131
|
-
# Extract claims from authorizer context
|
|
132
|
-
authorizer_info = event.get("requestContext", {}).get("authorizer", {})
|
|
133
|
-
claims = authorizer_info.get("claims", {})
|
|
134
|
-
scope_str = claims.get("scope", "")
|
|
135
|
-
|
|
136
|
-
# Validate OAuth scopes if requested
|
|
137
|
-
if validate_scopes and entity and scope_str:
|
|
138
|
-
_validate_oauth_scopes(method, entity, scope_str)
|
|
139
|
-
|
|
140
|
-
return Operation(
|
|
141
|
-
entity=entity,
|
|
142
|
-
action=action,
|
|
143
|
-
store_params=store_params,
|
|
144
|
-
query_params=query_params,
|
|
145
|
-
metadata_params=metadata_params,
|
|
146
|
-
claims=claims,
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def _decode_json_array(raw_value: Any) -> List[Any]:
|
|
151
|
-
"""Decode JSON-encoded arrays from OAuth context."""
|
|
152
|
-
if isinstance(raw_value, str):
|
|
153
|
-
try:
|
|
154
|
-
return json.loads(raw_value)
|
|
155
|
-
except (json.JSONDecodeError, TypeError):
|
|
156
|
-
return []
|
|
157
|
-
else:
|
|
158
|
-
return raw_value if isinstance(raw_value, list) else []
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
def _validate_oauth_scopes(method: str, entity: str, scope_str: str) -> None:
|
|
162
|
-
"""
|
|
163
|
-
Validate OAuth scopes based on the operation.
|
|
164
|
-
|
|
165
|
-
Enforces scope pattern: read|write|delete:<entity>
|
|
166
|
-
"""
|
|
167
|
-
required_action = {
|
|
168
|
-
"GET": "read",
|
|
169
|
-
"POST": "write",
|
|
170
|
-
"PUT": "write",
|
|
171
|
-
"PATCH": "write",
|
|
172
|
-
"DELETE": "delete",
|
|
173
|
-
}.get(method, "read")
|
|
174
|
-
|
|
175
|
-
required_scope = f"{required_action}:{entity}"
|
|
176
|
-
token_scopes = set(str(scope_str).split())
|
|
177
|
-
|
|
178
|
-
def _has_scope(required: str) -> bool:
|
|
179
|
-
return (
|
|
180
|
-
required in token_scopes
|
|
181
|
-
or f"{required_action}:*" in token_scopes
|
|
182
|
-
or "*" in token_scopes
|
|
183
|
-
or "*:*" in token_scopes
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
if not _has_scope(required_scope):
|
|
187
|
-
raise ApplicationException(
|
|
188
|
-
401, f"insufficient_scope: required_scope={required_scope}"
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
def _convert_parameters(
|
|
193
|
-
parameters: Optional[Dict[str, Any]]
|
|
194
|
-
) -> Optional[Dict[str, Any]]:
|
|
195
|
-
"""
|
|
196
|
-
Convert parameters to appropriate types.
|
|
197
|
-
|
|
198
|
-
Tries to convert strings to int, then float, falls back to string.
|
|
199
|
-
"""
|
|
200
|
-
if parameters is None:
|
|
201
|
-
return None
|
|
202
|
-
|
|
203
|
-
result = {}
|
|
204
|
-
for parameter, value in parameters.items():
|
|
205
|
-
try:
|
|
206
|
-
result[parameter] = int(value)
|
|
207
|
-
except ValueError:
|
|
208
|
-
try:
|
|
209
|
-
result[parameter] = float(value)
|
|
210
|
-
except ValueError:
|
|
211
|
-
result[parameter] = value
|
|
212
|
-
return result
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
def _split_params(parameters: Dict[str, Any]) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
|
216
|
-
"""
|
|
217
|
-
Split parameters into query_params and metadata_params.
|
|
218
|
-
|
|
219
|
-
Metadata parameters start with '__'.
|
|
220
|
-
"""
|
|
221
|
-
query_params = {}
|
|
222
|
-
metadata_params = {}
|
|
223
|
-
|
|
224
|
-
for key, value in parameters.items():
|
|
225
|
-
if key.startswith("__"):
|
|
226
|
-
metadata_params[key] = value
|
|
227
|
-
else:
|
|
228
|
-
query_params[key] = value
|
|
229
|
-
|
|
230
|
-
return query_params, metadata_params
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
def _marshal_response(result: Any) -> Dict[str, Any]:
|
|
234
|
-
"""
|
|
235
|
-
Marshal response data into API Gateway format.
|
|
236
|
-
|
|
237
|
-
Args:
|
|
238
|
-
result: The response data (list or dict)
|
|
239
|
-
|
|
240
|
-
Returns:
|
|
241
|
-
API Gateway compatible response
|
|
242
|
-
"""
|
|
243
|
-
try:
|
|
244
|
-
return {
|
|
245
|
-
"isBase64Encoded": False,
|
|
246
|
-
"statusCode": 200,
|
|
247
|
-
"headers": {"Content-Type": "application/json"},
|
|
248
|
-
"body": json.dumps(result) if result is not None else json.dumps([]),
|
|
249
|
-
}
|
|
250
|
-
except (TypeError, ValueError) as e:
|
|
251
|
-
log.error("Failed to marshal response: %s", e)
|
|
252
|
-
raise ApplicationException(
|
|
253
|
-
status_code=500, message="Failed to serialize response data"
|
|
254
|
-
)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
# Convenience decorators for specific use cases
|
|
258
|
-
def gateway_read_operation(validate_scopes: bool = True):
|
|
259
|
-
"""Convenience decorator for read operations."""
|
|
260
|
-
return gateway_operation(
|
|
261
|
-
validate_scopes=validate_scopes, auto_marshal_response=True
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
def gateway_write_operation(validate_scopes: bool = True):
|
|
266
|
-
"""Convenience decorator for write operations."""
|
|
267
|
-
return gateway_operation(
|
|
268
|
-
validate_scopes=validate_scopes, auto_marshal_response=True
|
|
269
|
-
)
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
def gateway_operation_no_validation():
|
|
273
|
-
"""Decorator that skips scope validation."""
|
|
274
|
-
return gateway_operation(validate_scopes=False, auto_marshal_response=True)
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
def gateway_operation_raw_response():
|
|
278
|
-
"""Decorator that doesn't auto-marshal the response."""
|
|
279
|
-
return gateway_operation(validate_scopes=True, auto_marshal_response=False)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|