summaryrefslogtreecommitdiff
path: root/django/utils
diff options
context:
space:
mode:
authorAndrew Godwin <andrew@aeracode.org>2019-04-12 06:15:18 -0700
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2019-06-20 12:29:43 +0200
commita415ce70bef6d91036b00dd2c8544aed7aeeaaed (patch)
tree3583cef22e9b56d2ed52456ab586d9c47620bc51 /django/utils
parentcce47ff65a4dd3786c049ec14ee889e128ca7de9 (diff)
Fixed #30451 -- Added ASGI handler and coroutine-safety.
This adds an ASGI handler, asgi.py file for the default project layout, a few async utilities and adds async-safety to many parts of Django.
Diffstat (limited to 'django/utils')
-rw-r--r--django/utils/asyncio.py32
-rw-r--r--django/utils/timezone.py4
-rw-r--r--django/utils/translation/reloader.py5
-rw-r--r--django/utils/translation/trans_real.py5
4 files changed, 40 insertions, 6 deletions
diff --git a/django/utils/asyncio.py b/django/utils/asyncio.py
new file mode 100644
index 0000000000..c4de04ba12
--- /dev/null
+++ b/django/utils/asyncio.py
@@ -0,0 +1,32 @@
+import asyncio
+import functools
+
+from django.core.exceptions import SynchronousOnlyOperation
+
+
+def async_unsafe(message):
+ """
+ Decorator to mark functions as async-unsafe. Someone trying to access
+ the function while in an async context will get an error 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)
+ # Pass onwards.
+ return func(*args, **kwargs)
+ return inner
+ # If the message is actually a function, then be a no-arguments decorator.
+ if callable(message):
+ func = message
+ message = 'You cannot call this from an async context - use a thread or sync_to_async.'
+ return decorator(func)
+ else:
+ return decorator
diff --git a/django/utils/timezone.py b/django/utils/timezone.py
index 58e92c1fa8..4c43377447 100644
--- a/django/utils/timezone.py
+++ b/django/utils/timezone.py
@@ -6,9 +6,9 @@ import functools
import warnings
from contextlib import ContextDecorator
from datetime import datetime, timedelta, timezone, tzinfo
-from threading import local
import pytz
+from asgiref.local import Local
from django.conf import settings
from django.utils.deprecation import RemovedInDjango31Warning
@@ -89,7 +89,7 @@ def get_default_timezone_name():
return _get_timezone_name(get_default_timezone())
-_active = local()
+_active = Local()
def get_current_timezone():
diff --git a/django/utils/translation/reloader.py b/django/utils/translation/reloader.py
index 8e2d320208..2d69ad44e0 100644
--- a/django/utils/translation/reloader.py
+++ b/django/utils/translation/reloader.py
@@ -1,6 +1,7 @@
-import threading
from pathlib import Path
+from asgiref.local import Local
+
from django.apps import apps
@@ -25,5 +26,5 @@ def translation_file_changed(sender, file_path, **kwargs):
gettext._translations = {}
trans_real._translations = {}
trans_real._default = None
- trans_real._active = threading.local()
+ trans_real._active = Local()
return True
diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py
index f4985fb3c1..e089597ccb 100644
--- a/django/utils/translation/trans_real.py
+++ b/django/utils/translation/trans_real.py
@@ -5,7 +5,8 @@ import os
import re
import sys
import warnings
-from threading import local
+
+from asgiref.local import Local
from django.apps import apps
from django.conf import settings
@@ -20,7 +21,7 @@ from . import to_language, to_locale
# Translations are cached in a dictionary for every language.
# The active translations are stored by threadid to make them thread local.
_translations = {}
-_active = local()
+_active = Local()
# The default translation is based on the settings file.
_default = None