diff --git a/CHANGES.rst b/CHANGES.rst index 34695123..22e22218 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -28,6 +28,9 @@ Unreleased the endpoint name. :issue:`4041` - Combine URL prefixes when nesting blueprints that were created with a ``url_prefix`` value. :issue:`4037` +- 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` Version 2.0.0 diff --git a/src/flask/ctx.py b/src/flask/ctx.py index 065edd5f..5c064635 100644 --- a/src/flask/ctx.py +++ b/src/flask/ctx.py @@ -395,9 +395,6 @@ class RequestContext: _request_ctx_stack.push(self) - if self.url_adapter is not None: - self.match_request() - # Open the session at the moment that the request context is available. # This allows a custom open_session method to use the request context. # Only open a new session if this is the first time the request was @@ -409,6 +406,11 @@ class RequestContext: if self.session is None: self.session = session_interface.make_null_session(self.app) + # Match the request URL after loading the session, so that the + # session is available in custom URL converters. + if self.url_adapter is not None: + self.match_request() + def pop(self, exc: t.Optional[BaseException] = _sentinel) -> None: # type: ignore """Pops the request context and unbinds it by doing that. This will also trigger the execution of functions registered by the diff --git a/tests/test_converters.py b/tests/test_converters.py index 14c92afd..d94a7658 100644 --- a/tests/test_converters.py +++ b/tests/test_converters.py @@ -1,6 +1,7 @@ from werkzeug.routing import BaseConverter -from flask import has_request_context +from flask import request +from flask import session from flask import url_for @@ -28,12 +29,13 @@ def test_custom_converters(app, client): def test_context_available(app, client): class ContextConverter(BaseConverter): def to_python(self, value): - assert has_request_context() + assert request is not None + assert session is not None return value app.url_map.converters["ctx"] = ContextConverter - @app.route("/") + @app.get("/") def index(name): return name diff --git a/tests/test_session_interface.py b/tests/test_session_interface.py index aa9ecafa..39562f5a 100644 --- a/tests/test_session_interface.py +++ b/tests/test_session_interface.py @@ -2,21 +2,26 @@ import flask from flask.sessions import SessionInterface -def test_open_session_endpoint_not_none(): - # Define a session interface that breaks if request.endpoint is None +def test_open_session_with_endpoint(): + """If request.endpoint (or other URL matching behavior) is needed + while loading the session, RequestContext.match_request() can be + called manually. + """ + class MySessionInterface(SessionInterface): - def save_session(self): + def save_session(self, app, session, response): pass - def open_session(self, _, request): + def open_session(self, app, request): + flask._request_ctx_stack.top.match_request() assert request.endpoint is not None - def index(): - return "Hello World!" - - # Confirm a 200 response, indicating that request.endpoint was NOT None app = flask.Flask(__name__) - app.route("/")(index) app.session_interface = MySessionInterface() - response = app.test_client().open("/") + + @app.get("/") + def index(): + return "Hello, World!" + + response = app.test_client().get("/") assert response.status_code == 200