api-foundry-query-engine 0.8.49__tar.gz → 0.8.51__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.49 → api_foundry_query_engine-0.8.51}/PKG-INFO +1 -1
  2. api_foundry_query_engine-0.8.51/api_foundry_query_engine/__init__.py +1 -0
  3. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/dao/sql_insert_query_handler.py +2 -4
  4. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/dao/sql_restore_query_handler.py +1 -2
  5. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/dao/sql_select_query_handler.py +1 -0
  6. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/dao/sql_subselect_query_handler.py +1 -1
  7. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/lambda_handler.py +1 -1
  8. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/utils/claims_check.py +9 -10
  9. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/utils/token_decoder.py +16 -47
  10. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/pyproject.toml +1 -1
  11. api_foundry_query_engine-0.8.49/api_foundry_query_engine/__init__.py +0 -1
  12. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/.gitignore +0 -0
  13. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/LICENSE +0 -0
  14. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/.pre-commit-config.yaml +0 -0
  15. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/adapters/adapter.py +0 -0
  16. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/adapters/case_change_adapter.py +0 -0
  17. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/adapters/gateway_adapter.py +0 -0
  18. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/adapters/security_adapter.py +0 -0
  19. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/connectors/connection.py +0 -0
  20. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/connectors/connection_factory.py +0 -0
  21. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/connectors/oracle_connector.py +0 -0
  22. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/connectors/postgres_connection.py +0 -0
  23. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/dao/batch_operation_handler.py +0 -0
  24. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/dao/dao.py +0 -0
  25. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/dao/operation_dao.py +0 -0
  26. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/dao/sql_custom_query_handler.py +0 -0
  27. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/dao/sql_delete_query_handler.py +0 -0
  28. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/dao/sql_query_handler.py +0 -0
  29. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/dao/sql_update_query_handler.py +0 -0
  30. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/operation.py +0 -0
  31. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/services/service.py +0 -0
  32. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/services/transactional_service.py +0 -0
  33. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/utils/api_model.py +0 -0
  34. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/utils/app_exception.py +0 -0
  35. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/utils/dependency_resolver.py +0 -0
  36. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/utils/gateway_operation.py +0 -0
  37. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/utils/logger.py +0 -0
  38. {api_foundry_query_engine-0.8.49 → api_foundry_query_engine-0.8.51}/api_foundry_query_engine/utils/reference_resolver.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: api-foundry-query-engine
3
- Version: 0.8.49
3
+ Version: 0.8.51
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.51"
@@ -70,7 +70,7 @@ class SQLInsertSchemaQueryHandler(SQLSchemaQueryHandler):
70
70
  # Get columns to return - use read permissions if available,
71
71
  # otherwise fall back to primary key
72
72
  returning_clause = self._get_returning_clause()
73
-
73
+
74
74
  if not self.concurrency_property:
