api-foundry-query-engine 0.8.57__tar.gz → 0.8.58__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 (37) hide show
  1. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/PKG-INFO +1 -1
  2. api_foundry_query_engine-0.8.58/api_foundry_query_engine/__init__.py +1 -0
  3. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/adapters/gateway_adapter.py +13 -20
  4. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/lambda_handler.py +1 -1
  5. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/utils/claims_check.py +13 -30
  6. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/pyproject.toml +7 -0
  7. api_foundry_query_engine-0.8.57/api_foundry_query_engine/__init__.py +0 -1
  8. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/.gitignore +0 -0
  9. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/LICENSE +0 -0
  10. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/.pre-commit-config.yaml +0 -0
  11. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/adapters/adapter.py +0 -0
  12. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/adapters/case_change_adapter.py +0 -0
  13. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/adapters/security_adapter.py +0 -0
  14. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/connectors/connection.py +0 -0
  15. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/connectors/connection_factory.py +0 -0
  16. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/connectors/oracle_connector.py +0 -0
  17. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/connectors/postgres_connection.py +0 -0
  18. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/dao/batch_operation_handler.py +0 -0
  19. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/dao/dao.py +0 -0
  20. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/dao/operation_dao.py +0 -0
  21. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/dao/sql_custom_query_handler.py +0 -0
  22. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/dao/sql_delete_query_handler.py +0 -0
  23. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/dao/sql_insert_query_handler.py +0 -0
  24. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/dao/sql_query_handler.py +0 -0
  25. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/dao/sql_restore_query_handler.py +0 -0
  26. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/dao/sql_select_query_handler.py +0 -0
  27. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/dao/sql_subselect_query_handler.py +0 -0
  28. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/dao/sql_update_query_handler.py +0 -0
  29. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/operation.py +0 -0
  30. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/services/service.py +0 -0
  31. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/services/transactional_service.py +0 -0
  32. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/utils/api_model.py +0 -0
  33. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/utils/app_exception.py +0 -0
  34. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/utils/dependency_resolver.py +0 -0
  35. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/utils/logger.py +0 -0
  36. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/utils/reference_resolver.py +0 -0
  37. {api_foundry_query_engine-0.8.57 → api_foundry_query_engine-0.8.58}/api_foundry_query_engine/utils/token_decoder.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: api-foundry-query-engine
3
- Version: 0.8.57
3
+ Version: 0.8.58
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.58"
@@ -4,7 +4,6 @@ from typing import Any, Dict, List, Optional, Tuple
4
4
 
5
5
  from api_foundry_query_engine.adapters.adapter import Adapter
6
6
  from api_foundry_query_engine.operation import Operation
7
- from api_foundry_query_engine.utils.app_exception import ApplicationException
8
7
 
9
8
  log = logging.getLogger(__name__)
10
9
 
@@ -57,14 +56,15 @@ class GatewayAdapter(Adapter):
57
56
 
58
57
  # Handle different authorizer types
59
58
  if isinstance(claims, dict):
60
- # Parse JSON-stringified fields (roles, permissions) from token validator
61
- for key in ['roles', 'permissions']:
59
+ # Parse JSON-stringified fields (roles, permissions)
60
+ # from token validator
61
+ for key in ["roles", "permissions"]:
62
62
  if key in claims and isinstance(claims[key], str):
63
63
  try:
64
64
  claims[key] = json.loads(claims[key])
65
65
  except (json.JSONDecodeError, ValueError):
66
66
  pass # Keep as string if not valid JSON
67
-
67
+
68
68
  # TOKEN authorizer puts claims directly in authorizer object
69
69
  if "sub" in claims or "iss" in claims or "roles" in claims:
70
70
  # Already have JWT claims at top level
@@ -103,19 +103,16 @@ class GatewayAdapter(Adapter):
103
103
  if path_parameters is not None:
104
104
  event_params.update(path_parameters)
105
105
 
