sqlengine-lite 2.2.0__py3-none-any.whl → 2.2.2__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 +7 -3
- sqlengine/_internal/__init__.py +4 -0
- sqlengine/{core/connection.py → _internal/connection_manager.py} +25 -11
- sqlengine/{core → _internal}/statements.py +53 -22
- sqlengine/{core → _internal}/types.py +9 -8
- sqlengine/exceptions.py +20 -0
- sqlengine/schema.py +3 -9
- sqlengine/sqltable.py +100 -68
- sqlengine/utils/__init__.py +2 -2
- sqlengine/utils/connection.py +12 -9
- sqlengine/utils/convert.py +116 -10
- {sqlengine_lite-2.2.0.dist-info → sqlengine_lite-2.2.2.dist-info}/METADATA +181 -98
- sqlengine_lite-2.2.2.dist-info/RECORD +18 -0
- sqlengine/core/__init__.py +0 -4
- sqlengine_lite-2.2.0.dist-info/RECORD +0 -17
- /sqlengine/{core → _internal}/repr.py +0 -0
- /sqlengine/{core → _internal}/sqlgen.py +0 -0
- {sqlengine_lite-2.2.0.dist-info → sqlengine_lite-2.2.2.dist-info}/WHEEL +0 -0
- {sqlengine_lite-2.2.0.dist-info → sqlengine_lite-2.2.2.dist-info}/licenses/LICENSE +0 -0
- {sqlengine_lite-2.2.0.dist-info → sqlengine_lite-2.2.2.dist-info}/top_level.txt +0 -0
sqlengine/__init__.py
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
from .
|
|
2
|
-
from .
|
|
1
|
+
from ._internal import sqlgen
|
|
2
|
+
from ._internal import ConnectionManager
|
|
3
|
+
from ._internal.types import Schema, Primary, register_type
|
|
3
4
|
from .sqltable import SqlTableMixin
|
|
4
5
|
|
|
5
6
|
__author__ = "suffermuffin"
|
|
6
7
|
|
|
7
|
-
__all__ = [
|
|
8
|
+
__all__ = [
|
|
9
|
+
"sqlgen", "Schema", "SqlTableMixin",
|
|
10
|
+
"Primary", "ConnectionManager", "register_type"
|
|
11
|
+
]
|
|
@@ -6,6 +6,7 @@ from typing import overload, Literal, Sequence
|
|
|
6
6
|
from contextlib import contextmanager
|
|
7
7
|
|
|
8
8
|
from .types import SqlValue, SqlRow
|
|
9
|
+
from ..exceptions import TransactionError, NestedTransactionError, OutsideTransactionError
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
logger = logging.getLogger("sqlengine")
|
|
@@ -14,6 +15,15 @@ logger.setLevel(os.getenv("SQL_ENGINE_LOG_LEVEL", "WARNING").upper())
|
|
|
14
15
|
|
|
15
16
|
class ConnectionManager:
|
|
16
17
|
|
|
18
|
+
"""
|
|
19
|
+
Connection manager for sqlite3
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
|
|
23
|
+
database (str): database filename to connect to. If `":memory:"` is passed, then database will be set in memory.
|
|
24
|
+
**connection_params: Params to create connection with. Reference: https://docs.python.org/3/library/sqlite3.html#sqlite3.connect
|
|
25
|
+
"""
|
|
26
|
+
|
|
17
27
|
_trans : sqlite3.Connection
|
|
18
28
|
_trans_cursor : sqlite3.Cursor
|
|
19
29
|
|
|
@@ -128,7 +138,7 @@ class ConnectionManager:
|
|
|
128
138
|
Args:
|
|
129
139
|
query (str): SQL query
|
|
130
140
|
*args (tuple[SqlValue, ...]): Arguments to the execution
|
|
131
|
-
size (
|
|
141
|
+
size (int): Number of rows to return
|
|
132
142
|
|
|
133
143
|
Returns:
|
|
134
144
|
rows (list[SqlRow]): list of `size` rows
|
|
@@ -172,7 +182,7 @@ class ConnectionManager:
|
|
|
172
182
|
def open(self) -> None:
|
|
173
183
|
""" Opens unmanaged transaction """
|
|
174
184
|
if self.in_transaction():
|
|
175
|
-
raise
|
|
185
|
+
raise NestedTransactionError("Can't re-open existing connection")
|
|
176
186
|
|
|
177
187
|
self._trans = self.connect()
|
|
178
188
|
self._trans_cursor = self._trans.cursor()
|
|
@@ -184,24 +194,28 @@ class ConnectionManager:
|
|
|
184
194
|
return
|
|
185
195
|
|
|
186
196
|
if self._is_managed_transaction:
|
|
187
|
-
raise
|
|
197
|
+
raise TransactionError("Can't manually close managed transaction")
|
|
188
198
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
199
|
+
try:
|
|
200
|
+
self._trans_cursor.close()
|
|
201
|
+
self._trans.close()
|
|
202
|
+
except sqlite3.ProgrammingError as e:
|
|
203
|
+
raise TransactionError("Can't close connection properly") from e
|
|
204
|
+
finally:
|
|
205
|
+
del(self._trans_cursor)
|
|
206
|
+
del(self._trans)
|
|
193
207
|
|
|
194
208
|
|
|
195
209
|
def commit(self) -> None:
|
|
196
210
|
if not self.in_transaction():
|
|
197
|
-
raise
|
|
211
|
+
raise OutsideTransactionError("Can't commit outside transaction mode")
|
|
198
212
|
|
|
199
213
|
self._trans.commit()
|
|
200
214
|
|
|
201
215
|
|
|
202
216
|
def rollback(self) -> None:
|
|
203
217
|
if not self.in_transaction():
|
|
204
|
-
raise
|
|
218
|
+
raise OutsideTransactionError("Can't rollback outside transaction mode")
|
|
205
219
|
|
|
206
220
|
self._trans.rollback()
|
|
207
221
|
|
|
@@ -242,7 +256,7 @@ class ConnectionManager:
|
|
|
242
256
|
def tx_conn(self) -> sqlite3.Connection:
|
|
243
257
|
""" Gives access to connection while in transaction """
|
|
244
258
|
if not self.in_transaction():
|
|
245
|
-
raise
|
|
259
|
+
raise OutsideTransactionError("`tx_conn` is not available outside the transaction mode")
|
|
246
260
|
return self._trans
|
|
247
261
|
|
|
248
262
|
|
|
@@ -250,6 +264,6 @@ class ConnectionManager:
|
|
|
250
264
|
def tx_cursor(self) -> sqlite3.Cursor:
|
|
251
265
|
""" Gives access to connection cursor while in transaction """
|
|
252
266
|
if not self.in_transaction():
|
|
253
|
-
raise
|
|
267
|
+
raise OutsideTransactionError("`tx_cursor` is not available outside the transaction mode")
|
|
254
268
|
return self._trans_cursor
|
|
255
269
|
|
|
@@ -2,10 +2,11 @@ from typing import Sequence, Literal, Generator, Self
|
|
|
2
2
|
from abc import ABC, abstractmethod
|
|
3
3
|
|
|
4
4
|
from . import sqlgen as sql
|
|
5
|
-
from .
|
|
5
|
+
from .connection_manager import ConnectionManager
|
|
6
6
|
|
|
7
7
|
from .types import SqlValue, SqlRow, Schema
|
|
8
8
|
from .repr import to_html
|
|
9
|
+
from ..exceptions import SqlEngineError, OutsideTransactionError
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class Where[T : "Statement"]:
|
|
@@ -31,39 +32,46 @@ class Where[T : "Statement"]:
|
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
def op(self, column : str, value : SqlValue, operator : str) -> Self:
|
|
35
|
+
""" Adds operator to the where clause """
|
|
34
36
|
self._clause.append(f"{column} {operator} ?")
|
|
35
37
|
self._args.append(value)
|
|
36
38
|
return self
|
|
37
39
|
|
|
38
40
|
|
|
39
41
|
def join(self, lop : str = "AND") -> Self:
|
|
40
|
-
"""
|
|
42
|
+
""" Join previous expression via logical operator `lop` """
|
|
41
43
|
joined = f" {lop} ".join(self._clause)
|
|
42
44
|
self._clause = [f"({joined})"]
|
|
43
45
|
return self
|
|
44
46
|
|
|
45
47
|
|
|
46
48
|
def eq(self, column : str, value : SqlValue) -> Self:
|
|
49
|
+
""" Add `column = value` to the where clause """
|
|
47
50
|
return self.op(column, value, "=")
|
|
48
51
|
|
|
49
52
|
|
|
50
53
|
def neq(self, column : str, value : SqlValue) -> Self:
|
|
54
|
+
""" Add `column != value` to the where clause """
|
|
51
55
|
return self.op(column, value, "!=")
|
|
52
56
|
|
|
53
57
|
|
|
54
58
|
def gt(self, column : str, value : SqlValue) -> Self:
|
|
59
|
+
""" Add `column > value` to the where clause """
|
|
55
60
|
return self.op(column, value, ">")
|
|
56
61
|
|
|
57
62
|
|
|
58
63
|
def gte(self, column : str, value : SqlValue) -> Self:
|
|
64
|
+
""" Add `column >= value` to the where clause """
|
|
59
65
|
return self.op(column, value, ">=")
|
|
60
66
|
|
|
61
67
|
|
|
62
68
|
def lt(self, column : str, value : SqlValue) -> Self:
|
|
69
|
+
""" Add `column < value` to the where clause """
|
|
63
70
|
return self.op(column, value, "<")
|
|
64
71
|
|
|
65
72
|
|
|
66
73
|
def lte(self, column : str, value : SqlValue) -> Self:
|
|
74
|
+
""" Add `column <= value` to the where clause """
|
|
67
75
|
return self.op(column, value, "<=")
|
|
68
76
|
|
|
69
77
|
|
|
@@ -76,12 +84,13 @@ class Where[T : "Statement"]:
|
|
|
76
84
|
|
|
77
85
|
|
|
78
86
|
def is_null(self, column : str) -> Self:
|
|
87
|
+
""" Add `column IS NULL` to the where clause """
|
|
79
88
|
self._clause.append(f"{column} IS NULL")
|
|
80
89
|
return self
|
|
81
90
|
|
|
82
91
|
|
|
83
92
|
def inverted(self) -> Self:
|
|
84
|
-
""" Invert
|
|
93
|
+
""" Invert previous where clauses with `NOT` """
|
|
85
94
|
self._clause[-1] = f"NOT ({self._clause[-1]})"
|
|
86
95
|
return self
|
|
87
96
|
|
|
@@ -127,14 +136,16 @@ class Where[T : "Statement"]:
|
|
|
127
136
|
return self._statement.__repr__()
|
|
128
137
|
|
|
129
138
|
|
|
139
|
+
def __iter__(self):
|
|
140
|
+
if not isinstance(self._statement, Select):
|
|
141
|
+
raise SqlEngineError("Can iterate only over `Select` statements")
|
|
142
|
+
return iter(self._statement)
|
|
143
|
+
|
|
144
|
+
|
|
130
145
|
def _repr_html_(self) -> str | None:
|
|
131
146
|
if isinstance(self._statement, Select):
|
|
132
147
|
return self._statement._repr_html_()
|
|
133
148
|
return None
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def __len__(self) -> int:
|
|
137
|
-
return len(self._args)
|
|
138
149
|
|
|
139
150
|
|
|
140
151
|
class Statement(ABC):
|
|
@@ -146,7 +157,7 @@ class Statement(ABC):
|
|
|
146
157
|
|
|
147
158
|
self._tableschema = tableschema
|
|
148
159
|
self._connection = connection
|
|
149
|
-
self._where
|
|
160
|
+
self._where = Where(self)
|
|
150
161
|
|
|
151
162
|
self._custom_query : str | None = None
|
|
152
163
|
self._custom_args : tuple[SqlValue, ...] = ()
|
|
@@ -206,6 +217,7 @@ class Statement(ABC):
|
|
|
206
217
|
class MutationalStatement(Statement, ABC):
|
|
207
218
|
|
|
208
219
|
def execute(self) -> None:
|
|
220
|
+
""" Execute built statement """
|
|
209
221
|
query, args = self.build()
|
|
210
222
|
self._connection.execute(query, *args)
|
|
211
223
|
|
|
@@ -222,66 +234,74 @@ class Select(Statement):
|
|
|
222
234
|
|
|
223
235
|
|
|
224
236
|
def __call__(self, *columns : str) -> Self:
|
|
237
|
+
""" Shortcut to columns selector """
|
|
225
238
|
return self.columns(*columns)
|
|
226
239
|
|
|
227
240
|
|
|
228
241
|
def columns(self, *columns : str) -> Self:
|
|
229
|
-
"""
|
|
242
|
+
""" Columns selector """
|
|
230
243
|
self._columns.extend(columns)
|
|
231
244
|
return self
|
|
232
245
|
|
|
233
246
|
|
|
234
247
|
def aggregate(self, by : Literal['COUNT', 'SUM', 'AVG', 'MIN', 'MAX']) -> Self:
|
|
235
|
-
|
|
248
|
+
""" Aggregate by provided method """
|
|
236
249
|
if self._aggregate:
|
|
237
|
-
raise
|
|
250
|
+
raise SqlEngineError("Can't aggregate columns multiple times")
|
|
238
251
|
|
|
239
252
|
self._aggregate = by
|
|
240
253
|
return self
|
|
241
254
|
|
|
242
255
|
|
|
243
256
|
def order_by(self, column : str, ascending : bool = True) -> Self:
|
|
257
|
+
""" Orders returned rows by provided column """
|
|
244
258
|
order = "ASC" if ascending else "DESC"
|
|
245
259
|
self._order_by.append(f"{column} {order}")
|
|
246
260
|
return self
|
|
247
261
|
|
|
248
262
|
|
|
249
263
|
def limit(self, n : int) -> Self:
|
|
264
|
+
""" Limit number of returned rows """
|
|
250
265
|
self._limit = n
|
|
251
266
|
return self
|
|
252
267
|
|
|
253
268
|
|
|
254
269
|
def fetchone(self) -> SqlRow:
|
|
270
|
+
""" Fetch first row """
|
|
255
271
|
query, args = self.build()
|
|
256
272
|
return self._connection.fetchone(query, *args)
|
|
257
273
|
|
|
258
274
|
|
|
259
275
|
def fetchmany(self, size : int = 1) -> list[SqlRow]:
|
|
276
|
+
""" Fetch first `size` rows """
|
|
260
277
|
query, args = self.build()
|
|
261
278
|
return self._connection.fetchmany(query, *args, size=size)
|
|
262
279
|
|
|
263
280
|
|
|
264
281
|
def fetchall(self) -> list[SqlRow]:
|
|
282
|
+
""" Fetch all rows """
|
|
265
283
|
query, args = self.build()
|
|
266
284
|
return self._connection.fetchall(query, *args)
|
|
267
285
|
|
|
268
286
|
|
|
269
287
|
def fetchmany_iterator(self, batch_size: int) -> Generator[list[SqlRow], None, None]:
|
|
270
288
|
"""
|
|
271
|
-
Yields all rows in batches
|
|
289
|
+
Yields all rows in batches within a single transaction.
|
|
272
290
|
|
|
273
291
|
Args:
|
|
274
292
|
batch_size (int): Size of each batch
|
|
275
293
|
|
|
276
|
-
|
|
294
|
+
Example:
|
|
277
295
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
296
|
+
```python
|
|
297
|
+
with table.transaction():
|
|
298
|
+
for batch in table.select.where.gt("Age", 30).then.fetchmany_iterator(1000):
|
|
299
|
+
process_batch(batch)
|
|
300
|
+
```
|
|
281
301
|
"""
|
|
282
302
|
if not self._connection.in_transaction():
|
|
283
|
-
raise
|
|
284
|
-
to keep open the transaction of the table
|
|
303
|
+
raise OutsideTransactionError("To use the `fetchall_iterator()` method you have "
|
|
304
|
+
"to keep open the transaction of the table")
|
|
285
305
|
|
|
286
306
|
query, exec_args = self.build()
|
|
287
307
|
|
|
@@ -293,11 +313,21 @@ class Select(Statement):
|
|
|
293
313
|
|
|
294
314
|
|
|
295
315
|
def __iter__(self) -> Generator[SqlRow, None, None]:
|
|
296
|
-
"""
|
|
316
|
+
"""
|
|
317
|
+
Select statement rows iterator
|
|
318
|
+
|
|
319
|
+
Example:
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
with table.transaction():
|
|
323
|
+
for row in table.select.where.gt("Age", 30):
|
|
324
|
+
process_row(row)
|
|
325
|
+
```
|
|
326
|
+
"""
|
|
297
327
|
|
|
298
328
|
if not self._connection.in_transaction():
|
|
299
|
-
raise
|
|
300
|
-
to keep open the transaction of the table
|
|
329
|
+
raise OutsideTransactionError("To use the `__iter__` method you have "
|
|
330
|
+
"to keep open the transaction of the table")
|
|
301
331
|
|
|
302
332
|
query, exec_args = self.build()
|
|
303
333
|
|
|
@@ -360,7 +390,7 @@ class Delete(MutationalStatement):
|
|
|
360
390
|
def _build(self, where_clause : str, *args : SqlValue) -> tuple[str, tuple[SqlValue, ...]]:
|
|
361
391
|
|
|
362
392
|
if not where_clause:
|
|
363
|
-
raise
|
|
393
|
+
raise SqlEngineError("Delete statement must have a where clause")
|
|
364
394
|
|
|
365
395
|
query = sql.delete_rows(self._tableschema["tablename"], where_clause)
|
|
366
396
|
return query, args
|
|
@@ -379,6 +409,7 @@ class Update(MutationalStatement):
|
|
|
379
409
|
|
|
380
410
|
|
|
381
411
|
def __call__(self, column : str, value : SqlValue) -> Self:
|
|
412
|
+
""" Shortcut to set value to a column """
|
|
382
413
|
return self.set(column, value)
|
|
383
414
|
|
|
384
415
|
|
|
@@ -12,15 +12,16 @@ class CustomType(Protocol):
|
|
|
12
12
|
...
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
type SqlValue
|
|
16
|
-
type SqlRow
|
|
17
|
-
type SqlType
|
|
15
|
+
type SqlValue = str | int | float | bytes | None | CustomType
|
|
16
|
+
type SqlRow = tuple[SqlValue, ...]
|
|
17
|
+
type SqlType = type[str | int | float | bytes | CustomType]
|
|
18
|
+
type ColumnType = SqlType | str | UnionType
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
class Schema(TypedDict):
|
|
21
22
|
tablename : str
|
|
22
23
|
columns : list[str]
|
|
23
|
-
types : list[
|
|
24
|
+
types : list[ColumnType]
|
|
24
25
|
primary : list[str]
|
|
25
26
|
|
|
26
27
|
|
|
@@ -41,7 +42,7 @@ _TYPES_MAP : dict[type | UnionType, str] = {
|
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
|
|
44
|
-
def is_custom_type(type_: SqlType) -> TypeGuard[type[CustomType]]:
|
|
45
|
+
def is_custom_type(type_: SqlType | UnionType) -> TypeGuard[type[CustomType]]:
|
|
45
46
|
return (
|
|
46
47
|
type_ not in (str, int, float, bytes)
|
|
47
48
|
and isinstance(type_, type)
|
|
@@ -52,7 +53,7 @@ def is_custom_type(type_: SqlType) -> TypeGuard[type[CustomType]]:
|
|
|
52
53
|
|
|
53
54
|
def register_type(cls : type[CustomType], type_name : str | None = None) -> None:
|
|
54
55
|
"""
|
|
55
|
-
Register custom type to be able to store it in tables
|
|
56
|
+
Register custom type into sqlite3 to be able to store it in tables
|
|
56
57
|
|
|
57
58
|
Args:
|
|
58
59
|
cls (CustomType): Class that implements `from_sql(cls, sql : bytes) -> Self` and `
|
|
@@ -65,7 +66,7 @@ def register_type(cls : type[CustomType], type_name : str | None = None) -> None
|
|
|
65
66
|
sqlite3.register_converter(type_name, cls.from_sql)
|
|
66
67
|
|
|
67
68
|
|
|
68
|
-
def pytype_to_sqltype(type_ : type) -> str:
|
|
69
|
+
def pytype_to_sqltype(type_ : type | UnionType) -> str:
|
|
69
70
|
""" Converts python type to sql type """
|
|
70
71
|
if type_ not in _TYPES_MAP:
|
|
71
72
|
raise TypeError(f"{type_} is not natively supported by sqlite3")
|
|
@@ -73,7 +74,7 @@ def pytype_to_sqltype(type_ : type) -> str:
|
|
|
73
74
|
return _TYPES_MAP[type_]
|
|
74
75
|
|
|
75
76
|
|
|
76
|
-
def register_resolve_types(types : list[
|
|
77
|
+
def register_resolve_types(types : list[ColumnType], **connection_params) -> tuple[list[str], dict[str, Any]]:
|
|
77
78
|
""" Converts py types to sql types, registers custom types, resolves type names, updates connection params """
|
|
78
79
|
|
|
79
80
|
resolved : list[str] = []
|
sqlengine/exceptions.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
class SqlEngineError(Exception):
|
|
3
|
+
""" Errors linked to sqlengine module """
|
|
4
|
+
pass
|
|
5
|
+
|
|
6
|
+
class TransactionError(SqlEngineError):
|
|
7
|
+
""" Errors within transactions """
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
class OutsideTransactionError(TransactionError):
|
|
11
|
+
""" Errors of prohibited outside tranasctions operations """
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
class NestedTransactionError(TransactionError):
|
|
15
|
+
""" Errors of nested transaction operations """
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
class TableDeclarationError(SqlEngineError):
|
|
19
|
+
""" Errors of table declaration """
|
|
20
|
+
pass
|
sqlengine/schema.py
CHANGED
|
@@ -3,7 +3,7 @@ import sqlite3
|
|
|
3
3
|
from typing import overload
|
|
4
4
|
|
|
5
5
|
from .sqltable import SqlTableMixin
|
|
6
|
-
from .
|
|
6
|
+
from ._internal.types import Schema
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def get_database_tablenames(database : str, cursor : sqlite3.Cursor | None = None) -> list[str]:
|
|
@@ -62,15 +62,9 @@ def get_database_schemas(database : str) -> list[Schema]:
|
|
|
62
62
|
cursor = conn.cursor()
|
|
63
63
|
names = get_database_tablenames(database, cursor)
|
|
64
64
|
|
|
65
|
-
schemas
|
|
65
|
+
schemas = (get_table_schema(database, table_name, cursor) for table_name in names)
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
schema = get_table_schema(database, tablename, cursor)
|
|
69
|
-
|
|
70
|
-
if schema:
|
|
71
|
-
schemas.append(schema)
|
|
72
|
-
|
|
73
|
-
return schemas
|
|
67
|
+
return [sh for sh in schemas if sh]
|
|
74
68
|
|
|
75
69
|
|
|
76
70
|
def table_from_schema(database : str, schema : Schema, **kwargs) -> SqlTableMixin:
|