-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathartifact_db.py
More file actions
99 lines (78 loc) · 3.05 KB
/
Copy pathartifact_db.py
File metadata and controls
99 lines (78 loc) · 3.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import asyncio
import sqlite3
from pathlib import Path
import aiosqlite
import config
def _resolve(task_uuid: str, filename: str) -> Path:
return (Path(config.ARTIFACTS_PATH) / task_uuid / filename).absolute()
class SyncConnect:
"""Sync read context manager for artifact SQLite databases.
Keeps one persistent connection to avoid journal conflicts with the
background writer (repeated connect/close cycles corrupt DELETE-mode journals).
"""
def __init__(self, task_uuid: str, filename: str, timeout: float = 30) -> None:
self.path = _resolve(task_uuid, filename)
self._timeout = timeout
self._conn: sqlite3.Connection | None = None
def __enter__(self) -> sqlite3.Connection:
self._conn = sqlite3.connect(
str(self.path), isolation_level=None, timeout=self._timeout
)
return self._conn
def __exit__(self, *_) -> None:
if self._conn is not None:
self._conn.close()
self._conn = None
class AsyncConnect:
"""Async read context manager for artifact SQLite databases.
Keeps one persistent connection to avoid journal conflicts with the
background writer (repeated connect/close cycles corrupt DELETE-mode journals).
"""
def __init__(self, task_uuid: str, filename: str, timeout: float = 30) -> None:
self.path = _resolve(task_uuid, filename)
self._timeout = timeout
self._conn: aiosqlite.Connection | None = None
async def __aenter__(self) -> "AsyncConnect":
self._conn = await aiosqlite.connect(
self.path, isolation_level=None, timeout=self._timeout
)
return self
async def __aexit__(self, *_) -> None:
if self._conn is not None:
await self._conn.close()
self._conn = None
def execute(
self, sql: str, parameters: tuple = (), retries: int = 10
) -> "_ExecuteContext":
return _ExecuteContext(self._conn, sql, parameters, retries)
class _ExecuteContext:
def __init__(
self,
conn: aiosqlite.Connection,
sql: str,
parameters: tuple,
retries: int,
) -> None:
self._conn = conn
self._sql = sql
self._parameters = parameters
self._retries = retries
self._cursor: aiosqlite.Cursor | None = None
async def __aenter__(self) -> aiosqlite.Cursor:
is_select = self._sql.lstrip().upper().startswith("SELECT")
last_exc: Exception | None = None
for _ in range(self._retries):
try:
self._cursor = await self._conn.execute(self._sql, self._parameters)
return self._cursor
except sqlite3.DatabaseError as exc:
if "malformed" in str(exc).lower():
last_exc = exc
await asyncio.sleep(0.3)
continue
raise
raise last_exc # type: ignore[misc]
async def __aexit__(self, *_) -> None:
if self._cursor is not None:
await self._cursor.close()
self._cursor = None