106
- queryStringParameters = self._convert_parameters(
107
- event.get("queryStringParameters")
108
- )
109
- if queryStringParameters is not None:
110
- event_params.update(queryStringParameters)
111
-
106
+ query_string_parameters = self._convert_parameters(event.get("queryStringParameters"))
107
+ if query_string_parameters is not None:
108
+ event_params.update(query_string_parameters)
112
109
  query_params, metadata_params = self.split_params(event_params)
113
110
 
114
111
  store_params = {}
115
112
  body = event.get("body")
116
113
  if body is not None and len(body) > 0:
117
114
  store_params = json.loads(body)
118
- \
115
+
119
116
  return Operation(
120
117
  entity=entity,
121
118
  action=action,
@@ -125,9 +122,7 @@ class GatewayAdapter(Adapter):
125
122
  claims=claims,
126
123
  )
127
124
 
128
- def _convert_parameters(
129
- self, parameters: Optional[Dict[str, Any]]
130
- ) -> Optional[Dict[str, Any]]:
125
+ def _convert_parameters(self, parameters: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
131
126
  """
132
127
  Convert parameters to appropriate types.
133
128
 
@@ -151,9 +146,7 @@ class GatewayAdapter(Adapter):
151
146
  result[parameter] = value
152
147
  return result
153
148
 
154
- def split_params(
155
- self, parameters: Dict[str, Any]
156
- ) -> Tuple[Dict[str, Any], Dict[str, Any]]:
149
+ def split_params(self, parameters: Dict[str, Any]) -> Tuple[Dict[str, Any], Dict[str, Any]]:
157
150
  """
158
151
  Split a dictionary into two dictionaries based on keys.
159
152
 
@@ -174,8 +167,8 @@ class GatewayAdapter(Adapter):
174
167
  else:
175
168
  query_params[key] = value
176
169
 
177
- log.info(f"split_params input: {parameters}")
178
- log.info(f"query_params: {query_params}")
179
- log.info(f"metadata_params: {metadata_params}")
170
+ log.info("split_params input: %s", parameters)
171
+ log.info("query_params: %s", query_params)
172
+ log.info("metadata_params: %s", metadata_params)
180
173
 
181
174
  return query_params, metadata_params
@@ -48,7 +48,7 @@ class QueryEngine:
48
48
 
49
49
 
50
50
  @token_decoder()
51
- @claims_check(validate_path_scope=False)
51
+ @claims_check(validate_path_scope=False, validate_scope_format=False)
52
52
  def handler(event, _):
53
53
  if not hasattr(handler, "engine_config"):
54
54
  log.info("Loading engine config from environment variables")
@@ -83,17 +83,13 @@ def claims_check(
83
83
  config_require_authentication = require_authentication or os.getenv(
84
84
  "REQUIRE_AUTHENTICATION", ""
85
85
  ).lower() in ("true", "1", "yes")
86
- log.debug(
87
- "config_require_authentication: %s", config_require_authentication
88
- )
86
+ log.debug("config_require_authentication: %s", config_require_authentication)
89
87
 
90
88
  # Extract claims from the event (set by token_decoder)
91
89
  claims = _extract_claims(event)
92
90
  if not claims:
93
91
  if config_require_authentication:
94
- raise ApplicationException(
95
- status_code=401, message="No authentication claims found"
96
- )
92
+ raise ApplicationException(status_code=401, message="No authentication claims found")
97
93
  else:
98
94
  log.debug("Found claims: %s", list(claims.keys()))
99
95
 
@@ -111,9 +107,7 @@ def claims_check(
111
107
 
112
108
  # Check specific scopes if required
113
109
  if required_scopes:
114
- _validate_required_scopes(
115
- claims, required_scopes, event, operation_type, entity_name
116
- )
110
+ _validate_required_scopes(claims, required_scopes, event, operation_type, entity_name)
117
111
 
118
112
  # Check specific permissions if required
119
113
  if required_permissions:
@@ -157,9 +151,7 @@ def _extract_claims(event: Dict[str, Any]) -> Optional[Dict[str, Any]]:
157
151
 
158
152
  # Check direct authorizer context (most common for TOKEN authorizer)
159
153
  claim_keys = ["sub", "scope", "permissions", "roles"]
160
- if isinstance(authorizer, dict) and any(
161
- key in authorizer for key in claim_keys
162
- ):
154
+ if isinstance(authorizer, dict) and any(key in authorizer for key in claim_keys):
163
155
  log.debug("Found claims in requestContext.authorizer")
164
156
  return authorizer
165
157
 
@@ -202,12 +194,12 @@ def _validate_scope_format(claims: Dict[str, Any]) -> None:
202
194
  elif not isinstance(scopes, list):
203
195
  scopes = []
204
196
 
205
- # Allow empty scopes if permissions exist
197
+ # Scopes and permissions are now optional - they may be present but are not required
198
+ # The x-af-permissions in the API spec handles role-based access control
206
199
  permissions = claims.get("permissions", [])
207
200
  if not scopes and not permissions:
208
- raise ApplicationException(
209
- status_code=403, message="No scopes or permissions found in token"
210
- )
201
+ log.debug("No scopes or permissions found in token - relying on role-based permissions")
202
+ return
211
203
 
212
204
  # Validate scope format: should be operation:resource or operation:*
213
205
  for scope in scopes:
@@ -238,9 +230,7 @@ def _validate_min_scope_level(claims: Dict[str, Any], min_level: str) -> None:
238
230
  user_max_level = max(user_max_level, scope_value)
239
231
 
240
232
  if user_max_level < min_level_value:
241
- raise ApplicationException(
242
- status_code=403, message=f"Insufficient scope level. Required: {min_level}"
243
- )
233
+ raise ApplicationException(status_code=403, message=f"Insufficient scope level. Required: {min_level}")
244
234
 
245
235
 
246
236
  def _validate_path_scope(
@@ -293,8 +283,7 @@ def _validate_path_scope(
293
283
  if not has_scope and not has_permission:
294
284
  raise ApplicationException(
295
285
  status_code=403,
296
- message=f"Access denied. Required scope: {required_scope} "
297
- f"or permission: {required_permission}",
286
+ message=f"Access denied. Required scope: {required_scope} " f"or permission: {required_permission}",
298
287
  )
299
288
 
300
289
  log.debug("Path scope validation passed for %s", required_scope)
@@ -321,14 +310,10 @@ def _validate_required_scopes(
321
310
  # Check each required scope
322
311
  for required_scope in required_scopes:
323
312
  if not _scope_matches(user_scopes, required_scope, operation, entity):
324
- raise ApplicationException(
325
- status_code=403, message=f"Required scope not found: {required_scope}"
326
- )
313
+ raise ApplicationException(status_code=403, message=f"Required scope not found: {required_scope}")
327
314
 
328
315
 
329
- def _validate_required_permissions(
330
- claims: Dict[str, Any], required_permissions: list
331
- ) -> None:
316
+ def _validate_required_permissions(claims: Dict[str, Any], required_permissions: list) -> None:
332
317
  """Validate that user has required permissions."""
333
318
  user_permissions = claims.get("permissions", [])
334
319
  if isinstance(user_permissions, str):
@@ -431,9 +416,7 @@ def _extract_entity_from_path(event: Dict[str, Any]) -> Optional[str]:
431
416
  return None
432
417
 
433
418
 
434
- def _scope_matches(
435
- user_scopes: list, required_scope: str, operation: str, entity: str
436
- ) -> bool:
419
+ def _scope_matches(user_scopes: list, required_scope: str, operation: str, entity: str) -> bool:
437
420
  """Check if user scopes match the required scope."""
438
421
  _ = entity # Silence unused warning
439
422
  # Check for exact match
@@ -90,3 +90,10 @@ serve = "mkdocs serve --dev-addr localhost:8000"
90
90
 
91
91
  [tool.hatch.publish.index]
92
92
  disable = true # Disable publishing by default for safety
93
+
94
+ # Linting / formatting configuration
95
+ [tool.ruff]
96
+ line-length = 120
97
+
98
+ [tool.black]
99
+ line-length = 120
@@ -1 +0,0 @@
1
- __version__ = "0.8.57"