pyturso 0.1.5rc5__cp310-cp310-macosx_11_0_arm64.whl → 0.4.0rc9__cp310-cp310-macosx_11_0_arm64.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.
Potentially problematic release.
This version of pyturso might be problematic. Click here for more details.
- pyturso-0.4.0rc9.dist-info/METADATA +209 -0
- pyturso-0.4.0rc9.dist-info/RECORD +14 -0
- {pyturso-0.1.5rc5.dist-info → pyturso-0.4.0rc9.dist-info}/WHEEL +1 -1
- turso/__init__.py +19 -7
- turso/_turso.cpython-310-darwin.so +0 -0
- turso/aio/__init__.py +7 -0
- turso/aio/sync/__init__.py +15 -0
- turso/lib.py +926 -0
- turso/lib_aio.py +351 -0
- turso/lib_sync.py +468 -0
- turso/lib_sync_aio.py +93 -0
- turso/sync/__init__.py +15 -0
- turso/worker.py +43 -0
- pyturso-0.1.5rc5.dist-info/METADATA +0 -33
- pyturso-0.1.5rc5.dist-info/RECORD +0 -6
turso/lib_aio.py
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from queue import SimpleQueue
|
|
5
|
+
from typing import Any, Callable, Iterable, Mapping, Optional, Sequence
|
|
6
|
+
|
|
7
|
+
from .lib import (
|
|
8
|
+
Connection as BlockingConnection,
|
|
9
|
+
)
|
|
10
|
+
from .lib import (
|
|
11
|
+
Cursor as BlockingCursor,
|
|
12
|
+
)
|
|
13
|
+
from .lib import (
|
|
14
|
+
ProgrammingError,
|
|
15
|
+
)
|
|
16
|
+
from .lib import (
|
|
17
|
+
connect as blocking_connect,
|
|
18
|
+
)
|
|
19
|
+
from .worker import STOP_RUNNING_SENTINEL, Worker
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Connection goes FIRST
|
|
23
|
+
class Connection:
|
|
24
|
+
def __init__(self, connector: Callable[[], BlockingConnection]) -> None:
|
|
25
|
+
# Event loop and per-connection worker thread state
|
|
26
|
+
self._loop = asyncio.get_event_loop()
|
|
27
|
+
self._queue: SimpleQueue[tuple[asyncio.Future, Callable[[], Any]]] = SimpleQueue()
|
|
28
|
+
self._worker = Worker(self._queue, self._loop)
|
|
29
|
+
self._connector = connector
|
|
30
|
+
|
|
31
|
+
# Underlying blocking connection created in worker thread
|
|
32
|
+
self._conn: Optional[BlockingConnection] = None
|
|
33
|
+
self._closed: bool = False
|
|
34
|
+
|
|
35
|
+
# Schedule connection creation as the very first job in the worker
|
|
36
|
+
self._open_future: asyncio.Future[Connection] = self._loop.create_future()
|
|
37
|
+
|
|
38
|
+
def _open() -> Connection:
|
|
39
|
+
# Create the blocking connection inside the worker thread once
|
|
40
|
+
if self._conn is None:
|
|
41
|
+
self._conn = self._connector()
|
|
42
|
+
return self
|
|
43
|
+
|
|
44
|
+
self._queue.put_nowait((self._open_future, _open))
|
|
45
|
+
self._worker.start()
|
|
46
|
+
|
|
47
|
+
# Cached properties mirrored to the underlying connection.
|
|
48
|
+
# Setters will enqueue mutation jobs; we keep local cache for getters.
|
|
49
|
+
self._isolation_level_cache: Optional[str] = None
|
|
50
|
+
self._row_factory_cache: Any = None
|
|
51
|
+
self._text_factory_cache: Any = None
|
|
52
|
+
self._autocommit_cache: object | bool | None = None
|
|
53
|
+
|
|
54
|
+
async def close(self) -> None:
|
|
55
|
+
if self._closed:
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
# Ensure underlying Connection.close() is called in worker thread
|
|
59
|
+
def _do_close() -> None:
|
|
60
|
+
if self._conn is not None:
|
|
61
|
+
self._conn.close()
|
|
62
|
+
|
|
63
|
+
await self._run(lambda: (_do_close(), None)[1]) # schedule and await completion of close
|
|
64
|
+
|
|
65
|
+
# Request worker stop; we must not block the event loop while waiting.
|
|
66
|
+
# Note: STOP_RUNNING_SENTINEL item will terminate the worker loop.
|
|
67
|
+
stop_future = self._loop.create_future()
|
|
68
|
+
self._queue.put_nowait((stop_future, lambda: STOP_RUNNING_SENTINEL))
|
|
69
|
+
|
|
70
|
+
# Wait for the worker thread to terminate without blocking the loop
|
|
71
|
+
await self._loop.run_in_executor(None, self._worker.join)
|
|
72
|
+
self._closed = True
|
|
73
|
+
|
|
74
|
+
def __await__(self):
|
|
75
|
+
async def _await_open() -> "Connection":
|
|
76
|
+
await self._open_future
|
|
77
|
+
return self
|
|
78
|
+
|
|
79
|
+
return _await_open().__await__()
|
|
80
|
+
|
|
81
|
+
async def __aenter__(self) -> "Connection":
|
|
82
|
+
await self
|
|
83
|
+
return self
|
|
84
|
+
|
|
85
|
+
async def __aexit__(self, exc_type, exc, tb) -> None:
|
|
86
|
+
# Just close the connection - do not add any extra logic
|
|
87
|
+
await self.close()
|
|
88
|
+
|
|
89
|
+
# Internal helper: schedule a callable to run in the worker thread and await its result.
|
|
90
|
+
async def _run(self, func: Callable[[], Any]) -> Any:
|
|
91
|
+
if self._closed:
|
|
92
|
+
raise ProgrammingError("Cannot operate on a closed connection")
|
|
93
|
+
fut = self._loop.create_future()
|
|
94
|
+
self._queue.put_nowait((fut, func))
|
|
95
|
+
return await fut
|
|
96
|
+
|
|
97
|
+
# Internal helper: enqueue a callable but do not await completion (used for property setters).
|
|
98
|
+
def _run_nowait(self, func: Callable[[], Any]) -> None:
|
|
99
|
+
if self._closed:
|
|
100
|
+
raise ProgrammingError("Cannot operate on a closed connection")
|
|
101
|
+
fut = self._loop.create_future()
|
|
102
|
+
self._queue.put_nowait((fut, func))
|
|
103
|
+
|
|
104
|
+
# Cursor factory returning async Cursor wrapper
|
|
105
|
+
def cursor(self, factory: Optional[Callable[[BlockingConnection], BlockingCursor]] = None) -> "Cursor":
|
|
106
|
+
# Creation of the underlying blocking cursor is enqueued to preserve thread affinity.
|
|
107
|
+
return Cursor(self, factory=factory)
|
|
108
|
+
|
|
109
|
+
# Helpers similar to aiosqlite
|
|
110
|
+
async def execute(self, sql: str, parameters: Sequence[Any] | Mapping[str, Any] = ()) -> "Cursor":
|
|
111
|
+
cur = self.cursor()
|
|
112
|
+
await cur.execute(sql, parameters)
|
|
113
|
+
return cur
|
|
114
|
+
|
|
115
|
+
async def executemany(self, sql: str, parameters: Iterable[Sequence[Any] | Mapping[str, Any]]) -> "Cursor":
|
|
116
|
+
cur = self.cursor()
|
|
117
|
+
await cur.executemany(sql, parameters)
|
|
118
|
+
return cur
|
|
119
|
+
|
|
120
|
+
async def executescript(self, sql_script: str) -> "Cursor":
|
|
121
|
+
cur = self.cursor()
|
|
122
|
+
await cur.executescript(sql_script)
|
|
123
|
+
return cur
|
|
124
|
+
|
|
125
|
+
async def commit(self) -> None:
|
|
126
|
+
await self._run(lambda: self._conn.commit()) # type: ignore[union-attr]
|
|
127
|
+
|
|
128
|
+
async def rollback(self) -> None:
|
|
129
|
+
await self._run(lambda: self._conn.rollback()) # type: ignore[union-attr]
|
|
130
|
+
|
|
131
|
+
# Read/write properties mirrored to the underlying blocking connection.
|
|
132
|
+
# DB-API hints:
|
|
133
|
+
# - isolation_level controls implicit transactions (BEGIN DEFERRED/IMMEDIATE/EXCLUSIVE or None for autocommit).
|
|
134
|
+
@property
|
|
135
|
+
def isolation_level(self) -> Optional[str]:
|
|
136
|
+
return self._isolation_level_cache
|
|
137
|
+
|
|
138
|
+
@isolation_level.setter
|
|
139
|
+
def isolation_level(self, value: Optional[str]) -> None:
|
|
140
|
+
self._isolation_level_cache = value
|
|
141
|
+
|
|
142
|
+
def _set() -> None:
|
|
143
|
+
# Will be applied in the worker thread before subsequent operations
|
|
144
|
+
self._conn.isolation_level = value # type: ignore[union-attr]
|
|
145
|
+
|
|
146
|
+
self._run_nowait(_set)
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def row_factory(self) -> Any:
|
|
150
|
+
return self._row_factory_cache
|
|
151
|
+
|
|
152
|
+
@row_factory.setter
|
|
153
|
+
def row_factory(self, value: Any) -> None:
|
|
154
|
+
self._row_factory_cache = value
|
|
155
|
+
|
|
156
|
+
def _set() -> None:
|
|
157
|
+
self._conn.row_factory = value # type: ignore[union-attr]
|
|
158
|
+
|
|
159
|
+
self._run_nowait(_set)
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def text_factory(self) -> Any:
|
|
163
|
+
return self._text_factory_cache
|
|
164
|
+
|
|
165
|
+
@text_factory.setter
|
|
166
|
+
def text_factory(self, value: Any) -> None:
|
|
167
|
+
self._text_factory_cache = value
|
|
168
|
+
|
|
169
|
+
def _set() -> None:
|
|
170
|
+
self._conn.text_factory = value # type: ignore[union-attr]
|
|
171
|
+
|
|
172
|
+
self._run_nowait(_set)
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def autocommit(self) -> object | bool | None:
|
|
176
|
+
return self._autocommit_cache
|
|
177
|
+
|
|
178
|
+
@autocommit.setter
|
|
179
|
+
def autocommit(self, value: object | bool) -> None:
|
|
180
|
+
self._autocommit_cache = value
|
|
181
|
+
|
|
182
|
+
def _set() -> None:
|
|
183
|
+
self._conn.autocommit = value # type: ignore[union-attr]
|
|
184
|
+
|
|
185
|
+
self._run_nowait(_set)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# Cursor goes SECOND
|
|
189
|
+
class Cursor:
|
|
190
|
+
def __init__(
|
|
191
|
+
self,
|
|
192
|
+
connection: Connection,
|
|
193
|
+
factory: Optional[Callable[[BlockingConnection], BlockingCursor]] = None
|
|
194
|
+
):
|
|
195
|
+
self._connection: Connection = connection
|
|
196
|
+
self._loop = asyncio.get_event_loop()
|
|
197
|
+
|
|
198
|
+
# Underlying blocking cursor and its creation job
|
|
199
|
+
self._cursor_created: bool = False
|
|
200
|
+
self._bcursor: Optional[BlockingCursor] = None
|
|
201
|
+
|
|
202
|
+
# Cursor attributes (DB-API)
|
|
203
|
+
self.arraysize: int = 1
|
|
204
|
+
self._description: Optional[tuple[tuple[str, None, None, None, None, None, None], ...]] = None
|
|
205
|
+
self._lastrowid: Optional[int] = None
|
|
206
|
+
self._rowcount: int = -1
|
|
207
|
+
self._closed: bool = False
|
|
208
|
+
|
|
209
|
+
# Enqueue creation of the underlying blocking cursor in the worker thread
|
|
210
|
+
def _create() -> None:
|
|
211
|
+
if self._cursor_created:
|
|
212
|
+
return
|
|
213
|
+
if factory is None:
|
|
214
|
+
self._bcursor = self._connection._conn.cursor() # type: ignore[union-attr]
|
|
215
|
+
else:
|
|
216
|
+
# Use provided factory to create BlockingCursor from BlockingConnection
|
|
217
|
+
self._bcursor = factory(self._connection._conn) # type: ignore[union-attr]
|
|
218
|
+
self._cursor_created = True
|
|
219
|
+
# Apply initial arraysize if any
|
|
220
|
+
self._bcursor.arraysize = self.arraysize # type: ignore[union-attr]
|
|
221
|
+
|
|
222
|
+
self._connection._run_nowait(_create)
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def connection(self) -> Connection:
|
|
226
|
+
return self._connection
|
|
227
|
+
|
|
228
|
+
async def close(self) -> None:
|
|
229
|
+
if self._closed:
|
|
230
|
+
return
|
|
231
|
+
|
|
232
|
+
def _close() -> None:
|
|
233
|
+
if self._bcursor is not None:
|
|
234
|
+
self._bcursor.close()
|
|
235
|
+
|
|
236
|
+
await self._connection._run(_close)
|
|
237
|
+
self._closed = True
|
|
238
|
+
|
|
239
|
+
# Internal helpers for updating cached metadata after execute-like calls
|
|
240
|
+
def _update_meta_cache(self, description, lastrowid, rowcount) -> None:
|
|
241
|
+
self._description = description
|
|
242
|
+
self._lastrowid = lastrowid
|
|
243
|
+
self._rowcount = rowcount if rowcount is not None else -1
|
|
244
|
+
|
|
245
|
+
async def execute(self, sql: str, parameters: Sequence[Any] | Mapping[str, Any] = ()) -> "Cursor":
|
|
246
|
+
self._ensure_open()
|
|
247
|
+
|
|
248
|
+
def _exec() -> tuple[Any, Any, Any]:
|
|
249
|
+
# Perform the execute and collect metadata
|
|
250
|
+
cur = self._bcursor # type: ignore[assignment]
|
|
251
|
+
cur.execute(sql, parameters)
|
|
252
|
+
return (cur.description, cur.lastrowid, cur.rowcount)
|
|
253
|
+
|
|
254
|
+
description, lastrowid, rowcount = await self._connection._run(_exec)
|
|
255
|
+
self._update_meta_cache(description, lastrowid, rowcount)
|
|
256
|
+
return self
|
|
257
|
+
|
|
258
|
+
async def executemany(self, sql: str, parameters: Iterable[Sequence[Any] | Mapping[str, Any]]) -> "Cursor":
|
|
259
|
+
self._ensure_open()
|
|
260
|
+
|
|
261
|
+
def _execm() -> tuple[Any, Any, Any]:
|
|
262
|
+
cur = self._bcursor # type: ignore[assignment]
|
|
263
|
+
cur.executemany(sql, parameters)
|
|
264
|
+
return (cur.description, cur.lastrowid, cur.rowcount)
|
|
265
|
+
|
|
266
|
+
description, lastrowid, rowcount = await self._connection._run(_execm)
|
|
267
|
+
self._update_meta_cache(description, lastrowid, rowcount)
|
|
268
|
+
return self
|
|
269
|
+
|
|
270
|
+
async def executescript(self, sql_script: str) -> "Cursor":
|
|
271
|
+
self._ensure_open()
|
|
272
|
+
|
|
273
|
+
def _execs() -> tuple[Any, Any, Any]:
|
|
274
|
+
cur = self._bcursor # type: ignore[assignment]
|
|
275
|
+
cur.executescript(sql_script)
|
|
276
|
+
return (cur.description, cur.lastrowid, cur.rowcount)
|
|
277
|
+
|
|
278
|
+
description, lastrowid, rowcount = await self._connection._run(_execs)
|
|
279
|
+
self._update_meta_cache(description, lastrowid, rowcount)
|
|
280
|
+
return self
|
|
281
|
+
|
|
282
|
+
async def fetchone(self) -> Any:
|
|
283
|
+
self._ensure_open()
|
|
284
|
+
|
|
285
|
+
def _one() -> Any:
|
|
286
|
+
return self._bcursor.fetchone() # type: ignore[union-attr]
|
|
287
|
+
|
|
288
|
+
return await self._connection._run(_one)
|
|
289
|
+
|
|
290
|
+
async def fetchmany(self, size: Optional[int] = None) -> list[Any]:
|
|
291
|
+
self._ensure_open()
|
|
292
|
+
|
|
293
|
+
def _many() -> list[Any]:
|
|
294
|
+
n = self.arraysize if size is None else size
|
|
295
|
+
return list(self._bcursor.fetchmany(n)) # type: ignore[union-attr]
|
|
296
|
+
|
|
297
|
+
return await self._connection._run(_many)
|
|
298
|
+
|
|
299
|
+
async def fetchall(self) -> list[Any]:
|
|
300
|
+
self._ensure_open()
|
|
301
|
+
|
|
302
|
+
def _all() -> list[Any]:
|
|
303
|
+
return list(self._bcursor.fetchall()) # type: ignore[union-attr]
|
|
304
|
+
|
|
305
|
+
return await self._connection._run(_all)
|
|
306
|
+
|
|
307
|
+
def _ensure_open(self) -> None:
|
|
308
|
+
if self._closed:
|
|
309
|
+
raise ProgrammingError("Cannot operate on a closed cursor")
|
|
310
|
+
|
|
311
|
+
# Properties reflecting DB-API attributes of the last executed statement
|
|
312
|
+
@property
|
|
313
|
+
def description(self) -> tuple[tuple[str, None, None, None, None, None, None], ...] | None:
|
|
314
|
+
return self._description
|
|
315
|
+
|
|
316
|
+
@property
|
|
317
|
+
def lastrowid(self) -> int | None:
|
|
318
|
+
return self._lastrowid
|
|
319
|
+
|
|
320
|
+
@property
|
|
321
|
+
def rowcount(self) -> int:
|
|
322
|
+
return self._rowcount
|
|
323
|
+
|
|
324
|
+
# Make cursor usable as async context manager, similar to aiosqlite
|
|
325
|
+
async def __aenter__(self) -> "Cursor":
|
|
326
|
+
return self
|
|
327
|
+
|
|
328
|
+
async def __aexit__(self, exc_type, exc, tb) -> None:
|
|
329
|
+
await self.close()
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
# connect is not async because it returns awaitable Connection
|
|
333
|
+
# same signature as in the lib.py
|
|
334
|
+
def connect(
|
|
335
|
+
database: str,
|
|
336
|
+
*,
|
|
337
|
+
experimental_features: Optional[str] = None,
|
|
338
|
+
isolation_level: Optional[str] = "DEFERRED",
|
|
339
|
+
extra_io: Optional[Callable[[], None]] = None,
|
|
340
|
+
) -> Connection:
|
|
341
|
+
# Create a connector that opens a blocking Connection using the existing driver.
|
|
342
|
+
def _connector() -> BlockingConnection:
|
|
343
|
+
conn = blocking_connect(
|
|
344
|
+
database,
|
|
345
|
+
experimental_features=experimental_features,
|
|
346
|
+
isolation_level=isolation_level,
|
|
347
|
+
extra_io=extra_io,
|
|
348
|
+
)
|
|
349
|
+
return conn
|
|
350
|
+
|
|
351
|
+
return Connection(_connector)
|