mirror of
https://github.com/django/django.git
synced 2025-10-24 06:06:09 +00:00
Fixed #31224 -- Added support for asynchronous views and middleware.
This implements support for asynchronous views, asynchronous tests, asynchronous middleware, and an asynchronous test client.
This commit is contained in:
committed by
Mariusz Felisiak
parent
3f7e4b16bf
commit
fc0fa72ff4
@@ -1,6 +1,9 @@
|
||||
from django.http import Http404, HttpResponse
|
||||
from django.template import engines
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.decorators import (
|
||||
async_only_middleware, sync_and_async_middleware, sync_only_middleware,
|
||||
)
|
||||
|
||||
log = []
|
||||
|
||||
@@ -18,6 +21,12 @@ class ProcessExceptionMiddleware(BaseMiddleware):
|
||||
return HttpResponse('Exception caught')
|
||||
|
||||
|
||||
@async_only_middleware
|
||||
class AsyncProcessExceptionMiddleware(BaseMiddleware):
|
||||
async def process_exception(self, request, exception):
|
||||
return HttpResponse('Exception caught')
|
||||
|
||||
|
||||
class ProcessExceptionLogMiddleware(BaseMiddleware):
|
||||
def process_exception(self, request, exception):
|
||||
log.append('process-exception')
|
||||
@@ -33,6 +42,12 @@ class ProcessViewMiddleware(BaseMiddleware):
|
||||
return HttpResponse('Processed view %s' % view_func.__name__)
|
||||
|
||||
|
||||
@async_only_middleware
|
||||
class AsyncProcessViewMiddleware(BaseMiddleware):
|
||||
async def process_view(self, request, view_func, view_args, view_kwargs):
|
||||
return HttpResponse('Processed view %s' % view_func.__name__)
|
||||
|
||||
|
||||
class ProcessViewNoneMiddleware(BaseMiddleware):
|
||||
def process_view(self, request, view_func, view_args, view_kwargs):
|
||||
log.append('processed view %s' % view_func.__name__)
|
||||
@@ -51,6 +66,13 @@ class TemplateResponseMiddleware(BaseMiddleware):
|
||||
return response
|
||||
|
||||
|
||||
@async_only_middleware
|
||||
class AsyncTemplateResponseMiddleware(BaseMiddleware):
|
||||
async def process_template_response(self, request, response):
|
||||
response.context_data['mw'].append(self.__class__.__name__)
|
||||
return response
|
||||
|
||||
|
||||
class LogMiddleware(BaseMiddleware):
|
||||
def __call__(self, request):
|
||||
response = self.get_response(request)
|
||||
@@ -63,6 +85,48 @@ class NoTemplateResponseMiddleware(BaseMiddleware):
|
||||
return None
|
||||
|
||||
|
||||
@async_only_middleware
|
||||
class AsyncNoTemplateResponseMiddleware(BaseMiddleware):
|
||||
async def process_template_response(self, request, response):
|
||||
return None
|
||||
|
||||
|
||||
class NotFoundMiddleware(BaseMiddleware):
|
||||
def __call__(self, request):
|
||||
raise Http404('not found')
|
||||
|
||||
|
||||
class TeapotMiddleware(BaseMiddleware):
|
||||
def __call__(self, request):
|
||||
response = self.get_response(request)
|
||||
response.status_code = 418
|
||||
return response
|
||||
|
||||
|
||||
@async_only_middleware
|
||||
def async_teapot_middleware(get_response):
|
||||
async def middleware(request):
|
||||
response = await get_response(request)
|
||||
response.status_code = 418
|
||||
return response
|
||||
|
||||
return middleware
|
||||
|
||||
|
||||
@sync_and_async_middleware
|
||||
class SyncAndAsyncMiddleware(BaseMiddleware):
|
||||
pass
|
||||
|
||||
|
||||
@sync_only_middleware
|
||||
class DecoratedTeapotMiddleware(TeapotMiddleware):
|
||||
pass
|
||||
|
||||
|
||||
class NotSyncOrAsyncMiddleware(BaseMiddleware):
|
||||
"""Middleware that is deliberately neither sync or async."""
|
||||
sync_capable = False
|
||||
async_capable = False
|
||||
|
||||
def __call__(self, request):
|
||||
return self.get_response(request)
|
||||
|
||||
@@ -180,3 +180,162 @@ class MiddlewareNotUsedTests(SimpleTestCase):
|
||||
with self.assertRaisesMessage(AssertionError, 'no logs'):
|
||||
with self.assertLogs('django.request', 'DEBUG'):
|
||||
self.client.get('/middleware_exceptions/view/')
|
||||
|
||||
|
||||
@override_settings(
|
||||
DEBUG=True,
|
||||
ROOT_URLCONF='middleware_exceptions.urls',
|
||||
)
|
||||
class MiddlewareSyncAsyncTests(SimpleTestCase):
|
||||
@override_settings(MIDDLEWARE=[
|
||||
'middleware_exceptions.middleware.TeapotMiddleware',
|
||||
])
|
||||
def test_sync_teapot_middleware(self):
|
||||
response = self.client.get('/middleware_exceptions/view/')
|
||||
self.assertEqual(response.status_code, 418)
|
||||
|
||||
@override_settings(MIDDLEWARE=[
|
||||
'middleware_exceptions.middleware.DecoratedTeapotMiddleware',
|
||||
])
|
||||
def test_sync_decorated_teapot_middleware(self):
|
||||
response = self.client.get('/middleware_exceptions/view/')
|
||||
self.assertEqual(response.status_code, 418)
|
||||
|
||||
@override_settings(MIDDLEWARE=[
|
||||
'middleware_exceptions.middleware.async_teapot_middleware',
|
||||
])
|
||||
def test_async_teapot_middleware(self):
|
||||
with self.assertLogs('django.request', 'DEBUG') as cm:
|
||||
response = self.client.get('/middleware_exceptions/view/')
|
||||
self.assertEqual(response.status_code, 418)
|
||||
self.assertEqual(
|
||||
cm.records[0].getMessage(),
|
||||
"Synchronous middleware "
|
||||
"middleware_exceptions.middleware.async_teapot_middleware "
|
||||
"adapted.",
|
||||
)
|
||||
|
||||
@override_settings(MIDDLEWARE=[
|
||||
'middleware_exceptions.middleware.NotSyncOrAsyncMiddleware',
|
||||
])
|
||||
def test_not_sync_or_async_middleware(self):
|
||||
msg = (
|
||||
'Middleware '
|
||||
'middleware_exceptions.middleware.NotSyncOrAsyncMiddleware must '
|
||||
'have at least one of sync_capable/async_capable set to True.'
|
||||
)
|
||||
with self.assertRaisesMessage(RuntimeError, msg):
|
||||
self.client.get('/middleware_exceptions/view/')
|
||||
|
||||
@override_settings(MIDDLEWARE=[
|
||||
'middleware_exceptions.middleware.TeapotMiddleware',
|
||||
])
|
||||
async def test_sync_teapot_middleware_async(self):
|
||||
with self.assertLogs('django.request', 'DEBUG') as cm:
|
||||
response = await self.async_client.get('/middleware_exceptions/view/')
|
||||
self.assertEqual(response.status_code, 418)
|
||||
self.assertEqual(
|
||||
cm.records[0].getMessage(),
|
||||
"Asynchronous middleware "
|
||||
"middleware_exceptions.middleware.TeapotMiddleware adapted.",
|
||||
)
|
||||
|
||||
@override_settings(MIDDLEWARE=[
|
||||
'middleware_exceptions.middleware.async_teapot_middleware',
|
||||
])
|
||||
async def test_async_teapot_middleware_async(self):
|
||||
with self.assertLogs('django.request', 'WARNING') as cm:
|
||||
response = await self.async_client.get('/middleware_exceptions/view/')
|
||||
self.assertEqual(response.status_code, 418)
|
||||
self.assertEqual(
|
||||
cm.records[0].getMessage(),
|
||||
'Unknown Status Code: /middleware_exceptions/view/',
|
||||
)
|
||||
|
||||
@override_settings(
|
||||
DEBUG=False,
|
||||
MIDDLEWARE=[
|
||||
'middleware_exceptions.middleware.AsyncNoTemplateResponseMiddleware',
|
||||
],
|
||||
)
|
||||
def test_async_process_template_response_returns_none_with_sync_client(self):
|
||||
msg = (
|
||||
"AsyncNoTemplateResponseMiddleware.process_template_response "
|
||||
"didn't return an HttpResponse object."
|
||||
)
|
||||
with self.assertRaisesMessage(ValueError, msg):
|
||||
self.client.get('/middleware_exceptions/template_response/')
|
||||
|
||||
@override_settings(MIDDLEWARE=[
|
||||
'middleware_exceptions.middleware.SyncAndAsyncMiddleware',
|
||||
])
|
||||
async def test_async_and_sync_middleware_async_call(self):
|
||||
response = await self.async_client.get('/middleware_exceptions/view/')
|
||||
self.assertEqual(response.content, b'OK')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@override_settings(MIDDLEWARE=[
|
||||
'middleware_exceptions.middleware.SyncAndAsyncMiddleware',
|
||||
])
|
||||
def test_async_and_sync_middleware_sync_call(self):
|
||||
response = self.client.get('/middleware_exceptions/view/')
|
||||
self.assertEqual(response.content, b'OK')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
@override_settings(ROOT_URLCONF='middleware_exceptions.urls')
|
||||
class AsyncMiddlewareTests(SimpleTestCase):
|
||||
@override_settings(MIDDLEWARE=[
|
||||
'middleware_exceptions.middleware.AsyncTemplateResponseMiddleware',
|
||||
])
|
||||
async def test_process_template_response(self):
|
||||
response = await self.async_client.get(
|
||||
'/middleware_exceptions/template_response/'
|
||||
)
|
||||
self.assertEqual(
|
||||
response.content,
|
||||
b'template_response OK\nAsyncTemplateResponseMiddleware',
|
||||
)
|
||||
|
||||
@override_settings(MIDDLEWARE=[
|
||||
'middleware_exceptions.middleware.AsyncNoTemplateResponseMiddleware',
|
||||
])
|
||||
async def test_process_template_response_returns_none(self):
|
||||
msg = (
|
||||
"AsyncNoTemplateResponseMiddleware.process_template_response "
|
||||
"didn't return an HttpResponse object. It returned None instead."
|
||||
)
|
||||
with self.assertRaisesMessage(ValueError, msg):
|
||||
await self.async_client.get('/middleware_exceptions/template_response/')
|
||||
|
||||
@override_settings(MIDDLEWARE=[
|
||||
'middleware_exceptions.middleware.AsyncProcessExceptionMiddleware',
|
||||
])
|
||||
async def test_exception_in_render_passed_to_process_exception(self):
|
||||
response = await self.async_client.get(
|
||||
'/middleware_exceptions/exception_in_render/'
|
||||
)
|
||||
self.assertEqual(response.content, b'Exception caught')
|
||||
|
||||
@override_settings(MIDDLEWARE=[
|
||||
'middleware_exceptions.middleware.AsyncProcessExceptionMiddleware',
|
||||
])
|
||||
async def test_exception_in_async_render_passed_to_process_exception(self):
|
||||
response = await self.async_client.get(
|
||||
'/middleware_exceptions/async_exception_in_render/'
|
||||
)
|
||||
self.assertEqual(response.content, b'Exception caught')
|
||||
|
||||
@override_settings(MIDDLEWARE=[
|
||||
'middleware_exceptions.middleware.AsyncProcessExceptionMiddleware',
|
||||
])
|
||||
async def test_view_exception_handled_by_process_exception(self):
|
||||
response = await self.async_client.get('/middleware_exceptions/error/')
|
||||
self.assertEqual(response.content, b'Exception caught')
|
||||
|
||||
@override_settings(MIDDLEWARE=[
|
||||
'middleware_exceptions.middleware.AsyncProcessViewMiddleware',
|
||||
])
|
||||
async def test_process_view_return_response(self):
|
||||
response = await self.async_client.get('/middleware_exceptions/view/')
|
||||
self.assertEqual(response.content, b'Processed view normal_view')
|
||||
|
||||
@@ -8,4 +8,9 @@ urlpatterns = [
|
||||
path('middleware_exceptions/permission_denied/', views.permission_denied),
|
||||
path('middleware_exceptions/exception_in_render/', views.exception_in_render),
|
||||
path('middleware_exceptions/template_response/', views.template_response),
|
||||
# Async views.
|
||||
path(
|
||||
'middleware_exceptions/async_exception_in_render/',
|
||||
views.async_exception_in_render,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -27,3 +27,11 @@ def exception_in_render(request):
|
||||
raise Exception('Exception in HttpResponse.render()')
|
||||
|
||||
return CustomHttpResponse('Error')
|
||||
|
||||
|
||||
async def async_exception_in_render(request):
|
||||
class CustomHttpResponse(HttpResponse):
|
||||
async def render(self):
|
||||
raise Exception('Exception in HttpResponse.render()')
|
||||
|
||||
return CustomHttpResponse('Error')
|
||||
|
||||
Reference in New Issue
Block a user