database-wrapper 0.1.33__py3-none-any.whl → 0.1.37__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.
- database_wrapper/__init__.py +2 -3
- database_wrapper/common.py +14 -0
- database_wrapper/config.py +3 -3
- database_wrapper/db_data_model.py +50 -3
- database_wrapper/db_wrapper.py +71 -296
- database_wrapper/db_wrapper_async.py +69 -295
- database_wrapper/db_wrapper_mixin.py +308 -0
- database_wrapper/utils/dataclass_addons.py +3 -3
- {database_wrapper-0.1.33.dist-info → database_wrapper-0.1.37.dist-info}/METADATA +5 -5
- database_wrapper-0.1.37.dist-info/RECORD +16 -0
- {database_wrapper-0.1.33.dist-info → database_wrapper-0.1.37.dist-info}/WHEEL +1 -1
- database_wrapper/db_wrapper_interface.py +0 -434
- database_wrapper-0.1.33.dist-info/RECORD +0 -15
- {database_wrapper-0.1.33.dist-info → database_wrapper-0.1.37.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from typing import cast, Any
|
|
4
|
+
|
|
5
|
+
from .db_backend import DatabaseBackend
|
|
6
|
+
from .common import OrderByItem, NoParam, DataModelType
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DBWrapperMixin:
|
|
10
|
+
"""
|
|
11
|
+
Mixin class for the DBWrapper class to provide methods that can be
|
|
12
|
+
used by both sync and async versions of the DBWrapper class.
|
|
13
|
+
|
|
14
|
+
:property db: Database backend object.
|
|
15
|
+
:property dbConn: Database connection object.
|
|
16
|
+
:property logger: Logger object
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
###########################
|
|
20
|
+
### Instance properties ###
|
|
21
|
+
###########################
|
|
22
|
+
|
|
23
|
+
# Db backend
|
|
24
|
+
db: Any
|
|
25
|
+
"""Database backend object"""
|
|
26
|
+
|
|
27
|
+
dbConn: Any
|
|
28
|
+
"""
|
|
29
|
+
Database connection object.
|
|
30
|
+
|
|
31
|
+
Its not always set. Currently is used as a placeholder for async connections.
|
|
32
|
+
For sync connections db - DatabaseBackend.connection is used.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
# logger
|
|
36
|
+
logger: Any
|
|
37
|
+
"""Logger object"""
|
|
38
|
+
|
|
39
|
+
#######################
|
|
40
|
+
### Class lifecycle ###
|
|
41
|
+
#######################
|
|
42
|
+
|
|
43
|
+
# Meta methods
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
db: DatabaseBackend,
|
|
47
|
+
dbConn: Any = None,
|
|
48
|
+
logger: logging.Logger | None = None,
|
|
49
|
+
):
|
|
50
|
+
"""
|
|
51
|
+
Initializes a new instance of the DBWrapper class.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
db (DatabaseBackend): The DatabaseBackend object.
|
|
55
|
+
logger (logging.Logger, optional): The logger object. Defaults to None.
|
|
56
|
+
"""
|
|
57
|
+
self.db = db
|
|
58
|
+
self.dbConn = dbConn
|
|
59
|
+
|
|
60
|
+
if logger is None:
|
|
61
|
+
loggerName = f"{__name__}.{self.__class__.__name__}"
|
|
62
|
+
self.logger = logging.getLogger(loggerName)
|
|
63
|
+
else:
|
|
64
|
+
self.logger = logger
|
|
65
|
+
|
|
66
|
+
def __del__(self):
|
|
67
|
+
"""
|
|
68
|
+
Deallocates the instance of the DBWrapper class.
|
|
69
|
+
"""
|
|
70
|
+
self.logger.debug("Dealloc")
|
|
71
|
+
|
|
72
|
+
# Force remove instances so that there are no circular references
|
|
73
|
+
if hasattr(self, "db") and self.db:
|
|
74
|
+
del self.db
|
|
75
|
+
|
|
76
|
+
if hasattr(self, "dbConn") and self.dbConn:
|
|
77
|
+
del self.dbConn
|
|
78
|
+
|
|
79
|
+
######################
|
|
80
|
+
### Helper methods ###
|
|
81
|
+
######################
|
|
82
|
+
|
|
83
|
+
def makeIdentifier(self, schema: str | None, name: str) -> Any:
|
|
84
|
+
"""
|
|
85
|
+
Creates a SQL identifier object from the given name.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
schema (str | None): The schema to create the identifier from.
|
|
89
|
+
name (str): The name to create the identifier from.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
str: The created SQL identifier object.
|
|
93
|
+
"""
|
|
94
|
+
if schema:
|
|
95
|
+
return f"{schema}.{name}"
|
|
96
|
+
|
|
97
|
+
return name
|
|
98
|
+
|
|
99
|
+
def logQuery(self, cursor: Any, query: Any, params: tuple[Any, ...]) -> None:
|
|
100
|
+
"""
|
|
101
|
+
Logs the given query and parameters.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
cursor (Any): The database cursor.
|
|
105
|
+
query (Any): The query to log.
|
|
106
|
+
params (tuple[Any, ...]): The parameters to log.
|
|
107
|
+
"""
|
|
108
|
+
logging.getLogger().debug(f"Query: {query} with params: {params}")
|
|
109
|
+
|
|
110
|
+
def turnDataIntoModel(
|
|
111
|
+
self,
|
|
112
|
+
emptyDataClass: DataModelType,
|
|
113
|
+
dbData: dict[str, Any],
|
|
114
|
+
) -> DataModelType:
|
|
115
|
+
"""
|
|
116
|
+
Turns the given data into a data model.
|
|
117
|
+
By default we are pretty sure that there is no factory in the cursor,
|
|
118
|
+
So we need to create a new instance of the data model and fill it with data
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
emptyDataClass (DataModelType): The data model to use.
|
|
122
|
+
dbData (dict[str, Any]): The data to turn into a model.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
DataModelType: The data model filled with data.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
result = emptyDataClass.__class__()
|
|
129
|
+
result.fillDataFromDict(dbData)
|
|
130
|
+
result.raw_data = dbData
|
|
131
|
+
|
|
132
|
+
# If the id key is not "id", we set it manually so that its filled correctly
|
|
133
|
+
if result.idKey != "id":
|
|
134
|
+
result.id = dbData.get(result.idKey, None)
|
|
135
|
+
|
|
136
|
+
return result
|
|
137
|
+
|
|
138
|
+
#####################
|
|
139
|
+
### Query methods ###
|
|
140
|
+
#####################
|
|
141
|
+
|
|
142
|
+
def filterQuery(self, schemaName: str | None, tableName: str) -> Any:
|
|
143
|
+
"""
|
|
144
|
+
Creates a SQL query to filter data from the given table.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
schemaName (str | None): The name of the schema to filter data from.
|
|
148
|
+
tableName (str): The name of the table to filter data from.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Any: The created SQL query object.
|
|
152
|
+
"""
|
|
153
|
+
fullTableName = self.makeIdentifier(schemaName, tableName)
|
|
154
|
+
return f"SELECT * FROM {fullTableName}"
|
|
155
|
+
|
|
156
|
+
def orderQuery(self, orderBy: OrderByItem | None = None) -> Any | None:
|
|
157
|
+
"""
|
|
158
|
+
Creates a SQL query to order the results by the given column.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
orderBy (OrderByItem | None, optional): The column to order the results by. Defaults to None.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Any: The created SQL query object.
|
|
165
|
+
"""
|
|
166
|
+
if orderBy is None:
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
orderList = [
|
|
170
|
+
f"{item[0]} {item[1] if len(item) > 1 and item[1] != None else 'ASC'}"
|
|
171
|
+
for item in orderBy
|
|
172
|
+
]
|
|
173
|
+
return "ORDER BY %s" % ", ".join(orderList)
|
|
174
|
+
|
|
175
|
+
def limitQuery(self, offset: int = 0, limit: int = 100) -> Any | None:
|
|
176
|
+
"""
|
|
177
|
+
Creates a SQL query to limit the number of results returned.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
offset (int, optional): The number of results to skip. Defaults to 0.
|
|
181
|
+
limit (int, optional): The maximum number of results to return. Defaults to 100.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Any: The created SQL query object.
|
|
185
|
+
"""
|
|
186
|
+
if limit == 0:
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
return f"LIMIT {limit} OFFSET {offset}"
|
|
190
|
+
|
|
191
|
+
def formatFilter(self, key: str, filter: Any) -> tuple[Any, ...]:
|
|
192
|
+
if type(filter) is dict:
|
|
193
|
+
if "$contains" in filter:
|
|
194
|
+
return (
|
|
195
|
+
f"{key} LIKE %s",
|
|
196
|
+
f"%{filter['$contains']}%",
|
|
197
|
+
)
|
|
198
|
+
elif "$starts_with" in filter:
|
|
199
|
+
return (f"{key} LIKE %s", f"{filter['$starts_with']}%")
|
|
200
|
+
elif "$ends_with" in filter:
|
|
201
|
+
return (f"{key} LIKE %s", f"%{filter['$ends_with']}")
|
|
202
|
+
elif "$min" in filter and "$max" not in filter:
|
|
203
|
+
return (f"{key} >= %s", filter["$min"]) # type: ignore
|
|
204
|
+
elif "$max" in filter and "$min" not in filter:
|
|
205
|
+
return (f"{key} <= %s", filter["$max"]) # type: ignore
|
|
206
|
+
elif "$min" in filter and "$max" in filter:
|
|
207
|
+
return (f"{key} BETWEEN %s AND %s", filter["$min"], filter["$max"]) # type: ignore
|
|
208
|
+
elif "$in" in filter:
|
|
209
|
+
inFilter1: list[Any] = cast(list[Any], filter["$in"])
|
|
210
|
+
return (f"{key} IN (%s)" % ",".join(["%s"] * len(inFilter1)),) + tuple(
|
|
211
|
+
inFilter1
|
|
212
|
+
)
|
|
213
|
+
elif "$not_in" in filter:
|
|
214
|
+
inFilter2: list[Any] = cast(list[Any], filter["$in"])
|
|
215
|
+
return (
|
|
216
|
+
f"{key} NOT IN (%s)" % ",".join(["%s"] * len(inFilter2)),
|
|
217
|
+
) + tuple(inFilter2)
|
|
218
|
+
elif "$not" in filter:
|
|
219
|
+
return (f"{key} != %s", filter["$not"]) # type: ignore
|
|
220
|
+
|
|
221
|
+
elif "$gt" in filter:
|
|
222
|
+
return (f"{key} > %s", filter["$gt"]) # type: ignore
|
|
223
|
+
elif "$gte" in filter:
|
|
224
|
+
return (f"{key} >= %s", filter["$gte"]) # type: ignore
|
|
225
|
+
elif "$lt" in filter:
|
|
226
|
+
return (f"{key} < %s", filter["$lt"]) # type: ignore
|
|
227
|
+
elif "$lte" in filter:
|
|
228
|
+
return (f"{key} <= %s", filter["$lte"]) # type: ignore
|
|
229
|
+
elif "$is_null" in filter:
|
|
230
|
+
return (f"{key} IS NULL",) # type: ignore
|
|
231
|
+
elif "$is_not_null" in filter:
|
|
232
|
+
return (f"{key} IS NOT NULL",) # type: ignore
|
|
233
|
+
|
|
234
|
+
raise NotImplementedError("Filter type not supported")
|
|
235
|
+
elif type(filter) is str or type(filter) is int or type(filter) is float:
|
|
236
|
+
return (f"{key} = %s", filter)
|
|
237
|
+
elif type(filter) is bool:
|
|
238
|
+
return (
|
|
239
|
+
f"{key} = TRUE" if filter else f"{key} = FALSE",
|
|
240
|
+
NoParam,
|
|
241
|
+
)
|
|
242
|
+
else:
|
|
243
|
+
raise NotImplementedError(
|
|
244
|
+
f"Filter type not supported: {key} = {type(filter)}"
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
def createFilter(
|
|
248
|
+
self, filter: dict[str, Any] | None
|
|
249
|
+
) -> tuple[str, tuple[Any, ...]]:
|
|
250
|
+
if filter is None or len(filter) == 0:
|
|
251
|
+
return ("", tuple())
|
|
252
|
+
|
|
253
|
+
raw = [self.formatFilter(key, filter[key]) for key in filter]
|
|
254
|
+
_query = " AND ".join([tup[0] for tup in raw])
|
|
255
|
+
_query = f"WHERE {_query}"
|
|
256
|
+
_params = tuple([val for tup in raw for val in tup[1:] if val is not NoParam])
|
|
257
|
+
|
|
258
|
+
return (_query, _params)
|
|
259
|
+
|
|
260
|
+
def _formatFilterQuery(
|
|
261
|
+
self,
|
|
262
|
+
query: Any,
|
|
263
|
+
qFilter: Any,
|
|
264
|
+
order: Any,
|
|
265
|
+
limit: Any,
|
|
266
|
+
) -> Any:
|
|
267
|
+
if qFilter is None:
|
|
268
|
+
qFilter = ""
|
|
269
|
+
if order is None:
|
|
270
|
+
order = ""
|
|
271
|
+
if limit is None:
|
|
272
|
+
limit = ""
|
|
273
|
+
return f"{query} {qFilter} {order} {limit}"
|
|
274
|
+
|
|
275
|
+
def _formatInsertQuery(
|
|
276
|
+
self,
|
|
277
|
+
tableIdentifier: Any,
|
|
278
|
+
storeData: dict[str, Any],
|
|
279
|
+
returnKey: Any,
|
|
280
|
+
) -> Any:
|
|
281
|
+
keys = storeData.keys()
|
|
282
|
+
values = list(storeData.values())
|
|
283
|
+
|
|
284
|
+
columns = ", ".join(keys)
|
|
285
|
+
valuesPlaceholder = ", ".join(["%s"] * len(values))
|
|
286
|
+
return (
|
|
287
|
+
f"INSERT INTO {tableIdentifier} "
|
|
288
|
+
f"({columns}) "
|
|
289
|
+
f"VALUES ({valuesPlaceholder}) "
|
|
290
|
+
f"RETURNING {returnKey}"
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
def _formatUpdateQuery(
|
|
294
|
+
self,
|
|
295
|
+
tableIdentifier: Any,
|
|
296
|
+
updateKey: Any,
|
|
297
|
+
updateData: dict[str, Any],
|
|
298
|
+
) -> Any:
|
|
299
|
+
keys = updateData.keys()
|
|
300
|
+
set_clause = ", ".join(f"{key} = %s" for key in keys)
|
|
301
|
+
return f"UPDATE {tableIdentifier} SET {set_clause} WHERE {updateKey} = %s"
|
|
302
|
+
|
|
303
|
+
def _formatDeleteQuery(
|
|
304
|
+
self,
|
|
305
|
+
tableIdentifier: Any,
|
|
306
|
+
deleteKey: Any,
|
|
307
|
+
) -> Any:
|
|
308
|
+
return f"DELETE FROM {tableIdentifier} WHERE {deleteKey} = %s"
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
from typing import Any, Callable, Type, TypeVar
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
AnyDataType = TypeVar("AnyDataType", bound=Type[Any])
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
def ignore_unknown_kwargs() -> Callable[[
|
|
6
|
+
def ignore_unknown_kwargs() -> Callable[[AnyDataType], AnyDataType]:
|
|
7
7
|
"""
|
|
8
8
|
Class decorator factory that modifies the __init__ method to ignore unknown keyword arguments.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
def decorator(cls:
|
|
11
|
+
def decorator(cls: AnyDataType) -> AnyDataType:
|
|
12
12
|
originalInit = cls.__init__
|
|
13
13
|
|
|
14
14
|
# @wraps(originalInit)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: database_wrapper
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.37
|
|
4
4
|
Summary: A Different Approach to Database Wrappers in Python
|
|
5
5
|
Author-email: Gints Murans <gm@gm.lv>
|
|
6
6
|
License: GNU General Public License v3.0 (GPL-3.0)
|
|
@@ -46,13 +46,13 @@ Requires-Dist: psycopg[pool] >=3.2.0 ; extra == 'dev'
|
|
|
46
46
|
Requires-Dist: mysqlclient >=2.2.2 ; extra == 'dev'
|
|
47
47
|
Requires-Dist: pymssql >=2.2.10 ; extra == 'dev'
|
|
48
48
|
Provides-Extra: mssql
|
|
49
|
-
Requires-Dist: database-wrapper-mssql ==0.1.
|
|
49
|
+
Requires-Dist: database-wrapper-mssql ==0.1.37 ; extra == 'mssql'
|
|
50
50
|
Provides-Extra: mysql
|
|
51
|
-
Requires-Dist: database-wrapper-mysql ==0.1.
|
|
51
|
+
Requires-Dist: database-wrapper-mysql ==0.1.37 ; extra == 'mysql'
|
|
52
52
|
Provides-Extra: pgsql
|
|
53
|
-
Requires-Dist: database-wrapper-pgsql ==0.1.
|
|
53
|
+
Requires-Dist: database-wrapper-pgsql ==0.1.37 ; extra == 'pgsql'
|
|
54
54
|
Provides-Extra: sqlite
|
|
55
|
-
Requires-Dist: database-wrapper-sqlite ==0.1.
|
|
55
|
+
Requires-Dist: database-wrapper-sqlite ==0.1.37 ; extra == 'sqlite'
|
|
56
56
|
|
|
57
57
|
# database_wrapper
|
|
58
58
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
database_wrapper/__init__.py,sha256=amoShlhRpOhrFbGeKbdq47H5omcVRd4xWG-RUX7bdIk,830
|
|
2
|
+
database_wrapper/common.py,sha256=fsxe28o_4xCrotPbB274dmzQ9rOyes0sBtcHog-9RVc,258
|
|
3
|
+
database_wrapper/config.py,sha256=6Xp2QYt44wT0t1bY2-J2xx4LdExLN2Tt3pLDwBvNDxA,334
|
|
4
|
+
database_wrapper/db_backend.py,sha256=iQd1yVsLumutfoOmWY-XAAiLg00_I-IueOv_RtO_7ew,5068
|
|
5
|
+
database_wrapper/db_data_model.py,sha256=njef4LR7XQcTdob961t1DQ7q3i2dzfVrFyuAtD3YYwQ,12181
|
|
6
|
+
database_wrapper/db_wrapper.py,sha256=9RuixREftA52pL24qYBJSmaZ2_4a9dDSUS4MUhPSrlE,17874
|
|
7
|
+
database_wrapper/db_wrapper_async.py,sha256=0fFqrV8fbFOaloXOg1qQVu_xA3RRJVrrJ6rQWti1aek,18318
|
|
8
|
+
database_wrapper/db_wrapper_mixin.py,sha256=YXMLPehomcG3NNE2NEcHFNuAgCF4IvAeY6YPFJbY7Ho,9987
|
|
9
|
+
database_wrapper/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
database_wrapper/utils/__init__.py,sha256=mnewmY38-837VAh4f42hvpMUBVUjOLoMtIfdZBxbkg0,134
|
|
11
|
+
database_wrapper/utils/dataclass_addons.py,sha256=5_ZAj8h-4RtimEM-b9lo6TXi4qYVTf7KIjTtu0jzAS4,762
|
|
12
|
+
database_wrapper/utils/timer.py,sha256=ZJpVMsQ7oHHgyuqMOxVee1fZD78kcDrP4c8gHug3xGU,8927
|
|
13
|
+
database_wrapper-0.1.37.dist-info/METADATA,sha256=liIL1QV89i2wUHeYplauF7TpYQomobwtRFFjBhU5E3c,3370
|
|
14
|
+
database_wrapper-0.1.37.dist-info/WHEEL,sha256=a7TGlA-5DaHMRrarXjVbQagU3Man_dCnGIWMJr5kRWo,91
|
|
15
|
+
database_wrapper-0.1.37.dist-info/top_level.txt,sha256=QcnS4ocJygxcKE5eoOqriuja306oVu-zJRn6yjRRhBw,17
|
|
16
|
+
database_wrapper-0.1.37.dist-info/RECORD,,
|