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 CHANGED
@@ -1,7 +1,11 @@
1
- from .core import sqlgen, types
2
- from .core.types import Schema, Primary
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__ = ["types", "sqlgen", "Schema", "SqlTableMixin", "Primary"]
8
+ __all__ = [
9
+ "sqlgen", "Schema", "SqlTableMixin",
10
+ "Primary", "ConnectionManager", "register_type"
11
+ ]
@@ -0,0 +1,4 @@
1
+ from .statements import Select, Delete, Update
2
+ from .connection_manager import ConnectionManager
3
+
4
+ __all__ = ["Select", "Delete", "Update", "ConnectionManager"]
@@ -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 (str): Number of rows to return
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 RuntimeError("Can't re-open existing connection")
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 RuntimeError("Can't manually close managed transaction")
197
+ raise TransactionError("Can't manually close managed transaction")
188
198
 
189
- self._trans_cursor.close()
190
- self._trans.close()
191
- del(self._trans_cursor)
192
- del(self._trans)
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 RuntimeError("Can't commit outside transaction mode")
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 RuntimeError("Can't rollback outside transaction mode")
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 RuntimeError("`tx_conn` is not available outside the transaction mode")
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 RuntimeError("`tx_cursor` is not available outside the transaction mode")
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 .connection import ConnectionManager
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
- """ Joins previous expression via logical operator `lop` """
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 last where clause with NOT """
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: Where[Self] = Where(self)
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
- """ Column selector """
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 ValueError("Can't aggregate columns multiple times")
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, each batch in its own transaction.
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
- Examples:
294
+ Example:
277
295
 
278
- >>> with table.transaction():
279
- >>> for batch in table.select.where.gt("Age", 30).then.fetchmany_iterator(1000):
280
- >>> process_batch(batch)
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 RuntimeError("To use the `fetchall_iterator()` method you have \
284
- to keep open the transaction of the table with `transaction()` manager")
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
- """ Select statement rows iterator """
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 RuntimeError("To use the __iter__ method you have \
300
- to keep open the transaction of the table with `transaction()` manager")
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 ValueError("Delete statement must have a where clause")
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 = str | int | float | bytes | None | CustomType
16
- type SqlRow = tuple[SqlValue, ...]
17
- type SqlType = type[str | int | float | bytes | CustomType]
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[SqlType | str]
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[SqlType | str], **connection_params) -> tuple[list[str], dict[str, Any]]:
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] = []
@@ -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 .core.types import Schema
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 : list[Schema] = []
65
+ schemas = (get_table_schema(database, table_name, cursor) for table_name in names)
66
66
 
67
- for tablename in names:
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: