11from typing import Any
22
3- from sqlalchemy import MetaData
3+ from sqlalchemy import MetaData , event
44from sqlalchemy .ext .asyncio import AsyncAttrs , async_sessionmaker , create_async_engine
55from sqlalchemy .orm import DeclarativeBase , MappedAsDataclass
66
@@ -33,7 +33,7 @@ def build_engine_options(settings: DatabaseSettings) -> dict[str, Any]:
3333def database_pool_summary (settings : DatabaseSettings , process_count : int = 1 ) -> str :
3434 """Describe the effective connection budget without exposing the database URL."""
3535 if settings .is_sqlite :
36- return "Database pool: SQLite driver-managed connections (QueuePool settings do not apply)."
36+ return "Database pool: SQLite driver-managed connections in WAL mode (QueuePool settings do not apply)."
3737
3838 per_process = settings .connection_ceiling ()
3939 service_ceiling = settings .connection_ceiling (process_count )
@@ -47,6 +47,18 @@ def database_pool_summary(settings: DatabaseSettings, process_count: int = 1) ->
4747
4848engine = create_async_engine (database_settings .url , ** build_engine_options (database_settings ))
4949
50+ if database_settings .is_sqlite :
51+ # WAL mode lets readers proceed concurrently with the single writer, preventing
52+ # "database is locked" errors under async workloads. busy_timeout makes SQLite
53+ # retry for up to 5 s on a locked write instead of failing immediately.
54+ @event .listens_for (engine .sync_engine , "connect" )
55+ def _set_sqlite_pragmas (dbapi_conn , _connection_record ):
56+ cursor = dbapi_conn .cursor ()
57+ cursor .execute ("PRAGMA journal_mode=WAL" )
58+ cursor .execute ("PRAGMA busy_timeout=5000" )
59+ cursor .close ()
60+
61+
5062SessionLocal = async_sessionmaker (autocommit = False , autoflush = False , expire_on_commit = False , bind = engine )
5163
5264naming_convention = {
0 commit comments