api-foundry-query-engine 0.8.30__tar.gz → 0.8.32__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 (33) hide show
  1. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/PKG-INFO +2 -1
  2. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/adapters/adapter.py +1 -1
  3. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/adapters/case_change_adapter.py +1 -1
  4. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/adapters/gateway_adapter.py +23 -35
  5. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/adapters/security_adapter.py +2 -2
  6. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/connectors/connection_factory.py +6 -6
  7. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/connectors/oracle_connector.py +1 -1
  8. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/connectors/postgres_connection.py +1 -1
  9. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/dao/sql_custom_query_handler.py +1 -1
  10. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/dao/sql_delete_query_handler.py +21 -5
  11. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/dao/sql_insert_query_handler.py +1 -1
  12. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/dao/sql_query_handler.py +68 -20
  13. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/lambda_handler.py +7 -6
  14. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/operation.py +31 -14
  15. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/services/service.py +12 -6
  16. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/services/transactional_service.py +2 -2
  17. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/utils/api_model.py +2 -2
  18. api_foundry_query_engine-0.8.32/api_foundry_query_engine/utils/claims_check.py +471 -0
  19. api_foundry_query_engine-0.8.32/api_foundry_query_engine/utils/gateway_operation.py +279 -0
  20. api_foundry_query_engine-0.8.32/api_foundry_query_engine/utils/token_decoder.py +415 -0
  21. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/pyproject.toml +38 -2
  22. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/.gitignore +0 -0
  23. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/LICENSE +0 -0
  24. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/.pre-commit-config.yaml +0 -0
  25. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/__init__.py +0 -0
  26. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/connectors/connection.py +0 -0
  27. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/dao/dao.py +0 -0
  28. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/dao/operation_dao.py +0 -0
  29. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/dao/sql_select_query_handler.py +0 -0
  30. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/dao/sql_subselect_query_handler.py +0 -0
  31. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/dao/sql_update_query_handler.py +0 -0
  32. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/utils/app_exception.py +0 -0
  33. {api_foundry_query_engine-0.8.30 → api_foundry_query_engine-0.8.32}/api_foundry_query_engine/utils/logger.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: api-foundry-query-engine
3
- Version: 0.8.30
3
+ Version: 0.8.32
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
@@ -18,3 +18,4 @@ Requires-Dist: fixture-foundry>=0.1.0; extra == 'dev'
18
18
  Requires-Dist: isort>=5.12.0; extra == 'dev'
19
19
  Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
20
20
  Requires-Dist: pytest>=7.0; extra == 'dev'
21
+ Requires-Dist: simple-oauth-server; extra == 'dev'
@@ -68,6 +68,6 @@ class Adapter(metaclass=abc.ABCMeta):
68
68
  operation = self.unmarshal(event)
69
69
 
70
70
  result = self.service.execute(operation)
71
- log.debug(f"adapter result: {result}")
71
+ log.debug("adapter result: %s", result)
72
72
 
73
73
  return self.marshal(result)
@@ -30,7 +30,7 @@ class CaseChangeAdapter(Adapter):
30
30
  or self.__check_camel_case(operation.store_params)
31
31
  or self.__check_camel_case(operation.query_params)
32
32
  )
33
- log.info(f"camel_case: {self.camel_case}")
33
+ log.info("camel_case: %s", self.camel_case)
34
34
 
35
35
  if self.camel_case:
