From 0ce270d1f3615f222226fb316e505a60c1e3baab Mon Sep 17 00:00:00 2001 From: laggardkernel Date: Wed, 12 May 2021 05:44:00 +0800 Subject: [PATCH 01/13] Update doc about minimal Python version for async support --- docs/async-await.rst | 3 ++- docs/installation.rst | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/async-await.rst b/docs/async-await.rst index 34751d47..ad7cbf38 100644 --- a/docs/async-await.rst +++ b/docs/async-await.rst @@ -7,7 +7,8 @@ Using ``async`` and ``await`` 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 +``async`` extra (``pip install flask[async]``). It requires Python 3.7+ +where ``contextvars.ContextVar`` is available. This allows views to be defined with ``async def`` and use ``await``. .. code-block:: python diff --git a/docs/installation.rst b/docs/installation.rst index aef7df0c..a5d105f7 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -8,6 +8,8 @@ Python Version We recommend using the latest version of Python. Flask supports Python 3.6 and newer. +``async`` support in Flask requires Python 3.7+ for ``contextvars.ContextVar``. + Dependencies ------------ From 491ea32803cb05fb8fc4dfb81ec6c96f94529eb0 Mon Sep 17 00:00:00 2001 From: laggardkernel Date: Tue, 1 Jun 2021 13:43:28 +0800 Subject: [PATCH 02/13] Optimize loop in Flask._find_error_handler() --- src/flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/flask/app.py b/src/flask/app.py index cacb40a5..f6656458 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -1276,7 +1276,7 @@ class Flask(Scaffold): """ exc_class, code = self._get_exc_class_and_code(type(e)) - for c in [code, None]: + for c in [code, None] if code is not None else [None]: for name in chain(request.blueprints, [None]): handler_map = self.error_handler_spec[name][c] From 270eb2df2a1606383f50ff079a32240966b43fbc Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Fri, 28 May 2021 00:01:48 +0100 Subject: [PATCH 03/13] Support View and MethodView instances with async handlers --- CHANGES.rst | 1 + docs/async-await.rst | 6 ++++++ src/flask/views.py | 5 +++-- tests/test_async.py | 25 ++++++++++++++++++++++++- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 7920e5ab..be263970 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,6 +10,7 @@ Unreleased decorators. :issue:`4104` - Fixed the issue where typing requires template global decorators to accept functions with no arguments. :issue:`4098` +- Support View and MethodView instances with async handlers. :issue:`4112` Version 2.0.1 diff --git a/docs/async-await.rst b/docs/async-await.rst index ad7cbf38..71e5f452 100644 --- a/docs/async-await.rst +++ b/docs/async-await.rst @@ -18,6 +18,12 @@ defined with ``async def`` and use ``await``. data = await async_db_query(...) 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 Python 3.8 has a bug related to asyncio on Windows. If you encounter diff --git a/src/flask/views.py b/src/flask/views.py index 339ffa18..1bd5c68b 100644 --- a/src/flask/views.py +++ b/src/flask/views.py @@ -1,5 +1,6 @@ import typing as t +from .globals import current_app from .globals import request from .typing import ResponseReturnValue @@ -80,7 +81,7 @@ class View: def view(*args: t.Any, **kwargs: t.Any) -> ResponseReturnValue: 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: view.__name__ = name @@ -154,4 +155,4 @@ class MethodView(View, metaclass=MethodViewType): meth = getattr(self, "get", None) assert meth is not None, f"Unimplemented method {request.method!r}" - return meth(*args, **kwargs) + return current_app.ensure_sync(meth)(*args, **kwargs) diff --git a/tests/test_async.py b/tests/test_async.py index 26a91118..344e9fe6 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -6,6 +6,8 @@ import pytest from flask import Blueprint from flask import Flask from flask import request +from flask.views import MethodView +from flask.views import View pytest.importorskip("asgiref") @@ -18,6 +20,24 @@ class BlueprintError(Exception): 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") def _async_app(): app = Flask(__name__) @@ -53,11 +73,14 @@ def _async_app(): 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 @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): test_client = async_app.test_client() response = test_client.get(path) From 5205cd4ea979f6c322e4e6a256a72e7808592818 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Jun 2021 17:56:33 +0000 Subject: [PATCH 04/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_async.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_async.py b/tests/test_async.py index 344e9fe6..8276c4a8 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -31,11 +31,11 @@ class AsyncView(View): class AsyncMethodView(MethodView): async def get(self): await asyncio.sleep(0) - return 'GET' + return "GET" async def post(self): await asyncio.sleep(0) - return 'POST' + return "POST" @pytest.fixture(name="async_app") @@ -73,8 +73,8 @@ def _async_app(): 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')) + app.add_url_rule("/view", view_func=AsyncView.as_view("view")) + app.add_url_rule("/methodview", view_func=AsyncMethodView.as_view("methodview")) return app From 6a4e7e948d7e5bd5b2b76c1b7f1a0392644bee1e Mon Sep 17 00:00:00 2001 From: Pascal Corpet Date: Mon, 31 May 2021 12:28:44 +0200 Subject: [PATCH 05/13] improve typing for `app.errorhandler` decorator --- CHANGES.rst | 1 + src/flask/app.py | 6 ++++-- src/flask/blueprints.py | 6 ++++-- src/flask/scaffold.py | 27 +++++++++++++++++++-------- src/flask/typing.py | 11 ++++++++++- 5 files changed, 38 insertions(+), 13 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index be263970..d8dd974f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -11,6 +11,7 @@ Unreleased - Fixed the issue where typing requires template global decorators to accept functions with no arguments. :issue:`4098` - Support View and MethodView instances with async handlers. :issue:`4112` +- Enhance typing of ``app.errorhandler`` decorator. :issue:`4095` Version 2.0.1 diff --git a/src/flask/app.py b/src/flask/app.py index f6656458..22cc9abc 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -61,7 +61,6 @@ from .templating import Environment from .typing import AfterRequestCallable from .typing import BeforeFirstRequestCallable from .typing import BeforeRequestCallable -from .typing import ErrorHandlerCallable from .typing import ResponseReturnValue from .typing import TeardownCallable from .typing import TemplateContextProcessorCallable @@ -78,6 +77,7 @@ if t.TYPE_CHECKING: from .blueprints import Blueprint from .testing import FlaskClient from .testing import FlaskCliRunner + from .typing import ErrorHandlerCallable if sys.version_info >= (3, 8): iscoroutinefunction = inspect.iscoroutinefunction @@ -1268,7 +1268,9 @@ class Flask(Scaffold): self.shell_context_processors.append(f) return f - def _find_error_handler(self, e: Exception) -> t.Optional[ErrorHandlerCallable]: + def _find_error_handler( + self, e: Exception + ) -> t.Optional["ErrorHandlerCallable[Exception]"]: """Return a registered error handler for an exception in this order: blueprint handler for a specific code, app handler for a specific code, blueprint handler for an exception class, app handler for an exception diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index 883fc2ff..8241420b 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -8,7 +8,6 @@ from .scaffold import Scaffold from .typing import AfterRequestCallable from .typing import BeforeFirstRequestCallable from .typing import BeforeRequestCallable -from .typing import ErrorHandlerCallable from .typing import TeardownCallable from .typing import TemplateContextProcessorCallable from .typing import TemplateFilterCallable @@ -19,6 +18,7 @@ from .typing import URLValuePreprocessorCallable if t.TYPE_CHECKING: from .app import Flask + from .typing import ErrorHandlerCallable DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable] @@ -581,7 +581,9 @@ class Blueprint(Scaffold): handler is used for all requests, even if outside of the blueprint. """ - def decorator(f: ErrorHandlerCallable) -> ErrorHandlerCallable: + def decorator( + f: "ErrorHandlerCallable[Exception]", + ) -> "ErrorHandlerCallable[Exception]": self.record_once(lambda s: s.app.errorhandler(code)(f)) return f diff --git a/src/flask/scaffold.py b/src/flask/scaffold.py index 239bc46a..e80e1915 100644 --- a/src/flask/scaffold.py +++ b/src/flask/scaffold.py @@ -21,7 +21,7 @@ from .templating import _default_template_ctx_processor from .typing import AfterRequestCallable from .typing import AppOrBlueprintKey from .typing import BeforeRequestCallable -from .typing import ErrorHandlerCallable +from .typing import GenericException from .typing import TeardownCallable from .typing import TemplateContextProcessorCallable from .typing import URLDefaultCallable @@ -29,6 +29,7 @@ from .typing import URLValuePreprocessorCallable if t.TYPE_CHECKING: from .wrappers import Response + from .typing import ErrorHandlerCallable # a singleton sentinel value for parameter defaults _sentinel = object() @@ -144,7 +145,10 @@ class Scaffold: #: directly and its format may change at any time. self.error_handler_spec: t.Dict[ AppOrBlueprintKey, - t.Dict[t.Optional[int], t.Dict[t.Type[Exception], ErrorHandlerCallable]], + t.Dict[ + t.Optional[int], + t.Dict[t.Type[Exception], "ErrorHandlerCallable[Exception]"], + ], ] = defaultdict(lambda: defaultdict(dict)) #: A data structure of functions to call at the beginning of @@ -643,8 +647,11 @@ class Scaffold: @setupmethod def errorhandler( - self, code_or_exception: t.Union[t.Type[Exception], int] - ) -> t.Callable[[ErrorHandlerCallable], ErrorHandlerCallable]: + self, code_or_exception: t.Union[t.Type[GenericException], int] + ) -> t.Callable[ + ["ErrorHandlerCallable[GenericException]"], + "ErrorHandlerCallable[GenericException]", + ]: """Register a function to handle errors by code or exception class. A decorator that is used to register a function given an @@ -674,7 +681,9 @@ class Scaffold: an arbitrary exception """ - def decorator(f: ErrorHandlerCallable) -> ErrorHandlerCallable: + def decorator( + f: "ErrorHandlerCallable[GenericException]", + ) -> "ErrorHandlerCallable[GenericException]": self.register_error_handler(code_or_exception, f) return f @@ -683,8 +692,8 @@ class Scaffold: @setupmethod def register_error_handler( self, - code_or_exception: t.Union[t.Type[Exception], int], - f: ErrorHandlerCallable, + code_or_exception: t.Union[t.Type[GenericException], int], + f: "ErrorHandlerCallable[GenericException]", ) -> None: """Alternative error attach function to the :meth:`errorhandler` decorator that is more straightforward to use for non decorator @@ -708,7 +717,9 @@ class Scaffold: " instead." ) - self.error_handler_spec[None][code][exc_class] = f + self.error_handler_spec[None][code][exc_class] = t.cast( + "ErrorHandlerCallable[Exception]", f + ) @staticmethod def _get_exc_class_and_code( diff --git a/src/flask/typing.py b/src/flask/typing.py index b1a6cbdc..f1c84670 100644 --- a/src/flask/typing.py +++ b/src/flask/typing.py @@ -33,11 +33,12 @@ ResponseReturnValue = t.Union[ "WSGIApplication", ] +GenericException = t.TypeVar("GenericException", bound=Exception, contravariant=True) + AppOrBlueprintKey = t.Optional[str] # The App key is None, whereas blueprints are named AfterRequestCallable = t.Callable[["Response"], "Response"] BeforeFirstRequestCallable = t.Callable[[], None] BeforeRequestCallable = t.Callable[[], t.Optional[ResponseReturnValue]] -ErrorHandlerCallable = t.Callable[[Exception], ResponseReturnValue] TeardownCallable = t.Callable[[t.Optional[BaseException]], None] TemplateContextProcessorCallable = t.Callable[[], t.Dict[str, t.Any]] TemplateFilterCallable = t.Callable[..., t.Any] @@ -45,3 +46,11 @@ TemplateGlobalCallable = t.Callable[..., t.Any] TemplateTestCallable = t.Callable[..., bool] URLDefaultCallable = t.Callable[[str, dict], None] URLValuePreprocessorCallable = t.Callable[[t.Optional[str], t.Optional[dict]], None] + + +if t.TYPE_CHECKING: + import typing_extensions as te + + class ErrorHandlerCallable(te.Protocol[GenericException]): + def __call__(self, error: GenericException) -> ResponseReturnValue: + ... From 63893a427bd022d192febadad0b31ae06fa80776 Mon Sep 17 00:00:00 2001 From: pgjones Date: Sun, 6 Jun 2021 11:09:03 +0100 Subject: [PATCH 06/13] Improve the changelog entry The fix to the teardown_request also applies to all teardown_* methods. --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index d8dd974f..c27b623f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,7 @@ Version 2.0.2 Unreleased -- Fix type annotation for ``teardown_request``. :issue:`4093` +- Fix type annotation for ``teardown_*`` methods. :issue:`4093` - Fix type annotation for ``before_request`` and ``before_app_request`` decorators. :issue:`4104` - Fixed the issue where typing requires template global From 92bed6619459da93f79e85ec674b24cb6fa322f6 Mon Sep 17 00:00:00 2001 From: Hugo Montenegro Date: Tue, 8 Jun 2021 19:01:07 +0200 Subject: [PATCH 07/13] Update celery.rst small typo --- docs/patterns/celery.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/celery.rst b/docs/patterns/celery.rst index e1f6847e..38a9a025 100644 --- a/docs/patterns/celery.rst +++ b/docs/patterns/celery.rst @@ -64,7 +64,7 @@ An example task Let's write a task that adds two numbers together and returns the result. We configure Celery's broker and backend to use Redis, create a ``celery`` -application using the factor from above, and then use it to define the task. :: +application using the factory from above, and then use it to define the task. :: from flask import Flask From 34027d8d876c140f84e18d6b56c0aecc5481874c Mon Sep 17 00:00:00 2001 From: Grey Li Date: Mon, 14 Jun 2021 14:20:04 +0800 Subject: [PATCH 08/13] Improve the contributing guide --- CONTRIBUTING.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 3a9177a4..a3c8b851 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -12,14 +12,16 @@ to address bugs and feature requests in Flask itself. Use one of the following resources for questions about using Flask or issues with your own code: -- The ``#get-help`` channel on our Discord chat: +- The ``#questions`` channel on our Discord chat: https://discord.gg/pallets - The mailing list flask@python.org for long term discussion or larger issues. - Ask on `Stack Overflow`_. Search with Google first using: ``site:stackoverflow.com flask {search term, exception message, etc.}`` +- Ask on our `GitHub Discussions`_. .. _Stack Overflow: https://stackoverflow.com/questions/tagged/flask?tab=Frequent +.. _GitHub Discussions: https://github.com/pallets/flask/discussions Reporting issues @@ -92,7 +94,7 @@ First time setup .. code-block:: text - git remote add fork https://github.com/{username}/flask + $ git remote add fork https://github.com/{username}/flask - Create a virtualenv. From a44c7228600044f530856a33c7da147c528644ff Mon Sep 17 00:00:00 2001 From: pgjones Date: Sat, 5 Jun 2021 16:08:51 +0100 Subject: [PATCH 09/13] Fix registering a blueprint twice with differing names Previously the blueprint recorded aspects (before request, after request etc) would only be added to the app if it was the first registration of the blueprint instance. However only the record-once aspects (app-before requests, app-after request) should be added once on registration of the instance, whereas everything else should be added on every unique name registration. This ensures that these trigger under the new name as well as the old. --- CHANGES.rst | 1 + src/flask/blueprints.py | 8 +++++--- tests/test_blueprints.py | 10 ++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index c27b623f..bdfa9f01 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,6 +12,7 @@ Unreleased decorators to accept functions with no arguments. :issue:`4098` - Support View and MethodView instances with async handlers. :issue:`4112` - Enhance typing of ``app.errorhandler`` decorator. :issue:`4095` +- Fix registering a blueprint twice with differing names. :issue:`4124` Version 2.0.1 diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index 8241420b..c8ce67a4 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -293,7 +293,6 @@ class Blueprint(Scaffold): Registering the same blueprint with the same name multiple times is deprecated and will become an error in Flask 2.1. """ - first_registration = not any(bp is self for bp in app.blueprints.values()) name_prefix = options.get("name_prefix", "") self_name = options.get("name", self.name) name = f"{name_prefix}.{self_name}".lstrip(".") @@ -318,9 +317,12 @@ class Blueprint(Scaffold): stacklevel=4, ) + first_bp_registration = not any(bp is self for bp in app.blueprints.values()) + first_name_registration = name not in app.blueprints + app.blueprints[name] = self self._got_registered_once = True - state = self.make_setup_state(app, options, first_registration) + state = self.make_setup_state(app, options, first_bp_registration) if self.has_static_folder: state.add_url_rule( @@ -330,7 +332,7 @@ class Blueprint(Scaffold): ) # Merge blueprint data into parent. - if first_registration: + if first_bp_registration or first_name_registration: def extend(bp_dict, parent_dict): for key, values in bp_dict.items(): diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index 088ad779..a124c612 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -899,6 +899,14 @@ def test_blueprint_renaming(app, client) -> None: def index(): return flask.request.endpoint + @bp.get("/error") + def error(): + flask.abort(403) + + @bp.errorhandler(403) + def forbidden(_: Exception): + return "Error", 403 + @bp2.get("/") def index2(): return flask.request.endpoint @@ -911,3 +919,5 @@ def test_blueprint_renaming(app, client) -> None: assert client.get("/b/").data == b"alt.index" assert client.get("/a/a/").data == b"bp.sub.index2" assert client.get("/b/a/").data == b"alt.sub.index2" + assert client.get("/a/error").data == b"Error" + assert client.get("/b/error").data == b"Error" From f353d126d199071c7933e2f62409e5532333bec5 Mon Sep 17 00:00:00 2001 From: Frank Yu <1032897296@qq.com> Date: Fri, 18 Jun 2021 10:10:29 +0800 Subject: [PATCH 10/13] Update docs of rendering templates (#4153) * Update docs of rendering templates * Improve the grammar Co-authored-by: Grey Li --- docs/quickstart.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 5cd59f41..b5071ab0 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -444,9 +444,9 @@ Here is an example template:

Hello, World!

{% endif %} -Inside templates you also have access to the :class:`~flask.request`, -:class:`~flask.session` and :class:`~flask.g` [#]_ objects -as well as the :func:`~flask.get_flashed_messages` function. +Inside templates you also have access to the :data:`~flask.Flask.config`, +:class:`~flask.request`, :class:`~flask.session` and :class:`~flask.g` [#]_ objects +as well as the :func:`~flask.url_for` and :func:`~flask.get_flashed_messages` functions. Templates are especially useful if inheritance is used. If you want to know how that works, see :doc:`patterns/templateinheritance`. Basically From 5fa7d2efe757e028a734d3dc56e9f13bb812a92a Mon Sep 17 00:00:00 2001 From: Frank Yu Date: Fri, 18 Jun 2021 20:15:02 +0800 Subject: [PATCH 11/13] Update templating.rst --- docs/templating.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/templating.rst b/docs/templating.rst index b0964df8..dcc757c3 100644 --- a/docs/templating.rst +++ b/docs/templating.rst @@ -37,7 +37,7 @@ by default: .. data:: config :noindex: - The current configuration object (:data:`flask.config`) + The current configuration object (:data:`flask.Flask.config`) .. versionadded:: 0.6 From 7b696e076a8ebf22e8ad096b7278051d475c88be Mon Sep 17 00:00:00 2001 From: Frank Yu Date: Fri, 18 Jun 2021 22:18:15 +0800 Subject: [PATCH 12/13] Update testing.rst --- docs/testing.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/testing.rst b/docs/testing.rst index 2fedc600..73230b4b 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -48,20 +48,21 @@ the application for testing and initializes a new database:: import pytest from flaskr import create_app + from flaskr.db import init_db @pytest.fixture def client(): - db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() - flaskr.app.config['TESTING'] = True + db_fd, db_path = tempfile.mkstemp() + app = create_app({'TESTING': True, 'DATABASE': db_path}) - with flaskr.app.test_client() as client: - with flaskr.app.app_context(): - flaskr.init_db() + with app.test_client() as client: + with app.app_context(): + init_db() yield client os.close(db_fd) - os.unlink(flaskr.app.config['DATABASE']) + os.unlink(db_path) This client fixture will be called by each individual test. It gives us a simple interface to the application, where we can trigger test requests to the From 35eb582bf3ddbe995681363167eb3233f539ef8b Mon Sep 17 00:00:00 2001 From: Frank Yu Date: Sun, 20 Jun 2021 23:20:14 +0800 Subject: [PATCH 13/13] Change flask.xxx to plain version in testing docs --- docs/testing.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/testing.rst b/docs/testing.rst index 73230b4b..061d46d2 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -225,13 +225,13 @@ temporarily. With this you can access the :class:`~flask.request`, :class:`~flask.g` and :class:`~flask.session` objects like in view functions. Here is a full example that demonstrates this approach:: - import flask + from flask import Flask, request - app = flask.Flask(__name__) + app = Flask(__name__) with app.test_request_context('/?name=Peter'): - assert flask.request.path == '/' - assert flask.request.args['name'] == 'Peter' + assert request.path == '/' + assert request.args['name'] == 'Peter' All the other objects that are context bound can be used in the same way. @@ -248,7 +248,7 @@ the test request context leaves the ``with`` block. If you do want the :meth:`~flask.Flask.before_request` functions to be called as well, you need to call :meth:`~flask.Flask.preprocess_request` yourself:: - app = flask.Flask(__name__) + app = Flask(__name__) with app.test_request_context('/?name=Peter'): app.preprocess_request() @@ -261,7 +261,7 @@ If you want to call the :meth:`~flask.Flask.after_request` functions you need to call into :meth:`~flask.Flask.process_response` which however requires that you pass it a response object:: - app = flask.Flask(__name__) + app = Flask(__name__) with app.test_request_context('/?name=Peter'): resp = Response('...') @@ -330,7 +330,7 @@ context around for a little longer so that additional introspection can happen. With Flask 0.4 this is possible by using the :meth:`~flask.Flask.test_client` with a ``with`` block:: - app = flask.Flask(__name__) + app = Flask(__name__) with app.test_client() as c: rv = c.get('/?tequila=42') @@ -354,7 +354,7 @@ keep the context around and access :data:`flask.session`:: with app.test_client() as c: rv = c.get('/') - assert flask.session['foo'] == 42 + assert session['foo'] == 42 This however does not make it possible to also modify the session or to access the session before a request was fired. Starting with Flask 0.8 we