Skip to content

Commit

Permalink
[3.0.x] Fixed #31056 -- Allowed disabling async-unsafe check with an …
Browse files Browse the repository at this point in the history
…environment variable.

Backport of c90ab30 from master
  • Loading branch information
andrewgodwin authored and felixxm committed Dec 3, 2019
1 parent 45de0c2 commit 9243435
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 10 deletions.
18 changes: 10 additions & 8 deletions django/utils/asyncio.py
@@ -1,5 +1,6 @@
import asyncio
import functools
import os

from django.core.exceptions import SynchronousOnlyOperation

Expand All @@ -12,14 +13,15 @@ def async_unsafe(message):
def decorator(func):
@functools.wraps(func)
def inner(*args, **kwargs):
# Detect a running event loop in this thread.
try:
event_loop = asyncio.get_event_loop()
except RuntimeError:
pass
else:
if event_loop.is_running():
raise SynchronousOnlyOperation(message)
if not os.environ.get('DJANGO_ALLOW_ASYNC_UNSAFE'):
# Detect a running event loop in this thread.
try:
event_loop = asyncio.get_event_loop()
except RuntimeError:
pass
else:
if event_loop.is_running():
raise SynchronousOnlyOperation(message)
# Pass onwards.
return func(*args, **kwargs)
return inner
Expand Down
5 changes: 4 additions & 1 deletion docs/releases/3.0.1.txt
Expand Up @@ -9,4 +9,7 @@ Django 3.0.1 fixes several bugs in 3.0.
Bugfixes
========

* ...
* Fixed a regression in Django 3.0 by restoring the ability to use Django
inside Jupyter and other environments that force an async context, by adding
and option to disable :ref:`async-safety` mechanism with
``DJANGO_ALLOW_ASYNC_UNSAFE`` environment variable (:ticket:`31056`).
1 change: 1 addition & 0 deletions docs/spelling_wordlist
Expand Up @@ -309,6 +309,7 @@ ize
JavaScript
Jinja
jQuery
Jupyter
jython
Kaplan
Kessler
Expand Down
20 changes: 20 additions & 0 deletions docs/topics/async.txt
Expand Up @@ -12,6 +12,8 @@ There is limited support for other parts of the async ecosystem; namely, Django
can natively talk :doc:`ASGI </howto/deployment/asgi/index>`, and some async
safety support.

.. _async-safety:

Async-safety
------------

Expand All @@ -34,3 +36,21 @@ 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.async_to_sync``, 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.

.. warning::

If you enable this option and there is concurrent access to the
async-unsafe parts of Django, you may suffer data loss or corruption. Be
very careful and do not use this in production environments.

If you need to do this from within Python, do that with ``os.environ``::

os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"

.. _Jupyter: https://jupyter.org/
13 changes: 12 additions & 1 deletion tests/async/tests.py
@@ -1,5 +1,6 @@
import os
import sys
from unittest import skipIf
from unittest import mock, skipIf

from asgiref.sync import async_to_sync

Expand Down Expand Up @@ -39,3 +40,13 @@ async def test_async_unsafe(self):
)
with self.assertRaisesMessage(SynchronousOnlyOperation, msg):
self.dangerous_method()

@mock.patch.dict(os.environ, {'DJANGO_ALLOW_ASYNC_UNSAFE': 'true'})
@async_to_sync
async def test_async_unsafe_suppressed(self):
# Decorator doesn't trigger check when the environment variable to
# suppress it is set.
try:
self.dangerous_method()
except SynchronousOnlyOperation:
self.fail('SynchronousOnlyOperation should not be raised.')

0 comments on commit 9243435

Please sign in to comment.