#37382 new Bug

@non_atomic_requests no longer works on async views

Reported by: Jacob Walls Owned by:
Component: Database layer (models, ORM) Version: dev
Severity: Release blocker Keywords:
Cc: Sina Chaichi Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

After bdbda29c3e126754c3ae04ceb5c5d35d49aae01c (#29303), @non_atomic_requests wraps all views in a sync callable regardless if the view was a coroutine. This test passes on 6.1:

  • tests/handlers/tests.py

    diff --git a/tests/handlers/tests.py b/tests/handlers/tests.py
    index 240c398988..c641aeb82a 100644
    a b class TransactionsPerRequestTests(TransactionTestCase):  
    155155        self.assertEqual(wrapped_once._non_atomic_requests, {"default"})
    156156        self.assertEqual(wrapped_twice._non_atomic_requests, {"default", "other"})
    157157
     158    async def test_no_auto_transaction_async(self):
     159        old_atomic_requests = connection.settings_dict["ATOMIC_REQUESTS"]
     160        try:
     161            connection.settings_dict["ATOMIC_REQUESTS"] = True
     162            response = await self.async_client.get("/not_in_transaction_async/")
     163        finally:
     164            connection.settings_dict["ATOMIC_REQUESTS"] = old_atomic_requests
     165        self.assertContains(response, "False")
     166
    158167
    159168@override_settings(ROOT_URLCONF="handlers.urls")
    160169class SignalsTests(SimpleTestCase):
  • tests/handlers/urls.py

    diff --git a/tests/handlers/urls.py b/tests/handlers/urls.py
    index a0efece602..8c7b65e018 100644
    a b urlpatterns = [  
    1111    path("async_streaming/", views.async_streaming),
    1212    path("in_transaction/", views.in_transaction),
    1313    path("not_in_transaction/", views.not_in_transaction),
     14    path("not_in_transaction_async/", views.not_in_transaction_async),
    1415    path("not_in_transaction_using_none/", views.not_in_transaction_using_none),
    1516    path("not_in_transaction_using_text/", views.not_in_transaction_using_text),
    1617    path("bad_request/", views.bad_request),
  • tests/handlers/views.py

    diff --git a/tests/handlers/views.py b/tests/handlers/views.py
    index 95d663323d..fe72c600ae 100644
    a b def not_in_transaction(request):  
    3333    return HttpResponse(str(connection.in_atomic_block))
    3434
    3535
     36@transaction.non_atomic_requests
     37async def not_in_transaction_async(request):
     38    return HttpResponse(str(connection.in_atomic_block))
     39
     40
    3641@transaction.non_atomic_requests(using=None)
    3742def not_in_transaction_using_none(request):
    3843    return HttpResponse(str(connection.in_atomic_block))
Exception ignored while finalizing coroutine <coroutine object not_in_transaction_async at 0x10a5812a0>:
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/_py_warnings.py", line 856, in _warn_unawaited_coroutine
    _wm.warn(
RuntimeWarning: coroutine 'not_in_transaction_async' was never awaited

======================================================================
ERROR: test_no_auto_transaction_async (handlers.tests.TransactionsPerRequestTests.test_no_auto_transaction_async)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/unittest/case.py", line 58, in testPartExecutor
    yield
  File "/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/unittest/case.py", line 669, in run
    self._callTestMethod(testMethod)
    
  File "/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/unittest/case.py", line 615, in _callTestMethod
    result = method()
    ^^^^^^^^^^^^^^^
  File "/Users/jwalls/my314/lib/python3.14/site-packages/asgiref/sync.py", line 332, in __call__
    return call_result.result()
    ^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/concurrent/futures/_base.py", line 447, in result
    return self.__get_result()
    ^^^
  File "/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/concurrent/futures/_base.py", line 396, in __get_result
    raise self._exception
    ^^^^^^^
  File "/Users/jwalls/my314/lib/python3.14/site-packages/asgiref/sync.py", line 372, in main_wrap
    result = await awaitable
    ^^^^^^^
  File "/Users/jwalls/django/tests/handlers/tests.py", line 162, in test_no_auto_transaction_async
    response = await self.async_client.get("/not_in_transaction_async/")
    ^^^^^^^^^^^
  File "/Users/jwalls/django/django/test/client.py", line 1495, in get
    response = await super().get(
    ^^^^^^^^^^^^^^^
  File "/Users/jwalls/django/django/test/client.py", line 1458, in request
    self.check_exception(response)
    ^^^^^^^^^^^^^^^
  File "/Users/jwalls/django/django/test/client.py", line 810, in check_exception
    raise exc_value
    ^^^^^^^
  File "/Users/jwalls/my314/lib/python3.14/site-packages/asgiref/sync.py", line 577, in thread_handler
    raise exc_info[1]
    ^^^^^^^
  File "/Users/jwalls/django/django/core/handlers/exception.py", line 43, in inner
    response = await get_response(request)
    ^^^^^^^
  File "/Users/jwalls/django/django/core/handlers/base.py", line 267, in _get_response_async
    self.check_response(response, callback)
    ^^^^^^^^^^^^^^^
  File "/Users/jwalls/django/django/core/handlers/base.py", line 339, in check_response
    raise ValueError(
    ^^^^^^^^^^^
ValueError: The view handlers.views.not_in_transaction_async didn't return an HttpResponse object. It returned an unawaited coroutine instead. You may need to add an 'await' into your view.

----------------------------------------------------------------------
Ran 42 tests in 1.277s

FAILED (errors=1)

My understanding is that using this decorator is required for async views to avoid the safety check here:

    def make_view_atomic(self, view):
        non_atomic_requests = getattr(view, "_non_atomic_requests", set())
        for alias, settings_dict in connections.settings.items():
            if settings_dict["ATOMIC_REQUESTS"] and alias not in non_atomic_requests:
                if iscoroutinefunction(view):
                    raise RuntimeError(
                        "You cannot use ATOMIC_REQUESTS with async views."
                    )
                view = transaction.atomic(using=alias)(view)
        return view

Change History (0)

Note: See TracTickets for help on using tickets.
Back to Top