api-foundry-query-engine 0.0.1__py3-none-any.whl
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/adapters/adapter.py +67 -0
- api_foundry_query_engine/adapters/case_change_adapter.py +79 -0
- api_foundry_query_engine/adapters/gateway_adapter.py +112 -0
- api_foundry_query_engine/connectors/connection.py +32 -0
- api_foundry_query_engine/connectors/connection_factory.py +110 -0
- api_foundry_query_engine/connectors/oracle_connector.py +29 -0
- api_foundry_query_engine/connectors/postgres_connection.py +105 -0
- api_foundry_query_engine/dao/dao.py +23 -0
- api_foundry_query_engine/dao/operation_dao.py +141 -0
- api_foundry_query_engine/dao/sql_custom_query_handler.py +66 -0
- api_foundry_query_engine/dao/sql_delete_query_handler.py +36 -0
- api_foundry_query_engine/dao/sql_insert_query_handler.py +102 -0
- api_foundry_query_engine/dao/sql_query_handler.py +386 -0
- api_foundry_query_engine/dao/sql_select_query_handler.py +305 -0
- api_foundry_query_engine/dao/sql_subselect_query_handler.py +63 -0
- api_foundry_query_engine/dao/sql_update_query_handler.py +59 -0
- api_foundry_query_engine/handler.py +48 -0
- api_foundry_query_engine/operation.py +15 -0
- api_foundry_query_engine/services/service.py +60 -0
- api_foundry_query_engine/services/transactional_service.py +43 -0
- api_foundry_query_engine/utils/api_model.py +175 -0
- api_foundry_query_engine/utils/app_exception.py +22 -0
- api_foundry_query_engine/utils/logger.py +60 -0
- api_foundry_query_engine-0.0.1.dist-info/METADATA +14 -0
- api_foundry_query_engine-0.0.1.dist-info/RECORD +27 -0
- api_foundry_query_engine-0.0.1.dist-info/WHEEL +4 -0
- api_foundry_query_engine-0.0.1.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from api_foundry_query_engine.operation import Operation
|
|
5
|
+
from api_foundry_query_engine.services.transactional_service import TransactionalService
|
|
6
|
+
from api_foundry_query_engine.utils.logger import logger
|
|
7
|
+
from api_foundry_query_engine.services.service import Service
|
|
8
|
+
|
|
9
|
+
log = logger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Adapter(metaclass=abc.ABCMeta):
|
|
13
|
+
service: Service
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def __subclasshook__(cls, __subclass: type) -> bool:
|
|
17
|
+
return (
|
|
18
|
+
hasattr(__subclass, "marshal")
|
|
19
|
+
and callable(__subclass.marshal)
|
|
20
|
+
and hasattr(__subclass, "umarshal")
|
|
21
|
+
and callable(__subclass.unmarshal)
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
def __init__(self, service: Optional[Service] = None) -> None:
|
|
25
|
+
self.service = service if service is not None else TransactionalService()
|
|
26
|
+
|
|
27
|
+
def unmarshal(self, event) -> Operation:
|
|
28
|
+
"""
|
|
29
|
+
Unmarshal the event in a tuple for processing
|
|
30
|
+
|
|
31
|
+
Parameters:
|
|
32
|
+
- event (dict): Lambda event object.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
- Operation containing the entity, action, and parameters
|
|
36
|
+
"""
|
|
37
|
+
raise NotImplementedError
|
|
38
|
+
|
|
39
|
+
def marshal(self, result: list[dict]):
|
|
40
|
+
"""
|
|
41
|
+
Marshal the result into a event response
|
|
42
|
+
|
|
43
|
+
Parameters:
|
|
44
|
+
- result (list): the data set to return in the response
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
- the event response
|
|
48
|
+
"""
|
|
49
|
+
return result
|
|
50
|
+
|
|
51
|
+
def process_event(self, event):
|
|
52
|
+
"""
|
|
53
|
+
Process Lambda event using a domain function.
|
|
54
|
+
|
|
55
|
+
Parameters:
|
|
56
|
+
- service_function (callable): The service function to be executed.
|
|
57
|
+
- event (dict): Lambda event object.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
- any: Result of the domain function.
|
|
61
|
+
"""
|
|
62
|
+
operation = self.unmarshal(event)
|
|
63
|
+
|
|
64
|
+
result = self.service.execute(operation)
|
|
65
|
+
log.debug(f"adapter result: {result}")
|
|
66
|
+
|
|
67
|
+
return self.marshal(result)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from humps import camelize, decamelize
|
|
2
|
+
|
|
3
|
+
from api_foundry_query_engine.adapters.adapter import Adapter
|
|
4
|
+
from api_foundry_query_engine.utils.logger import logger
|
|
5
|
+
from api_foundry_query_engine.operation import Operation
|
|
6
|
+
|
|
7
|
+
log = logger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CaseChangeAdapter(Adapter):
|
|
11
|
+
"""
|
|
12
|
+
Handles changing case from snake to camel and back
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def unmarshal(self, event) -> Operation:
|
|
16
|
+
"""
|
|
17
|
+
Unmarshal the event in a tuple for processing
|
|
18
|
+
|
|
19
|
+
Parameters:
|
|
20
|
+
- event (dict): Lambda event object.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
- tuple: Tuple containing entity operation, store_params, query_params and metadata params.
|
|
24
|
+
"""
|
|
25
|
+
operation = super().unmarshal(event)
|
|
26
|
+
|
|
27
|
+
# determine case
|
|
28
|
+
self.camel_case = (
|
|
29
|
+
operation.metadata_params.get("_case", "snake") == "camel"
|
|
30
|
+
or self.__check_camel_case(operation.store_params)
|
|
31
|
+
or self.__check_camel_case(operation.query_params)
|
|
32
|
+
)
|
|
33
|
+
log.info(f"camel_case: {self.camel_case}")
|
|
34
|
+
|
|
35
|
+
if self.camel_case:
|
|
36
|
+
return Operation(
|
|
37
|
+
path=operation.entity,
|
|
38
|
+
action=operation.action,
|
|
39
|
+
store_params=decamelize(operation.store_params),
|
|
40
|
+
query_params=decamelize(operation.query_params),
|
|
41
|
+
metadata_params=operation.metadata_params,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
return operation
|
|
45
|
+
|
|
46
|
+
def marshal(self, result: list[dict]):
|
|
47
|
+
"""
|
|
48
|
+
Marshal the result into a event response
|
|
49
|
+
|
|
50
|
+
Parameters:
|
|
51
|
+
- result (list): the data set to return in the response
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
- the event response
|
|
55
|
+
"""
|
|
56
|
+
super().marshal(result)
|
|
57
|
+
|
|
58
|
+
if not self.camel_case:
|
|
59
|
+
return result
|
|
60
|
+
|
|
61
|
+
converted_result = []
|
|
62
|
+
for item in result:
|
|
63
|
+
converted_result.append(camelize(item))
|
|
64
|
+
|
|
65
|
+
# convert back to camel case if needed
|
|
66
|
+
return converted_result
|
|
67
|
+
|
|
68
|
+
def __check_camel_case(self, params: dict) -> bool:
|
|
69
|
+
if params is not None:
|
|
70
|
+
# check the keys for an upper case character
|
|
71
|
+
for param in params:
|
|
72
|
+
if (
|
|
73
|
+
param != param.lower()
|
|
74
|
+
and param != param.upper()
|
|
75
|
+
and "_" not in param
|
|
76
|
+
):
|
|
77
|
+
return True
|
|
78
|
+
|
|
79
|
+
return False
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from api_foundry_query_engine.adapters.adapter import Adapter
|
|
4
|
+
from api_foundry_query_engine.operation import Operation
|
|
5
|
+
|
|
6
|
+
actions_map = {
|
|
7
|
+
"GET": "read",
|
|
8
|
+
"POST": "create",
|
|
9
|
+
"PUT": "update",
|
|
10
|
+
"DELETE": "delete",
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class GatewayAdapter(Adapter):
|
|
15
|
+
def marshal(self, result: list[dict]):
|
|
16
|
+
"""
|
|
17
|
+
Marshal the result into a event response
|
|
18
|
+
|
|
19
|
+
Parameters:
|
|
20
|
+
- result (list): the data set to return in the response
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
- the event response
|
|
24
|
+
"""
|
|
25
|
+
return super().marshal(result)
|
|
26
|
+
|
|
27
|
+
def unmarshal(self, event):
|
|
28
|
+
"""
|
|
29
|
+
Get parameters from the Lambda event.
|
|
30
|
+
|
|
31
|
+
Parameters:
|
|
32
|
+
- event (dict): Lambda event object.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
- tuple: Tuple containing data, query and metadata parameters.
|
|
36
|
+
"""
|
|
37
|
+
entity = event.get("resource").split("/")[1]
|
|
38
|
+
action = actions_map.get(event.get("httpMethod").upper(), "read")
|
|
39
|
+
|
|
40
|
+
event_params = {}
|
|
41
|
+
|
|
42
|
+
path_parameters = self._convert_parameters(event.get("pathParameters"))
|
|
43
|
+
if path_parameters is not None:
|
|
44
|
+
event_params.update(path_parameters)
|
|
45
|
+
|
|
46
|
+
queryStringParameters = self._convert_parameters(
|
|
47
|
+
event.get("queryStringParameters")
|
|
48
|
+
)
|
|
49
|
+
if queryStringParameters is not None:
|
|
50
|
+
event_params.update(queryStringParameters)
|
|
51
|
+
|
|
52
|
+
query_params, metadata_params = self.split_params(event_params)
|
|
53
|
+
|
|
54
|
+
store_params = {}
|
|
55
|
+
body = event.get("body")
|
|
56
|
+
if body is not None and len(body) > 0:
|
|
57
|
+
store_params = json.loads(body)
|
|
58
|
+
|
|
59
|
+
return Operation(
|
|
60
|
+
entity=entity,
|
|
61
|
+
action=action,
|
|
62
|
+
store_params=store_params,
|
|
63
|
+
query_params=query_params,
|
|
64
|
+
metadata_params=metadata_params,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def _convert_parameters(self, parameters):
|
|
68
|
+
"""
|
|
69
|
+
Convert parameters to appropriate types.
|
|
70
|
+
|
|
71
|
+
Parameters:
|
|
72
|
+
- parameters (dict): Dictionary of parameters.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
- dict: Dictionary with parameters converted to appropriate types.
|
|
76
|
+
"""
|
|
77
|
+
if parameters is None:
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
result = {}
|
|
81
|
+
for parameter, value in parameters.items():
|
|
82
|
+
try:
|
|
83
|
+
result[parameter] = int(value)
|
|
84
|
+
except ValueError:
|
|
85
|
+
try:
|
|
86
|
+
result[parameter] = float(value)
|
|
87
|
+
except ValueError:
|
|
88
|
+
result[parameter] = value
|
|
89
|
+
return result
|
|
90
|
+
|
|
91
|
+
def split_params(self, parameters: dict):
|
|
92
|
+
"""
|
|
93
|
+
Split a dictionary into two dictionaries based on keys.
|
|
94
|
+
|
|
95
|
+
Parameters:
|
|
96
|
+
- dictionary (dict): Input dictionary.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
- tuple: A tuple containing two dictionaries.
|
|
100
|
+
The first dictionary contains metadata_params,
|
|
101
|
+
and the second dictionary query_params.
|
|
102
|
+
"""
|
|
103
|
+
query_params = {}
|
|
104
|
+
metadata_params = {}
|
|
105
|
+
|
|
106
|
+
for key, value in parameters.items():
|
|
107
|
+
if key.startswith("__"):
|
|
108
|
+
metadata_params[key] = value
|
|
109
|
+
else:
|
|
110
|
+
query_params[key] = value
|
|
111
|
+
|
|
112
|
+
return query_params, metadata_params
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from api_foundry_query_engine.utils.logger import logger
|
|
2
|
+
|
|
3
|
+
# Initialize the logger
|
|
4
|
+
log = logger(__name__)
|
|
5
|
+
|
|
6
|
+
db_config_map = dict()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Cursor:
|
|
10
|
+
def execute(self, sql: str, params: dict, selection_results: dict) -> list[dict]:
|
|
11
|
+
raise NotImplementedError
|
|
12
|
+
|
|
13
|
+
def close(self):
|
|
14
|
+
raise NotImplementedError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Connection:
|
|
18
|
+
def __init__(self, db_config: dict) -> None:
|
|
19
|
+
super().__init__()
|
|
20
|
+
self.db_config = db_config
|
|
21
|
+
|
|
22
|
+
def engine(self) -> str:
|
|
23
|
+
return self.db_config["engine"]
|
|
24
|
+
|
|
25
|
+
def cursor(self) -> Cursor:
|
|
26
|
+
raise NotImplementedError
|
|
27
|
+
|
|
28
|
+
def commit(self):
|
|
29
|
+
raise NotImplementedError
|
|
30
|
+
|
|
31
|
+
def close(self):
|
|
32
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import boto3
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from api_foundry_query_engine.connectors.connection import Connection
|
|
6
|
+
from api_foundry_query_engine.utils.app_exception import ApplicationException
|
|
7
|
+
from api_foundry_query_engine.utils.logger import logger
|
|
8
|
+
|
|
9
|
+
log = logger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ConnectionFactory:
|
|
13
|
+
db_config_map: dict[str, dict]
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self.db_config_map = dict()
|
|
17
|
+
|
|
18
|
+
def get_connection(self, database: str) -> Connection:
|
|
19
|
+
"""
|
|
20
|
+
Factory function to create a database connector based on the
|
|
21
|
+
specified engine and schema.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
- engine (str): The database engine type
|
|
25
|
+
('postgres', 'oracle', or 'mysql').
|
|
26
|
+
- schema (str): The schema for the database.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
- Connector: An instance of the appropriate Connector subclass.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
# Get the secret name based on the engine and database from the secrets map
|
|
33
|
+
log.info(f"database: {database}")
|
|
34
|
+
db_config = self.db_config_map.get(database)
|
|
35
|
+
if not db_config:
|
|
36
|
+
secret_name = json.loads(os.environ.get("SECRETS", "{}")).get(database)
|
|
37
|
+
log.info(f"secret_name: {secret_name}")
|
|
38
|
+
|
|
39
|
+
if secret_name:
|
|
40
|
+
db_config = self.__get_secret(secret_name)
|
|
41
|
+
else:
|
|
42
|
+
raise ValueError(f"Secret not found for database: {database}")
|
|
43
|
+
|
|
44
|
+
engine = db_config.get("engine")
|
|
45
|
+
if not engine:
|
|
46
|
+
raise ApplicationException(
|
|
47
|
+
500, "Database 'engine' is not defined in the secret."
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if engine == "postgres":
|
|
51
|
+
from .postgres_connection import PostgresConnection
|
|
52
|
+
|
|
53
|
+
return PostgresConnection(db_config)
|
|
54
|
+
|
|
55
|
+
# Add support for other engines here if needed in the future
|
|
56
|
+
|
|
57
|
+
raise ValueError(f"Unsupported database engine: {engine}")
|
|
58
|
+
|
|
59
|
+
def __get_secret(self, db_secret_name: str):
|
|
60
|
+
"""
|
|
61
|
+
Get the secret from AWS Secrets Manager.
|
|
62
|
+
|
|
63
|
+
Parameters:
|
|
64
|
+
- db_secret_name (str): The name of the AWS Secrets Manager secret.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
- dict: The database configuration obtained from the secret.
|
|
68
|
+
"""
|
|
69
|
+
endpoint_url = os.environ.get("AWS_ENDPOINT_URL") # LocalStack endpoint
|
|
70
|
+
sts_client = boto3.client("sts", endpoint_url=endpoint_url)
|
|
71
|
+
|
|
72
|
+
secret_account_id = os.environ.get("SECRET_ACCOUNT_ID", None)
|
|
73
|
+
log.info(f"secret_account_id: {secret_account_id}")
|
|
74
|
+
|
|
75
|
+
if secret_account_id:
|
|
76
|
+
# If a secret account ID is provided, assume a role in that account
|
|
77
|
+
secret_role = os.environ.get("ROLE_NAME", None)
|
|
78
|
+
assume_role_response = sts_client.assume_role(
|
|
79
|
+
RoleArn=f"arn:aws:iam::{secret_account_id}:role/{secret_role}",
|
|
80
|
+
RoleSessionName="AssumeRoleSession",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
credentials = assume_role_response["Credentials"]
|
|
84
|
+
|
|
85
|
+
secretsmanager = boto3.client(
|
|
86
|
+
"secretsmanager",
|
|
87
|
+
aws_access_key_id=credentials["AccessKeyId"],
|
|
88
|
+
aws_secret_access_key=credentials["SecretAccessKey"],
|
|
89
|
+
aws_session_token=credentials["SessionToken"],
|
|
90
|
+
endpoint_url=endpoint_url,
|
|
91
|
+
)
|
|
92
|
+
else:
|
|
93
|
+
# If no secret account ID is provided, use the default account
|
|
94
|
+
log.info(f"endpoint_url: {endpoint_url}")
|
|
95
|
+
secretsmanager = boto3.client(
|
|
96
|
+
"secretsmanager",
|
|
97
|
+
# endpoint_url=endpoint_url,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Get the secret value from AWS Secrets Manager
|
|
101
|
+
log.info(f"db_secret_name: {db_secret_name}")
|
|
102
|
+
db_secret = secretsmanager.describe_secret(SecretId=db_secret_name)
|
|
103
|
+
db_secret = secretsmanager.get_secret_value(SecretId=db_secret_name)
|
|
104
|
+
log.debug(f"loading secret name: {db_secret}")
|
|
105
|
+
|
|
106
|
+
# Return the parsed JSON secret string
|
|
107
|
+
return json.loads(db_secret.get("SecretString"))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
connection_factory = ConnectionFactory()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from api_foundry_query_engine.utils.logger import logger
|
|
2
|
+
from api_foundry_query_engine.utils.app_exception import ApplicationException
|
|
3
|
+
from api_foundry_query_engine.connectors.connection import Connector
|
|
4
|
+
|
|
5
|
+
log = logger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OracleConnnector(Connector):
|
|
9
|
+
def __init__(self, db_secret_name: str) -> None:
|
|
10
|
+
super().__init__(db_secret_name)
|
|
11
|
+
|
|
12
|
+
def close(self):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
def execute(self, cursor, sql: str, parameters: dict):
|
|
16
|
+
from oracledb import Error, IntegrityError, ProgrammingError
|
|
17
|
+
|
|
18
|
+
log.debug(f"sql: {sql}, parameters: {parameters}")
|
|
19
|
+
try:
|
|
20
|
+
cursor.execute(sql, parameters)
|
|
21
|
+
except IntegrityError as err:
|
|
22
|
+
(error,) = err.args
|
|
23
|
+
raise ApplicationException(409, error.message)
|
|
24
|
+
except ProgrammingError as err:
|
|
25
|
+
(error,) = err.args
|
|
26
|
+
raise ApplicationException(400, error.message)
|
|
27
|
+
except Error as err:
|
|
28
|
+
(error,) = err.args
|
|
29
|
+
raise ApplicationException(500, error.message)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from api_foundry_query_engine.connectors.connection import Connection, Cursor
|
|
2
|
+
from api_foundry_query_engine.utils.logger import logger
|
|
3
|
+
|
|
4
|
+
# Initialize the logger
|
|
5
|
+
log = logger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PostgresCursor(Cursor):
|
|
9
|
+
def __init__(self, cursor):
|
|
10
|
+
self.__cursor = cursor
|
|
11
|
+
|
|
12
|
+
def execute(self, sql: str, parameters: dict, result_columns: list[str]) -> list:
|
|
13
|
+
"""
|
|
14
|
+
Execute SQL statements on the PostgreSQL database.
|
|
15
|
+
|
|
16
|
+
Parameters:
|
|
17
|
+
- cursor: The database cursor.
|
|
18
|
+
- sql (str): The SQL statement to execute.
|
|
19
|
+
- parameters (dict): Parameters to be used in the SQL statement.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
- None
|
|
23
|
+
|
|
24
|
+
Raises:
|
|
25
|
+
- AppException: Custom exception for handling database-related errors.
|
|
26
|
+
"""
|
|
27
|
+
from psycopg2 import Error, IntegrityError, ProgrammingError
|
|
28
|
+
|
|
29
|
+
log.info(f"sql: {sql}, parameters: {parameters}")
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
# Execute the SQL statement with parameters
|
|
33
|
+
log.info(f"sql: {self.__cursor.mogrify(sql, parameters)}")
|
|
34
|
+
self.__cursor.execute(sql, parameters)
|
|
35
|
+
result = []
|
|
36
|
+
for record in self.__cursor:
|
|
37
|
+
# Convert record tuple to dictionary using result_columns
|
|
38
|
+
result.append(
|
|
39
|
+
{col: value for col, value in zip(result_columns, record)}
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
return result
|
|
43
|
+
except IntegrityError as err:
|
|
44
|
+
# Handle integrity constraint violation (e.g., duplicate key)
|
|
45
|
+
raise Exception(409, err.pgerror)
|
|
46
|
+
except ProgrammingError as err:
|
|
47
|
+
# Handle programming errors (e.g., syntax error in SQL)
|
|
48
|
+
raise Exception(400, err.pgerror)
|
|
49
|
+
except Error as err:
|
|
50
|
+
# Handle other database errors
|
|
51
|
+
raise Exception(500, err.pgerror)
|
|
52
|
+
|
|
53
|
+
def close(self):
|
|
54
|
+
self.__cursor.close()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class PostgresConnection(Connection):
|
|
58
|
+
def __init__(self, db_config: dict) -> None:
|
|
59
|
+
super().__init__(db_config)
|
|
60
|
+
self.__connection = self.get_connection()
|
|
61
|
+
|
|
62
|
+
def cursor(self) -> Cursor:
|
|
63
|
+
return PostgresCursor(self.__connection.cursor())
|
|
64
|
+
|
|
65
|
+
def close(self):
|
|
66
|
+
self.__connection.close()
|
|
67
|
+
|
|
68
|
+
def commit(self):
|
|
69
|
+
self.__connection.commit()
|
|
70
|
+
|
|
71
|
+
def get_connection(self):
|
|
72
|
+
"""
|
|
73
|
+
Get a connection to the PostgreSQL database.
|
|
74
|
+
|
|
75
|
+
Parameters:
|
|
76
|
+
- schema (str, optional): The database schema to set for the
|
|
77
|
+
connection.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
- connection: A connection to the PostgreSQL database.
|
|
81
|
+
"""
|
|
82
|
+
from psycopg2 import connect
|
|
83
|
+
|
|
84
|
+
dbname = self.db_config["dbname"]
|
|
85
|
+
user = self.db_config["username"]
|
|
86
|
+
password = self.db_config["password"]
|
|
87
|
+
host = self.db_config["host"]
|
|
88
|
+
port = self.db_config.get("port", 5432)
|
|
89
|
+
additional_config = self.db_config.get("configuration", {})
|
|
90
|
+
|
|
91
|
+
# Merge additional configuration parameters with the main connection parameters
|
|
92
|
+
connection_params = {
|
|
93
|
+
"dbname": dbname,
|
|
94
|
+
"user": user,
|
|
95
|
+
"password": password,
|
|
96
|
+
"host": host,
|
|
97
|
+
"port": port,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
connection_params.update(additional_config)
|
|
101
|
+
|
|
102
|
+
log.info(f"connection_params: {connection_params}")
|
|
103
|
+
|
|
104
|
+
# Create a connection to the PostgreSQL database
|
|
105
|
+
return connect(**connection_params)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
from api_foundry_query_engine.connectors.connection import Connection
|
|
5
|
+
from api_foundry_query_engine.operation import Operation
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DAO(metaclass=abc.ABCMeta):
|
|
9
|
+
@classmethod
|
|
10
|
+
def __subclasshook__(cls, __subclass: type) -> bool:
|
|
11
|
+
return hasattr(__subclass, "execute") and callable(__subclass.execute)
|
|
12
|
+
|
|
13
|
+
def execute(
|
|
14
|
+
self, connector: Connection, operation: Operation
|
|
15
|
+
) -> Union[list[dict], dict]:
|
|
16
|
+
raise NotImplementedError
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DAOAdapter(DAO):
|
|
20
|
+
def execute(
|
|
21
|
+
self, connector: Connection, operation: Operation
|
|
22
|
+
) -> Union[list[dict], dict]:
|
|
23
|
+
return super().execute(connector, operation)
|