Workaround for SynchronousOnlyOperation when using AuthenticationMiddleware

In Dango v4.2.3 I have to use some async views (long-running access to 3rd party APIs) and also have django.contrib.auth.middleware.AuthenticationMiddleware in my MIDDLEWARE setting. Now I hit the SynchronousOnlyOperation exception. As far as I know, this will be solved with Django v5. Is there in the meantime some 3rd party middleware I could use or some other workaround?

You need to give more detail. Sync middleware are adapted as needed when running under ASGI, so you’re doing something more than just using the authentication middleware.

Ah, ok … I didn’t know that it should work. Do you haven an idea what could be wrong with my setup, or how I investigate it further?

This is how I define the view:

class DicomExplorerFormView(LoginRequiredMixin, View):
    async def get(self, request: HttpRequest) -> HttpResponse:
        ....

Here is the full stack trace:

Environment:


Request Method: GET
Request URL: http://adit.krz.uni-heidelberg.de:8000/dicom-explorer/

Django Version: 4.2.3
Python Version: 3.11.4
Installed Applications:
['daphne',
 'whitenoise.runserver_nostatic',
 'adit.accounts.apps.AccountsConfig',
 'registration',
 'django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'django.contrib.humanize',
 'django.contrib.sites',
 'revproxy',
 'loginas',
 'crispy_forms',
 'crispy_bootstrap5',
 'django_htmx',
 'django_tables2',
 'rest_framework',
 'adrf',
 'adit.core.apps.CoreConfig',
 'adit.api.apps.ApiConfig',
 'adit.selective_transfer.apps.SelectiveTransferConfig',
 'adit.batch_query.apps.BatchQueryConfig',
 'adit.batch_transfer.apps.BatchTransferConfig',
 'adit.dicom_explorer.apps.DicomExplorerConfig',
 'adit.token_authentication.apps.TokenAuthenticationConfig',
 'adit.dicom_web.apps.DicomWebConfig',
 'channels',
 'debug_toolbar',
 'debug_permissions',
 'django_extensions']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'whitenoise.middleware.WhiteNoiseMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'django.contrib.sites.middleware.CurrentSiteMiddleware',
 'django_htmx.middleware.HtmxMiddleware',
 'adit.core.middlewares.MaintenanceMiddleware',
 'adit.core.middlewares.TimezoneMiddleware',
 'debug_toolbar.middleware.DebugToolbarMiddleware']



Traceback (most recent call last):
  File "/opt/pysetup/.venv/lib/python3.11/site-packages/django/core/handlers/exception.py", line 42, in inner
    response = await get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/pysetup/.venv/lib/python3.11/site-packages/django/core/handlers/base.py", line 253, in _get_response_async
    response = await wrapped_callback(
                     
  File "/opt/pysetup/.venv/lib/python3.11/site-packages/django/views/generic/base.py", line 104, in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/pysetup/.venv/lib/python3.11/site-packages/django/contrib/auth/mixins.py", line 71, in dispatch
    if not request.user.is_authenticated:
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/pysetup/.venv/lib/python3.11/site-packages/django/utils/functional.py", line 266, in inner
    self._setup()
    ^^^^^^^^^^^^^
  File "/opt/pysetup/.venv/lib/python3.11/site-packages/django/utils/functional.py", line 419, in _setup
    self._wrapped = self._setupfunc()
                    ^^^^^^^^^^^^^^^^^
  File "/opt/pysetup/.venv/lib/python3.11/site-packages/django/contrib/auth/middleware.py", line 25, in <lambda>
    request.user = SimpleLazyObject(lambda: get_user(request))
                                            ^^^^^^^^^^^^^^^^^
  File "/opt/pysetup/.venv/lib/python3.11/site-packages/django/contrib/auth/middleware.py", line 11, in get_user
    request._cached_user = auth.get_user(request)
                           ^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/pysetup/.venv/lib/python3.11/site-packages/django/contrib/auth/__init__.py", line 198, in get_user
    user = backend.get_user(user_id)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/pysetup/.venv/lib/python3.11/site-packages/django/contrib/auth/backends.py", line 161, in get_user
    user = UserModel._default_manager.get(pk=user_id)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/pysetup/.venv/lib/python3.11/site-packages/django/db/models/manager.py", line 87, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/pysetup/.venv/lib/python3.11/site-packages/django/db/models/query.py", line 633, in get
    num = len(clone)
          ^^^^^^^^^^
  File "/opt/pysetup/.venv/lib/python3.11/site-packages/django/db/models/query.py", line 380, in __len__
    self._fetch_all()
    ^^^^^^^^^^^^^^^^^
  File "/opt/pysetup/.venv/lib/python3.11/site-packages/django/db/models/query.py", line 1881, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/pysetup/.venv/lib/python3.11/site-packages/django/db/models/query.py", line 91, in __iter__
    results = compiler.execute_sql(
              
  File "/opt/pysetup/.venv/lib/python3.11/site-packages/django/db/models/sql/compiler.py", line 1560, in execute_sql
    cursor = self.connection.cursor()
             ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/pysetup/.venv/lib/python3.11/site-packages/django/utils/asyncio.py", line 24, in inner
    raise SynchronousOnlyOperation(message)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Exception Type: SynchronousOnlyOperation at /dicom-explorer/
Exception Value: You cannot call this from an async context - use a thread or sync_to_async.

EDIT:
I also tried to remove all nonessential middlewares (see below), but still got the exception.

MIDDLEWARE = [
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
]

Hi @medihack — you’re still not showing the actual code that’s raising the issue…

I’m going to assume — but guessing — it’s this issue that you’re hitting:

https://code.djangoproject.com/ticket/31920

Unwrapping request.user in an async view…

So is there a workaround? — You can use one of the examples from the report (Cases 4 and 5 from the description there.)

Something like…

user = await sync_to_async(bool)(request.user)

… should get you off the ground.

1 Like

Hi @carltongibson - you are absolutely right. You know what I do without seeing it (I really hope my wife doesn’t have this skill, too :joy:). I simply rendered my template like usual (return render(request, "dicom_explorer/query_form.html" {"form": form}), which indeed can not work as I access the user in there. But even if I don’t access the user in there but have Django debug toolbar enabled, it would raise the error as debug toolbar seems to access the user object. That is why I thought it was the middleware, because of the lazy evaluation in there. Wrapping the whole render in sync_to_async works fine. So, thanks a lot for the help!
By the way, big fan of yours and the Django podcast!

1 Like

Great! :dancer:

Glad you enjoy it. Thanks :blush: