From 1fc69bd3f4b67cbb36406adc7ae47ef439b8eda8 Mon Sep 17 00:00:00 2001 From: Dale Kube Date: Sun, 1 Mar 2026 19:22:43 -0600 Subject: [PATCH] test and documentation --- CHANGES.rst | 2 ++ docs/async-await.rst | 4 ++++ src/flask/app.py | 8 ++++++++ tests/test_basic.py | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index a5fa63f1..7456e9b4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,8 @@ Version 3.2.0 Unreleased +- Optimize request dispatching by caching the result of ``ensure_sync`` for view + functions in ``_sync_view_functions``, reducing CPU overhead. :issue:`9999` - Drop support for Python 3.9. :pr:`5730` - Remove previously deprecated code: ``__version__``. :pr:`5648` - ``RequestContext`` has merged with ``AppContext``. ``RequestContext`` is now diff --git a/docs/async-await.rst b/docs/async-await.rst index bb333802..ddba6023 100644 --- a/docs/async-await.rst +++ b/docs/async-await.rst @@ -107,6 +107,10 @@ the decorated function, return wrapper +To improve performance, consider caching the result of ``ensure_sync`` if your +extension calls it frequently on the same function. This is how Flask internally +optimizes request dispatching. + 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. diff --git a/src/flask/app.py b/src/flask/app.py index 600fc542..04e6e1a3 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -971,6 +971,10 @@ class Flask(App): be a response object. In order to convert the return value to a proper response object, call :func:`make_response`. + .. versionchanged:: 3.2 + The result of ``ensure_sync`` is cached in ``_sync_view_functions`` + to improve performance. + .. versionchanged:: 0.7 This no longer does the exception handling, this code was moved to the new :meth:`full_dispatch_request`. @@ -1077,6 +1081,10 @@ class Flask(App): Override this method to change how the app runs async views. + .. versionchanged:: 3.2 + The result of this method is cached during request dispatching + to improve performance. + .. versionadded:: 2.0 """ if iscoroutinefunction(func): diff --git a/tests/test_basic.py b/tests/test_basic.py index 1d9d83f8..bf8ed27a 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1968,3 +1968,37 @@ def test_app_freed_on_zero_refcount(): assert weak() is None finally: gc.enable() + + +def test_sync_view_functions_cache(app, client): + """Test that the _sync_view_functions cache is populated and used.""" + @app.route("/test") + def test_view(): + return "Hello" + + import unittest.mock + + with unittest.mock.patch.object(app, 'ensure_sync', wraps=app.ensure_sync) as mock_ensure_sync: + # First request should call ensure_sync + response = client.get("/test") + assert response.status_code == 200 + assert mock_ensure_sync.call_count == 1 + + # Second request should hit the cache and not call ensure_sync + response = client.get("/test") + assert response.status_code == 200 + assert mock_ensure_sync.call_count == 1 + + # Direct mutation test (to verify the cache is bound to endpoint) + def new_view(): + return "World" + + # Simulating a user directly updating the view functions after setup + # Because it's already cached, this mutation won't affect _sync_view_functions + app.view_functions["test"] = new_view + + # Ensure that it still returns the old result due to the cache + response = client.get("/test") + assert response.status_code == 200 + assert response.data == b"Hello" +