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 asyncio
import functools import functools
import os
from django.core.exceptions import SynchronousOnlyOperation from django.core.exceptions import SynchronousOnlyOperation
@ -12,6 +13,7 @@ def async_unsafe(message):
def decorator(func): def decorator(func):
@functools.wraps(func) @functools.wraps(func)
def inner(*args, **kwargs): def inner(*args, **kwargs):
if not os.environ.get('DJANGO_ALLOW_ASYNC_UNSAFE'):
# Detect a running event loop in this thread. # Detect a running event loop in this thread.
try: try:
event_loop = asyncio.get_event_loop() event_loop = asyncio.get_event_loop()

View File

@ -9,4 +9,7 @@ Django 3.0.1 fixes several bugs in 3.0.
Bugfixes 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 JavaScript
Jinja Jinja
jQuery jQuery
Jupyter
jython jython
Kaplan Kaplan
Kessler 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 can natively talk :doc:`ASGI </howto/deployment/asgi/index>`, and some async
safety support. safety support.
.. _async-safety:
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 in its own, synchronous function, and call that using
``asgiref.sync.async_to_sync``, or any other preferred way of running ``asgiref.sync.async_to_sync``, or any other preferred way of running
synchronous code in its own thread. 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 import sys
from unittest import skipIf from unittest import mock, skipIf
from asgiref.sync import async_to_sync from asgiref.sync import async_to_sync
@ -39,3 +40,13 @@ class AsyncUnsafeTest(SimpleTestCase):
) )
with self.assertRaisesMessage(SynchronousOnlyOperation, msg): with self.assertRaisesMessage(SynchronousOnlyOperation, msg):
self.dangerous_method() 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.')