mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Database(memory_name=) for shared in-memory databases, closes #1151
This commit is contained in:
parent
6119bd7973
commit
5e9895c67f
3 changed files with 86 additions and 5 deletions
|
|
@ -24,11 +24,18 @@ connections = threading.local()
|
||||||
|
|
||||||
|
|
||||||
class Database:
|
class Database:
|
||||||
def __init__(self, ds, path=None, is_mutable=False, is_memory=False):
|
def __init__(
|
||||||
|
self, ds, path=None, is_mutable=False, is_memory=False, memory_name=None
|
||||||
|
):
|
||||||
self.ds = ds
|
self.ds = ds
|
||||||
self.path = path
|
self.path = path
|
||||||
self.is_mutable = is_mutable
|
self.is_mutable = is_mutable
|
||||||
self.is_memory = is_memory
|
self.is_memory = is_memory
|
||||||
|
self.memory_name = memory_name
|
||||||
|
if memory_name is not None:
|
||||||
|
self.path = memory_name
|
||||||
|
self.is_memory = True
|
||||||
|
self.is_mutable = True
|
||||||
self.hash = None
|
self.hash = None
|
||||||
self.cached_size = None
|
self.cached_size = None
|
||||||
self.cached_table_counts = None
|
self.cached_table_counts = None
|
||||||
|
|
@ -46,6 +53,16 @@ class Database:
|
||||||
}
|
}
|
||||||
|
|
||||||
def connect(self, write=False):
|
def connect(self, write=False):
|
||||||
|
if self.memory_name:
|
||||||
|
uri = "file:{}?mode=memory&cache=shared".format(self.memory_name)
|
||||||
|
conn = sqlite3.connect(
|
||||||
|
uri,
|
||||||
|
uri=True,
|
||||||
|
check_same_thread=False,
|
||||||
|
)
|
||||||
|
if not write:
|
||||||
|
conn.execute("PRAGMA query_only=1")
|
||||||
|
return conn
|
||||||
if self.is_memory:
|
if self.is_memory:
|
||||||
return sqlite3.connect(":memory:")
|
return sqlite3.connect(":memory:")
|
||||||
# mode=ro or immutable=1?
|
# mode=ro or immutable=1?
|
||||||
|
|
@ -215,7 +232,10 @@ class Database:
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
if self.is_memory:
|
if self.is_memory:
|
||||||
return ":memory:"
|
if self.memory_name:
|
||||||
|
return ":memory:{}".format(self.memory_name)
|
||||||
|
else:
|
||||||
|
return ":memory:"
|
||||||
else:
|
else:
|
||||||
return Path(self.path).stem
|
return Path(self.path).stem
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -270,11 +270,16 @@ The ``db`` parameter should be an instance of the ``datasette.database.Database`
|
||||||
|
|
||||||
This will add a mutable database from the provided file path.
|
This will add a mutable database from the provided file path.
|
||||||
|
|
||||||
The ``Database()`` constructor takes four arguments: the first is the ``datasette`` instance you are attaching to, the second is a ``path=``, then ``is_mutable`` and ``is_memory`` are both optional arguments.
|
To create a shared in-memory database named ``statistics``, use the following:
|
||||||
|
|
||||||
Use ``is_mutable`` if it is possible that updates will be made to that database - otherwise Datasette will open it in immutable mode and any changes could cause undesired behavior.
|
.. code-block:: python
|
||||||
|
|
||||||
Use ``is_memory`` if the connection is to an in-memory SQLite database.
|
from datasette.database import Database
|
||||||
|
|
||||||
|
datasette.add_database("statistics", Database(
|
||||||
|
datasette,
|
||||||
|
memory_name="statistics"
|
||||||
|
))
|
||||||
|
|
||||||
.. _datasette_remove_database:
|
.. _datasette_remove_database:
|
||||||
|
|
||||||
|
|
@ -480,6 +485,32 @@ Database class
|
||||||
|
|
||||||
Instances of the ``Database`` class can be used to execute queries against attached SQLite databases, and to run introspection against their schemas.
|
Instances of the ``Database`` class can be used to execute queries against attached SQLite databases, and to run introspection against their schemas.
|
||||||
|
|
||||||
|
.. _database_constructor:
|
||||||
|
|
||||||
|
Database(ds, path=None, is_mutable=False, is_memory=False, memory_name=None)
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
The ``Database()`` constructor can be used by plugins, in conjunction with :ref:`datasette_add_database`, to create and register new databases.
|
||||||
|
|
||||||
|
The arguments are as follows:
|
||||||
|
|
||||||
|
``ds`` - :ref:`internals_datasette` (required)
|
||||||
|
The Datasette instance you are attaching this database to.
|
||||||
|
|
||||||
|
``path`` - string
|
||||||
|
Path to a SQLite database file on disk.
|
||||||
|
|
||||||
|
``is_mutable`` - boolean
|
||||||
|
Set this to ``True`` if it is possible that updates will be made to that database - otherwise Datasette will open it in immutable mode and any changes could cause undesired behavior.
|
||||||
|
|
||||||
|
``is_memory`` - boolean
|
||||||
|
Use this to create non-shared memory connections.
|
||||||
|
|
||||||
|
``memory_name`` - string or ``None``
|
||||||
|
Use this to create a named in-memory database. Unlike regular memory databases these can be accessed by multiple threads and will persist an changes made to them for the lifetime of the Datasette server process.
|
||||||
|
|
||||||
|
The first argument is the ``datasette`` instance you are attaching to, the second is a ``path=``, then ``is_mutable`` and ``is_memory`` are both optional arguments.
|
||||||
|
|
||||||
.. _database_execute:
|
.. _database_execute:
|
||||||
|
|
||||||
await db.execute(sql, ...)
|
await db.execute(sql, ...)
|
||||||
|
|
|
||||||
|
|
@ -464,3 +464,33 @@ def test_mtime_ns_is_none_for_memory(app_client):
|
||||||
def test_is_mutable(app_client):
|
def test_is_mutable(app_client):
|
||||||
assert Database(app_client.ds, is_memory=True, is_mutable=True).is_mutable is True
|
assert Database(app_client.ds, is_memory=True, is_mutable=True).is_mutable is True
|
||||||
assert Database(app_client.ds, is_memory=True, is_mutable=False).is_mutable is False
|
assert Database(app_client.ds, is_memory=True, is_mutable=False).is_mutable is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_database_memory_name(app_client):
|
||||||
|
ds = app_client.ds
|
||||||
|
foo1 = Database(ds, memory_name="foo")
|
||||||
|
foo2 = Database(ds, memory_name="foo")
|
||||||
|
bar1 = Database(ds, memory_name="bar")
|
||||||
|
bar2 = Database(ds, memory_name="bar")
|
||||||
|
for db in (foo1, foo2, bar1, bar2):
|
||||||
|
table_names = await db.table_names()
|
||||||
|
assert table_names == []
|
||||||
|
# Now create a table in foo
|
||||||
|
await foo1.execute_write("create table foo (t text)", block=True)
|
||||||
|
assert await foo1.table_names() == ["foo"]
|
||||||
|
assert await foo2.table_names() == ["foo"]
|
||||||
|
assert await bar1.table_names() == []
|
||||||
|
assert await bar2.table_names() == []
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_in_memory_databases_forbid_writes(app_client):
|
||||||
|
ds = app_client.ds
|
||||||
|
db = Database(ds, memory_name="test")
|
||||||
|
with pytest.raises(sqlite3.OperationalError):
|
||||||
|
await db.execute("create table foo (t text)")
|
||||||
|
assert await db.table_names() == []
|
||||||
|
# Using db.execute_write() should work:
|
||||||
|
await db.execute_write("create table foo (t text)", block=True)
|
||||||
|
assert await db.table_names() == ["foo"]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue