--- /dev/null
+.. change::
+ :tags: usecase, orm
+ :tickets: 11297
+
+ Improved the error message raised when a :class:`.Session` is used
+ inside a context manager after the transaction has been rolled back due
+ to an exception. The ``InvalidRequestError`` now includes the original
+ exception that triggered the rollback, making it clearer why the
+ transaction is no longer active. Pull request courtesy Ilan Keshet.
__slots__ = ("_outer_trans_ctx", "_trans_subject", "__weakref__")
_trans_subject: Optional[_TConsSubject]
+ _rollback_exception: Optional[BaseException] = None
def _transaction_is_active(self) -> bool:
raise NotImplementedError()
@classmethod
def _trans_ctx_check(cls, subject: _TConsSubject) -> None:
trans_context = subject._trans_context_manager
- if trans_context:
- if not trans_context._transaction_is_active():
- raise exc.InvalidRequestError(
- "Can't operate on closed transaction inside context "
- "manager. Please complete the context manager "
- "before emitting further commands."
+ if trans_context and not trans_context._transaction_is_active():
+ rollback_exc = trans_context._rollback_exception
+ raise exc.InvalidRequestError(
+ "Can't operate on closed transaction inside context "
+ "manager. "
+ + (
+ "The transaction was rolled back due to an "
+ f"exception: {rollback_exc}. "
+ if rollback_exc is not None
+ else ""
)
+ + "Please complete the context manager before "
+ "emitting further commands."
+ )
def __enter__(self) -> Self:
subject = self._get_subject()
import contextlib
import random
+import re
from typing import Optional
from typing import TYPE_CHECKING
from sqlalchemy.testing import assert_warnings
from sqlalchemy.testing import engines
from sqlalchemy.testing import eq_
+from sqlalchemy.testing import expect_raises
from sqlalchemy.testing import expect_raises_message
from sqlalchemy.testing import expect_warnings
from sqlalchemy.testing import fixtures
elif check_operation == "delete":
session.delete(u1)
+ def test_rollback_exception_in_ctxmanager_error_message(self):
+ """test that when a flush-level exception causes the transaction to
+ be rolled back inside a context manager, subsequent session use raises
+ an error that includes the original exception. #11297"""
+ users, User = self.tables.users, self.classes.User
+
+ self.mapper_registry.map_imperatively(User, users)
+
+ sess = fixture_session()
+
+ with expect_raises(sa_exc.DBAPIError):
+ with sess.begin():
+ sess.add(User()) # name can't be null
+ try:
+ sess.flush()
+ except sa_exc.DBAPIError as flush_err:
+ with expect_raises_message(
+ sa_exc.InvalidRequestError,
+ "Can't operate on closed transaction inside context "
+ "manager. The transaction was rolled back due to "
+ f"an exception: {re.escape(str(flush_err))}. "
+ "Please complete the context manager before "
+ "emitting further commands.",
+ ):
+ sess.connection()
+ raise
+
class TransactionFlagsTest(fixtures.TestBase):
def test_in_transaction(self):