api-foundry-query-engine 0.8.63__tar.gz → 0.8.64__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.63 → api_foundry_query_engine-0.8.64}/PKG-INFO +1 -1
- api_foundry_query_engine-0.8.64/api_foundry_query_engine/__init__.py +1 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/dao/sql_insert_query_handler.py +21 -34
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/dao/sql_query_handler.py +19 -88
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/dao/sql_subselect_query_handler.py +1 -6
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/dao/sql_update_query_handler.py +9 -18
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/operation.py +3 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/utils/api_model.py +14 -35
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/utils/claims_check.py +3 -4
- api_foundry_query_engine-0.8.63/api_foundry_query_engine/__init__.py +0 -1
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/.gitignore +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/LICENSE +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/.pre-commit-config.yaml +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/adapters/adapter.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/adapters/case_change_adapter.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/adapters/gateway_adapter.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/adapters/security_adapter.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/connectors/connection.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/connectors/connection_factory.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/connectors/oracle_connector.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/connectors/postgres_connection.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/dao/batch_operation_handler.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/dao/dao.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/dao/operation_dao.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/dao/sql_custom_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/dao/sql_delete_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/dao/sql_restore_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/dao/sql_select_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/lambda_handler.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/services/service.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/services/transactional_service.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/utils/app_exception.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/utils/dependency_resolver.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/utils/logger.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/utils/reference_resolver.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/api_foundry_query_engine/utils/token_decoder.py +0 -0
- {api_foundry_query_engine-0.8.63 → api_foundry_query_engine-0.8.64}/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.64
|
|
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.64"
|
|
@@ -12,9 +12,7 @@ log = logger(__name__)
|
|
|
12
12
|
class SQLInsertSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
13
13
|
key_property: Optional[SchemaObjectProperty]
|
|
14
14
|
|
|
15
|
-
def __init__(
|
|
16
|
-
self, operation: Operation, schema_object: SchemaObject, engine: str
|
|
17
|
-
) -> None:
|
|
15
|
+
def __init__(self, operation: Operation, schema_object: SchemaObject, engine: str) -> None:
|
|
18
16
|
super().__init__(operation, schema_object, engine)
|
|
19
17
|
self.key_property = schema_object.primary_key
|
|
20
18
|
if self.key_property:
|
|
@@ -33,9 +31,7 @@ class SQLInsertSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
33
31
|
+ f" required. schema_object: {schema_object.api_name}",
|
|
34
32
|
)
|
|
35
33
|
self.concurrency_property = schema_object.concurrency_property
|
|
36
|
-
if self.concurrency_property and operation.store_params.get(
|
|
37
|
-
self.concurrency_property.api_name
|
|
38
|
-
):
|
|
34
|
+
if self.concurrency_property and operation.store_params.get(self.concurrency_property.api_name):
|
|
39
35
|
raise ApplicationException(
|
|
40
36
|
400,
|
|
41
37
|
"Versioned properties can not be supplied a store parameters. "
|
|
@@ -62,7 +58,9 @@ class SQLInsertSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
62
58
|
self.schema_object.properties,
|
|
63
59
|
)
|
|
64
60
|
# Combine both (union of read and write)
|
|
65
|
-
|
|
61
|
+
combined = {**read_props, **write_props}
|
|
62
|
+
filters = self.operation.metadata_params.get("_properties", ".*").split()
|
|
63
|
+
self._insert_selection_results = self.filter_and_prefix_keys(filters, combined)
|
|
66
64
|
return self._insert_selection_results
|
|
67
65
|
|
|
68
66
|
@property
|
|
@@ -73,10 +71,7 @@ class SQLInsertSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
73
71
|
returning_clause = self._get_returning_clause()
|
|
74
72
|
|
|
75
73
|
if not self.concurrency_property:
|
|
76
|
-
return
|
|
77
|
-
f"INSERT INTO {self.table_expression}{self.insert_values} "
|
|
78
|
-
+ returning_clause
|
|
79
|
-
)
|
|
74
|
+
return f"INSERT INTO {self.table_expression}{self.insert_values} " + returning_clause
|
|
80
75
|
|
|
81
76
|
if self.operation.store_params.get(self.concurrency_property.api_name):
|
|
82
77
|
raise ApplicationException(
|
|
@@ -86,10 +81,7 @@ class SQLInsertSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
86
81
|
+ f" schema_object: {self.schema_object.api_name}, "
|
|
87
82
|
+ f"property: {self.concurrency_property.api_name}",
|
|
88
83
|
)
|
|
89
|
-
return
|
|
90
|
-
f"INSERT INTO {self.table_expression}{self.insert_values}"
|
|
91
|
-
+ returning_clause
|
|
92
|
-
)
|
|
84
|
+
return f"INSERT INTO {self.table_expression}{self.insert_values} " + returning_clause
|
|
93
85
|
|
|
94
86
|
def _get_returning_clause(self) -> str:
|
|
95
87
|
"""
|
|
@@ -122,11 +114,7 @@ class SQLInsertSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
122
114
|
allowed_property_names = self.check_permissions(
|
|
123
115
|
"write", self.schema_object.permissions, self.schema_object.properties
|
|
124
116
|
)
|
|
125
|
-
allowed_properties = {
|
|
126
|
-
k: v
|
|
127
|
-
for k, v in self.schema_object.properties.items()
|
|
128
|
-
if k in allowed_property_names
|
|
129
|
-
}
|
|
117
|
+
allowed_properties = {k: v for k, v in self.schema_object.properties.items() if k in allowed_property_names}
|
|
130
118
|
log.info("allowed properties: %s", allowed_properties)
|
|
131
119
|
|
|
132
120
|
for property_name, property in self.schema_object.properties.items():
|
|
@@ -161,17 +149,13 @@ class SQLInsertSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
161
149
|
|
|
162
150
|
columns.append(property.column_name)
|
|
163
151
|
if property.api_name is None:
|
|
164
|
-
raise ApplicationException(
|
|
165
|
-
400, f"Property '{name}' does not have a valid api_name."
|
|
166
|
-
)
|
|
152
|
+
raise ApplicationException(400, f"Property '{name}' does not have a valid api_name.")
|
|
167
153
|
placeholders.append(self.placeholder(property, property.api_name))
|
|
168
154
|
# Serialize embedded objects to JSON
|
|
169
155
|
if property.api_type == "object":
|
|
170
156
|
self.store_placeholders[property.api_name] = json.dumps(value)
|
|
171
157
|
else:
|
|
172
|
-
self.store_placeholders[
|
|
173
|
-
property.api_name
|
|
174
|
-
] = property.convert_to_db_value(value)
|
|
158
|
+
self.store_placeholders[property.api_name] = property.convert_to_db_value(value)
|
|
175
159
|
|
|
176
160
|
for property_name, property in self.schema_object.properties.items():
|
|
177
161
|
inject_value = getattr(property, "inject_value", None)
|
|
@@ -181,12 +165,17 @@ class SQLInsertSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
181
165
|
if property_name in self.store_placeholders:
|
|
182
166
|
continue
|
|
183
167
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
168
|
+
# Preserve legacy placeholder names for writable injected fields
|
|
169
|
+
# while using the API name for non-writable injected fields.
|
|
170
|
+
placeholder_name = (
|
|
171
|
+
f"__inject_{property_name}"
|
|
172
|
+
if property_name in allowed_properties
|
|
173
|
+
else (str(property.api_name) if property.api_name is not None else property_name)
|
|
189
174
|
)
|
|
175
|
+
injected_value = self.extract_injected_value(str(inject_value))
|
|
176
|
+
columns.append(property.column_name)
|
|
177
|
+
placeholders.append(self.placeholder(property, placeholder_name))
|
|
178
|
+
self.store_placeholders[placeholder_name] = property.convert_to_db_value(injected_value)
|
|
190
179
|
|
|
191
180
|
if self.key_property:
|
|
192
181
|
if self.key_property.key_type == "sequence":
|
|
@@ -198,8 +187,6 @@ class SQLInsertSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
198
187
|
if self.concurrency_property.column_type == "integer":
|
|
199
188
|
placeholders.append("1")
|
|
200
189
|
else:
|
|
201
|
-
placeholders.append(
|
|
202
|
-
self.concurrency_generator(self.concurrency_property)
|
|
203
|
-
)
|
|
190
|
+
placeholders.append(self.concurrency_generator(self.concurrency_property))
|
|
204
191
|
|
|
205
192
|
return f" ( {', '.join(columns)} ) VALUES ( {', '.join(placeholders)})"
|
|
@@ -110,9 +110,7 @@ class SQLQueryHandler:
|
|
|
110
110
|
operation: Operation
|
|
111
111
|
engine: str
|
|
112
112
|
|
|
113
|
-
def __init__(
|
|
114
|
-
self, operation: Operation, engine: str
|
|
115
|
-
): # , schema_object: SchemaObject):
|
|
113
|
+
def __init__(self, operation: Operation, engine: str): # , schema_object: SchemaObject):
|
|
116
114
|
self.operation = operation
|
|
117
115
|
self.engine = engine
|
|
118
116
|
self.__select_list_columns = None
|
|
@@ -182,9 +180,7 @@ class SQLQueryHandler:
|
|
|
182
180
|
List[str]: A list of properties the user is permitted to access.
|
|
183
181
|
"""
|
|
184
182
|
# if permissions are not defined then no restrictions are applied
|
|
185
|
-
log.info(
|
|
186
|
-
f"checking permissions permission_type: {permission_type}, permissions: {permissions}"
|
|
187
|
-
)
|
|
183
|
+
log.info(f"checking permissions permission_type: {permission_type}, permissions: {permissions}")
|
|
188
184
|
if not permissions:
|
|
189
185
|
return properties
|
|
190
186
|
|
|
@@ -225,9 +221,7 @@ class SQLQueryHandler:
|
|
|
225
221
|
|
|
226
222
|
if regex_pattern:
|
|
227
223
|
# Use efficient regex dictionary filtering
|
|
228
|
-
role_allowed = self._filter_properties_by_regex(
|
|
229
|
-
properties, regex_pattern
|
|
230
|
-
)
|
|
224
|
+
role_allowed = self._filter_properties_by_regex(properties, regex_pattern)
|
|
231
225
|
log.info(
|
|
232
226
|
"role: %s, pattern: %s, matched: %s",
|
|
233
227
|
role,
|
|
@@ -286,9 +280,7 @@ class SQLQueryHandler:
|
|
|
286
280
|
|
|
287
281
|
for action, rule in actions.items():
|
|
288
282
|
# Normalize action names: create/update -> write
|
|
289
|
-
normalized_action = (
|
|
290
|
-
"write" if action in ("create", "update") else action
|
|
291
|
-
)
|
|
283
|
+
normalized_action = "write" if action in ("create", "update") else action
|
|
292
284
|
|
|
293
285
|
if normalized_action in normalized["default"]:
|
|
294
286
|
normalized["default"][normalized_action][role] = rule
|
|
@@ -332,9 +324,7 @@ class SQLQueryHandler:
|
|
|
332
324
|
return permission_rule
|
|
333
325
|
elif isinstance(permission_rule, dict):
|
|
334
326
|
# Support both 'properties' (preferred) and 'fields' (legacy)
|
|
335
|
-
return permission_rule.get("properties") or permission_rule.get(
|
|
336
|
-
"fields", ""
|
|
337
|
-
)
|
|
327
|
+
return permission_rule.get("properties") or permission_rule.get("fields", "")
|
|
338
328
|
else:
|
|
339
329
|
return ""
|
|
340
330
|
|
|
@@ -342,38 +332,7 @@ class SQLQueryHandler:
|
|
|
342
332
|
def selection_results(self) -> Dict:
|
|
343
333
|
raise NotImplementedError()
|
|
344
334
|
|
|
345
|
-
def
|
|
346
|
-
self, property: SchemaObjectProperty
|
|
347
|
-
) -> Optional[object]:
|
|
348
|
-
inject_value = getattr(property, "inject_value", None)
|
|
349
|
-
if not inject_value:
|
|
350
|
-
return None
|
|
351
|
-
|
|
352
|
-
if inject_value.startswith("claim:"):
|
|
353
|
-
claim_name = inject_value.split(":", 1)[1]
|
|
354
|
-
return self.operation.claims.get(claim_name)
|
|
355
|
-
|
|
356
|
-
if inject_value == "timestamp":
|
|
357
|
-
return datetime.utcnow().isoformat()
|
|
358
|
-
|
|
359
|
-
if inject_value == "date":
|
|
360
|
-
return date.today().isoformat()
|
|
361
|
-
|
|
362
|
-
if inject_value == "uuid":
|
|
363
|
-
return str(uuid.uuid4())
|
|
364
|
-
|
|
365
|
-
if inject_value.startswith("env:"):
|
|
366
|
-
env_name = inject_value.split(":", 1)[1]
|
|
367
|
-
return os.getenv(env_name)
|
|
368
|
-
|
|
369
|
-
raise ApplicationException(
|
|
370
|
-
500,
|
|
371
|
-
f"Unsupported inject_value source '{inject_value}' for property '{property.api_name}'",
|
|
372
|
-
)
|
|
373
|
-
|
|
374
|
-
def generate_sql_condition(
|
|
375
|
-
self, property: SchemaObjectProperty, value, prefix: Optional[str] = None
|
|
376
|
-
) -> str:
|
|
335
|
+
def generate_sql_condition(self, property: SchemaObjectProperty, value, prefix: Optional[str] = None) -> str:
|
|
377
336
|
operand = "="
|
|
378
337
|
if isinstance(value, str):
|
|
379
338
|
parts = value.split("::", 1)
|
|
@@ -385,9 +344,7 @@ class SQLQueryHandler:
|
|
|
385
344
|
value_str = str(value)
|
|
386
345
|
|
|
387
346
|
column = f"{prefix}.{property.column_name}" if prefix else property.column_name
|
|
388
|
-
placeholder_name =
|
|
389
|
-
f"{prefix}_{property.api_name}" if prefix else property.api_name
|
|
390
|
-
)
|
|
347
|
+
placeholder_name = f"{prefix}_{property.api_name}" if prefix else property.api_name
|
|
391
348
|
|
|
392
349
|
if operand in ["between", "not-between"]:
|
|
393
350
|
value_set = value_str.split(",")
|
|
@@ -395,17 +352,14 @@ class SQLQueryHandler:
|
|
|
395
352
|
elif operand in ["in", "not-in"]:
|
|
396
353
|
value_set = value_str.split(",")
|
|
397
354
|
assignments = [
|
|
398
|
-
self.placeholder(property, f"{placeholder_name}_{index}")
|
|
399
|
-
for index, _ in enumerate(value_set)
|
|
355
|
+
self.placeholder(property, f"{placeholder_name}_{index}") for index, _ in enumerate(value_set)
|
|
400
356
|
]
|
|
401
357
|
sql = f"{column} {'NOT ' if operand == 'not-in' else ''}IN ({', '.join(assignments)})" # noqa E501
|
|
402
358
|
else:
|
|
403
359
|
sql = f"{column} {operand} {self.placeholder(property, str(placeholder_name))}"
|
|
404
360
|
return sql
|
|
405
361
|
|
|
406
|
-
def generate_placeholders(
|
|
407
|
-
self, property: SchemaObjectProperty, value, prefix: Optional[str] = None
|
|
408
|
-
) -> dict:
|
|
362
|
+
def generate_placeholders(self, property: SchemaObjectProperty, value, prefix: Optional[str] = None) -> dict:
|
|
409
363
|
operand = "="
|
|
410
364
|
|
|
411
365
|
if isinstance(value, str):
|
|
@@ -417,9 +371,7 @@ class SQLQueryHandler:
|
|
|
417
371
|
else:
|
|
418
372
|
value_str = str(value)
|
|
419
373
|
|
|
420
|
-
placeholder_name =
|
|
421
|
-
f"{prefix}_{property.api_name}" if prefix else property.api_name
|
|
422
|
-
)
|
|
374
|
+
placeholder_name = f"{prefix}_{property.api_name}" if prefix else property.api_name
|
|
423
375
|
placeholders = {}
|
|
424
376
|
|
|
425
377
|
if operand in ["between", "not-between"]:
|
|
@@ -459,9 +411,6 @@ class SQLQueryHandler:
|
|
|
459
411
|
Raises:
|
|
460
412
|
ApplicationException: If source format is invalid
|
|
461
413
|
"""
|
|
462
|
-
import os
|
|
463
|
-
import uuid
|
|
464
|
-
|
|
465
414
|
if inject_value_source.startswith("claim:"):
|
|
466
415
|
claim_key = inject_value_source[6:]
|
|
467
416
|
return self.operation.claims.get(claim_key)
|
|
@@ -475,17 +424,13 @@ class SQLQueryHandler:
|
|
|
475
424
|
env_key = inject_value_source[4:]
|
|
476
425
|
return os.environ.get(env_key)
|
|
477
426
|
else:
|
|
478
|
-
raise ApplicationException(
|
|
479
|
-
400, f"Unknown inject value source: {inject_value_source}"
|
|
480
|
-
)
|
|
427
|
+
raise ApplicationException(400, f"Unknown inject value source: {inject_value_source}")
|
|
481
428
|
|
|
482
429
|
|
|
483
430
|
class SQLSchemaQueryHandler(SQLQueryHandler):
|
|
484
431
|
schema_object: SchemaObject
|
|
485
432
|
|
|
486
|
-
def __init__(
|
|
487
|
-
self, operation: Operation, schema_object: SchemaObject, engine: str
|
|
488
|
-
) -> None:
|
|
433
|
+
def __init__(self, operation: Operation, schema_object: SchemaObject, engine: str) -> None:
|
|
489
434
|
super().__init__(operation, engine)
|
|
490
435
|
self.schema_object = schema_object
|
|
491
436
|
self.single_table = self.__single_table()
|
|
@@ -514,10 +459,7 @@ class SQLSchemaQueryHandler(SQLQueryHandler):
|
|
|
514
459
|
entity_lower = entity.lower()
|
|
515
460
|
for i in range(1, len(entity_lower) + 1):
|
|
516
461
|
substring = entity_lower[:i]
|
|
517
|
-
if (
|
|
518
|
-
substring not in self._prefix_map.values()
|
|
519
|
-
and substring not in SQL_RESERVED_WORDS
|
|
520
|
-
):
|
|
462
|
+
if substring not in self._prefix_map.values() and substring not in SQL_RESERVED_WORDS:
|
|
521
463
|
self._prefix_map[entity] = substring
|
|
522
464
|
break
|
|
523
465
|
return self._prefix_map
|
|
@@ -557,9 +499,7 @@ class SQLSchemaQueryHandler(SQLQueryHandler):
|
|
|
557
499
|
allowed_properties = self.check_permissions(
|
|
558
500
|
"read", self.schema_object.permissions, self.schema_object.properties
|
|
559
501
|
)
|
|
560
|
-
self.__selection_results = self.filter_and_prefix_keys(
|
|
561
|
-
filters, allowed_properties
|
|
562
|
-
)
|
|
502
|
+
self.__selection_results = self.filter_and_prefix_keys(filters, allowed_properties)
|
|
563
503
|
return self.__selection_results
|
|
564
504
|
|
|
565
505
|
def _has_soft_delete_conflicts(self) -> Dict[str, bool]:
|
|
@@ -648,8 +588,7 @@ class SQLSchemaQueryHandler(SQLQueryHandler):
|
|
|
648
588
|
if excluded_values:
|
|
649
589
|
# Format values for SQL IN clause
|
|
650
590
|
formatted_values = ", ".join(
|
|
651
|
-
f"'{val}'" if isinstance(val, str) else str(val)
|
|
652
|
-
for val in excluded_values
|
|
591
|
+
f"'{val}'" if isinstance(val, str) else str(val) for val in excluded_values
|
|
653
592
|
)
|
|
654
593
|
conditions.append(f"{column_name} NOT IN ({formatted_values})")
|
|
655
594
|
|
|
@@ -667,20 +606,14 @@ class SQLSchemaQueryHandler(SQLQueryHandler):
|
|
|
667
606
|
|
|
668
607
|
for name, value in self.operation.query_params.items():
|
|
669
608
|
if "." in name:
|
|
670
|
-
raise ApplicationException(
|
|
671
|
-
400, "Selection on relations is not supported"
|
|
672
|
-
)
|
|
609
|
+
raise ApplicationException(400, "Selection on relations is not supported")
|
|
673
610
|
property = self.schema_object.properties.get(name)
|
|
674
611
|
if not property:
|
|
675
|
-
raise ApplicationException(
|
|
676
|
-
500, f"Search condition column not found {name}"
|
|
677
|
-
)
|
|
612
|
+
raise ApplicationException(500, f"Search condition column not found {name}")
|
|
678
613
|
if (
|
|
679
614
|
self.operation.action != "read"
|
|
680
615
|
and isinstance(value, str)
|
|
681
|
-
and re.match(
|
|
682
|
-
r"^(lt|le|eq|ne|gt|ge|in|not-in|between|not-between)::(.+)$", value
|
|
683
|
-
)
|
|
616
|
+
and re.match(r"^(lt|le|eq|ne|gt|ge|in|not-in|between|not-between)::(.+)$", value)
|
|
684
617
|
and self.schema_object.concurrency_property
|
|
685
618
|
):
|
|
686
619
|
raise ApplicationException(
|
|
@@ -696,9 +629,7 @@ class SQLSchemaQueryHandler(SQLQueryHandler):
|
|
|
696
629
|
self.search_placeholders.update(holders)
|
|
697
630
|
return f" WHERE {' AND '.join(conditions)}" if conditions else ""
|
|
698
631
|
|
|
699
|
-
def filter_and_prefix_keys(
|
|
700
|
-
self, regex_list: List[str], properties: dict, prefix: Optional[str] = None
|
|
701
|
-
) -> dict:
|
|
632
|
+
def filter_and_prefix_keys(self, regex_list: List[str], properties: dict, prefix: Optional[str] = None) -> dict:
|
|
702
633
|
"""
|
|
703
634
|
Accepts a prefix string, list of regular expressions, and a dictionary.
|
|
704
635
|
Returns a new dictionary containing items whose keys match any of the
|
|
@@ -51,16 +51,11 @@ class SQLSubselectSchemaQueryHandler(SQLSelectSchemaQueryHandler):
|
|
|
51
51
|
if len(self.select_list_columns) == 1: # then it only contains the key
|
|
52
52
|
return None
|
|
53
53
|
|
|
54
|
-
# Get the parent table alias to avoid ambiguous column references
|
|
55
|
-
parent_alias = self.parent_generator.prefix_map[
|
|
56
|
-
str(self.parent_generator.schema_object.api_name)
|
|
57
|
-
]
|
|
58
|
-
|
|
59
54
|
sql = (
|
|
60
55
|
f"SELECT {self.select_list} "
|
|
61
56
|
+ f"FROM {self.relation.child_schema_object.qualified_name} "
|
|
62
57
|
+ f"WHERE {self.relation.child_property} "
|
|
63
|
-
+ f"IN ( SELECT {
|
|
58
|
+
+ f"IN ( SELECT {self.relation.parent_property} "
|
|
64
59
|
+ f"FROM {self.parent_generator.table_expression}"
|
|
65
60
|
+ f"{self.parent_generator.search_condition} "
|
|
66
61
|
# + f"{order_by} {limit} {offset})"
|
|
@@ -8,9 +8,7 @@ from api_foundry_query_engine.utils.api_model import SchemaObject
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class SQLUpdateSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
11
|
-
def __init__(
|
|
12
|
-
self, operation: Operation, schema_object: SchemaObject, engine: str
|
|
13
|
-
) -> None:
|
|
11
|
+
def __init__(self, operation: Operation, schema_object: SchemaObject, engine: str) -> None:
|
|
14
12
|
super().__init__(operation, schema_object, engine)
|
|
15
13
|
|
|
16
14
|
def _template_where(self, expr: str) -> str:
|
|
@@ -107,11 +105,7 @@ class SQLUpdateSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
107
105
|
allowed_property_names = self.check_permissions(
|
|
108
106
|
"write", self.schema_object.permissions, self.schema_object.properties
|
|
109
107
|
)
|
|
110
|
-
allowed_properties = {
|
|
111
|
-
k: v
|
|
112
|
-
for k, v in self.schema_object.properties.items()
|
|
113
|
-
if k in allowed_property_names
|
|
114
|
-
}
|
|
108
|
+
allowed_properties = {k: v for k, v in self.schema_object.properties.items() if k in allowed_property_names}
|
|
115
109
|
self.store_placeholders = {}
|
|
116
110
|
columns = []
|
|
117
111
|
invalid_columns = []
|
|
@@ -133,9 +127,7 @@ class SQLUpdateSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
133
127
|
invalid_columns.append(name)
|
|
134
128
|
continue
|
|
135
129
|
|
|
136
|
-
placeholder = (
|
|
137
|
-
str(property.api_name) if property.api_name is not None else name
|
|
138
|
-
)
|
|
130
|
+
placeholder = str(property.api_name) if property.api_name is not None else name
|
|
139
131
|
column_name = property.column_name
|
|
140
132
|
|
|
141
133
|
columns.append(f"{column_name} = {self.placeholder(property, placeholder)}")
|
|
@@ -143,9 +135,7 @@ class SQLUpdateSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
143
135
|
if property.api_type == "object":
|
|
144
136
|
self.store_placeholders[placeholder] = json.dumps(value)
|
|
145
137
|
else:
|
|
146
|
-
self.store_placeholders[placeholder] = property.convert_to_db_value(
|
|
147
|
-
value
|
|
148
|
-
)
|
|
138
|
+
self.store_placeholders[placeholder] = property.convert_to_db_value(value)
|
|
149
139
|
|
|
150
140
|
for property_name, property in self.schema_object.properties.items():
|
|
151
141
|
inject_value = getattr(property, "inject_value", None)
|
|
@@ -154,18 +144,19 @@ class SQLUpdateSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
154
144
|
continue
|
|
155
145
|
|
|
156
146
|
placeholder = (
|
|
157
|
-
|
|
147
|
+
f"__inject_{property_name}"
|
|
148
|
+
if property_name in allowed_properties
|
|
149
|
+
else (str(property.api_name) if property.api_name is not None else property_name)
|
|
158
150
|
)
|
|
159
151
|
columns.append(f"{property.column_name} = {self.placeholder(property, placeholder)}")
|
|
160
152
|
self.store_placeholders[placeholder] = property.convert_to_db_value(
|
|
161
|
-
self.extract_injected_value(
|
|
153
|
+
self.extract_injected_value(str(inject_value))
|
|
162
154
|
)
|
|
163
155
|
|
|
164
156
|
if invalid_columns:
|
|
165
157
|
raise ApplicationException(
|
|
166
158
|
403,
|
|
167
|
-
f"Subject does not have permission to update properties: "
|
|
168
|
-
f"{invalid_columns}",
|
|
159
|
+
f"Subject does not have permission to update properties: " f"{invalid_columns}",
|
|
169
160
|
)
|
|
170
161
|
return f" SET {', '.join(columns)}"
|
|
171
162
|
|
|
@@ -40,6 +40,7 @@ class Operation:
|
|
|
40
40
|
store_params: Optional[Dict[str, Any]] = None,
|
|
41
41
|
metadata_params: Optional[Dict[str, Any]] = None,
|
|
42
42
|
claims: Optional[Dict[str, Any]] = None,
|
|
43
|
+
roles: Optional[List[str]] = None,
|
|
43
44
|
):
|
|
44
45
|
"""
|
|
45
46
|
Initializes the Operation instance.
|
|
@@ -76,6 +77,8 @@ class Operation:
|
|
|
76
77
|
|
|
77
78
|
# Roles defining the context in which the operation is allowed.
|
|
78
79
|
self.claims = claims or {}
|
|
80
|
+
if roles is not None and "roles" not in self.claims:
|
|
81
|
+
self.claims["roles"] = roles
|
|
79
82
|
|
|
80
83
|
# Log the operation for debugging and audit purposes
|
|
81
84
|
logger.info(
|
|
@@ -207,9 +207,7 @@ class SchemaObjectAssociation:
|
|
|
207
207
|
raise ValueError(f"Primary key not defined for schema '{self.schema_name}'")
|
|
208
208
|
column_name = getattr(child_schema.primary_key, "column_name", None)
|
|
209
209
|
if column_name is None:
|
|
210
|
-
raise ValueError(
|
|
211
|
-
f"Primary key property does not have 'column_name' for schema '{self.schema_name}'"
|
|
212
|
-
)
|
|
210
|
+
raise ValueError(f"Primary key property does not have 'column_name' for schema '{self.schema_name}'")
|
|
213
211
|
return column_name
|
|
214
212
|
|
|
215
213
|
@property
|
|
@@ -220,14 +218,10 @@ class SchemaObjectAssociation:
|
|
|
220
218
|
if not parent_schema_obj:
|
|
221
219
|
raise ValueError(f"SchemaObject '{self.parent_schema}' not found")
|
|
222
220
|
if not parent_schema_obj.primary_key:
|
|
223
|
-
raise ValueError(
|
|
224
|
-
f"Primary key not defined for schema '{self.parent_schema}'"
|
|
225
|
-
)
|
|
221
|
+
raise ValueError(f"Primary key not defined for schema '{self.parent_schema}'")
|
|
226
222
|
column_name = getattr(parent_schema_obj.primary_key, "column_name", None)
|
|
227
223
|
if column_name is None:
|
|
228
|
-
raise ValueError(
|
|
229
|
-
f"Primary key property does not have 'column_name' for schema '{self.parent_schema}'"
|
|
230
|
-
)
|
|
224
|
+
raise ValueError(f"Primary key property does not have 'column_name' for schema '{self.parent_schema}'")
|
|
231
225
|
return column_name
|
|
232
226
|
|
|
233
227
|
def __repr__(self):
|
|
@@ -255,23 +249,16 @@ class SchemaObject:
|
|
|
255
249
|
self.database: str = str(data.get("database"))
|
|
256
250
|
self.schema: Optional[str] = data.get("schema")
|
|
257
251
|
self.table_name: str = str(data.get("table_name"))
|
|
258
|
-
self.qualified_name: str =
|
|
259
|
-
f"{self.schema}.{self.table_name}" if self.schema else self.table_name
|
|
260
|
-
)
|
|
252
|
+
self.qualified_name: str = f"{self.schema}.{self.table_name}" if self.schema else self.table_name
|
|
261
253
|
self.properties: Dict[str, SchemaObjectProperty] = {
|
|
262
|
-
name: SchemaObjectProperty(prop_data)
|
|
263
|
-
for name, prop_data in data.get("properties", {}).items()
|
|
254
|
+
name: SchemaObjectProperty(prop_data) for name, prop_data in data.get("properties", {}).items()
|
|
264
255
|
}
|
|
265
256
|
self.relations = {
|
|
266
|
-
name: SchemaObjectAssociation(
|
|
267
|
-
self.api_name if self.api_name is not None else "", assoc_data
|
|
268
|
-
)
|
|
257
|
+
name: SchemaObjectAssociation(self.api_name if self.api_name is not None else "", assoc_data)
|
|
269
258
|
for name, assoc_data in data.get("relations", {}).items()
|
|
270
259
|
}
|
|
271
260
|
self.concurrency_property = (
|
|
272
|
-
self.properties[str(data.get("concurrency_property"))]
|
|
273
|
-
if data.get("concurrency_property")
|
|
274
|
-
else None
|
|
261
|
+
self.properties[str(data.get("concurrency_property"))] if data.get("concurrency_property") else None
|
|
275
262
|
)
|
|
276
263
|
self._primary_key: str = str(data.get("primary_key"))
|
|
277
264
|
self.permissions = data.get("permissions")
|
|
@@ -293,8 +280,7 @@ class SchemaObject:
|
|
|
293
280
|
name: prop
|
|
294
281
|
for name, prop in self.properties.items()
|
|
295
282
|
if prop.is_soft_delete_field()
|
|
296
|
-
and prop.get_soft_delete_strategy()
|
|
297
|
-
in ["null_check", "boolean_flag", "exclude_values"]
|
|
283
|
+
and prop.get_soft_delete_strategy() in ["null_check", "boolean_flag", "exclude_values"]
|
|
298
284
|
}
|
|
299
285
|
|
|
300
286
|
def get_soft_delete_audit_properties(
|
|
@@ -304,8 +290,7 @@ class SchemaObject:
|
|
|
304
290
|
return {
|
|
305
291
|
name: prop
|
|
306
292
|
for name, prop in self.properties.items()
|
|
307
|
-
if prop.is_soft_delete_field()
|
|
308
|
-
and prop.get_soft_delete_strategy() == "audit_field"
|
|
293
|
+
if prop.is_soft_delete_field() and prop.get_soft_delete_strategy() == "audit_field"
|
|
309
294
|
}
|
|
310
295
|
|
|
311
296
|
def get_soft_delete_strategies(self) -> List[str]:
|
|
@@ -326,12 +311,10 @@ class PathOperation:
|
|
|
326
311
|
self.sql: str = data["sql"]
|
|
327
312
|
self.database: str = data["database"]
|
|
328
313
|
self.inputs: Dict[str, SchemaObjectProperty] = {
|
|
329
|
-
name: SchemaObjectProperty(input_data)
|
|
330
|
-
for name, input_data in data.get("inputs", {}).items()
|
|
314
|
+
name: SchemaObjectProperty(input_data) for name, input_data in data.get("inputs", {}).items()
|
|
331
315
|
}
|
|
332
316
|
self.outputs: Dict[str, SchemaObjectProperty] = {
|
|
333
|
-
name: SchemaObjectProperty(output_data)
|
|
334
|
-
for name, output_data in data.get("outputs", {}).items()
|
|
317
|
+
name: SchemaObjectProperty(output_data) for name, output_data in data.get("outputs", {}).items()
|
|
335
318
|
}
|
|
336
319
|
self.permissions = data.get("security")
|
|
337
320
|
|
|
@@ -345,12 +328,10 @@ class APIModel:
|
|
|
345
328
|
def __init__(self, config: Dict[str, Any]):
|
|
346
329
|
log.info("building api_model")
|
|
347
330
|
self.schema_objects = {
|
|
348
|
-
name: SchemaObject(schema_data)
|
|
349
|
-
for name, schema_data in config.get("schema_objects", {}).items()
|
|
331
|
+
name: SchemaObject(schema_data) for name, schema_data in config.get("schema_objects", {}).items()
|
|
350
332
|
}
|
|
351
333
|
self.path_operations = {
|
|
352
|
-
name: PathOperation(path_data)
|
|
353
|
-
for name, path_data in config.get("path_operations", {}).items()
|
|
334
|
+
name: PathOperation(path_data) for name, path_data in config.get("path_operations", {}).items()
|
|
354
335
|
}
|
|
355
336
|
|
|
356
337
|
def get_path_operation(self, path: str, method: str) -> Optional[PathOperation]:
|
|
@@ -373,8 +354,6 @@ def set_api_model(engine_config: Mapping[str, str]):
|
|
|
373
354
|
api_model = APIModel(yaml.safe_load(engine_config["API_SPEC"]))
|
|
374
355
|
else:
|
|
375
356
|
log.info("Loading API model from file")
|
|
376
|
-
with open(
|
|
377
|
-
os.environ.get("API_SPEC", "/var/task/api_spec.yaml"), "r"
|
|
378
|
-
) as file:
|
|
357
|
+
with open(os.environ.get("API_SPEC", "/var/task/api_spec.yaml"), "r") as file:
|
|
379
358
|
api_model = APIModel(yaml.safe_load(file))
|
|
380
359
|
log.info("Loaded API model: %s", api_model)
|
|
@@ -420,10 +420,9 @@ def _extract_entity_from_path(event: Dict[str, Any]) -> Optional[str]:
|
|
|
420
420
|
if not literal_parts:
|
|
421
421
|
return None
|
|
422
422
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
return "_".join(literal_parts)
|
|
423
|
+
# For API paths with prefixes (e.g. "/chinook-api/album"),
|
|
424
|
+
# the entity is the most specific trailing literal segment.
|
|
425
|
+
return literal_parts[-1]
|
|
427
426
|
|
|
428
427
|
|
|
429
428
|
def _scope_matches(user_scopes: list, required_scope: str, operation: str, entity: str) -> bool:
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.8.63"
|
|
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
|