tina4-python 0.2.122__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.
- tina4_python/Auth.py +222 -0
- tina4_python/Constant.py +43 -0
- tina4_python/Database.py +591 -0
- tina4_python/DatabaseResult.py +107 -0
- tina4_python/DatabaseTypes.py +15 -0
- tina4_python/Debug.py +126 -0
- tina4_python/Env.py +37 -0
- tina4_python/Localization.py +42 -0
- tina4_python/Messages.py +30 -0
- tina4_python/MiddleWare.py +90 -0
- tina4_python/Migration.py +107 -0
- tina4_python/ORM.py +639 -0
- tina4_python/Queue.py +615 -0
- tina4_python/Request.py +19 -0
- tina4_python/Response.py +121 -0
- tina4_python/Router.py +423 -0
- tina4_python/Session.py +342 -0
- tina4_python/ShellColors.py +20 -0
- tina4_python/Swagger.py +228 -0
- tina4_python/Template.py +107 -0
- tina4_python/Webserver.py +429 -0
- tina4_python/Websocket.py +49 -0
- tina4_python/__init__.py +392 -0
- tina4_python/messages.pot +83 -0
- tina4_python/public/css/readme.md +0 -0
- tina4_python/public/favicon.ico +0 -0
- tina4_python/public/images/403.png +0 -0
- tina4_python/public/images/404.png +0 -0
- tina4_python/public/images/500.png +0 -0
- tina4_python/public/images/logo.png +0 -0
- tina4_python/public/images/readme.md +0 -0
- tina4_python/public/js/readme.md +0 -0
- tina4_python/public/js/reconnecting-websocket.js +365 -0
- tina4_python/public/js/tina4helper.js +397 -0
- tina4_python/public/swagger/index.html +90 -0
- tina4_python/public/swagger/oauth2-redirect.html +63 -0
- tina4_python/templates/errors/403.twig +10 -0
- tina4_python/templates/errors/404.twig +10 -0
- tina4_python/templates/errors/500.twig +11 -0
- tina4_python/templates/readme.md +1 -0
- tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- tina4_python/translations/en/LC_MESSAGES/messages.po +80 -0
- tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- tina4_python/translations/fr/LC_MESSAGES/messages.po +84 -0
- tina4_python-0.2.122.dist-info/METADATA +465 -0
- tina4_python-0.2.122.dist-info/RECORD +47 -0
- tina4_python-0.2.122.dist-info/WHEEL +4 -0
tina4_python/ORM.py
ADDED
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Tina4 - This is not a 4ramework.
|
|
3
|
+
# Copy-right 2007 - current Tina4
|
|
4
|
+
# License: MIT https://opensource.org/licenses/MIT
|
|
5
|
+
#
|
|
6
|
+
# flake8: noqa: E501
|
|
7
|
+
import base64
|
|
8
|
+
from datetime import datetime, date
|
|
9
|
+
import inspect
|
|
10
|
+
import ast
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
from tina4_python.Constant import TINA4_LOG_ERROR
|
|
14
|
+
from tina4_python.Debug import Debug
|
|
15
|
+
from tina4_python.DatabaseTypes import *
|
|
16
|
+
import inspect
|
|
17
|
+
|
|
18
|
+
def find_all_sub_classes(a_class):
|
|
19
|
+
return a_class.__subclasses__()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def orm(dba):
|
|
23
|
+
from tina4_python import root_path
|
|
24
|
+
Debug("Initializing ORM")
|
|
25
|
+
orm_path = root_path + os.sep + "src" + os.sep + "orm"
|
|
26
|
+
if not os.path.exists(orm_path):
|
|
27
|
+
os.makedirs(orm_path)
|
|
28
|
+
|
|
29
|
+
# load and assign
|
|
30
|
+
for file in os.listdir(orm_path):
|
|
31
|
+
if not file.endswith(".py"):
|
|
32
|
+
continue
|
|
33
|
+
mod_name = file.removesuffix(".py")
|
|
34
|
+
if "__init__" not in mod_name and "__pycache__" not in mod_name and ".git" not in mod_name:
|
|
35
|
+
# import and set the database object
|
|
36
|
+
try:
|
|
37
|
+
Debug('from src.orm.' + mod_name + ' import ' + mod_name)
|
|
38
|
+
exec('from src.orm.' + mod_name + ' import ' + mod_name+"\n"+mod_name + ".__dba__ = dba")
|
|
39
|
+
except Exception as e:
|
|
40
|
+
Debug("Failed to import " + mod_name, str(e))
|
|
41
|
+
classes = find_all_sub_classes(ORM)
|
|
42
|
+
for a_class in classes:
|
|
43
|
+
a_class.__dba__ = dba
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class BaseField:
|
|
47
|
+
primary_key = False
|
|
48
|
+
column_type = None
|
|
49
|
+
default_value = None
|
|
50
|
+
column_name = None
|
|
51
|
+
auto_increment = False
|
|
52
|
+
value = None
|
|
53
|
+
field_size = None
|
|
54
|
+
decimal_places = None
|
|
55
|
+
protected_field = False
|
|
56
|
+
|
|
57
|
+
def get_definition(self, database_type="generic"):
|
|
58
|
+
return self.column_name.lower() + " not defined"
|
|
59
|
+
|
|
60
|
+
def __eq__(self, other):
|
|
61
|
+
if self.value is None:
|
|
62
|
+
self.value = self.default_value
|
|
63
|
+
return other == self.value
|
|
64
|
+
|
|
65
|
+
def __ne__(self, other):
|
|
66
|
+
if self.value is None:
|
|
67
|
+
self.value = self.default_value
|
|
68
|
+
return other != self.value
|
|
69
|
+
|
|
70
|
+
def __add__(self, other):
|
|
71
|
+
if self.value is None:
|
|
72
|
+
self.value = self.default_value
|
|
73
|
+
return other + self.value
|
|
74
|
+
|
|
75
|
+
def __mul__(self, other):
|
|
76
|
+
if self.value is None:
|
|
77
|
+
self.value = self.default_value
|
|
78
|
+
return other * self.value
|
|
79
|
+
|
|
80
|
+
def __sub__(self, other):
|
|
81
|
+
if self.value is None:
|
|
82
|
+
self.value = self.default_value
|
|
83
|
+
return self.value - other
|
|
84
|
+
|
|
85
|
+
def __truediv__(self, other):
|
|
86
|
+
if self.value is None:
|
|
87
|
+
self.value = self.default_value
|
|
88
|
+
return self.value / other
|
|
89
|
+
|
|
90
|
+
def __str__(self):
|
|
91
|
+
if self.value is None:
|
|
92
|
+
self.value = self.default_value
|
|
93
|
+
return str(self.value)
|
|
94
|
+
|
|
95
|
+
def __int__(self):
|
|
96
|
+
if self.value is None:
|
|
97
|
+
self.value = self.default_value
|
|
98
|
+
return int(self.value)
|
|
99
|
+
|
|
100
|
+
def __float__(self):
|
|
101
|
+
if self.value is None:
|
|
102
|
+
self.value = self.default_value
|
|
103
|
+
return float(self.value)
|
|
104
|
+
|
|
105
|
+
def __init__(self, column_name=None, primary_key=False, default_value=None, auto_increment=False, field_size=None,
|
|
106
|
+
decimal_places=None, protected_field=False):
|
|
107
|
+
self.primary_key = primary_key
|
|
108
|
+
self.column_type = None
|
|
109
|
+
if default_value is not None:
|
|
110
|
+
self.default_value = default_value
|
|
111
|
+
self.auto_increment = auto_increment
|
|
112
|
+
self.protected_field = protected_field
|
|
113
|
+
if field_size is not None:
|
|
114
|
+
self.field_size = field_size
|
|
115
|
+
if decimal_places is not None:
|
|
116
|
+
self.decimal_places = decimal_places
|
|
117
|
+
|
|
118
|
+
if column_name is None:
|
|
119
|
+
frame = inspect.stack()[1]
|
|
120
|
+
# Parse python syntax of the assignment line
|
|
121
|
+
st = ast.parse(frame.code_context[0].strip())
|
|
122
|
+
stmt = st.body[0]
|
|
123
|
+
# Assume class being instanced as simple assign statement
|
|
124
|
+
assert (isinstance(stmt, ast.Assign))
|
|
125
|
+
# Parse the target the class is assigned to
|
|
126
|
+
target = stmt.targets[0]
|
|
127
|
+
self.column_name = target.id
|
|
128
|
+
else:
|
|
129
|
+
self.column_name = column_name
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class DateTimeField(BaseField):
|
|
133
|
+
column_type = datetime
|
|
134
|
+
default_value = datetime.now()
|
|
135
|
+
|
|
136
|
+
def get_definition(self, database_type="generic"):
|
|
137
|
+
if database_type == MSSQL:
|
|
138
|
+
return self.column_name.lower() + " datetime"
|
|
139
|
+
else:
|
|
140
|
+
return self.column_name.lower() + " timestamp"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class IntegerField(BaseField):
|
|
144
|
+
column_type = int
|
|
145
|
+
default_value = 0
|
|
146
|
+
|
|
147
|
+
def get_definition(self, database_type="generic"):
|
|
148
|
+
not_null = ""
|
|
149
|
+
if self.primary_key:
|
|
150
|
+
not_null = " not null"
|
|
151
|
+
if database_type == MSSQL:
|
|
152
|
+
return self.column_name.lower() + " integer identity(1,1) " + not_null
|
|
153
|
+
else:
|
|
154
|
+
return self.column_name.lower() + " integer default " + str(self.default_value) + " " + not_null
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class NumericField(BaseField):
|
|
158
|
+
column_type = float
|
|
159
|
+
default_value = 0.00
|
|
160
|
+
field_size = 10
|
|
161
|
+
decimal_places = 2
|
|
162
|
+
|
|
163
|
+
def get_definition(self, database_type="generic"):
|
|
164
|
+
not_null = ""
|
|
165
|
+
if self.primary_key:
|
|
166
|
+
not_null = " not null"
|
|
167
|
+
|
|
168
|
+
return self.column_name.lower() + " numeric(" + str(self.field_size) + "," + str(
|
|
169
|
+
self.decimal_places) + ") default " + str(self.default_value) + not_null
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class StringField(BaseField):
|
|
173
|
+
column_type = str
|
|
174
|
+
default_value = ""
|
|
175
|
+
field_size = 255
|
|
176
|
+
|
|
177
|
+
def get_definition(self, database_type="generic"):
|
|
178
|
+
not_null = ""
|
|
179
|
+
if self.primary_key:
|
|
180
|
+
not_null = " not null"
|
|
181
|
+
|
|
182
|
+
return self.column_name.lower() + " varchar(" + str(self.field_size) + ") default '" + str(
|
|
183
|
+
self.default_value) + "'" + not_null
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class TextField(StringField):
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class BlobField(BaseField):
|
|
191
|
+
column_type = bytes
|
|
192
|
+
default_value = None
|
|
193
|
+
|
|
194
|
+
def get_definition(self, database_type="generic"):
|
|
195
|
+
field_type = "blob"
|
|
196
|
+
if database_type == MSSQL:
|
|
197
|
+
field_type = "varbinary(max)"
|
|
198
|
+
return self.column_name.lower() + " " + field_type
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class ForeignKeyField:
|
|
202
|
+
field_type = None
|
|
203
|
+
references_table = None
|
|
204
|
+
references_column = None
|
|
205
|
+
primary_key = False
|
|
206
|
+
foreign_key = True
|
|
207
|
+
value = None
|
|
208
|
+
default_value = None
|
|
209
|
+
protected_field = False
|
|
210
|
+
|
|
211
|
+
def __eq__(self, other):
|
|
212
|
+
if self.value is None:
|
|
213
|
+
self.value = self.default_value
|
|
214
|
+
return other == self.value
|
|
215
|
+
|
|
216
|
+
def __ne__(self, other):
|
|
217
|
+
if self.value is None:
|
|
218
|
+
self.value = self.default_value
|
|
219
|
+
return other != self.value
|
|
220
|
+
|
|
221
|
+
def __add__(self, other):
|
|
222
|
+
if self.value is None:
|
|
223
|
+
self.value = self.default_value
|
|
224
|
+
return other + self.value
|
|
225
|
+
|
|
226
|
+
def __mul__(self, other):
|
|
227
|
+
if self.value is None:
|
|
228
|
+
self.value = self.default_value
|
|
229
|
+
return other * self.value
|
|
230
|
+
|
|
231
|
+
def __sub__(self, other):
|
|
232
|
+
if self.value is None:
|
|
233
|
+
self.value = self.default_value
|
|
234
|
+
return self.value - other
|
|
235
|
+
|
|
236
|
+
def __truediv__(self, other):
|
|
237
|
+
if self.value is None:
|
|
238
|
+
self.value = self.default_value
|
|
239
|
+
return self.value / other
|
|
240
|
+
|
|
241
|
+
def __str__(self):
|
|
242
|
+
if self.value is None:
|
|
243
|
+
self.value = self.default_value
|
|
244
|
+
return str(self.value)
|
|
245
|
+
|
|
246
|
+
def __init__(self, field_type=BaseField, references_table=None, column_name=None, default_value = None, protected_field=False):
|
|
247
|
+
self.field_type = field_type
|
|
248
|
+
self.references_table = references_table
|
|
249
|
+
self.references_column = field_type.column_name
|
|
250
|
+
self.default_value = default_value
|
|
251
|
+
self.auto_increment = False
|
|
252
|
+
self.protected_field = protected_field
|
|
253
|
+
self.value = field_type
|
|
254
|
+
|
|
255
|
+
if column_name is None:
|
|
256
|
+
frame = inspect.stack()[1]
|
|
257
|
+
# Parse python syntax of the assignment line
|
|
258
|
+
st = ast.parse(frame.code_context[0].strip())
|
|
259
|
+
stmt = st.body[0]
|
|
260
|
+
# Assume class being instanced as simple assign statement
|
|
261
|
+
assert (isinstance(stmt, ast.Assign))
|
|
262
|
+
# Parse the target the class is assigned to
|
|
263
|
+
target = stmt.targets[0]
|
|
264
|
+
self.column_name = target.id
|
|
265
|
+
else:
|
|
266
|
+
self.column_name = column_name
|
|
267
|
+
|
|
268
|
+
def get_definition(self, database_type="generic"):
|
|
269
|
+
references_definition = self.field_type.get_definition(database_type).split(" ")
|
|
270
|
+
# print("REFERENCES", references_definition, self.references_table.__table_name__, self.references_column)
|
|
271
|
+
return self.column_name + " " + str(references_definition[1]) + " references " + self.references_table.__table_name__ + "(" + self.references_column + ") on update cascade on delete cascade"
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def json_serialize(obj):
|
|
275
|
+
"""JSON serializer for objects not serializable by default json code"""
|
|
276
|
+
if isinstance(obj, (datetime, date)):
|
|
277
|
+
return obj.isoformat()
|
|
278
|
+
elif isinstance(obj, bytes):
|
|
279
|
+
return base64.b64encode(obj).decode('utf-8')
|
|
280
|
+
raise TypeError("Type %s not serializable" % type(obj))
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class ORM:
|
|
284
|
+
__table_name__ = None
|
|
285
|
+
__dba__ = None
|
|
286
|
+
__field_definitions__ = {}
|
|
287
|
+
|
|
288
|
+
def __get_snake_case_name__(self, name):
|
|
289
|
+
"""
|
|
290
|
+
Gets the table name
|
|
291
|
+
:param name:
|
|
292
|
+
:return:
|
|
293
|
+
"""
|
|
294
|
+
if "_" in name:
|
|
295
|
+
return name.lower()
|
|
296
|
+
snake_case_name = ""
|
|
297
|
+
counter = 0
|
|
298
|
+
for c in name:
|
|
299
|
+
if c.isupper() and counter > 0:
|
|
300
|
+
snake_case_name = snake_case_name + "_" + c.lower()
|
|
301
|
+
else:
|
|
302
|
+
snake_case_name = snake_case_name + c.lower()
|
|
303
|
+
counter += 1
|
|
304
|
+
return snake_case_name
|
|
305
|
+
|
|
306
|
+
def __init__(self, init_object=None, table_name=None):
|
|
307
|
+
from tina4_python import root_path
|
|
308
|
+
# save the initial declarations
|
|
309
|
+
counter = 0
|
|
310
|
+
self.__field_definitions__ = {}
|
|
311
|
+
for key in dir(self):
|
|
312
|
+
if not key.startswith('__') and not key.startswith('_') and key not in ['save', 'load', 'delete', 'to_json',
|
|
313
|
+
'to_dict', 'create_table', 'select', 'fetch', 'fetch_one']:
|
|
314
|
+
self.__field_definitions__[key] = getattr(self, key)
|
|
315
|
+
counter += 1
|
|
316
|
+
|
|
317
|
+
if counter == 0:
|
|
318
|
+
self.__field_definitions__["id"] = IntegerField(default_value=0, auto_increment=True, primary_key=True)
|
|
319
|
+
|
|
320
|
+
class_name = self.__class__.__name__
|
|
321
|
+
if self.__table_name__ is None:
|
|
322
|
+
if table_name is None:
|
|
323
|
+
self.__table_name__ = self.__get_snake_case_name__(class_name)
|
|
324
|
+
else:
|
|
325
|
+
self.__table_name__ = table_name.lower()
|
|
326
|
+
|
|
327
|
+
if init_object is not None:
|
|
328
|
+
self.__populate_orm(init_object)
|
|
329
|
+
else:
|
|
330
|
+
self.__populate_orm({})
|
|
331
|
+
|
|
332
|
+
# Debug("Checking for", self.__table_name__, TINA4_LOG_INFO)
|
|
333
|
+
if self.__dba__ is not None:
|
|
334
|
+
self.__table_exists = self.__dba__.table_exists(self.__table_name__)
|
|
335
|
+
if not self.__table_exists:
|
|
336
|
+
sql = self.__create_table__(self.__table_name__)
|
|
337
|
+
filename = root_path + os.sep + "migrations" + os.sep + "__" + self.__table_name__ + ".sql"
|
|
338
|
+
os.makedirs(os.path.dirname(filename), exist_ok=True)
|
|
339
|
+
#with open(filename, "w") as f:
|
|
340
|
+
# f.write(sql)
|
|
341
|
+
# f.close()
|
|
342
|
+
Debug.warning("Create Table ? ", sql)
|
|
343
|
+
else:
|
|
344
|
+
self.__table_exists = False
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def __populate_orm(self, init_object):
|
|
349
|
+
"""
|
|
350
|
+
Populates an ORM object from an input object, also transforms camel case objects to snake case ...
|
|
351
|
+
:param init_object:
|
|
352
|
+
:return:
|
|
353
|
+
"""
|
|
354
|
+
for field, field_definition in self.__field_definitions__.items():
|
|
355
|
+
if hasattr(self, field):
|
|
356
|
+
setattr(self, field, None)
|
|
357
|
+
try:
|
|
358
|
+
field_definition.value = None
|
|
359
|
+
setattr(self, field, field_definition)
|
|
360
|
+
except Exception as e:
|
|
361
|
+
print("Could not set attribute for", field, str(e))
|
|
362
|
+
|
|
363
|
+
if isinstance(init_object, str):
|
|
364
|
+
init_object = json.loads(init_object)
|
|
365
|
+
|
|
366
|
+
for key, value in init_object.items():
|
|
367
|
+
snake_case_name = self.__get_snake_case_name__(key)
|
|
368
|
+
if snake_case_name in self.__field_definitions__:
|
|
369
|
+
try:
|
|
370
|
+
field_value = self.__field_definitions__[snake_case_name]
|
|
371
|
+
field_value.value = value
|
|
372
|
+
if hasattr(self, snake_case_name):
|
|
373
|
+
setattr(self, snake_case_name, field_value)
|
|
374
|
+
except Exception as e:
|
|
375
|
+
print("Could not set value for", snake_case_name, str(e))
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def __get_primary_keys(self):
|
|
379
|
+
primary_keys = []
|
|
380
|
+
for key, value in self.__field_definitions__.items():
|
|
381
|
+
if value.primary_key:
|
|
382
|
+
primary_keys.append(key)
|
|
383
|
+
|
|
384
|
+
return primary_keys
|
|
385
|
+
|
|
386
|
+
def to_json(self):
|
|
387
|
+
"""
|
|
388
|
+
Returns a json string
|
|
389
|
+
:return:
|
|
390
|
+
"""
|
|
391
|
+
return json.dumps(self.to_dict(), default=json_serialize)
|
|
392
|
+
|
|
393
|
+
def __is_class(self, class_name):
|
|
394
|
+
return str(type(class_name)).startswith("<class") and hasattr(class_name, '__weakref__')
|
|
395
|
+
|
|
396
|
+
def to_dict(self):
|
|
397
|
+
"""
|
|
398
|
+
Returns a Python dictionary
|
|
399
|
+
:return:
|
|
400
|
+
"""
|
|
401
|
+
# print(inspect.currentframe().f_back.f_code.co_qualname)
|
|
402
|
+
data = {}
|
|
403
|
+
|
|
404
|
+
for key, value in self.__field_definitions__.items():
|
|
405
|
+
current_value = getattr(self, key)
|
|
406
|
+
|
|
407
|
+
if current_value is not None and not isinstance(current_value, ForeignKeyField) and value.auto_increment and self.__is_class(current_value):
|
|
408
|
+
if current_value.value is None:
|
|
409
|
+
new_id = self.__dba__.get_next_id(table_name=self.__table_name__, column_name=value.column_name)
|
|
410
|
+
|
|
411
|
+
if new_id is not None:
|
|
412
|
+
current_value.value = new_id
|
|
413
|
+
else:
|
|
414
|
+
current_value.value = current_value.default_value
|
|
415
|
+
|
|
416
|
+
data[key] = current_value.value
|
|
417
|
+
elif isinstance(value, IntegerField):
|
|
418
|
+
data[key] = int(current_value)
|
|
419
|
+
else:
|
|
420
|
+
data[key] = str(current_value)
|
|
421
|
+
|
|
422
|
+
return data
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def __str__(self):
|
|
426
|
+
return self.to_json()
|
|
427
|
+
|
|
428
|
+
def __create_table__(self, table_name, execute=False):
|
|
429
|
+
sql = "create table " + table_name + " ("
|
|
430
|
+
counter = 0
|
|
431
|
+
for field, field_definition in self.__field_definitions__.items():
|
|
432
|
+
if counter > 0:
|
|
433
|
+
sql += ",\n"
|
|
434
|
+
sql += "\t" + field_definition.get_definition(self.__dba__.database_engine)
|
|
435
|
+
counter += 1
|
|
436
|
+
|
|
437
|
+
primary_keys = self.__get_primary_keys()
|
|
438
|
+
if primary_keys:
|
|
439
|
+
sql += ",\n"
|
|
440
|
+
sql += "\tprimary key (" + ",".join(primary_keys) + ")"
|
|
441
|
+
sql += "\n);\n"
|
|
442
|
+
|
|
443
|
+
if execute:
|
|
444
|
+
self.__dba__.execute(sql)
|
|
445
|
+
else:
|
|
446
|
+
return sql
|
|
447
|
+
|
|
448
|
+
def create_table(self):
|
|
449
|
+
"""
|
|
450
|
+
Creates the table for the ORM structure
|
|
451
|
+
:return:
|
|
452
|
+
"""
|
|
453
|
+
self.__dba__.create_table(self.__table_name__, True)
|
|
454
|
+
|
|
455
|
+
def __build_sql(self, column_names="*", join="", filter="", group_by="", having="", order_by=""):
|
|
456
|
+
"""
|
|
457
|
+
Helper method to build the SQL query
|
|
458
|
+
:param column_names:
|
|
459
|
+
:param join:
|
|
460
|
+
:param filter:
|
|
461
|
+
:param group_by:
|
|
462
|
+
:param having:
|
|
463
|
+
:param order_by:
|
|
464
|
+
:return:
|
|
465
|
+
"""
|
|
466
|
+
if isinstance(column_names, str):
|
|
467
|
+
if column_names in ("", "*"):
|
|
468
|
+
cols = "*"
|
|
469
|
+
else:
|
|
470
|
+
cols = ",\n".join(c.strip() for c in column_names.split(','))
|
|
471
|
+
else:
|
|
472
|
+
cols = ",\n".join(column_names)
|
|
473
|
+
|
|
474
|
+
group_by = [g.strip() for g in group_by.split(',')] if isinstance(group_by, str) and group_by else group_by if isinstance(group_by, list) else []
|
|
475
|
+
having = [h.strip() for h in having.split(',')] if isinstance(having, str) and having else having if isinstance(having, list) else []
|
|
476
|
+
order_by = [o.strip() for o in order_by.split(',')] if isinstance(order_by, str) and order_by else order_by if isinstance(order_by, list) else []
|
|
477
|
+
|
|
478
|
+
sql = f"select {cols}\nfrom {self.__table_name__} as t"
|
|
479
|
+
if join: sql += f"\n{join}"
|
|
480
|
+
if filter: sql += f"\nwhere {filter}"
|
|
481
|
+
if group_by: sql += "\ngroup by " + ", ".join(group_by)
|
|
482
|
+
if having: sql += "\nhaving " + ", ".join(having)
|
|
483
|
+
if order_by: sql += "\norder by " + ", ".join(order_by)
|
|
484
|
+
return sql
|
|
485
|
+
|
|
486
|
+
def fetch_one(self, column_names="*", filter="", params=[], join="", group_by="", having="", order_by=""):
|
|
487
|
+
"""
|
|
488
|
+
Fetch one record from the database
|
|
489
|
+
:param column_names:
|
|
490
|
+
:param filter:
|
|
491
|
+
:param params:
|
|
492
|
+
:param join:
|
|
493
|
+
:param group_by:
|
|
494
|
+
:param having:
|
|
495
|
+
:param order_by:
|
|
496
|
+
:return:
|
|
497
|
+
"""
|
|
498
|
+
sql = self.__build_sql(column_names, join, filter, group_by, having, order_by)
|
|
499
|
+
return self.__dba__.fetch_one(sql, params=params)
|
|
500
|
+
|
|
501
|
+
def fetch(self, column_names="*", filter="", params=[], join="", group_by="", having="", order_by="", limit=10, skip=0):
|
|
502
|
+
"""
|
|
503
|
+
Fetch multiple records from the database
|
|
504
|
+
:param column_names:
|
|
505
|
+
:param filter:
|
|
506
|
+
:param params:
|
|
507
|
+
:param join:
|
|
508
|
+
:param group_by:
|
|
509
|
+
:param having:
|
|
510
|
+
:param order_by:
|
|
511
|
+
:param limit:
|
|
512
|
+
:param skip:
|
|
513
|
+
:return:
|
|
514
|
+
"""
|
|
515
|
+
sql = self.__build_sql(column_names, join, filter, group_by, having, order_by)
|
|
516
|
+
return self.__dba__.fetch(sql, params=params, limit=limit, skip=skip)
|
|
517
|
+
|
|
518
|
+
def select (self, column_names="*", filter="", params=[], join="", group_by="", having="", order_by="", limit=10, skip=0):
|
|
519
|
+
return self.fetch(column_names, filter, params, join, group_by, having, order_by, limit, skip)
|
|
520
|
+
|
|
521
|
+
def load(self, query="", params=[]):
|
|
522
|
+
"""
|
|
523
|
+
Loads a single record into the object based on the primary key or query if query is set
|
|
524
|
+
:param query:
|
|
525
|
+
:param params:
|
|
526
|
+
:return:
|
|
527
|
+
"""
|
|
528
|
+
if not self.__table_exists:
|
|
529
|
+
Debug("ORM: Load Error - Table", self.__table_name__, "does not exist", TINA4_LOG_ERROR)
|
|
530
|
+
return False
|
|
531
|
+
|
|
532
|
+
if query == "":
|
|
533
|
+
sql = f"select * from {self.__table_name__} where "
|
|
534
|
+
primary_keys = self.__get_primary_keys()
|
|
535
|
+
values = self.to_dict()
|
|
536
|
+
counter = 0
|
|
537
|
+
for key in primary_keys:
|
|
538
|
+
if counter > 0:
|
|
539
|
+
sql += " and "
|
|
540
|
+
sql += key + " = '" + str(values[key]) + "'"
|
|
541
|
+
counter += 1
|
|
542
|
+
else:
|
|
543
|
+
sql = f"select * from {self.__table_name__} where {query}"
|
|
544
|
+
|
|
545
|
+
if self.__dba__ is not None:
|
|
546
|
+
record = self.__dba__.fetch_one(sql, params)
|
|
547
|
+
try:
|
|
548
|
+
if record:
|
|
549
|
+
for key, value in record.items():
|
|
550
|
+
if key in self.__field_definitions__:
|
|
551
|
+
field_value = self.__field_definitions__[key]
|
|
552
|
+
field_value.value = value
|
|
553
|
+
|
|
554
|
+
setattr(self, key, field_value)
|
|
555
|
+
return True
|
|
556
|
+
else:
|
|
557
|
+
return False
|
|
558
|
+
except Exception as e:
|
|
559
|
+
Debug("ORM Load Error", str(e), TINA4_LOG_ERROR)
|
|
560
|
+
else:
|
|
561
|
+
Debug("Database not initialized", TINA4_LOG_ERROR)
|
|
562
|
+
return False
|
|
563
|
+
|
|
564
|
+
def save(self):
|
|
565
|
+
"""
|
|
566
|
+
Saves the ORM object to the database
|
|
567
|
+
:return:
|
|
568
|
+
"""
|
|
569
|
+
if not self.__table_exists:
|
|
570
|
+
Debug("ORM: Save Error - Table", self.__table_name__, "does not exist", TINA4_LOG_ERROR)
|
|
571
|
+
return False
|
|
572
|
+
# check if record exists
|
|
573
|
+
data = self.to_dict()
|
|
574
|
+
|
|
575
|
+
primary_keys = self.__get_primary_keys()
|
|
576
|
+
sql = "select count(*) as \"count_records\" from " + self.__table_name__ + " where "
|
|
577
|
+
counter = 0
|
|
578
|
+
input_params = []
|
|
579
|
+
for key in primary_keys:
|
|
580
|
+
if counter > 0:
|
|
581
|
+
sql += " and "
|
|
582
|
+
sql += key + " = ?"
|
|
583
|
+
input_params.append(data[key])
|
|
584
|
+
counter += 1
|
|
585
|
+
|
|
586
|
+
try:
|
|
587
|
+
record = self.__dba__.fetch_one(sql, input_params)
|
|
588
|
+
for key, value in data.items():
|
|
589
|
+
if key in self.__field_definitions__:
|
|
590
|
+
if type(value) == BlobField and (isinstance(value, dict) or isinstance(value, list)) or (isinstance(value, str) and value.startswith("{") and value.endswith("}")):
|
|
591
|
+
Debug.warning("Saving BlobField as JSON", key)
|
|
592
|
+
data[key] = json.dumps(value)
|
|
593
|
+
|
|
594
|
+
if record["count_records"] == 0:
|
|
595
|
+
result = self.__dba__.insert(self.__table_name__, data)
|
|
596
|
+
else:
|
|
597
|
+
result = self.__dba__.update(self.__table_name__, data)
|
|
598
|
+
|
|
599
|
+
self.__dba__.commit()
|
|
600
|
+
|
|
601
|
+
if result:
|
|
602
|
+
self.load()
|
|
603
|
+
|
|
604
|
+
return True
|
|
605
|
+
except Exception as e:
|
|
606
|
+
Debug.error("Error saving", str(e))
|
|
607
|
+
return False
|
|
608
|
+
|
|
609
|
+
return result
|
|
610
|
+
|
|
611
|
+
def delete(self, query="", params=[]):
|
|
612
|
+
if not self.__table_exists:
|
|
613
|
+
Debug("ORM: Load Error - Table", self.__table_name__, "does not exist", TINA4_LOG_ERROR)
|
|
614
|
+
return False
|
|
615
|
+
if query == "":
|
|
616
|
+
sql = f"delete from {self.__table_name__} where "
|
|
617
|
+
primary_keys = self.__get_primary_keys()
|
|
618
|
+
values = self.to_dict()
|
|
619
|
+
counter = 0
|
|
620
|
+
for key in primary_keys:
|
|
621
|
+
if counter > 0:
|
|
622
|
+
sql += " and "
|
|
623
|
+
sql += key + " = '" + str(values[key]) + "'"
|
|
624
|
+
counter += 1
|
|
625
|
+
else:
|
|
626
|
+
sql = f"delete from {self.__table_name__} where {query}"
|
|
627
|
+
|
|
628
|
+
result = False
|
|
629
|
+
if self.__dba__ is not None:
|
|
630
|
+
result = self.__dba__.execute(sql, params)
|
|
631
|
+
|
|
632
|
+
if result.error is not None:
|
|
633
|
+
self.__dba__.rollback()
|
|
634
|
+
return False
|
|
635
|
+
else:
|
|
636
|
+
self.__dba__.commit()
|
|
637
|
+
return True
|
|
638
|
+
|
|
639
|
+
|