update changes.rst
This commit is contained in:
commit
9287b792c4
20 changed files with 199 additions and 114 deletions
|
|
@ -2,7 +2,7 @@ ci:
|
||||||
autoupdate_schedule: monthly
|
autoupdate_schedule: monthly
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v2.25.0
|
rev: v2.29.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: ["--py36-plus"]
|
args: ["--py36-plus"]
|
||||||
|
|
@ -14,7 +14,7 @@ repos:
|
||||||
files: "^(?!examples/)"
|
files: "^(?!examples/)"
|
||||||
args: ["--application-directories", "src"]
|
args: ["--application-directories", "src"]
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 21.8b0
|
rev: 21.9b0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
- repo: https://github.com/PyCQA/flake8
|
- repo: https://github.com/PyCQA/flake8
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Version 2.0.2
|
Version 2.0.2
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
Unreleased
|
Released 2021-10-04
|
||||||
|
|
||||||
- Fix type annotation for ``teardown_*`` methods. :issue:`4093`
|
- Fix type annotation for ``teardown_*`` methods. :issue:`4093`
|
||||||
- Fix type annotation for ``before_request`` and ``before_app_request``
|
- Fix type annotation for ``before_request`` and ``before_app_request``
|
||||||
|
|
@ -21,6 +21,9 @@ Unreleased
|
||||||
:issue:`4096`
|
:issue:`4096`
|
||||||
- The CLI loader handles ``**kwargs`` in a ``create_app`` function.
|
- The CLI loader handles ``**kwargs`` in a ``create_app`` function.
|
||||||
:issue:`4170`
|
:issue:`4170`
|
||||||
|
- Fix the order of ``before_request`` and other callbacks that trigger
|
||||||
|
before the view returns. They are called from the app down to the
|
||||||
|
closest nested blueprint. :issue:`4229`
|
||||||
- Fix Unable to extend FlaskClient with follow_redirects. :issue:`3396`
|
- Fix Unable to extend FlaskClient with follow_redirects. :issue:`3396`
|
||||||
|
|
||||||
Version 2.0.1
|
Version 2.0.1
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ following conditions are met:
|
||||||
1. Redistributions of source code must retain the above copyright
|
1. Redistributions of source code must retain the above copyright
|
||||||
notice and this list of conditions.
|
notice and this list of conditions.
|
||||||
|
|
||||||
3. Neither the name of the copyright holder nor the names of its
|
2. Neither the name of the copyright holder nor the names of its
|
||||||
contributors may be used to endorse or promote products derived from
|
contributors may be used to endorse or promote products derived from
|
||||||
this software without specific prior written permission.
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ a request, CLI command, or other activity. Rather than passing the
|
||||||
application around to each function, the :data:`current_app` and
|
application around to each function, the :data:`current_app` and
|
||||||
:data:`g` proxies are accessed instead.
|
:data:`g` proxies are accessed instead.
|
||||||
|
|
||||||
This is similar to the :doc:`/reqcontext`, which keeps track of
|
This is similar to :doc:`/reqcontext`, which keeps track of
|
||||||
request-level data during a request. A corresponding application context
|
request-level data during a request. A corresponding application context
|
||||||
is pushed when a request context is pushed.
|
is pushed when a request context is pushed.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ enables the debugger and reloader.
|
||||||
|
|
||||||
``FLASK_ENV`` can only be set as an environment variable. When running
|
``FLASK_ENV`` can only be set as an environment variable. When running
|
||||||
from Python code, passing ``debug=True`` enables debug mode, which is
|
from Python code, passing ``debug=True`` enables debug mode, which is
|
||||||
mostly equivalent. Debug mode can be controled separately from
|
mostly equivalent. Debug mode can be controlled separately from
|
||||||
``FLASK_ENV`` with the ``FLASK_DEBUG`` environment variable as well.
|
``FLASK_ENV`` with the ``FLASK_DEBUG`` environment variable as well.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ forms.
|
||||||
fun. You can get it from `PyPI
|
fun. You can get it from `PyPI
|
||||||
<https://pypi.org/project/Flask-WTF/>`_.
|
<https://pypi.org/project/Flask-WTF/>`_.
|
||||||
|
|
||||||
.. _Flask-WTF: https://flask-wtf.readthedocs.io/en/stable/
|
.. _Flask-WTF: https://flask-wtf.readthedocs.io/
|
||||||
|
|
||||||
The Forms
|
The Forms
|
||||||
---------
|
---------
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ request. Rather than passing the request object to each function that
|
||||||
runs during a request, the :data:`request` and :data:`session` proxies
|
runs during a request, the :data:`request` and :data:`session` proxies
|
||||||
are accessed instead.
|
are accessed instead.
|
||||||
|
|
||||||
This is similar to the :doc:`/appcontext`, which keeps track of the
|
This is similar to :doc:`/appcontext`, which keeps track of the
|
||||||
application-level data independent of a request. A corresponding
|
application-level data independent of a request. A corresponding
|
||||||
application context is pushed when a request context is pushed.
|
application context is pushed when a request context is pushed.
|
||||||
|
|
||||||
|
|
@ -33,8 +33,8 @@ Lifetime of the Context
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
When a Flask application begins handling a request, it pushes a request
|
When a Flask application begins handling a request, it pushes a request
|
||||||
context, which also pushes an :doc:`/appcontext`. When the request ends
|
context, which also pushes an :doc:`app context </appcontext>`. When the
|
||||||
it pops the request context then the application context.
|
request ends it pops the request context then the application context.
|
||||||
|
|
||||||
The context is unique to each thread (or other worker type).
|
The context is unique to each thread (or other worker type).
|
||||||
:data:`request` cannot be passed to another thread, the other thread
|
:data:`request` cannot be passed to another thread, the other thread
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,7 @@ that these functions are not only there for interactive shell usage, but
|
||||||
also for unit testing and other situations that require a faked request
|
also for unit testing and other situations that require a faked request
|
||||||
context.
|
context.
|
||||||
|
|
||||||
Generally it's recommended that you read the :doc:`reqcontext`
|
Generally it's recommended that you read :doc:`reqcontext` first.
|
||||||
chapter of the documentation first.
|
|
||||||
|
|
||||||
Command Line Interface
|
Command Line Interface
|
||||||
----------------------
|
----------------------
|
||||||
|
|
|
||||||
|
|
@ -22,25 +22,25 @@ cffi==1.14.6
|
||||||
# via cryptography
|
# via cryptography
|
||||||
cfgv==3.3.1
|
cfgv==3.3.1
|
||||||
# via pre-commit
|
# via pre-commit
|
||||||
charset-normalizer==2.0.4
|
charset-normalizer==2.0.6
|
||||||
# via requests
|
# via requests
|
||||||
click==8.0.1
|
click==8.0.1
|
||||||
# via pip-tools
|
# via pip-tools
|
||||||
cryptography==3.4.8
|
cryptography==35.0.0
|
||||||
# via -r requirements/typing.in
|
# via -r requirements/typing.in
|
||||||
distlib==0.3.2
|
distlib==0.3.3
|
||||||
# via virtualenv
|
# via virtualenv
|
||||||
docutils==0.16
|
docutils==0.16
|
||||||
# via
|
# via
|
||||||
# sphinx
|
# sphinx
|
||||||
# sphinx-tabs
|
# sphinx-tabs
|
||||||
filelock==3.0.12
|
filelock==3.2.0
|
||||||
# via
|
# via
|
||||||
# tox
|
# tox
|
||||||
# virtualenv
|
# virtualenv
|
||||||
greenlet==1.1.1
|
greenlet==1.1.2
|
||||||
# via -r requirements/tests.in
|
# via -r requirements/tests.in
|
||||||
identify==2.2.13
|
identify==2.2.15
|
||||||
# via pre-commit
|
# via pre-commit
|
||||||
idna==3.2
|
idna==3.2
|
||||||
# via requests
|
# via requests
|
||||||
|
|
@ -68,9 +68,9 @@ pallets-sphinx-themes==2.0.1
|
||||||
# via -r requirements/docs.in
|
# via -r requirements/docs.in
|
||||||
pep517==0.11.0
|
pep517==0.11.0
|
||||||
# via pip-tools
|
# via pip-tools
|
||||||
pip-tools==6.2.0
|
pip-tools==6.3.0
|
||||||
# via -r requirements/dev.in
|
# via -r requirements/dev.in
|
||||||
platformdirs==2.3.0
|
platformdirs==2.4.0
|
||||||
# via virtualenv
|
# via virtualenv
|
||||||
pluggy==1.0.0
|
pluggy==1.0.0
|
||||||
# via
|
# via
|
||||||
|
|
@ -106,7 +106,7 @@ six==1.16.0
|
||||||
# virtualenv
|
# virtualenv
|
||||||
snowballstemmer==2.1.0
|
snowballstemmer==2.1.0
|
||||||
# via sphinx
|
# via sphinx
|
||||||
sphinx==4.1.2
|
sphinx==4.2.0
|
||||||
# via
|
# via
|
||||||
# -r requirements/docs.in
|
# -r requirements/docs.in
|
||||||
# pallets-sphinx-themes
|
# pallets-sphinx-themes
|
||||||
|
|
@ -139,19 +139,19 @@ toml==0.10.2
|
||||||
# tox
|
# tox
|
||||||
tomli==1.2.1
|
tomli==1.2.1
|
||||||
# via pep517
|
# via pep517
|
||||||
tox==3.24.3
|
tox==3.24.4
|
||||||
# via -r requirements/dev.in
|
# via -r requirements/dev.in
|
||||||
types-contextvars==0.1.4
|
types-contextvars==0.1.4
|
||||||
# via -r requirements/typing.in
|
# via -r requirements/typing.in
|
||||||
types-dataclasses==0.1.7
|
types-dataclasses==0.1.7
|
||||||
# via -r requirements/typing.in
|
# via -r requirements/typing.in
|
||||||
types-setuptools==57.0.2
|
types-setuptools==57.4.0
|
||||||
# via -r requirements/typing.in
|
# via -r requirements/typing.in
|
||||||
typing-extensions==3.10.0.2
|
typing-extensions==3.10.0.2
|
||||||
# via mypy
|
# via mypy
|
||||||
urllib3==1.26.6
|
urllib3==1.26.7
|
||||||
# via requests
|
# via requests
|
||||||
virtualenv==20.7.2
|
virtualenv==20.8.1
|
||||||
# via
|
# via
|
||||||
# pre-commit
|
# pre-commit
|
||||||
# tox
|
# tox
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ babel==2.9.1
|
||||||
# via sphinx
|
# via sphinx
|
||||||
certifi==2021.5.30
|
certifi==2021.5.30
|
||||||
# via requests
|
# via requests
|
||||||
charset-normalizer==2.0.4
|
charset-normalizer==2.0.6
|
||||||
# via requests
|
# via requests
|
||||||
docutils==0.16
|
docutils==0.16
|
||||||
# via
|
# via
|
||||||
|
|
@ -42,7 +42,7 @@ requests==2.26.0
|
||||||
# via sphinx
|
# via sphinx
|
||||||
snowballstemmer==2.1.0
|
snowballstemmer==2.1.0
|
||||||
# via sphinx
|
# via sphinx
|
||||||
sphinx==4.1.2
|
sphinx==4.2.0
|
||||||
# via
|
# via
|
||||||
# -r requirements/docs.in
|
# -r requirements/docs.in
|
||||||
# pallets-sphinx-themes
|
# pallets-sphinx-themes
|
||||||
|
|
@ -67,7 +67,7 @@ sphinxcontrib-qthelp==1.0.3
|
||||||
# via sphinx
|
# via sphinx
|
||||||
sphinxcontrib-serializinghtml==1.1.5
|
sphinxcontrib-serializinghtml==1.1.5
|
||||||
# via sphinx
|
# via sphinx
|
||||||
urllib3==1.26.6
|
urllib3==1.26.7
|
||||||
# via requests
|
# via requests
|
||||||
|
|
||||||
# The following packages are considered to be unsafe in a requirements file:
|
# The following packages are considered to be unsafe in a requirements file:
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ attrs==21.2.0
|
||||||
# via pytest
|
# via pytest
|
||||||
blinker==1.4
|
blinker==1.4
|
||||||
# via -r requirements/tests.in
|
# via -r requirements/tests.in
|
||||||
greenlet==1.1.1
|
greenlet==1.1.2
|
||||||
# via -r requirements/tests.in
|
# via -r requirements/tests.in
|
||||||
iniconfig==1.1.1
|
iniconfig==1.1.1
|
||||||
# via pytest
|
# via pytest
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
#
|
#
|
||||||
cffi==1.14.6
|
cffi==1.14.6
|
||||||
# via cryptography
|
# via cryptography
|
||||||
cryptography==3.4.8
|
cryptography==35.0.0
|
||||||
# via -r requirements/typing.in
|
# via -r requirements/typing.in
|
||||||
mypy==0.910
|
mypy==0.910
|
||||||
# via -r requirements/typing.in
|
# via -r requirements/typing.in
|
||||||
|
|
@ -20,7 +20,7 @@ types-contextvars==0.1.4
|
||||||
# via -r requirements/typing.in
|
# via -r requirements/typing.in
|
||||||
types-dataclasses==0.1.7
|
types-dataclasses==0.1.7
|
||||||
# via -r requirements/typing.in
|
# via -r requirements/typing.in
|
||||||
types-setuptools==57.0.2
|
types-setuptools==57.4.0
|
||||||
# via -r requirements/typing.in
|
# via -r requirements/typing.in
|
||||||
typing-extensions==3.10.0.2
|
typing-extensions==3.10.0.2
|
||||||
# via mypy
|
# via mypy
|
||||||
|
|
|
||||||
|
|
@ -43,4 +43,4 @@ from .signals import template_rendered as template_rendered
|
||||||
from .templating import render_template as render_template
|
from .templating import render_template as render_template
|
||||||
from .templating import render_template_string as render_template_string
|
from .templating import render_template_string as render_template_string
|
||||||
|
|
||||||
__version__ = "2.0.2.dev0"
|
__version__ = "2.0.2"
|
||||||
|
|
|
||||||
124
src/flask/app.py
124
src/flask/app.py
|
|
@ -58,17 +58,12 @@ from .signals import request_started
|
||||||
from .signals import request_tearing_down
|
from .signals import request_tearing_down
|
||||||
from .templating import DispatchingJinjaLoader
|
from .templating import DispatchingJinjaLoader
|
||||||
from .templating import Environment
|
from .templating import Environment
|
||||||
from .typing import AfterRequestCallable
|
|
||||||
from .typing import BeforeFirstRequestCallable
|
from .typing import BeforeFirstRequestCallable
|
||||||
from .typing import BeforeRequestCallable
|
|
||||||
from .typing import ResponseReturnValue
|
from .typing import ResponseReturnValue
|
||||||
from .typing import TeardownCallable
|
from .typing import TeardownCallable
|
||||||
from .typing import TemplateContextProcessorCallable
|
|
||||||
from .typing import TemplateFilterCallable
|
from .typing import TemplateFilterCallable
|
||||||
from .typing import TemplateGlobalCallable
|
from .typing import TemplateGlobalCallable
|
||||||
from .typing import TemplateTestCallable
|
from .typing import TemplateTestCallable
|
||||||
from .typing import URLDefaultCallable
|
|
||||||
from .typing import URLValuePreprocessorCallable
|
|
||||||
from .wrappers import Request
|
from .wrappers import Request
|
||||||
from .wrappers import Response
|
from .wrappers import Response
|
||||||
|
|
||||||
|
|
@ -366,7 +361,8 @@ class Flask(Scaffold):
|
||||||
#: .. versionadded:: 1.1.0
|
#: .. versionadded:: 1.1.0
|
||||||
url_map_class = Map
|
url_map_class = Map
|
||||||
|
|
||||||
#: the test client that is used with when `test_client` is used.
|
#: The :meth:`test_client` method creates an instance of this test
|
||||||
|
#: client class. Defaults to :class:`~flask.testing.FlaskClient`.
|
||||||
#:
|
#:
|
||||||
#: .. versionadded:: 0.7
|
#: .. versionadded:: 0.7
|
||||||
test_client_class: t.Optional[t.Type["FlaskClient"]] = None
|
test_client_class: t.Optional[t.Type["FlaskClient"]] = None
|
||||||
|
|
@ -744,20 +740,21 @@ class Flask(Scaffold):
|
||||||
:param context: the context as a dictionary that is updated in place
|
:param context: the context as a dictionary that is updated in place
|
||||||
to add extra variables.
|
to add extra variables.
|
||||||
"""
|
"""
|
||||||
funcs: t.Iterable[
|
names: t.Iterable[t.Optional[str]] = (None,)
|
||||||
TemplateContextProcessorCallable
|
|
||||||
] = self.template_context_processors[None]
|
# A template may be rendered outside a request context.
|
||||||
reqctx = _request_ctx_stack.top
|
if request:
|
||||||
if reqctx is not None:
|
names = chain(names, reversed(request.blueprints))
|
||||||
for bp in request.blueprints:
|
|
||||||
if bp in self.template_context_processors:
|
# The values passed to render_template take precedence. Keep a
|
||||||
funcs = chain(funcs, self.template_context_processors[bp])
|
# copy to re-apply after all context functions.
|
||||||
orig_ctx = context.copy()
|
orig_ctx = context.copy()
|
||||||
for func in funcs:
|
|
||||||
context.update(func())
|
for name in names:
|
||||||
# make sure the original values win. This makes it possible to
|
if name in self.template_context_processors:
|
||||||
# easier add new variables in context processors without breaking
|
for func in self.template_context_processors[name]:
|
||||||
# existing views.
|
context.update(func())
|
||||||
|
|
||||||
context.update(orig_ctx)
|
context.update(orig_ctx)
|
||||||
|
|
||||||
def make_shell_context(self) -> dict:
|
def make_shell_context(self) -> dict:
|
||||||
|
|
@ -1277,9 +1274,10 @@ class Flask(Scaffold):
|
||||||
class, or ``None`` if a suitable handler is not found.
|
class, or ``None`` if a suitable handler is not found.
|
||||||
"""
|
"""
|
||||||
exc_class, code = self._get_exc_class_and_code(type(e))
|
exc_class, code = self._get_exc_class_and_code(type(e))
|
||||||
|
names = (*request.blueprints, None)
|
||||||
|
|
||||||
for c in [code, None] if code is not None else [None]:
|
for c in (code, None) if code is not None else (None,):
|
||||||
for name in chain(request.blueprints, [None]):
|
for name in names:
|
||||||
handler_map = self.error_handler_spec[name][c]
|
handler_map = self.error_handler_spec[name][c]
|
||||||
|
|
||||||
if not handler_map:
|
if not handler_map:
|
||||||
|
|
@ -1621,7 +1619,7 @@ class Flask(Scaffold):
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Install Flask with the 'async' extra in order to use async views."
|
"Install Flask with the 'async' extra in order to use async views."
|
||||||
)
|
) from None
|
||||||
|
|
||||||
# Check that Werkzeug isn't using its fallback ContextVar class.
|
# Check that Werkzeug isn't using its fallback ContextVar class.
|
||||||
if ContextVar.__module__ == "werkzeug.local":
|
if ContextVar.__module__ == "werkzeug.local":
|
||||||
|
|
@ -1727,7 +1725,7 @@ class Flask(Scaffold):
|
||||||
" response. The return type must be a string,"
|
" response. The return type must be a string,"
|
||||||
" dict, tuple, Response instance, or WSGI"
|
" dict, tuple, Response instance, or WSGI"
|
||||||
f" callable, but it was a {type(rv).__name__}."
|
f" callable, but it was a {type(rv).__name__}."
|
||||||
).with_traceback(sys.exc_info()[2])
|
).with_traceback(sys.exc_info()[2]) from None
|
||||||
else:
|
else:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"The view function did not return a valid"
|
"The view function did not return a valid"
|
||||||
|
|
@ -1799,17 +1797,19 @@ class Flask(Scaffold):
|
||||||
|
|
||||||
.. versionadded:: 0.7
|
.. versionadded:: 0.7
|
||||||
"""
|
"""
|
||||||
funcs: t.Iterable[URLDefaultCallable] = self.url_default_functions[None]
|
names: t.Iterable[t.Optional[str]] = (None,)
|
||||||
|
|
||||||
|
# url_for may be called outside a request context, parse the
|
||||||
|
# passed endpoint instead of using request.blueprints.
|
||||||
if "." in endpoint:
|
if "." in endpoint:
|
||||||
# This is called by url_for, which can be called outside a
|
names = chain(
|
||||||
# request, can't use request.blueprints.
|
names, reversed(_split_blueprint_path(endpoint.rpartition(".")[0]))
|
||||||
bps = _split_blueprint_path(endpoint.rpartition(".")[0])
|
)
|
||||||
bp_funcs = chain.from_iterable(self.url_default_functions[bp] for bp in bps)
|
|
||||||
funcs = chain(funcs, bp_funcs)
|
|
||||||
|
|
||||||
for func in funcs:
|
for name in names:
|
||||||
func(endpoint, values)
|
if name in self.url_default_functions:
|
||||||
|
for func in self.url_default_functions[name]:
|
||||||
|
func(endpoint, values)
|
||||||
|
|
||||||
def handle_url_build_error(
|
def handle_url_build_error(
|
||||||
self, error: Exception, endpoint: str, values: dict
|
self, error: Exception, endpoint: str, values: dict
|
||||||
|
|
@ -1844,24 +1844,20 @@ class Flask(Scaffold):
|
||||||
value is handled as if it was the return value from the view, and
|
value is handled as if it was the return value from the view, and
|
||||||
further request handling is stopped.
|
further request handling is stopped.
|
||||||
"""
|
"""
|
||||||
|
names = (None, *reversed(request.blueprints))
|
||||||
|
|
||||||
funcs: t.Iterable[URLValuePreprocessorCallable] = self.url_value_preprocessors[
|
for name in names:
|
||||||
None
|
if name in self.url_value_preprocessors:
|
||||||
]
|
for url_func in self.url_value_preprocessors[name]:
|
||||||
for bp in request.blueprints:
|
url_func(request.endpoint, request.view_args)
|
||||||
if bp in self.url_value_preprocessors:
|
|
||||||
funcs = chain(funcs, self.url_value_preprocessors[bp])
|
|
||||||
for func in funcs:
|
|
||||||
func(request.endpoint, request.view_args)
|
|
||||||
|
|
||||||
funcs: t.Iterable[BeforeRequestCallable] = self.before_request_funcs[None]
|
for name in names:
|
||||||
for bp in request.blueprints:
|
if name in self.before_request_funcs:
|
||||||
if bp in self.before_request_funcs:
|
for before_func in self.before_request_funcs[name]:
|
||||||
funcs = chain(funcs, self.before_request_funcs[bp])
|
rv = self.ensure_sync(before_func)()
|
||||||
for func in funcs:
|
|
||||||
rv = self.ensure_sync(func)()
|
if rv is not None:
|
||||||
if rv is not None:
|
return rv
|
||||||
return rv
|
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -1879,16 +1875,18 @@ class Flask(Scaffold):
|
||||||
instance of :attr:`response_class`.
|
instance of :attr:`response_class`.
|
||||||
"""
|
"""
|
||||||
ctx = _request_ctx_stack.top
|
ctx = _request_ctx_stack.top
|
||||||
funcs: t.Iterable[AfterRequestCallable] = ctx._after_request_functions
|
|
||||||
for bp in request.blueprints:
|
for func in ctx._after_request_functions:
|
||||||
if bp in self.after_request_funcs:
|
response = self.ensure_sync(func)(response)
|
||||||
funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
|
|
||||||
if None in self.after_request_funcs:
|
for name in chain(request.blueprints, (None,)):
|
||||||
funcs = chain(funcs, reversed(self.after_request_funcs[None]))
|
if name in self.after_request_funcs:
|
||||||
for handler in funcs:
|
for func in reversed(self.after_request_funcs[name]):
|
||||||
response = self.ensure_sync(handler)(response)
|
response = self.ensure_sync(func)(response)
|
||||||
|
|
||||||
if not self.session_interface.is_null_session(ctx.session):
|
if not self.session_interface.is_null_session(ctx.session):
|
||||||
self.session_interface.save_session(self, ctx.session, response)
|
self.session_interface.save_session(self, ctx.session, response)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def do_teardown_request(
|
def do_teardown_request(
|
||||||
|
|
@ -1916,14 +1914,12 @@ class Flask(Scaffold):
|
||||||
"""
|
"""
|
||||||
if exc is _sentinel:
|
if exc is _sentinel:
|
||||||
exc = sys.exc_info()[1]
|
exc = sys.exc_info()[1]
|
||||||
funcs: t.Iterable[TeardownCallable] = reversed(
|
|
||||||
self.teardown_request_funcs[None]
|
for name in chain(request.blueprints, (None,)):
|
||||||
)
|
if name in self.teardown_request_funcs:
|
||||||
for bp in request.blueprints:
|
for func in reversed(self.teardown_request_funcs[name]):
|
||||||
if bp in self.teardown_request_funcs:
|
self.ensure_sync(func)(exc)
|
||||||
funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
|
|
||||||
for func in funcs:
|
|
||||||
self.ensure_sync(func)(exc)
|
|
||||||
request_tearing_down.send(self, exc=exc)
|
request_tearing_down.send(self, exc=exc)
|
||||||
|
|
||||||
def do_teardown_appcontext(
|
def do_teardown_appcontext(
|
||||||
|
|
@ -1945,8 +1941,10 @@ class Flask(Scaffold):
|
||||||
"""
|
"""
|
||||||
if exc is _sentinel:
|
if exc is _sentinel:
|
||||||
exc = sys.exc_info()[1]
|
exc = sys.exc_info()[1]
|
||||||
|
|
||||||
for func in reversed(self.teardown_appcontext_funcs):
|
for func in reversed(self.teardown_appcontext_funcs):
|
||||||
self.ensure_sync(func)(exc)
|
self.ensure_sync(func)(exc)
|
||||||
|
|
||||||
appcontext_tearing_down.send(self, exc=exc)
|
appcontext_tearing_down.send(self, exc=exc)
|
||||||
|
|
||||||
def app_context(self) -> AppContext:
|
def app_context(self) -> AppContext:
|
||||||
|
|
|
||||||
|
|
@ -69,15 +69,16 @@ def find_best_app(script_info, module):
|
||||||
|
|
||||||
if isinstance(app, Flask):
|
if isinstance(app, Flask):
|
||||||
return app
|
return app
|
||||||
except TypeError:
|
except TypeError as e:
|
||||||
if not _called_with_wrong_args(app_factory):
|
if not _called_with_wrong_args(app_factory):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
f"Detected factory {attr_name!r} in module {module.__name__!r},"
|
f"Detected factory {attr_name!r} in module {module.__name__!r},"
|
||||||
" but could not call it without arguments. Use"
|
" but could not call it without arguments. Use"
|
||||||
f" \"FLASK_APP='{module.__name__}:{attr_name}(args)'\""
|
f" \"FLASK_APP='{module.__name__}:{attr_name}(args)'\""
|
||||||
" to specify arguments."
|
" to specify arguments."
|
||||||
)
|
) from e
|
||||||
|
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
"Failed to find Flask application or factory in module"
|
"Failed to find Flask application or factory in module"
|
||||||
|
|
@ -161,7 +162,7 @@ def find_app_by_string(script_info, module, app_name):
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
f"Failed to parse {app_name!r} as an attribute name or function call."
|
f"Failed to parse {app_name!r} as an attribute name or function call."
|
||||||
)
|
) from None
|
||||||
|
|
||||||
if isinstance(expr, ast.Name):
|
if isinstance(expr, ast.Name):
|
||||||
name = expr.id
|
name = expr.id
|
||||||
|
|
@ -184,7 +185,7 @@ def find_app_by_string(script_info, module, app_name):
|
||||||
# message with the full expression instead.
|
# message with the full expression instead.
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
f"Failed to parse arguments as literal values: {app_name!r}."
|
f"Failed to parse arguments as literal values: {app_name!r}."
|
||||||
)
|
) from None
|
||||||
else:
|
else:
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
f"Failed to parse {app_name!r} as an attribute name or function call."
|
f"Failed to parse {app_name!r} as an attribute name or function call."
|
||||||
|
|
@ -192,17 +193,17 @@ def find_app_by_string(script_info, module, app_name):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
attr = getattr(module, name)
|
attr = getattr(module, name)
|
||||||
except AttributeError:
|
except AttributeError as e:
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
f"Failed to find attribute {name!r} in {module.__name__!r}."
|
f"Failed to find attribute {name!r} in {module.__name__!r}."
|
||||||
)
|
) from e
|
||||||
|
|
||||||
# If the attribute is a function, call it with any args and kwargs
|
# If the attribute is a function, call it with any args and kwargs
|
||||||
# to get the real application.
|
# to get the real application.
|
||||||
if inspect.isfunction(attr):
|
if inspect.isfunction(attr):
|
||||||
try:
|
try:
|
||||||
app = call_factory(script_info, attr, args, kwargs)
|
app = call_factory(script_info, attr, args, kwargs)
|
||||||
except TypeError:
|
except TypeError as e:
|
||||||
if not _called_with_wrong_args(attr):
|
if not _called_with_wrong_args(attr):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
@ -210,7 +211,7 @@ def find_app_by_string(script_info, module, app_name):
|
||||||
f"The factory {app_name!r} in module"
|
f"The factory {app_name!r} in module"
|
||||||
f" {module.__name__!r} could not be called with the"
|
f" {module.__name__!r} could not be called with the"
|
||||||
" specified arguments."
|
" specified arguments."
|
||||||
)
|
) from e
|
||||||
else:
|
else:
|
||||||
app = attr
|
app = attr
|
||||||
|
|
||||||
|
|
@ -257,16 +258,15 @@ def locate_app(script_info, module_name, app_name, raise_if_not_found=True):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
__import__(module_name)
|
__import__(module_name)
|
||||||
except ImportError:
|
except ImportError as e:
|
||||||
# Reraise the ImportError if it occurred within the imported module.
|
# Reraise the ImportError if it occurred within the imported module.
|
||||||
# Determine this by checking whether the trace has a depth > 1.
|
# Determine this by checking whether the trace has a depth > 1.
|
||||||
if sys.exc_info()[2].tb_next:
|
if sys.exc_info()[2].tb_next:
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
f"While importing {module_name!r}, an ImportError was"
|
f"While importing {module_name!r}, an ImportError was raised."
|
||||||
f" raised:\n\n{traceback.format_exc()}"
|
) from e
|
||||||
)
|
|
||||||
elif raise_if_not_found:
|
elif raise_if_not_found:
|
||||||
raise NoAppException(f"Could not import {module_name!r}.")
|
raise NoAppException(f"Could not import {module_name!r}.") from e
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -725,7 +725,7 @@ class CertParamType(click.ParamType):
|
||||||
"Using ad-hoc certificates requires the cryptography library.",
|
"Using ad-hoc certificates requires the cryptography library.",
|
||||||
ctx,
|
ctx,
|
||||||
param,
|
param,
|
||||||
)
|
) from None
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,10 +83,11 @@ def attach_enctype_error_multidict(request):
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
try:
|
try:
|
||||||
return oldcls.__getitem__(self, key)
|
return oldcls.__getitem__(self, key)
|
||||||
except KeyError:
|
except KeyError as e:
|
||||||
if key not in request.form:
|
if key not in request.form:
|
||||||
raise
|
raise
|
||||||
raise DebugFilesKeyError(request, key)
|
|
||||||
|
raise DebugFilesKeyError(request, key) from e
|
||||||
|
|
||||||
newcls.__name__ = oldcls.__name__
|
newcls.__name__ = oldcls.__name__
|
||||||
newcls.__module__ = oldcls.__module__
|
newcls.__module__ = oldcls.__module__
|
||||||
|
|
|
||||||
|
|
@ -715,7 +715,7 @@ class Scaffold:
|
||||||
f"'{code_or_exception}' is not a recognized HTTP error"
|
f"'{code_or_exception}' is not a recognized HTTP error"
|
||||||
" code. Use a subclass of HTTPException with that code"
|
" code. Use a subclass of HTTPException with that code"
|
||||||
" instead."
|
" instead."
|
||||||
)
|
) from None
|
||||||
|
|
||||||
self.error_handler_spec[None][code][exc_class] = t.cast(
|
self.error_handler_spec[None][code][exc_class] = t.cast(
|
||||||
"ErrorHandlerCallable[Exception]", f
|
"ErrorHandlerCallable[Exception]", f
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ except ImportError:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Signalling support is unavailable because the blinker"
|
"Signalling support is unavailable because the blinker"
|
||||||
" library is not installed."
|
" library is not installed."
|
||||||
)
|
) from None
|
||||||
|
|
||||||
connect = connect_via = connected_to = temporarily_connected_to = _fail
|
connect = connect_via = connected_to = temporarily_connected_to = _fail
|
||||||
disconnect = _fail
|
disconnect = _fail
|
||||||
|
|
|
||||||
|
|
@ -837,6 +837,86 @@ def test_nested_blueprint(app, client):
|
||||||
assert client.get("/parent/child/grandchild/no").data == b"Grandchild no"
|
assert client.get("/parent/child/grandchild/no").data == b"Grandchild no"
|
||||||
|
|
||||||
|
|
||||||
|
def test_nested_callback_order(app, client):
|
||||||
|
parent = flask.Blueprint("parent", __name__)
|
||||||
|
child = flask.Blueprint("child", __name__)
|
||||||
|
|
||||||
|
@app.before_request
|
||||||
|
def app_before1():
|
||||||
|
flask.g.setdefault("seen", []).append("app_1")
|
||||||
|
|
||||||
|
@app.teardown_request
|
||||||
|
def app_teardown1(e=None):
|
||||||
|
assert flask.g.seen.pop() == "app_1"
|
||||||
|
|
||||||
|
@app.before_request
|
||||||
|
def app_before2():
|
||||||
|
flask.g.setdefault("seen", []).append("app_2")
|
||||||
|
|
||||||
|
@app.teardown_request
|
||||||
|
def app_teardown2(e=None):
|
||||||
|
assert flask.g.seen.pop() == "app_2"
|
||||||
|
|
||||||
|
@app.context_processor
|
||||||
|
def app_ctx():
|
||||||
|
return dict(key="app")
|
||||||
|
|
||||||
|
@parent.before_request
|
||||||
|
def parent_before1():
|
||||||
|
flask.g.setdefault("seen", []).append("parent_1")
|
||||||
|
|
||||||
|
@parent.teardown_request
|
||||||
|
def parent_teardown1(e=None):
|
||||||
|
assert flask.g.seen.pop() == "parent_1"
|
||||||
|
|
||||||
|
@parent.before_request
|
||||||
|
def parent_before2():
|
||||||
|
flask.g.setdefault("seen", []).append("parent_2")
|
||||||
|
|
||||||
|
@parent.teardown_request
|
||||||
|
def parent_teardown2(e=None):
|
||||||
|
assert flask.g.seen.pop() == "parent_2"
|
||||||
|
|
||||||
|
@parent.context_processor
|
||||||
|
def parent_ctx():
|
||||||
|
return dict(key="parent")
|
||||||
|
|
||||||
|
@child.before_request
|
||||||
|
def child_before1():
|
||||||
|
flask.g.setdefault("seen", []).append("child_1")
|
||||||
|
|
||||||
|
@child.teardown_request
|
||||||
|
def child_teardown1(e=None):
|
||||||
|
assert flask.g.seen.pop() == "child_1"
|
||||||
|
|
||||||
|
@child.before_request
|
||||||
|
def child_before2():
|
||||||
|
flask.g.setdefault("seen", []).append("child_2")
|
||||||
|
|
||||||
|
@child.teardown_request
|
||||||
|
def child_teardown2(e=None):
|
||||||
|
assert flask.g.seen.pop() == "child_2"
|
||||||
|
|
||||||
|
@child.context_processor
|
||||||
|
def child_ctx():
|
||||||
|
return dict(key="child")
|
||||||
|
|
||||||
|
@child.route("/a")
|
||||||
|
def a():
|
||||||
|
return ", ".join(flask.g.seen)
|
||||||
|
|
||||||
|
@child.route("/b")
|
||||||
|
def b():
|
||||||
|
return flask.render_template_string("{{ key }}")
|
||||||
|
|
||||||
|
parent.register_blueprint(child)
|
||||||
|
app.register_blueprint(parent)
|
||||||
|
assert (
|
||||||
|
client.get("/a").data == b"app_1, app_2, parent_1, parent_2, child_1, child_2"
|
||||||
|
)
|
||||||
|
assert client.get("/b").data == b"child"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"parent_init, child_init, parent_registration, child_registration",
|
"parent_init, child_init, parent_registration, child_registration",
|
||||||
[
|
[
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
# This file was part of Flask-CLI and was modified under the terms of
|
# This file was part of Flask-CLI and was modified under the terms of
|
||||||
# its Revised BSD License. Copyright © 2015 CERN.
|
# its Revised BSD License. Copyright © 2015 CERN.
|
||||||
import os
|
import os
|
||||||
|
import platform
|
||||||
import ssl
|
import ssl
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
|
|
@ -320,6 +321,7 @@ def test_scriptinfo(test_apps, monkeypatch):
|
||||||
assert app.name == "testapp"
|
assert app.name == "testapp"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail(platform.python_implementation() == "PyPy", reason="flaky on pypy")
|
||||||
def test_lazy_load_error(monkeypatch):
|
def test_lazy_load_error(monkeypatch):
|
||||||
"""When using lazy loading, the correct exception should be
|
"""When using lazy loading, the correct exception should be
|
||||||
re-raised.
|
re-raised.
|
||||||
|
|
@ -334,7 +336,9 @@ def test_lazy_load_error(monkeypatch):
|
||||||
lazy = DispatchingApp(bad_load, use_eager_loading=False)
|
lazy = DispatchingApp(bad_load, use_eager_loading=False)
|
||||||
|
|
||||||
with pytest.raises(BadExc):
|
with pytest.raises(BadExc):
|
||||||
lazy._flush_bg_loading_exception()
|
# reduce flakiness by waiting for the internal loading lock
|
||||||
|
with lazy._lock:
|
||||||
|
lazy._flush_bg_loading_exception()
|
||||||
|
|
||||||
|
|
||||||
def test_with_appcontext(runner):
|
def test_with_appcontext(runner):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue