Skip to content
This repository was archived by the owner on Aug 19, 2025. It is now read-only.

Commit 9795187

Browse files
zaniebNathan Joshi
andauthored
Allow SQLite query parameters and support cached databases (#561)
* add support for sqlite connection string query parameters, cached memory databases * add additional comments #196 * tweaked comments #196 * Lint --------- Co-authored-by: Nathan Joshi <[email protected]>
1 parent 25fa295 commit 9795187

File tree

3 files changed

+79
-5
lines changed

3 files changed

+79
-5
lines changed

databases/backends/sqlite.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import logging
2+
import sqlite3
23
import typing
34
import uuid
5+
from urllib.parse import urlencode
46

57
import aiosqlite
68
from sqlalchemy.dialects.sqlite import pysqlite
@@ -45,7 +47,9 @@ async def connect(self) -> None:
4547
# )
4648

4749
async def disconnect(self) -> None:
48-
pass
50+
# if it extsis, remove reference to connection to cached in-memory database on disconnect
51+
if self._pool._memref:
52+
self._pool._memref = None
4953
# assert self._pool is not None, "DatabaseBackend is not running"
5054
# self._pool.close()
5155
# await self._pool.wait_closed()
@@ -57,12 +61,20 @@ def connection(self) -> "SQLiteConnection":
5761

5862
class SQLitePool:
5963
def __init__(self, url: DatabaseURL, **options: typing.Any) -> None:
60-
self._url = url
64+
self._database = url.database
65+
self._memref = None
66+
# add query params to database connection string
67+
if url.options:
68+
self._database += "?" + urlencode(url.options)
6169
self._options = options
6270

71+
if url.options and "cache" in url.options:
72+
# reference to a connection to the cached in-memory database must be held to keep it from being deleted
73+
self._memref = sqlite3.connect(self._database, **self._options)
74+
6375
async def acquire(self) -> aiosqlite.Connection:
6476
connection = aiosqlite.connect(
65-
database=self._url.database, isolation_level=None, **self._options
77+
database=self._database, isolation_level=None, **self._options
6678
)
6779
await connection.__aenter__()
6880
return connection

requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ aiosqlite==0.17.0
88
asyncpg==0.26.0
99

1010
# Sync database drivers for standard tooling around setup/teardown/migrations.
11-
psycopg2-binary==2.9.3
12-
pymysql==1.0.2
11+
# psycopg2-binary==2.9.3
12+
# pymysql==1.0.2
1313

1414
# Testing
1515
autoflake==1.4

tests/test_databases.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import itertools
77
import os
88
import re
9+
import sqlite3
910
from typing import MutableMapping
1011
from unittest.mock import MagicMock, patch
1112

@@ -1529,6 +1530,67 @@ async def test_result_named_access(database_url):
15291530
assert result.completed is True
15301531

15311532

1533+
@pytest.mark.parametrize("database_url", DATABASE_URLS)
1534+
@async_adapter
1535+
async def test_mapping_property_interface(database_url):
1536+
"""
1537+
Test that all connections implement interface with `_mapping` property
1538+
"""
1539+
async with Database(database_url) as database:
1540+
query = notes.select()
1541+
single_result = await database.fetch_one(query=query)
1542+
assert single_result._mapping["text"] == "example1"
1543+
assert single_result._mapping["completed"] is True
1544+
1545+
list_result = await database.fetch_all(query=query)
1546+
assert list_result[0]._mapping["text"] == "example1"
1547+
assert list_result[0]._mapping["completed"] is True
1548+
1549+
1550+
@async_adapter
1551+
async def test_should_not_maintain_ref_when_no_cache_param():
1552+
async with Database("sqlite:///file::memory:", uri=True) as database:
1553+
query = sqlalchemy.schema.CreateTable(notes)
1554+
await database.execute(query)
1555+
1556+
query = notes.insert()
1557+
values = {"text": "example1", "completed": True}
1558+
with pytest.raises(sqlite3.OperationalError):
1559+
await database.execute(query, values)
1560+
1561+
1562+
@async_adapter
1563+
async def test_should_maintain_ref_when_cache_param():
1564+
async with Database("sqlite:///file::memory:?cache=shared", uri=True) as database:
1565+
query = sqlalchemy.schema.CreateTable(notes)
1566+
await database.execute(query)
1567+
1568+
query = notes.insert()
1569+
values = {"text": "example1", "completed": True}
1570+
await database.execute(query, values)
1571+
1572+
query = notes.select().where(notes.c.text == "example1")
1573+
result = await database.fetch_one(query=query)
1574+
assert result.text == "example1"
1575+
assert result.completed is True
1576+
1577+
1578+
@async_adapter
1579+
async def test_should_remove_ref_on_disconnect():
1580+
async with Database("sqlite:///file::memory:?cache=shared", uri=True) as database:
1581+
query = sqlalchemy.schema.CreateTable(notes)
1582+
await database.execute(query)
1583+
1584+
query = notes.insert()
1585+
values = {"text": "example1", "completed": True}
1586+
await database.execute(query, values)
1587+
1588+
async with Database("sqlite:///file::memory:?cache=shared", uri=True) as database:
1589+
query = notes.select()
1590+
with pytest.raises(sqlite3.OperationalError):
1591+
await database.fetch_all(query=query)
1592+
1593+
15321594
@pytest.mark.parametrize("database_url", DATABASE_URLS)
15331595
@async_adapter
15341596
async def test_mapping_property_interface(database_url):

0 commit comments

Comments
 (0)