Skip to content

Commit 5e5b003

Browse files
authored
[BUG] Fix SQLite temp DB file on Windows (#691)
Summary: - Make the test `engine` fixture create the sqlite file with `NamedTemporaryFile(delete=False, suffix=".db")`, close the handle before creating the SQLAlchemy engine, and explicitly dispose & unlink the file in teardown. - Add a regression test that ensures the temp `.db` exists while the fixture is active and is removed after teardown. Files changed: - src/tests/testutils/default_sqlalchemy.py - src/tests/testutils/test_default_sqlalchemy.py Reason: On Windows, `NamedTemporaryFile()` deletes the file on close by default, causing SQLAlchemy to fail to open the DB path. This change ensures the DB file remains accessible for the engine and is cleaned up after tests. Tests: - Added `test_engine_fixture_uses_non_deleting_temp_file_and_cleans_up` to validate behavior. Fixes: #687
1 parent 57a85c1 commit 5e5b003

File tree

2 files changed

+42
-3
lines changed

2 files changed

+42
-3
lines changed

src/tests/testutils/default_sqlalchemy.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os
12
import sqlite3
23
import tempfile
34
from typing import Iterator, Any
@@ -106,8 +107,11 @@ def engine() -> Iterator[Engine]:
106107
"""
107108
Create a SqlAlchemy engine for tests, backed by a temporary sqlite file.
108109
"""
109-
temporary_file = tempfile.NamedTemporaryFile()
110-
engine = create_engine(f"sqlite:///{temporary_file.name}?check_same_thread=False")
110+
temporary_file = tempfile.NamedTemporaryFile(delete=False, suffix=".db")
111+
temp_path = temporary_file.name
112+
temporary_file.close()
113+
114+
engine = create_engine(f"sqlite:///{temp_path}?check_same_thread=False")
111115
AIoDConcept.metadata.create_all(engine)
112116
with Session(engine) as session:
113117
for trigger in create_delete_triggers(AIoDConcept):
@@ -116,9 +120,12 @@ def engine() -> Iterator[Engine]:
116120
session.execute(trigger)
117121
EngineSingleton().patch(engine)
118122

119-
# Yielding is essential, the temporary file will be closed after the engine is used
120123
yield engine
121124

125+
engine.dispose()
126+
if os.path.exists(temp_path):
127+
os.unlink(temp_path)
128+
122129

123130
@pytest.fixture
124131
def engine_test_resource_filled(engine: Engine) -> Iterator[str]:
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import os
2+
import tempfile
3+
4+
import pytest
5+
6+
from tests.testutils import default_sqlalchemy
7+
8+
9+
def test_engine_fixture_uses_non_deleting_temp_file_and_cleans_up(monkeypatch):
10+
captured: dict[str, object] = {}
11+
original_named_temporary_file = tempfile.NamedTemporaryFile
12+
13+
def tracked_named_temporary_file(*args, **kwargs):
14+
captured["kwargs"] = kwargs.copy()
15+
temporary_file = original_named_temporary_file(*args, **kwargs)
16+
captured["path"] = temporary_file.name
17+
return temporary_file
18+
19+
monkeypatch.setattr(default_sqlalchemy.tempfile, "NamedTemporaryFile", tracked_named_temporary_file)
20+
21+
engine_generator = default_sqlalchemy.engine.__wrapped__()
22+
_ = next(engine_generator)
23+
24+
temp_path = str(captured["path"])
25+
assert captured["kwargs"]["delete"] is False
26+
assert temp_path.endswith(".db")
27+
assert os.path.exists(temp_path)
28+
29+
with pytest.raises(StopIteration):
30+
next(engine_generator)
31+
32+
assert not os.path.exists(temp_path)

0 commit comments

Comments
 (0)