jaaql-middleware-python 4.24.4__tar.gz → 4.24.6__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.
- {jaaql-middleware-python-4.24.4/jaaql_middleware_python.egg-info → jaaql-middleware-python-4.24.6}/PKG-INFO +1 -1
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/constants.py +1 -1
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/db/db_interface.py +241 -241
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/db/db_pg_interface.py +247 -247
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/db/db_utils.py +5 -2
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/documentation/documentation_internal.py +16 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/interpreter/interpret_jaaql.py +66 -7
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/mvc/controller.py +4 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/mvc/model.py +93 -1
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6/jaaql_middleware_python.egg-info}/PKG-INFO +1 -1
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/LICENSE.txt +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/README.md +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/__init__.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/config/__init__.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/config/config-docker.ini +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/config/config-test.ini +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/config/config.ini +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/config_constants.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/db/__init__.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/db/db_utils_no_circ.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/documentation/__init__.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/documentation/documentation_public.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/documentation/documentation_shared.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/email/__init__.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/email/email_manager.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/email/email_manager_service.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/email/patch_ems.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/exceptions/__init__.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/exceptions/custom_http_status.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/exceptions/http_status_exception.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/exceptions/jaaql_interpretable_handled_errors.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/exceptions/not_yet_implement_exception.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/generated_constants.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/interpreter/__init__.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/jaaql.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/migrations/__init__.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/migrations/migrations.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/mvc/__init__.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/mvc/base_controller.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/mvc/base_model.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/mvc/controller_interface.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/mvc/exception_queries.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/mvc/generated_queries.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/mvc/handmade_queries.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/mvc/model_interface.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/mvc/response.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/openapi/__init__.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/openapi/swagger_documentation.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/patch.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/scripts/01.install_domains.generated.sql +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/scripts/02.install_super_user.exceptions.sql +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/scripts/03.install_super_user.handwritten.sql +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/scripts/04.install_jaaql_data_structures.generated.sql +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/scripts/05.install_jaaql.exceptions.sql +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/scripts/06.install_jaaql.handwritten.sql +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/scripts/ZZZZ.generated_functions_views_and_permissions.sql +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/scripts/ZZZZ.reset_references.sql +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/scripts/swagger_template.html +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/services/__init__.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/services/cached_canned_query_service.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/services/migrations_manager_service.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/services/patch_mms.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/services/patch_shared_var_service.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/services/shared_var_service.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/utilities/__init__.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/utilities/crypt_utils.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/utilities/options.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/utilities/utils.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/utilities/utils_no_project_imports.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql/utilities/vault.py +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql_middleware_python.egg-info/SOURCES.txt +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql_middleware_python.egg-info/dependency_links.txt +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql_middleware_python.egg-info/requires.txt +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/jaaql_middleware_python.egg-info/top_level.txt +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/setup.cfg +0 -0
- {jaaql-middleware-python-4.24.4 → jaaql-middleware-python-4.24.6}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: jaaql-middleware-python
|
|
3
|
-
Version: 4.24.
|
|
3
|
+
Version: 4.24.6
|
|
4
4
|
Summary: The jaaql package, allowing for rapid development and deployment of RESTful HTTP applications
|
|
5
5
|
Home-page: https://github.com/JAAQL/JAAQL-middleware-python
|
|
6
6
|
Author: Software Quality Measurement and Improvement bv
|
|
@@ -1,241 +1,241 @@
|
|
|
1
|
-
import traceback
|
|
2
|
-
from abc import ABC, abstractmethod
|
|
3
|
-
from datetime import datetime
|
|
4
|
-
import logging
|
|
5
|
-
from jaaql.exceptions.http_status_exception import *
|
|
6
|
-
from typing import Optional
|
|
7
|
-
from jaaql.utilities.utils_no_project_imports import objectify
|
|
8
|
-
import queue
|
|
9
|
-
|
|
10
|
-
ERR__unknown_echo = "Unknown echo type '%s'. Please use either %s"
|
|
11
|
-
|
|
12
|
-
KEY_CONFIG__system = "SYSTEM"
|
|
13
|
-
KEY_CONFIG__logging = "logging"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
RET__echo = "echo"
|
|
17
|
-
RET__columns = "columns"
|
|
18
|
-
RET__type_codes = "type_codes"
|
|
19
|
-
RET__rows = "rows"
|
|
20
|
-
|
|
21
|
-
DIVIDER__protocol = "://"
|
|
22
|
-
DIVIDER__db = "/"
|
|
23
|
-
DIVIDER__port = ":"
|
|
24
|
-
DIVIDER__password = ":"
|
|
25
|
-
DIVIDER__address = "@"
|
|
26
|
-
|
|
27
|
-
ECHO__none = False
|
|
28
|
-
ECHO__debug = "debug"
|
|
29
|
-
ECHO__execute = True
|
|
30
|
-
ECHO__allowed = [ECHO__none, ECHO__debug, ECHO__execute]
|
|
31
|
-
|
|
32
|
-
CHAR__newline = "\r\n"
|
|
33
|
-
|
|
34
|
-
FILE__read = "r"
|
|
35
|
-
FILE__query_separator = ";"
|
|
36
|
-
|
|
37
|
-
ERR__missing_database = "The database name was missing from the connection"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class DBInterface(ABC):
|
|
41
|
-
|
|
42
|
-
@abstractmethod
|
|
43
|
-
def __init__(self, config, address: str, username: str):
|
|
44
|
-
self.config = config
|
|
45
|
-
self.output_exceptions = self.config["DEBUG"]["output_query_exceptions"].lower() == "true"
|
|
46
|
-
self.logging = config.get(KEY_CONFIG__system, {KEY_CONFIG__logging: True}).get(KEY_CONFIG__logging, True)
|
|
47
|
-
self.username = username
|
|
48
|
-
self.address = address
|
|
49
|
-
|
|
50
|
-
@staticmethod
|
|
51
|
-
def fracture_uri(uri: str, allow_missing_database: bool = False) -> (str, int, str, str, str):
|
|
52
|
-
if DIVIDER__protocol in uri:
|
|
53
|
-
uri = uri.split(DIVIDER__protocol)[1]
|
|
54
|
-
|
|
55
|
-
db_split = uri.split(DIVIDER__address)[-1].split(DIVIDER__db)
|
|
56
|
-
address = db_split[0]
|
|
57
|
-
db_name = db_split[1] if len(db_split) > 1 else None
|
|
58
|
-
|
|
59
|
-
if not allow_missing_database and db_name is None:
|
|
60
|
-
raise HttpStatusException(ERR__missing_database)
|
|
61
|
-
|
|
62
|
-
username, password = DIVIDER__address.join(uri.split(DIVIDER__address)[:-1]).split(DIVIDER__password)
|
|
63
|
-
address, port = address.split(DIVIDER__port)
|
|
64
|
-
|
|
65
|
-
return address, port, db_name, username, password
|
|
66
|
-
|
|
67
|
-
@abstractmethod
|
|
68
|
-
def get_conn(self):
|
|
69
|
-
pass
|
|
70
|
-
|
|
71
|
-
def log_warning(self, exc):
|
|
72
|
-
if self.logging:
|
|
73
|
-
logging.warning(exc, exc_info=False)
|
|
74
|
-
|
|
75
|
-
def log_critical(self, exc):
|
|
76
|
-
if self.logging:
|
|
77
|
-
logging.warning(exc, exc_info=False)
|
|
78
|
-
|
|
79
|
-
def __attempt_commit_rollback(self, conn, err):
|
|
80
|
-
try:
|
|
81
|
-
if err is None:
|
|
82
|
-
self.commit(conn)
|
|
83
|
-
else:
|
|
84
|
-
self.rollback(conn)
|
|
85
|
-
except Exception as ex:
|
|
86
|
-
if err is None:
|
|
87
|
-
self.log_warning(ex) # error committing. User stuffed up the SQL
|
|
88
|
-
else:
|
|
89
|
-
self.log_critical(ex) # error rolling back. It is serious
|
|
90
|
-
|
|
91
|
-
def __err_to_exception(self, err, echo):
|
|
92
|
-
if err is not None:
|
|
93
|
-
if isinstance(err, HttpStatusException) or isinstance(err, JaaqlInterpretableHandledError):
|
|
94
|
-
self.log_warning(err)
|
|
95
|
-
raise err
|
|
96
|
-
else:
|
|
97
|
-
ex = self.handle_db_error(err, echo)
|
|
98
|
-
if ex.response_code in [HTTPStatus.BAD_REQUEST, HTTPStatus.NOT_FOUND, HTTPStatus.UNAUTHORIZED]:
|
|
99
|
-
self.log_warning(ex) # Minor error. User made a bad request (bad query, unauthorised etc.)
|
|
100
|
-
else:
|
|
101
|
-
self.log_warning(ex) # Serious error, connection failure to db or similar
|
|
102
|
-
raise ex
|
|
103
|
-
|
|
104
|
-
def handle_error(self, conn, err, echo=ECHO__none):
|
|
105
|
-
self.__attempt_commit_rollback(conn, err)
|
|
106
|
-
self.__err_to_exception(err, echo)
|
|
107
|
-
|
|
108
|
-
def put_conn_handle_error(self, conn, err, echo=ECHO__none, skip_rollback_commit: bool = False):
|
|
109
|
-
if not skip_rollback_commit:
|
|
110
|
-
self.__attempt_commit_rollback(conn, err)
|
|
111
|
-
|
|
112
|
-
try:
|
|
113
|
-
self.put_conn(conn)
|
|
114
|
-
except Exception as ex:
|
|
115
|
-
self.log_warning(ex)
|
|
116
|
-
|
|
117
|
-
self.__err_to_exception(err, echo)
|
|
118
|
-
|
|
119
|
-
@abstractmethod
|
|
120
|
-
def put_conn(self, conn):
|
|
121
|
-
pass
|
|
122
|
-
|
|
123
|
-
@abstractmethod
|
|
124
|
-
def rollback(self, conn):
|
|
125
|
-
pass
|
|
126
|
-
|
|
127
|
-
def execute_query_fetching_results(self, conn, query, parameters=None, echo=ECHO__none, as_objects=False, wait_hook: queue.Queue = None,
|
|
128
|
-
requires_dba_check: bool = False):
|
|
129
|
-
if echo not in ECHO__allowed:
|
|
130
|
-
allowed_echoes = ", ".join([str(allowed_echo) for allowed_echo in ECHO__allowed])
|
|
131
|
-
raise HttpStatusException(ERR__unknown_echo % (str(echo), allowed_echoes), HTTPStatus.BAD_REQUEST)
|
|
132
|
-
|
|
133
|
-
if parameters is None:
|
|
134
|
-
parameters = {}
|
|
135
|
-
|
|
136
|
-
if echo == ECHO__debug:
|
|
137
|
-
ret = {}
|
|
138
|
-
else:
|
|
139
|
-
new_parameters = {}
|
|
140
|
-
for key, value in parameters.items():
|
|
141
|
-
if value is not None:
|
|
142
|
-
if isinstance(value, datetime):
|
|
143
|
-
new_parameters[key] = str(value)
|
|
144
|
-
else:
|
|
145
|
-
new_parameters[key] = value
|
|
146
|
-
|
|
147
|
-
if requires_dba_check:
|
|
148
|
-
self.check_dba(conn, wait_hook=wait_hook)
|
|
149
|
-
if wait_hook:
|
|
150
|
-
wait_hook = None
|
|
151
|
-
|
|
152
|
-
columns, type_codes, rows = self.execute_query(conn, query, new_parameters, wait_hook)
|
|
153
|
-
|
|
154
|
-
ret = {
|
|
155
|
-
RET__columns: columns,
|
|
156
|
-
RET__rows: rows,
|
|
157
|
-
RET__type_codes: type_codes
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if echo != ECHO__none:
|
|
161
|
-
ret[RET__echo] = query
|
|
162
|
-
|
|
163
|
-
if as_objects:
|
|
164
|
-
return objectify(ret)
|
|
165
|
-
else:
|
|
166
|
-
return ret
|
|
167
|
-
|
|
168
|
-
def read_utf8(self, filename):
|
|
169
|
-
with open(filename, 'rb') as file:
|
|
170
|
-
# Read the first few bytes to check for BOM
|
|
171
|
-
first_bytes = file.read(3)
|
|
172
|
-
|
|
173
|
-
# Check for UTF-8-SIG (BOM)
|
|
174
|
-
if first_bytes.startswith(b'\xef\xbb\xbf'):
|
|
175
|
-
# Reopen with 'utf-8-sig' to properly handle BOM
|
|
176
|
-
file = open(filename, FILE__read, encoding='utf-8-sig')
|
|
177
|
-
else:
|
|
178
|
-
# Reopen with 'utf-8' assuming no BOM
|
|
179
|
-
file = open(filename, FILE__read, encoding='utf-8')
|
|
180
|
-
|
|
181
|
-
return file
|
|
182
|
-
|
|
183
|
-
def execute_script_file(self, conn, file_loc: str = None, as_content: str = None, as_individual=False, commit=True):
|
|
184
|
-
ret = None
|
|
185
|
-
err = None
|
|
186
|
-
|
|
187
|
-
if as_individual:
|
|
188
|
-
ret = []
|
|
189
|
-
|
|
190
|
-
try:
|
|
191
|
-
if as_content:
|
|
192
|
-
queries = as_content
|
|
193
|
-
else:
|
|
194
|
-
with self.read_utf8(file_loc) as file:
|
|
195
|
-
queries = file.read()
|
|
196
|
-
|
|
197
|
-
if as_individual:
|
|
198
|
-
queries = queries.split(FILE__query_separator)
|
|
199
|
-
else:
|
|
200
|
-
queries = [queries]
|
|
201
|
-
|
|
202
|
-
for query in queries:
|
|
203
|
-
if len(query.strip()) != 0:
|
|
204
|
-
query = query.strip()
|
|
205
|
-
resp = self.execute_query_fetching_results(conn, query, {}, ECHO__execute)
|
|
206
|
-
|
|
207
|
-
if as_individual:
|
|
208
|
-
ret.append(resp)
|
|
209
|
-
else:
|
|
210
|
-
ret = resp
|
|
211
|
-
|
|
212
|
-
except Exception as ex:
|
|
213
|
-
if self.output_exceptions:
|
|
214
|
-
traceback.print_exc()
|
|
215
|
-
err = ex
|
|
216
|
-
|
|
217
|
-
if commit:
|
|
218
|
-
self.handle_error(conn, err)
|
|
219
|
-
return ret
|
|
220
|
-
else:
|
|
221
|
-
return ret, err
|
|
222
|
-
|
|
223
|
-
@abstractmethod
|
|
224
|
-
def check_dba(self, conn, wait_hook: queue.Queue = None):
|
|
225
|
-
pass
|
|
226
|
-
|
|
227
|
-
@abstractmethod
|
|
228
|
-
def execute_query(self, conn, query, parameters: Optional[dict] = None, wait_hook: queue.Queue = None):
|
|
229
|
-
pass
|
|
230
|
-
|
|
231
|
-
@abstractmethod
|
|
232
|
-
def commit(self, conn):
|
|
233
|
-
pass
|
|
234
|
-
|
|
235
|
-
@abstractmethod
|
|
236
|
-
def handle_db_error(self, err, echo) -> HttpStatusException:
|
|
237
|
-
pass
|
|
238
|
-
|
|
239
|
-
@abstractmethod
|
|
240
|
-
def close(self):
|
|
241
|
-
pass
|
|
1
|
+
import traceback
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
import logging
|
|
5
|
+
from jaaql.exceptions.http_status_exception import *
|
|
6
|
+
from typing import Optional
|
|
7
|
+
from jaaql.utilities.utils_no_project_imports import objectify
|
|
8
|
+
import queue
|
|
9
|
+
|
|
10
|
+
ERR__unknown_echo = "Unknown echo type '%s'. Please use either %s"
|
|
11
|
+
|
|
12
|
+
KEY_CONFIG__system = "SYSTEM"
|
|
13
|
+
KEY_CONFIG__logging = "logging"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
RET__echo = "echo"
|
|
17
|
+
RET__columns = "columns"
|
|
18
|
+
RET__type_codes = "type_codes"
|
|
19
|
+
RET__rows = "rows"
|
|
20
|
+
|
|
21
|
+
DIVIDER__protocol = "://"
|
|
22
|
+
DIVIDER__db = "/"
|
|
23
|
+
DIVIDER__port = ":"
|
|
24
|
+
DIVIDER__password = ":"
|
|
25
|
+
DIVIDER__address = "@"
|
|
26
|
+
|
|
27
|
+
ECHO__none = False
|
|
28
|
+
ECHO__debug = "debug"
|
|
29
|
+
ECHO__execute = True
|
|
30
|
+
ECHO__allowed = [ECHO__none, ECHO__debug, ECHO__execute]
|
|
31
|
+
|
|
32
|
+
CHAR__newline = "\r\n"
|
|
33
|
+
|
|
34
|
+
FILE__read = "r"
|
|
35
|
+
FILE__query_separator = ";"
|
|
36
|
+
|
|
37
|
+
ERR__missing_database = "The database name was missing from the connection"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class DBInterface(ABC):
|
|
41
|
+
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def __init__(self, config, address: str, username: str):
|
|
44
|
+
self.config = config
|
|
45
|
+
self.output_exceptions = self.config["DEBUG"]["output_query_exceptions"].lower() == "true"
|
|
46
|
+
self.logging = config.get(KEY_CONFIG__system, {KEY_CONFIG__logging: True}).get(KEY_CONFIG__logging, True)
|
|
47
|
+
self.username = username
|
|
48
|
+
self.address = address
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def fracture_uri(uri: str, allow_missing_database: bool = False) -> (str, int, str, str, str):
|
|
52
|
+
if DIVIDER__protocol in uri:
|
|
53
|
+
uri = uri.split(DIVIDER__protocol)[1]
|
|
54
|
+
|
|
55
|
+
db_split = uri.split(DIVIDER__address)[-1].split(DIVIDER__db)
|
|
56
|
+
address = db_split[0]
|
|
57
|
+
db_name = db_split[1] if len(db_split) > 1 else None
|
|
58
|
+
|
|
59
|
+
if not allow_missing_database and db_name is None:
|
|
60
|
+
raise HttpStatusException(ERR__missing_database)
|
|
61
|
+
|
|
62
|
+
username, password = DIVIDER__address.join(uri.split(DIVIDER__address)[:-1]).split(DIVIDER__password)
|
|
63
|
+
address, port = address.split(DIVIDER__port)
|
|
64
|
+
|
|
65
|
+
return address, port, db_name, username, password
|
|
66
|
+
|
|
67
|
+
@abstractmethod
|
|
68
|
+
def get_conn(self):
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
def log_warning(self, exc):
|
|
72
|
+
if self.logging:
|
|
73
|
+
logging.warning(exc, exc_info=False)
|
|
74
|
+
|
|
75
|
+
def log_critical(self, exc):
|
|
76
|
+
if self.logging:
|
|
77
|
+
logging.warning(exc, exc_info=False)
|
|
78
|
+
|
|
79
|
+
def __attempt_commit_rollback(self, conn, err):
|
|
80
|
+
try:
|
|
81
|
+
if err is None:
|
|
82
|
+
self.commit(conn)
|
|
83
|
+
else:
|
|
84
|
+
self.rollback(conn)
|
|
85
|
+
except Exception as ex:
|
|
86
|
+
if err is None:
|
|
87
|
+
self.log_warning(ex) # error committing. User stuffed up the SQL
|
|
88
|
+
else:
|
|
89
|
+
self.log_critical(ex) # error rolling back. It is serious
|
|
90
|
+
|
|
91
|
+
def __err_to_exception(self, err, echo):
|
|
92
|
+
if err is not None:
|
|
93
|
+
if isinstance(err, HttpStatusException) or isinstance(err, JaaqlInterpretableHandledError):
|
|
94
|
+
self.log_warning(err)
|
|
95
|
+
raise err
|
|
96
|
+
else:
|
|
97
|
+
ex = self.handle_db_error(err, echo)
|
|
98
|
+
if ex.response_code in [HTTPStatus.BAD_REQUEST, HTTPStatus.NOT_FOUND, HTTPStatus.UNAUTHORIZED]:
|
|
99
|
+
self.log_warning(ex) # Minor error. User made a bad request (bad query, unauthorised etc.)
|
|
100
|
+
else:
|
|
101
|
+
self.log_warning(ex) # Serious error, connection failure to db or similar
|
|
102
|
+
raise ex
|
|
103
|
+
|
|
104
|
+
def handle_error(self, conn, err, echo=ECHO__none):
|
|
105
|
+
self.__attempt_commit_rollback(conn, err)
|
|
106
|
+
self.__err_to_exception(err, echo)
|
|
107
|
+
|
|
108
|
+
def put_conn_handle_error(self, conn, err, echo=ECHO__none, skip_rollback_commit: bool = False):
|
|
109
|
+
if not skip_rollback_commit:
|
|
110
|
+
self.__attempt_commit_rollback(conn, err)
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
self.put_conn(conn)
|
|
114
|
+
except Exception as ex:
|
|
115
|
+
self.log_warning(ex)
|
|
116
|
+
|
|
117
|
+
self.__err_to_exception(err, echo)
|
|
118
|
+
|
|
119
|
+
@abstractmethod
|
|
120
|
+
def put_conn(self, conn):
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
@abstractmethod
|
|
124
|
+
def rollback(self, conn):
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
def execute_query_fetching_results(self, conn, query, parameters=None, echo=ECHO__none, as_objects=False, wait_hook: queue.Queue = None,
|
|
128
|
+
requires_dba_check: bool = False):
|
|
129
|
+
if echo not in ECHO__allowed:
|
|
130
|
+
allowed_echoes = ", ".join([str(allowed_echo) for allowed_echo in ECHO__allowed])
|
|
131
|
+
raise HttpStatusException(ERR__unknown_echo % (str(echo), allowed_echoes), HTTPStatus.BAD_REQUEST)
|
|
132
|
+
|
|
133
|
+
if parameters is None:
|
|
134
|
+
parameters = {}
|
|
135
|
+
|
|
136
|
+
if echo == ECHO__debug:
|
|
137
|
+
ret = {}
|
|
138
|
+
else:
|
|
139
|
+
new_parameters = {}
|
|
140
|
+
for key, value in parameters.items():
|
|
141
|
+
if value is not None:
|
|
142
|
+
if isinstance(value, datetime):
|
|
143
|
+
new_parameters[key] = str(value)
|
|
144
|
+
else:
|
|
145
|
+
new_parameters[key] = value
|
|
146
|
+
|
|
147
|
+
if requires_dba_check:
|
|
148
|
+
self.check_dba(conn, wait_hook=wait_hook)
|
|
149
|
+
if wait_hook:
|
|
150
|
+
wait_hook = None
|
|
151
|
+
|
|
152
|
+
columns, type_codes, rows = self.execute_query(conn, query, new_parameters, wait_hook)
|
|
153
|
+
|
|
154
|
+
ret = {
|
|
155
|
+
RET__columns: columns,
|
|
156
|
+
RET__rows: rows,
|
|
157
|
+
RET__type_codes: type_codes
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if echo != ECHO__none:
|
|
161
|
+
ret[RET__echo] = query
|
|
162
|
+
|
|
163
|
+
if as_objects:
|
|
164
|
+
return objectify(ret)
|
|
165
|
+
else:
|
|
166
|
+
return ret
|
|
167
|
+
|
|
168
|
+
def read_utf8(self, filename):
|
|
169
|
+
with open(filename, 'rb') as file:
|
|
170
|
+
# Read the first few bytes to check for BOM
|
|
171
|
+
first_bytes = file.read(3)
|
|
172
|
+
|
|
173
|
+
# Check for UTF-8-SIG (BOM)
|
|
174
|
+
if first_bytes.startswith(b'\xef\xbb\xbf'):
|
|
175
|
+
# Reopen with 'utf-8-sig' to properly handle BOM
|
|
176
|
+
file = open(filename, FILE__read, encoding='utf-8-sig')
|
|
177
|
+
else:
|
|
178
|
+
# Reopen with 'utf-8' assuming no BOM
|
|
179
|
+
file = open(filename, FILE__read, encoding='utf-8')
|
|
180
|
+
|
|
181
|
+
return file
|
|
182
|
+
|
|
183
|
+
def execute_script_file(self, conn, file_loc: str = None, as_content: str = None, as_individual=False, commit=True):
|
|
184
|
+
ret = None
|
|
185
|
+
err = None
|
|
186
|
+
|
|
187
|
+
if as_individual:
|
|
188
|
+
ret = []
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
if as_content:
|
|
192
|
+
queries = as_content
|
|
193
|
+
else:
|
|
194
|
+
with self.read_utf8(file_loc) as file:
|
|
195
|
+
queries = file.read()
|
|
196
|
+
|
|
197
|
+
if as_individual:
|
|
198
|
+
queries = queries.split(FILE__query_separator)
|
|
199
|
+
else:
|
|
200
|
+
queries = [queries]
|
|
201
|
+
|
|
202
|
+
for query in queries:
|
|
203
|
+
if len(query.strip()) != 0:
|
|
204
|
+
query = query.strip()
|
|
205
|
+
resp = self.execute_query_fetching_results(conn, query, {}, ECHO__execute)
|
|
206
|
+
|
|
207
|
+
if as_individual:
|
|
208
|
+
ret.append(resp)
|
|
209
|
+
else:
|
|
210
|
+
ret = resp
|
|
211
|
+
|
|
212
|
+
except Exception as ex:
|
|
213
|
+
if self.output_exceptions:
|
|
214
|
+
traceback.print_exc()
|
|
215
|
+
err = ex
|
|
216
|
+
|
|
217
|
+
if commit:
|
|
218
|
+
self.handle_error(conn, err)
|
|
219
|
+
return ret
|
|
220
|
+
else:
|
|
221
|
+
return ret, err
|
|
222
|
+
|
|
223
|
+
@abstractmethod
|
|
224
|
+
def check_dba(self, conn, wait_hook: queue.Queue = None):
|
|
225
|
+
pass
|
|
226
|
+
|
|
227
|
+
@abstractmethod
|
|
228
|
+
def execute_query(self, conn, query, parameters: Optional[dict] = None, wait_hook: queue.Queue = None):
|
|
229
|
+
pass
|
|
230
|
+
|
|
231
|
+
@abstractmethod
|
|
232
|
+
def commit(self, conn):
|
|
233
|
+
pass
|
|
234
|
+
|
|
235
|
+
@abstractmethod
|
|
236
|
+
def handle_db_error(self, err, echo) -> HttpStatusException:
|
|
237
|
+
pass
|
|
238
|
+
|
|
239
|
+
@abstractmethod
|
|
240
|
+
def close(self):
|
|
241
|
+
pass
|