36
36
  return Operation(
@@ -65,38 +65,30 @@ class GatewayAdapter(Adapter):
65
65
  if body is not None and len(body) > 0:
66
66
  store_params = json.loads(body)
67
67
 
68
- authorizer_info = event.get("requestContext", {}).get("authorizer", {})
69
- claims = authorizer_info.get("claims", {})
70
-
71
- # Decode JSON-encoded arrays from OAuth context
72
- roles_raw = claims.get("roles", [])
73
- if isinstance(roles_raw, str):
74
- try:
75
- roles = json.loads(roles_raw)
76
- except (json.JSONDecodeError, TypeError):
77
- roles = []
78
- else:
79
- roles = roles_raw if isinstance(roles_raw, list) else []
80
-
81
- groups_raw = claims.get("groups", [])
82
- if isinstance(groups_raw, str):
83
- try:
84
- groups = json.loads(groups_raw)
85
- except (json.JSONDecodeError, TypeError):
86
- groups = []
87
- else:
88
- groups = groups_raw if isinstance(groups_raw, list) else []
89
-
90
- permissions_raw = claims.get("permissions", [])
91
- if isinstance(permissions_raw, str):
92
- try:
93
- permissions = json.loads(permissions_raw)
94
- except (json.JSONDecodeError, TypeError):
95
- permissions = []
68
+ # Extract JWT claims from API Gateway event
69
+ claims = event.get("requestContext", {}).get("authorizer", {})
70
+
71
+ # Handle different authorizer types
72
+ if isinstance(claims, dict):
73
+ # TOKEN authorizer puts claims directly in authorizer object
74
+ if "sub" in claims or "iss" in claims:
75
+ # Already have JWT claims at top level
76
+ pass
77
+ elif "claims" in claims:
78
+ # Some configurations nest claims
79
+ claims = claims["claims"]
80
+ elif "iam" in claims:
81
+ # IAM authorizer fallback
82
+ claims = claims["iam"]
83
+ elif "lambda" in claims:
84
+ # Lambda authorizer fallback
85
+ claims = claims["lambda"]
86
+ else:
87
+ # Empty or unknown format
88
+ claims = {}
96
89
  else:
97
- permissions = permissions_raw if isinstance(permissions_raw, list) else []
98
-
99
- subject = claims.get("sub")
90
+ # Non-dict authorizer context
91
+ claims = {}
100
92
  scope_str = claims.get("scope")
101
93
 
102
94
  # Enforce OAuth scopes (simulating API Gateway authorizer behavior)
@@ -132,10 +124,6 @@ class GatewayAdapter(Adapter):
132
124
  store_params=store_params,
133
125
  query_params=query_params,
134
126
  metadata_params=metadata_params,
135
- roles=roles,
136
- groups=groups,
137
- subject=subject,
138
- permissions=permissions,
139
127
  claims=claims,
140
128
  )
141
129
 
@@ -99,8 +99,8 @@ class SecurityAdapter(Adapter):
99
99
  try:
100
100
  operation = self.unmarshal(event)
101
101
  result = self.service.execute(operation)
102
- log.debug(f"adapter result: {result}")
102
+ log.debug("adapter result: %s", result)
103
103
  return self.marshal(result)
104
104
  except PermissionError as e:
105
- log.error(f"Permission error: {e}")
105
+ log.error("Permission error: %s", e)
106
106
  return {"error": str(e), "status": "permission_denied"}
