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.
Files changed (38) hide show
  1. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/PKG-INFO +1 -1
  2. api_foundry_query_engine-0.8.55/api_foundry_query_engine/__init__.py +1 -0
  3. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/adapters/gateway_adapter.py +12 -3
  4. api_foundry_query_engine-0.8.53/api_foundry_query_engine/__init__.py +0 -1
  5. api_foundry_query_engine-0.8.53/api_foundry_query_engine/utils/gateway_operation.py +0 -279
  6. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/.gitignore +0 -0
  7. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/LICENSE +0 -0
  8. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/.pre-commit-config.yaml +0 -0
  9. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/adapters/adapter.py +0 -0
  10. {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
  11. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/adapters/security_adapter.py +0 -0
  12. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/connectors/connection.py +0 -0
  13. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/connectors/connection_factory.py +0 -0
  14. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/connectors/oracle_connector.py +0 -0
  15. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/connectors/postgres_connection.py +0 -0
  16. {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
  17. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/dao/dao.py +0 -0
  18. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/dao/operation_dao.py +0 -0
  19. {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
  20. {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
  21. {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
  22. {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
  23. {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
  24. {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
  25. {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
  26. {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
  27. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/lambda_handler.py +0 -0
  28. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/operation.py +0 -0
  29. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/services/service.py +0 -0
  30. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/services/transactional_service.py +0 -0
  31. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/utils/api_model.py +0 -0
  32. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/utils/app_exception.py +0 -0
  33. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/utils/claims_check.py +0 -0
  34. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/utils/dependency_resolver.py +0 -0
  35. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/utils/logger.py +0 -0
  36. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/utils/reference_resolver.py +0 -0
  37. {api_foundry_query_engine-0.8.53 → api_foundry_query_engine-0.8.55}/api_foundry_query_engine/utils/token_decoder.py +0 -0
  38. {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.53
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
- claims = {}
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)