75
75
  return (
76
76
  f"INSERT INTO {self.table_expression}{self.insert_values} "
@@ -100,9 +100,7 @@ class SQLInsertSchemaQueryHandler(SQLSchemaQueryHandler):
100
100
 
101
101
  if allowed_properties:
102
102
  # Return all accessible columns
103
- columns = [
104
- prop.column_name for prop in allowed_properties.values()
105
- ]
103
+ columns = [prop.column_name for prop in allowed_properties.values()]
106
104
  return f"RETURNING {', '.join(columns)}"
107
105
 
108
106
  # Fallback to primary key if no permissions
@@ -172,8 +172,7 @@ class SQLRestoreSchemaQueryHandler(SQLSchemaQueryHandler):
172
172
  if not self.check_permission():
173
173
  raise ApplicationException(
174
174
  403,
175
- f"Subject is not allowed to restore "
176
- f"{self.schema_object.api_name}",
175
+ f"Subject is not allowed to restore " f"{self.schema_object.api_name}",
177
176
  )
178
177
 
179
178
  if not self.schema_object.has_soft_delete_support():
@@ -11,6 +11,7 @@ from api_foundry_query_engine.utils.api_model import SchemaObjectProperty
11
11
 
12
12
  log = logging.getLogger(__name__)
13
13
 
14
+
14
15
  class SQLSelectSchemaQueryHandler(SQLSchemaQueryHandler):
15
16
  def _soft_delete_where_clause(self) -> str:
16
17
  """
@@ -55,7 +55,7 @@ class SQLSubselectSchemaQueryHandler(SQLSelectSchemaQueryHandler):
55
55
  parent_alias = self.parent_generator.prefix_map[
56
56
  str(self.parent_generator.schema_object.api_name)
57
57
  ]
58
-
58
+
59
59
  sql = (
60
60
  f"SELECT {self.select_list} "
61
61
  + f"FROM {self.relation.child_schema_object.qualified_name} "
@@ -48,7 +48,7 @@ class QueryEngine:
48
48
 
49
49
 
50
50
  @token_decoder()
51
- @claims_check()
51
+ @claims_check(validate_path_scope=False)
52
52
  def handler(event, _):
53
53
  if not hasattr(handler, "engine_config"):
54
54
  log.info("Loading engine config from environment variables")
@@ -418,16 +418,15 @@ def _extract_entity_from_path(event: Dict[str, Any]) -> Optional[str]:
418
418
  # Remove leading slash and split by slash
419
419
  path_parts = path.lstrip("/").split("/")
420
420
 
421
- # Skip API prefixes and return the last non-parameter part
422
- # For paths like "/chinook-api/album" or "/api/v1/customer/123"
423
- for part in reversed(path_parts):
424
- if part and not part.startswith("{") and not part.isdigit():
425
- return part
426
-
427
- # If no suitable part found, return the last non-parameter part
428
- for part in reversed(path_parts):
429
- if part and not part.startswith("{"):
430
- return part
421
+ # Find the first non-parameter part (entity is typically first)
422
+ # For paths like "/comment/123" or "/comment/123/version/1"
423
+ # Skip common API prefixes like "api", "v1", etc.
424
+ api_prefixes = {"api", "v1", "v2", "v3"}
425
+ for part in path_parts:
426
+ if part and not part.startswith("{") and part not in api_prefixes:
427
+ # Skip numeric IDs and known path keywords
428
+ if not part.isdigit() and part not in {"version", "batch"}:
429
+ return part
431
430
 
432
431
  return None
433
432
 
@@ -53,14 +53,10 @@ class LambdaTokenValidator:
53
53
  import boto3
54
54
 
55
55
  self.lambda_client = boto3.client("lambda")
56
- log.debug(
57
- "Lambda client initialized for validator: %s", lambda_arn
58
- )
56
+ log.debug("Lambda client initialized for validator: %s", lambda_arn)
59
57
  except ImportError:
60
58
  log.error("boto3 not available for Lambda token validation")
61
- raise ImportError(
62
- "boto3 is required for Lambda token validation"
63
- )
59
+ raise ImportError("boto3 is required for Lambda token validation")
64
60
 
65
61
  def validate(
66
62
  self, token: str, method_arn: str = "arn:aws:execute-api:*:*:*"
@@ -110,9 +106,7 @@ class LambdaTokenValidator:
110
106
  log.warning("Authorizer returned no context/claims")
111
107
  raise ValueError("Authorizer returned no context/claims")
112
108
 
113
- log.debug(
114
- "Token validated successfully, claims extracted from context"
115
- )
109
+ log.debug("Token validated successfully, claims extracted from context")
116
110
  return context
117
111
 
118
112
  except Exception as e:
@@ -177,9 +171,7 @@ def token_decoder(
177
171
 
178
172
  # Validate configuration at decorator time
179
173
  config_anonymous_role = anonymous_role or os.getenv("ANONYMOUS_ROLE")
180
- config_lambda_arn = lambda_validator_arn or os.getenv(
181
- "TOKEN_VALIDATOR_LAMBDA_ARN"
182
- )
174
+ config_lambda_arn = lambda_validator_arn or os.getenv("TOKEN_VALIDATOR_LAMBDA_ARN")
183
175
 
184
176
  # If no config provided, allow for runtime configuration
185
177
  if (
@@ -206,9 +198,7 @@ def token_decoder(
206
198
 
207
199
  # Check if authorizer already exists (gateway validated)
208
200
  if event.get("requestContext", {}).get("authorizer"):
209
- log.debug(
210
- "Authorizer already exists, skipping JWT processing"
211
- )
201
+ log.debug("Authorizer already exists, skipping JWT processing")
212
202
  return handler(event, context)
213
203
 
214
204
  # Determine configuration sources
@@ -216,26 +206,20 @@ def token_decoder(
216
206
  "TOKEN_VALIDATOR_LAMBDA_ARN"
217
207
  )
218
208
  config_jwks_url = (
219
- jwks_url
220
- or f"{os.getenv('JWKS_HOST')}/.well-known/jwks.json"
209
+ jwks_url or f"{os.getenv('JWKS_HOST')}/.well-known/jwks.json"
221
210
  if os.getenv("JWKS_HOST")
222
211
  else None
223
212
  )
224
213
  config_issuer = issuer or os.getenv("JWT_ISSUER")
225
214
  config_audience = (
226
- audience
227
- or os.getenv("JWT_ALLOWED_AUDIENCES", "").split(",")
215
+ audience or os.getenv("JWT_ALLOWED_AUDIENCES", "").split(",")
228
216
  if os.getenv("JWT_ALLOWED_AUDIENCES")
229
217
  else []
230
218
  )
231
219
  config_algorithms = algorithms or ["RS256"]
232
- config_anonymous_role = anonymous_role or os.getenv(
233
- "ANONYMOUS_ROLE"
234
- )
220
+ config_anonymous_role = anonymous_role or os.getenv("ANONYMOUS_ROLE")
235
221
  log.debug("anonymous_role parameter: %s", anonymous_role)
236
- log.debug(
237
- "ANONYMOUS_ROLE env: %s", os.getenv("ANONYMOUS_ROLE", "")
238
- )
222
+ log.debug("ANONYMOUS_ROLE env: %s", os.getenv("ANONYMOUS_ROLE", ""))
239
223
  log.debug("config_anonymous_role: %s", config_anonymous_role)
240
224
  log.debug("config_lambda_arn: %s", config_lambda_arn)
241
225
 
@@ -243,9 +227,7 @@ def token_decoder(
243
227
  if not config_lambda_arn and not config_jwks_url:
244
228
  log.debug("No validation method configured")
245
229
  if config_anonymous_role:
246
- log.debug(
247
- "Using anonymous role: %s", config_anonymous_role
248
- )
230
+ log.debug("Using anonymous role: %s", config_anonymous_role)
249
231
  if "requestContext" not in event:
250
232
  event["requestContext"] = {}
251
233
  event["requestContext"]["authorizer"] = {
@@ -304,10 +286,7 @@ def token_decoder(
304
286
 
305
287
  if auth_header:
306
288
  auth_parts = auth_header.split(" ")
307
- if (
308
- len(auth_parts) == 2
309
- and auth_parts[0].lower() == "bearer"
310
- ):
289
+ if len(auth_parts) == 2 and auth_parts[0].lower() == "bearer":
311
290
  token = auth_parts[1]
312
291
  log.debug("Token extracted, length: %d", len(token))
313
292
  except Exception:
@@ -319,9 +298,7 @@ def token_decoder(
319
298
  if token and wrapper._lambda_validator:
320
299
  try:
321
300
  log.debug("Attempting Lambda validation")
322
- decoded_token = wrapper._lambda_validator.validate(
323
- token
324
- )
301
+ decoded_token = wrapper._lambda_validator.validate(token)
325
302
  log.debug("Lambda validation successful")
326
303
  except Exception as e:
327
304
  log.warning("Lambda validation failed: %s", e)
@@ -331,9 +308,7 @@ def token_decoder(
331
308
  if token and not decoded_token and wrapper._jwt_decoder:
332
309
  try:
333
310
  log.debug("Attempting JWKS validation")
334
- decoded_token = wrapper._jwt_decoder.decode_token(
335
- token
336
- )
311
+ decoded_token = wrapper._jwt_decoder.decode_token(token)
337
312
  log.debug("JWKS validation successful")
338
313
  except Exception as e:
339
314
  log.warning("JWKS validation failed: %s", e)
@@ -345,24 +320,18 @@ def token_decoder(
345
320
  "Using anonymous role: %s",
346
321
  wrapper._anonymous_role,
347
322
  )
348
- decoded_token = {
349
- "roles": [wrapper._anonymous_role]
350
- }
323
+ decoded_token = {"roles": [wrapper._anonymous_role]}
351
324
  else:
352
325
  log.error("No valid token and no anonymous access")
353
326
  return {
354
327
  "statusCode": 401,
355
328
  "headers": {"Content-Type": "application/json"},
356
- "body": json.dumps(
357
- {"error": "Unauthorized"}
358
- ),
329
+ "body": json.dumps({"error": "Unauthorized"}),
359
330
  }
360
331
 
361
332
  log.debug(
362
333
  "JWT token result: %s",
363
- json.dumps(decoded_token, default=str)
364
- if decoded_token
365
- else "None",
334
+ json.dumps(decoded_token, default=str) if decoded_token else "None",
366
335
  )
367
336
 
368
337
  # Populate requestContext
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "api-foundry-query-engine"
7
- dynamic = ["version"]
7
+ dynamic = ["version"]
8
8
  authors = [
9
9
  { name="Daniel Repik", email="danrepik@icloud.com" },
10
10
  ]
@@ -1 +0,0 @@
1
- __version__ = "0.8.49"