@@ -32,7 +32,7 @@ class ConnectionFactory:
32
32
  """
33
33
 
34
34
  # Get the secret name based on the engine and database from the secrets map
35
- log.info(f"database: {database}")
35
+ log.info("database: %s", database)
36
36
  db_config = self.db_config_map.get(database)
37
37
  if not db_config:
38
38
  # Use config dict for secrets
@@ -40,7 +40,7 @@ class ConnectionFactory:
40
40
  if isinstance(secrets_map, str):
41
41
  secrets_map = json.loads(secrets_map)
42
42
  secret_name = secrets_map.get(database)
43
- log.debug(f"secret_name: {secret_name}")
43
+ log.debug("secret_name: %s", secret_name)
44
44
 
45
45
  if secret_name:
46
46
  db_config = self.__get_secret(secret_name)
@@ -79,7 +79,7 @@ class ConnectionFactory:
79
79
  sts_client = boto3.client("sts", endpoint_url=endpoint_url)
80
80
 
81
81
  secret_account_id = self.config.get("SECRET_ACCOUNT_ID", None)
82
- log.debug(f"secret_account_id: {secret_account_id}")
82
+ log.debug("secret_account_id: %s", secret_account_id)
83
83
 
84
84
  if secret_account_id:
85
85
  # If a secret account ID is provided, assume a role in that account
@@ -100,16 +100,16 @@ class ConnectionFactory:
100
100
  )
101
101
  else:
102
102
  # If no secret account ID is provided, use the default account
103
- log.info(f"endpoint_url: {endpoint_url}")
103
+ log.info("endpoint_url: %s", endpoint_url)
104
104
  secretsmanager = boto3.client(
105
105
  "secretsmanager",
106
106
  endpoint_url=endpoint_url,
107
107
  )
108
108
 
109
109
  # Get the secret value from AWS Secrets Manager
110
- log.info(f"db_secret_name: {db_secret_name}")
110
+ log.info("db_secret_name: %s", db_secret_name)
111
111
  db_secret = secretsmanager.get_secret_value(SecretId=db_secret_name)
112
- log.debug(f"loading secret name: {db_secret}")
112
+ log.debug("loading secret name: %s", db_secret)
113
113
 
114
114
  # Return the parsed JSON secret string
115
115
  return json.loads(db_secret.get("SecretString"))
@@ -15,7 +15,7 @@ class OracleConnnector(Connector):
15
15
  def execute(self, cursor, sql: str, parameters: dict):
16
16
  from oracledb import Error, IntegrityError, ProgrammingError
17
17
 
18
- log.debug(f"sql: {sql}, parameters: {parameters}")
18
+ log.debug("sql: %s, parameters: %s", sql, parameters)
19
19
  try:
20
20
  cursor.execute(sql, parameters)
21
21
  except IntegrityError as err:
@@ -27,7 +27,7 @@ class PostgresCursor(Cursor):
27
27
  """
28
28
  from psycopg2 import Error, IntegrityError, ProgrammingError
29
29
 
30
- log.info(f"sql: {sql}")
30
+ log.info("sql: %s", sql)
31
31
 
32
32
  try:
33
33
  # Execute the SQL statement with parameters
@@ -39,7 +39,7 @@ class SQLCustomQueryHandler(SQLQueryHandler):
39
39
  self._selection_results = self.check_permissions(
40
40
  "read", self.path_operation.permissions, self.path_operation.outputs
41
41
  )
42
- log.debug(f"selection_results: {self._selection_results}")
42
+ log.debug("selection_results: %s", self._selection_results)
43
43
  return self._selection_results
44
44
 
45
45
  def _compile(self):
@@ -29,12 +29,28 @@ class SQLDeleteSchemaQueryHandler(SQLSchemaQueryHandler):
29
29
  if not self.schema_object.permissions:
30
30
  return True
31
31
 
32
+ # Use the proper provider-action-role structure
33
+ provider_permissions = self.schema_object.permissions.get("default", {})
34
+ delete_permissions = provider_permissions.get("delete", {})
35
+
32
36
  for role in self.operation.roles:
33
- role_permissions = self.schema_object.permissions.get(role, {})
34
- log.info(f"role: {role}, role_permissions: {role_permissions}")
35
- if len(role_permissions) == 0:
36
- continue
37
- allowed = role_permissions.get("delete", False)
37
+ role_permissions = delete_permissions.get(role)
38
+ log.info("role: %s, role_permissions: %s", role, role_permissions)
39
+
40
+ # If no permissions found for role, check wildcard "*"
41
+ if role_permissions is None:
42
+ role_permissions = delete_permissions.get("*")
43
+ log.info("Fallback to wildcard role '*': %s", role_permissions)
44
+ if role_permissions is None:
45
+ continue
46
+
47
+ # Handle both boolean and object permission formats
48
+ if isinstance(role_permissions, bool):
49
+ allowed = role_permissions
50
+ else:
51
+ # For complex permission objects, existence means allowed
52
+ allowed = True
53
+
38
54
  if allowed:
39
55
  return True
40
56
 
@@ -78,7 +78,7 @@ class SQLInsertSchemaQueryHandler(SQLSchemaQueryHandler):
78
78
  for k, v in self.schema_object.properties.items()
79
79
  if k in allowed_property_names
80
80
  }
81
- log.info(f"allowed properties: {allowed_properties}")
81
+ log.info("allowed properties: %s", allowed_properties)
82
82
 
83
83
  import json
