From 409 warnings down to 52 warnings.

By closing unclosed database connections.

Refs #2614
This commit is contained in:
Simon Willison 2025-12-12 22:38:04 -08:00
commit f02484c3de
14 changed files with 75 additions and 27 deletions

View file

@ -1819,6 +1819,7 @@ class Datasette:
break
except importlib.metadata.PackageNotFoundError:
pass
conn.close()
return info
def _plugins(self, request=None, all=False):

View file

@ -615,7 +615,9 @@ def serve(
for file in file_paths:
if not pathlib.Path(file).exists():
if create:
sqlite3.connect(file).execute("vacuum")
conn = sqlite3.connect(file)
conn.execute("vacuum")
conn.close()
else:
raise click.ClickException(
"Invalid value for '[FILES]...': Path '{}' does not exist.".format(

View file

@ -681,13 +681,18 @@ def detect_fts_sql(table):
def detect_json1(conn=None):
close_conn = False
if conn is None:
conn = sqlite3.connect(":memory:")
close_conn = True
try:
conn.execute("SELECT json('{}')")
return True
except Exception:
return False
finally:
if close_conn:
conn.close()
def table_columns(conn, table):

View file

@ -20,15 +20,16 @@ def sqlite_version():
def _sqlite_version():
return tuple(
map(
int,
sqlite3.connect(":memory:")
.execute("select sqlite_version()")
.fetchone()[0]
.split("."),
conn = sqlite3.connect(":memory:")
try:
return tuple(
map(
int,
conn.execute("select sqlite_version()").fetchone()[0].split("."),
)
)
)
finally:
conn.close()
def supports_table_xinfo():

View file

@ -100,9 +100,10 @@ async def ds_client():
def pytest_report_header(config):
return "SQLite: {}".format(
sqlite3.connect(":memory:").execute("select sqlite_version()").fetchone()[0]
)
conn = sqlite3.connect(":memory:")
version = conn.execute("select sqlite_version()").fetchone()[0]
conn.close()
return "SQLite: {}".format(version)
def pytest_configure(config):

View file

@ -187,6 +187,8 @@ def app_client():
def app_client_no_files():
ds = Datasette([])
yield TestClient(ds)
for db in ds.databases.values():
db.close()
@pytest.fixture(scope="session")
@ -822,6 +824,7 @@ def cli(db_filename, config, metadata, plugins_path, recreate, extra_db_filename
for sql, params in TABLE_PARAMETERIZED_SQL:
with conn:
conn.execute(sql, params)
conn.close()
print(f"Test tables written to {db_filename}")
if metadata:
with open(metadata, "w") as fp:
@ -850,6 +853,7 @@ def cli(db_filename, config, metadata, plugins_path, recreate, extra_db_filename
pathlib.Path(extra_db_filename).unlink()
conn = sqlite3.connect(extra_db_filename)
conn.executescript(EXTRA_DATABASE_SQL)
conn.close()
print(f"Test tables written to {extra_db_filename}")

View file

@ -17,6 +17,8 @@ def ds_write(tmp_path_factory):
db.execute(
"create table docs (id integer primary key, title text, score float, age integer)"
)
db1.close()
db2.close()
ds = Datasette([db_path], immutables=[db_path_immutable])
ds.root_enabled = True
yield ds

View file

@ -472,7 +472,9 @@ def test_serve_duplicate_database_names(tmpdir):
nested.mkdir()
db_2_path = str(tmpdir / "nested" / "db.db")
for path in (db_1_path, db_2_path):
sqlite3.connect(path).execute("vacuum")
conn = sqlite3.connect(path)
conn.execute("vacuum")
conn.close()
result = runner.invoke(cli, [db_1_path, db_2_path, "--get", "/-/databases.json"])
assert result.exit_code == 0, result.output
databases = json.loads(result.output)
@ -486,7 +488,9 @@ def test_weird_database_names(tmpdir, filename):
# https://github.com/simonw/datasette/issues/1181
runner = CliRunner()
db_path = str(tmpdir / filename)
sqlite3.connect(db_path).execute("vacuum")
conn = sqlite3.connect(db_path)
conn.execute("vacuum")
conn.close()
result1 = runner.invoke(cli, [db_path, "--get", "/"])
assert result1.exit_code == 0, result1.output
filename_no_stem = filename.rsplit(".", 1)[0]
@ -523,7 +527,9 @@ def test_duplicate_database_files_error(tmpdir):
"""Test that passing the same database file multiple times raises an error"""
runner = CliRunner()
db_path = str(tmpdir / "test.db")
sqlite3.connect(db_path).execute("vacuum")
conn = sqlite3.connect(db_path)
conn.execute("vacuum")
conn.close()
# Test with exact duplicate
result = runner.invoke(cli, ["serve", db_path, db_path, "--get", "/"])
@ -542,7 +548,9 @@ def test_duplicate_database_files_error(tmpdir):
config_dir = tmpdir / "config"
config_dir.mkdir()
config_db_path = str(config_dir / "data.db")
sqlite3.connect(config_db_path).execute("vacuum")
conn = sqlite3.connect(config_db_path)
conn.execute("vacuum")
conn.close()
result3 = runner.invoke(
cli, ["serve", config_db_path, str(config_dir), "--get", "/"]
@ -553,7 +561,9 @@ def test_duplicate_database_files_error(tmpdir):
# Test that mixing a file NOT in the directory with a directory works fine
other_db_path = str(tmpdir / "other.db")
sqlite3.connect(other_db_path).execute("vacuum")
conn = sqlite3.connect(other_db_path)
conn.execute("vacuum")
conn.close()
result4 = runner.invoke(
cli, ["serve", other_db_path, str(config_dir), "--get", "/-/databases.json"]

View file

@ -60,6 +60,7 @@ def config_dir(tmp_path_factory):
(1, 'San Francisco')
;
""")
db.close()
# Mark "immutable.db" as immutable
(config_dir / "inspect-data.json").write_text(
@ -95,6 +96,8 @@ def test_invalid_settings(config_dir):
def config_dir_client(config_dir):
ds = Datasette([], config_dir=config_dir)
yield _TestClient(ds)
for db in ds.databases.values():
db.close()
def test_settings(config_dir_client):

View file

@ -43,6 +43,7 @@ def test_crossdb_warning_if_too_many_databases(tmp_path_factory):
path = str(db_dir / "db_{}.db".format(i))
conn = sqlite3.connect(path)
conn.execute("vacuum")
conn.close()
dbs.append(path)
runner = CliRunner()
result = runner.invoke(

View file

@ -543,7 +543,9 @@ async def test_execute_write_fn_exception(db):
@pytest.mark.timeout(1)
async def test_execute_write_fn_connection_exception(tmpdir, app_client):
path = str(tmpdir / "immutable.db")
sqlite3.connect(path).execute("vacuum")
conn = sqlite3.connect(path)
conn.execute("vacuum")
conn.close()
db = Database(app_client.ds, path=path, is_mutable=False)
app_client.ds.add_database(db, name="immutable-db")
@ -747,15 +749,19 @@ async def test_replace_database(tmpdir):
path1 = str(tmpdir / "data1.db")
(tmpdir / "two").mkdir()
path2 = str(tmpdir / "two" / "data1.db")
sqlite3.connect(path1).executescript("""
conn1 = sqlite3.connect(path1)
conn1.executescript("""
create table t (id integer primary key);
insert into t (id) values (1);
insert into t (id) values (2);
""")
sqlite3.connect(path2).executescript("""
conn1.close()
conn2 = sqlite3.connect(path2)
conn2.executescript("""
create table t (id integer primary key);
insert into t (id) values (1);
""")
conn2.close()
datasette = Datasette([path1])
db = datasette.get_database("data1")
count = (await db.execute("select count(*) from t")).first()[0]

View file

@ -422,9 +422,9 @@ def test_plugins_async_template_function(restore_working_directory):
.select("pre.extra_from_awaitable_function")[0]
.text
)
expected = (
sqlite3.connect(":memory:").execute("select sqlite_version()").fetchone()[0]
)
conn = sqlite3.connect(":memory:")
expected = conn.execute("select sqlite_version()").fetchone()[0]
conn.close()
assert expected == extra_from_awaitable_function
@ -466,6 +466,7 @@ def view_names_client(tmp_path_factory):
db_path = str(tmpdir / "fixtures.db")
conn = sqlite3.connect(db_path)
conn.executescript(TABLES)
conn.close()
return _TestClient(
Datasette([db_path], template_dir=str(templates), plugins_dir=str(plugins))
)

View file

@ -208,6 +208,7 @@ def test_detect_fts(open_quote, close_quote):
assert None is utils.detect_fts(conn, "Test_View")
assert None is utils.detect_fts(conn, "r")
assert "Street_Tree_List_fts" == utils.detect_fts(conn, "Street_Tree_List")
conn.close()
@pytest.mark.parametrize("table", ("regular", "has'single quote"))
@ -222,6 +223,7 @@ def test_detect_fts_different_table_names(table):
conn = utils.sqlite3.connect(":memory:")
conn.executescript(sql)
assert "{table}_fts".format(table=table) == utils.detect_fts(conn, table)
conn.close()
@pytest.mark.parametrize(
@ -359,6 +361,7 @@ def test_table_columns():
create table places (id integer primary key, name text, bob integer)
""")
assert ["id", "name", "bob"] == utils.table_columns(conn, "places")
conn.close()
@pytest.mark.parametrize(
@ -433,11 +436,13 @@ def test_check_connection_spatialite_raises():
conn = sqlite3.connect(path)
with pytest.raises(utils.SpatialiteConnectionProblem):
utils.check_connection(conn)
conn.close()
def test_check_connection_passes():
conn = sqlite3.connect(":memory:")
utils.check_connection(conn)
conn.close()
def test_call_with_supported_arguments():
@ -564,10 +569,14 @@ def test_display_actor(actor, expected):
async def test_initial_path_for_datasette(tmp_path_factory, dbs, expected_path):
db_dir = tmp_path_factory.mktemp("dbs")
one_table = str(db_dir / "one.db")
sqlite3.connect(one_table).execute("create table one (id integer primary key)")
conn1 = sqlite3.connect(one_table)
conn1.execute("create table one (id integer primary key)")
conn1.close()
two_tables = str(db_dir / "two.db")
sqlite3.connect(two_tables).execute("create table two (id integer primary key)")
sqlite3.connect(two_tables).execute("create table three (id integer primary key)")
conn2 = sqlite3.connect(two_tables)
conn2.execute("create table two (id integer primary key)")
conn2.execute("create table three (id integer primary key)")
conn2.close()
datasette = Datasette(
[{"one_table": one_table, "two_tables": two_tables}[db] for db in dbs]
)

View file

@ -34,7 +34,9 @@ def inner_html(soup):
def has_load_extension():
conn = sqlite3.connect(":memory:")
return hasattr(conn, "enable_load_extension")
result = hasattr(conn, "enable_load_extension")
conn.close()
return result
def cookie_was_deleted(response, cookie):