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

This commit is contained in:
Andrew Godwin 2019-12-02 13:02:21 -07:00 committed by Mariusz Felisiak
parent 635a3f8e6e
commit c90ab30fa1
5 changed files with 47 additions and 10 deletions

View File

@ -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

View File

@ -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`).

View File

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

View File

@ -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/

View File

@ -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.')