merge app and request context

This commit is contained in:
David Lord 2025-09-12 14:52:03 -07:00
parent 330123258e
commit c2705ffd9c
No known key found for this signature in database
GPG key ID: 43368A7AA8CC5926
36 changed files with 779 additions and 1007 deletions

View file

@ -29,20 +29,15 @@ from werkzeug.wsgi import get_host
from . import cli
from . import typing as ft
from .ctx import AppContext
from .ctx import RequestContext
from .globals import _cv_app
from .globals import _cv_request
from .globals import current_app
from .globals import g
from .globals import request
from .globals import request_ctx
from .globals import session
from .helpers import get_debug_flag
from .helpers import get_flashed_messages
from .helpers import get_load_dotenv
from .helpers import send_from_directory
from .sansio.app import App
from .sansio.scaffold import _sentinel
from .sessions import SecureCookieSessionInterface
from .sessions import SessionInterface
from .signals import appcontext_tearing_down
@ -295,7 +290,7 @@ class Flask(App):
.. versionadded:: 0.9
"""
value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"]
value = self.config["SEND_FILE_MAX_AGE_DEFAULT"]
if value is None:
return None
@ -517,8 +512,8 @@ class Flask(App):
names: t.Iterable[str | None] = (None,)
# A template may be rendered outside a request context.
if request:
names = chain(names, reversed(request.blueprints))
if (ctx := _cv_app.get(None)) is not None and ctx.has_request:
names = chain(names, reversed(ctx.request.blueprints))
# The values passed to render_template take precedence. Keep a
# copy to re-apply after all context functions.
@ -886,7 +881,8 @@ class Flask(App):
This no longer does the exception handling, this code was
moved to the new :meth:`full_dispatch_request`.
"""
req = request_ctx.request
req = _cv_app.get().request
if req.routing_exception is not None:
self.raise_routing_exception(req)
rule: Rule = req.url_rule # type: ignore[assignment]
@ -957,7 +953,7 @@ class Flask(App):
.. versionadded:: 0.7
"""
adapter = request_ctx.url_adapter
adapter = _cv_app.get().url_adapter
methods = adapter.allowed_methods() # type: ignore[union-attr]
rv = self.response_class()
rv.allow.update(methods)
@ -1057,11 +1053,9 @@ class Flask(App):
.. versionadded:: 2.2
Moved from ``flask.url_for``, which calls this method.
"""
req_ctx = _cv_request.get(None)
if req_ctx is not None:
url_adapter = req_ctx.url_adapter
blueprint_name = req_ctx.request.blueprint
if (ctx := _cv_app.get(None)) is not None and ctx.has_request:
url_adapter = ctx.url_adapter
blueprint_name = ctx.request.blueprint
# If the endpoint starts with "." and the request matches a
# blueprint, the endpoint is relative to the blueprint.
@ -1076,13 +1070,11 @@ class Flask(App):
if _external is None:
_external = _scheme is not None
else:
app_ctx = _cv_app.get(None)
# If called by helpers.url_for, an app context is active,
# use its url_adapter. Otherwise, app.url_for was called
# directly, build an adapter.
if app_ctx is not None:
url_adapter = app_ctx.url_adapter
if ctx is not None:
url_adapter = ctx.url_adapter
else:
url_adapter = self.create_url_adapter(None)
@ -1278,12 +1270,13 @@ class Flask(App):
value is handled as if it was the return value from the view, and
further request handling is stopped.
"""
names = (None, *reversed(request.blueprints))
req = _cv_app.get().request
names = (None, *reversed(req.blueprints))
for name in names:
if name in self.url_value_preprocessors:
for url_func in self.url_value_preprocessors[name]:
url_func(request.endpoint, request.view_args)
url_func(req.endpoint, req.view_args)
for name in names:
if name in self.before_request_funcs:
@ -1308,12 +1301,12 @@ class Flask(App):
:return: a new response object or the same, has to be an
instance of :attr:`response_class`.
"""
ctx = request_ctx._get_current_object() # type: ignore[attr-defined]
ctx = _cv_app.get()
for func in ctx._after_request_functions:
response = self.ensure_sync(func)(response)
for name in chain(request.blueprints, (None,)):
for name in chain(ctx.request.blueprints, (None,)):
if name in self.after_request_funcs:
for func in reversed(self.after_request_funcs[name]):
response = self.ensure_sync(func)(response)
@ -1323,77 +1316,57 @@ class Flask(App):
return response
def do_teardown_request(
self,
exc: BaseException | None = _sentinel, # type: ignore[assignment]
) -> None:
"""Called after the request is dispatched and the response is
returned, right before the request context is popped.
def do_teardown_request(self, exc: BaseException | None = None) -> None:
"""Called after the request is dispatched and the response is finalized,
right before the request context is popped. Called by
:meth:`.AppContext.pop`.
This calls all functions decorated with
:meth:`teardown_request`, and :meth:`Blueprint.teardown_request`
if a blueprint handled the request. Finally, the
:data:`request_tearing_down` signal is sent.
This calls all functions decorated with :meth:`teardown_request`, and
:meth:`Blueprint.teardown_request` if a blueprint handled the request.
Finally, the :data:`request_tearing_down` signal is sent.
This is called by
:meth:`RequestContext.pop() <flask.ctx.RequestContext.pop>`,
which may be delayed during testing to maintain access to
resources.
:param exc: An unhandled exception raised while dispatching the
request. Detected from the current exception information if
not passed. Passed to each teardown function.
:param exc: An unhandled exception raised while dispatching the request.
Passed to each teardown function.
.. versionchanged:: 0.9
Added the ``exc`` argument.
"""
if exc is _sentinel:
exc = sys.exc_info()[1]
req = _cv_app.get().request
for name in chain(request.blueprints, (None,)):
for name in chain(req.blueprints, (None,)):
if name in self.teardown_request_funcs:
for func in reversed(self.teardown_request_funcs[name]):
self.ensure_sync(func)(exc)
request_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc)
def do_teardown_appcontext(
self,
exc: BaseException | None = _sentinel, # type: ignore[assignment]
) -> None:
"""Called right before the application context is popped.
def do_teardown_appcontext(self, exc: BaseException | None = None) -> None:
"""Called right before the application context is popped. Called by
:meth:`.AppContext.pop`.
When handling a request, the application context is popped
after the request context. See :meth:`do_teardown_request`.
This calls all functions decorated with :meth:`teardown_appcontext`.
Then the :data:`appcontext_tearing_down` signal is sent.
This calls all functions decorated with
:meth:`teardown_appcontext`. Then the
:data:`appcontext_tearing_down` signal is sent.
This is called by
:meth:`AppContext.pop() <flask.ctx.AppContext.pop>`.
:param exc: An unhandled exception raised while the context was active.
Passed to each teardown function.
.. versionadded:: 0.9
"""
if exc is _sentinel:
exc = sys.exc_info()[1]
for func in reversed(self.teardown_appcontext_funcs):
self.ensure_sync(func)(exc)
appcontext_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc)
def app_context(self) -> AppContext:
"""Create an :class:`~flask.ctx.AppContext`. Use as a ``with``
block to push the context, which will make :data:`current_app`
point at this application.
"""Create an :class:`.AppContext`. When the context is pushed,
:data:`.current_app` and :data:`.g` become available.
An application context is automatically pushed by
:meth:`RequestContext.push() <flask.ctx.RequestContext.push>`
when handling a request, and when running a CLI command. Use
this to manually create a context outside of these situations.
A context is automatically pushed when handling each request, and when
running any ``flask`` CLI command. Use this as a ``with`` block to
manually push a context outside of those situations, such as during
setup or testing.
::
.. code-block:: python
with app.app_context():
init_db()
@ -1404,44 +1377,37 @@ class Flask(App):
"""
return AppContext(self)
def request_context(self, environ: WSGIEnvironment) -> RequestContext:
"""Create a :class:`~flask.ctx.RequestContext` representing a
WSGI environment. Use a ``with`` block to push the context,
which will make :data:`request` point at this request.
def request_context(self, environ: WSGIEnvironment) -> AppContext:
"""Create an :class:`.AppContext` with request information representing
the given WSGI environment. A context is automatically pushed when
handling each request. When the context is pushed, :data:`.request`,
:data:`.session`, :data:`g:, and :data:`.current_app` become available.
See :doc:`/reqcontext`.
This method should not be used in your own code. Creating a valid WSGI
environ is not trivial. Use :meth:`test_request_context` to correctly
create a WSGI environ and request context instead.
Typically you should not call this from your own code. A request
context is automatically pushed by the :meth:`wsgi_app` when
handling a request. Use :meth:`test_request_context` to create
an environment and context instead of this method.
See :doc:`/appcontext`.
:param environ: a WSGI environment
:param environ: A WSGI environment.
"""
return RequestContext(self, environ)
return AppContext.from_environ(self, environ)
def test_request_context(self, *args: t.Any, **kwargs: t.Any) -> RequestContext:
"""Create a :class:`~flask.ctx.RequestContext` for a WSGI
environment created from the given values. This is mostly useful
during testing, where you may want to run a function that uses
request data without dispatching a full request.
def test_request_context(self, *args: t.Any, **kwargs: t.Any) -> AppContext:
"""Create an :class:`.AppContext` with request information created from
the given arguments. When the context is pushed, :data:`.request`,
:data:`.session`, :data:`g:, and :data:`.current_app` become available.
See :doc:`/reqcontext`.
This is useful during testing to run a function that uses request data
without dispatching a full request. Use this as a ``with`` block to push
a context.
Use a ``with`` block to push the context, which will make
:data:`request` point at the request for the created
environment. ::
.. code-block:: python
with app.test_request_context(...):
generate_report()
When using the shell, it may be easier to push and pop the
context manually to avoid indentation. ::
ctx = app.test_request_context(...)
ctx.push()
...
ctx.pop()
See :doc:`/appcontext`.
Takes the same arguments as Werkzeug's
:class:`~werkzeug.test.EnvironBuilder`, with some defaults from
@ -1451,20 +1417,18 @@ class Flask(App):
:param path: URL path being requested.
:param base_url: Base URL where the app is being served, which
``path`` is relative to. If not given, built from
:data:`PREFERRED_URL_SCHEME`, ``subdomain``,
:data:`SERVER_NAME`, and :data:`APPLICATION_ROOT`.
:param subdomain: Subdomain name to append to
:data:`SERVER_NAME`.
:data:`PREFERRED_URL_SCHEME`, ``subdomain``, :data:`SERVER_NAME`,
and :data:`APPLICATION_ROOT`.
:param subdomain: Subdomain name to prepend to :data:`SERVER_NAME`.
:param url_scheme: Scheme to use instead of
:data:`PREFERRED_URL_SCHEME`.
:param data: The request body, either as a string or a dict of
form keys and values.
:param data: The request body text or bytes,or a dict of form data.
:param json: If given, this is serialized as JSON and passed as
``data``. Also defaults ``content_type`` to
``application/json``.
:param args: other positional arguments passed to
:param args: Other positional arguments passed to
:class:`~werkzeug.test.EnvironBuilder`.
:param kwargs: other keyword arguments passed to
:param kwargs: Other keyword arguments passed to
:class:`~werkzeug.test.EnvironBuilder`.
"""
from .testing import EnvironBuilder
@ -1472,10 +1436,12 @@ class Flask(App):
builder = EnvironBuilder(self, *args, **kwargs)
try:
return self.request_context(builder.get_environ())
environ = builder.get_environ()
finally:
builder.close()
return self.request_context(environ)
def wsgi_app(
self, environ: WSGIEnvironment, start_response: StartResponse
) -> cabc.Iterable[bytes]:
@ -1496,7 +1462,6 @@ class Flask(App):
Teardown events for the request and app contexts are called
even if an unhandled error occurs. Other events may not be
called depending on when an error occurs during dispatch.
See :ref:`callbacks-and-errors`.
:param environ: A WSGI environment.
:param start_response: A callable accepting a status code,
@ -1519,7 +1484,6 @@ class Flask(App):
finally:
if "werkzeug.debug.preserve_context" in environ:
environ["werkzeug.debug.preserve_context"](_cv_app.get())
environ["werkzeug.debug.preserve_context"](_cv_request.get())
if error is not None and self.should_ignore_error(error):
error = None