diff --git a/CHANGES.rst b/CHANGES.rst index fa24b104..55caae37 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -23,7 +23,9 @@ Unreleased - Roll back a change to the order that URL matching was done. The URL is again matched after the session is loaded, so the session is available in custom URL converters. :issue:`4053` - +- Fix the request ``blueprint`` method to ensure it returns the + current blueprint name. A ``blueprints`` method is added to + replace any functionality lost in this fix. :pr:`4059` Version 2.0.0 ------------- diff --git a/src/flask/app.py b/src/flask/app.py index f0f31486..38bd303b 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -747,7 +747,7 @@ class Flask(Scaffold): ] = self.template_context_processors[None] reqctx = _request_ctx_stack.top if reqctx is not None: - for bp in self._request_blueprints(): + for bp in request.blueprints: if bp in self.template_context_processors: funcs = chain(funcs, self.template_context_processors[bp]) orig_ctx = context.copy() @@ -1261,7 +1261,7 @@ class Flask(Scaffold): exc_class, code = self._get_exc_class_and_code(type(e)) for c in [code, None]: - for name in chain(self._request_blueprints(), [None]): + for name in chain(request.blueprints, [None]): handler_map = self.error_handler_spec[name][c] if not handler_map: @@ -1825,14 +1825,14 @@ class Flask(Scaffold): funcs: t.Iterable[URLValuePreprocessorCallable] = self.url_value_preprocessors[ None ] - for bp in self._request_blueprints(): + for bp in request.blueprints: 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 bp in self._request_blueprints(): + for bp in request.blueprints: if bp in self.before_request_funcs: funcs = chain(funcs, self.before_request_funcs[bp]) for func in funcs: @@ -1857,7 +1857,7 @@ class Flask(Scaffold): """ ctx = _request_ctx_stack.top funcs: t.Iterable[AfterRequestCallable] = ctx._after_request_functions - for bp in self._request_blueprints(): + for bp in request.blueprints: if bp in self.after_request_funcs: funcs = chain(funcs, reversed(self.after_request_funcs[bp])) if None in self.after_request_funcs: @@ -1896,7 +1896,7 @@ class Flask(Scaffold): funcs: t.Iterable[TeardownCallable] = reversed( self.teardown_request_funcs[None] ) - for bp in self._request_blueprints(): + for bp in request.blueprints: if bp in self.teardown_request_funcs: funcs = chain(funcs, reversed(self.teardown_request_funcs[bp])) for func in funcs: @@ -2068,9 +2068,3 @@ class Flask(Scaffold): wrapped to apply middleware. """ return self.wsgi_app(environ, start_response) - - def _request_blueprints(self) -> t.Iterable[str]: - if _request_ctx_stack.top.request.blueprint is None: - return [] - else: - return reversed(_request_ctx_stack.top.request.blueprint.split(".")) diff --git a/src/flask/wrappers.py b/src/flask/wrappers.py index bfa9d7ce..4f118213 100644 --- a/src/flask/wrappers.py +++ b/src/flask/wrappers.py @@ -1,6 +1,7 @@ import typing as t from werkzeug.exceptions import BadRequest +from werkzeug.utils import cached_property from werkzeug.wrappers import Request as RequestBase from werkzeug.wrappers import Response as ResponseBase @@ -73,10 +74,24 @@ class Request(RequestBase): def blueprint(self) -> t.Optional[str]: """The name of the current blueprint""" if self.url_rule and "." in self.url_rule.endpoint: - return self.url_rule.endpoint.rsplit(".", 1)[0] + return self.url_rule.endpoint.split(".")[-2] else: return None + @cached_property + def blueprints(self) -> t.List[str]: + """Return the names of the current blueprints. + + The returned list is ordered from the current blueprint, + upwards through parent blueprints. + """ + if self.url_rule and "." in self.url_rule.endpoint: + bps = self.url_rule.endpoint.split(".")[:-1] + bps.reverse() + return bps + else: + return [] + def _load_form_data(self) -> None: RequestBase._load_form_data(self)