mirror of
https://github.com/simonw/datasette.git
synced 2026-06-06 09:07:00 +02:00
Datasette.close() closes databases, shuts down executor, unlinks temp file
Datasette.close() iterates over every attached Database (including the internal database), calls Database.close() on each, then shuts down the ThreadPoolExecutor. Exceptions raised by one Database don't prevent the others from being closed; the first exception is re-raised afterwards. Idempotent. Refs #2692 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
dabf8e4199
commit
290f27158f
3 changed files with 100 additions and 0 deletions
|
|
@ -3,8 +3,10 @@ Tests for the datasette.app.Datasette class
|
|||
"""
|
||||
|
||||
import dataclasses
|
||||
import os
|
||||
from datasette import Context
|
||||
from datasette.app import Datasette, Database, ResourcesSQL
|
||||
from datasette.database import DatasetteClosedError
|
||||
from datasette.resources import DatabaseResource
|
||||
from itsdangerous import BadSignature
|
||||
import pytest
|
||||
|
|
@ -213,3 +215,60 @@ async def test_allowed_resources_sql(datasette):
|
|||
assert isinstance(result, ResourcesSQL)
|
||||
assert "all_rules AS" in result.sql
|
||||
assert result.params["action"] == "view-table"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_datasette_close_closes_all_databases_and_executor():
|
||||
ds = Datasette(memory=True)
|
||||
await ds.invoke_startup()
|
||||
# Confirm internal DB has write machinery running
|
||||
assert ds._internal_database._write_thread is not None
|
||||
assert ds._internal_database._write_thread.is_alive()
|
||||
temp_path = ds._internal_database.path
|
||||
assert os.path.exists(temp_path)
|
||||
executor = ds.executor
|
||||
ds.close()
|
||||
# Executor is shut down
|
||||
assert executor._shutdown
|
||||
# All attached Database instances are closed
|
||||
for db in ds.databases.values():
|
||||
assert db._closed
|
||||
assert ds._internal_database._closed
|
||||
# Temp internal DB file is unlinked
|
||||
assert not os.path.exists(temp_path)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_datasette_close_is_idempotent():
|
||||
ds = Datasette(memory=True)
|
||||
await ds.invoke_startup()
|
||||
ds.close()
|
||||
# Second call should be a no-op
|
||||
ds.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_datasette_close_raises_on_use():
|
||||
ds = Datasette(memory=True)
|
||||
await ds.invoke_startup()
|
||||
ds.close()
|
||||
with pytest.raises(DatasetteClosedError):
|
||||
await ds.get_internal_database().execute("select 1")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_datasette_close_continues_past_db_error():
|
||||
# If one Database raises during close(), the others still get closed.
|
||||
ds = Datasette(memory=True)
|
||||
await ds.invoke_startup()
|
||||
|
||||
class Boom(Database):
|
||||
def close(self):
|
||||
raise RuntimeError("boom")
|
||||
|
||||
bad = ds.add_database(Boom(ds, is_memory=True), name="bad")
|
||||
good = ds.add_database(Database(ds, is_memory=True), name="good")
|
||||
with pytest.raises(RuntimeError, match="boom"):
|
||||
ds.close()
|
||||
assert good._closed
|
||||
assert ds._internal_database._closed
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue