8000
We read every piece of feedback, and take your input very seriously.
1 parent 4346498 commit 166a2a6Copy full SHA for 166a2a6
CHANGES.rst
@@ -21,6 +21,9 @@ Unreleased
21
:issue:`4096`
22
- The CLI loader handles ``**kwargs`` in a ``create_app`` function.
23
:issue:`4170`
24
+- Fix the order of ``before_request`` and other callbacks that trigger
25
+ before the view returns. They are called from the app down to the
26
+ closest nested blueprint. :issue:`4229`
27
28
29
Version 2.0.1
src/flask/app.py
@@ -745,12 +745,12 @@ def update_template_context(self, context: dict) -> None:
745
:param context: the context as a dictionary that is updated in place
746
to add extra variables.
747
"""
748
- funcs: t.Iterable[
749
- TemplateContextProcessorCallable
750
- ] = self.template_context_processors[None]
+ funcs: t.Iterable[TemplateContextProcessorCallable] = []
+ if None in self.template_context_processors:
+ funcs = chain(funcs, self.template_context_processors[None])
751
reqctx = _request_ctx_stack.top
752
if reqctx is not None:
753
- for bp in request.blueprints:
+ for bp in reversed(request.blueprints):
754
if bp in self.template_context_processors:
755
funcs = chain(funcs, self.template_context_processors[bp])
756
orig_ctx = context.copy()
@@ -1806,7 +1806,9 @@ def inject_url_defaults(self, endpoint: str, values: dict) -> None:
1806
# This is called by url_for, which can be called outside a
1807
# request, can't use request.blueprints.
1808
bps = _split_blueprint_path(endpoint.rpartition(".")[0])
1809
- bp_funcs = chain.from_iterable(self.url_default_functions[bp] for bp in bps)
+ bp_funcs = chain.from_iterable(
1810
+ self.url_default_functions[bp] for bp in reversed(bps)
1811
+ )
1812
funcs = chain(funcs, bp_funcs)
1813
1814
for func in funcs:
@@ -1846,19 +1848,17 @@ def preprocess_request(self) -> t.Optional[ResponseReturnValue]:
1846
1848
further request handling is stopped.
1847
1849
1850
- funcs: t.Iterable[URLValuePreprocessorCallable] = self.url_value_preprocessors[
- None
1851
- ]
1852
1853
- if bp in self.url_value_preprocessors:
1854
- funcs = chain(funcs, self.url_value_preprocessors[bp])
+ funcs: t.Iterable[URLValuePreprocessorCallable] = []
+ for name in chain([None], reversed(request.blueprints)):
+ if name in self.url_value_preprocessors:
+ funcs = chain(funcs, self.url_value_preprocessors[name])
1855
1856
func(request.endpoint, request.view_args)
1857
1858
- funcs: t.Iterable[BeforeRequestCallable] = self.before_request_funcs[None]
1859
1860
- if bp in self.before_request_funcs:
1861
- funcs = chain(funcs, self.before_request_funcs[bp])
+ funcs: t.Iterable[BeforeRequestCallable] = []
+ if name in self.before_request_funcs:
+ funcs = chain(funcs, self.before_request_funcs[name])
1862
1863
rv = self.ensure_sync(func)()
1864
if rv is not None:
@@ -1881,11 +1881,9 @@ def process_response(self, response: Response) -> Response:
1881
1882
ctx = _request_ctx_stack.top
1883
funcs: t.Iterable[AfterRequestCallable] = ctx._after_request_functions
1884
1885
- if bp in self.after_request_funcs:
1886
- funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
1887
- if None in self.after_request_funcs:
1888
- funcs = chain(funcs, reversed(self.after_request_funcs[None]))
+ for< 57AE /span> name in chain(request.blueprints, [None]):
+ if name in self.after_request_funcs:
+ funcs = chain(funcs, reversed(self.after_request_funcs[name]))
1889
for handler in funcs:
1890
response = self.ensure_sync(handler)(response)
1891
if not self.session_interface.is_null_session(ctx.session):
@@ -1917,12 +1915,10 @@ def do_teardown_request(
1917
1915
1918
1916
if exc is _sentinel:
1919
exc = sys.exc_info()[1]
1920
- funcs: t.Iterable[TeardownCallable] = reversed(
1921
- self.teardown_request_funcs[None]
1922
- )
1923
1924
- if bp in self.teardown_request_funcs:
1925
- funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
+ funcs: t.Iterable[TeardownCallable] = []
+ for name in chain(request.blueprints, [None]):
+ if name in self.teardown_request_funcs:
+ funcs = chain(funcs, reversed(self.teardown_request_funcs[name]))
1926
1927
self.ensure_sync(func)(exc)
1928
request_tearing_down.send(self, exc=exc)
tests/test_blueprints.py
@@ -837,6 +837,86 @@ def grandchild_no():
837
assert client.get("/parent/child/grandchild/no").data == b"Grandchild no"
838
839
840
+def test_nested_callback_order(app, client):
841
+ parent = flask.Blueprint("parent", __name__)
842
+ child = flask.Blueprint("child", __name__)
843
+
844
+ @app.before_request
845
+ def app_before1():
846
+ flask.g.setdefault("seen", []).append("app_1")
847
848
+ @app.teardown_request
849
+ def app_teardown1(e=None):
850
+ assert flask.g.seen.pop() == "app_1"
851
852
853
+ def app_before2():
854
+ flask.g.setdefault("seen", []).append("app_2")
855
856
857
+ def app_teardown2(e=None):
858
+ assert flask.g.seen.pop() == "app_2"
859
860
+ @app.context_processor
861
+ def app_ctx():
862
+ return dict(key="app")
863
864
+ @parent.before_request
865
+ def parent_before1():
866
+ flask.g.setdefault("seen", []).append("parent_1")
867
868
+ @parent.teardown_request
869
+ def parent_teardown1(e=None):
870
+ assert flask.g.seen.pop() == "parent_1"
871
872
873
+ def parent_before2():
874
+ flask.g.setdefault("seen", []).append("parent_2")
875
876
877
+ def parent_teardown2(e=None):
878
+ assert flask.g.seen.pop() == "parent_2"
879
880
+ @parent.context_processor
881
+ def parent_ctx():
882
+ return dict(key="parent")
883
884
+ @child.before_request
885
+ def child_before1():
886
+ flask.g.setdefault("seen", []).append("child_1")
887
888
+ @child.teardown_request
889
+ def child_teardown1(e=None):
890
+ assert flask.g.seen.pop() == "child_1"
891
892
893
+ def child_before2():
894
+ flask.g.setdefault("seen", []).append("child_2")
895
896
897
+ def child_teardown2(e=None):
898
+ assert flask.g.seen.pop() == "child_2"
899
900
+ @child.context_processor
901
+ def child_ctx():
902
+ return dict(key="child")
903
904
+ @child.route("/a")
905
+ def a():
906
+ return ", ".join(flask.g.seen)
907
908
+ @child.route("/b")
909
+ def b():
910
+ return flask.render_template_string("{{ key }}")
911
912
+ parent.register_blueprint(child)
913
+ app.register_blueprint(parent)
914
+ assert (
915
+ client.get("/a").data == b"app_1, app_2, parent_1, parent_2, child_1, child_2"
916
917
+ assert client.get("/b").data == b"child"
918
919
920
@pytest.mark.parametrize(
921
"parent_init, child_init, parent_registration, child_registration",
922
[