diff options
| author | Andrew Godwin <andrew@aeracode.org> | 2019-04-12 06:15:18 -0700 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2019-06-20 12:29:43 +0200 |
| commit | a415ce70bef6d91036b00dd2c8544aed7aeeaaed (patch) | |
| tree | 3583cef22e9b56d2ed52456ab586d9c47620bc51 /django/db/backends | |
| parent | cce47ff65a4dd3786c049ec14ee889e128ca7de9 (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.py | 11 | ||||
| -rw-r--r-- | django/db/backends/mysql/base.py | 3 | ||||
| -rw-r--r-- | django/db/backends/oracle/base.py | 3 | ||||
| -rw-r--r-- | django/db/backends/postgresql/base.py | 30 | ||||
| -rw-r--r-- | django/db/backends/sqlite3/base.py | 3 |
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 |
