Browse Source

Fixed #31056 -- Allowed disabling async-unsafe check with an environment variable.

Andrew Godwin 5 years ago
parent
commit
c90ab30fa1
5 changed files with 47 additions and 10 deletions
  1. 10 8
      django/utils/asyncio.py
  2. 4 1
      docs/releases/3.0.1.txt
  3. 1 0
      docs/spelling_wordlist
  4. 20 0
      docs/topics/async.txt
  5. 12 1
      tests/async/tests.py

+ 10 - 8
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

+ 4 - 1
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`).

+ 1 - 0
docs/spelling_wordlist

@@ -309,6 +309,7 @@ ize
 JavaScript
 Jinja
 jQuery
+Jupyter
 jython
 Kaplan
 Kessler

+ 20 - 0
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/

+ 12 - 1
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.')