Support View and MethodView instances with async handlers

This commit is contained in:
Miguel Grinberg 2021-05-28 00:01:48 +01:00 committed by Phil Jones
parent 491ea32803
commit 270eb2df2a
4 changed files with 34 additions and 3 deletions

View file

@ -10,6 +10,7 @@ Unreleased
decorators. :issue:`4104` decorators. :issue:`4104`
- Fixed the issue where typing requires template global - Fixed the issue where typing requires template global
decorators to accept functions with no arguments. :issue:`4098` decorators to accept functions with no arguments. :issue:`4098`
- Support View and MethodView instances with async handlers. :issue:`4112`
Version 2.0.1 Version 2.0.1

View file

@ -18,6 +18,12 @@ defined with ``async def`` and use ``await``.
data = await async_db_query(...) data = await async_db_query(...)
return jsonify(data) return jsonify(data)
Pluggable class-based views also support handlers that are implemented as
coroutines. This applies to the :meth:`~flask.views.View.dispatch_request`
method in views that inherit from the :class:`flask.views.View` class, as
well as all the HTTP method handlers in views that inherit from the
:class:`flask.views.MethodView` class.
.. admonition:: Using ``async`` on Windows on Python 3.8 .. admonition:: Using ``async`` on Windows on Python 3.8
Python 3.8 has a bug related to asyncio on Windows. If you encounter Python 3.8 has a bug related to asyncio on Windows. If you encounter

View file

@ -1,5 +1,6 @@
import typing as t import typing as t
from .globals import current_app
from .globals import request from .globals import request
from .typing import ResponseReturnValue from .typing import ResponseReturnValue
@ -80,7 +81,7 @@ class View:
def view(*args: t.Any, **kwargs: t.Any) -> ResponseReturnValue: def view(*args: t.Any, **kwargs: t.Any) -> ResponseReturnValue:
self = view.view_class(*class_args, **class_kwargs) # type: ignore self = view.view_class(*class_args, **class_kwargs) # type: ignore
return self.dispatch_request(*args, **kwargs) return current_app.ensure_sync(self.dispatch_request)(*args, **kwargs)
if cls.decorators: if cls.decorators:
view.__name__ = name view.__name__ = name
@ -154,4 +155,4 @@ class MethodView(View, metaclass=MethodViewType):
meth = getattr(self, "get", None) meth = getattr(self, "get", None)
assert meth is not None, f"Unimplemented method {request.method!r}" assert meth is not None, f"Unimplemented method {request.method!r}"
return meth(*args, **kwargs) return current_app.ensure_sync(meth)(*args, **kwargs)

View file

@ -6,6 +6,8 @@ import pytest
from flask import Blueprint from flask import Blueprint
from flask import Flask from flask import Flask
from flask import request from flask import request
from flask.views import MethodView
from flask.views import View
pytest.importorskip("asgiref") pytest.importorskip("asgiref")
@ -18,6 +20,24 @@ class BlueprintError(Exception):
pass pass
class AsyncView(View):
methods = ["GET", "POST"]
async def dispatch_request(self):
await asyncio.sleep(0)
return request.method
class AsyncMethodView(MethodView):
async def get(self):
await asyncio.sleep(0)
return 'GET'
async def post(self):
await asyncio.sleep(0)
return 'POST'
@pytest.fixture(name="async_app") @pytest.fixture(name="async_app")
def _async_app(): def _async_app():
app = Flask(__name__) app = Flask(__name__)
@ -53,11 +73,14 @@ def _async_app():
app.register_blueprint(blueprint, url_prefix="/bp") app.register_blueprint(blueprint, url_prefix="/bp")
app.add_url_rule('/view', view_func=AsyncView.as_view('view'))
app.add_url_rule('/methodview', view_func=AsyncMethodView.as_view('methodview'))
return app return app
@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires Python >= 3.7") @pytest.mark.skipif(sys.version_info < (3, 7), reason="requires Python >= 3.7")
@pytest.mark.parametrize("path", ["/", "/home", "/bp/"]) @pytest.mark.parametrize("path", ["/", "/home", "/bp/", "/view", "/methodview"])
def test_async_route(path, async_app): def test_async_route(path, async_app):
test_client = async_app.test_client() test_client = async_app.test_client()
response = test_client.get(path) response = test_client.get(path)