summaryrefslogtreecommitdiff
path: root/django/db/backends
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/db/backends
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/db/backends')
-rw-r--r--django/db/backends/base/base.py11
-rw-r--r--django/db/backends/mysql/base.py3
-rw-r--r--django/db/backends/oracle/base.py3
-rw-r--r--django/db/backends/postgresql/base.py30
-rw-r--r--django/db/backends/sqlite3/base.py3
5 files changed, 48 insertions, 2 deletions
diff --git a/django/db/backends/base/base.py b/django/db/backends/base/base.py
index 057fe8ac43..6435f478dd 100644
--- a/django/db/backends/base/base.py
+++ b/django/db/backends/base/base.py
@@ -17,6 +17,7 @@ from django.db.backends.signals import connection_created
from django.db.transaction import TransactionManagementError
from django.db.utils import DatabaseError, DatabaseErrorWrapper
from django.utils import timezone
+from django.utils.asyncio import async_unsafe
from django.utils.functional import cached_property
NO_DB_ALIAS = '__no_db__'
@@ -177,6 +178,7 @@ class BaseDatabaseWrapper:
# ##### Backend-specific methods for creating connections #####
+ @async_unsafe
def connect(self):
"""Connect to the database. Assume that the connection is closed."""
# Check for invalid configurations.
@@ -210,6 +212,7 @@ class BaseDatabaseWrapper:
"Connection '%s' cannot set TIME_ZONE because its engine "
"handles time zones conversions natively." % self.alias)
+ @async_unsafe
def ensure_connection(self):
"""Guarantee that a connection to the database is established."""
if self.connection is None:
@@ -251,10 +254,12 @@ class BaseDatabaseWrapper:
# ##### Generic wrappers for PEP-249 connection methods #####
+ @async_unsafe
def cursor(self):
"""Create a cursor, opening a connection if necessary."""
return self._cursor()
+ @async_unsafe
def commit(self):
"""Commit a transaction and reset the dirty flag."""
self.validate_thread_sharing()
@@ -264,6 +269,7 @@ class BaseDatabaseWrapper:
self.errors_occurred = False
self.run_commit_hooks_on_set_autocommit_on = True
+ @async_unsafe
def rollback(self):
"""Roll back a transaction and reset the dirty flag."""
self.validate_thread_sharing()
@@ -274,6 +280,7 @@ class BaseDatabaseWrapper:
self.needs_rollback = False
self.run_on_commit = []
+ @async_unsafe
def close(self):
"""Close the connection to the database."""
self.validate_thread_sharing()
@@ -313,6 +320,7 @@ class BaseDatabaseWrapper:
# ##### Generic savepoint management methods #####
+ @async_unsafe
def savepoint(self):
"""
Create a savepoint inside the current transaction. Return an
@@ -333,6 +341,7 @@ class BaseDatabaseWrapper:
return sid
+ @async_unsafe
def savepoint_rollback(self, sid):
"""
Roll back to a savepoint. Do nothing if savepoints are not supported.
@@ -348,6 +357,7 @@ class BaseDatabaseWrapper:
(sids, func) for (sids, func) in self.run_on_commit if sid not in sids
]
+ @async_unsafe
def savepoint_commit(self, sid):
"""
Release a savepoint. Do nothing if savepoints are not supported.
@@ -358,6 +368,7 @@ class BaseDatabaseWrapper:
self.validate_thread_sharing()
self._savepoint_commit(sid)
+ @async_unsafe
def clean_savepoints(self):
"""
Reset the counter used to generate unique savepoint ids in this thread.
diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py
index 6613a85b1b..9b88c5ac25 100644
--- a/django/db/backends/mysql/base.py
+++ b/django/db/backends/mysql/base.py
@@ -9,6 +9,7 @@ from django.core.exceptions import ImproperlyConfigured
from django.db import utils
from django.db.backends import utils as backend_utils
from django.db.backends.base.base import BaseDatabaseWrapper
+from django.utils.asyncio import async_unsafe
from django.utils.functional import cached_property
try:
@@ -223,6 +224,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
kwargs.update(options)
return kwargs
+ @async_unsafe
def get_new_connection(self, conn_params):
return Database.connect(**conn_params)
@@ -242,6 +244,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
with self.cursor() as cursor:
cursor.execute('; '.join(assignments))
+ @async_unsafe
def create_cursor(self, name=None):
cursor = self.connection.cursor()
return CursorWrapper(cursor)
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index b19361b157..0fbe96ab9b 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -13,6 +13,7 @@ from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.db import utils
from django.db.backends.base.base import BaseDatabaseWrapper
+from django.utils.asyncio import async_unsafe
from django.utils.encoding import force_bytes, force_str
from django.utils.functional import cached_property
@@ -221,6 +222,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
del conn_params['use_returning_into']
return conn_params
+ @async_unsafe
def get_new_connection(self, conn_params):
return Database.connect(
user=self.settings_dict['USER'],
@@ -269,6 +271,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
if not self.get_autocommit():
self.commit()
+ @async_unsafe
def create_cursor(self, name=None):
return FormatStylePlaceholderCursor(self.connection)
diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py
index 6f8e06fe23..7e34a3a177 100644
--- a/django/db/backends/postgresql/base.py
+++ b/django/db/backends/postgresql/base.py
@@ -4,6 +4,7 @@ PostgreSQL database backend for Django.
Requires psycopg 2: http://initd.org/projects/psycopg2
"""
+import asyncio
import threading
import warnings
@@ -15,6 +16,7 @@ from django.db.backends.utils import (
CursorDebugWrapper as BaseCursorDebugWrapper,
)
from django.db.utils import DatabaseError as WrappedDatabaseError
+from django.utils.asyncio import async_unsafe
from django.utils.functional import cached_property
from django.utils.safestring import SafeString
from django.utils.version import get_version_tuple
@@ -177,6 +179,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
conn_params['port'] = settings_dict['PORT']
return conn_params
+ @async_unsafe
def get_new_connection(self, conn_params):
connection = Database.connect(**conn_params)
@@ -217,6 +220,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
if not self.get_autocommit():
self.connection.commit()
+ @async_unsafe
def create_cursor(self, name=None):
if name:
# In autocommit mode, the cursor will be used outside of a
@@ -227,12 +231,34 @@ class DatabaseWrapper(BaseDatabaseWrapper):
cursor.tzinfo_factory = utc_tzinfo_factory if settings.USE_TZ else None
return cursor
+ @async_unsafe
def chunked_cursor(self):
self._named_cursor_idx += 1
+ # Get the current async task
+ # Note that right now this is behind @async_unsafe, so this is
+ # unreachable, but in future we'll start loosening this restriction.
+ # For now, it's here so that every use of "threading" is
+ # also async-compatible.
+ try:
+ if hasattr(asyncio, 'current_task'):
+ # Python 3.7 and up
+ current_task = asyncio.current_task()
+ else:
+ # Python 3.6
+ current_task = asyncio.Task.current_task()
+ except RuntimeError:
+ current_task = None
+ # Current task can be none even if the current_task call didn't error
+ if current_task:
+ task_ident = str(id(current_task))
+ else:
+ task_ident = 'sync'
+ # Use that and the thread ident to get a unique name
return self._cursor(
- name='_django_curs_%d_%d' % (
- # Avoid reusing name in other threads
+ name='_django_curs_%d_%s_%d' % (
+ # Avoid reusing name in other threads / tasks
threading.current_thread().ident,
+ task_ident,
self._named_cursor_idx,
)
)
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index f4184fce05..fff65197f9 100644
--- a/django/db/backends/sqlite3/base.py
+++ b/django/db/backends/sqlite3/base.py
@@ -20,6 +20,7 @@ from django.db import utils
from django.db.backends import utils as backend_utils
from django.db.backends.base.base import BaseDatabaseWrapper
from django.utils import timezone
+from django.utils.asyncio import async_unsafe
from django.utils.dateparse import parse_datetime, parse_time
from django.utils.duration import duration_microseconds
@@ -191,6 +192,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
kwargs.update({'check_same_thread': False, 'uri': True})
return kwargs
+ @async_unsafe
def get_new_connection(self, conn_params):
conn = Database.connect(**conn_params)
conn.create_function("django_date_extract", 2, _sqlite_datetime_extract)
@@ -248,6 +250,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
def create_cursor(self, name=None):
return self.connection.cursor(factory=SQLiteCursorWrapper)
+ @async_unsafe
def close(self):
self.validate_thread_sharing()
# If database is in memory, closing the connection destroys the