84
84
 
@@ -187,29 +187,77 @@ class SQLQueryHandler:
187
187
  return properties
188
188
 
189
189
  allowed_properties = {}
190
+ log.info(
191
+ "Input properties keys: %s",
192
+ list(properties.keys()) if properties else "None",
193
+ )
194
+ log.info("Operation roles: %s", self.operation.roles)
195
+
196
+ # Permissions structure: {provider: {action: {role: rule}}}
197
+ # Use 'default' as the standard provider
198
+ provider_permissions = permissions.get("default", {})
199
+ action_permissions = provider_permissions.get(permission_type, {})
200
+
201
+ log.info(
202
+ "Extracted action permissions for %s: %s",
203
+ permission_type,
204
+ action_permissions,
205
+ )
190
206
 
191
207
  for role in self.operation.roles:
192
- role_permissions = permissions.get(role, {})
193
- log.info(f"role: {role}, role_permissions: {role_permissions}")
194
- if len(role_permissions) == 0:
195
- continue
196
-
197
- # Extract permission patterns for this role
198
- read_perm = role_permissions.get("read", "")
199
- write_perm = role_permissions.get("write", "")
200
- read_pattern = self._extract_permission_pattern(read_perm)
201
- write_pattern = self._extract_permission_pattern(write_perm)
202
-
203
- for prop_name, property in properties.items():
204
- log.info(f"prop_name: {prop_name}, property: {property}")
205
- if permission_type == "read" and re.match(read_pattern, prop_name):
206
- allowed_properties[prop_name] = property
207
- if permission_type == "write" and re.match(write_pattern, prop_name):
208
- allowed_properties[prop_name] = property
209
-
210
- log.info(f"allowed_properties: {allowed_properties}")
208
+ role_permissions = action_permissions.get(role, {})
209
+ log.info("role: %s, role_permissions: %s", role, role_permissions)
210
+
211
+ # If no permissions found for specific role, check wildcard "*"
212
+ if not role_permissions:
213
+ role_permissions = action_permissions.get("*", {})
214
+ log.info("Fallback to wildcard role '*': %s", role_permissions)
215
+ if not role_permissions:
216
+ continue
217
+
218
+ # Extract permission pattern from the rule
219
+ regex_pattern = self._extract_permission_pattern(role_permissions)
220
+
221
+ if regex_pattern:
222
+ # Use efficient regex dictionary filtering
223
+ role_allowed = self._filter_properties_by_regex(
224
+ properties, regex_pattern
225
+ )
226
+ log.info(
227
+ "role: %s, pattern: %s, matched: %s",
228
+ role,
229
+ regex_pattern,
230
+ list(role_allowed.keys()),
231
+ )
232
+
233
+ # Merge with existing allowed properties
234
+ allowed_properties.update(role_allowed)
235
+
236
+ log.info("allowed_properties: %s", allowed_properties)
211
237
  return allowed_properties
212
238
 
239
+ def _filter_properties_by_regex(
240
+ self, properties: Dict[str, SchemaObjectProperty], regex_pattern: str
241
+ ) -> Dict[str, SchemaObjectProperty]:
242
+ """Filter dictionary properties using a regex pattern.
243
+
244
+ Args:
245
+ properties: Dictionary of property name -> SchemaObjectProperty
246
+ regex_pattern: Regex pattern to match property names
247
+
248
+ Returns:
249
+ Dict[str, SchemaObjectProperty]: Filtered properties dict
250
+ """
251
+ if not regex_pattern:
252
+ return {}
253
+
254
+ try:
255
+ compiled_regex = re.compile(regex_pattern)
256
+ return {k: v for k, v in properties.items() if compiled_regex.match(k)}
257
+ except re.error as e:
258
+ log.warning("Invalid regex pattern '%s': %s", regex_pattern, e)
259
+ return {}
260
+
213
261
  def _extract_permission_pattern(self, permission_rule) -> str:
214
262
  """Extract the regex pattern from a permission rule.
215
263
 
@@ -381,7 +429,7 @@ class SQLSchemaQueryHandler(SQLQueryHandler):
381
429
  """
