summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Godwin <andrew@aeracode.org>2019-12-02 13:02:21 -0700
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2019-12-03 17:29:31 +0100
commitc90ab30fa1305481024b9c3c50b5a6ed6cd9a2f5 (patch)
treec1178f55153aec63190c0329882a00513569e2cc
parent635a3f8e6e0303e8a87586ef6be4ab0cc01d7c0d (diff)
Fixed #31056 -- Allowed disabling async-unsafe check with an environment variable.
-rw-r--r--django/utils/asyncio.py18
-rw-r--r--docs/releases/3.0.1.txt5
-rw-r--r--docs/spelling_wordlist1
-rw-r--r--docs/topics/async.txt20
-rw-r--r--tests/async/tests.py13
5 files changed, 47 insertions, 10 deletions
diff --git a/django/utils/asyncio.py b/django/utils/asyncio.py
index c4de04ba12..2405e3413e 100644
--- a/django/utils/asyncio.py
+++ b/django/utils/asyncio.py
@@ -1,5 +1,6 @@
import asyncio
import functools
+import os
from django.core.exceptions import SynchronousOnlyOperation
@@ -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
diff --git a/docs/releases/3.0.1.txt b/docs/releases/3.0.1.txt
index 18e2ea44c4..589ef40499 100644
--- a/docs/releases/3.0.1.txt
+++ b/docs/releases/3.0.1.txt
@@ -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`).
diff --git a/docs/spelling_wordlist b/docs/spelling_wordlist
index 9c56339cc0..6a36a11707 100644
--- a/docs/spelling_wordlist
+++ b/docs/spelling_wordlist
@@ -309,6 +309,7 @@ ize
JavaScript
Jinja
jQuery
+Jupyter
jython
Kaplan
Kessler
diff --git a/docs/topics/async.txt b/docs/topics/async.txt
index 6a6f0030c9..b341084b1a 100644
--- a/docs/topics/async.txt
+++ b/docs/topics/async.txt
@@ -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
------------
@@ -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/
diff --git a/tests/async/tests.py b/tests/async/tests.py
index 450a38018d..f42e549075 100644
--- a/tests/async/tests.py
+++ b/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
@@ -39,3 +40,13 @@ class AsyncUnsafeTest(SimpleTestCase):
)
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.')