merge app and request context (#5812)
pre-commit / main (push) Has been cancelled Details
Tests / ${{ matrix.name || matrix.python }} (3.10) (push) Has been cancelled Details
Tests / ${{ matrix.name || matrix.python }} (3.11) (push) Has been cancelled Details
Tests / ${{ matrix.name || matrix.python }} (3.12) (push) Has been cancelled Details
Tests / ${{ matrix.name || matrix.python }} (3.13) (push) Has been cancelled Details
Tests / ${{ matrix.name || matrix.python }} (Development Versions, 3.10, tests-dev) (push) Has been cancelled Details
Tests / ${{ matrix.name || matrix.python }} (Mac, macos-latest, 3.13) (push) Has been cancelled Details
Tests / ${{ matrix.name || matrix.python }} (Minimum Versions, 3.13, tests-min) (push) Has been cancelled Details
Tests / ${{ matrix.name || matrix.python }} (PyPy, pypy-3.11, pypy3.11) (push) Has been cancelled Details
Tests / ${{ matrix.name || matrix.python }} (Windows, windows-latest, 3.13) (push) Has been cancelled Details
Tests / typing (push) Has been cancelled Details

This commit is contained in:
David Lord 2025-09-19 16:45:27 -07:00 committed by GitHub
commit adf363679d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 779 additions and 1007 deletions

View File

@ -5,6 +5,10 @@ Unreleased
- Drop support for Python 3.9. :pr:`5730`
- Remove previously deprecated code: ``__version__``. :pr:`5648`
- ``RequestContext`` has merged with ``AppContext``. ``RequestContext`` is now
a deprecated alias. If an app context is already pushed, it is not reused
when dispatching a request. This greatly simplifies the internal code for tracking
the active context. :issue:`5639`
- ``template_filter``, ``template_test``, and ``template_global`` decorators
can be used without parentheses. :issue:`5729`

View File

@ -31,17 +31,15 @@ Incoming Request Data
:inherited-members:
:exclude-members: json_module
.. attribute:: request
.. data:: request
To access incoming request data, you can use the global `request`
object. Flask parses incoming request data for you and gives you
access to it through that global object. Internally Flask makes
sure that you always get the correct data for the active thread if you
are in a multithreaded environment.
A proxy to the request data for the current request, an instance of
:class:`.Request`.
This is a proxy. See :ref:`notes-on-proxies` for more information.
This is only available when a :doc:`request context </appcontext>` is
active.
The request object is an instance of a :class:`~flask.Request`.
This is a proxy. See :ref:`context-visibility` for more information.
Response Objects
@ -62,40 +60,33 @@ does this is by using a signed cookie. The user can look at the session
contents, but can't modify it unless they know the secret key, so make sure to
set that to something complex and unguessable.
To access the current session you can use the :class:`session` object:
To access the current session you can use the :data:`.session` proxy.
.. class:: session
.. data:: session
The session object works pretty much like an ordinary dict, with the
difference that it keeps track of modifications.
A proxy to the session data for the current request, an instance of
:class:`.SessionMixin`.
This is a proxy. See :ref:`notes-on-proxies` for more information.
This is only available when a :doc:`request context </appcontext>` is
active.
The following attributes are interesting:
This is a proxy. See :ref:`context-visibility` for more information.
.. attribute:: new
The session object works like a dict but tracks assignment and access to its
keys. It cannot track modifications to mutable values, you need to set
:attr:`~.SessionMixin.modified` manually when modifying a list, dict, etc.
``True`` if the session is new, ``False`` otherwise.
.. code-block:: python
.. attribute:: modified
``True`` if the session object detected a modification. Be advised
that modifications on mutable structures are not picked up
automatically, in that situation you have to explicitly set the
attribute to ``True`` yourself. Here an example::
# this change is not picked up because a mutable object (here
# a list) is changed.
session['objects'].append(42)
# appending to a list is not detected
session["numbers"].append(42)
# so mark it as modified yourself
session.modified = True
.. attribute:: permanent
If set to ``True`` the session lives for
:attr:`~flask.Flask.permanent_session_lifetime` seconds. The
default is 31 days. If set to ``False`` (which is the default) the
session will be deleted when the user closes the browser.
The session is persisted across requests using a cookie. By default the
users's browser will clear the cookie when it is closed. Set
:attr:`~.SessionMixin.permanent` to ``True`` to persist the cookie for
:data:`PERMANENT_SESSION_LIFETIME`.
Session Interface
@ -158,20 +149,21 @@ another, a global variable is not good enough because it would break in
threaded environments. Flask provides you with a special object that
ensures it is only valid for the active request and that will return
different values for each request. In a nutshell: it does the right
thing, like it does for :class:`request` and :class:`session`.
thing, like it does for :data:`.request` and :data:`.session`.
.. data:: g
A namespace object that can store data during an
:doc:`application context </appcontext>`. This is an instance of
:attr:`Flask.app_ctx_globals_class`, which defaults to
:class:`ctx._AppCtxGlobals`.
A proxy to a namespace object used to store data during a single request or
app context. An instance of :attr:`.Flask.app_ctx_globals_class`, which
defaults to :class:`._AppCtxGlobals`.
This is a good place to store resources during a request. For
example, a ``before_request`` function could load a user object from
a session id, then set ``g.user`` to be used in the view function.
This is a good place to store resources during a request. For example, a
:meth:`~.Flask.before_request` function could load a user object from a
session id, then set ``g.user`` to be used in the view function.
This is a proxy. See :ref:`notes-on-proxies` for more information.
This is only available when an :doc:`app context </appcontext>` is active.
This is a proxy. See :ref:`context-visibility` for more information.
.. versionchanged:: 0.10
Bound to the application context instead of the request context.
@ -185,17 +177,16 @@ Useful Functions and Classes
.. data:: current_app
A proxy to the application handling the current request. This is
useful to access the application without needing to import it, or if
it can't be imported, such as when using the application factory
pattern or in blueprints and extensions.
A proxy to the :class:`.Flask` application handling the current request or
other activity.
This is only available when an
:doc:`application context </appcontext>` is pushed. This happens
automatically during requests and CLI commands. It can be controlled
manually with :meth:`~flask.Flask.app_context`.
This is useful to access the application without needing to import it, or if
it can't be imported, such as when using the application factory pattern or
in blueprints and extensions.
This is a proxy. See :ref:`notes-on-proxies` for more information.
This is only available when an :doc:`app context </appcontext>` is active.
This is a proxy. See :ref:`context-visibility` for more information.
.. autofunction:: has_request_context
@ -299,31 +290,31 @@ Stream Helpers
Useful Internals
----------------
.. autoclass:: flask.ctx.RequestContext
:members:
.. data:: flask.globals.request_ctx
The current :class:`~flask.ctx.RequestContext`. If a request context
is not active, accessing attributes on this proxy will raise a
``RuntimeError``.
This is an internal object that is essential to how Flask handles
requests. Accessing this should not be needed in most cases. Most
likely you want :data:`request` and :data:`session` instead.
.. autoclass:: flask.ctx.AppContext
:members:
.. data:: flask.globals.app_ctx
The current :class:`~flask.ctx.AppContext`. If an app context is not
active, accessing attributes on this proxy will raise a
``RuntimeError``.
A proxy to the active :class:`.AppContext`.
This is an internal object that is essential to how Flask handles
requests. Accessing this should not be needed in most cases. Most
likely you want :data:`current_app` and :data:`g` instead.
This is an internal object that is essential to how Flask handles requests.
Accessing this should not be needed in most cases. Most likely you want
:data:`.current_app`, :data:`.g`, :data:`.request`, and :data:`.session` instead.
This is only available when a :doc:`request context </appcontext>` is
active.
This is a proxy. See :ref:`context-visibility` for more information.
.. class:: flask.ctx.RequestContext
.. deprecated:: 3.2
Merged with :class:`AppContext`. This alias will be removed in Flask 4.0.
.. data:: flask.globals.request_ctx
.. deprecated:: 3.2
Merged with :data:`.app_ctx`. This alias will be removed in Flask 4.0.
.. autoclass:: flask.blueprints.BlueprintSetupState
:members:

View File

@ -1,74 +1,63 @@
.. currentmodule:: flask
The App and Request Context
===========================
The Application Context
=======================
The context keeps track of data and objects during a request, CLI command, or
other activity. Rather than passing this data around to every function, the
:data:`.current_app`, :data:`.g`, :data:`.request`, and :data:`.session` proxies
are accessed instead.
The application context keeps track of the application-level data during
a request, CLI command, or other activity. Rather than passing the
application around to each function, the :data:`current_app` and
:data:`g` proxies are accessed instead.
When handling a request, the context is referred to as the "request context"
because it contains request data in addition to application data. Otherwise,
such as during a CLI command, it is referred to as the "app context". During an
app context, :data:`.current_app` and :data:`.g` are available, while during a
request context :data:`.request` and :data:`.session` are also available.
This is similar to :doc:`/reqcontext`, which keeps track of
request-level data during a request. A corresponding application context
is pushed when a request context is pushed.
Purpose of the Context
----------------------
The :class:`Flask` application object has attributes, such as
:attr:`~Flask.config`, that are useful to access within views and
:doc:`CLI commands </cli>`. However, importing the ``app`` instance
within the modules in your project is prone to circular import issues.
When using the :doc:`app factory pattern </patterns/appfactories>` or
writing reusable :doc:`blueprints </blueprints>` or
:doc:`extensions </extensions>` there won't be an ``app`` instance to
import at all.
The context and proxies help solve two development issues: circular imports, and
passing around global data during a request.
Flask solves this issue with the *application context*. Rather than
referring to an ``app`` directly, you use the :data:`current_app`
proxy, which points to the application handling the current activity.
The :class:`.Flask` application object has attributes, such as
:attr:`~.Flask.config`, that are useful to access within views and other
functions. However, importing the ``app`` instance within the modules in your
project is prone to circular import issues. When using the
:doc:`app factory pattern </patterns/appfactories>` or writing reusable
:doc:`blueprints </blueprints>` or :doc:`extensions </extensions>` there won't
be an ``app`` instance to import at all.
Flask automatically *pushes* an application context when handling a
request. View functions, error handlers, and other functions that run
during a request will have access to :data:`current_app`.
When the application handles a request, it creates a :class:`.Request` object.
Because a *worker* handles only one request at a time, the request data can be
considered global to that worker during that request. Passing it as an argument
through every function during the request becomes verbose and redundant.
Flask will also automatically push an app context when running CLI
commands registered with :attr:`Flask.cli` using ``@app.cli.command()``.
Flask solves these issues with the *active context* pattern. Rather than
importing an ``app`` directly, or having to pass it and the request through to
every single function, you import and access the proxies, which point to the
currently active application and request data. This is sometimes referred to
as "context local" data.
Lifetime of the Context
-----------------------
Context During Setup
--------------------
The application context is created and destroyed as necessary. When a
Flask application begins handling a request, it pushes an application
context and a :doc:`request context </reqcontext>`. When the request
ends it pops the request context then the application context.
Typically, an application context will have the same lifetime as a
request.
See :doc:`/reqcontext` for more information about how the contexts work
and the full life cycle of a request.
Manually Push a Context
-----------------------
If you try to access :data:`current_app`, or anything that uses it,
outside an application context, you'll get this error message:
If you try to access :data:`.current_app`, :data:`.g`, or anything that uses it,
outside an app context, you'll get this error message:
.. code-block:: pytb
RuntimeError: Working outside of application context.
This typically means that you attempted to use functionality that
needed to interface with the current application object in some way.
To solve this, set up an application context with app.app_context().
Attempted to use functionality that expected a current application to be
set. To solve this, set up an app context using 'with app.app_context()'.
See the documentation on app context for more information.
If you see that error while configuring your application, such as when
initializing an extension, you can push a context manually since you
have direct access to the ``app``. Use :meth:`~Flask.app_context` in a
``with`` block, and everything that runs in the block will have access
to :data:`current_app`. ::
initializing an extension, you can push a context manually since you have direct
access to the ``app``. Use :meth:`.Flask.app_context` in a ``with`` block.
.. code-block:: python
def create_app():
app = Flask(__name__)
@ -78,70 +67,120 @@ to :data:`current_app`. ::
return app
If you see that error somewhere else in your code not related to
configuring the application, it most likely indicates that you should
move that code into a view function or CLI command.
If you see that error somewhere else in your code not related to setting up the
application, it most likely indicates that you should move that code into a view
function or CLI command.
Storing Data
------------
Context During Testing
----------------------
The application context is a good place to store common data during a
request or CLI command. Flask provides the :data:`g object <g>` for this
purpose. It is a simple namespace object that has the same lifetime as
an application context.
See :doc:`/testing` for detailed information about managing the context during
tests.
.. note::
The ``g`` name stands for "global", but that is referring to the
data being global *within a context*. The data on ``g`` is lost
after the context ends, and it is not an appropriate place to store
data between requests. Use the :data:`session` or a database to
store data across requests.
If you try to access :data:`.request`, :data:`.session`, or anything that uses
it, outside a request context, you'll get this error message:
A common use for :data:`g` is to manage resources during a request.
.. code-block:: pytb
1. ``get_X()`` creates resource ``X`` if it does not exist, caching it
as ``g.X``.
2. ``teardown_X()`` closes or otherwise deallocates the resource if it
exists. It is registered as a :meth:`~Flask.teardown_appcontext`
handler.
RuntimeError: Working outside of request context.
For example, you can manage a database connection using this pattern::
Attempted to use functionality that expected an active HTTP request. See the
documentation on request context for more information.
from flask import g
This will probably only happen during tests. If you see that error somewhere
else in your code not related to testing, it most likely indicates that you
should move that code into a view function.
def get_db():
if 'db' not in g:
g.db = connect_to_database()
The primary way to solve this is to use :meth:`.Flask.test_client` to simulate
a full request.
return g.db
If you only want to unit test one function, rather than a full request, use
:meth:`.Flask.test_request_context` in a ``with`` block.
@app.teardown_appcontext
def teardown_db(exception):
db = g.pop('db', None)
.. code-block:: python
if db is not None:
db.close()
def generate_report(year):
format = request.args.get("format")
...
During a request, every call to ``get_db()`` will return the same
connection, and it will be closed automatically at the end of the
request.
You can use :class:`~werkzeug.local.LocalProxy` to make a new context
local from ``get_db()``::
from werkzeug.local import LocalProxy
db = LocalProxy(get_db)
Accessing ``db`` will call ``get_db`` internally, in the same way that
:data:`current_app` works.
with app.test_request_context(
"/make_report/2017", query_string={"format": "short"}
):
generate_report()
Events and Signals
------------------
.. _context-visibility:
The application will call functions registered with :meth:`~Flask.teardown_appcontext`
when the application context is popped.
Visibility of the Context
-------------------------
The following signals are sent: :data:`appcontext_pushed`,
:data:`appcontext_tearing_down`, and :data:`appcontext_popped`.
The context will have the same lifetime as an activity, such as a request, CLI
command, or ``with`` block. Various callbacks and signals registered with the
app will be run during the context.
When a Flask application handles a request, it pushes a requet context
to set the active application and request data. When it handles a CLI command,
it pushes an app context to set the active application. When the activity ends,
it pops that context. Proxy objects like :data:`.request`, :data:`.session`,
:data:`.g`, and :data:`.current_app`, are accessible while the context is pushed
and active, and are not accessible after the context is popped.
The context is unique to each thread (or other worker type). The proxies cannot
be passed to another worker, which has a different context space and will not
know about the active context in the parent's space.
Besides being scoped to each worker, the proxy object has a separate type and
identity than the proxied real object. In some cases you'll need access to the
real object, rather than the proxy. Use the
:meth:`~.LocalProxy._get_current_object` method in those cases.
.. code-block:: python
app = current_app._get_current_object()
my_signal.send(app)
Lifcycle of the Context
-----------------------
Flask dispatches a request in multiple stages which can affect the request,
response, and how errors are handled. See :doc:`/lifecycle` for a list of all
the steps, callbacks, and signals during each request. The following are the
steps directly related to the context.
- The app context is pushed, the proxies are available.
- The :data:`.appcontext_pushed` signal is sent.
- The request is dispatched.
- Any :meth:`.Flask.teardown_request` decorated functions are called.
- The :data:`.request_tearing_down` signal is sent.
- Any :meth:`.Flask.teardown_appcontext` decorated functions are called.
- The :data:`.appcontext_tearing_down` signal is sent.
- The app context is popped, the proxies are no longer available.
- The :data:`.appcontext_popped` signal is sent.
The teardown callbacks are called by the context when it is popped. They are
called even if there is an unhandled exception during dispatch. They may be
called multiple times in some test scenarios. This means there is no guarantee
that any other parts of the request dispatch have run. Be sure to write these
functions in a way that does not depend on other callbacks and will not fail.
How the Context Works
---------------------
Context locals are implemented using Python's :mod:`contextvars` and Werkzeug's
:class:`~werkzeug.local.LocalProxy`. Python's contextvars are a low level
structure to manage data local to a thread or coroutine. ``LocalProxy`` wraps
the contextvar so that access to any attributes and methods is forwarded to the
object stored in the contextvar.
The context is tracked like a stack, with the active context at the top of the
stack. Flask manages pushing and popping contexts during requests, CLI commands,
testing, ``with`` blocks, etc. The proxies access attributes on the active
context.
Because it is a stack, other contexts may be pushed to change the proxies during
an already active context. This is not a common pattern, but can be used in
advanced use cases. For example, a Flask application can be used as WSGI
middleware, calling another wrapped Flask app from a view.

View File

@ -169,19 +169,20 @@ infrastructure, packages with dependencies are no longer an issue and
there are very few reasons against having libraries that depend on others.
Thread Locals
-------------
Context Locals
--------------
Flask uses thread local objects (context local objects in fact, they
support greenlet contexts as well) for request, session and an extra
object you can put your own things on (:data:`~flask.g`). Why is that and
isn't that a bad idea?
Flask uses special context locals and proxies to provide access to the
current app and request data to any code running during a request, CLI command,
etc. Context locals are specific to the worker handling the activity, such as a
thread, process, coroutine, or greenlet.
Yes it is usually not such a bright idea to use thread locals. They cause
troubles for servers that are not based on the concept of threads and make
large applications harder to maintain. However Flask is just not designed
for large applications or asynchronous servers. Flask wants to make it
quick and easy to write a traditional web application.
The context and proxies help solve two development issues: circular imports, and
passing around global data. :data:`.current_app: can be used to access the
application object without needing to import the app object directly, avoiding
circular import issues. :data:`.request`, :data:`.session`, and :data`.g` can be
imported to access the current data for the request, rather than needing to
pass them as arguments through every single function in your project.
Async/await and ASGI support

View File

@ -67,7 +67,7 @@ application instance.
It is important that the app is not stored on the extension, don't do
``self.app = app``. The only time the extension should have direct
access to an app is during ``init_app``, otherwise it should use
:data:`current_app`.
:data:`.current_app`.
This allows the extension to support the application factory pattern,
avoids circular import issues when importing the extension instance
@ -105,7 +105,7 @@ during an extension's ``init_app`` method.
A common pattern is to use :meth:`~Flask.before_request` to initialize
some data or a connection at the beginning of each request, then
:meth:`~Flask.teardown_request` to clean it up at the end. This can be
stored on :data:`g`, discussed more below.
stored on :data:`.g`, discussed more below.
A more lazy approach is to provide a method that initializes and caches
the data or connection. For example, a ``ext.get_db`` method could
@ -179,13 +179,12 @@ name as a prefix, or as a namespace.
g._hello = SimpleNamespace()
g._hello.user_id = 2
The data in ``g`` lasts for an application context. An application
context is active when a request context is, or when a CLI command is
run. If you're storing something that should be closed, use
:meth:`~flask.Flask.teardown_appcontext` to ensure that it gets closed
when the application context ends. If it should only be valid during a
request, or would not be used in the CLI outside a request, use
:meth:`~flask.Flask.teardown_request`.
The data in ``g`` lasts for an application context. An application context is
active during a request, CLI command, or ``with app.app_context()`` block. If
you're storing something that should be closed, use
:meth:`~flask.Flask.teardown_appcontext` to ensure that it gets closed when the
app context ends. If it should only be valid during a request, or would not be
used in the CLI outside a request, use :meth:`~flask.Flask.teardown_request`.
Views and Models

View File

@ -52,7 +52,6 @@ community-maintained extensions to add even more functionality.
views
lifecycle
appcontext
reqcontext
blueprints
extensions
cli

View File

@ -117,15 +117,12 @@ the view function, and pass the return value back to the server. But there are m
parts that you can use to customize its behavior.
#. WSGI server calls the Flask object, which calls :meth:`.Flask.wsgi_app`.
#. A :class:`.RequestContext` object is created. This converts the WSGI ``environ``
dict into a :class:`.Request` object. It also creates an :class:`AppContext` object.
#. The :doc:`app context <appcontext>` is pushed, which makes :data:`.current_app` and
:data:`.g` available.
#. An :class:`.AppContext` object is created. This converts the WSGI ``environ``
dict into a :class:`.Request` object.
#. The :doc:`app context <appcontext>` is pushed, which makes
:data:`.current_app`, :data:`.g`, :data:`.request`, and :data:`.session`
available.
#. The :data:`.appcontext_pushed` signal is sent.
#. The :doc:`request context <reqcontext>` is pushed, which makes :attr:`.request` and
:class:`.session` available.
#. The session is opened, loading any existing session data using the app's
:attr:`~.Flask.session_interface`, an instance of :class:`.SessionInterface`.
#. The URL is matched against the URL rules registered with the :meth:`~.Flask.route`
decorator during application setup. If there is no match, the error - usually a 404,
405, or redirect - is stored to be handled later.
@ -141,7 +138,8 @@ parts that you can use to customize its behavior.
called to handle the error and return a response.
#. Whatever returned a response value - a before request function, the view, or an
error handler, that value is converted to a :class:`.Response` object.
#. Any :func:`~.after_this_request` decorated functions are called, then cleared.
#. Any :func:`~.after_this_request` decorated functions are called, which can modify
the response object. They are then cleared.
#. Any :meth:`~.Flask.after_request` decorated functions are called, which can modify
the response object.
#. The session is saved, persisting any modified session data using the app's
@ -154,14 +152,19 @@ parts that you can use to customize its behavior.
#. The response object's status, headers, and body are returned to the WSGI server.
#. Any :meth:`~.Flask.teardown_request` decorated functions are called.
#. The :data:`.request_tearing_down` signal is sent.
#. The request context is popped, :attr:`.request` and :class:`.session` are no longer
available.
#. Any :meth:`~.Flask.teardown_appcontext` decorated functions are called.
#. The :data:`.appcontext_tearing_down` signal is sent.
#. The app context is popped, :data:`.current_app` and :data:`.g` are no longer
available.
#. The app context is popped, :data:`.current_app`, :data:`.g`, :data:`.request`,
and :data:`.session` are no longer available.
#. The :data:`.appcontext_popped` signal is sent.
When executing a CLI command or plain app context without request data, the same
order of steps is followed, omitting the steps that refer to the request.
A :class:`Blueprint` can add handlers for these events that are specific to the
blueprint. The handlers for a blueprint will run if the blueprint
owns the route that matches the request.
There are even more decorators and customization points than this, but that aren't part
of every request lifecycle. They're more specific to certain things you might use during
a request, such as templates, building URLs, or handling JSON data. See the rest of this

View File

@ -131,9 +131,8 @@ Here is an example :file:`database.py` module for your application::
def init_db():
metadata.create_all(bind=engine)
As in the declarative approach, you need to close the session after
each request or application context shutdown. Put this into your
application module::
As in the declarative approach, you need to close the session after each app
context. Put this into your application module::
from yourapplication.database import db_session

View File

@ -1,9 +1,9 @@
Using SQLite 3 with Flask
=========================
In Flask you can easily implement the opening of database connections on
demand and closing them when the context dies (usually at the end of the
request).
You can implement a few functions to work with a SQLite connection during a
request context. The connection is created the first time it's accessed,
reused on subsequent access, until it is closed when the request context ends.
Here is a simple example of how you can use SQLite 3 with Flask::

View File

@ -49,13 +49,13 @@ the template.
Streaming with Context
----------------------
The :data:`~flask.request` will not be active while the generator is
running, because the view has already returned at that point. If you try
to access ``request``, you'll get a ``RuntimeError``.
The :data:`.request` proxy will not be active while the generator is
running, because the app has already returned control to the WSGI server at that
point. If you try to access ``request``, you'll get a ``RuntimeError``.
If your generator function relies on data in ``request``, use the
:func:`~flask.stream_with_context` wrapper. This will keep the request
context active during the generator.
:func:`.stream_with_context` wrapper. This will keep the request context active
during the generator.
.. code-block:: python

View File

@ -258,7 +258,7 @@ Why would you want to build URLs using the URL reversing function
For example, here we use the :meth:`~flask.Flask.test_request_context` method
to try out :func:`~flask.url_for`. :meth:`~flask.Flask.test_request_context`
tells Flask to behave as though it's handling a request even while we use a
Python shell. See :ref:`context-locals`.
Python shell. See :doc:`/appcontext`.
.. code-block:: python
@ -449,105 +449,58 @@ Here is a basic introduction to how the :class:`~markupsafe.Markup` class works:
Accessing Request Data
----------------------
For web applications it's crucial to react to the data a client sends to
the server. In Flask this information is provided by the global
:class:`~flask.request` object. If you have some experience with Python
you might be wondering how that object can be global and how Flask
manages to still be threadsafe. The answer is context locals:
For web applications it's crucial to react to the data a client sends to the
server. In Flask this information is provided by the global :data:`.request`
object, which is an instance of :class:`.Request`. This object has many
attributes and methods to work with the incoming request data, but here is a
broad overview. First it needs to be imported.
.. _context-locals:
Context Locals
``````````````
.. admonition:: Insider Information
If you want to understand how that works and how you can implement
tests with context locals, read this section, otherwise just skip it.
Certain objects in Flask are global objects, but not of the usual kind.
These objects are actually proxies to objects that are local to a specific
context. What a mouthful. But that is actually quite easy to understand.
Imagine the context being the handling thread. A request comes in and the
web server decides to spawn a new thread (or something else, the
underlying object is capable of dealing with concurrency systems other
than threads). When Flask starts its internal request handling it
figures out that the current thread is the active context and binds the
current application and the WSGI environments to that context (thread).
It does that in an intelligent way so that one application can invoke another
application without breaking.
So what does this mean to you? Basically you can completely ignore that
this is the case unless you are doing something like unit testing. You
will notice that code which depends on a request object will suddenly break
because there is no request object. The solution is creating a request
object yourself and binding it to the context. The easiest solution for
unit testing is to use the :meth:`~flask.Flask.test_request_context`
context manager. In combination with the ``with`` statement it will bind a
test request so that you can interact with it. Here is an example::
.. code-block:: python
from flask import request
with app.test_request_context('/hello', method='POST'):
# now you can do something with the request until the
# end of the with block, such as basic assertions:
assert request.path == '/hello'
assert request.method == 'POST'
If you have some experience with Python you might be wondering how that object
can be global when Flask handles multiple requests at a time. The answer is
that :data:`.request` is actually a proxy, pointing at whatever request is
currently being handled by a given worker, which is managed interanlly by Flask
and Python. See :doc:`/appcontext` for much more information.
The other possibility is passing a whole WSGI environment to the
:meth:`~flask.Flask.request_context` method::
The current request method is available in the :attr:`~.Request.method`
attribute. To access form data (data transmitted in a ``POST`` or ``PUT``
request), use the :attr:`~flask.Request.form` attribute, which behaves like a
dict.
with app.request_context(environ):
assert request.method == 'POST'
.. code-block:: python
The Request Object
``````````````````
The request object is documented in the API section and we will not cover
it here in detail (see :class:`~flask.Request`). Here is a broad overview of
some of the most common operations. First of all you have to import it from
the ``flask`` module::
from flask import request
The current request method is available by using the
:attr:`~flask.Request.method` attribute. To access form data (data
transmitted in a ``POST`` or ``PUT`` request) you can use the
:attr:`~flask.Request.form` attribute. Here is a full example of the two
attributes mentioned above::
@app.route('/login', methods=['POST', 'GET'])
@app.route("/login", methods=["GET", "POST"])
def login():
error = None
if request.method == 'POST':
if valid_login(request.form['username'],
request.form['password']):
return log_the_user_in(request.form['username'])
if request.method == "POST":
if valid_login(request.form["username"], request.form["password"]):
return store_login(request.form["username"])
else:
error = 'Invalid username/password'
# the code below is executed if the request method
# was GET or the credentials were invalid
return render_template('login.html', error=error)
error = "Invalid username or password"
What happens if the key does not exist in the ``form`` attribute? In that
case a special :exc:`KeyError` is raised. You can catch it like a
standard :exc:`KeyError` but if you don't do that, a HTTP 400 Bad Request
error page is shown instead. So for many situations you don't have to
deal with that problem.
# Executed if the request method was GET or the credentials were invalid.
return render_template("login.html", error=error)
To access parameters submitted in the URL (``?key=value``) you can use the
:attr:`~flask.Request.args` attribute::
If the key does not exist in ``form``, a special :exc:`KeyError` is raised. You
can catch it like a normal ``KeyError``, otherwise it will return a HTTP 400
Bad Request error page. You can also use the
:meth:`~werkzeug.datastructures.MultiDict.get` method to get a default
instead of an error.
To access parameters submitted in the URL (``?key=value``), use the
:attr:`~.Request.args` attribute. Key errors behave the same as ``form``,
returning a 400 response if not caught.
.. code-block:: python
searchword = request.args.get('key', '')
We recommend accessing URL parameters with `get` or by catching the
:exc:`KeyError` because users might change the URL and presenting them a 400
bad request page in that case is not user friendly.
For a full list of methods and attributes of the request object, head over
to the :class:`~flask.Request` documentation.
For a full list of methods and attributes of the request object, see the
:class:`~.Request` documentation.
File Uploads

View File

@ -1,243 +1,6 @@
.. currentmodule:: flask
:orphan:
The Request Context
===================
The request context keeps track of the request-level data during a
request. Rather than passing the request object to each function that
runs during a request, the :data:`request` and :data:`session` proxies
are accessed instead.
This is similar to :doc:`/appcontext`, which keeps track of the
application-level data independent of a request. A corresponding
application context is pushed when a request context is pushed.
Purpose of the Context
----------------------
When the :class:`Flask` application handles a request, it creates a
:class:`Request` object based on the environment it received from the
WSGI server. Because a *worker* (thread, process, or coroutine depending
on the server) handles only one request at a time, the request data can
be considered global to that worker during that request. Flask uses the
term *context local* for this.
Flask automatically *pushes* a request context when handling a request.
View functions, error handlers, and other functions that run during a
request will have access to the :data:`request` proxy, which points to
the request object for the current request.
Lifetime of the Context
-----------------------
When a Flask application begins handling a request, it pushes a request
context, which also pushes an :doc:`app context </appcontext>`. When the
request ends it pops the request context then the application context.
The context is unique to each thread (or other worker type).
:data:`request` cannot be passed to another thread, the other thread has
a different context space and will not know about the request the parent
thread was pointing to.
Context locals are implemented using Python's :mod:`contextvars` and
Werkzeug's :class:`~werkzeug.local.LocalProxy`. Python manages the
lifetime of context vars automatically, and local proxy wraps that
low-level interface to make the data easier to work with.
Manually Push a Context
-----------------------
If you try to access :data:`request`, or anything that uses it, outside
a request context, you'll get this error message:
.. code-block:: pytb
RuntimeError: Working outside of request context.
This typically means that you attempted to use functionality that
needed an active HTTP request. Consult the documentation on testing
for information about how to avoid this problem.
This should typically only happen when testing code that expects an
active request. One option is to use the
:meth:`test client <Flask.test_client>` to simulate a full request. Or
you can use :meth:`~Flask.test_request_context` in a ``with`` block, and
everything that runs in the block will have access to :data:`request`,
populated with your test data. ::
def generate_report(year):
format = request.args.get("format")
...
with app.test_request_context(
"/make_report/2017", query_string={"format": "short"}
):
generate_report()
If you see that error somewhere else in your code not related to
testing, it most likely indicates that you should move that code into a
view function.
For information on how to use the request context from the interactive
Python shell, see :doc:`/shell`.
How the Context Works
---------------------
The :meth:`Flask.wsgi_app` method is called to handle each request. It
manages the contexts during the request. Internally, the request and
application contexts work like stacks. When contexts are pushed, the
proxies that depend on them are available and point at information from
the top item.
When the request starts, a :class:`~ctx.RequestContext` is created and
pushed, which creates and pushes an :class:`~ctx.AppContext` first if
a context for that application is not already the top context. While
these contexts are pushed, the :data:`current_app`, :data:`g`,
:data:`request`, and :data:`session` proxies are available to the
original thread handling the request.
Other contexts may be pushed to change the proxies during a request.
While this is not a common pattern, it can be used in advanced
applications to, for example, do internal redirects or chain different
applications together.
After the request is dispatched and a response is generated and sent,
the request context is popped, which then pops the application context.
Immediately before they are popped, the :meth:`~Flask.teardown_request`
and :meth:`~Flask.teardown_appcontext` functions are executed. These
execute even if an unhandled exception occurred during dispatch.
.. _callbacks-and-errors:
Callbacks and Errors
--------------------
Flask dispatches a request in multiple stages which can affect the
request, response, and how errors are handled. The contexts are active
during all of these stages.
A :class:`Blueprint` can add handlers for these events that are specific
to the blueprint. The handlers for a blueprint will run if the blueprint
owns the route that matches the request.
#. Before each request, :meth:`~Flask.before_request` functions are
called. If one of these functions return a value, the other
functions are skipped. The return value is treated as the response
and the view function is not called.
#. If the :meth:`~Flask.before_request` functions did not return a
response, the view function for the matched route is called and
returns a response.
#. The return value of the view is converted into an actual response
object and passed to the :meth:`~Flask.after_request`
functions. Each function returns a modified or new response object.
#. After the response is returned, the contexts are popped, which calls
the :meth:`~Flask.teardown_request` and
:meth:`~Flask.teardown_appcontext` functions. These functions are
called even if an unhandled exception was raised at any point above.
If an exception is raised before the teardown functions, Flask tries to
match it with an :meth:`~Flask.errorhandler` function to handle the
exception and return a response. If no error handler is found, or the
handler itself raises an exception, Flask returns a generic
``500 Internal Server Error`` response. The teardown functions are still
called, and are passed the exception object.
If debug mode is enabled, unhandled exceptions are not converted to a
``500`` response and instead are propagated to the WSGI server. This
allows the development server to present the interactive debugger with
the traceback.
Teardown Callbacks
~~~~~~~~~~~~~~~~~~
The teardown callbacks are independent of the request dispatch, and are
instead called by the contexts when they are popped. The functions are
called even if there is an unhandled exception during dispatch, and for
manually pushed contexts. This means there is no guarantee that any
other parts of the request dispatch have run first. Be sure to write
these functions in a way that does not depend on other callbacks and
will not fail.
During testing, it can be useful to defer popping the contexts after the
request ends, so that their data can be accessed in the test function.
Use the :meth:`~Flask.test_client` as a ``with`` block to preserve the
contexts until the ``with`` block exits.
.. code-block:: python
from flask import Flask, request
app = Flask(__name__)
@app.route('/')
def hello():
print('during view')
return 'Hello, World!'
@app.teardown_request
def show_teardown(exception):
print('after with block')
with app.test_request_context():
print('during with block')
# teardown functions are called after the context with block exits
with app.test_client() as client:
client.get('/')
# the contexts are not popped even though the request ended
print(request.path)
# the contexts are popped and teardown functions are called after
# the client with block exits
Signals
~~~~~~~
The following signals are sent:
#. :data:`request_started` is sent before the :meth:`~Flask.before_request` functions
are called.
#. :data:`request_finished` is sent after the :meth:`~Flask.after_request` functions
are called.
#. :data:`got_request_exception` is sent when an exception begins to be handled, but
before an :meth:`~Flask.errorhandler` is looked up or called.
#. :data:`request_tearing_down` is sent after the :meth:`~Flask.teardown_request`
functions are called.
.. _notes-on-proxies:
Notes On Proxies
----------------
Some of the objects provided by Flask are proxies to other objects. The
proxies are accessed in the same way for each worker thread, but
point to the unique object bound to each worker behind the scenes as
described on this page.
Most of the time you don't have to care about that, but there are some
exceptions where it is good to know that this object is actually a proxy:
- The proxy objects cannot fake their type as the actual object types.
If you want to perform instance checks, you have to do that on the
object being proxied.
- The reference to the proxied object is needed in some situations,
such as sending :doc:`signals` or passing data to a background
thread.
If you need to access the underlying object that is proxied, use the
:meth:`~werkzeug.local.LocalProxy._get_current_object` method::
app = current_app._get_current_object()
my_signal.send(app)
Obsolete, see :doc:`/appcontext` instead.

View File

@ -1,56 +1,37 @@
Working with the Shell
======================
.. versionadded:: 0.3
One of the reasons everybody loves Python is the interactive shell. It allows
you to play around with code in real time and immediately get results back.
Flask provides the ``flask shell`` CLI command to start an interactive Python
shell with some setup done to make working with the Flask app easier.
One of the reasons everybody loves Python is the interactive shell. It
basically allows you to execute Python commands in real time and
immediately get results back. Flask itself does not come with an
interactive shell, because it does not require any specific setup upfront,
just import your application and start playing around.
.. code-block:: text
There are however some handy helpers to make playing around in the shell a
more pleasant experience. The main issue with interactive console
sessions is that you're not triggering a request like a browser does which
means that :data:`~flask.g`, :data:`~flask.request` and others are not
available. But the code you want to test might depend on them, so what
can you do?
This is where some helper functions come in handy. Keep in mind however
that these functions are not only there for interactive shell usage, but
also for unit testing and other situations that require a faked request
context.
Generally it's recommended that you read :doc:`reqcontext` first.
Command Line Interface
----------------------
Starting with Flask 0.11 the recommended way to work with the shell is the
``flask shell`` command which does a lot of this automatically for you.
For instance the shell is automatically initialized with a loaded
application context.
For more information see :doc:`/cli`.
$ flask shell
Creating a Request Context
--------------------------
``flask shell`` pushes an app context automatically, so :data:`.current_app` and
:data:`.g` are already available. However, there is no HTTP request being
handled in the shell, so :data:`.request` and :data:`.session` are not yet
available.
The easiest way to create a proper request context from the shell is by
using the :attr:`~flask.Flask.test_request_context` method which creates
us a :class:`~flask.ctx.RequestContext`:
>>> ctx = app.test_request_context()
Normally you would use the ``with`` statement to make this request object
active, but in the shell it's easier to use the
:meth:`~flask.ctx.RequestContext.push` and
:meth:`~flask.ctx.RequestContext.pop` methods by hand:
Normally you would use the ``with`` statement to make this context active, but
in the shell it's easier to call :meth:`~.RequestContext.push` and
:meth:`~.RequestContext.pop` manually:
>>> ctx.push()
From that point onwards you can work with the request object until you
call `pop`:
From that point onwards you can work with the request object until you call
``pop``:
>>> ctx.pop()

View File

@ -144,11 +144,10 @@ function, you can pass ``current_app._get_current_object()`` as sender.
Signals and Flask's Request Context
-----------------------------------
Signals fully support :doc:`reqcontext` when receiving signals.
Context-local variables are consistently available between
:data:`~flask.request_started` and :data:`~flask.request_finished`, so you can
rely on :class:`flask.g` and others as needed. Note the limitations described
in :ref:`signals-sending` and the :data:`~flask.request_tearing_down` signal.
Context-local proxies are available between :data:`~flask.request_started` and
:data:`~flask.request_finished`, so you can rely on :class:`flask.g` and others
as needed. Note the limitations described in :ref:`signals-sending` and the
:data:`~flask.request_tearing_down` signal.
Decorator Based Signal Subscriptions

View File

@ -275,11 +275,10 @@ command from the command line.
Tests that depend on an Active Context
--------------------------------------
You may have functions that are called from views or commands, that
expect an active :doc:`application context </appcontext>` or
:doc:`request context </reqcontext>` because they access ``request``,
``session``, or ``current_app``. Rather than testing them by making a
request or invoking the command, you can create and activate a context
You may have functions that are called from views or commands, that expect an
active :doc:`app context </appcontext>` because they access :data:`.request`,
:data:`.session`, :data:`.g`, or :data:`.current_app`. Rather than testing them by
making a request or invoking the command, you can create and activate a context
directly.
Use ``with app.app_context()`` to push an application context. For

View File

@ -305,7 +305,7 @@ The pattern ``{{ request.form['title'] or post['title'] }}`` is used to
choose what data appears in the form. When the form hasn't been
submitted, the original ``post`` data appears, but if invalid form data
was posted you want to display that so the user can fix the error, so
``request.form`` is used instead. :data:`request` is another variable
``request.form`` is used instead. :data:`.request` is another variable
that's automatically available in templates.

View File

@ -60,17 +60,17 @@ response is sent.
if db is not None:
db.close()
:data:`g` is a special object that is unique for each request. It is
:data:`.g` is a special object that is unique for each request. It is
used to store data that might be accessed by multiple functions during
the request. The connection is stored and reused instead of creating a
new connection if ``get_db`` is called a second time in the same
request.
:data:`current_app` is another special object that points to the Flask
:data:`.current_app` is another special object that points to the Flask
application handling the request. Since you used an application factory,
there is no application object when writing the rest of your code.
``get_db`` will be called when the application has been created and is
handling a request, so :data:`current_app` can be used.
handling a request, so :data:`.current_app` can be used.
:func:`sqlite3.connect` establishes a connection to the file pointed at
by the ``DATABASE`` configuration key. This file doesn't have to exist

View File

@ -71,7 +71,7 @@ specific sections.
{% block content %}{% endblock %}
</section>
:data:`g` is automatically available in templates. Based on if
:data:`.g` is automatically available in templates. Based on if
``g.user`` is set (from ``load_logged_in_user``), either the username
and a log out link are displayed, or links to register and log in
are displayed. :func:`url_for` is also automatically available, and is

View File

@ -311,7 +311,7 @@ input and error messages without writing the same code three times.
The tests for the ``login`` view are very similar to those for
``register``. Rather than testing the data in the database,
:data:`session` should have ``user_id`` set after logging in.
:data:`.session` should have ``user_id`` set after logging in.
.. code-block:: python
:caption: ``tests/test_auth.py``
@ -336,10 +336,10 @@ The tests for the ``login`` view are very similar to those for
assert message in response.data
Using ``client`` in a ``with`` block allows accessing context variables
such as :data:`session` after the response is returned. Normally,
such as :data:`.session` after the response is returned. Normally,
accessing ``session`` outside of a request would raise an error.
Testing ``logout`` is the opposite of ``login``. :data:`session` should
Testing ``logout`` is the opposite of ``login``. :data:`.session` should
not contain ``user_id`` after logging out.
.. code-block:: python

View File

@ -208,13 +208,13 @@ There are a few differences from the ``register`` view:
password in the same way as the stored hash and securely compares
them. If they match, the password is valid.
#. :data:`session` is a :class:`dict` that stores data across requests.
#. :data:`.session` is a :class:`dict` that stores data across requests.
When validation succeeds, the user's ``id`` is stored in a new
session. The data is stored in a *cookie* that is sent to the
browser, and the browser then sends it back with subsequent requests.
Flask securely *signs* the data so that it can't be tampered with.
Now that the user's ``id`` is stored in the :data:`session`, it will be
Now that the user's ``id`` is stored in the :data:`.session`, it will be
available on subsequent requests. At the beginning of each request, if
a user is logged in their information should be loaded and made
available to other views.
@ -236,7 +236,7 @@ available to other views.
:meth:`bp.before_app_request() <Blueprint.before_app_request>` registers
a function that runs before the view function, no matter what URL is
requested. ``load_logged_in_user`` checks if a user id is stored in the
:data:`session` and gets that user's data from the database, storing it
:data:`.session` and gets that user's data from the database, storing it
on :data:`g.user <g>`, which lasts for the length of the request. If
there is no user id, or if the id doesn't exist, ``g.user`` will be
``None``.
@ -245,7 +245,7 @@ there is no user id, or if the id doesn't exist, ``g.user`` will be
Logout
------
To log out, you need to remove the user id from the :data:`session`.
To log out, you need to remove the user id from the :data:`.session`.
Then ``load_logged_in_user`` won't load a user on subsequent requests.
.. code-block:: python

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

View File

@ -628,7 +628,7 @@ class FlaskGroup(AppGroup):
# Push an app context for the loaded app unless it is already
# active somehow. This makes the context available to parameter
# and command callbacks without needing @with_appcontext.
if not current_app or current_app._get_current_object() is not app: # type: ignore[attr-defined]
if not current_app or current_app._get_current_object() is not app:
ctx.with_resource(app.app_context())
return app.cli.get_command(ctx, name)

View File

@ -1,20 +1,20 @@
from __future__ import annotations
import contextvars
import sys
import typing as t
from functools import update_wrapper
from types import TracebackType
from werkzeug.exceptions import HTTPException
from werkzeug.routing import MapAdapter
from . import typing as ft
from .globals import _cv_app
from .globals import _cv_request
from .signals import appcontext_popped
from .signals import appcontext_pushed
if t.TYPE_CHECKING: # pragma: no cover
if t.TYPE_CHECKING:
import typing_extensions as te
from _typeshed.wsgi import WSGIEnvironment
from .app import Flask
@ -31,7 +31,7 @@ class _AppCtxGlobals:
application context.
Creating an app context automatically creates this object, which is
made available as the :data:`g` proxy.
made available as the :data:`.g` proxy.
.. describe:: 'key' in g
@ -117,29 +117,27 @@ class _AppCtxGlobals:
def after_this_request(
f: ft.AfterRequestCallable[t.Any],
) -> ft.AfterRequestCallable[t.Any]:
"""Executes a function after this request. This is useful to modify
response objects. The function is passed the response object and has
to return the same or a new one.
"""Decorate a function to run after the current request. The behavior is the
same as :meth:`.Flask.after_request`, except it only applies to the current
request, rather than every request. Therefore, it must be used within a
request context, rather than during setup.
Example::
.. code-block:: python
@app.route('/')
@app.route("/")
def index():
@after_this_request
def add_header(response):
response.headers['X-Foo'] = 'Parachute'
response.headers["X-Foo"] = "Parachute"
return response
return 'Hello World!'
This is more useful if a function other than the view function wants to
modify a response. For instance think of a decorator that wants to add
some headers without converting the return value into a response object.
return "Hello, World!"
.. versionadded:: 0.9
"""
ctx = _cv_request.get(None)
ctx = _cv_app.get(None)
if ctx is None:
if ctx is None or not ctx.has_request:
raise RuntimeError(
"'after_this_request' can only be used when a request"
" context is active, such as in a view function."
@ -153,13 +151,27 @@ F = t.TypeVar("F", bound=t.Callable[..., t.Any])
def copy_current_request_context(f: F) -> F:
"""A helper function that decorates a function to retain the current
request context. This is useful when working with greenlets. The moment
the function is decorated a copy of the request context is created and
then pushed when the function is called. The current session is also
included in the copied request context.
"""Decorate a function to run inside the current request context. This can
be used when starting a background task, otherwise it will not see the app
and request objects that were active in the parent.
Example::
.. warning::
Due to the following caveats, it is often safer (and simpler) to pass
the data you need when starting the task, rather than using this and
relying on the context objects.
In order to avoid execution switching partially though reading data, either
read the request body (access ``form``, ``json``, ``data``, etc) before
starting the task, or use a lock. This can be an issue when using threading,
but shouldn't be an issue when using greenlet/gevent or asyncio.
If the task will access ``session``, be sure to do so in the parent as well
so that the ``Vary: cookie`` header will be set. Modifying ``session`` in
the task should be avoided, as it may execute after the response cookie has
already been written.
.. code-block:: python
import gevent
from flask import copy_current_request_context
@ -176,7 +188,7 @@ def copy_current_request_context(f: F) -> F:
.. versionadded:: 0.10
"""
ctx = _cv_request.get(None)
ctx = _cv_app.get(None)
if ctx is None:
raise RuntimeError(
@ -194,41 +206,50 @@ def copy_current_request_context(f: F) -> F:
def has_request_context() -> bool:
"""If you have code that wants to test if a request context is there or
not this function can be used. For instance, you may want to take advantage
of request information if the request object is available, but fail
silently if it is unavailable.
"""Test if an app context is active and if it has request information.
::
.. code-block:: python
class User(db.Model):
from flask import has_request_context, request
def __init__(self, username, remote_addr=None):
self.username = username
if remote_addr is None and has_request_context():
remote_addr = request.remote_addr
self.remote_addr = remote_addr
if has_request_context():
remote_addr = request.remote_addr
Alternatively you can also just test any of the context bound objects
(such as :class:`request` or :class:`g`) for truthness::
If a request context is active, the :data:`.request` and :data:`.session`
context proxies will available and ``True``, otherwise ``False``. You can
use that to test the data you use, rather than using this function.
class User(db.Model):
.. code-block:: python
def __init__(self, username, remote_addr=None):
self.username = username
if remote_addr is None and request:
remote_addr = request.remote_addr
self.remote_addr = remote_addr
from flask import request
if request:
remote_addr = request.remote_addr
.. versionadded:: 0.7
"""
return _cv_request.get(None) is not None
return (ctx := _cv_app.get(None)) is not None and ctx.has_request
def has_app_context() -> bool:
"""Works like :func:`has_request_context` but for the application
context. You can also just do a boolean check on the
:data:`current_app` object instead.
"""Test if an app context is active. Unlike :func:`has_request_context`
this can be true outside a request, such as in a CLI command.
.. code-block:: python
from flask import has_app_context, g
if has_app_context():
g.cached_data = ...
If an app context is active, the :data:`.g` and :data:`.current_app` context
proxies will available and ``True``, otherwise ``False``. You can use that
to test the data you use, rather than using this function.
from flask import g
if g:
g.cached_data = ...
.. versionadded:: 0.9
"""
@ -236,214 +257,260 @@ def has_app_context() -> bool:
class AppContext:
"""The app context contains application-specific information. An app
context is created and pushed at the beginning of each request if
one is not already active. An app context is also pushed when
running CLI commands.
"""
"""An app context contains information about an app, and about the request
when handling a request. A context is pushed at the beginning of each
request and CLI command, and popped at the end. The context is referred to
as a "request context" if it has request information, and an "app context"
if not.
def __init__(self, app: Flask) -> None:
self.app = app
self.url_adapter = app.create_url_adapter(None)
self.g: _AppCtxGlobals = app.app_ctx_globals_class()
self._cv_tokens: list[contextvars.Token[AppContext]] = []
Do not use this class directly. Use :meth:`.Flask.app_context` to create an
app context if needed during setup, and :meth:`.Flask.test_request_context`
to create a request context if needed during tests.
def push(self) -> None:
"""Binds the app context to the current context."""
self._cv_tokens.append(_cv_app.set(self))
appcontext_pushed.send(self.app, _async_wrapper=self.app.ensure_sync)
When the context is popped, it will evaluate all the teardown functions
registered with :meth:`~flask.Flask.teardown_request` (if handling a
request) then :meth:`.Flask.teardown_appcontext`.
def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore
"""Pops the app context."""
try:
if len(self._cv_tokens) == 1:
if exc is _sentinel:
exc = sys.exc_info()[1]
self.app.do_teardown_appcontext(exc)
finally:
ctx = _cv_app.get()
_cv_app.reset(self._cv_tokens.pop())
When using the interactive debugger, the context will be restored so
``request`` is still accessible. Similarly, the test client can preserve the
context after the request ends. However, teardown functions may already have
closed some resources such as database connections, and will run again when
the restored context is popped.
if ctx is not self:
raise AssertionError(
f"Popped wrong app context. ({ctx!r} instead of {self!r})"
)
:param app: The application this context represents.
:param request: The request data this context represents.
:param session: The session data this context represents. If not given,
loaded from the request on first access.
appcontext_popped.send(self.app, _async_wrapper=self.app.ensure_sync)
.. versionchanged:: 3.2
Merged with ``RequestContext``. The ``RequestContext`` alias will be
removed in Flask 4.0.
def __enter__(self) -> AppContext:
self.push()
return self
.. versionchanged:: 3.2
A combined app and request context is pushed for every request and CLI
command, rather than trying to detect if an app context is already
pushed.
def __exit__(
self,
exc_type: type | None,
exc_value: BaseException | None,
tb: TracebackType | None,
) -> None:
self.pop(exc_value)
class RequestContext:
"""The request context contains per-request information. The Flask
app creates and pushes it at the beginning of the request, then pops
it at the end of the request. It will create the URL adapter and
request object for the WSGI environment provided.
Do not attempt to use this class directly, instead use
:meth:`~flask.Flask.test_request_context` and
:meth:`~flask.Flask.request_context` to create this object.
When the request context is popped, it will evaluate all the
functions registered on the application for teardown execution
(:meth:`~flask.Flask.teardown_request`).
The request context is automatically popped at the end of the
request. When using the interactive debugger, the context will be
restored so ``request`` is still accessible. Similarly, the test
client can preserve the context after the request ends. However,
teardown functions may already have closed some resources such as
database connections.
.. versionchanged:: 3.2
The session is loaded the first time it is accessed, rather than when
the context is pushed.
"""
def __init__(
self,
app: Flask,
environ: WSGIEnvironment,
*,
request: Request | None = None,
session: SessionMixin | None = None,
) -> None:
self.app = app
if request is None:
request = app.request_class(environ)
request.json_module = app.json
self.request: Request = request
self.url_adapter = None
try:
self.url_adapter = app.create_url_adapter(self.request)
except HTTPException as e:
self.request.routing_exception = e
self.flashes: list[tuple[str, str]] | None = None
self.session: SessionMixin | None = session
# Functions that should be executed after the request on the response
# object. These will be called before the regular "after_request"
# functions.
"""The application represented by this context. Accessed through
:data:`.current_app`.
"""
self.g: _AppCtxGlobals = app.app_ctx_globals_class()
"""The global data for this context. Accessed through :data:`.g`."""
self.url_adapter: MapAdapter | None = None
"""The URL adapter bound to the request, or the app if not in a request.
May be ``None`` if binding failed.
"""
self._request: Request | None = request
self._session: SessionMixin | None = session
self._flashes: list[tuple[str, str]] | None = None
self._after_request_functions: list[ft.AfterRequestCallable[t.Any]] = []
self._cv_tokens: list[
tuple[contextvars.Token[RequestContext], AppContext | None]
] = []
try:
self.url_adapter = app.create_url_adapter(self._request)
except HTTPException as e:
if self._request is not None:
self._request.routing_exception = e
def copy(self) -> RequestContext:
"""Creates a copy of this request context with the same request object.
This can be used to move a request context to a different greenlet.
Because the actual request object is the same this cannot be used to
move a request context to a different thread unless access to the
request object is locked.
self._cv_token: contextvars.Token[AppContext] | None = None
"""The previous state to restore when popping."""
.. versionadded:: 0.10
self._push_count: int = 0
"""Track nested pushes of this context. Cleanup will only run once the
original push has been popped.
"""
@classmethod
def from_environ(cls, app: Flask, environ: WSGIEnvironment, /) -> te.Self:
"""Create an app context with request data from the given WSGI environ.
:param app: The application this context represents.
:param environ: The request data this context represents.
"""
request = app.request_class(environ)
request.json_module = app.json
return cls(app, request=request)
@property
def has_request(self) -> bool:
"""True if this context was created with request data."""
return self._request is not None
def copy(self) -> te.Self:
"""Create a new context with the same data objects as this context. See
:func:`.copy_current_request_context`.
.. versionchanged:: 1.1
The current session object is used instead of reloading the original
data. This prevents `flask.session` pointing to an out-of-date object.
The current session data is used instead of reloading the original data.
.. versionadded:: 0.10
"""
return self.__class__(
self.app,
environ=self.request.environ,
request=self.request,
session=self.session,
request=self._request,
session=self._session,
)
@property
def request(self) -> Request:
"""The request object associated with this context. Accessed through
:data:`.request`. Only available in request contexts, otherwise raises
:exc:`RuntimeError`.
"""
if self._request is None:
raise RuntimeError("There is no request in this context.")
return self._request
@property
def session(self) -> SessionMixin:
"""The session object associated with this context. Accessed through
:data:`.session`. Only available in request contexts, otherwise raises
:exc:`RuntimeError`.
"""
if self._request is None:
raise RuntimeError("There is no request in this context.")
if self._session is None:
si = self.app.session_interface
self._session = si.open_session(self.app, self.request)
if self._session is None:
self._session = si.make_null_session(self.app)
return self._session
def match_request(self) -> None:
"""Can be overridden by a subclass to hook into the matching
of the request.
"""Apply routing to the current request, storing either the matched
endpoint and args, or a routing exception.
"""
try:
result = self.url_adapter.match(return_rule=True) # type: ignore
self.request.url_rule, self.request.view_args = result # type: ignore
result = self.url_adapter.match(return_rule=True) # type: ignore[union-attr]
except HTTPException as e:
self.request.routing_exception = e
self._request.routing_exception = e # type: ignore[union-attr]
else:
self._request.url_rule, self._request.view_args = result # type: ignore[union-attr]
def push(self) -> None:
# Before we push the request context we have to ensure that there
# is an application context.
app_ctx = _cv_app.get(None)
"""Push this context so that it is the active context. If this is a
request context, calls :meth:`match_request` to perform routing with
the context active.
if app_ctx is None or app_ctx.app is not self.app:
app_ctx = self.app.app_context()
app_ctx.push()
else:
app_ctx = None
Typically, this is not used directly. Instead, use a ``with`` block
to manage the context.
self._cv_tokens.append((_cv_request.set(self), app_ctx))
In some situations, such as streaming or testing, the context may be
pushed multiple times. It will only trigger matching and signals if it
is not currently pushed.
"""
self._push_count += 1
# 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
# pushed, otherwise stream_with_context loses the session.
if self.session is None:
session_interface = self.app.session_interface
self.session = session_interface.open_session(self.app, self.request)
if self._cv_token is not None:
return
if self.session is None:
self.session = session_interface.make_null_session(self.app)
self._cv_token = _cv_app.set(self)
appcontext_pushed.send(self.app, _async_wrapper=self.app.ensure_sync)
# 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:
if self._request is not None and self.url_adapter is not None:
self.match_request()
def pop(self, exc: BaseException | None = _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
:meth:`~flask.Flask.teardown_request` decorator.
def pop(self, exc: BaseException | None = None) -> None:
"""Pop this context so that it is no longer the active context. Then
call teardown functions and signals.
Typically, this is not used directly. Instead, use a ``with`` block
to manage the context.
This context must currently be the active context, otherwise a
:exc:`RuntimeError` is raised. In some situations, such as streaming or
testing, the context may have been pushed multiple times. It will only
trigger cleanup once it has been popped as many times as it was pushed.
Until then, it will remain the active context.
:param exc: An unhandled exception that was raised while the context was
active. Passed to teardown functions.
.. versionchanged:: 0.9
Added the `exc` argument.
Added the ``exc`` argument.
"""
clear_request = len(self._cv_tokens) == 1
if self._cv_token is None:
raise RuntimeError(f"Cannot pop this context ({self!r}), it is not pushed.")
ctx = _cv_app.get(None)
if ctx is None or self._cv_token is None:
raise RuntimeError(
f"Cannot pop this context ({self!r}), there is no active context."
)
if ctx is not self:
raise RuntimeError(
f"Cannot pop this context ({self!r}), it is not the active"
f" context ({ctx!r})."
)
self._push_count -= 1
if self._push_count > 0:
return
try:
if clear_request:
if exc is _sentinel:
exc = sys.exc_info()[1]
if self._request is not None:
self.app.do_teardown_request(exc)
request_close = getattr(self.request, "close", None)
if request_close is not None:
request_close()
self._request.close()
finally:
ctx = _cv_request.get()
token, app_ctx = self._cv_tokens.pop()
_cv_request.reset(token)
self.app.do_teardown_appcontext(exc)
_cv_app.reset(self._cv_token)
self._cv_token = None
appcontext_popped.send(self.app, _async_wrapper=self.app.ensure_sync)
# get rid of circular dependencies at the end of the request
# so that we don't require the GC to be active.
if clear_request:
ctx.request.environ["werkzeug.request"] = None
if app_ctx is not None:
app_ctx.pop(exc)
if ctx is not self:
raise AssertionError(
f"Popped wrong request context. ({ctx!r} instead of {self!r})"
)
def __enter__(self) -> RequestContext:
def __enter__(self) -> te.Self:
self.push()
return self
def __exit__(
self,
exc_type: type | None,
exc_type: type[BaseException] | None,
exc_value: BaseException | None,
tb: TracebackType | None,
) -> None:
self.pop(exc_value)
def __repr__(self) -> str:
return (
f"<{type(self).__name__} {self.request.url!r}"
f" [{self.request.method}] of {self.app.name}>"
if self._request is not None:
return (
f"<{type(self).__name__} {id(self)} of {self.app.name},"
f" {self.request.method} {self.request.url!r}>"
)
return f"<{type(self).__name__} {id(self)} of {self.app.name}>"
def __getattr__(name: str) -> t.Any:
import warnings
if name == "RequestContext":
warnings.warn(
"'RequestContext' has merged with 'AppContext', and will be removed"
" in Flask 4.0. Use 'AppContext' instead.",
DeprecationWarning,
stacklevel=2,
)
return AppContext
raise AttributeError(name)

View File

@ -6,7 +6,7 @@ from jinja2.loaders import BaseLoader
from werkzeug.routing import RequestRedirect
from .blueprints import Blueprint
from .globals import request_ctx
from .globals import _cv_app
from .sansio.app import App
if t.TYPE_CHECKING:
@ -136,8 +136,9 @@ def explain_template_loading_attempts(
info = [f"Locating template {template!r}:"]
total_found = 0
blueprint = None
if request_ctx and request_ctx.request.blueprint is not None:
blueprint = request_ctx.request.blueprint
if (ctx := _cv_app.get(None)) is not None and ctx.has_request:
blueprint = ctx.request.blueprint
for idx, (loader, srcobj, triple) in enumerate(attempts):
if isinstance(srcobj, App):

View File

@ -9,43 +9,69 @@ if t.TYPE_CHECKING: # pragma: no cover
from .app import Flask
from .ctx import _AppCtxGlobals
from .ctx import AppContext
from .ctx import RequestContext
from .sessions import SessionMixin
from .wrappers import Request
T = t.TypeVar("T", covariant=True)
class ProxyMixin(t.Protocol[T]):
def _get_current_object(self) -> T: ...
# These subclasses inform type checkers that the proxy objects look like the
# proxied type along with the _get_current_object method.
class FlaskProxy(ProxyMixin[Flask], Flask): ...
class AppContextProxy(ProxyMixin[AppContext], AppContext): ...
class _AppCtxGlobalsProxy(ProxyMixin[_AppCtxGlobals], _AppCtxGlobals): ...
class RequestProxy(ProxyMixin[Request], Request): ...
class SessionMixinProxy(ProxyMixin[SessionMixin], SessionMixin): ...
_no_app_msg = """\
Working outside of application context.
This typically means that you attempted to use functionality that needed
the current application. To solve this, set up an application context
with app.app_context(). See the documentation for more information.\
Attempted to use functionality that expected a current application to be set. To
solve this, set up an app context using 'with app.app_context()'. See the
documentation on app context for more information.\
"""
_cv_app: ContextVar[AppContext] = ContextVar("flask.app_ctx")
app_ctx: AppContext = LocalProxy( # type: ignore[assignment]
app_ctx: AppContextProxy = LocalProxy( # type: ignore[assignment]
_cv_app, unbound_message=_no_app_msg
)
current_app: Flask = LocalProxy( # type: ignore[assignment]
current_app: FlaskProxy = LocalProxy( # type: ignore[assignment]
_cv_app, "app", unbound_message=_no_app_msg
)
g: _AppCtxGlobals = LocalProxy( # type: ignore[assignment]
g: _AppCtxGlobalsProxy = LocalProxy( # type: ignore[assignment]
_cv_app, "g", unbound_message=_no_app_msg
)
_no_req_msg = """\
Working outside of request context.
This typically means that you attempted to use functionality that needed
an active HTTP request. Consult the documentation on testing for
information about how to avoid this problem.\
Attempted to use functionality that expected an active HTTP request. See the
documentation on request context for more information.\
"""
_cv_request: ContextVar[RequestContext] = ContextVar("flask.request_ctx")
request_ctx: RequestContext = LocalProxy( # type: ignore[assignment]
_cv_request, unbound_message=_no_req_msg
request: RequestProxy = LocalProxy( # type: ignore[assignment]
_cv_app, "request", unbound_message=_no_req_msg
)
request: Request = LocalProxy( # type: ignore[assignment]
_cv_request, "request", unbound_message=_no_req_msg
)
session: SessionMixin = LocalProxy( # type: ignore[assignment]
_cv_request, "session", unbound_message=_no_req_msg
session: SessionMixinProxy = LocalProxy( # type: ignore[assignment]
_cv_app, "session", unbound_message=_no_req_msg
)
def __getattr__(name: str) -> t.Any:
import warnings
if name == "request_ctx":
warnings.warn(
"'request_ctx' has merged with 'app_ctx', and will be removed"
" in Flask 4.0. Use 'app_ctx' instead.",
DeprecationWarning,
stacklevel=2,
)
return app_ctx
raise AttributeError(name)

View File

@ -14,10 +14,9 @@ from werkzeug.utils import redirect as _wz_redirect
from werkzeug.wrappers import Response as BaseResponse
from .globals import _cv_app
from .globals import _cv_request
from .globals import app_ctx
from .globals import current_app
from .globals import request
from .globals import request_ctx
from .globals import session
from .signals import message_flashed
@ -64,7 +63,7 @@ def stream_with_context(
generator_or_function: t.Iterator[t.AnyStr] | t.Callable[..., t.Iterator[t.AnyStr]],
) -> t.Iterator[t.AnyStr] | t.Callable[[t.Iterator[t.AnyStr]], t.Iterator[t.AnyStr]]:
"""Wrap a response generator function so that it runs inside the current
request context. This keeps :data:`request`, :data:`session`, and :data:`g`
request context. This keeps :data:`.request`, :data:`.session`, and :data:`.g`
available, even though at the point the generator runs the request context
will typically have ended.
@ -112,22 +111,15 @@ def stream_with_context(
return update_wrapper(decorator, generator_or_function) # type: ignore[arg-type]
def generator() -> t.Iterator[t.AnyStr]:
if (req_ctx := _cv_request.get(None)) is None:
if (ctx := _cv_app.get(None)) is None:
raise RuntimeError(
"'stream_with_context' can only be used when a request"
" context is active, such as in a view function."
)
app_ctx = _cv_app.get()
# Setup code below will run the generator to this point, so that the
# current contexts are recorded. The contexts must be pushed after,
# otherwise their ContextVar will record the wrong event loop during
# async view functions.
yield None # type: ignore[misc]
with ctx:
yield None # type: ignore[misc]
# Push the app context first, so that the request context does not
# automatically create and push a different app context.
with app_ctx, req_ctx:
try:
yield from gen
finally:
@ -135,9 +127,9 @@ def stream_with_context(
if hasattr(gen, "close"):
gen.close()
# Execute the generator to the sentinel value. This ensures the context is
# preserved in the generator's state. Further iteration will push the
# context and yield from the original iterator.
# Execute the generator to the sentinel value. This captures the current
# context and pushes it to preserve it. Further iteration will yield from
# the original iterator.
wrapped_g = generator()
next(wrapped_g)
return wrapped_g
@ -264,8 +256,8 @@ def redirect(
Calls ``current_app.redirect`` if available instead of always
using Werkzeug's default ``redirect``.
"""
if current_app:
return current_app.redirect(location, code=code)
if (ctx := _cv_app.get(None)) is not None:
return ctx.app.redirect(location, code=code)
return _wz_redirect(location, code=code, Response=Response)
@ -287,8 +279,8 @@ def abort(code: int | BaseResponse, *args: t.Any, **kwargs: t.Any) -> t.NoReturn
Calls ``current_app.aborter`` if available instead of always
using Werkzeug's default ``abort``.
"""
if current_app:
current_app.aborter(code, *args, **kwargs)
if (ctx := _cv_app.get(None)) is not None:
ctx.app.aborter(code, *args, **kwargs)
_wz_abort(code, *args, **kwargs)
@ -340,7 +332,7 @@ def flash(message: str, category: str = "message") -> None:
flashes = session.get("_flashes", [])
flashes.append((category, message))
session["_flashes"] = flashes
app = current_app._get_current_object() # type: ignore
app = current_app._get_current_object()
message_flashed.send(
app,
_async_wrapper=app.ensure_sync,
@ -380,10 +372,10 @@ def get_flashed_messages(
:param category_filter: filter of categories to limit return values. Only
categories in the list will be returned.
"""
flashes = request_ctx.flashes
flashes = app_ctx._flashes
if flashes is None:
flashes = session.pop("_flashes") if "_flashes" in session else []
request_ctx.flashes = flashes
app_ctx._flashes = flashes
if category_filter:
flashes = list(filter(lambda f: f[0] in category_filter, flashes))
if not with_categories:
@ -392,14 +384,16 @@ def get_flashed_messages(
def _prepare_send_file_kwargs(**kwargs: t.Any) -> dict[str, t.Any]:
ctx = app_ctx._get_current_object()
if kwargs.get("max_age") is None:
kwargs["max_age"] = current_app.get_send_file_max_age
kwargs["max_age"] = ctx.app.get_send_file_max_age
kwargs.update(
environ=request.environ,
use_x_sendfile=current_app.config["USE_X_SENDFILE"],
response_class=current_app.response_class,
_root_path=current_app.root_path,
environ=ctx.request.environ,
use_x_sendfile=ctx.app.config["USE_X_SENDFILE"],
response_class=ctx.app.response_class,
_root_path=ctx.app.root_path,
)
return kwargs

View File

@ -141,7 +141,7 @@ def jsonify(*args: t.Any, **kwargs: t.Any) -> Response:
mimetype. A dict or list returned from a view will be converted to a
JSON response automatically without needing to call this.
This requires an active request or application context, and calls
This requires an active app context, and calls
:meth:`app.json.response() <flask.json.provider.JSONProvider.response>`.
In debug mode, the output is formatted with indentation to make it

View File

@ -177,11 +177,8 @@ class App(Scaffold):
#: 3. Return None instead of AttributeError on unexpected attributes.
#: 4. Raise exception if an unexpected attr is set, a "controlled" flask.g.
#:
#: In Flask 0.9 this property was called `request_globals_class` but it
#: was changed in 0.10 to :attr:`app_ctx_globals_class` because the
#: flask.g object is now application context scoped.
#:
#: .. versionadded:: 0.10
#: Renamed from ``request_globals_class`.
app_ctx_globals_class = _AppCtxGlobals
#: The class that is used for the ``config`` attribute of this app.
@ -825,10 +822,9 @@ class App(Scaffold):
@setupmethod
def teardown_appcontext(self, f: T_teardown) -> T_teardown:
"""Registers a function to be called when the application
context is popped. The application context is typically popped
after the request context for each request, at the end of CLI
commands, or after a manually pushed context ends.
"""Registers a function to be called when the app context is popped. The
context is popped at the end of a request, CLI command, or manual ``with``
block.
.. code-block:: python
@ -837,9 +833,7 @@ class App(Scaffold):
When the ``with`` block exits (or ``ctx.pop()`` is called), the
teardown functions are called just before the app context is
made inactive. Since a request context typically also manages an
application context it would also be called when you pop a
request context.
made inactive.
When a teardown function was called because of an unhandled
exception it will be passed an error object. If an

View File

@ -507,8 +507,8 @@ class Scaffold:
@setupmethod
def teardown_request(self, f: T_teardown) -> T_teardown:
"""Register a function to be called when the request context is
popped. Typically this happens at the end of each request, but
contexts may be pushed manually as well during testing.
popped. Typically, this happens at the end of each request, but
contexts may be pushed manually during testing.
.. code-block:: python

View File

@ -8,9 +8,7 @@ from jinja2 import Template
from jinja2 import TemplateNotFound
from .globals import _cv_app
from .globals import _cv_request
from .globals import current_app
from .globals import request
from .helpers import stream_with_context
from .signals import before_render_template
from .signals import template_rendered
@ -25,14 +23,16 @@ def _default_template_ctx_processor() -> dict[str, t.Any]:
"""Default template context processor. Injects `request`,
`session` and `g`.
"""
appctx = _cv_app.get(None)
reqctx = _cv_request.get(None)
ctx = _cv_app.get(None)
rv: dict[str, t.Any] = {}
if appctx is not None:
rv["g"] = appctx.g
if reqctx is not None:
rv["request"] = reqctx.request
rv["session"] = reqctx.session
if ctx is not None:
rv["g"] = ctx.g
if ctx.has_request:
rv["request"] = ctx.request
rv["session"] = ctx.session
return rv
@ -145,7 +145,7 @@ def render_template(
a list is given, the first name to exist will be rendered.
:param context: The variables to make available in the template.
"""
app = current_app._get_current_object() # type: ignore[attr-defined]
app = current_app._get_current_object()
template = app.jinja_env.get_or_select_template(template_name_or_list)
return _render(app, template, context)
@ -157,7 +157,7 @@ def render_template_string(source: str, **context: t.Any) -> str:
:param source: The source code of the template to render.
:param context: The variables to make available in the template.
"""
app = current_app._get_current_object() # type: ignore[attr-defined]
app = current_app._get_current_object()
template = app.jinja_env.from_string(source)
return _render(app, template, context)
@ -176,13 +176,7 @@ def _stream(
app, _async_wrapper=app.ensure_sync, template=template, context=context
)
rv = generate()
# If a request context is active, keep it while generating.
if request:
rv = stream_with_context(rv)
return rv
return stream_with_context(generate())
def stream_template(
@ -199,7 +193,7 @@ def stream_template(
.. versionadded:: 2.2
"""
app = current_app._get_current_object() # type: ignore[attr-defined]
app = current_app._get_current_object()
template = app.jinja_env.get_or_select_template(template_name_or_list)
return _stream(app, template, context)
@ -214,6 +208,6 @@ def stream_template_string(source: str, **context: t.Any) -> t.Iterator[str]:
.. versionadded:: 2.2
"""
app = current_app._get_current_object() # type: ignore[attr-defined]
app = current_app._get_current_object()
template = app.jinja_env.from_string(source)
return _stream(app, template, context)

View File

@ -107,10 +107,10 @@ def _get_werkzeug_version() -> str:
class FlaskClient(Client):
"""Works like a regular Werkzeug test client but has knowledge about
Flask's contexts to defer the cleanup of the request context until
the end of a ``with`` block. For general information about how to
use this class refer to :class:`werkzeug.test.Client`.
"""Works like a regular Werkzeug test client, with additional behavior for
Flask. Can defer the cleanup of the request context until the end of a
``with`` block. For general information about how to use this class refer to
:class:`werkzeug.test.Client`.
.. versionchanged:: 0.12
`app.test_client()` includes preset default environment, which can be

View File

@ -5,7 +5,7 @@ import pytest
from _pytest import monkeypatch
from flask import Flask
from flask.globals import request_ctx
from flask.globals import app_ctx as _app_ctx
@pytest.fixture(scope="session", autouse=True)
@ -83,16 +83,17 @@ def test_apps(monkeypatch):
@pytest.fixture(autouse=True)
def leak_detector():
"""Fails if any app contexts are still pushed when a test ends. Pops all
contexts so subsequent tests are not affected.
"""
yield
# make sure we're not leaking a request context since we are
# testing flask internally in debug mode in a few cases
leaks = []
while request_ctx:
leaks.append(request_ctx._get_current_object())
request_ctx.pop()
assert leaks == []
while _app_ctx:
leaks.append(_app_ctx._get_current_object())
_app_ctx.pop()
assert not leaks
@pytest.fixture

View File

@ -2,7 +2,6 @@ import pytest
import flask
from flask.globals import app_ctx
from flask.globals import request_ctx
def test_basic_url_generation(app):
@ -107,7 +106,8 @@ def test_app_tearing_down_with_handled_exception_by_app_handler(app, client):
with app.app_context():
client.get("/")
assert cleanup_stuff == [None]
# teardown request context, and with block context
assert cleanup_stuff == [None, None]
def test_app_tearing_down_with_unhandled_exception(app, client):
@ -126,9 +126,11 @@ def test_app_tearing_down_with_unhandled_exception(app, client):
with app.app_context():
client.get("/")
assert len(cleanup_stuff) == 1
assert len(cleanup_stuff) == 2
assert isinstance(cleanup_stuff[0], ValueError)
assert str(cleanup_stuff[0]) == "dummy"
# exception propagated, seen by request context and with block context
assert cleanup_stuff[0] is cleanup_stuff[1]
def test_app_ctx_globals_methods(app, app_ctx):
@ -178,8 +180,7 @@ def test_context_refcounts(app, client):
@app.route("/")
def index():
with app_ctx:
with request_ctx:
pass
pass
assert flask.request.environ["werkzeug.request"] is not None
return ""

View File

@ -3,7 +3,7 @@ import warnings
import pytest
import flask
from flask.globals import request_ctx
from flask.globals import app_ctx
from flask.sessions import SecureCookieSessionInterface
from flask.sessions import SessionInterface
@ -153,12 +153,12 @@ class TestGreenletContextCopying:
@app.route("/")
def index():
flask.session["fizz"] = "buzz"
reqctx = request_ctx.copy()
ctx = app_ctx.copy()
def g():
assert not flask.request
assert not flask.current_app
with reqctx:
with ctx:
assert flask.request
assert flask.current_app == app
assert flask.request.path == "/"

View File

@ -1,5 +1,5 @@
import flask
from flask.globals import request_ctx
from flask.globals import app_ctx
from flask.sessions import SessionInterface
@ -14,7 +14,7 @@ def test_open_session_with_endpoint():
pass
def open_session(self, app, request):
request_ctx.match_request()
app_ctx.match_request()
assert request.endpoint is not None
app = flask.Flask(__name__)

View File

@ -6,7 +6,7 @@ import pytest
import flask
from flask import appcontext_popped
from flask.cli import ScriptInfo
from flask.globals import _cv_request
from flask.globals import _cv_app
from flask.json import jsonify
from flask.testing import EnvironBuilder
from flask.testing import FlaskCliRunner
@ -382,4 +382,4 @@ def test_client_pop_all_preserved(app, req_ctx, client):
# close the response, releasing the context held by stream_with_context
rv.close()
# only req_ctx fixture should still be pushed
assert _cv_request.get(None) is req_ctx
assert _cv_app.get(None) is req_ctx