382
430
  log.info("selection_result")
383
431
  if not hasattr(self, "__selection_results"):
384
- log.info(f"prefix_map: {self.prefix_map}")
432
+ log.info("prefix_map: %s", self.prefix_map)
385
433
  filters = self.operation.metadata_params.get("_properties", ".*").split()
386
434
  allowed_properties = self.check_permissions(
387
435
  "read", self.schema_object.permissions, self.schema_object.properties
@@ -1,12 +1,15 @@
1
1
  import json
2
2
  import logging
3
3
  import os
4
- from typing import Optional, Mapping, Any
4
+ from typing import Mapping, Any
5
5
 
6
6
  from api_foundry_query_engine.utils.api_model import set_api_model
7
7
  from api_foundry_query_engine.utils.app_exception import ApplicationException
8
8
  from api_foundry_query_engine.adapters.gateway_adapter import GatewayAdapter
9
+ from api_foundry_query_engine.utils.token_decoder import token_decoder
10
+ from api_foundry_query_engine.utils.claims_check import claims_check
9
11
 
12
+ logging.basicConfig(level=os.getenv("LOG_LEVEL", "INFO"))
10
13
  log = logging.getLogger(__name__)
11
14
 
12
15
 
@@ -44,15 +47,13 @@ class QueryEngine:
44
47
  }
45
48
 
46
49
 
47
- engine_config: Optional[Mapping[str, str]] = None
48
- query_engine: Optional[QueryEngine] = None
49
-
50
-
50
+ @token_decoder()
51
+ @claims_check()
51
52
  def handler(event, _):
52
53
  if not hasattr(handler, "engine_config"):
53
54
  log.info("Loading engine config from environment variables")
54
55
  handler.engine_config = os.environ
55
- log.info(f"engine_config: {handler.engine_config}")
56
+ log.info("engine_config: %s", handler.engine_config)
56
57
 
57
58
  if not hasattr(handler, "query_engine"):
58
59
  set_api_model(handler.engine_config)
@@ -1,5 +1,9 @@
1
1
  from typing import Any, Dict, List, Optional
2
2
 
3
+ from api_foundry_query_engine.utils.logger import logger
4
+
5
+ logger = logger(__name__)
6
+
3
7
 
4
8
  class Operation:
5
9
  """
@@ -35,11 +39,6 @@ class Operation:
35
39
  query_params: Optional[Dict[str, Any]] = None,
36
40
  store_params: Optional[Dict[str, Any]] = None,
37
41
  metadata_params: Optional[Dict[str, Any]] = None,
38
- roles: Optional[List[str]] = None,
39
- scope: Optional[Dict[str, Any]] = None,
40
- groups: Optional[List[str]] = None,
41
- permissions: Optional[List[str]] = None,
42
- subject: Optional[str] = None,
43
42
  claims: Optional[Dict[str, Any]] = None,
44
43
  ):
45
44
  """
@@ -76,12 +75,30 @@ class Operation:
76
75
  self.metadata_params = metadata_params or {}
77
76
 
78
77
  # Roles defining the context in which the operation is allowed.
79
- self.scope = scope or {}
80
- self.roles = roles or []
81
- self.groups = groups or []
82
- self.permissions = permissions or []
83
-
84
- self.subject = subject
85
- # Full set of claims (when available). Used for row-level filters
86
- # like where: "id = ${claims.sub}".
87
- self.claims = claims or ({"sub": subject} if subject is not None else {})
78
+ self.claims = claims or {}
79
+
80
+ # Log the operation for debugging and audit purposes
81
+ logger.info(
82
+ "Operation created: entity=%s, action=%s, "
83
+ "query_params=%s, store_params=%s, "
84
+ "metadata_params=%s, claims=%s",
85
+ self.entity,
86
+ self.action,
87
+ self.query_params,
88
+ self.store_params,
89
+ self.metadata_params,
90
+ self.claims,
91
+ )
92
+
93
+ @property
94
+ def roles(self) -> List[str]:
95
+ """Get the roles from claims."""
96
+ return self.claims.get("roles", []) if self.claims else []
97
+
98
+ def subject(self) -> Optional[str]:
99
+ """Get the subject from claims."""
100
+ return self.claims.get("sub") if self.claims else None
101
+
102
+ def groups(self) -> List[str]:
103
+ """Get the groups from claims."""
104
+ return self.claims.get("groups", []) if self.claims else []
@@ -8,7 +8,9 @@ log = logger(__name__)
8
8
 
9
9
 
10
10
  class Service:
11
- def __init__(self, config: dict = {}):
11
+ def __init__(self, config: dict = None):
12
+ if not config:
13
+ config = {}
12
14
  self.config = config
13
15
 
14
16
  def execute(self, operation: Operation) -> list[dict]:
@@ -16,7 +18,9 @@ class Service:
16
18
 
17
19
 
18
20
  class ServiceAdapter(Service):
19
- def __init__(self, config: dict = {}):
21
+ def __init__(self, config: dict = None):
22
+ if not config:
23
+ config = {}
20
24
  super().__init__(config)
21
25
 
22
26
  def execute(self, operation: Operation) -> list[dict]:
@@ -24,7 +28,9 @@ class ServiceAdapter(Service):
24
28
 
25
29
 
26
30
  class MutationPublisher(ServiceAdapter):
27
- def __init__(self, config: dict = {}):
31
+ def __init__(self, config: dict = None):
32
+ if not config:
33
+ config = {}
28
34
  super().__init__(config)
29
35
 
30
36
  def execute(self, operation):
@@ -34,7 +40,7 @@ class MutationPublisher(ServiceAdapter):
34
40
 
35
41
  def publish_notification(self, operation):
36
42
  topic_arn = self.config.get("BROADCAST_TOPIC", None)
37
- log.debug(f"Topic ARN: {topic_arn}")
43
+ log.debug("Topic ARN: %s", topic_arn)
38
44
 
39
45
  if topic_arn is not None:
40
46
  log.debug("Sending message")
@@ -46,7 +52,7 @@ class MutationPublisher(ServiceAdapter):
46
52
  }
47
53
 
48
54
  message_str = json.dumps({"default": json.dumps(message)})
49
- log.debug(f"message_str: {message_str}")
55
+ log.debug("message_str: %s", message_str)
50
56
  hash_object = hashlib.sha256(message_str.encode("utf-8"))
51
57
  hex_dig = hash_object.hexdigest()
52
58
 
@@ -57,7 +63,7 @@ class MutationPublisher(ServiceAdapter):
57
63
  MessageGroupId=operation.api_name,
58
64
  Message=message_str,
59
65
  )
60
- log.info(f"publish msg id {msg_id}")
66
+ log.info("publish msg id %s", msg_id)
61
67
 
62
68
  def __client(self, client_type):
63
69
  import boto3
@@ -45,8 +45,8 @@ class TransactionalService(ServiceAdapter):
45
45
  return [result]
46
46
  return result
47
47
  except Exception as error:
48
- log.error(f"transaction exception: {error}")
49
- log.error(f"traceback: {traceback.format_exc()}")
48
+ log.error("transaction exception: %s", error)
49
+ log.error("traceback: %s", traceback.format_exc())
50
50
  raise error
51
51
  finally:
52
52
  connection.close()
@@ -289,7 +289,7 @@ class APIModel:
289
289
  """Class to load and expose the API configuration as objects."""
290
290
 
291
291
  def __init__(self, config: Dict[str, Any]):
292
- print("building api_model")
292
+ log.info("building api_model")
293
293
  self.schema_objects = {
294
294
  name: SchemaObject(schema_data)
295
295
  for name, schema_data in config.get("schema_objects", {}).items()
@@ -323,4 +323,4 @@ def set_api_model(engine_config: Mapping[str, str]):
323
323
  os.environ.get("API_SPEC", "/var/task/api_spec.yaml"), "r"
324
324
  ) as file:
325
325
  api_model = APIModel(yaml.safe_load(file))
326
- log.info(f"Loaded API model: {api_model}")
326
+ log.info("Loaded API model: %s", api_model)