File size: 1,983 Bytes
57384dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Shared SQLite connection helper.

Centralizes what psycopg used to provide automatically: dict-shaped rows,
`with`-block commit/rollback/close semantics (including on `conn.cursor()`,
which stock sqlite3.Cursor does not support as a context manager), and
date/datetime round-tripping.
"""

from __future__ import annotations

import sqlite3
from datetime import date, datetime, timezone
from pathlib import Path

sqlite3.register_adapter(date, lambda d: d.isoformat())
sqlite3.register_adapter(
    datetime,
    lambda dt: (dt if dt.tzinfo else dt.replace(tzinfo=timezone.utc)).astimezone(timezone.utc).isoformat(),
)
sqlite3.register_converter("DATE", lambda v: date.fromisoformat(v.decode()))
sqlite3.register_converter("TIMESTAMP", lambda v: datetime.fromisoformat(v.decode().replace("Z", "+00:00")))


def _dict_row(cursor: sqlite3.Cursor, row: tuple) -> dict:
    return dict(zip((column[0] for column in cursor.description), row))


class Cursor(sqlite3.Cursor):
    def __enter__(self) -> "Cursor":
        return self

    def __exit__(self, *exc) -> None:
        self.close()


class Connection(sqlite3.Connection):
    def cursor(self, factory=None) -> Cursor:
        return super().cursor(factory or Cursor)

    def __exit__(self, *exc) -> None:
        super().__exit__(*exc)
        self.close()


def _resolve_path(database_url: str) -> str:
    if "sqlite:///" in database_url:
        return database_url.split("sqlite:///", 1)[-1]
    return database_url


def connect(database_url: str) -> Connection:
    path = _resolve_path(database_url)
    if path != ":memory:":
        Path(path).parent.mkdir(parents=True, exist_ok=True)
    conn = sqlite3.connect(
        path,
        factory=Connection,
        detect_types=sqlite3.PARSE_DECLTYPES,
        timeout=30,
    )
    conn.row_factory = _dict_row
    conn.execute("PRAGMA foreign_keys = ON")
    conn.execute("PRAGMA journal_mode = WAL")
    conn.execute("PRAGMA busy_timeout = 5000")
    return conn