sqlite7 1.0.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.
- sqlite7/__init__.py +87 -0
- sqlite7/_native.py +446 -0
- sqlite7/async_database.py +312 -0
- sqlite7/database.py +579 -0
- sqlite7/dialect.py +38 -0
- sqlite7/exc.py +101 -0
- sqlite7/helpers.py +77 -0
- sqlite7/result.py +22 -0
- sqlite7/transaction.py +23 -0
- sqlite7-1.0.0.dist-info/METADATA +72 -0
- sqlite7-1.0.0.dist-info/RECORD +14 -0
- sqlite7-1.0.0.dist-info/WHEEL +5 -0
- sqlite7-1.0.0.dist-info/licenses/LICENSE +21 -0
- sqlite7-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"""Asynchronous SQLite client for sqlite7 built on top of the native sync backend."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from collections.abc import Iterable, Sequence
|
|
7
|
+
from contextlib import AbstractAsyncContextManager
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from .database import Database, Table
|
|
11
|
+
from .result import RowDict, StatementResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _AsyncConnectionGate:
|
|
15
|
+
"""Task-aware reentrant gate that serializes access to a single connection."""
|
|
16
|
+
|
|
17
|
+
__slots__ = ("_lock", "_owner", "_depth")
|
|
18
|
+
|
|
19
|
+
def __init__(self) -> None:
|
|
20
|
+
self._lock = asyncio.Lock()
|
|
21
|
+
self._owner: asyncio.Task[Any] | None = None
|
|
22
|
+
self._depth = 0
|
|
23
|
+
|
|
24
|
+
def owned_by_current_task(self) -> bool:
|
|
25
|
+
task = asyncio.current_task()
|
|
26
|
+
return task is not None and task is self._owner
|
|
27
|
+
|
|
28
|
+
async def acquire(self) -> None:
|
|
29
|
+
task = asyncio.current_task()
|
|
30
|
+
if task is not None and task is self._owner:
|
|
31
|
+
self._depth += 1
|
|
32
|
+
return
|
|
33
|
+
await self._lock.acquire()
|
|
34
|
+
self._owner = task
|
|
35
|
+
self._depth = 1
|
|
36
|
+
|
|
37
|
+
def release(self) -> None:
|
|
38
|
+
task = asyncio.current_task()
|
|
39
|
+
if task is not None and task is not self._owner:
|
|
40
|
+
raise RuntimeError("sqlite7 async connection gate released by non-owner task")
|
|
41
|
+
if self._depth <= 0:
|
|
42
|
+
raise RuntimeError("sqlite7 async connection gate release without acquire")
|
|
43
|
+
self._depth -= 1
|
|
44
|
+
if self._depth == 0:
|
|
45
|
+
self._owner = None
|
|
46
|
+
self._lock.release()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class AsyncTransaction(AbstractAsyncContextManager["AsyncDatabase"]):
|
|
50
|
+
__slots__ = ("database",)
|
|
51
|
+
|
|
52
|
+
def __init__(self, database: "AsyncDatabase") -> None:
|
|
53
|
+
self.database = database
|
|
54
|
+
|
|
55
|
+
async def __aenter__(self) -> "AsyncDatabase":
|
|
56
|
+
await self.database._gate.acquire()
|
|
57
|
+
try:
|
|
58
|
+
await asyncio.to_thread(self.database._db._enter_transaction)
|
|
59
|
+
except Exception:
|
|
60
|
+
self.database._gate.release()
|
|
61
|
+
raise
|
|
62
|
+
return self.database
|
|
63
|
+
|
|
64
|
+
async def __aexit__(self, exc_type, exc, tb) -> None:
|
|
65
|
+
try:
|
|
66
|
+
await asyncio.to_thread(self.database._db._exit_transaction, exc is None)
|
|
67
|
+
finally:
|
|
68
|
+
self.database._gate.release()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class AsyncDatabase:
|
|
72
|
+
"""Async facade with method parity for the synchronous Database API."""
|
|
73
|
+
|
|
74
|
+
__slots__ = ("_db", "_gate")
|
|
75
|
+
|
|
76
|
+
def __init__(self, path: str, **kwargs: Any) -> None:
|
|
77
|
+
self._db = Database(path, **kwargs)
|
|
78
|
+
self._gate = _AsyncConnectionGate()
|
|
79
|
+
|
|
80
|
+
async def _call(self, func, /, *args, **kwargs):
|
|
81
|
+
if self._gate.owned_by_current_task():
|
|
82
|
+
return await asyncio.to_thread(func, *args, **kwargs)
|
|
83
|
+
await self._gate.acquire()
|
|
84
|
+
try:
|
|
85
|
+
return await asyncio.to_thread(func, *args, **kwargs)
|
|
86
|
+
finally:
|
|
87
|
+
self._gate.release()
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def path(self) -> str:
|
|
91
|
+
return self._db.path
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def timeout(self) -> float:
|
|
95
|
+
return self._db.timeout
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def detect_types(self) -> int:
|
|
99
|
+
return self._db.detect_types
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def isolation_level(self) -> str | None:
|
|
103
|
+
return self._db.isolation_level
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def pragmas(self) -> dict[str, Any]:
|
|
107
|
+
return self._db.pragmas
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def uri(self) -> bool:
|
|
111
|
+
return self._db.uri
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def check_same_thread(self) -> bool:
|
|
115
|
+
return self._db.check_same_thread
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def statement_cache_size(self) -> int:
|
|
119
|
+
return self._db.statement_cache_size
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def total_changes(self) -> int:
|
|
123
|
+
return self._db.total_changes
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def in_transaction(self) -> bool:
|
|
127
|
+
return self._db.in_transaction
|
|
128
|
+
|
|
129
|
+
async def close(self) -> None:
|
|
130
|
+
await self._call(self._db.close)
|
|
131
|
+
|
|
132
|
+
async def __aenter__(self) -> "AsyncDatabase":
|
|
133
|
+
return self
|
|
134
|
+
|
|
135
|
+
async def __aexit__(self, exc_type, exc, tb) -> None:
|
|
136
|
+
await self.close()
|
|
137
|
+
|
|
138
|
+
async def execute(self, sql: str, params: Sequence[Any] | None = None) -> StatementResult:
|
|
139
|
+
return await self._call(self._db.execute, sql, params)
|
|
140
|
+
|
|
141
|
+
async def executemany(self, sql: str, seq_of_params: Iterable[Sequence[Any]]) -> StatementResult:
|
|
142
|
+
materialized = tuple(tuple(params) for params in seq_of_params)
|
|
143
|
+
return await self._call(self._db.executemany, sql, materialized)
|
|
144
|
+
|
|
145
|
+
async def script(self, sql_script: str) -> None:
|
|
146
|
+
await self._call(self._db.script, sql_script)
|
|
147
|
+
|
|
148
|
+
async def fetch_all(self, sql: str, params: Sequence[Any] | None = None) -> list[RowDict]:
|
|
149
|
+
return await self._call(self._db.fetch_all, sql, params)
|
|
150
|
+
|
|
151
|
+
async def fetch_one(self, sql: str, params: Sequence[Any] | None = None) -> RowDict | None:
|
|
152
|
+
return await self._call(self._db.fetch_one, sql, params)
|
|
153
|
+
|
|
154
|
+
async def fetch_value(self, sql: str, params: Sequence[Any] | None = None, *, default: Any = None) -> Any:
|
|
155
|
+
return await self._call(self._db.fetch_value, sql, params, default=default)
|
|
156
|
+
|
|
157
|
+
async def select(self, table: str, **kwargs: Any) -> list[RowDict]:
|
|
158
|
+
return await self._call(self._db.select, table, **kwargs)
|
|
159
|
+
|
|
160
|
+
async def insert(self, table: str, values: dict[str, Any], *, on_conflict: str = "abort") -> StatementResult:
|
|
161
|
+
return await self._call(self._db.insert, table, values, on_conflict=on_conflict)
|
|
162
|
+
|
|
163
|
+
async def insert_many(
|
|
164
|
+
self,
|
|
165
|
+
table: str,
|
|
166
|
+
rows: Iterable[dict[str, Any]],
|
|
167
|
+
*,
|
|
168
|
+
on_conflict: str = "abort",
|
|
169
|
+
chunk_size: int = 500,
|
|
170
|
+
) -> StatementResult:
|
|
171
|
+
materialized = tuple(dict(row) for row in rows)
|
|
172
|
+
return await self._call(self._db.insert_many, table, materialized, on_conflict=on_conflict, chunk_size=chunk_size)
|
|
173
|
+
|
|
174
|
+
async def update(self, table: str, values: dict[str, Any], **kwargs: Any) -> StatementResult:
|
|
175
|
+
return await self._call(self._db.update, table, values, **kwargs)
|
|
176
|
+
|
|
177
|
+
async def delete(self, table: str, **kwargs: Any) -> StatementResult:
|
|
178
|
+
return await self._call(self._db.delete, table, **kwargs)
|
|
179
|
+
|
|
180
|
+
async def upsert(self, table: str, values: dict[str, Any], **kwargs: Any) -> StatementResult:
|
|
181
|
+
return await self._call(self._db.upsert, table, values, **kwargs)
|
|
182
|
+
|
|
183
|
+
async def count(self, table: str, **kwargs: Any) -> int:
|
|
184
|
+
return await self._call(self._db.count, table, **kwargs)
|
|
185
|
+
|
|
186
|
+
async def exists(self, table: str, **kwargs: Any) -> bool:
|
|
187
|
+
return await self._call(self._db.exists, table, **kwargs)
|
|
188
|
+
|
|
189
|
+
def table(self, name: str) -> "AsyncTable":
|
|
190
|
+
return AsyncTable(self, name)
|
|
191
|
+
|
|
192
|
+
def transaction(self) -> AsyncTransaction:
|
|
193
|
+
return AsyncTransaction(self)
|
|
194
|
+
|
|
195
|
+
async def commit(self) -> None:
|
|
196
|
+
await self._call(self._db.commit)
|
|
197
|
+
|
|
198
|
+
async def rollback(self) -> None:
|
|
199
|
+
await self._call(self._db.rollback)
|
|
200
|
+
|
|
201
|
+
async def backup(self, target: Any, *, pages: int = -1) -> None:
|
|
202
|
+
await self._call(self._db.backup, target, pages=pages)
|
|
203
|
+
|
|
204
|
+
async def iterdump(self) -> list[str]:
|
|
205
|
+
return await self._call(lambda: list(self._db.iterdump()))
|
|
206
|
+
|
|
207
|
+
async def create_function(self, name: str, narg: int, func, *, deterministic: bool = False) -> None:
|
|
208
|
+
await self._call(self._db.create_function, name, narg, func, deterministic=deterministic)
|
|
209
|
+
|
|
210
|
+
async def create_aggregate(self, name: str, n_arg: int, aggregate_class: type) -> None:
|
|
211
|
+
await self._call(self._db.create_aggregate, name, n_arg, aggregate_class)
|
|
212
|
+
|
|
213
|
+
async def create_collation(self, name: str, callable_) -> None:
|
|
214
|
+
await self._call(self._db.create_collation, name, callable_)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class AsyncTable:
|
|
218
|
+
"""Async table-bound facade with parity for the synchronous Table API."""
|
|
219
|
+
|
|
220
|
+
__slots__ = ("database", "name")
|
|
221
|
+
|
|
222
|
+
def __init__(self, database: AsyncDatabase, name: str) -> None:
|
|
223
|
+
self.database = database
|
|
224
|
+
self.name = name
|
|
225
|
+
|
|
226
|
+
async def close(self) -> None:
|
|
227
|
+
await self.database.close()
|
|
228
|
+
|
|
229
|
+
async def script(self, sql_script: str) -> None:
|
|
230
|
+
await self.database.script(sql_script)
|
|
231
|
+
|
|
232
|
+
async def commit(self) -> None:
|
|
233
|
+
await self.database.commit()
|
|
234
|
+
|
|
235
|
+
async def rollback(self) -> None:
|
|
236
|
+
await self.database.rollback()
|
|
237
|
+
|
|
238
|
+
async def backup(self, target: Any, *, pages: int = -1) -> None:
|
|
239
|
+
await self.database.backup(target, pages=pages)
|
|
240
|
+
|
|
241
|
+
async def iterdump(self) -> list[str]:
|
|
242
|
+
return await self.database.iterdump()
|
|
243
|
+
|
|
244
|
+
async def create_function(self, name: str, narg: int, func, *, deterministic: bool = False) -> None:
|
|
245
|
+
await self.database.create_function(name, narg, func, deterministic=deterministic)
|
|
246
|
+
|
|
247
|
+
async def create_aggregate(self, name: str, n_arg: int, aggregate_class: type) -> None:
|
|
248
|
+
await self.database.create_aggregate(name, n_arg, aggregate_class)
|
|
249
|
+
|
|
250
|
+
async def create_collation(self, name: str, callable_) -> None:
|
|
251
|
+
await self.database.create_collation(name, callable_)
|
|
252
|
+
|
|
253
|
+
async def execute(self, sql: str, params: Sequence[Any] | None = None) -> StatementResult:
|
|
254
|
+
return await self.database.execute(sql, params)
|
|
255
|
+
|
|
256
|
+
async def executemany(self, sql: str, seq_of_params: Iterable[Sequence[Any]]) -> StatementResult:
|
|
257
|
+
return await self.database.executemany(sql, seq_of_params)
|
|
258
|
+
|
|
259
|
+
async def fetch_all(self, sql: str, params: Sequence[Any] | None = None) -> list[RowDict]:
|
|
260
|
+
return await self.database.fetch_all(sql, params)
|
|
261
|
+
|
|
262
|
+
async def fetch_one(self, sql: str, params: Sequence[Any] | None = None) -> RowDict | None:
|
|
263
|
+
return await self.database.fetch_one(sql, params)
|
|
264
|
+
|
|
265
|
+
async def fetch_value(self, sql: str, params: Sequence[Any] | None = None, *, default: Any = None) -> Any:
|
|
266
|
+
return await self.database.fetch_value(sql, params, default=default)
|
|
267
|
+
|
|
268
|
+
async def all(self, **kwargs: Any) -> list[RowDict]:
|
|
269
|
+
return await self.select(**kwargs)
|
|
270
|
+
|
|
271
|
+
async def select(self, **kwargs: Any) -> list[RowDict]:
|
|
272
|
+
return await self.database.select(self.name, **kwargs)
|
|
273
|
+
|
|
274
|
+
async def get(self, where: str, params: Sequence[Any] | None = None, **kwargs: Any) -> RowDict | None:
|
|
275
|
+
options = dict(kwargs)
|
|
276
|
+
options["where"] = where
|
|
277
|
+
options["params"] = params
|
|
278
|
+
options.setdefault("limit", 1)
|
|
279
|
+
rows = await self.select(**options)
|
|
280
|
+
return rows[0] if rows else None
|
|
281
|
+
|
|
282
|
+
async def insert(self, values: dict[str, Any], *, on_conflict: str = "abort") -> StatementResult:
|
|
283
|
+
return await self.database.insert(self.name, values, on_conflict=on_conflict)
|
|
284
|
+
|
|
285
|
+
async def insert_many(self, rows: Iterable[dict[str, Any]], *, on_conflict: str = "abort", chunk_size: int = 500) -> StatementResult:
|
|
286
|
+
return await self.database.insert_many(self.name, rows, on_conflict=on_conflict, chunk_size=chunk_size)
|
|
287
|
+
|
|
288
|
+
async def update(self, values: dict[str, Any], **kwargs: Any) -> StatementResult:
|
|
289
|
+
return await self.database.update(self.name, values, **kwargs)
|
|
290
|
+
|
|
291
|
+
async def delete(self, **kwargs: Any) -> StatementResult:
|
|
292
|
+
return await self.database.delete(self.name, **kwargs)
|
|
293
|
+
|
|
294
|
+
async def upsert(self, values: dict[str, Any], **kwargs: Any) -> StatementResult:
|
|
295
|
+
return await self.database.upsert(self.name, values, **kwargs)
|
|
296
|
+
|
|
297
|
+
async def count(self, **kwargs: Any) -> int:
|
|
298
|
+
return await self.database.count(self.name, **kwargs)
|
|
299
|
+
|
|
300
|
+
async def exists(self, **kwargs: Any) -> bool:
|
|
301
|
+
return await self.database.exists(self.name, **kwargs)
|
|
302
|
+
|
|
303
|
+
def transaction(self) -> AsyncTransaction:
|
|
304
|
+
return self.database.transaction()
|
|
305
|
+
|
|
306
|
+
@property
|
|
307
|
+
def total_changes(self) -> int:
|
|
308
|
+
return self.database.total_changes
|
|
309
|
+
|
|
310
|
+
@property
|
|
311
|
+
def in_transaction(self) -> bool:
|
|
312
|
+
return self.database.in_transaction
|