66

I use Django 3.0.6 and Jupyter notebook running with shell_plus --notebook.

I try run queryset:

User.objects.all()

But return this error SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.

I try this command

from asgiref.sync import sync_to_async

users = sync_to_async(User.objects.all())

for user in users:
    print(user)

TypeError: 'SyncToAsync' object is not iterable

The solution of Django documentation

os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true" in settings.py is the unique solution?

Regis Santos
  • 3,469
  • 8
  • 43
  • 65

6 Answers6

67

The error occurs because Jupyter notebooks has a running event loop. From documentation

If you try to run any of these parts from a thread where there is a running event loop, you will get a SynchronousOnlyOperation error. Note that you don’t have to be inside an async function directly to have this error occur. If you have called a synchronous function directly from an asynchronous function without going through something like sync_to_async() or a threadpool, then it can also occur, as your code is still running in an asynchronous context.

If you encounter this error, you should fix your code to not call the offending code from an async context; instead, write your code that talks to async-unsafe in its own, synchronous function, and call that using asgiref.sync.sync_to_async(), or any other preferred way of running synchronous code in its own thread.

If you are absolutely in dire need to run this code from an asynchronous context - for example, it is being forced on you by an external environment, and you are sure there is no chance of it being run concurrently (e.g. you are in a Jupyter notebook), then you can disable the warning with the DJANGO_ALLOW_ASYNC_UNSAFE environment variable.

As the question is specific to jupyter notebook environment following is a valid solution.

import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rest.settings')
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
django.setup()
    
from users.models import User
User.objects.all()

One need to however by wary and not use it in production environment as per documentation warnings.

lyh543
  • 847
  • 6
  • 8
  • 1
    This answer is so much better than the top voted. No need to change the Django src. – Stuart Buckingham Jan 15 '21 at 17:28
  • 3
    Easy, but as pointed out in the docs, this may lead to data corruption: "If you accidentally try to call a part of Django that is still synchronous-only from an async view, you will trigger Django’s asynchronous safety protection to protect your data from corruption." – caram Jan 19 '21 at 08:02
  • 1
    I need only these lines (when using 3.2rc1, if it matters): `import os` `os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"` – Aapo Rista Mar 28 '21 at 10:23
  • 1
    Agree -- this is the simplest way to get a notebook running. After all, we're not actually running a service here, just using a notebook to poke around the Models, etc. – n2ygk Sep 07 '21 at 21:23
42

sync_to_async takes a callable, not the result. Instead, you want this:

from asgiref.sync import sync_to_async

users = sync_to_async(User.objects.all)()

for user in users:
    print(user)

You can also put the call(s) you want to wrap in a decorated function:

from asgiref.sync import sync_to_async

@sync_to_async
def get_all_users():
    return User.objects.all()

for user in await get_all_users():
    print(user)

Note that this must be used from an async context, so a full example would look like:

from asgiref.sync import sync_to_async

@sync_to_async
def get_all_users():
    return User.objects.all()

async def foo(request):
    for user in await get_all_users():
        print(user)

Full documentation

Tom Carrick
  • 6,349
  • 13
  • 54
  • 78
  • 5
    return this error. `:1: RuntimeWarning: coroutine 'SyncToAsync.__call__' was never awaited for user in get_all_users(): RuntimeWarning: Enable tracemalloc to get the object allocation traceback --------------------------------------------------------------------------- TypeError Traceback (most recent call last) in ----> 1 for user in get_all_users(): 2 print(user) TypeError: 'coroutine' object is not iterable` – Regis Santos May 26 '20 at 21:36
  • 2
    Your code `(for user in ...` needs to be inside an async function. You may also need to await it since you're turning it into a coroutine, such as `users = await get_all_users()` – Tom Carrick May 27 '20 at 09:57
  • 1
    But doesn't that lead to making the entire code async, not just the `foo` method? This would increase code complexity. – caram Jan 19 '21 at 10:33
13

You cant pass around a Queryset between sync and async functions since it is lazy. You need to evaluate it inside a async context.

Like this:

from django.contrib.auth.models import User
from asgiref.sync import sync_to_async
import asyncio


@sync_to_async
def get_users():
    return list(
        User.objects.all()
    )

async def user_loop():
    results = await get_users()
    for r in results:
        print(r.username)


loop = asyncio.get_event_loop()
loop.run_until_complete(user_loop())
5

From documentation

If you're trying to run any none-async parts of Django like ORM, you will get a SynchronousOnlyOperation error. Note that you don’t have to be inside an async function directly to have this error occur. If you have called a sync function directly from an async function, without using sync_to_async() or similar, then it can also occur. This is because your code is still running in a thread with an active event loop, even though it may not be declared as an async code.

You should use:

from asgiref.sync import sync_to_async

def _get_blog(pk):
    return Blog.objects.get(pk=pk)

get_blog = await sync_to_async(_get_blog, thread_sensitive=True)(10)

....

Or

from asgiref.sync import sync_to_async

get_blog = await sync_to_async(Blog.objects.get, thread_sensitive=True)(10)

You can use database_sync_to_async of Django channel which is an improved sync_to_async function of asgiref.sync

for example:

from channels.db import database_sync_to_async

get_blog = await database_sync_to_async(Blog.objects.get, thread_sensitive=True)(10)

Ehsan Ahmadi
  • 1,382
  • 15
  • 16
  • 1
    I tried this: get_blog = await sync_to_async(Blog.objects.all(), thread_sensitive=True) object SyncToAsync can't be used in 'await' expression – Siva Sankar Jun 11 '21 at 14:21
  • 1
    if you are to use await, you have to use database_sync_to_async or sync_to_async inside an async function – Ehsan Ahmadi Jun 12 '21 at 07:05
0

I just used this way to get all the user as normal sync method.

What is i done is

  1. First i create a method the get all the query and then i iterate by for loop.

  2. The for loop is only for put a time loop like time.sleep(seconds). The loop itself choose a time. If the queryset completely got in the users variable. The timer is started (i.e) the for loop.

     from asgiref.sync import sync_to_async
    
     @sync_to_async
     def get_all_user():
         users = None
         users = User.objects.all()
         for user in users:
             '''
                 timming loop. itself set a time by the time taken for iterate over the all User.objects.all()
             ''' 
             pass
         return users
    
-2

You can find the answer from here: https://docs.djangoproject.com/en/3.2/topics/async/#async-safety

If you try to run any of these parts from a thread where there is a running event loop, you will get a SynchronousOnlyOperation error. Note that you don’t have to be inside an async function directly to have this error occur. If you have called a sync function directly from an async function, without using sync_to_async() or similar, then it can also occur. This is because your code is still running in a thread with an active event loop, even though it may not be declared as async code.

Ni Xiaoni
  • 1,639
  • 2
  • 13
  • 10