api-foundry-query-engine 0.8.0__tar.gz → 0.8.2__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.0 → api_foundry_query_engine-0.8.2}/.gitignore +2 -0
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/PKG-INFO +1 -1
- api_foundry_query_engine-0.8.2/api_foundry_query_engine/.pre-commit-config.yaml +22 -0
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/adapters/adapter.py +9 -3
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/connectors/connection_factory.py +16 -10
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/connectors/postgres_connection.py +8 -7
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/dao/operation_dao.py +17 -7
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/dao/sql_custom_query_handler.py +1 -1
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/dao/sql_insert_query_handler.py +27 -12
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/dao/sql_query_handler.py +6 -6
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/dao/sql_select_query_handler.py +21 -18
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/dao/sql_update_query_handler.py +20 -9
- api_foundry_query_engine-0.8.2/api_foundry_query_engine/lambda_handler.py +55 -0
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/services/service.py +15 -6
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/services/transactional_service.py +13 -9
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/utils/api_model.py +94 -52
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/pyproject.toml +1 -1
- api_foundry_query_engine-0.8.0/api_foundry_query_engine/lambda_handler.py +0 -43
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/LICENSE +0 -0
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/__init__.py +0 -0
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/adapters/case_change_adapter.py +0 -0
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/adapters/gateway_adapter.py +0 -0
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/adapters/security_adapter.py +0 -0
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/connectors/athena_connector.py +0 -0
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/connectors/connection.py +0 -0
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/connectors/oracle_connector.py +0 -0
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/dao/dao.py +0 -0
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/dao/sql_delete_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/dao/sql_subselect_query_handler.py +0 -0
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/operation.py +0 -0
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/services/security_service.py +0 -0
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/api_foundry_query_engine/utils/app_exception.py +0 -0
- {api_foundry_query_engine-0.8.0 → api_foundry_query_engine-0.8.2}/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.
|
|
3
|
+
Version: 0.8.2
|
|
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,22 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v3.4.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: trailing-whitespace
|
|
6
|
+
- id: end-of-file-fixer
|
|
7
|
+
- id: check-yaml
|
|
8
|
+
- id: check-added-large-files
|
|
9
|
+
|
|
10
|
+
- repo: https://github.com/psf/black
|
|
11
|
+
rev: 23.12.1
|
|
12
|
+
hooks:
|
|
13
|
+
- id: black
|
|
14
|
+
language_version: python3.11
|
|
15
|
+
|
|
16
|
+
- repo: https://github.com/PyCQA/flake8
|
|
17
|
+
rev: 7.0.0
|
|
18
|
+
hooks:
|
|
19
|
+
- id: flake8
|
|
20
|
+
args: [--max-line-length=120]
|
|
21
|
+
|
|
22
|
+
exclude: '^$'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import abc
|
|
2
|
-
from typing import Optional
|
|
2
|
+
from typing import Mapping, Optional
|
|
3
3
|
|
|
4
4
|
from api_foundry_query_engine.operation import Operation
|
|
5
5
|
from api_foundry_query_engine.services.transactional_service import TransactionalService
|
|
@@ -11,6 +11,7 @@ log = logger(__name__)
|
|
|
11
11
|
|
|
12
12
|
class Adapter(metaclass=abc.ABCMeta):
|
|
13
13
|
service: Service
|
|
14
|
+
config: Mapping[str, str]
|
|
14
15
|
|
|
15
16
|
@classmethod
|
|
16
17
|
def __subclasshook__(cls, __subclass: type) -> bool:
|
|
@@ -21,8 +22,13 @@ class Adapter(metaclass=abc.ABCMeta):
|
|
|
21
22
|
and callable(__subclass.unmarshal)
|
|
22
23
|
)
|
|
23
24
|
|
|
24
|
-
def __init__(
|
|
25
|
-
self
|
|
25
|
+
def __init__(
|
|
26
|
+
self, config: Mapping[str, str] = {}, service: Optional[Service] = None
|
|
27
|
+
) -> None:
|
|
28
|
+
self.config = config
|
|
29
|
+
self.service = (
|
|
30
|
+
service if service is not None else TransactionalService(config=self.config)
|
|
31
|
+
)
|
|
26
32
|
|
|
27
33
|
def unmarshal(self, event) -> Operation:
|
|
28
34
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
from typing import Mapping
|
|
1
2
|
import boto3
|
|
2
3
|
import json
|
|
3
|
-
import os
|
|
4
4
|
|
|
5
5
|
from api_foundry_query_engine.connectors.connection import Connection
|
|
6
6
|
from api_foundry_query_engine.utils.app_exception import ApplicationException
|
|
@@ -11,9 +11,11 @@ log = logger(__name__)
|
|
|
11
11
|
|
|
12
12
|
class ConnectionFactory:
|
|
13
13
|
db_config_map: dict[str, dict]
|
|
14
|
+
config: Mapping[str, str]
|
|
14
15
|
|
|
15
|
-
def __init__(self):
|
|
16
|
+
def __init__(self, config: Mapping[str, str] = {}):
|
|
16
17
|
self.db_config_map = dict()
|
|
18
|
+
self.config = config
|
|
17
19
|
|
|
18
20
|
def get_connection(self, database: str) -> Connection:
|
|
19
21
|
"""
|
|
@@ -33,7 +35,11 @@ class ConnectionFactory:
|
|
|
33
35
|
log.info(f"database: {database}")
|
|
34
36
|
db_config = self.db_config_map.get(database)
|
|
35
37
|
if not db_config:
|
|
36
|
-
|
|
38
|
+
# Use config dict for secrets
|
|
39
|
+
secrets_map = self.config.get("SECRETS", {})
|
|
40
|
+
if isinstance(secrets_map, str):
|
|
41
|
+
secrets_map = json.loads(secrets_map)
|
|
42
|
+
secret_name = secrets_map.get(database)
|
|
37
43
|
log.debug(f"secret_name: {secret_name}")
|
|
38
44
|
|
|
39
45
|
if secret_name:
|
|
@@ -66,15 +72,18 @@ class ConnectionFactory:
|
|
|
66
72
|
Returns:
|
|
67
73
|
- dict: The database configuration obtained from the secret.
|
|
68
74
|
"""
|
|
69
|
-
|
|
75
|
+
if self.config.get(db_secret_name):
|
|
76
|
+
return self.config.get(db_secret_name)
|
|
77
|
+
|
|
78
|
+
endpoint_url = self.config.get("AWS_ENDPOINT_URL") # LocalStack endpoint
|
|
70
79
|
sts_client = boto3.client("sts", endpoint_url=endpoint_url)
|
|
71
80
|
|
|
72
|
-
secret_account_id =
|
|
81
|
+
secret_account_id = self.config.get("SECRET_ACCOUNT_ID", None)
|
|
73
82
|
log.debug(f"secret_account_id: {secret_account_id}")
|
|
74
83
|
|
|
75
84
|
if secret_account_id:
|
|
76
85
|
# If a secret account ID is provided, assume a role in that account
|
|
77
|
-
secret_role =
|
|
86
|
+
secret_role = self.config.get("ROLE_NAME", None)
|
|
78
87
|
assume_role_response = sts_client.assume_role(
|
|
79
88
|
RoleArn=f"arn:aws:iam::{secret_account_id}:role/{secret_role}",
|
|
80
89
|
RoleSessionName="AssumeRoleSession",
|
|
@@ -94,7 +103,7 @@ class ConnectionFactory:
|
|
|
94
103
|
log.info(f"endpoint_url: {endpoint_url}")
|
|
95
104
|
secretsmanager = boto3.client(
|
|
96
105
|
"secretsmanager",
|
|
97
|
-
|
|
106
|
+
endpoint_url=endpoint_url,
|
|
98
107
|
)
|
|
99
108
|
|
|
100
109
|
# Get the secret value from AWS Secrets Manager
|
|
@@ -104,6 +113,3 @@ class ConnectionFactory:
|
|
|
104
113
|
|
|
105
114
|
# Return the parsed JSON secret string
|
|
106
115
|
return json.loads(db_secret.get("SecretString"))
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
connection_factory = ConnectionFactory()
|
|
@@ -9,17 +9,18 @@ class PostgresCursor(Cursor):
|
|
|
9
9
|
def __init__(self, cursor):
|
|
10
10
|
self.__cursor = cursor
|
|
11
11
|
|
|
12
|
-
def execute(self, sql: str,
|
|
12
|
+
def execute(self, sql: str, params: dict, selection_results: dict) -> list[dict]:
|
|
13
13
|
"""
|
|
14
14
|
Execute SQL statements on the PostgreSQL database.
|
|
15
15
|
|
|
16
16
|
Parameters:
|
|
17
17
|
- cursor: The database cursor.
|
|
18
18
|
- sql (str): The SQL statement to execute.
|
|
19
|
-
-
|
|
19
|
+
- params (dict): Parameters to be used in the SQL statement.
|
|
20
|
+
- selection_results (dict): Mapping of result columns.
|
|
20
21
|
|
|
21
22
|
Returns:
|
|
22
|
-
-
|
|
23
|
+
- list[dict]: List of result records as dictionaries.
|
|
23
24
|
|
|
24
25
|
Raises:
|
|
25
26
|
- AppException: Custom exception for handling database-related errors.
|
|
@@ -30,12 +31,12 @@ class PostgresCursor(Cursor):
|
|
|
30
31
|
|
|
31
32
|
try:
|
|
32
33
|
# Execute the SQL statement with parameters
|
|
33
|
-
self.__cursor.execute(sql,
|
|
34
|
+
self.__cursor.execute(sql, params)
|
|
34
35
|
result = []
|
|
35
36
|
for record in self.__cursor:
|
|
36
|
-
# Convert record tuple to dictionary using
|
|
37
|
+
# Convert record tuple to dictionary using selection_results
|
|
37
38
|
result.append(
|
|
38
|
-
{col: value for col, value in zip(
|
|
39
|
+
{col: value for col, value in zip(selection_results, record)}
|
|
39
40
|
)
|
|
40
41
|
|
|
41
42
|
return result
|
|
@@ -80,7 +81,7 @@ class PostgresConnection(Connection):
|
|
|
80
81
|
"""
|
|
81
82
|
from psycopg2 import connect
|
|
82
83
|
|
|
83
|
-
dbname = self.db_config["
|
|
84
|
+
dbname = self.db_config["database"]
|
|
84
85
|
user = self.db_config["username"]
|
|
85
86
|
password = self.db_config["password"]
|
|
86
87
|
host = self.db_config["host"]
|
|
@@ -21,8 +21,8 @@ from api_foundry_query_engine.dao.dao import DAO
|
|
|
21
21
|
from api_foundry_query_engine.connectors.connection import Cursor
|
|
22
22
|
from api_foundry_query_engine.operation import Operation
|
|
23
23
|
from api_foundry_query_engine.utils.api_model import (
|
|
24
|
-
get_schema_object,
|
|
25
24
|
get_path_operation,
|
|
25
|
+
get_schema_object,
|
|
26
26
|
)
|
|
27
27
|
from api_foundry_query_engine.dao.sql_query_handler import SQLQueryHandler
|
|
28
28
|
|
|
@@ -60,6 +60,10 @@ class OperationDAO(DAO):
|
|
|
60
60
|
return self._query_handler
|
|
61
61
|
|
|
62
62
|
schema_object = get_schema_object(self.operation.entity)
|
|
63
|
+
if not schema_object:
|
|
64
|
+
raise ApplicationException(
|
|
65
|
+
500, f"Unknown operation: {self.operation.entity}"
|
|
66
|
+
)
|
|
63
67
|
if self.operation.action == "read":
|
|
64
68
|
self._query_handler = SQLSelectSchemaQueryHandler(
|
|
65
69
|
self.operation, schema_object, self.engine
|
|
@@ -82,25 +86,31 @@ class OperationDAO(DAO):
|
|
|
82
86
|
)
|
|
83
87
|
return self._query_handler
|
|
84
88
|
|
|
85
|
-
def execute(self,
|
|
89
|
+
def execute(self, connector, operation=None) -> Union[list[dict], dict]:
|
|
86
90
|
"""
|
|
87
|
-
Execute the database operation based on the provided
|
|
91
|
+
Execute the database operation based on the provided connector.
|
|
88
92
|
|
|
89
93
|
Args:
|
|
90
|
-
|
|
94
|
+
connector (Connection): The database connection.
|
|
95
|
+
operation (Operation, optional): The operation to perform.
|
|
91
96
|
|
|
92
97
|
Returns:
|
|
93
98
|
list[dict]: A list of dictionaries containing the results
|
|
94
99
|
of the operation.
|
|
95
100
|
"""
|
|
96
101
|
|
|
102
|
+
# Use self.operation if operation is not provided
|
|
103
|
+
op = operation if operation is not None else self.operation
|
|
104
|
+
# Assume connector has a 'cursor()' method to get a Cursor
|
|
105
|
+
cursor = connector.cursor()
|
|
106
|
+
|
|
97
107
|
result = self.__fetch_record_set(self.query_handler, cursor)
|
|
98
108
|
|
|
99
|
-
if
|
|
100
|
-
if
|
|
109
|
+
if op.action == "read":
|
|
110
|
+
if op.metadata_params.get("count", False):
|
|
101
111
|
return result[0]
|
|
102
112
|
self.__fetch_many(result, cursor)
|
|
103
|
-
elif
|
|
113
|
+
elif op.action in ["update", "delete"] and len(result) == 0:
|
|
104
114
|
raise ApplicationException(400, "No records were modified")
|
|
105
115
|
|
|
106
116
|
return result
|
|
@@ -70,22 +70,29 @@ class SQLInsertSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
70
70
|
placeholders = []
|
|
71
71
|
columns = []
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
allowed_property_names = self.check_permissions(
|
|
74
74
|
"write", self.schema_object.permissions, self.schema_object.properties
|
|
75
75
|
)
|
|
76
|
+
allowed_properties = {
|
|
77
|
+
k: v
|
|
78
|
+
for k, v in self.schema_object.properties.items()
|
|
79
|
+
if k in allowed_property_names
|
|
80
|
+
}
|
|
76
81
|
log.info(f"allowed properties: {allowed_properties}")
|
|
82
|
+
|
|
83
|
+
import json
|
|
84
|
+
|
|
77
85
|
for name, value in self.operation.store_params.items():
|
|
78
86
|
parts = name.split(".")
|
|
79
87
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
)
|
|
88
|
+
if len(parts) > 1:
|
|
89
|
+
raise ApplicationException(
|
|
90
|
+
400,
|
|
91
|
+
"Properties can not be set on associated objects " + name,
|
|
92
|
+
)
|
|
86
93
|
|
|
87
|
-
|
|
88
|
-
|
|
94
|
+
property = allowed_properties.get(parts[0], None)
|
|
95
|
+
if property is None:
|
|
89
96
|
if parts[0] not in self.schema_object.properties:
|
|
90
97
|
raise ApplicationException(400, f"Invalid property: {name}")
|
|
91
98
|
else:
|
|
@@ -95,10 +102,18 @@ class SQLInsertSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
95
102
|
)
|
|
96
103
|
|
|
97
104
|
columns.append(property.column_name)
|
|
105
|
+
if property.api_name is None:
|
|
106
|
+
raise ApplicationException(
|
|
107
|
+
400, f"Property '{name}' does not have a valid api_name."
|
|
108
|
+
)
|
|
98
109
|
placeholders.append(self.placeholder(property, property.api_name))
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
110
|
+
# Serialize embedded objects to JSON
|
|
111
|
+
if property.api_type == "object":
|
|
112
|
+
self.store_placeholders[property.api_name] = json.dumps(value)
|
|
113
|
+
else:
|
|
114
|
+
self.store_placeholders[
|
|
115
|
+
property.api_name
|
|
116
|
+
] = property.convert_to_db_value(value)
|
|
102
117
|
|
|
103
118
|
if self.key_property:
|
|
104
119
|
if self.key_property.key_type == "sequence":
|
|
@@ -150,7 +150,7 @@ class SQLQueryHandler:
|
|
|
150
150
|
|
|
151
151
|
def placeholder(self, property: SchemaObjectProperty, param: str = "") -> str:
|
|
152
152
|
if len(param) == 0:
|
|
153
|
-
param = property.api_name
|
|
153
|
+
param = property.api_name if property.api_name is not None else ""
|
|
154
154
|
|
|
155
155
|
if self.engine == "oracle":
|
|
156
156
|
if property.column_type == "date":
|
|
@@ -166,7 +166,7 @@ class SQLQueryHandler:
|
|
|
166
166
|
self,
|
|
167
167
|
permission_type: str,
|
|
168
168
|
permissions: Optional[dict],
|
|
169
|
-
properties:
|
|
169
|
+
properties: Dict[str, SchemaObjectProperty],
|
|
170
170
|
) -> Dict[str, SchemaObjectProperty]:
|
|
171
171
|
"""
|
|
172
172
|
Checks the user's permissions for the specified permission type.
|
|
@@ -240,7 +240,7 @@ class SQLQueryHandler:
|
|
|
240
240
|
]
|
|
241
241
|
sql = f"{column} {'NOT ' if operand == 'not-in' else ''}IN ({', '.join(assignments)})" # noqa E501
|
|
242
242
|
else:
|
|
243
|
-
sql = f"{column} {operand} {self.placeholder(property, placeholder_name)}"
|
|
243
|
+
sql = f"{column} {operand} {self.placeholder(property, str(placeholder_name))}"
|
|
244
244
|
return sql
|
|
245
245
|
|
|
246
246
|
def generate_placeholders(
|
|
@@ -346,7 +346,7 @@ class SQLSchemaQueryHandler(SQLQueryHandler):
|
|
|
346
346
|
|
|
347
347
|
@property
|
|
348
348
|
def table_expression(self) -> str:
|
|
349
|
-
return self.schema_object.table_name
|
|
349
|
+
return self.schema_object.table_name or ""
|
|
350
350
|
|
|
351
351
|
@property
|
|
352
352
|
def selection_results(self) -> Dict:
|
|
@@ -393,9 +393,9 @@ class SQLSchemaQueryHandler(SQLQueryHandler):
|
|
|
393
393
|
raise ApplicationException(
|
|
394
394
|
400,
|
|
395
395
|
"Concurrency settings prohibit multi-record updates "
|
|
396
|
-
+ self.schema_object.api_name
|
|
396
|
+
+ str(self.schema_object.api_name)
|
|
397
397
|
+ ", property: "
|
|
398
|
-
+ property.api_name,
|
|
398
|
+
+ str(property.api_name),
|
|
399
399
|
)
|
|
400
400
|
|
|
401
401
|
assignment, holders = self.search_value_assignment(property, value)
|
|
@@ -50,7 +50,7 @@ class SQLSelectSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
50
50
|
raise ApplicationException(
|
|
51
51
|
400,
|
|
52
52
|
"Invalid selection property "
|
|
53
|
-
+ self.schema_object.api_name
|
|
53
|
+
+ str(self.schema_object.api_name)
|
|
54
54
|
+ " does not have a property "
|
|
55
55
|
+ parts[0],
|
|
56
56
|
)
|
|
@@ -59,7 +59,7 @@ class SQLSelectSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
59
59
|
raise ApplicationException(
|
|
60
60
|
400,
|
|
61
61
|
"Property not found, "
|
|
62
|
-
+ relation.child_schema_object.api_name
|
|
62
|
+
+ str(relation.child_schema_object.api_name)
|
|
63
63
|
+ " does not have property "
|
|
64
64
|
+ parts[1],
|
|
65
65
|
)
|
|
@@ -67,17 +67,20 @@ class SQLSelectSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
67
67
|
prefix = self.prefix_map[parts[0]]
|
|
68
68
|
else:
|
|
69
69
|
property = self.schema_object.properties[parts[0]]
|
|
70
|
+
if self.schema_object.api_name is None:
|
|
71
|
+
raise ApplicationException(
|
|
72
|
+
500,
|
|
73
|
+
"schema_object.api_name is None, cannot use as key in prefix_map",
|
|
74
|
+
)
|
|
70
75
|
prefix = self.prefix_map[self.schema_object.api_name]
|
|
71
76
|
except KeyError:
|
|
72
77
|
raise ApplicationException(
|
|
73
78
|
500,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
+ name
|
|
80
|
-
),
|
|
79
|
+
"Invalid query parameter, property not found. "
|
|
80
|
+
+ "schema object: "
|
|
81
|
+
+ str(self.schema_object.api_name)
|
|
82
|
+
+ ", property: "
|
|
83
|
+
+ name,
|
|
81
84
|
)
|
|
82
85
|
|
|
83
86
|
assignment, holders = self.search_value_assignment(property, value, prefix)
|
|
@@ -90,13 +93,13 @@ class SQLSelectSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
90
93
|
@property
|
|
91
94
|
def table_expression(self) -> str:
|
|
92
95
|
joins = []
|
|
93
|
-
parent_prefix = self.prefix_map[self.schema_object.api_name]
|
|
96
|
+
parent_prefix = self.prefix_map[str(self.schema_object.api_name)]
|
|
94
97
|
for name, relation in self.schema_object.relations.items():
|
|
95
|
-
child_prefix = self.prefix_map[relation.api_name]
|
|
98
|
+
child_prefix = self.prefix_map[str(relation.api_name)]
|
|
96
99
|
if child_prefix in self.active_prefixes:
|
|
97
100
|
joins.append(
|
|
98
101
|
"INNER JOIN "
|
|
99
|
-
+ relation.child_schema_object.table_name
|
|
102
|
+
+ str(relation.child_schema_object.table_name)
|
|
100
103
|
+ " AS "
|
|
101
104
|
+ child_prefix
|
|
102
105
|
+ " ON "
|
|
@@ -110,9 +113,9 @@ class SQLSelectSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
110
113
|
)
|
|
111
114
|
|
|
112
115
|
return (
|
|
113
|
-
self.schema_object.table_name
|
|
116
|
+
str(self.schema_object.table_name)
|
|
114
117
|
+ " AS "
|
|
115
|
-
+ self.prefix_map[self.schema_object.api_name]
|
|
118
|
+
+ str(self.prefix_map[str(self.schema_object.api_name)])
|
|
116
119
|
+ (f" {' '.join(joins)}" if len(joins) > 0 else "")
|
|
117
120
|
)
|
|
118
121
|
|
|
@@ -152,7 +155,7 @@ class SQLSelectSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
152
155
|
raise ApplicationException(
|
|
153
156
|
400,
|
|
154
157
|
"Bad object association: "
|
|
155
|
-
+ schema_object.api_name
|
|
158
|
+
+ str(schema_object.api_name)
|
|
156
159
|
+ " does not have a "
|
|
157
160
|
+ relation
|
|
158
161
|
+ " property",
|
|
@@ -202,14 +205,14 @@ class SQLSelectSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
202
205
|
component = (
|
|
203
206
|
parts[0]
|
|
204
207
|
if len(parts) > 1
|
|
205
|
-
else self.prefix_map[self.schema_object.api_name]
|
|
208
|
+
else self.prefix_map[str(self.schema_object.api_name)]
|
|
206
209
|
)
|
|
207
210
|
object = object_set.get(component, {})
|
|
208
211
|
if not object:
|
|
209
212
|
object_set[component] = object
|
|
210
213
|
object[property.api_name] = property.convert_to_api_value(value)
|
|
211
214
|
|
|
212
|
-
result = object_set[self.prefix_map[self.schema_object.api_name]]
|
|
215
|
+
result = object_set[self.prefix_map[str(self.schema_object.api_name)]]
|
|
213
216
|
for name, prefix in self.prefix_map.items():
|
|
214
217
|
if name != self.schema_object.api_name and prefix in object_set:
|
|
215
218
|
result[name] = object_set[prefix]
|
|
@@ -239,7 +242,7 @@ class SQLSelectSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
239
242
|
# handle entity prefix
|
|
240
243
|
field_parts = field_name.split(".")
|
|
241
244
|
if len(field_parts) == 1:
|
|
242
|
-
prefix = self.prefix_map[self.schema_object.api_name]
|
|
245
|
+
prefix = self.prefix_map[str(self.schema_object.api_name)]
|
|
243
246
|
property = self.schema_object.properties.get(field_parts[0])
|
|
244
247
|
if not property:
|
|
245
248
|
raise ApplicationException(
|
|
@@ -39,25 +39,36 @@ class SQLUpdateSchemaQueryHandler(SQLSchemaQueryHandler):
|
|
|
39
39
|
|
|
40
40
|
@property
|
|
41
41
|
def update_values(self) -> str:
|
|
42
|
-
|
|
42
|
+
allowed_property_names = self.check_permissions(
|
|
43
43
|
"write", self.schema_object.permissions, self.schema_object.properties
|
|
44
44
|
)
|
|
45
|
+
allowed_properties = {
|
|
46
|
+
k: v
|
|
47
|
+
for k, v in self.schema_object.properties.items()
|
|
48
|
+
if k in allowed_property_names
|
|
49
|
+
}
|
|
45
50
|
self.store_placeholders = {}
|
|
46
51
|
columns = []
|
|
47
52
|
invalid_columns = []
|
|
48
53
|
|
|
54
|
+
import json
|
|
55
|
+
|
|
49
56
|
for name, value in self.operation.store_params.items():
|
|
50
|
-
|
|
57
|
+
property = allowed_properties.get(name, None)
|
|
58
|
+
if property is None:
|
|
51
59
|
invalid_columns.append(name)
|
|
52
|
-
|
|
53
|
-
property = allowed_properties.get(name, None)
|
|
60
|
+
continue
|
|
54
61
|
|
|
55
|
-
|
|
56
|
-
|
|
62
|
+
placeholder = (
|
|
63
|
+
str(property.api_name) if property.api_name is not None else name
|
|
64
|
+
)
|
|
65
|
+
column_name = property.column_name
|
|
57
66
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
columns.append(f"{column_name} = {self.placeholder(property, placeholder)}")
|
|
68
|
+
# Serialize embedded objects to JSON
|
|
69
|
+
if property.api_type == "object":
|
|
70
|
+
self.store_placeholders[placeholder] = json.dumps(value)
|
|
71
|
+
else:
|
|
61
72
|
self.store_placeholders[placeholder] = property.convert_to_db_value(
|
|
62
73
|
value
|
|
63
74
|
)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
from typing import Optional, Mapping
|
|
5
|
+
|
|
6
|
+
from api_foundry_query_engine.utils.app_exception import ApplicationException
|
|
7
|
+
from api_foundry_query_engine.adapters.gateway_adapter import GatewayAdapter
|
|
8
|
+
|
|
9
|
+
log = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
engine_config: Optional[Mapping[str, str]] = None
|
|
12
|
+
adapter: Optional[GatewayAdapter] = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class QueryEngine:
|
|
16
|
+
def __init__(self, config: Mapping[str, str]):
|
|
17
|
+
self.adapter = GatewayAdapter(config)
|
|
18
|
+
|
|
19
|
+
def handler(self, event):
|
|
20
|
+
log.debug(f"event: {event}")
|
|
21
|
+
try:
|
|
22
|
+
response = self.adapter.process_event(event)
|
|
23
|
+
|
|
24
|
+
# Ensure the response conforms to API Gateway requirements
|
|
25
|
+
return {
|
|
26
|
+
"isBase64Encoded": False,
|
|
27
|
+
"statusCode": 200,
|
|
28
|
+
"headers": {"Content-Type": "application/json"},
|
|
29
|
+
"body": json.dumps(response),
|
|
30
|
+
}
|
|
31
|
+
except ApplicationException as e:
|
|
32
|
+
log.error(f"exception: {e}", exc_info=True)
|
|
33
|
+
return {
|
|
34
|
+
"isBase64Encoded": False,
|
|
35
|
+
"statusCode": e.status_code,
|
|
36
|
+
"headers": {"Content-Type": "application/json"},
|
|
37
|
+
"body": json.dumps({"message": f"exception: {e}"}),
|
|
38
|
+
}
|
|
39
|
+
except Exception as e:
|
|
40
|
+
log.error(f"exception: {e}", exc_info=True)
|
|
41
|
+
return {
|
|
42
|
+
"isBase64Encoded": False,
|
|
43
|
+
"statusCode": 500,
|
|
44
|
+
"headers": {"Content-Type": "application/json"},
|
|
45
|
+
"body": json.dumps({"message": f"exception: {e}"}),
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def handler(event, _):
|
|
50
|
+
global engine_config, adapter
|
|
51
|
+
if engine_config is None:
|
|
52
|
+
engine_config = os.environ
|
|
53
|
+
|
|
54
|
+
query_engine = QueryEngine(engine_config)
|
|
55
|
+
return query_engine.handler(event)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import hashlib
|
|
2
2
|
import json
|
|
3
|
-
import os
|
|
4
3
|
|
|
5
4
|
from api_foundry_query_engine.utils.logger import logger
|
|
6
5
|
from api_foundry_query_engine.operation import Operation
|
|
@@ -9,23 +8,32 @@ log = logger(__name__)
|
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
class Service:
|
|
11
|
+
def __init__(self, config: dict = {}):
|
|
12
|
+
self.config = config
|
|
13
|
+
|
|
12
14
|
def execute(self, operation: Operation) -> list[dict]:
|
|
13
15
|
raise NotImplementedError
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
class ServiceAdapter(Service):
|
|
17
|
-
def
|
|
18
|
-
super().
|
|
19
|
+
def __init__(self, config: dict = {}):
|
|
20
|
+
super().__init__(config)
|
|
21
|
+
|
|
22
|
+
def execute(self, operation: Operation) -> list[dict]:
|
|
23
|
+
return super().execute(operation)
|
|
19
24
|
|
|
20
25
|
|
|
21
26
|
class MutationPublisher(ServiceAdapter):
|
|
27
|
+
def __init__(self, config: dict = {}):
|
|
28
|
+
super().__init__(config)
|
|
29
|
+
|
|
22
30
|
def execute(self, operation):
|
|
23
31
|
result = super().execute(operation)
|
|
24
32
|
self.publish_notification(operation)
|
|
25
33
|
return result
|
|
26
34
|
|
|
27
35
|
def publish_notification(self, operation):
|
|
28
|
-
topic_arn =
|
|
36
|
+
topic_arn = self.config.get("BROADCAST_TOPIC", None)
|
|
29
37
|
log.debug(f"Topic ARN: {topic_arn}")
|
|
30
38
|
|
|
31
39
|
if topic_arn is not None:
|
|
@@ -51,10 +59,11 @@ class MutationPublisher(ServiceAdapter):
|
|
|
51
59
|
)
|
|
52
60
|
log.info(f"publish msg id {msg_id}")
|
|
53
61
|
|
|
54
|
-
def __client(
|
|
62
|
+
def __client(self, client_type):
|
|
55
63
|
import boto3
|
|
56
64
|
|
|
57
|
-
|
|
65
|
+
region = self.config.get("AWS_REGION", "us-east-1")
|
|
66
|
+
session = boto3.Session()
|
|
58
67
|
if session:
|
|
59
68
|
return session.client(client_type, region_name=region)
|
|
60
69
|
return boto3.client(client_type, region_name=region)
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import traceback
|
|
2
|
+
from typing import Mapping
|
|
2
3
|
|
|
3
4
|
from api_foundry_query_engine.utils.logger import logger
|
|
4
5
|
from api_foundry_query_engine.utils.app_exception import ApplicationException
|
|
5
6
|
from api_foundry_query_engine.operation import Operation
|
|
6
7
|
from api_foundry_query_engine.services.service import ServiceAdapter
|
|
7
|
-
from api_foundry_query_engine.connectors.connection_factory import
|
|
8
|
+
from api_foundry_query_engine.connectors.connection_factory import ConnectionFactory
|
|
8
9
|
from api_foundry_query_engine.dao.operation_dao import OperationDAO
|
|
9
10
|
from api_foundry_query_engine.utils.api_model import (
|
|
10
11
|
get_path_operation,
|
|
@@ -15,7 +16,12 @@ log = logger(__name__)
|
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class TransactionalService(ServiceAdapter):
|
|
18
|
-
def
|
|
19
|
+
def __init__(self, config: Mapping[str, str]):
|
|
20
|
+
super().__init__()
|
|
21
|
+
self.config = config
|
|
22
|
+
self.connection_factory = ConnectionFactory(config)
|
|
23
|
+
|
|
24
|
+
def execute(self, operation: Operation) -> list[dict]:
|
|
19
25
|
path_operation = get_path_operation(operation.entity, operation.action)
|
|
20
26
|
if path_operation:
|
|
21
27
|
database = path_operation.database
|
|
@@ -28,17 +34,15 @@ class TransactionalService(ServiceAdapter):
|
|
|
28
34
|
500, f"Unknown operation: {operation.entity}"
|
|
29
35
|
)
|
|
30
36
|
|
|
31
|
-
|
|
37
|
+
# Pass config to connection_factory if needed (future extension)
|
|
38
|
+
connection = self.connection_factory.get_connection(database)
|
|
32
39
|
|
|
33
40
|
try:
|
|
34
|
-
result =
|
|
35
|
-
cursor = connection.cursor()
|
|
36
|
-
try:
|
|
37
|
-
result = OperationDAO(operation, connection.engine()).execute(cursor)
|
|
38
|
-
finally:
|
|
39
|
-
cursor.close()
|
|
41
|
+
result = OperationDAO(operation, connection.engine()).execute(connection)
|
|
40
42
|
if operation.action != "read":
|
|
41
43
|
connection.commit()
|
|
44
|
+
if isinstance(result, dict):
|
|
45
|
+
return [result]
|
|
42
46
|
return result
|
|
43
47
|
except Exception as error:
|
|
44
48
|
log.error(f"transaction exception: {error}")
|
|
@@ -1,12 +1,29 @@
|
|
|
1
|
+
import os
|
|
1
2
|
import yaml
|
|
2
3
|
|
|
3
4
|
from datetime import datetime
|
|
4
|
-
from typing import Any, Dict, Optional
|
|
5
|
+
from typing import Any, Dict, Mapping, Optional
|
|
5
6
|
|
|
6
7
|
from api_foundry_query_engine.utils.logger import logger
|
|
7
8
|
|
|
8
9
|
log = logger(__name__)
|
|
9
10
|
|
|
11
|
+
api_model = None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_schema_object(name: str) -> Optional["SchemaObject"]:
|
|
15
|
+
global api_model
|
|
16
|
+
if api_model is None:
|
|
17
|
+
return None
|
|
18
|
+
return api_model.schema_objects.get(name)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_path_operation(path: str, method: str) -> Optional["PathOperation"]:
|
|
22
|
+
global api_model
|
|
23
|
+
if api_model is None:
|
|
24
|
+
return None
|
|
25
|
+
return api_model.path_operations.get(f"{path}_{method}")
|
|
26
|
+
|
|
10
27
|
|
|
11
28
|
class SchemaObjectProperty:
|
|
12
29
|
"""Represents a property of a schema object."""
|
|
@@ -42,7 +59,9 @@ class SchemaObjectProperty:
|
|
|
42
59
|
"date-time": lambda x: datetime.fromisoformat(x) if x else None,
|
|
43
60
|
"time": lambda x: datetime.strptime(x, "%H:%M:%S").time() if x else None,
|
|
44
61
|
}
|
|
45
|
-
conversion_func = conversion_mapping.get(
|
|
62
|
+
conversion_func = conversion_mapping.get(
|
|
63
|
+
self.column_type if self.column_type is not None else "string", lambda x: x
|
|
64
|
+
)
|
|
46
65
|
return conversion_func(value)
|
|
47
66
|
|
|
48
67
|
def convert_to_api_value(self, value) -> Optional[Any]:
|
|
@@ -58,7 +77,9 @@ class SchemaObjectProperty:
|
|
|
58
77
|
"date-time": lambda x: x.isoformat() if x else None,
|
|
59
78
|
"time": lambda x: x.time().isoformat() if x else None,
|
|
60
79
|
}
|
|
61
|
-
conversion_func = conversion_mapping.get(
|
|
80
|
+
conversion_func = conversion_mapping.get(
|
|
81
|
+
self.api_type if self.api_type is not None else "string", lambda x: x
|
|
82
|
+
)
|
|
62
83
|
return conversion_func(value)
|
|
63
84
|
|
|
64
85
|
|
|
@@ -75,19 +96,39 @@ class SchemaObjectAssociation:
|
|
|
75
96
|
|
|
76
97
|
@property
|
|
77
98
|
def child_property(self) -> str:
|
|
78
|
-
|
|
79
|
-
self._child_property
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
)
|
|
99
|
+
if self._child_property:
|
|
100
|
+
return self._child_property
|
|
101
|
+
if not self.schema_name:
|
|
102
|
+
raise ValueError("schema_name is None in SchemaObjectAssociation")
|
|
103
|
+
child_schema = get_schema_object(self.schema_name)
|
|
104
|
+
if not child_schema:
|
|
105
|
+
raise ValueError(f"SchemaObject '{self.schema_name}' not found")
|
|
106
|
+
if not child_schema.primary_key:
|
|
107
|
+
raise ValueError(f"Primary key not defined for schema '{self.schema_name}'")
|
|
108
|
+
column_name = getattr(child_schema.primary_key, "column_name", None)
|
|
109
|
+
if column_name is None:
|
|
110
|
+
raise ValueError(
|
|
111
|
+
f"Primary key property does not have 'column_name' for schema '{self.schema_name}'"
|
|
112
|
+
)
|
|
113
|
+
return column_name
|
|
83
114
|
|
|
84
115
|
@property
|
|
85
116
|
def parent_property(self) -> str:
|
|
86
|
-
|
|
87
|
-
self._parent_property
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
117
|
+
if self._parent_property:
|
|
118
|
+
return self._parent_property
|
|
119
|
+
parent_schema_obj = get_schema_object(self.parent_schema)
|
|
120
|
+
if not parent_schema_obj:
|
|
121
|
+
raise ValueError(f"SchemaObject '{self.parent_schema}' not found")
|
|
122
|
+
if not parent_schema_obj.primary_key:
|
|
123
|
+
raise ValueError(
|
|
124
|
+
f"Primary key not defined for schema '{self.parent_schema}'"
|
|
125
|
+
)
|
|
126
|
+
column_name = getattr(parent_schema_obj.primary_key, "column_name", None)
|
|
127
|
+
if column_name is None:
|
|
128
|
+
raise ValueError(
|
|
129
|
+
f"Primary key property does not have 'column_name' for schema '{self.parent_schema}'"
|
|
130
|
+
)
|
|
131
|
+
return column_name
|
|
91
132
|
|
|
92
133
|
def __repr__(self):
|
|
93
134
|
return (
|
|
@@ -98,30 +139,37 @@ class SchemaObjectAssociation:
|
|
|
98
139
|
|
|
99
140
|
@property
|
|
100
141
|
def child_schema_object(self) -> "SchemaObject":
|
|
101
|
-
|
|
142
|
+
if self.schema_name is None:
|
|
143
|
+
raise ValueError("schema_name is None in SchemaObjectAssociation")
|
|
144
|
+
schema_obj = get_schema_object(self.schema_name)
|
|
145
|
+
if schema_obj is None:
|
|
146
|
+
raise ValueError(f"SchemaObject '{self.schema_name}' not found")
|
|
147
|
+
return schema_obj
|
|
102
148
|
|
|
103
149
|
|
|
104
150
|
class SchemaObject:
|
|
105
151
|
"""Represents a schema object in the API configuration."""
|
|
106
152
|
|
|
107
153
|
def __init__(self, data: Dict[str, Any]):
|
|
108
|
-
self.api_name = data.get("api_name")
|
|
109
|
-
self.database = data.get("database")
|
|
110
|
-
self.table_name = data.get("table_name")
|
|
111
|
-
self.properties = {
|
|
154
|
+
self.api_name: str = str(data.get("api_name"))
|
|
155
|
+
self.database: str = str(data.get("database"))
|
|
156
|
+
self.table_name: str = str(data.get("table_name"))
|
|
157
|
+
self.properties: Dict[str, SchemaObjectProperty] = {
|
|
112
158
|
name: SchemaObjectProperty(prop_data)
|
|
113
159
|
for name, prop_data in data.get("properties", {}).items()
|
|
114
160
|
}
|
|
115
161
|
self.relations = {
|
|
116
|
-
name: SchemaObjectAssociation(
|
|
162
|
+
name: SchemaObjectAssociation(
|
|
163
|
+
self.api_name if self.api_name is not None else "", assoc_data
|
|
164
|
+
)
|
|
117
165
|
for name, assoc_data in data.get("relations", {}).items()
|
|
118
166
|
}
|
|
119
167
|
self.concurrency_property = (
|
|
120
|
-
self.properties[data.get("concurrency_property")]
|
|
168
|
+
self.properties[str(data.get("concurrency_property"))]
|
|
121
169
|
if data.get("concurrency_property")
|
|
122
170
|
else None
|
|
123
171
|
)
|
|
124
|
-
self._primary_key = data.get("primary_key")
|
|
172
|
+
self._primary_key: str = str(data.get("primary_key"))
|
|
125
173
|
self.permissions = data.get("permissions")
|
|
126
174
|
|
|
127
175
|
def __repr__(self):
|
|
@@ -136,39 +184,22 @@ class PathOperation:
|
|
|
136
184
|
"""Represents a path operation in the API configuration."""
|
|
137
185
|
|
|
138
186
|
def __init__(self, data: Dict[str, Any]):
|
|
139
|
-
self.entity = data["entity"]
|
|
140
|
-
self.action = data["action"]
|
|
141
|
-
self.sql = data["sql"]
|
|
142
|
-
self.database = data["database"]
|
|
143
|
-
self.inputs = {
|
|
187
|
+
self.entity: str = data["entity"]
|
|
188
|
+
self.action: str = data["action"]
|
|
189
|
+
self.sql: str = data["sql"]
|
|
190
|
+
self.database: str = data["database"]
|
|
191
|
+
self.inputs: Dict[str, SchemaObjectProperty] = {
|
|
144
192
|
name: SchemaObjectProperty(input_data)
|
|
145
193
|
for name, input_data in data.get("inputs", {}).items()
|
|
146
194
|
}
|
|
147
|
-
self.outputs = {
|
|
195
|
+
self.outputs: Dict[str, SchemaObjectProperty] = {
|
|
148
196
|
name: SchemaObjectProperty(output_data)
|
|
149
197
|
for name, output_data in data.get("outputs", {}).items()
|
|
150
198
|
}
|
|
151
199
|
self.permissions = data.get("security")
|
|
152
200
|
|
|
153
201
|
def __repr__(self):
|
|
154
|
-
return f"PathOperation(entity={self.entity},
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
schema_objects = None
|
|
158
|
-
path_operations = None
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
def get_schema_object(name: str) -> Optional[SchemaObject]:
|
|
162
|
-
"""Returns a schema object by name."""
|
|
163
|
-
global schema_objects
|
|
164
|
-
return schema_objects.get(name)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
def get_path_operation(path: str, method: str) -> Optional[PathOperation]:
|
|
168
|
-
"""Returns a path operation by name."""
|
|
169
|
-
log.info(f"path: {path}, method: {method}")
|
|
170
|
-
global path_operations
|
|
171
|
-
return path_operations.get(f"{path}_{method}")
|
|
202
|
+
return f"PathOperation(entity={self.entity}, action={self.action})"
|
|
172
203
|
|
|
173
204
|
|
|
174
205
|
class APIModel:
|
|
@@ -176,17 +207,21 @@ class APIModel:
|
|
|
176
207
|
|
|
177
208
|
def __init__(self, config: Dict[str, Any]):
|
|
178
209
|
print("building api_model")
|
|
179
|
-
|
|
180
|
-
schema_objects = {
|
|
210
|
+
self.schema_objects = {
|
|
181
211
|
name: SchemaObject(schema_data)
|
|
182
212
|
for name, schema_data in config.get("schema_objects", {}).items()
|
|
183
213
|
}
|
|
184
|
-
|
|
185
|
-
path_operations = {
|
|
214
|
+
self.path_operations = {
|
|
186
215
|
name: PathOperation(path_data)
|
|
187
216
|
for name, path_data in config.get("path_operations", {}).items()
|
|
188
217
|
}
|
|
189
218
|
|
|
219
|
+
def get_path_operation(self, path: str, method: str) -> Optional[PathOperation]:
|
|
220
|
+
"""Returns a path operation by name."""
|
|
221
|
+
if self.path_operations is None:
|
|
222
|
+
return None
|
|
223
|
+
return self.path_operations.get(f"{path}_{method}")
|
|
224
|
+
|
|
190
225
|
def __repr__(self):
|
|
191
226
|
return (
|
|
192
227
|
f"APIModel(schema_objects={list(self.schema_objects.keys())}, "
|
|
@@ -194,6 +229,13 @@ class APIModel:
|
|
|
194
229
|
)
|
|
195
230
|
|
|
196
231
|
|
|
197
|
-
def
|
|
198
|
-
|
|
199
|
-
|
|
232
|
+
def set_api_model(engine_config: Mapping[str, str]):
|
|
233
|
+
global api_model
|
|
234
|
+
if api_model is None:
|
|
235
|
+
if engine_config.get("API_SPEC"):
|
|
236
|
+
api_model = APIModel(yaml.safe_load(engine_config["API_SPEC"]))
|
|
237
|
+
else:
|
|
238
|
+
with open(
|
|
239
|
+
os.environ.get("API_SPEC", "/var/task/api_spec.yaml"), "r"
|
|
240
|
+
) as file:
|
|
241
|
+
api_model = APIModel(yaml.safe_load(file))
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import logging
|
|
3
|
-
import os
|
|
4
|
-
|
|
5
|
-
from api_foundry_query_engine.utils.app_exception import ApplicationException
|
|
6
|
-
from api_foundry_query_engine.adapters.gateway_adapter import GatewayAdapter
|
|
7
|
-
from api_foundry_query_engine.utils.api_model import load_api
|
|
8
|
-
|
|
9
|
-
log = logging.getLogger(__name__)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
load_api(os.environ.get("API_SPEC", "/var/task/api_spec.yaml"))
|
|
13
|
-
adapter = GatewayAdapter()
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def handler(event, _):
|
|
17
|
-
log.debug(f"event: {event}")
|
|
18
|
-
try:
|
|
19
|
-
response = adapter.process_event(event)
|
|
20
|
-
|
|
21
|
-
# Ensure the response conforms to API Gateway requirements
|
|
22
|
-
return {
|
|
23
|
-
"isBase64Encoded": False,
|
|
24
|
-
"statusCode": 200,
|
|
25
|
-
"headers": {"Content-Type": "application/json"},
|
|
26
|
-
"body": json.dumps(response),
|
|
27
|
-
}
|
|
28
|
-
except ApplicationException as e:
|
|
29
|
-
log.error(f"exception: {e}", exc_info=True)
|
|
30
|
-
return {
|
|
31
|
-
"isBase64Encoded": False,
|
|
32
|
-
"statusCode": e.status_code,
|
|
33
|
-
"headers": {"Content-Type": "application/json"},
|
|
34
|
-
"body": json.dumps({"message": f"exception: {e}"}),
|
|
35
|
-
}
|
|
36
|
-
except Exception as e:
|
|
37
|
-
log.error(f"exception: {e}", exc_info=True)
|
|
38
|
-
return {
|
|
39
|
-
"isBase64Encoded": False,
|
|
40
|
-
"statusCode": 500,
|
|
41
|
-
"headers": {"Content-Type": "application/json"},
|
|
42
|
-
"body": json.dumps({"message": f"exception: {e}"}),
|
|
43
|
-
}
|
|
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
|