Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 436
saabeilin Avatar asked Feb 06 '23 15:02

saabeilin


2 Answers

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.

like image 109
Kevin Christopher Henry Avatar answered Feb 08 '23 15:02

Kevin Christopher Henry


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.

like image 44
TGO Avatar answered Feb 08 '23 15:02

TGO