0

I'm using sqlalchemy to create a transaction. I'm using with to take advantage of the auto commit and rollback:

with session.begin():
    do_stuff()

If an exception is raised, is there anyway to propagate that exception after the auto rollback?

4
  • 6
    Why do you think it wouldn't be propagated automatically? Commented Jun 8, 2020 at 19:36
  • @Barmar presumably session has an except clause to determine whether it should commit or rollback. You're depending on it to re-raise the exception. Or maybe not, now that I think about it a bit more I'm not sure how the mechanics of that would work. Commented Jun 8, 2020 at 19:42
  • I'm presuming that it uses finally: for the cleanup actions, not except: Commented Jun 8, 2020 at 19:43
  • @Barmar It was a long day...I was changing how I started transactions and my unit test was failing because the validations were not being raised....because it was mocked. The __exit__ function never raises in the Mock. Commented Jun 8, 2020 at 23:50

2 Answers 2

1

If any exception is raised during the body of a with statement, the __exit__ method is immediately called with that exception as an argument. It is up to __exit__ to then decide whether the exception has been properly handled (by returning a truthy value) or whether it should be re-raised after __exit__ completes (by returning a non-truthy value).

SessionTransaction.__exit__ only returns None, which indicates that any exception that may have been raised in the body of the with statement will be raised again.


Note that since the "default" return value of any function is None, the default behavior of __exit__ is to propagate any exceptions. No user-level code is expected to call __exit__ explicitly or look at its return value, so you really have to do out of your way to return a truthy value and suppress an expression.


Also note the description of the with statement's semantics:

The following code:

with EXPRESSION as TARGET:
    SUITE

is semantically equivalent to:

manager = (EXPRESSION)
enter = type(manager).__enter__
exit = type(manager).__exit__ 
value = enter(manager)
hit_except = False

try:
    TARGET = value
    SUITE 
except:
    hit_except = True
    if not exit(manager, *sys.exc_info()):
        raise 
finally:
    if not hit_except:
        exit(manager, None, None, None)

The __exit__ method is called under one of two mutually exclusive conditions:

  1. An exception was raised, in which case it was called in the except clause. If it returns false, the caught exception is re-raised.
  2. An exception was not raised, in which case it is called in the finally block. hit_except is ensures that exit is not called twice if exit itself raises an exception in the except block.
Sign up to request clarification or add additional context in comments.

1 Comment

This was helpful, it reminded me that this was a unit test and not actually sqlalchemy, and I had mocked __exit__ to do nothing.
0

You shouldn't need to do anything special.

Context managers created using with are roughly equivalent to:

try:
    context initialization
    do_stuff()
finally:
    contact cleanup

Unlike an except: clause, finally: doesn't block propagation of the exception. It executes the statements and then continues propagation.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.