Django decorator @transaction.non_atomic_requests not working in a ViewSet method

Question:

I recently ran into the need to disable transaction requests in one of my views, in order to be able to call db.connection.close() and connect() during requests in an effort to improve performance.

I have a DRF ViewSet, and used the following very simple view to verify that the non_atomic_requests decoractor seems to have no effect. ATOMIC_REQUESTS=True is enabled in settings.py, and DEBUG=False.

from django.db import transaction    

@transaction.non_atomic_requests
def create(self, *args, **kwargs):
    m = MyModel(stuff="hello")
    m.save()
    raise Exception('exception! row should still be saved though')
    return Response()

After calling the view, I open Django shell, and verify that the amount of rows in the db has not grown, even though it should have. Also opening a debugger during the request to halt execution after the line m.save(), I can observe in Django shell that a new row is not visible yet.

If I set ATOMIC_REQUESTS=False in settings.py, the code works as expected, and the number of rows in the db is grown by one, even if an error is raised before returning from the view.

When ATOMIC_REQUESTS=False, using @transaction.atomic decorator does work as expected though. So as a workaround, I could use it to set every other view as atomic instead…

I am currently thinking this is a bug in the framework. Can anybody verify my findings, or point out if I am misunderstanding how this decorator is meant to function?

I am using Python 3.6, Django 2.0 and DRF 3.7.7.

Asked By: hannu40k

||

Answers:

As documented, non_atomic_requests only works if it’s applied to the view itself.

In your case, create is a viewset method, it is not the view itself. With a regular class based view in Django, you’d need to wrap the dispatch method using method_decorator.

@method_decorator(transaction.non_atomic_requests, name='dispatch') 
class MyViewSet(ViewSet):
    ...

    def create(self, *args, **kwargs):
        ...

I’m not familiar enough with the rest framework internals to say whether this will work or not. Note that it will disable atomic requests for all views handled by the viewset, not just the create method.

The non_atomic_requests method has this limitation because the Django request handler has to inspect the view before it runs so that it knows whether to run it in a transaction. The transaction.atomic decorator does not have the same requirement – Django can simply start the transaction as soon as it enters an atomic function or block.

Answered By: Alasdair

In case you are using db other than ‘default’:
you need to explicitly mention the ‘using’ property.
otherwise, it would default to ‘default’
transaction.non_atomic_requests(using=’db_name’)

in case of class based views-
either apply it on dispatch in views:
@method_decorator(transaction.non_atomic_requests(using=’db_name’), name=’dispatch’)
class MyViewSet(ViewSet):

or apply it on as_view method in urls
path(r’endpoint/’, transaction.non_atomic_requests(using=’db_name’)(MyViewSet.as_view()), name=’myview’)

Answered By: Aditya Bansal