From 8fab62dc640d36ab4202bdd97dd5ca70568271c0 Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Tue, 12 Mar 2024 17:50:06 -0700 Subject: [PATCH] Ensure per-request database sessions are closed In the db_session_dependency FastAPI dependency, do database session cleanup in a finally block. This will hopefully ensure that the sessions are cleaned up even on uncaught exceptions, which in turn fixes warnings in test cases in other packages. --- changelog.d/20240312_174906_rra_DM_43288.md | 3 +++ src/safir/dependencies/db_session.py | 15 ++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 changelog.d/20240312_174906_rra_DM_43288.md diff --git a/changelog.d/20240312_174906_rra_DM_43288.md b/changelog.d/20240312_174906_rra_DM_43288.md new file mode 100644 index 00000000..a67bcdd8 --- /dev/null +++ b/changelog.d/20240312_174906_rra_DM_43288.md @@ -0,0 +1,3 @@ +### Bug fixes + +- Ensure that per-request database sessions provided by `db_session_dependency` are cleaned up even if the request aborts with an uncaught exception. diff --git a/src/safir/dependencies/db_session.py b/src/safir/dependencies/db_session.py index f9ab8be2..58cf0540 100644 --- a/src/safir/dependencies/db_session.py +++ b/src/safir/dependencies/db_session.py @@ -50,13 +50,14 @@ async def __call__(self) -> AsyncIterator[async_scoped_session]: """ if not self._session: raise RuntimeError("db_session_dependency not initialized") - yield self._session - - # Following the recommendations in the SQLAlchemy documentation, each - # session is scoped to a single web request. However, this all uses - # the same async_scoped_session object, so should share an underlying - # engine and connection pool. - await self._session.remove() + try: + yield self._session + finally: + # Following the recommendations in the SQLAlchemy documentation, + # each session is scoped to a single web request. However, this + # all uses the same async_scoped_session object, so should share + # an underlying engine and connection pool. + await self._session.remove() async def aclose(self) -> None: """Shut down the database engine."""