sqlengine-lite 2.1.0__py3-none-any.whl → 2.2.0__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.
- sqlengine/__init__.py +3 -3
- sqlengine/core/__init__.py +4 -0
- sqlengine/core/connection.py +255 -0
- sqlengine/core/repr.py +52 -0
- sqlengine/core/sqlgen.py +109 -0
- sqlengine/core/statements.py +400 -0
- sqlengine/core/types.py +104 -0
- sqlengine/schema.py +1 -1
- sqlengine/sqltable.py +162 -276
- sqlengine/utils/__init__.py +4 -0
- sqlengine/utils/connection.py +93 -0
- sqlengine/utils/convert.py +24 -0
- {sqlengine_lite-2.1.0.dist-info → sqlengine_lite-2.2.0.dist-info}/METADATA +53 -9
- sqlengine_lite-2.2.0.dist-info/RECORD +17 -0
- sqlengine_lite-2.1.0.dist-info/RECORD +0 -8
- {sqlengine_lite-2.1.0.dist-info → sqlengine_lite-2.2.0.dist-info}/WHEEL +0 -0
- {sqlengine_lite-2.1.0.dist-info → sqlengine_lite-2.2.0.dist-info}/licenses/LICENSE +0 -0
- {sqlengine_lite-2.1.0.dist-info → sqlengine_lite-2.2.0.dist-info}/top_level.txt +0 -0
sqlengine/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from .
|
|
2
|
-
from .
|
|
1
|
+
from .core import sqlgen, types
|
|
2
|
+
from .core.types import Schema, Primary
|
|
3
3
|
from .sqltable import SqlTableMixin
|
|
4
4
|
|
|
5
5
|
__author__ = "suffermuffin"
|
|
6
6
|
|
|
7
|
-
__all__ = ["types", "sqlgen", "Schema", "SqlTableMixin"]
|
|
7
|
+
__all__ = ["types", "sqlgen", "Schema", "SqlTableMixin", "Primary"]
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from typing import overload, Literal, Sequence
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
|
|
8
|
+
from .types import SqlValue, SqlRow
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger("sqlengine")
|
|
12
|
+
logger.setLevel(os.getenv("SQL_ENGINE_LOG_LEVEL", "WARNING").upper())
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ConnectionManager:
|
|
16
|
+
|
|
17
|
+
_trans : sqlite3.Connection
|
|
18
|
+
_trans_cursor : sqlite3.Cursor
|
|
19
|
+
|
|
20
|
+
def __init__(self, database : str, **connection_params) -> None:
|
|
21
|
+
|
|
22
|
+
self.database = database
|
|
23
|
+
self.connection_params = connection_params
|
|
24
|
+
|
|
25
|
+
self._is_managed_transaction = False
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@overload
|
|
29
|
+
def _fetch(self, query : str, args : SqlRow,
|
|
30
|
+
method : Literal["fetchone"]) -> SqlRow: ...
|
|
31
|
+
@overload
|
|
32
|
+
def _fetch(self, query : str, args : SqlRow,
|
|
33
|
+
method : Literal["fetchall"]) -> list[SqlRow]: ...
|
|
34
|
+
|
|
35
|
+
def _fetch(
|
|
36
|
+
self,
|
|
37
|
+
query : str,
|
|
38
|
+
args : SqlRow = (),
|
|
39
|
+
method : Literal["fetchone", "fetchall"] = "fetchall"
|
|
40
|
+
) -> SqlRow | list[SqlRow]:
|
|
41
|
+
|
|
42
|
+
logger.debug(f"{self.database}: {query} {args}")
|
|
43
|
+
|
|
44
|
+
if self.in_transaction():
|
|
45
|
+
self._trans_cursor.execute(query, args)
|
|
46
|
+
return getattr(self._trans_cursor, method)()
|
|
47
|
+
|
|
48
|
+
with self.connect() as conn:
|
|
49
|
+
cursor = conn.cursor()
|
|
50
|
+
cursor.execute(query, args)
|
|
51
|
+
return getattr(cursor, method)()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@overload
|
|
55
|
+
def _execute(self, query : str, args : tuple[SqlValue, ...], method : Literal["execute"]) -> None: ...
|
|
56
|
+
@overload
|
|
57
|
+
def _execute(self, query : str, args : Sequence[SqlRow], method : Literal["executemany"]) -> None: ...
|
|
58
|
+
|
|
59
|
+
def _execute(
|
|
60
|
+
self,
|
|
61
|
+
query : str,
|
|
62
|
+
args : tuple[SqlValue, ...] | Sequence[SqlRow] = (),
|
|
63
|
+
method : Literal["execute", "executemany"] = "execute"
|
|
64
|
+
) -> None:
|
|
65
|
+
"""
|
|
66
|
+
Shortcut to connect() -> execute[<many>]() -> commit() for single operations.
|
|
67
|
+
Can be used in transaction using `transaction()` manager.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
query (str): SQL query to execute on SQLite3 DB
|
|
71
|
+
*args (Any): Arguments to the execution
|
|
72
|
+
method (str): "execute" or "executemany"
|
|
73
|
+
"""
|
|
74
|
+
logger.debug(f"{self.database}: {query} {args}")
|
|
75
|
+
|
|
76
|
+
if self.in_transaction():
|
|
77
|
+
getattr(self._trans_cursor, method)(query, args)
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
with self.connect() as conn:
|
|
81
|
+
cursor = conn.cursor()
|
|
82
|
+
getattr(cursor, method)(query, args)
|
|
83
|
+
conn.commit()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def execute(self, query : str, *args : SqlValue) -> None:
|
|
87
|
+
"""
|
|
88
|
+
Shortcut to connect() -> execute() -> commit() for single operations.
|
|
89
|
+
Can be used in transaction using `transaction()` manager.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
query (str): SQL query to execute on SQLite3 DB
|
|
93
|
+
*args (tuple[SqlValue, ...]): Arguments to the execution
|
|
94
|
+
"""
|
|
95
|
+
return self._execute(query, args, method="execute")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def executemany(self, query : str, args : Sequence[SqlRow]) -> None:
|
|
99
|
+
"""
|
|
100
|
+
Shortcut to connect() -> executemany() -> commit() for single operations.
|
|
101
|
+
Can be used in transaction using `transaction()` manager.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
query (str): SQL query to execute on SQLite3 DB
|
|
105
|
+
args (list[tuple[SqlValue, ...]]): Arguments to the execution
|
|
106
|
+
"""
|
|
107
|
+
return self._execute(query, args, method="executemany")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def fetchone(self, query : str, *args : SqlValue) -> SqlRow:
|
|
111
|
+
"""
|
|
112
|
+
Fetch first row based on `query`
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
query (str): SQL query
|
|
116
|
+
*args (tuple[SqlValue, ...]): Arguments to the execution
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
row (SqlRow): Single row
|
|
120
|
+
"""
|
|
121
|
+
return self._fetch(query, args, method="fetchone")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def fetchmany(self, query : str, *args : SqlValue, size : int = 1) -> list[SqlRow]:
|
|
125
|
+
"""
|
|
126
|
+
Fetch first `size` rows based on `query`
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
query (str): SQL query
|
|
130
|
+
*args (tuple[SqlValue, ...]): Arguments to the execution
|
|
131
|
+
size (str): Number of rows to return
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
rows (list[SqlRow]): list of `size` rows
|
|
135
|
+
"""
|
|
136
|
+
logger.debug(f"{self.database}: {query} {args}")
|
|
137
|
+
|
|
138
|
+
if self.in_transaction():
|
|
139
|
+
self._trans_cursor.execute(query, args)
|
|
140
|
+
return self._trans_cursor.fetchmany(size)
|
|
141
|
+
|
|
142
|
+
with self.connect() as conn:
|
|
143
|
+
cursor = conn.cursor()
|
|
144
|
+
cursor.execute(query, args)
|
|
145
|
+
return cursor.fetchmany(size)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def fetchall(self, query : str, *args : SqlValue) -> list[SqlRow]:
|
|
149
|
+
"""
|
|
150
|
+
Fetch all rows based on `query`
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
query (str): SQL query
|
|
154
|
+
*args (tuple[SqlValue, ...]): Arguments to the execution
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
rows (list[SqlRow]): list of rows
|
|
158
|
+
"""
|
|
159
|
+
return self._fetch(query, args, method="fetchall")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def in_transaction(self) -> bool:
|
|
163
|
+
""" Returns True if instance is in transaction """
|
|
164
|
+
return hasattr(self, "_trans") and hasattr(self, "_trans_cursor")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def connect(self) -> sqlite3.Connection:
|
|
168
|
+
""" Shortcut to sqlite3 connection context manager """
|
|
169
|
+
return sqlite3.connect(self.database, **self.connection_params)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def open(self) -> None:
|
|
173
|
+
""" Opens unmanaged transaction """
|
|
174
|
+
if self.in_transaction():
|
|
175
|
+
raise RuntimeError("Can't re-open existing connection")
|
|
176
|
+
|
|
177
|
+
self._trans = self.connect()
|
|
178
|
+
self._trans_cursor = self._trans.cursor()
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def close(self) -> None:
|
|
182
|
+
""" Closes unmanaged transaction """
|
|
183
|
+
if not self.in_transaction():
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
if self._is_managed_transaction:
|
|
187
|
+
raise RuntimeError("Can't manually close managed transaction")
|
|
188
|
+
|
|
189
|
+
self._trans_cursor.close()
|
|
190
|
+
self._trans.close()
|
|
191
|
+
del(self._trans_cursor)
|
|
192
|
+
del(self._trans)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def commit(self) -> None:
|
|
196
|
+
if not self.in_transaction():
|
|
197
|
+
raise RuntimeError("Can't commit outside transaction mode")
|
|
198
|
+
|
|
199
|
+
self._trans.commit()
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def rollback(self) -> None:
|
|
203
|
+
if not self.in_transaction():
|
|
204
|
+
raise RuntimeError("Can't rollback outside transaction mode")
|
|
205
|
+
|
|
206
|
+
self._trans.rollback()
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@contextmanager
|
|
210
|
+
def transaction(self, autocommit : bool = True):
|
|
211
|
+
"""
|
|
212
|
+
Creates context manager to use class methods in transaction
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
autocommit (bool): If `True`, will commit changes at the end of transaction
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
self.open()
|
|
219
|
+
self._is_managed_transaction = True
|
|
220
|
+
logger.debug(f"{self.database}: Transaction started")
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
yield
|
|
224
|
+
|
|
225
|
+
except Exception as e:
|
|
226
|
+
logger.error(f"{self.database}: Error while in transaction: {e}")
|
|
227
|
+
logger.debug(e, exc_info=True)
|
|
228
|
+
self._trans.rollback()
|
|
229
|
+
raise e
|
|
230
|
+
|
|
231
|
+
else:
|
|
232
|
+
if autocommit:
|
|
233
|
+
self._trans.commit()
|
|
234
|
+
|
|
235
|
+
finally:
|
|
236
|
+
self._is_managed_transaction = False
|
|
237
|
+
self.close()
|
|
238
|
+
logger.debug(f"{self.database}: Transaction finished")
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@property
|
|
242
|
+
def tx_conn(self) -> sqlite3.Connection:
|
|
243
|
+
""" Gives access to connection while in transaction """
|
|
244
|
+
if not self.in_transaction():
|
|
245
|
+
raise RuntimeError("`tx_conn` is not available outside the transaction mode")
|
|
246
|
+
return self._trans
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@property
|
|
250
|
+
def tx_cursor(self) -> sqlite3.Cursor:
|
|
251
|
+
""" Gives access to connection cursor while in transaction """
|
|
252
|
+
if not self.in_transaction():
|
|
253
|
+
raise RuntimeError("`tx_cursor` is not available outside the transaction mode")
|
|
254
|
+
return self._trans_cursor
|
|
255
|
+
|
sqlengine/core/repr.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from html import escape
|
|
2
|
+
from typing import Sequence
|
|
3
|
+
|
|
4
|
+
from .types import SqlRow
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def to_html(tablename : str, columns : list[str], repr_rows : Sequence[SqlRow], limit : int = 25) -> str:
|
|
8
|
+
|
|
9
|
+
tablestyle = "<table style=\"border-collapse: collapse; font-size: 14px;\">"
|
|
10
|
+
tablenamestyle = "<caption style=\"font-size: 18px; font-weight: bold;\">{}</caption>"
|
|
11
|
+
colstyle = "<td style=\"border: 1px solid #555; text-align: center;\">{}</td>"
|
|
12
|
+
cellstyle = "<td style=\"border: 1px solid #000; text-align: center;\">{}</td>"
|
|
13
|
+
morestyle = "<td colspan=\"{}\" style=\"text-align:center;color:#888;font-style:italic;padding:8px;\">... more rows ...</td>"
|
|
14
|
+
|
|
15
|
+
cols = [colstyle.format(escape(col)) for col in columns]
|
|
16
|
+
tablename = tablenamestyle.format(escape(tablename))
|
|
17
|
+
more = morestyle.format(len(columns))
|
|
18
|
+
|
|
19
|
+
html = [
|
|
20
|
+
tablestyle,
|
|
21
|
+
tablename,
|
|
22
|
+
"<thead>",
|
|
23
|
+
"<tr>",
|
|
24
|
+
*cols,
|
|
25
|
+
"</tr>",
|
|
26
|
+
"</thead>",
|
|
27
|
+
"<tbody>",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
for row in repr_rows[:limit]:
|
|
31
|
+
values = [cellstyle.format(escape(str(val))) for val in row]
|
|
32
|
+
|
|
33
|
+
html.extend([
|
|
34
|
+
"<tr>",
|
|
35
|
+
*values,
|
|
36
|
+
"</tr>"
|
|
37
|
+
])
|
|
38
|
+
|
|
39
|
+
if len(repr_rows) > limit:
|
|
40
|
+
html.extend([
|
|
41
|
+
"<tr>",
|
|
42
|
+
more,
|
|
43
|
+
"</tr>"
|
|
44
|
+
])
|
|
45
|
+
|
|
46
|
+
html.extend([
|
|
47
|
+
"</tbody>",
|
|
48
|
+
"</table>",
|
|
49
|
+
])
|
|
50
|
+
|
|
51
|
+
return "".join(html)
|
|
52
|
+
|
sqlengine/core/sqlgen.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from typing import Sequence
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def format_list(items : Sequence | set, brackets : bool = True) -> str:
|
|
5
|
+
""" Formats list into `(item1, item2, ...)` format """
|
|
6
|
+
items_str = ', '.join([str(i) for i in items])
|
|
7
|
+
return f'({items_str})' if brackets else items_str
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def create_table(
|
|
11
|
+
tablename: str,
|
|
12
|
+
columns : list[str],
|
|
13
|
+
types : list[str],
|
|
14
|
+
primary : list[str]
|
|
15
|
+
) -> str:
|
|
16
|
+
|
|
17
|
+
columns_types = format_list([
|
|
18
|
+
f'{col} {dtype}' for col, dtype
|
|
19
|
+
in zip(columns, types)], brackets=False)
|
|
20
|
+
|
|
21
|
+
primary_keys = format_list(primary)
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
f"CREATE TABLE IF NOT EXISTS {tablename} "
|
|
25
|
+
f"({columns_types}, PRIMARY KEY {primary_keys});"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def drop_table(tablename : str) -> str:
|
|
30
|
+
return f"DROP TABLE IF EXISTS {tablename}"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def values_placeholder(n_values : int) -> str:
|
|
34
|
+
""" Creates placeholder `(?, ?, ?, ...)` with "?" `n_values` times """
|
|
35
|
+
return format_list(['?']*n_values)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def bulk_placeholder(n_values : int, n_rows : int) -> str:
|
|
39
|
+
""" Creates placeholders `(?, ?, ..), (?, ?, ..), ...` for each row """
|
|
40
|
+
place_holder = values_placeholder(n_values)
|
|
41
|
+
return format_list([place_holder]*n_rows, False)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def insert(tablename : str, columns : list[str], values : str) -> str:
|
|
45
|
+
""" Creates insert query """
|
|
46
|
+
return f"INSERT INTO {tablename} {format_list(columns)} VALUES {values};"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def insert_row(tablename : str, columns : list[str]) -> str:
|
|
50
|
+
""" Creates insert query for 1 row """
|
|
51
|
+
n_values = len(columns)
|
|
52
|
+
placeholder = values_placeholder(n_values)
|
|
53
|
+
return insert(tablename, columns, placeholder)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def insert_many(tablename : str, columns : list[str], n_rows : int) -> str:
|
|
57
|
+
""" Creates insert query for multiple rows """
|
|
58
|
+
n_values = len(columns)
|
|
59
|
+
placeholder = bulk_placeholder(n_values, n_rows)
|
|
60
|
+
return insert(tablename, columns, placeholder)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def delete_rows(tablename : str, where_clause : str) -> str:
|
|
64
|
+
return f"DELETE FROM {tablename} WHERE {where_clause};"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def select(
|
|
68
|
+
tablename : str,
|
|
69
|
+
columns : str | list[str] = "*",
|
|
70
|
+
where_clause: str | None = None,
|
|
71
|
+
order_by : str | None = None,
|
|
72
|
+
limit : int | str | None = None
|
|
73
|
+
) -> str:
|
|
74
|
+
""" Creates select query """
|
|
75
|
+
|
|
76
|
+
_columns = columns if isinstance(columns, str) else format_list(columns, False)
|
|
77
|
+
|
|
78
|
+
query = f"SELECT {_columns} FROM {tablename}"
|
|
79
|
+
query += f" WHERE {where_clause}" if where_clause else ""
|
|
80
|
+
query += f" ORDER BY {order_by}" if order_by else ""
|
|
81
|
+
query += f" LIMIT {limit}" if limit else ""
|
|
82
|
+
query += ";"
|
|
83
|
+
|
|
84
|
+
return query
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def upsert(tablename : str, columns : list[str], primary_key : list[str]) -> str:
|
|
88
|
+
"""
|
|
89
|
+
Creates query to upsert (update or insert) row based on `primary_key`
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
tablename (str): name of the table in db
|
|
93
|
+
columns (list[str]): list of table columns
|
|
94
|
+
primary_key (list[str]): list of primary keys
|
|
95
|
+
"""
|
|
96
|
+
placeholder = values_placeholder(len(columns))
|
|
97
|
+
non_primary = set(columns) - set(primary_key)
|
|
98
|
+
updated_list = [f"{col}=excluded.{col}" for col in non_primary]
|
|
99
|
+
|
|
100
|
+
columns_str = format_list(columns)
|
|
101
|
+
updated_str = format_list(updated_list, False)
|
|
102
|
+
primary_str = format_list(primary_key)
|
|
103
|
+
|
|
104
|
+
query = (
|
|
105
|
+
f"INSERT INTO {tablename} {columns_str} VALUES {placeholder} "
|
|
106
|
+
f"ON CONFLICT {primary_str} "
|
|
107
|
+
f"DO UPDATE SET {updated_str};"
|
|
108
|
+
)
|
|
109
|
+
return query
|