saabeilin
saabeilin

Reputation: 620

django: commit and raise inside transaction.atomic()

We are trying to migrate from commit_manually to atomic so we can upgrade Django to at least 1.8 in a legacy project. In most of cases we need to do something like that:

with transaction.atomic():
    obj = Entity.objects.select_for_update().get(pk=pk)
    try:
        obj.do_something()
        obj.set_some_status()
        obj.save()
    except SomeException:
        obj.set_failed_flag()
        obj.save()
        raise

becuase the callee needs this exception information to continue with the certain flow. But in this case the transaction/savepoint will be rolled back and that's not what we want since we want obj.set_failed_flag() to be committed. Also it seems logical to set it inside the same atomic block since we already have a locked row for this object.

Any ideas/patterns? Thanks in advance!

P.S. It was so easy with old manual transaction management!

P.P.S. We use exceptions also for "early-exit" and moving to some flags etc. would bring a log of mess and I personally would love to avoid it.

Upvotes: 4

Views: 5927

Answers (2)

BitParser
BitParser

Reputation: 3968

Besides the answer already posted, in case of having nested methods where you start the atomic block somewhere higher in the chain, it helps to use the following:

transaction.on_commit(lambda: method_that_raises_exception())

This way the exception is raised after the transaction has been committed.

Upvotes: 3

Kevin Christopher Henry
Kevin Christopher Henry

Reputation: 48982

Assuming that SomeException isn't a database exception, you can just save it and raise it outside the atomic block:

with transaction.atomic():
    obj = Entity.objects.select_for_update().get(pk=pk)

    try:
        obj.do_something()
        obj.set_some_status()
    except SomeException as e:
        obj.set_failed_flag()
        exception = e
    else:
        exception = None

    obj.save()

if exception:
    raise exception

If you find this too verbose and need to do it frequently, you might be able to write a context manager that acts as a proxy for transaction.atomic() but doesn't trigger a rollback in certain cases.

Finally, note that Django still has manual transaction management.

Upvotes: 5

Related Questions