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.
@@ -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