Skip to content

Commit

Permalink
Fixed #30451 -- Added ASGI handler and coroutine-safety.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
andrewgodwin authored and felixxm committed Jun 20, 2019
1 parent cce47ff commit a415ce7
Show file tree
Hide file tree
Showing 38 changed files with 839 additions and 42 deletions.
16 changes: 16 additions & 0 deletions django/conf/project_template/project_name/asgi.py-tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
ASGI config for {{ project_name }} project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/{{ docs_version }}/howto/deployment/asgi/
"""

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', '{{ project_name }}.settings')

application = get_asgi_application()
41 changes: 33 additions & 8 deletions django/contrib/staticfiles/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,20 @@
from django.conf import settings
from django.contrib.staticfiles import utils
from django.contrib.staticfiles.views import serve
from django.core.handlers.asgi import ASGIHandler
from django.core.handlers.exception import response_for_exception
from django.core.handlers.wsgi import WSGIHandler, get_path_info
from django.http import Http404


class StaticFilesHandler(WSGIHandler):
class StaticFilesHandlerMixin:
"""
WSGI middleware that intercepts calls to the static files directory, as
defined by the STATIC_URL setting, and serves those files.
Common methods used by WSGI and ASGI handlers.
"""
# May be used to differentiate between handler types (e.g. in a
# request_finished signal)
handles_files = True

def __init__(self, application):
self.application = application
self.base_url = urlparse(self.get_base_url())
super().__init__()

def load_middleware(self):
# Middleware are already loaded for self.application; no need to reload
# them for self.
Expand Down Expand Up @@ -57,7 +52,37 @@ def get_response(self, request):
except Http404 as e:
return response_for_exception(request, e)


class StaticFilesHandler(StaticFilesHandlerMixin, WSGIHandler):
"""
WSGI middleware that intercepts calls to the static files directory, as
defined by the STATIC_URL setting, and serves those files.
"""
def __init__(self, application):
self.application = application
self.base_url = urlparse(self.get_base_url())
super().__init__()

def __call__(self, environ, start_response):
if not self._should_handle(get_path_info(environ)):
return self.application(environ, start_response)
return super().__call__(environ, start_response)


class ASGIStaticFilesHandler(StaticFilesHandlerMixin, ASGIHandler):
"""
ASGI application which wraps another and intercepts requests for static
files, passing them off to Django's static file serving.
"""
def __init__(self, application):
self.application = application
self.base_url = urlparse(self.get_base_url())

async def __call__(self, scope, receive, send):
# Only even look at HTTP requests
if scope['type'] == 'http' and self._should_handle(scope['path']):
# Serve static content
# (the one thing super() doesn't do is __call__, apparently)
return await super().__call__(scope, receive, send)
# Hand off to the main app
return await self.application(scope, receive, send)
13 changes: 13 additions & 0 deletions django/core/asgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import django
from django.core.handlers.asgi import ASGIHandler


def get_asgi_application():
"""
The public interface to Django's ASGI support. Return an ASGI 3 callable.
Avoids making django.core.handlers.ASGIHandler a public API, in case the
internal implementation changes or moves in the future.
"""
django.setup(set_prefix=False)
return ASGIHandler()
10 changes: 10 additions & 0 deletions django/core/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ class RequestDataTooBig(SuspiciousOperation):
pass


class RequestAborted(Exception):
"""The request was closed before it was completed, or timed out."""
pass


class PermissionDenied(Exception):
"""The user did not have permission to do that"""
pass
Expand Down Expand Up @@ -181,3 +186,8 @@ def __repr__(self):
class EmptyResultSet(Exception):
"""A database query predicate is impossible."""
pass


class SynchronousOnlyOperation(Exception):
"""The user tried to call a sync-only function from an async context."""
pass
Loading

0 comments on commit a415ce7

Please sign in to comment.