diff --git a/docs/async-await.rst b/docs/async-await.rst new file mode 100644 index 00000000..c8981f88 --- /dev/null +++ b/docs/async-await.rst @@ -0,0 +1,81 @@ +.. _async_await: + +Using ``async`` and ``await`` +============================= + +.. versionadded:: 2.0 + +Routes, error handlers, before request, after request, and teardown +functions can all be coroutine functions if Flask is installed with the +``async`` extra (``pip install flask[async]``). This allows views to be +defined with ``async def`` and use ``await``. + +.. code-block:: python + + @app.route("/get-data") + async def get_data(): + data = await async_db_query(...) + return jsonify(data) + + +Performance +----------- + +Async functions require an event loop to run. Flask, as a WSGI +application, uses one worker to handle one request/response cycle. +When a request comes in to an async view, Flask will start an event loop +in a thread, run the view function there, then return the result. + +Each request still ties up one worker, even for async views. The upside +is that you can run async code within a view, for example to make +multiple concurrent database queries, HTTP requests to an external API, +etc. However, the number of requests your application can handle at one +time will remain the same. + +**Async is not inherently faster than sync code.** Async is beneficial +when performing concurrent IO-bound tasks, but will probably not improve +CPU-bound tasks. Traditional Flask views will still be appropriate for +most use cases, but Flask's async support enables writing and using +code that wasn't possible natively before. + + +When to use Quart instead +------------------------- + +Flask's async support is less performant than async-first frameworks due +to the way it is implemented. If you have a mainly async codebase it +would make sense to consider `Quart`_. Quart is a reimplementation of +Flask based on the `ASGI`_ standard instead of WSGI. This allows it to +handle many concurrent requests, long running requests, and websockets +without requiring individual worker processes or threads. + +It has also already been possible to run Flask with Gevent or Eventlet +to get many of the benefits of async request handling. These libraries +patch low-level Python functions to accomplish this, whereas ``async``/ +``await`` and ASGI use standard, modern Python capabilities. Deciding +whether you should use Flask, Quart, or something else is ultimately up +to understanding the specific needs of your project. + +.. _Quart: https://gitlab.com/pgjones/quart +.. _ASGI: https://asgi.readthedocs.io/en/latest/ + + +Extensions +---------- + +Existing Flask extensions only expect views to be synchronous. If they +provide decorators to add functionality to views, those will probably +not work with async views because they will not await the function or be +awaitable. Other functions they provide will not be awaitable either and +will probably be blocking if called within an async view. + +Check the changelog of the extension you want to use to see if they've +implemented async support, or make a feature request or PR to them. + + +Other event loops +----------------- + +At the moment Flask only supports :mod:`asyncio`. It's possible to +override :meth:`flask.Flask.ensure_sync` to change how async functions +are wrapped to use a different library. diff --git a/docs/async_await.rst b/docs/async_await.rst deleted file mode 100644 index b46fad3b..00000000 --- a/docs/async_await.rst +++ /dev/null @@ -1,46 +0,0 @@ -.. _async_await: - -Using async and await -===================== - -.. versionadded:: 2.0 - -Routes, error handlers, before request, after request, and teardown -functions can all be coroutine functions if Flask is installed with -the ``async`` extra (``pip install flask[async]``). This allows code -such as, - -.. code-block:: python - - @app.route("/") - async def index(): - return await ... - -including the usage of any asyncio based libraries. - - -When to use Quart instead -------------------------- - -Flask's ``async/await`` support is less performant than async first -frameworks due to the way it is implemented. Therefore if you have a -mainly async codebase it would make sense to consider `Quart -`_. Quart is a reimplementation of -the Flask using ``async/await`` based on the ASGI standard (Flask is -based on the WSGI standard). - - -Decorators ----------- - -Decorators designed for Flask, such as those in Flask extensions are -unlikely to work. This is because the decorator will not await the -coroutine function nor will they themselves be awaitable. - - -Other event loops ------------------ - -At the moment Flask only supports asyncio - the -:meth:`flask.Flask.ensure_sync` should be overridden to support -alternative event loops. diff --git a/docs/design.rst b/docs/design.rst index b41a08c2..5d57063e 100644 --- a/docs/design.rst +++ b/docs/design.rst @@ -171,16 +171,23 @@ Also see the :doc:`/becomingbig` section of the documentation for some inspiration for larger applications based on Flask. -Async-await and ASGI support +Async/await and ASGI support ---------------------------- -Flask supports ``async`` coroutines for view functions, and certain -others by executing the coroutine on a seperate thread instead of -utilising an event loop on the main thread as an async first (ASGI) -frameworks would. This is necessary for Flask to remain backwards -compatibility with extensions and code built before ``async`` was -introduced into Python. This compromise introduces a performance cost -compared with the ASGI frameworks, due to the overhead of the threads. +Flask supports ``async`` coroutines for view functions by executing the +coroutine on a separate thread instead of using an event loop on the +main thread as an async-first (ASGI) framework would. This is necessary +for Flask to remain backwards compatible with extensions and code built +before ``async`` was introduced into Python. This compromise introduces +a performance cost compared with the ASGI frameworks, due to the +overhead of the threads. + +Due to how tied to WSGI Flask's code is, it's not clear if it's possible +to make the ``Flask`` class support ASGI and WSGI at the same time. Work +is currently being done in Werkzeug to work with ASGI, which may +eventually enable support in Flask as well. + +See :doc:`/async-await` for more discussion. What Flask is, What Flask is Not diff --git a/docs/index.rst b/docs/index.rst index a1c49a90..6ff62529 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -59,7 +59,7 @@ instructions for web development with Flask. patterns/index deploying/index becomingbig - async_await + async-await API Reference diff --git a/src/flask/app.py b/src/flask/app.py index c1743e6e..eefd361a 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -1520,12 +1520,13 @@ class Flask(Scaffold): return False def ensure_sync(self, func): - """Ensure that the returned function is sync and calls the async func. + """Ensure that the function is synchronous for WSGI workers. + Plain ``def`` functions are returned as-is. ``async def`` + functions are wrapped to run and wait for the response. + + Override this method to change how the app runs async views. .. versionadded:: 2.0 - - Override if you wish to change how asynchronous functions are - run. """ if iscoroutinefunction(func): return run_async(func) diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index c4f94a06..d769cd58 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -475,8 +475,8 @@ class Blueprint(Scaffold): """Ensure the function is synchronous. Override if you would like custom async to sync behaviour in - this blueprint. Otherwise :meth:`~flask.Flask..ensure_sync` is - used. + this blueprint. Otherwise the app's + :meth:`~flask.Flask.ensure_sync` is used. .. versionadded:: 2.0 """ diff --git a/src/flask/helpers.py b/src/flask/helpers.py index 3b4377c3..a4d03861 100644 --- a/src/flask/helpers.py +++ b/src/flask/helpers.py @@ -742,9 +742,10 @@ def run_async(func): "Install Flask with the 'async' extra in order to use async views." ) + # Check that Werkzeug isn't using its fallback ContextVar class. if ContextVar.__module__ == "werkzeug.local": raise RuntimeError( - "async cannot be used with this combination of Python & Greenlet versions" + "Async cannot be used with this combination of Python & Greenlet versions." ) @wraps(func) @@ -763,9 +764,9 @@ def run_async(func): async def inner(*a, **k): """This restores the context before awaiting the func. - This is required as the func must be awaited within the - context. Simply calling func (as per the - copy_current_xxx_context functions) doesn't work as the + This is required as the function must be awaited within the + context. Only calling ``func`` (as per the + ``copy_current_xxx_context`` functions) doesn't work as the with block will close before the coroutine is awaited. """ if ctx is not None: