Compare commits
No commits in common. "main" and "stable" have entirely different histories.
55 changed files with 2942 additions and 2343 deletions
1
.github/workflows/tests.yaml
vendored
1
.github/workflows/tests.yaml
vendored
|
|
@ -25,6 +25,7 @@ jobs:
|
|||
- {python: '3.12'}
|
||||
- {python: '3.11'}
|
||||
- {python: '3.10'}
|
||||
- {python: '3.9'}
|
||||
- {name: PyPy, python: 'pypy-3.11', tox: pypy3.11}
|
||||
- {name: Minimum Versions, python: '3.14', tox: tests-min}
|
||||
- {name: Development Versions, python: '3.10', tox: tests-dev}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: 5e2fb545eba1ea9dc051f6f962d52fe8f76a9794 # frozen: v0.15.13
|
||||
rev: c60c980e561ed3e73101667fe8365c609d19a438 # frozen: v0.15.9
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
- id: ruff-format
|
||||
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||
rev: fa60a193803535a9e2accdb3ca4b1b584b1150cb # frozen: 0.11.15
|
||||
rev: 0397b68f6f88c024f1d2b355a9818779f6336d16 # frozen: 0.11.3
|
||||
hooks:
|
||||
- id: uv-lock
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: 2ccb47ff45ad361a21071a7eedda4c37e6ae8c5a # frozen: v2.4.2
|
||||
hooks:
|
||||
- id: codespell
|
||||
args: ['--write-changes']
|
||||
additional_dependencies:
|
||||
- tomli
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0
|
||||
hooks:
|
||||
|
|
|
|||
32
CHANGES.rst
32
CHANGES.rst
|
|
@ -1,35 +1,3 @@
|
|||
Version 3.2.0
|
||||
-------------
|
||||
|
||||
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`
|
||||
- Many ``Flask`` methods involved in request dispatch now take the current
|
||||
``AppContext`` as the first parameter, instead of using the proxy objects.
|
||||
If subclasses were overriding these methods, the old signature is detected,
|
||||
shows a deprecation warning, and will continue to work during the
|
||||
deprecation period. :issue:`5815`
|
||||
- All teardown callbacks are called, even if any raise an error. :pr:`5928`
|
||||
- The ``should_ignore_error`` is deprecated. Handle errors as needed in
|
||||
teardown handlers instead. :issue:`5816`
|
||||
- ``template_filter``, ``template_test``, and ``template_global`` decorators
|
||||
can be used without parentheses. :issue:`5729`
|
||||
- ``redirect`` returns a ``303`` status code by default instead of ``302``.
|
||||
This tells the client to always switch to ``GET``, rather than only
|
||||
switching ``POST`` to ``GET``. This preserves the current behavior of
|
||||
``GET`` and ``POST`` redirects, and is also correct for frontend libraries
|
||||
such as HTMX. :issue:`5895`
|
||||
- ``provide_automatic_options=True`` can be used to enable it for a view when
|
||||
it's disabled in config. Previously, only disabling worked. :issue:`5916`
|
||||
- ``Flask.select_jinja_autoescape`` uses case-insensitive comparison instead
|
||||
of only lower case file extensions. :pr:`6012`
|
||||
|
||||
|
||||
Version 3.1.3
|
||||
-------------
|
||||
|
||||
|
|
|
|||
131
docs/api.rst
131
docs/api.rst
|
|
@ -31,15 +31,17 @@ Incoming Request Data
|
|||
:inherited-members:
|
||||
:exclude-members: json_module
|
||||
|
||||
.. data:: request
|
||||
.. attribute:: request
|
||||
|
||||
A proxy to the request data for the current request, an instance of
|
||||
:class:`.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.
|
||||
|
||||
This is only available when a :doc:`request context </appcontext>` is
|
||||
active.
|
||||
This is a proxy. See :ref:`notes-on-proxies` for more information.
|
||||
|
||||
This is a proxy. See :ref:`context-visibility` for more information.
|
||||
The request object is an instance of a :class:`~flask.Request`.
|
||||
|
||||
|
||||
Response Objects
|
||||
|
|
@ -60,33 +62,40 @@ 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 :data:`.session` proxy.
|
||||
To access the current session you can use the :class:`session` object:
|
||||
|
||||
.. data:: session
|
||||
.. class:: session
|
||||
|
||||
A proxy to the session data for the current request, an instance of
|
||||
:class:`.SessionMixin`.
|
||||
The session object works pretty much like an ordinary dict, with the
|
||||
difference that it keeps track of modifications.
|
||||
|
||||
This is only available when a :doc:`request context </appcontext>` is
|
||||
active.
|
||||
This is a proxy. See :ref:`notes-on-proxies` for more information.
|
||||
|
||||
This is a proxy. See :ref:`context-visibility` for more information.
|
||||
The following attributes are interesting:
|
||||
|
||||
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.
|
||||
.. attribute:: new
|
||||
|
||||
.. code-block:: python
|
||||
``True`` if the session is new, ``False`` otherwise.
|
||||
|
||||
# appending to a list is not detected
|
||||
session["numbers"].append(42)
|
||||
.. 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)
|
||||
# so mark it as modified yourself
|
||||
session.modified = True
|
||||
|
||||
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`.
|
||||
.. 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.
|
||||
|
||||
|
||||
Session Interface
|
||||
|
|
@ -149,21 +158,20 @@ 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 :data:`.request` and :data:`.session`.
|
||||
thing, like it does for :class:`request` and :class:`session`.
|
||||
|
||||
.. data:: g
|
||||
|
||||
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`.
|
||||
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`.
|
||||
|
||||
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 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 only available when an :doc:`app context </appcontext>` is active.
|
||||
|
||||
This is a proxy. See :ref:`context-visibility` for more information.
|
||||
This is a proxy. See :ref:`notes-on-proxies` for more information.
|
||||
|
||||
.. versionchanged:: 0.10
|
||||
Bound to the application context instead of the request context.
|
||||
|
|
@ -177,16 +185,17 @@ Useful Functions and Classes
|
|||
|
||||
.. data:: current_app
|
||||
|
||||
A proxy to the :class:`.Flask` application handling the current request or
|
||||
other activity.
|
||||
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.
|
||||
|
||||
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 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 only available when an :doc:`app context </appcontext>` is active.
|
||||
|
||||
This is a proxy. See :ref:`context-visibility` for more information.
|
||||
This is a proxy. See :ref:`notes-on-proxies` for more information.
|
||||
|
||||
.. autofunction:: has_request_context
|
||||
|
||||
|
|
@ -290,31 +299,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
|
||||
|
||||
A proxy to the active :class:`.AppContext`.
|
||||
The current :class:`~flask.ctx.AppContext`. If an app 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:`.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.
|
||||
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.
|
||||
|
||||
.. autoclass:: flask.blueprints.BlueprintSetupState
|
||||
:members:
|
||||
|
|
@ -596,7 +605,7 @@ This specifies that ``/users/`` will be the URL for page one and
|
|||
``/users/page/N`` will be the URL for page ``N``.
|
||||
|
||||
If a URL contains a default value, it will be redirected to its simpler
|
||||
form with a 308 redirect. In the above example, ``/users/page/1`` will
|
||||
form with a 301 redirect. In the above example, ``/users/page/1`` will
|
||||
be redirected to ``/users/``. If your route handles ``GET`` and ``POST``
|
||||
requests, make sure the default route only handles ``GET``, as redirects
|
||||
can't preserve form data. ::
|
||||
|
|
|
|||
|
|
@ -1,63 +1,74 @@
|
|||
The App and Request Context
|
||||
===========================
|
||||
.. currentmodule:: flask
|
||||
|
||||
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
|
||||
=======================
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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 context and proxies help solve two development issues: circular imports, and
|
||||
passing around global data during a request.
|
||||
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 :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 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.
|
||||
|
||||
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 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`.
|
||||
|
||||
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.
|
||||
Flask will also automatically push an app context when running CLI
|
||||
commands registered with :attr:`Flask.cli` using ``@app.cli.command()``.
|
||||
|
||||
|
||||
Context During Setup
|
||||
--------------------
|
||||
Lifetime of the Context
|
||||
-----------------------
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
.. code-block:: pytb
|
||||
|
||||
RuntimeError: Working outside of application 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.
|
||||
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().
|
||||
|
||||
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.
|
||||
|
||||
.. code-block:: python
|
||||
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`. ::
|
||||
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
|
|
@ -67,121 +78,70 @@ access to the ``app``. Use :meth:`.Flask.app_context` in a ``with`` block.
|
|||
|
||||
return app
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
|
||||
Context During Testing
|
||||
----------------------
|
||||
Storing Data
|
||||
------------
|
||||
|
||||
See :doc:`/testing` for detailed information about managing the context during
|
||||
tests.
|
||||
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.
|
||||
|
||||
If you try to access :data:`.request`, :data:`.session`, or anything that uses
|
||||
it, outside a request context, you'll get this error message:
|
||||
.. 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.
|
||||
|
||||
.. code-block:: pytb
|
||||
A common use for :data:`g` is to manage resources during a request.
|
||||
|
||||
RuntimeError: Working outside of request context.
|
||||
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.
|
||||
|
||||
Attempted to use functionality that expected an active HTTP request. See the
|
||||
documentation on request context for more information.
|
||||
For example, you can manage a database connection using this pattern::
|
||||
|
||||
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.
|
||||
from flask import g
|
||||
|
||||
The primary way to solve this is to use :meth:`.Flask.test_client` to simulate
|
||||
a full request.
|
||||
def get_db():
|
||||
if 'db' not in g:
|
||||
g.db = connect_to_database()
|
||||
|
||||
If you only want to unit test one function, rather than a full request, use
|
||||
:meth:`.Flask.test_request_context` in a ``with`` block.
|
||||
return g.db
|
||||
|
||||
.. code-block:: python
|
||||
@app.teardown_appcontext
|
||||
def teardown_db(exception):
|
||||
db = g.pop('db', None)
|
||||
|
||||
def generate_report(year):
|
||||
format = request.args.get("format")
|
||||
...
|
||||
if db is not None:
|
||||
db.close()
|
||||
|
||||
with app.test_request_context(
|
||||
"/make_report/2017", query_string={"format": "short"}
|
||||
):
|
||||
generate_report()
|
||||
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.
|
||||
|
||||
|
||||
.. _context-visibility:
|
||||
Events and Signals
|
||||
------------------
|
||||
|
||||
Visibility of the Context
|
||||
-------------------------
|
||||
The application will call functions registered with :meth:`~Flask.teardown_appcontext`
|
||||
when the application context is 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 request 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)
|
||||
|
||||
|
||||
Lifecycle 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. All callbacks are
|
||||
called even if any raise an error.
|
||||
|
||||
|
||||
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.
|
||||
The following signals are sent: :data:`appcontext_pushed`,
|
||||
:data:`appcontext_tearing_down`, and :data:`appcontext_popped`.
|
||||
|
|
|
|||
|
|
@ -445,7 +445,7 @@ The following configuration values are used internally by Flask:
|
|||
.. versionchanged:: 2.3
|
||||
``ENV`` was removed.
|
||||
|
||||
.. versionadded:: 3.1
|
||||
.. versionadded:: 3.10
|
||||
Added :data:`PROVIDE_AUTOMATIC_OPTIONS` to control the default
|
||||
addition of autogenerated OPTIONS responses.
|
||||
|
||||
|
|
|
|||
|
|
@ -169,20 +169,19 @@ infrastructure, packages with dependencies are no longer an issue and
|
|||
there are very few reasons against having libraries that depend on others.
|
||||
|
||||
|
||||
Context Locals
|
||||
--------------
|
||||
Thread Locals
|
||||
-------------
|
||||
|
||||
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.
|
||||
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?
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
|
||||
Async/await and ASGI support
|
||||
|
|
|
|||
|
|
@ -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,12 +179,13 @@ 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 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`.
|
||||
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`.
|
||||
|
||||
|
||||
Views and Models
|
||||
|
|
@ -293,13 +294,11 @@ ecosystem remain consistent and compatible.
|
|||
indicate minimum compatibility support. For example,
|
||||
``sqlalchemy>=1.4``.
|
||||
9. Indicate the versions of Python supported using ``python_requires=">=version"``.
|
||||
Flask and Pallets policy is to support all Python versions that are not
|
||||
within six months of end of life (EOL). See Python's `EOL calendar`_ for
|
||||
timing.
|
||||
Flask itself supports Python >=3.9 as of October 2024, and this will update
|
||||
over time.
|
||||
|
||||
.. _PyPI: https://pypi.org/search/?c=Framework+%3A%3A+Flask
|
||||
.. _Discord Chat: https://discord.gg/pallets
|
||||
.. _GitHub Discussions: https://github.com/pallets/flask/discussions
|
||||
.. _Official Pallets Themes: https://pypi.org/project/Pallets-Sphinx-Themes/
|
||||
.. _Pallets-Eco: https://github.com/pallets-eco
|
||||
.. _EOL calendar: https://devguide.python.org/versions/
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ community-maintained extensions to add even more functionality.
|
|||
views
|
||||
lifecycle
|
||||
appcontext
|
||||
reqcontext
|
||||
blueprints
|
||||
extensions
|
||||
cli
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ Installation
|
|||
Python Version
|
||||
--------------
|
||||
|
||||
We recommend using the latest version of Python. Flask supports Python 3.10 and newer.
|
||||
We recommend using the latest version of Python. Flask supports Python 3.9 and newer.
|
||||
|
||||
|
||||
Dependencies
|
||||
|
|
|
|||
|
|
@ -117,12 +117,15 @@ 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`.
|
||||
#. 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.
|
||||
#. 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.
|
||||
#. 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.
|
||||
|
|
@ -138,8 +141,7 @@ 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, which can modify
|
||||
the response object. They are then cleared.
|
||||
#. Any :func:`~.after_this_request` decorated functions are called, 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
|
||||
|
|
@ -152,19 +154,14 @@ 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`, :data:`.g`, :data:`.request`,
|
||||
and :data:`.session` are no longer available.
|
||||
#. The app context is popped, :data:`.current_app` and :data:`.g` 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
|
||||
|
|
|
|||
|
|
@ -131,8 +131,9 @@ 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 app
|
||||
context. Put this into your application module::
|
||||
As in the declarative approach, you need to close the session after
|
||||
each request or application context shutdown. Put this into your
|
||||
application module::
|
||||
|
||||
from yourapplication.database import db_session
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
Using SQLite 3 with Flask
|
||||
=========================
|
||||
|
||||
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.
|
||||
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).
|
||||
|
||||
Here is a simple example of how you can use SQLite 3 with Flask::
|
||||
|
||||
|
|
|
|||
|
|
@ -64,13 +64,13 @@ the template.
|
|||
Streaming with Context
|
||||
----------------------
|
||||
|
||||
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``.
|
||||
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``.
|
||||
|
||||
If your generator function relies on data in ``request``, use the
|
||||
:func:`.stream_with_context` wrapper. This will keep the request context active
|
||||
during the generator.
|
||||
:func:`~flask.stream_with_context` wrapper. This will keep the request
|
||||
context active during the generator.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
|
|||
|
|
@ -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 :doc:`/appcontext`.
|
||||
Python shell. See :ref:`context-locals`.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
@ -449,58 +449,105 @@ 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 :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.
|
||||
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:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
.. _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::
|
||||
|
||||
from flask import request
|
||||
|
||||
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 internally by Flask
|
||||
and Python. See :doc:`/appcontext` for much more information.
|
||||
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'
|
||||
|
||||
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.
|
||||
The other possibility is passing a whole WSGI environment to the
|
||||
:meth:`~flask.Flask.request_context` method::
|
||||
|
||||
.. code-block:: python
|
||||
with app.request_context(environ):
|
||||
assert request.method == 'POST'
|
||||
|
||||
@app.route("/login", methods=["GET", "POST"])
|
||||
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'])
|
||||
def login():
|
||||
error = None
|
||||
|
||||
if request.method == "POST":
|
||||
if valid_login(request.form["username"], request.form["password"]):
|
||||
return store_login(request.form["username"])
|
||||
if request.method == 'POST':
|
||||
if valid_login(request.form['username'],
|
||||
request.form['password']):
|
||||
return log_the_user_in(request.form['username'])
|
||||
else:
|
||||
error = "Invalid username or password"
|
||||
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)
|
||||
|
||||
# Executed if the request method was GET or the credentials were invalid.
|
||||
return render_template("login.html", error=error)
|
||||
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.
|
||||
|
||||
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
|
||||
To access parameters submitted in the URL (``?key=value``) you can use the
|
||||
:attr:`~flask.Request.args` attribute::
|
||||
|
||||
searchword = request.args.get('key', '')
|
||||
|
||||
For a full list of methods and attributes of the request object, see the
|
||||
:class:`~.Request` documentation.
|
||||
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.
|
||||
|
||||
|
||||
File Uploads
|
||||
|
|
|
|||
|
|
@ -1,6 +1,243 @@
|
|||
:orphan:
|
||||
.. currentmodule:: flask
|
||||
|
||||
The Request Context
|
||||
===================
|
||||
|
||||
Obsolete, see :doc:`/appcontext` instead.
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1,37 +1,56 @@
|
|||
Working with the Shell
|
||||
======================
|
||||
|
||||
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.
|
||||
.. versionadded:: 0.3
|
||||
|
||||
.. code-block:: text
|
||||
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.
|
||||
|
||||
$ flask shell
|
||||
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`.
|
||||
|
||||
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 context active, but
|
||||
in the shell it's easier to call :meth:`~.RequestContext.push` and
|
||||
:meth:`~.RequestContext.pop` manually:
|
||||
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:
|
||||
|
||||
>>> 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()
|
||||
|
||||
|
|
|
|||
|
|
@ -144,10 +144,11 @@ function, you can pass ``current_app._get_current_object()`` as sender.
|
|||
Signals and Flask's Request Context
|
||||
-----------------------------------
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
|
||||
Decorator Based Signal Subscriptions
|
||||
|
|
|
|||
|
|
@ -137,58 +137,32 @@ using in this block.
|
|||
|
||||
.. _registering-filters:
|
||||
|
||||
Registering Filters, Tests, and Globals
|
||||
---------------------------------------
|
||||
Registering Filters
|
||||
-------------------
|
||||
|
||||
The Flask app and blueprints provide decorators and methods to register your own
|
||||
filters, tests, and global functions for use in Jinja templates. They all follow
|
||||
the same pattern, so the following examples only discuss filters.
|
||||
If you want to register your own filters in Jinja you have two ways to do
|
||||
that. You can either put them by hand into the
|
||||
:attr:`~flask.Flask.jinja_env` of the application or use the
|
||||
:meth:`~flask.Flask.template_filter` decorator.
|
||||
|
||||
Decorate a function with :meth:`~.Flask.template_filter` to register it as a
|
||||
template filter.
|
||||
The two following examples work the same and both reverse an object::
|
||||
|
||||
.. code-block:: python
|
||||
@app.template_filter('reverse')
|
||||
def reverse_filter(s):
|
||||
return s[::-1]
|
||||
|
||||
@app.template_filter
|
||||
def reverse(s):
|
||||
return reversed(s)
|
||||
def reverse_filter(s):
|
||||
return s[::-1]
|
||||
app.jinja_env.filters['reverse'] = reverse_filter
|
||||
|
||||
.. code-block:: jinja
|
||||
In case of the decorator the argument is optional if you want to use the
|
||||
function name as name of the filter. Once registered, you can use the filter
|
||||
in your templates in the same way as Jinja's builtin filters, for example if
|
||||
you have a Python list in context called `mylist`::
|
||||
|
||||
{% for item in data | reverse %}
|
||||
{% for x in mylist | reverse %}
|
||||
{% endfor %}
|
||||
|
||||
By default it will use the name of the function as the name of the filter, but
|
||||
that can be changed by passing a name to the decorator.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.template_filter("reverse")
|
||||
def reverse_filter(s):
|
||||
return reversed(s)
|
||||
|
||||
A filter can be registered separately using :meth:`~.Flask.add_template_filter`.
|
||||
The name is optional and will use the function name if not given.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def reverse_filter(s):
|
||||
return reversed(s)
|
||||
|
||||
app.add_template_filter(reverse_filter, "reverse")
|
||||
|
||||
For template tests, use the :meth:`~.Flask.template_test` decorator or
|
||||
:meth:`~.Flask.add_template_test` method. For template global functions, use the
|
||||
:meth:`~.Flask.template_global` decorator or :meth:`~.Flask.add_template_global`
|
||||
method.
|
||||
|
||||
The same methods also exist on :class:`.Blueprint`, prefixed with ``app_`` to
|
||||
indicate that the registered functions will be available to all templates, not
|
||||
only when rendering from within the blueprint.
|
||||
|
||||
The Jinja environment is also available as :attr:`~.Flask.jinja_env`. It may be
|
||||
modified directly, as you would when using Jinja outside Flask.
|
||||
|
||||
|
||||
Context Processors
|
||||
------------------
|
||||
|
|
|
|||
|
|
@ -275,10 +275,11 @@ 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:`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
|
||||
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
|
||||
directly.
|
||||
|
||||
Use ``with app.app_context()`` to push an application context. For
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -81,7 +81,8 @@ By the end, your project layout will look like this:
|
|||
│ ├── test_auth.py
|
||||
│ └── test_blog.py
|
||||
├── .venv/
|
||||
└── pyproject.toml
|
||||
├── pyproject.toml
|
||||
└── MANIFEST.in
|
||||
|
||||
If you're using version control, the following files that are generated
|
||||
while running your project should be ignored. There may be other files
|
||||
|
|
@ -102,4 +103,8 @@ write. For example, with git:
|
|||
.coverage
|
||||
htmlcov/
|
||||
|
||||
dist/
|
||||
build/
|
||||
*.egg-info/
|
||||
|
||||
Continue to :doc:`factory`.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "Flask"
|
||||
version = "3.2.0.dev"
|
||||
version = "3.1.3"
|
||||
description = "A simple framework for building complex web applications."
|
||||
readme = "README.md"
|
||||
license = "BSD-3-Clause"
|
||||
|
|
@ -19,10 +19,11 @@ classifiers = [
|
|||
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
||||
"Typing :: Typed",
|
||||
]
|
||||
requires-python = ">=3.10"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = [
|
||||
"blinker>=1.9.0",
|
||||
"click>=8.1.3",
|
||||
"importlib-metadata>=3.6.0; python_version < '3.10'",
|
||||
"itsdangerous>=2.2.0",
|
||||
"jinja2>=3.1.2",
|
||||
"markupsafe>=2.1.1",
|
||||
|
|
@ -57,6 +58,7 @@ pre-commit = [
|
|||
]
|
||||
tests = [
|
||||
"asgiref",
|
||||
"greenlet",
|
||||
"pytest",
|
||||
"python-dotenv",
|
||||
]
|
||||
|
|
@ -124,7 +126,7 @@ exclude_also = [
|
|||
]
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.10"
|
||||
python_version = "3.9"
|
||||
files = ["src", "tests/type_check"]
|
||||
show_error_codes = true
|
||||
pretty = true
|
||||
|
|
@ -140,7 +142,7 @@ module = [
|
|||
ignore_missing_imports = true
|
||||
|
||||
[tool.pyright]
|
||||
pythonVersion = "3.10"
|
||||
pythonVersion = "3.9"
|
||||
include = ["src", "tests/type_check"]
|
||||
typeCheckingMode = "basic"
|
||||
|
||||
|
|
@ -170,7 +172,7 @@ ignore-words-list = "te"
|
|||
[tool.tox]
|
||||
env_list = [
|
||||
"py3.14", "py3.14t",
|
||||
"py3.13", "py3.12", "py3.11", "py3.10",
|
||||
"py3.13", "py3.12", "py3.11", "py3.10", "py3.9",
|
||||
"pypy3.11",
|
||||
"tests-min", "tests-dev",
|
||||
"style",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import typing as t
|
||||
|
||||
from . import json as json
|
||||
from .app import Flask as Flask
|
||||
from .blueprints import Blueprint as Blueprint
|
||||
|
|
@ -37,3 +41,21 @@ from .templating import stream_template as stream_template
|
|||
from .templating import stream_template_string as stream_template_string
|
||||
from .wrappers import Request as Request
|
||||
from .wrappers import Response as Response
|
||||
|
||||
if not t.TYPE_CHECKING:
|
||||
|
||||
def __getattr__(name: str) -> t.Any:
|
||||
if name == "__version__":
|
||||
import importlib.metadata
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"The '__version__' attribute is deprecated and will be removed in"
|
||||
" Flask 3.2. Use feature detection or"
|
||||
" 'importlib.metadata.version(\"flask\")' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return importlib.metadata.version("flask")
|
||||
|
||||
raise AttributeError(name)
|
||||
|
|
|
|||
357
src/flask/app.py
357
src/flask/app.py
|
|
@ -1,13 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import collections.abc as cabc
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
import typing as t
|
||||
import weakref
|
||||
from datetime import timedelta
|
||||
from functools import update_wrapper
|
||||
from inspect import iscoroutinefunction
|
||||
from itertools import chain
|
||||
from types import TracebackType
|
||||
|
|
@ -31,17 +29,20 @@ 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 app_ctx
|
||||
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 _CollectErrors
|
||||
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
|
||||
|
|
@ -77,35 +78,6 @@ def _make_timedelta(value: timedelta | int | None) -> timedelta | None:
|
|||
return timedelta(seconds=value)
|
||||
|
||||
|
||||
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
||||
|
||||
|
||||
# Other methods may call the overridden method with the new ctx arg. Remove it
|
||||
# and call the method with the remaining args.
|
||||
def remove_ctx(f: F) -> F:
|
||||
def wrapper(self: Flask, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
||||
if args and isinstance(args[0], AppContext):
|
||||
args = args[1:]
|
||||
|
||||
return f(self, *args, **kwargs)
|
||||
|
||||
return update_wrapper(wrapper, f) # type: ignore[return-value]
|
||||
|
||||
|
||||
# The overridden method may call super().base_method without the new ctx arg.
|
||||
# Add it to the args for the call.
|
||||
def add_ctx(f: F) -> F:
|
||||
def wrapper(self: Flask, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
||||
if not args:
|
||||
args = (app_ctx._get_current_object(),)
|
||||
elif not isinstance(args[0], AppContext):
|
||||
args = (app_ctx._get_current_object(), *args)
|
||||
|
||||
return f(self, *args, **kwargs)
|
||||
|
||||
return update_wrapper(wrapper, f) # type: ignore[return-value]
|
||||
|
||||
|
||||
class Flask(App):
|
||||
"""The flask object implements a WSGI application and acts as the central
|
||||
object. It is passed the name of the module or package of the
|
||||
|
|
@ -251,62 +223,6 @@ class Flask(App):
|
|||
#: .. versionadded:: 0.8
|
||||
session_interface: SessionInterface = SecureCookieSessionInterface()
|
||||
|
||||
def __init_subclass__(cls, **kwargs: t.Any) -> None:
|
||||
import warnings
|
||||
|
||||
# These method signatures were updated to take a ctx param. Detect
|
||||
# overridden methods in subclasses that still have the old signature.
|
||||
# Show a deprecation warning and wrap to call with correct args.
|
||||
for method in (
|
||||
cls.handle_http_exception,
|
||||
cls.handle_user_exception,
|
||||
cls.handle_exception,
|
||||
cls.log_exception,
|
||||
cls.dispatch_request,
|
||||
cls.full_dispatch_request,
|
||||
cls.finalize_request,
|
||||
cls.make_default_options_response,
|
||||
cls.preprocess_request,
|
||||
cls.process_response,
|
||||
cls.do_teardown_request,
|
||||
cls.do_teardown_appcontext,
|
||||
):
|
||||
base_method = getattr(Flask, method.__name__)
|
||||
|
||||
if method is base_method:
|
||||
# not overridden
|
||||
continue
|
||||
|
||||
# get the second parameter (first is self)
|
||||
iter_params = iter(inspect.signature(method).parameters.values())
|
||||
next(iter_params)
|
||||
param = next(iter_params, None)
|
||||
|
||||
# must have second parameter named ctx or annotated AppContext
|
||||
if param is None or not (
|
||||
# no annotation, match name
|
||||
(param.annotation is inspect.Parameter.empty and param.name == "ctx")
|
||||
or (
|
||||
# string annotation, access path ends with AppContext
|
||||
isinstance(param.annotation, str)
|
||||
and param.annotation.rpartition(".")[2] == "AppContext"
|
||||
)
|
||||
or (
|
||||
# class annotation
|
||||
inspect.isclass(param.annotation)
|
||||
and issubclass(param.annotation, AppContext)
|
||||
)
|
||||
):
|
||||
warnings.warn(
|
||||
f"The '{method.__name__}' method now takes 'ctx: AppContext'"
|
||||
" as the first parameter. The old signature is deprecated"
|
||||
" and will not be supported in Flask 4.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
setattr(cls, method.__name__, remove_ctx(method))
|
||||
setattr(Flask, method.__name__, add_ctx(base_method))
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
import_name: str,
|
||||
|
|
@ -379,7 +295,7 @@ class Flask(App):
|
|||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
value = self.config["SEND_FILE_MAX_AGE_DEFAULT"]
|
||||
value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"]
|
||||
|
||||
if value is None:
|
||||
return None
|
||||
|
|
@ -587,9 +503,7 @@ class Flask(App):
|
|||
|
||||
raise FormDataRoutingRedirect(request)
|
||||
|
||||
def update_template_context(
|
||||
self, ctx: AppContext, context: dict[str, t.Any]
|
||||
) -> None:
|
||||
def update_template_context(self, context: dict[str, t.Any]) -> None:
|
||||
"""Update the template context with some commonly used variables.
|
||||
This injects request, session, config and g into the template
|
||||
context as well as everything template context processors want
|
||||
|
|
@ -603,8 +517,8 @@ class Flask(App):
|
|||
names: t.Iterable[str | None] = (None,)
|
||||
|
||||
# A template may be rendered outside a request context.
|
||||
if ctx.has_request:
|
||||
names = chain(names, reversed(ctx.request.blueprints))
|
||||
if request:
|
||||
names = chain(names, reversed(request.blueprints))
|
||||
|
||||
# The values passed to render_template take precedence. Keep a
|
||||
# copy to re-apply after all context functions.
|
||||
|
|
@ -828,7 +742,7 @@ class Flask(App):
|
|||
return cls(self, **kwargs) # type: ignore
|
||||
|
||||
def handle_http_exception(
|
||||
self, ctx: AppContext, e: HTTPException
|
||||
self, e: HTTPException
|
||||
) -> HTTPException | ft.ResponseReturnValue:
|
||||
"""Handles an HTTP exception. By default this will invoke the
|
||||
registered error handlers and fall back to returning the
|
||||
|
|
@ -857,13 +771,13 @@ class Flask(App):
|
|||
if isinstance(e, RoutingException):
|
||||
return e
|
||||
|
||||
handler = self._find_error_handler(e, ctx.request.blueprints)
|
||||
handler = self._find_error_handler(e, request.blueprints)
|
||||
if handler is None:
|
||||
return e
|
||||
return self.ensure_sync(handler)(e) # type: ignore[no-any-return]
|
||||
|
||||
def handle_user_exception(
|
||||
self, ctx: AppContext, e: Exception
|
||||
self, e: Exception
|
||||
) -> HTTPException | ft.ResponseReturnValue:
|
||||
"""This method is called whenever an exception occurs that
|
||||
should be handled. A special case is :class:`~werkzeug
|
||||
|
|
@ -885,16 +799,16 @@ class Flask(App):
|
|||
e.show_exception = True
|
||||
|
||||
if isinstance(e, HTTPException) and not self.trap_http_exception(e):
|
||||
return self.handle_http_exception(ctx, e)
|
||||
return self.handle_http_exception(e)
|
||||
|
||||
handler = self._find_error_handler(e, ctx.request.blueprints)
|
||||
handler = self._find_error_handler(e, request.blueprints)
|
||||
|
||||
if handler is None:
|
||||
raise
|
||||
|
||||
return self.ensure_sync(handler)(e) # type: ignore[no-any-return]
|
||||
|
||||
def handle_exception(self, ctx: AppContext, e: Exception) -> Response:
|
||||
def handle_exception(self, e: Exception) -> Response:
|
||||
"""Handle an exception that did not have an error handler
|
||||
associated with it, or that was raised from an error handler.
|
||||
This always causes a 500 ``InternalServerError``.
|
||||
|
|
@ -937,20 +851,19 @@ class Flask(App):
|
|||
|
||||
raise e
|
||||
|
||||
self.log_exception(ctx, exc_info)
|
||||
self.log_exception(exc_info)
|
||||
server_error: InternalServerError | ft.ResponseReturnValue
|
||||
server_error = InternalServerError(original_exception=e)
|
||||
handler = self._find_error_handler(server_error, ctx.request.blueprints)
|
||||
handler = self._find_error_handler(server_error, request.blueprints)
|
||||
|
||||
if handler is not None:
|
||||
server_error = self.ensure_sync(handler)(server_error)
|
||||
|
||||
return self.finalize_request(ctx, server_error, from_error_handler=True)
|
||||
return self.finalize_request(server_error, from_error_handler=True)
|
||||
|
||||
def log_exception(
|
||||
self,
|
||||
ctx: AppContext,
|
||||
exc_info: tuple[type, BaseException, TracebackType] | tuple[None, None, None],
|
||||
exc_info: (tuple[type, BaseException, TracebackType] | tuple[None, None, None]),
|
||||
) -> None:
|
||||
"""Logs an exception. This is called by :meth:`handle_exception`
|
||||
if debugging is disabled and right before the handler is called.
|
||||
|
|
@ -960,10 +873,10 @@ class Flask(App):
|
|||
.. versionadded:: 0.8
|
||||
"""
|
||||
self.logger.error(
|
||||
f"Exception on {ctx.request.path} [{ctx.request.method}]", exc_info=exc_info
|
||||
f"Exception on {request.path} [{request.method}]", exc_info=exc_info
|
||||
)
|
||||
|
||||
def dispatch_request(self, ctx: AppContext) -> ft.ResponseReturnValue:
|
||||
def dispatch_request(self) -> ft.ResponseReturnValue:
|
||||
"""Does the request dispatching. Matches the URL and returns the
|
||||
return value of the view or error handler. This does not have to
|
||||
be a response object. In order to convert the return value to a
|
||||
|
|
@ -973,8 +886,7 @@ class Flask(App):
|
|||
This no longer does the exception handling, this code was
|
||||
moved to the new :meth:`full_dispatch_request`.
|
||||
"""
|
||||
req = ctx.request
|
||||
|
||||
req = request_ctx.request
|
||||
if req.routing_exception is not None:
|
||||
self.raise_routing_exception(req)
|
||||
rule: Rule = req.url_rule # type: ignore[assignment]
|
||||
|
|
@ -984,43 +896,31 @@ class Flask(App):
|
|||
getattr(rule, "provide_automatic_options", False)
|
||||
and req.method == "OPTIONS"
|
||||
):
|
||||
return self.make_default_options_response(ctx)
|
||||
return self.make_default_options_response()
|
||||
# otherwise dispatch to the handler for that endpoint
|
||||
view_args: dict[str, t.Any] = req.view_args # type: ignore[assignment]
|
||||
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return]
|
||||
|
||||
def full_dispatch_request(self, ctx: AppContext) -> Response:
|
||||
def full_dispatch_request(self) -> Response:
|
||||
"""Dispatches the request and on top of that performs request
|
||||
pre and postprocessing as well as HTTP exception catching and
|
||||
error handling.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
if not self._got_first_request and self.should_ignore_error is not None:
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"The 'should_ignore_error' method is deprecated and will"
|
||||
" be removed in Flask 3.3. Handle errors as needed in"
|
||||
" teardown handlers instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=1,
|
||||
)
|
||||
|
||||
self._got_first_request = True
|
||||
|
||||
try:
|
||||
request_started.send(self, _async_wrapper=self.ensure_sync)
|
||||
rv = self.preprocess_request(ctx)
|
||||
rv = self.preprocess_request()
|
||||
if rv is None:
|
||||
rv = self.dispatch_request(ctx)
|
||||
rv = self.dispatch_request()
|
||||
except Exception as e:
|
||||
rv = self.handle_user_exception(ctx, e)
|
||||
return self.finalize_request(ctx, rv)
|
||||
rv = self.handle_user_exception(e)
|
||||
return self.finalize_request(rv)
|
||||
|
||||
def finalize_request(
|
||||
self,
|
||||
ctx: AppContext,
|
||||
rv: ft.ResponseReturnValue | HTTPException,
|
||||
from_error_handler: bool = False,
|
||||
) -> Response:
|
||||
|
|
@ -1038,7 +938,7 @@ class Flask(App):
|
|||
"""
|
||||
response = self.make_response(rv)
|
||||
try:
|
||||
response = self.process_response(ctx, response)
|
||||
response = self.process_response(response)
|
||||
request_finished.send(
|
||||
self, _async_wrapper=self.ensure_sync, response=response
|
||||
)
|
||||
|
|
@ -1050,14 +950,15 @@ class Flask(App):
|
|||
)
|
||||
return response
|
||||
|
||||
def make_default_options_response(self, ctx: AppContext) -> Response:
|
||||
def make_default_options_response(self) -> Response:
|
||||
"""This method is called to create the default ``OPTIONS`` response.
|
||||
This can be changed through subclassing to change the default
|
||||
behavior of ``OPTIONS`` responses.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
methods = ctx.url_adapter.allowed_methods() # type: ignore[union-attr]
|
||||
adapter = request_ctx.url_adapter
|
||||
methods = adapter.allowed_methods() # type: ignore[union-attr]
|
||||
rv = self.response_class()
|
||||
rv.allow.update(methods)
|
||||
return rv
|
||||
|
|
@ -1156,9 +1057,11 @@ class Flask(App):
|
|||
.. versionadded:: 2.2
|
||||
Moved from ``flask.url_for``, which calls this method.
|
||||
"""
|
||||
if (ctx := _cv_app.get(None)) is not None and ctx.has_request:
|
||||
url_adapter = ctx.url_adapter
|
||||
blueprint_name = ctx.request.blueprint
|
||||
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 the endpoint starts with "." and the request matches a
|
||||
# blueprint, the endpoint is relative to the blueprint.
|
||||
|
|
@ -1173,11 +1076,13 @@ 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 ctx is not None:
|
||||
url_adapter = ctx.url_adapter
|
||||
if app_ctx is not None:
|
||||
url_adapter = app_ctx.url_adapter
|
||||
else:
|
||||
url_adapter = self.create_url_adapter(None)
|
||||
|
||||
|
|
@ -1363,7 +1268,7 @@ class Flask(App):
|
|||
|
||||
return rv
|
||||
|
||||
def preprocess_request(self, ctx: AppContext) -> ft.ResponseReturnValue | None:
|
||||
def preprocess_request(self) -> ft.ResponseReturnValue | None:
|
||||
"""Called before the request is dispatched. Calls
|
||||
:attr:`url_value_preprocessors` registered with the app and the
|
||||
current blueprint (if any). Then calls :attr:`before_request_funcs`
|
||||
|
|
@ -1373,13 +1278,12 @@ class Flask(App):
|
|||
value is handled as if it was the return value from the view, and
|
||||
further request handling is stopped.
|
||||
"""
|
||||
req = ctx.request
|
||||
names = (None, *reversed(req.blueprints))
|
||||
names = (None, *reversed(request.blueprints))
|
||||
|
||||
for name in names:
|
||||
if name in self.url_value_preprocessors:
|
||||
for url_func in self.url_value_preprocessors[name]:
|
||||
url_func(req.endpoint, req.view_args)
|
||||
url_func(request.endpoint, request.view_args)
|
||||
|
||||
for name in names:
|
||||
if name in self.before_request_funcs:
|
||||
|
|
@ -1391,7 +1295,7 @@ class Flask(App):
|
|||
|
||||
return None
|
||||
|
||||
def process_response(self, ctx: AppContext, response: Response) -> Response:
|
||||
def process_response(self, response: Response) -> Response:
|
||||
"""Can be overridden in order to modify the response object
|
||||
before it's sent to the WSGI server. By default this will
|
||||
call all the :meth:`after_request` decorated functions.
|
||||
|
|
@ -1404,90 +1308,92 @@ 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]
|
||||
|
||||
for func in ctx._after_request_functions:
|
||||
response = self.ensure_sync(func)(response)
|
||||
|
||||
for name in chain(ctx.request.blueprints, (None,)):
|
||||
for name in chain(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)
|
||||
|
||||
if not self.session_interface.is_null_session(ctx._get_session()):
|
||||
self.session_interface.save_session(self, ctx._get_session(), response)
|
||||
if not self.session_interface.is_null_session(ctx._session):
|
||||
self.session_interface.save_session(self, ctx._session, response)
|
||||
|
||||
return response
|
||||
|
||||
def do_teardown_request(
|
||||
self, ctx: AppContext, exc: BaseException | None = None
|
||||
self,
|
||||
exc: BaseException | None = _sentinel, # type: ignore[assignment]
|
||||
) -> None:
|
||||
"""Called after the request is dispatched and the response is finalized,
|
||||
right before the request context is popped. Called by
|
||||
:meth:`.AppContext.pop`.
|
||||
"""Called after the request is dispatched and the response is
|
||||
returned, right before the request context is popped.
|
||||
|
||||
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.
|
||||
|
||||
:param exc: An unhandled exception raised while dispatching the request.
|
||||
Passed to each teardown function.
|
||||
This is called by
|
||||
:meth:`RequestContext.pop() <flask.ctx.RequestContext.pop>`,
|
||||
which may be delayed during testing to maintain access to
|
||||
resources.
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
All callbacks are called rather than stopping on the first error.
|
||||
:param exc: An unhandled exception raised while dispatching the
|
||||
request. Detected from the current exception information if
|
||||
not passed. Passed to each teardown function.
|
||||
|
||||
.. versionchanged:: 0.9
|
||||
Added the ``exc`` argument.
|
||||
"""
|
||||
collect_errors = _CollectErrors()
|
||||
if exc is _sentinel:
|
||||
exc = sys.exc_info()[1]
|
||||
|
||||
for name in chain(ctx.request.blueprints, (None,)):
|
||||
for name in chain(request.blueprints, (None,)):
|
||||
if name in self.teardown_request_funcs:
|
||||
for func in reversed(self.teardown_request_funcs[name]):
|
||||
with collect_errors:
|
||||
self.ensure_sync(func)(exc)
|
||||
self.ensure_sync(func)(exc)
|
||||
|
||||
with collect_errors:
|
||||
request_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc)
|
||||
|
||||
collect_errors.raise_any("Errors during request teardown")
|
||||
request_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc)
|
||||
|
||||
def do_teardown_appcontext(
|
||||
self, ctx: AppContext, exc: BaseException | None = None
|
||||
self,
|
||||
exc: BaseException | None = _sentinel, # type: ignore[assignment]
|
||||
) -> None:
|
||||
"""Called right before the application context is popped. Called by
|
||||
:meth:`.AppContext.pop`.
|
||||
"""Called right before the application context is popped.
|
||||
|
||||
This calls all functions decorated with :meth:`teardown_appcontext`.
|
||||
Then the :data:`appcontext_tearing_down` signal is sent.
|
||||
When handling a request, the application context is popped
|
||||
after the request context. See :meth:`do_teardown_request`.
|
||||
|
||||
:param exc: An unhandled exception raised while the context was active.
|
||||
Passed to each teardown function.
|
||||
This calls all functions decorated with
|
||||
:meth:`teardown_appcontext`. Then the
|
||||
:data:`appcontext_tearing_down` signal is sent.
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
All callbacks are called rather than stopping on the first error.
|
||||
This is called by
|
||||
:meth:`AppContext.pop() <flask.ctx.AppContext.pop>`.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
collect_errors = _CollectErrors()
|
||||
if exc is _sentinel:
|
||||
exc = sys.exc_info()[1]
|
||||
|
||||
for func in reversed(self.teardown_appcontext_funcs):
|
||||
with collect_errors:
|
||||
self.ensure_sync(func)(exc)
|
||||
self.ensure_sync(func)(exc)
|
||||
|
||||
with collect_errors:
|
||||
appcontext_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc)
|
||||
|
||||
collect_errors.raise_any("Errors during app teardown")
|
||||
appcontext_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc)
|
||||
|
||||
def app_context(self) -> AppContext:
|
||||
"""Create an :class:`.AppContext`. When the context is pushed,
|
||||
:data:`.current_app` and :data:`.g` become available.
|
||||
"""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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
.. code-block:: python
|
||||
::
|
||||
|
||||
with app.app_context():
|
||||
init_db()
|
||||
|
|
@ -1498,37 +1404,44 @@ class Flask(App):
|
|||
"""
|
||||
return AppContext(self)
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
See :doc:`/reqcontext`.
|
||||
|
||||
See :doc:`/appcontext`.
|
||||
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.
|
||||
|
||||
:param environ: A WSGI environment.
|
||||
:param environ: a WSGI environment
|
||||
"""
|
||||
return AppContext.from_environ(self, environ)
|
||||
return RequestContext(self, environ)
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
See :doc:`/reqcontext`.
|
||||
|
||||
.. code-block:: python
|
||||
Use a ``with`` block to push the context, which will make
|
||||
:data:`request` point at the request for the created
|
||||
environment. ::
|
||||
|
||||
with app.test_request_context(...):
|
||||
generate_report()
|
||||
|
||||
See :doc:`/appcontext`.
|
||||
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()
|
||||
|
||||
Takes the same arguments as Werkzeug's
|
||||
:class:`~werkzeug.test.EnvironBuilder`, with some defaults from
|
||||
|
|
@ -1538,18 +1451,20 @@ 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 prepend to :data:`SERVER_NAME`.
|
||||
:data:`PREFERRED_URL_SCHEME`, ``subdomain``,
|
||||
:data:`SERVER_NAME`, and :data:`APPLICATION_ROOT`.
|
||||
:param subdomain: Subdomain name to append to
|
||||
:data:`SERVER_NAME`.
|
||||
:param url_scheme: Scheme to use instead of
|
||||
:data:`PREFERRED_URL_SCHEME`.
|
||||
:param data: The request body text or bytes,or a dict of form data.
|
||||
:param data: The request body, either as a string or a dict of
|
||||
form keys and values.
|
||||
: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
|
||||
|
|
@ -1557,12 +1472,10 @@ class Flask(App):
|
|||
builder = EnvironBuilder(self, *args, **kwargs)
|
||||
|
||||
try:
|
||||
environ = builder.get_environ()
|
||||
return self.request_context(builder.get_environ())
|
||||
finally:
|
||||
builder.close()
|
||||
|
||||
return self.request_context(environ)
|
||||
|
||||
def wsgi_app(
|
||||
self, environ: WSGIEnvironment, start_response: StartResponse
|
||||
) -> cabc.Iterable[bytes]:
|
||||
|
|
@ -1583,6 +1496,7 @@ 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,
|
||||
|
|
@ -1594,23 +1508,20 @@ class Flask(App):
|
|||
try:
|
||||
try:
|
||||
ctx.push()
|
||||
response = self.full_dispatch_request(ctx)
|
||||
response = self.full_dispatch_request()
|
||||
except Exception as e:
|
||||
error = e
|
||||
response = self.handle_exception(ctx, e)
|
||||
response = self.handle_exception(e)
|
||||
except:
|
||||
error = sys.exc_info()[1]
|
||||
raise
|
||||
return response(environ, start_response)
|
||||
finally:
|
||||
if "werkzeug.debug.preserve_context" in environ:
|
||||
environ["werkzeug.debug.preserve_context"](ctx)
|
||||
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 is not None
|
||||
and self.should_ignore_error(error)
|
||||
):
|
||||
if error is not None and self.should_ignore_error(error):
|
||||
error = None
|
||||
|
||||
ctx.pop(error)
|
||||
|
|
|
|||
|
|
@ -468,7 +468,7 @@ _app_option = click.Option(
|
|||
def _set_debug(ctx: click.Context, param: click.Option, value: bool) -> bool | None:
|
||||
# If the flag isn't provided, it will default to False. Don't use
|
||||
# that, let debug be set by env in that case.
|
||||
source = ctx.get_parameter_source(param.name)
|
||||
source = ctx.get_parameter_source(param.name) # type: ignore[arg-type]
|
||||
|
||||
if source is not None and source in (
|
||||
ParameterSource.DEFAULT,
|
||||
|
|
@ -601,7 +601,15 @@ class FlaskGroup(AppGroup):
|
|||
if self._loaded_plugin_commands:
|
||||
return
|
||||
|
||||
for ep in importlib.metadata.entry_points(group="flask.commands"):
|
||||
if sys.version_info >= (3, 10):
|
||||
from importlib import metadata
|
||||
else:
|
||||
# Use a backport on Python < 3.10. We technically have
|
||||
# importlib.metadata on 3.8+, but the API changed in 3.10,
|
||||
# so use the backport for consistency.
|
||||
import importlib_metadata as metadata # pyright: ignore
|
||||
|
||||
for ep in metadata.entry_points(group="flask.commands"):
|
||||
self.add_command(ep.load(), ep.name)
|
||||
|
||||
self._loaded_plugin_commands = True
|
||||
|
|
@ -628,7 +636,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:
|
||||
if not current_app or current_app._get_current_object() is not app: # type: ignore[attr-defined]
|
||||
ctx.with_resource(app.app_context())
|
||||
|
||||
return app.cli.get_command(ctx, name)
|
||||
|
|
@ -777,7 +785,7 @@ def show_server_banner(debug: bool, app_import_path: str | None) -> None:
|
|||
click.echo(f" * Debug mode: {'on' if debug else 'off'}")
|
||||
|
||||
|
||||
class CertParamType(click.ParamType[t.Any]):
|
||||
class CertParamType(click.ParamType):
|
||||
"""Click option type for the ``--cert`` option. Allows either an
|
||||
existing file, the string ``'adhoc'``, or an import for a
|
||||
:class:`~ssl.SSLContext` object.
|
||||
|
|
@ -803,7 +811,7 @@ class CertParamType(click.ParamType[t.Any]):
|
|||
try:
|
||||
return self.path_type(value, param, ctx)
|
||||
except click.BadParameter:
|
||||
value = click.STRING(value, param, ctx).lower() # type: ignore[union-attr]
|
||||
value = click.STRING(value, param, ctx).lower()
|
||||
|
||||
if value == "adhoc":
|
||||
try:
|
||||
|
|
|
|||
547
src/flask/ctx.py
547
src/flask/ctx.py
|
|
@ -1,21 +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 .helpers import _CollectErrors
|
||||
from .globals import _cv_request
|
||||
from .signals import appcontext_popped
|
||||
from .signals import appcontext_pushed
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
if t.TYPE_CHECKING: # pragma: no cover
|
||||
from _typeshed.wsgi import WSGIEnvironment
|
||||
|
||||
from .app import Flask
|
||||
|
|
@ -32,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
|
||||
|
||||
|
|
@ -118,27 +117,29 @@ class _AppCtxGlobals:
|
|||
def after_this_request(
|
||||
f: ft.AfterRequestCallable[t.Any],
|
||||
) -> ft.AfterRequestCallable[t.Any]:
|
||||
"""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.
|
||||
"""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.
|
||||
|
||||
.. code-block:: python
|
||||
Example::
|
||||
|
||||
@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!'
|
||||
|
||||
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.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
ctx = _cv_app.get(None)
|
||||
ctx = _cv_request.get(None)
|
||||
|
||||
if ctx is None or not ctx.has_request:
|
||||
if ctx is None:
|
||||
raise RuntimeError(
|
||||
"'after_this_request' can only be used when a request"
|
||||
" context is active, such as in a view function."
|
||||
|
|
@ -189,68 +190,59 @@ def copy_current_request_context(f: F) -> F:
|
|||
|
||||
.. versionadded:: 0.10
|
||||
"""
|
||||
# Store the context that was active when the decorator was applied.
|
||||
original = _cv_app.get(None)
|
||||
ctx = _cv_request.get(None)
|
||||
|
||||
if original is None:
|
||||
if ctx is None:
|
||||
raise RuntimeError(
|
||||
"'copy_current_request_context' can only be used when a"
|
||||
" request context is active, such as in a view function."
|
||||
)
|
||||
|
||||
ctx = ctx.copy()
|
||||
|
||||
def wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
|
||||
# Copy the context before pushing, so each worker acts independently.
|
||||
with original.copy() as ctx:
|
||||
with ctx:
|
||||
return ctx.app.ensure_sync(f)(*args, **kwargs)
|
||||
|
||||
return update_wrapper(wrapper, f) # type: ignore[return-value]
|
||||
|
||||
|
||||
def has_request_context() -> bool:
|
||||
"""Test if an app context is active and if it has request information.
|
||||
"""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.
|
||||
|
||||
.. code-block:: python
|
||||
::
|
||||
|
||||
from flask import has_request_context, request
|
||||
class User(db.Model):
|
||||
|
||||
if has_request_context():
|
||||
remote_addr = request.remote_addr
|
||||
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 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.
|
||||
Alternatively you can also just test any of the context bound objects
|
||||
(such as :class:`request` or :class:`g`) for truthness::
|
||||
|
||||
.. code-block:: python
|
||||
class User(db.Model):
|
||||
|
||||
from flask import request
|
||||
|
||||
if request:
|
||||
remote_addr = request.remote_addr
|
||||
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
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
return (ctx := _cv_app.get(None)) is not None and ctx.has_request
|
||||
return _cv_request.get(None) is not None
|
||||
|
||||
|
||||
def has_app_context() -> bool:
|
||||
"""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 = ...
|
||||
"""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.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
|
|
@ -258,283 +250,224 @@ def has_app_context() -> bool:
|
|||
|
||||
|
||||
class AppContext:
|
||||
"""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.
|
||||
|
||||
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.
|
||||
|
||||
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`.
|
||||
|
||||
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.
|
||||
|
||||
: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.
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
Merged with ``RequestContext``. The ``RequestContext`` alias will be
|
||||
removed in Flask 4.0.
|
||||
|
||||
.. 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.
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
The session is loaded the first time it is accessed, rather than when
|
||||
the context is pushed.
|
||||
"""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.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
app: Flask,
|
||||
*,
|
||||
request: Request | None = None,
|
||||
session: SessionMixin | None = None,
|
||||
) -> None:
|
||||
def __init__(self, app: Flask) -> None:
|
||||
self.app = app
|
||||
"""The application represented by this context. Accessed through
|
||||
:data:`.current_app`.
|
||||
"""
|
||||
|
||||
self.url_adapter = app.create_url_adapter(None)
|
||||
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]] = []
|
||||
|
||||
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
|
||||
|
||||
self._cv_token: contextvars.Token[AppContext] | None = None
|
||||
"""The previous state to restore when popping."""
|
||||
|
||||
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 data is used instead of reloading the original data.
|
||||
|
||||
.. versionadded:: 0.10
|
||||
"""
|
||||
return self.__class__(
|
||||
self.app,
|
||||
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
|
||||
|
||||
def _get_session(self) -> SessionMixin:
|
||||
"""Open the session if it is not already open for this request context."""
|
||||
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
|
||||
|
||||
@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`. Accessing this sets :attr:`.SessionMixin.accessed`.
|
||||
"""
|
||||
session = self._get_session()
|
||||
session.accessed = True
|
||||
return session
|
||||
|
||||
def match_request(self) -> None:
|
||||
"""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[union-attr]
|
||||
except HTTPException as e:
|
||||
self._request.routing_exception = e # type: ignore[union-attr]
|
||||
else:
|
||||
self._request.url_rule, self._request.view_args = result # type: ignore[union-attr]
|
||||
self._cv_tokens: list[contextvars.Token[AppContext]] = []
|
||||
|
||||
def push(self) -> 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.
|
||||
|
||||
Typically, this is not used directly. Instead, use a ``with`` block
|
||||
to manage the context.
|
||||
|
||||
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
|
||||
|
||||
if self._cv_token is not None:
|
||||
return
|
||||
|
||||
self._cv_token = _cv_app.set(self)
|
||||
"""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)
|
||||
|
||||
if self._request is not None:
|
||||
# Open the session at the moment that the request context is available.
|
||||
# This allows a custom open_session method to use the request context.
|
||||
self._get_session()
|
||||
|
||||
# Match the request URL after loading the session, so that the
|
||||
# session is available in custom URL converters.
|
||||
if self.url_adapter is not None:
|
||||
self.match_request()
|
||||
|
||||
def pop(self, exc: 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.
|
||||
"""
|
||||
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."
|
||||
)
|
||||
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())
|
||||
|
||||
if ctx is not self:
|
||||
raise RuntimeError(
|
||||
f"Cannot pop this context ({self!r}), it is not the active"
|
||||
f" context ({ctx!r})."
|
||||
raise AssertionError(
|
||||
f"Popped wrong app context. ({ctx!r} instead of {self!r})"
|
||||
)
|
||||
|
||||
self._push_count -= 1
|
||||
appcontext_popped.send(self.app, _async_wrapper=self.app.ensure_sync)
|
||||
|
||||
if self._push_count > 0:
|
||||
return
|
||||
|
||||
collect_errors = _CollectErrors()
|
||||
|
||||
if self._request is not None:
|
||||
with collect_errors:
|
||||
self.app.do_teardown_request(self, exc)
|
||||
|
||||
with collect_errors:
|
||||
self._request.close()
|
||||
|
||||
with collect_errors:
|
||||
self.app.do_teardown_appcontext(self, exc)
|
||||
|
||||
_cv_app.reset(self._cv_token)
|
||||
self._cv_token = None
|
||||
|
||||
with collect_errors:
|
||||
appcontext_popped.send(self.app, _async_wrapper=self.app.ensure_sync)
|
||||
|
||||
collect_errors.raise_any("Errors during context teardown")
|
||||
|
||||
def __enter__(self) -> te.Self:
|
||||
def __enter__(self) -> AppContext:
|
||||
self.push()
|
||||
return self
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: type[BaseException] | None,
|
||||
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.
|
||||
"""
|
||||
|
||||
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.
|
||||
self._after_request_functions: list[ft.AfterRequestCallable[t.Any]] = []
|
||||
|
||||
self._cv_tokens: list[
|
||||
tuple[contextvars.Token[RequestContext], AppContext | None]
|
||||
] = []
|
||||
|
||||
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.
|
||||
|
||||
.. versionadded:: 0.10
|
||||
|
||||
.. 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.
|
||||
"""
|
||||
return self.__class__(
|
||||
self.app,
|
||||
environ=self.request.environ,
|
||||
request=self.request,
|
||||
session=self._session,
|
||||
)
|
||||
|
||||
def match_request(self) -> None:
|
||||
"""Can be overridden by a subclass to hook into the matching
|
||||
of the request.
|
||||
"""
|
||||
try:
|
||||
result = self.url_adapter.match(return_rule=True) # type: ignore
|
||||
self.request.url_rule, self.request.view_args = result # type: ignore
|
||||
except HTTPException as e:
|
||||
self.request.routing_exception = e
|
||||
|
||||
@property
|
||||
def session(self) -> SessionMixin:
|
||||
"""The session data associated with this request. Not available until
|
||||
this context has been pushed. Accessing this property, also accessed by
|
||||
the :data:`~flask.session` proxy, sets :attr:`.SessionMixin.accessed`.
|
||||
"""
|
||||
assert self._session is not None, "The session has not yet been opened."
|
||||
self._session.accessed = True
|
||||
return self._session
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
self._cv_tokens.append((_cv_request.set(self), app_ctx))
|
||||
|
||||
# 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._session is None:
|
||||
self._session = session_interface.make_null_session(self.app)
|
||||
|
||||
# Match the request URL after loading the session, so that the
|
||||
# session is available in custom URL converters.
|
||||
if self.url_adapter is not None:
|
||||
self.match_request()
|
||||
|
||||
def pop(self, exc: 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.
|
||||
|
||||
.. versionchanged:: 0.9
|
||||
Added the `exc` argument.
|
||||
"""
|
||||
clear_request = len(self._cv_tokens) == 1
|
||||
|
||||
try:
|
||||
if clear_request:
|
||||
if exc is _sentinel:
|
||||
exc = sys.exc_info()[1]
|
||||
self.app.do_teardown_request(exc)
|
||||
|
||||
request_close = getattr(self.request, "close", None)
|
||||
if request_close is not None:
|
||||
request_close()
|
||||
finally:
|
||||
ctx = _cv_request.get()
|
||||
token, app_ctx = self._cv_tokens.pop()
|
||||
_cv_request.reset(token)
|
||||
|
||||
# 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:
|
||||
self.push()
|
||||
return self
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: type | None,
|
||||
exc_value: BaseException | None,
|
||||
tb: TracebackType | None,
|
||||
) -> None:
|
||||
self.pop(exc_value)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
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 (
|
||||
f"<{type(self).__name__} {self.request.url!r}"
|
||||
f" [{self.request.method}] of {self.app.name}>"
|
||||
)
|
||||
return AppContext
|
||||
|
||||
raise AttributeError(name)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from jinja2.loaders import BaseLoader
|
|||
from werkzeug.routing import RequestRedirect
|
||||
|
||||
from .blueprints import Blueprint
|
||||
from .globals import _cv_app
|
||||
from .globals import request_ctx
|
||||
from .sansio.app import App
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
|
|
@ -136,9 +136,8 @@ def explain_template_loading_attempts(
|
|||
info = [f"Locating template {template!r}:"]
|
||||
total_found = 0
|
||||
blueprint = None
|
||||
|
||||
if (ctx := _cv_app.get(None)) is not None and ctx.has_request:
|
||||
blueprint = ctx.request.blueprint
|
||||
if request_ctx and request_ctx.request.blueprint is not None:
|
||||
blueprint = request_ctx.request.blueprint
|
||||
|
||||
for idx, (loader, srcobj, triple) in enumerate(attempts):
|
||||
if isinstance(srcobj, App):
|
||||
|
|
|
|||
|
|
@ -9,69 +9,43 @@ 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.
|
||||
|
||||
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.\
|
||||
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.\
|
||||
"""
|
||||
_cv_app: ContextVar[AppContext] = ContextVar("flask.app_ctx")
|
||||
app_ctx: AppContextProxy = LocalProxy( # type: ignore[assignment]
|
||||
app_ctx: AppContext = LocalProxy( # type: ignore[assignment]
|
||||
_cv_app, unbound_message=_no_app_msg
|
||||
)
|
||||
current_app: FlaskProxy = LocalProxy( # type: ignore[assignment]
|
||||
current_app: Flask = LocalProxy( # type: ignore[assignment]
|
||||
_cv_app, "app", unbound_message=_no_app_msg
|
||||
)
|
||||
g: _AppCtxGlobalsProxy = LocalProxy( # type: ignore[assignment]
|
||||
g: _AppCtxGlobals = LocalProxy( # type: ignore[assignment]
|
||||
_cv_app, "g", unbound_message=_no_app_msg
|
||||
)
|
||||
|
||||
_no_req_msg = """\
|
||||
Working outside of request context.
|
||||
|
||||
Attempted to use functionality that expected an active HTTP request. See the
|
||||
documentation on request context for more information.\
|
||||
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.\
|
||||
"""
|
||||
request: RequestProxy = LocalProxy( # type: ignore[assignment]
|
||||
_cv_app, "request", unbound_message=_no_req_msg
|
||||
_cv_request: ContextVar[RequestContext] = ContextVar("flask.request_ctx")
|
||||
request_ctx: RequestContext = LocalProxy( # type: ignore[assignment]
|
||||
_cv_request, unbound_message=_no_req_msg
|
||||
)
|
||||
session: SessionMixinProxy = LocalProxy( # type: ignore[assignment]
|
||||
_cv_app, "session", 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
|
||||
)
|
||||
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import typing as t
|
|||
from datetime import datetime
|
||||
from functools import cache
|
||||
from functools import update_wrapper
|
||||
from types import TracebackType
|
||||
|
||||
import werkzeug.utils
|
||||
from werkzeug.exceptions import abort as _wz_abort
|
||||
|
|
@ -15,9 +14,10 @@ from werkzeug.utils import redirect as _wz_redirect
|
|||
from werkzeug.wrappers import Response as BaseResponse
|
||||
|
||||
from .globals import _cv_app
|
||||
from .globals import app_ctx
|
||||
from .globals import _cv_request
|
||||
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 +64,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.
|
||||
|
||||
|
|
@ -124,15 +124,22 @@ def stream_with_context(
|
|||
return update_wrapper(decorator, generator_or_function) # type: ignore[arg-type]
|
||||
|
||||
def generator() -> t.Iterator[t.AnyStr]:
|
||||
if (ctx := _cv_app.get(None)) is None:
|
||||
if (req_ctx := _cv_request.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."
|
||||
)
|
||||
|
||||
with ctx:
|
||||
yield None # type: ignore[misc]
|
||||
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]
|
||||
|
||||
# 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:
|
||||
|
|
@ -140,9 +147,9 @@ def stream_with_context(
|
|||
if hasattr(gen, "close"):
|
||||
gen.close()
|
||||
|
||||
# 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.
|
||||
# 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.
|
||||
wrapped_g = generator()
|
||||
next(wrapped_g)
|
||||
return wrapped_g
|
||||
|
|
@ -252,7 +259,7 @@ def url_for(
|
|||
|
||||
|
||||
def redirect(
|
||||
location: str, code: int = 303, Response: type[BaseResponse] | None = None
|
||||
location: str, code: int = 302, Response: type[BaseResponse] | None = None
|
||||
) -> BaseResponse:
|
||||
"""Create a redirect response object.
|
||||
|
||||
|
|
@ -265,15 +272,12 @@ def redirect(
|
|||
:param Response: The response class to use. Not used when
|
||||
``current_app`` is active, which uses ``app.response_class``.
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
``code`` defaults to ``303`` instead of ``302``.
|
||||
|
||||
.. versionadded:: 2.2
|
||||
Calls ``current_app.redirect`` if available instead of always
|
||||
using Werkzeug's default ``redirect``.
|
||||
"""
|
||||
if (ctx := _cv_app.get(None)) is not None:
|
||||
return ctx.app.redirect(location, code=code)
|
||||
if current_app:
|
||||
return current_app.redirect(location, code=code)
|
||||
|
||||
return _wz_redirect(location, code=code, Response=Response)
|
||||
|
||||
|
|
@ -295,8 +299,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 (ctx := _cv_app.get(None)) is not None:
|
||||
ctx.app.aborter(code, *args, **kwargs)
|
||||
if current_app:
|
||||
current_app.aborter(code, *args, **kwargs)
|
||||
|
||||
_wz_abort(code, *args, **kwargs)
|
||||
|
||||
|
|
@ -348,7 +352,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()
|
||||
app = current_app._get_current_object() # type: ignore
|
||||
message_flashed.send(
|
||||
app,
|
||||
_async_wrapper=app.ensure_sync,
|
||||
|
|
@ -388,10 +392,10 @@ def get_flashed_messages(
|
|||
:param category_filter: filter of categories to limit return values. Only
|
||||
categories in the list will be returned.
|
||||
"""
|
||||
flashes = app_ctx._flashes
|
||||
flashes = request_ctx.flashes
|
||||
if flashes is None:
|
||||
flashes = session.pop("_flashes") if "_flashes" in session else []
|
||||
app_ctx._flashes = flashes
|
||||
request_ctx.flashes = flashes
|
||||
if category_filter:
|
||||
flashes = list(filter(lambda f: f[0] in category_filter, flashes))
|
||||
if not with_categories:
|
||||
|
|
@ -400,16 +404,14 @@ 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"] = ctx.app.get_send_file_max_age
|
||||
kwargs["max_age"] = current_app.get_send_file_max_age
|
||||
|
||||
kwargs.update(
|
||||
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,
|
||||
environ=request.environ,
|
||||
use_x_sendfile=current_app.config["USE_X_SENDFILE"],
|
||||
response_class=current_app.response_class,
|
||||
_root_path=current_app.root_path,
|
||||
)
|
||||
return kwargs
|
||||
|
||||
|
|
@ -649,34 +651,3 @@ def _split_blueprint_path(name: str) -> list[str]:
|
|||
out.extend(_split_blueprint_path(name.rpartition(".")[0]))
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class _CollectErrors:
|
||||
"""A context manager that records and silences an error raised within it.
|
||||
Used to run all teardown functions, then raise any errors afterward.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.errors: list[BaseException] = []
|
||||
|
||||
def __enter__(self) -> None:
|
||||
pass
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: type[BaseException] | None,
|
||||
exc_val: BaseException | None,
|
||||
exc_tb: TracebackType | None,
|
||||
) -> bool:
|
||||
if exc_val is not None:
|
||||
self.errors.append(exc_val)
|
||||
|
||||
return True
|
||||
|
||||
def raise_any(self, message: str) -> None:
|
||||
"""Raise if any errors were collected."""
|
||||
if self.errors:
|
||||
if sys.version_info >= (3, 11):
|
||||
raise BaseExceptionGroup(message, self.errors) # noqa: F821
|
||||
else:
|
||||
raise self.errors[0]
|
||||
|
|
|
|||
|
|
@ -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 app context, and calls
|
||||
This requires an active request or application context, and calls
|
||||
:meth:`app.json.response() <flask.json.provider.JSONProvider.response>`.
|
||||
|
||||
In debug mode, the output is formatted with indentation to make it
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ class DefaultJSONProvider(JSONProvider):
|
|||
method) will call the ``__html__`` method to get a string.
|
||||
"""
|
||||
|
||||
default: t.Callable[[t.Any], t.Any] = staticmethod(_default)
|
||||
default: t.Callable[[t.Any], t.Any] = staticmethod(_default) # type: ignore[assignment]
|
||||
"""Apply this function to any object that :meth:`json.dumps` does
|
||||
not know how to serialize. It should return a valid JSON type or
|
||||
raise a ``TypeError``.
|
||||
|
|
|
|||
|
|
@ -177,8 +177,11 @@ 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.
|
||||
|
|
@ -210,7 +213,7 @@ class App(Scaffold):
|
|||
#:
|
||||
#: This attribute can also be configured from the config with the
|
||||
#: :data:`SECRET_KEY` configuration key. Defaults to ``None``.
|
||||
secret_key = ConfigAttribute[str | bytes | None]("SECRET_KEY")
|
||||
secret_key = ConfigAttribute[t.Union[str, bytes, None]]("SECRET_KEY")
|
||||
|
||||
#: A :class:`~datetime.timedelta` which is used to set the expiration
|
||||
#: date of a permanent session. The default is 31 days which makes a
|
||||
|
|
@ -534,9 +537,6 @@ class App(Scaffold):
|
|||
"""Returns ``True`` if autoescaping should be active for the given
|
||||
template name. If no template name is given, returns `True`.
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
Use case-insensitive comparison instead of only lower case.
|
||||
|
||||
.. versionchanged:: 2.2
|
||||
Autoescaping is now enabled by default for ``.svg`` files.
|
||||
|
||||
|
|
@ -544,7 +544,7 @@ class App(Scaffold):
|
|||
"""
|
||||
if filename is None:
|
||||
return True
|
||||
return filename.lower().endswith((".html", ".htm", ".xml", ".xhtml", ".svg"))
|
||||
return filename.endswith((".html", ".htm", ".xml", ".xhtml", ".svg"))
|
||||
|
||||
@property
|
||||
def debug(self) -> bool:
|
||||
|
|
@ -630,19 +630,19 @@ class App(Scaffold):
|
|||
# Methods that should always be added
|
||||
required_methods: set[str] = set(getattr(view_func, "required_methods", ()))
|
||||
|
||||
# starting with Flask 0.8 the view_func object can disable and
|
||||
# force-enable the automatic options handling.
|
||||
if provide_automatic_options is None:
|
||||
provide_automatic_options = getattr(
|
||||
view_func, "provide_automatic_options", None
|
||||
)
|
||||
|
||||
if provide_automatic_options is None:
|
||||
provide_automatic_options = (
|
||||
"OPTIONS" not in methods
|
||||
and self.config["PROVIDE_AUTOMATIC_OPTIONS"]
|
||||
)
|
||||
|
||||
if provide_automatic_options:
|
||||
required_methods.add("OPTIONS")
|
||||
if provide_automatic_options is None:
|
||||
if "OPTIONS" not in methods and self.config["PROVIDE_AUTOMATIC_OPTIONS"]:
|
||||
provide_automatic_options = True
|
||||
required_methods.add("OPTIONS")
|
||||
else:
|
||||
provide_automatic_options = False
|
||||
|
||||
# Add the required methods now.
|
||||
methods |= required_methods
|
||||
|
|
@ -660,34 +660,21 @@ class App(Scaffold):
|
|||
)
|
||||
self.view_functions[endpoint] = view_func
|
||||
|
||||
@t.overload
|
||||
def template_filter(self, name: T_template_filter) -> T_template_filter: ...
|
||||
@t.overload
|
||||
def template_filter(
|
||||
self, name: str | None = None
|
||||
) -> t.Callable[[T_template_filter], T_template_filter]: ...
|
||||
@setupmethod
|
||||
def template_filter(
|
||||
self, name: T_template_filter | str | None = None
|
||||
) -> T_template_filter | t.Callable[[T_template_filter], T_template_filter]:
|
||||
"""Decorate a function to register it as a custom Jinja filter. The name
|
||||
is optional. The decorator may be used without parentheses.
|
||||
self, name: str | None = None
|
||||
) -> t.Callable[[T_template_filter], T_template_filter]:
|
||||
"""A decorator that is used to register custom template filter.
|
||||
You can specify a name for the filter, otherwise the function
|
||||
name will be used. Example::
|
||||
|
||||
.. code-block:: python
|
||||
@app.template_filter()
|
||||
def reverse(s):
|
||||
return s[::-1]
|
||||
|
||||
@app.template_filter("reverse")
|
||||
def reverse_filter(s):
|
||||
return reversed(s)
|
||||
|
||||
The :meth:`add_template_filter` method may be used to register a
|
||||
function later rather than decorating.
|
||||
|
||||
:param name: The name to register the filter as. If not given, uses the
|
||||
function's name.
|
||||
:param name: the optional name of the filter, otherwise the
|
||||
function name will be used.
|
||||
"""
|
||||
if callable(name):
|
||||
self.add_template_filter(name)
|
||||
return name
|
||||
|
||||
def decorator(f: T_template_filter) -> T_template_filter:
|
||||
self.add_template_filter(f, name=name)
|
||||
|
|
@ -699,52 +686,36 @@ class App(Scaffold):
|
|||
def add_template_filter(
|
||||
self, f: ft.TemplateFilterCallable, name: str | None = None
|
||||
) -> None:
|
||||
"""Register a function to use as a custom Jinja filter.
|
||||
"""Register a custom template filter. Works exactly like the
|
||||
:meth:`template_filter` decorator.
|
||||
|
||||
The :meth:`template_filter` decorator can be used to register a function
|
||||
by decorating instead.
|
||||
|
||||
:param f: The function to register.
|
||||
:param name: The name to register the filter as. If not given, uses the
|
||||
function's name.
|
||||
:param name: the optional name of the filter, otherwise the
|
||||
function name will be used.
|
||||
"""
|
||||
self.jinja_env.filters[name or f.__name__] = f
|
||||
|
||||
@t.overload
|
||||
def template_test(self, name: T_template_test) -> T_template_test: ...
|
||||
@t.overload
|
||||
def template_test(
|
||||
self, name: str | None = None
|
||||
) -> t.Callable[[T_template_test], T_template_test]: ...
|
||||
@setupmethod
|
||||
def template_test(
|
||||
self, name: T_template_test | str | None = None
|
||||
) -> T_template_test | t.Callable[[T_template_test], T_template_test]:
|
||||
"""Decorate a function to register it as a custom Jinja test. The name
|
||||
is optional. The decorator may be used without parentheses.
|
||||
self, name: str | None = None
|
||||
) -> t.Callable[[T_template_test], T_template_test]:
|
||||
"""A decorator that is used to register custom template test.
|
||||
You can specify a name for the test, otherwise the function
|
||||
name will be used. Example::
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.template_test("prime")
|
||||
def is_prime_test(n):
|
||||
if n == 2:
|
||||
return True
|
||||
for i in range(2, int(math.ceil(math.sqrt(n))) + 1):
|
||||
if n % i == 0:
|
||||
return False
|
||||
@app.template_test()
|
||||
def is_prime(n):
|
||||
if n == 2:
|
||||
return True
|
||||
for i in range(2, int(math.ceil(math.sqrt(n))) + 1):
|
||||
if n % i == 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
The :meth:`add_template_test` method may be used to register a function
|
||||
later rather than decorating.
|
||||
|
||||
:param name: The name to register the filter as. If not given, uses the
|
||||
function's name.
|
||||
|
||||
.. versionadded:: 0.10
|
||||
|
||||
:param name: the optional name of the test, otherwise the
|
||||
function name will be used.
|
||||
"""
|
||||
if callable(name):
|
||||
self.add_template_test(name)
|
||||
return name
|
||||
|
||||
def decorator(f: T_template_test) -> T_template_test:
|
||||
self.add_template_test(f, name=name)
|
||||
|
|
@ -756,49 +727,33 @@ class App(Scaffold):
|
|||
def add_template_test(
|
||||
self, f: ft.TemplateTestCallable, name: str | None = None
|
||||
) -> None:
|
||||
"""Register a function to use as a custom Jinja test.
|
||||
|
||||
The :meth:`template_test` decorator can be used to register a function
|
||||
by decorating instead.
|
||||
|
||||
:param f: The function to register.
|
||||
:param name: The name to register the test as. If not given, uses the
|
||||
function's name.
|
||||
"""Register a custom template test. Works exactly like the
|
||||
:meth:`template_test` decorator.
|
||||
|
||||
.. versionadded:: 0.10
|
||||
|
||||
:param name: the optional name of the test, otherwise the
|
||||
function name will be used.
|
||||
"""
|
||||
self.jinja_env.tests[name or f.__name__] = f
|
||||
|
||||
@t.overload
|
||||
def template_global(self, name: T_template_global) -> T_template_global: ...
|
||||
@t.overload
|
||||
def template_global(
|
||||
self, name: str | None = None
|
||||
) -> t.Callable[[T_template_global], T_template_global]: ...
|
||||
@setupmethod
|
||||
def template_global(
|
||||
self, name: T_template_global | str | None = None
|
||||
) -> T_template_global | t.Callable[[T_template_global], T_template_global]:
|
||||
"""Decorate a function to register it as a custom Jinja global. The name
|
||||
is optional. The decorator may be used without parentheses.
|
||||
self, name: str | None = None
|
||||
) -> t.Callable[[T_template_global], T_template_global]:
|
||||
"""A decorator that is used to register a custom template global function.
|
||||
You can specify a name for the global function, otherwise the function
|
||||
name will be used. Example::
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.template_global
|
||||
@app.template_global()
|
||||
def double(n):
|
||||
return 2 * n
|
||||
|
||||
The :meth:`add_template_global` method may be used to register a
|
||||
function later rather than decorating.
|
||||
|
||||
:param name: The name to register the global as. If not given, uses the
|
||||
function's name.
|
||||
|
||||
.. versionadded:: 0.10
|
||||
|
||||
:param name: the optional name of the global function, otherwise the
|
||||
function name will be used.
|
||||
"""
|
||||
if callable(name):
|
||||
self.add_template_global(name)
|
||||
return name
|
||||
|
||||
def decorator(f: T_template_global) -> T_template_global:
|
||||
self.add_template_global(f, name=name)
|
||||
|
|
@ -810,24 +765,22 @@ class App(Scaffold):
|
|||
def add_template_global(
|
||||
self, f: ft.TemplateGlobalCallable, name: str | None = None
|
||||
) -> None:
|
||||
"""Register a function to use as a custom Jinja global.
|
||||
|
||||
The :meth:`template_global` decorator can be used to register a function
|
||||
by decorating instead.
|
||||
|
||||
:param f: The function to register.
|
||||
:param name: The name to register the global as. If not given, uses the
|
||||
function's name.
|
||||
"""Register a custom template global function. Works exactly like the
|
||||
:meth:`template_global` decorator.
|
||||
|
||||
.. versionadded:: 0.10
|
||||
|
||||
:param name: the optional name of the global function, otherwise the
|
||||
function name will be used.
|
||||
"""
|
||||
self.jinja_env.globals[name or f.__name__] = f
|
||||
|
||||
@setupmethod
|
||||
def teardown_appcontext(self, f: T_teardown) -> T_teardown:
|
||||
"""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.
|
||||
"""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.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
@ -836,7 +789,9 @@ 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.
|
||||
made inactive. Since a request context typically also manages an
|
||||
application context it would also be called when you pop a
|
||||
request context.
|
||||
|
||||
When a teardown function was called because of an unhandled
|
||||
exception it will be passed an error object. If an
|
||||
|
|
@ -925,18 +880,17 @@ class App(Scaffold):
|
|||
|
||||
return False
|
||||
|
||||
should_ignore_error: None = None
|
||||
"""If this method returns ``True``, the error will not be passed to
|
||||
teardown handlers, and the context will not be preserved for
|
||||
debugging.
|
||||
def should_ignore_error(self, error: BaseException | None) -> bool:
|
||||
"""This is called to figure out if an error should be ignored
|
||||
or not as far as the teardown system is concerned. If this
|
||||
function returns ``True`` then the teardown handlers will not be
|
||||
passed the error.
|
||||
|
||||
.. deprecated:: 3.2
|
||||
Handle errors as needed in teardown handlers instead.
|
||||
.. versionadded:: 0.10
|
||||
"""
|
||||
return False
|
||||
|
||||
.. versionadded:: 0.10
|
||||
"""
|
||||
|
||||
def redirect(self, location: str, code: int = 303) -> BaseResponse:
|
||||
def redirect(self, location: str, code: int = 302) -> BaseResponse:
|
||||
"""Create a redirect response object.
|
||||
|
||||
This is called by :func:`flask.redirect`, and can be called
|
||||
|
|
@ -945,9 +899,6 @@ class App(Scaffold):
|
|||
:param location: The URL to redirect to.
|
||||
:param code: The status code for the redirect.
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
``code`` defaults to ``303`` instead of ``302``.
|
||||
|
||||
.. versionadded:: 2.2
|
||||
Moved from ``flask.redirect``, which calls this method.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -440,31 +440,16 @@ class Blueprint(Scaffold):
|
|||
)
|
||||
)
|
||||
|
||||
@t.overload
|
||||
def app_template_filter(self, name: T_template_filter) -> T_template_filter: ...
|
||||
@t.overload
|
||||
def app_template_filter(
|
||||
self, name: str | None = None
|
||||
) -> t.Callable[[T_template_filter], T_template_filter]: ...
|
||||
@setupmethod
|
||||
def app_template_filter(
|
||||
self, name: T_template_filter | str | None = None
|
||||
) -> T_template_filter | t.Callable[[T_template_filter], T_template_filter]:
|
||||
"""Decorate a function to register it as a custom Jinja filter. The name
|
||||
is optional. The decorator may be used without parentheses.
|
||||
self, name: str | None = None
|
||||
) -> t.Callable[[T_template_filter], T_template_filter]:
|
||||
"""Register a template filter, available in any template rendered by the
|
||||
application. Equivalent to :meth:`.Flask.template_filter`.
|
||||
|
||||
The :meth:`add_app_template_filter` method may be used to register a
|
||||
function later rather than decorating.
|
||||
|
||||
The filter is available in all templates, not only those under this
|
||||
blueprint. Equivalent to :meth:`.Flask.template_filter`.
|
||||
|
||||
:param name: The name to register the filter as. If not given, uses the
|
||||
function's name.
|
||||
:param name: the optional name of the filter, otherwise the
|
||||
function name will be used.
|
||||
"""
|
||||
if callable(name):
|
||||
self.add_app_template_filter(name)
|
||||
return name
|
||||
|
||||
def decorator(f: T_template_filter) -> T_template_filter:
|
||||
self.add_app_template_filter(f, name=name)
|
||||
|
|
@ -476,51 +461,31 @@ class Blueprint(Scaffold):
|
|||
def add_app_template_filter(
|
||||
self, f: ft.TemplateFilterCallable, name: str | None = None
|
||||
) -> None:
|
||||
"""Register a function to use as a custom Jinja filter.
|
||||
"""Register a template filter, available in any template rendered by the
|
||||
application. Works like the :meth:`app_template_filter` decorator. Equivalent to
|
||||
:meth:`.Flask.add_template_filter`.
|
||||
|
||||
The :meth:`app_template_filter` decorator can be used to register a
|
||||
function by decorating instead.
|
||||
|
||||
The filter is available in all templates, not only those under this
|
||||
blueprint. Equivalent to :meth:`.Flask.add_template_filter`.
|
||||
|
||||
:param f: The function to register.
|
||||
:param name: The name to register the filter as. If not given, uses the
|
||||
function's name.
|
||||
:param name: the optional name of the filter, otherwise the
|
||||
function name will be used.
|
||||
"""
|
||||
|
||||
def register_template_filter(state: BlueprintSetupState) -> None:
|
||||
state.app.add_template_filter(f, name=name)
|
||||
def register_template(state: BlueprintSetupState) -> None:
|
||||
state.app.jinja_env.filters[name or f.__name__] = f
|
||||
|
||||
self.record_once(register_template_filter)
|
||||
self.record_once(register_template)
|
||||
|
||||
@t.overload
|
||||
def app_template_test(self, name: T_template_test) -> T_template_test: ...
|
||||
@t.overload
|
||||
def app_template_test(
|
||||
self, name: str | None = None
|
||||
) -> t.Callable[[T_template_test], T_template_test]: ...
|
||||
@setupmethod
|
||||
def app_template_test(
|
||||
self, name: T_template_test | str | None = None
|
||||
) -> T_template_test | t.Callable[[T_template_test], T_template_test]:
|
||||
"""Decorate a function to register it as a custom Jinja test. The name
|
||||
is optional. The decorator may be used without parentheses.
|
||||
|
||||
The :meth:`add_app_template_test` method may be used to register a
|
||||
function later rather than decorating.
|
||||
|
||||
The test is available in all templates, not only those under this
|
||||
blueprint. Equivalent to :meth:`.Flask.template_test`.
|
||||
|
||||
:param name: The name to register the filter as. If not given, uses the
|
||||
function's name.
|
||||
self, name: str | None = None
|
||||
) -> t.Callable[[T_template_test], T_template_test]:
|
||||
"""Register a template test, available in any template rendered by the
|
||||
application. Equivalent to :meth:`.Flask.template_test`.
|
||||
|
||||
.. versionadded:: 0.10
|
||||
|
||||
:param name: the optional name of the test, otherwise the
|
||||
function name will be used.
|
||||
"""
|
||||
if callable(name):
|
||||
self.add_app_template_test(name)
|
||||
return name
|
||||
|
||||
def decorator(f: T_template_test) -> T_template_test:
|
||||
self.add_app_template_test(f, name=name)
|
||||
|
|
@ -532,53 +497,33 @@ class Blueprint(Scaffold):
|
|||
def add_app_template_test(
|
||||
self, f: ft.TemplateTestCallable, name: str | None = None
|
||||
) -> None:
|
||||
"""Register a function to use as a custom Jinja test.
|
||||
|
||||
The :meth:`app_template_test` decorator can be used to register a
|
||||
function by decorating instead.
|
||||
|
||||
The test is available in all templates, not only those under this
|
||||
blueprint. Equivalent to :meth:`.Flask.add_template_test`.
|
||||
|
||||
:param f: The function to register.
|
||||
:param name: The name to register the test as. If not given, uses the
|
||||
function's name.
|
||||
"""Register a template test, available in any template rendered by the
|
||||
application. Works like the :meth:`app_template_test` decorator. Equivalent to
|
||||
:meth:`.Flask.add_template_test`.
|
||||
|
||||
.. versionadded:: 0.10
|
||||
|
||||
:param name: the optional name of the test, otherwise the
|
||||
function name will be used.
|
||||
"""
|
||||
|
||||
def register_template_test(state: BlueprintSetupState) -> None:
|
||||
state.app.add_template_test(f, name=name)
|
||||
def register_template(state: BlueprintSetupState) -> None:
|
||||
state.app.jinja_env.tests[name or f.__name__] = f
|
||||
|
||||
self.record_once(register_template_test)
|
||||
self.record_once(register_template)
|
||||
|
||||
@t.overload
|
||||
def app_template_global(self, name: T_template_global) -> T_template_global: ...
|
||||
@t.overload
|
||||
def app_template_global(
|
||||
self, name: str | None = None
|
||||
) -> t.Callable[[T_template_global], T_template_global]: ...
|
||||
@setupmethod
|
||||
def app_template_global(
|
||||
self, name: T_template_global | str | None = None
|
||||
) -> T_template_global | t.Callable[[T_template_global], T_template_global]:
|
||||
"""Decorate a function to register it as a custom Jinja global. The name
|
||||
is optional. The decorator may be used without parentheses.
|
||||
|
||||
The :meth:`add_app_template_global` method may be used to register a
|
||||
function later rather than decorating.
|
||||
|
||||
The global is available in all templates, not only those under this
|
||||
blueprint. Equivalent to :meth:`.Flask.template_global`.
|
||||
|
||||
:param name: The name to register the global as. If not given, uses the
|
||||
function's name.
|
||||
self, name: str | None = None
|
||||
) -> t.Callable[[T_template_global], T_template_global]:
|
||||
"""Register a template global, available in any template rendered by the
|
||||
application. Equivalent to :meth:`.Flask.template_global`.
|
||||
|
||||
.. versionadded:: 0.10
|
||||
|
||||
:param name: the optional name of the global, otherwise the
|
||||
function name will be used.
|
||||
"""
|
||||
if callable(name):
|
||||
self.add_app_template_global(name)
|
||||
return name
|
||||
|
||||
def decorator(f: T_template_global) -> T_template_global:
|
||||
self.add_app_template_global(f, name=name)
|
||||
|
|
@ -590,25 +535,20 @@ class Blueprint(Scaffold):
|
|||
def add_app_template_global(
|
||||
self, f: ft.TemplateGlobalCallable, name: str | None = None
|
||||
) -> None:
|
||||
"""Register a function to use as a custom Jinja global.
|
||||
|
||||
The :meth:`app_template_global` decorator can be used to register a function
|
||||
by decorating instead.
|
||||
|
||||
The global is available in all templates, not only those under this
|
||||
blueprint. Equivalent to :meth:`.Flask.add_template_global`.
|
||||
|
||||
:param f: The function to register.
|
||||
:param name: The name to register the global as. If not given, uses the
|
||||
function's name.
|
||||
"""Register a template global, available in any template rendered by the
|
||||
application. Works like the :meth:`app_template_global` decorator. Equivalent to
|
||||
:meth:`.Flask.add_template_global`.
|
||||
|
||||
.. versionadded:: 0.10
|
||||
|
||||
:param name: the optional name of the global, otherwise the
|
||||
function name will be used.
|
||||
"""
|
||||
|
||||
def register_template_global(state: BlueprintSetupState) -> None:
|
||||
state.app.add_template_global(f, name=name)
|
||||
def register_template(state: BlueprintSetupState) -> None:
|
||||
state.app.jinja_env.globals[name or f.__name__] = f
|
||||
|
||||
self.record_once(register_template_global)
|
||||
self.record_once(register_template)
|
||||
|
||||
@setupmethod
|
||||
def before_app_request(self, f: T_before_request) -> T_before_request:
|
||||
|
|
|
|||
|
|
@ -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 during testing.
|
||||
popped. Typically this happens at the end of each request, but
|
||||
contexts may be pushed manually as well during testing.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
|
|||
|
|
@ -7,13 +7,16 @@ from jinja2 import Environment as BaseEnvironment
|
|||
from jinja2 import Template
|
||||
from jinja2 import TemplateNotFound
|
||||
|
||||
from .ctx import AppContext
|
||||
from .globals import app_ctx
|
||||
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
|
||||
|
||||
if t.TYPE_CHECKING: # pragma: no cover
|
||||
from .app import Flask
|
||||
from .sansio.app import App
|
||||
from .sansio.scaffold import Scaffold
|
||||
|
||||
|
|
@ -22,14 +25,15 @@ def _default_template_ctx_processor() -> dict[str, t.Any]:
|
|||
"""Default template context processor. Replaces the ``request`` and ``g``
|
||||
proxies with their concrete objects for faster access.
|
||||
"""
|
||||
ctx = app_ctx._get_current_object()
|
||||
rv: dict[str, t.Any] = {"g": ctx.g}
|
||||
|
||||
if ctx.has_request:
|
||||
rv["request"] = ctx.request
|
||||
appctx = _cv_app.get(None)
|
||||
reqctx = _cv_request.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
|
||||
# The session proxy cannot be replaced, accessing it gets
|
||||
# RequestContext.session, which sets session.accessed.
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
|
|
@ -120,9 +124,8 @@ class DispatchingJinjaLoader(BaseLoader):
|
|||
return list(result)
|
||||
|
||||
|
||||
def _render(ctx: AppContext, template: Template, context: dict[str, t.Any]) -> str:
|
||||
app = ctx.app
|
||||
app.update_template_context(ctx, context)
|
||||
def _render(app: Flask, template: Template, context: dict[str, t.Any]) -> str:
|
||||
app.update_template_context(context)
|
||||
before_render_template.send(
|
||||
app, _async_wrapper=app.ensure_sync, template=template, context=context
|
||||
)
|
||||
|
|
@ -143,9 +146,9 @@ 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.
|
||||
"""
|
||||
ctx = app_ctx._get_current_object()
|
||||
template = ctx.app.jinja_env.get_or_select_template(template_name_or_list)
|
||||
return _render(ctx, template, context)
|
||||
app = current_app._get_current_object() # type: ignore[attr-defined]
|
||||
template = app.jinja_env.get_or_select_template(template_name_or_list)
|
||||
return _render(app, template, context)
|
||||
|
||||
|
||||
def render_template_string(source: str, **context: t.Any) -> str:
|
||||
|
|
@ -155,16 +158,15 @@ 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.
|
||||
"""
|
||||
ctx = app_ctx._get_current_object()
|
||||
template = ctx.app.jinja_env.from_string(source)
|
||||
return _render(ctx, template, context)
|
||||
app = current_app._get_current_object() # type: ignore[attr-defined]
|
||||
template = app.jinja_env.from_string(source)
|
||||
return _render(app, template, context)
|
||||
|
||||
|
||||
def _stream(
|
||||
ctx: AppContext, template: Template, context: dict[str, t.Any]
|
||||
app: Flask, template: Template, context: dict[str, t.Any]
|
||||
) -> t.Iterator[str]:
|
||||
app = ctx.app
|
||||
app.update_template_context(ctx, context)
|
||||
app.update_template_context(context)
|
||||
before_render_template.send(
|
||||
app, _async_wrapper=app.ensure_sync, template=template, context=context
|
||||
)
|
||||
|
|
@ -175,7 +177,13 @@ def _stream(
|
|||
app, _async_wrapper=app.ensure_sync, template=template, context=context
|
||||
)
|
||||
|
||||
return stream_with_context(generate())
|
||||
rv = generate()
|
||||
|
||||
# If a request context is active, keep it while generating.
|
||||
if request:
|
||||
rv = stream_with_context(rv)
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
def stream_template(
|
||||
|
|
@ -192,9 +200,9 @@ def stream_template(
|
|||
|
||||
.. versionadded:: 2.2
|
||||
"""
|
||||
ctx = app_ctx._get_current_object()
|
||||
template = ctx.app.jinja_env.get_or_select_template(template_name_or_list)
|
||||
return _stream(ctx, template, context)
|
||||
app = current_app._get_current_object() # type: ignore[attr-defined]
|
||||
template = app.jinja_env.get_or_select_template(template_name_or_list)
|
||||
return _stream(app, template, context)
|
||||
|
||||
|
||||
def stream_template_string(source: str, **context: t.Any) -> t.Iterator[str]:
|
||||
|
|
@ -207,6 +215,6 @@ def stream_template_string(source: str, **context: t.Any) -> t.Iterator[str]:
|
|||
|
||||
.. versionadded:: 2.2
|
||||
"""
|
||||
ctx = app_ctx._get_current_object()
|
||||
template = ctx.app.jinja_env.from_string(source)
|
||||
return _stream(ctx, template, context)
|
||||
app = current_app._get_current_object() # type: ignore[attr-defined]
|
||||
template = app.jinja_env.from_string(source)
|
||||
return _stream(app, template, context)
|
||||
|
|
|
|||
|
|
@ -107,10 +107,10 @@ def _get_werkzeug_version() -> str:
|
|||
|
||||
|
||||
class FlaskClient(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`.
|
||||
"""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`.
|
||||
|
||||
.. versionchanged:: 0.12
|
||||
`app.test_client()` includes preset default environment, which can be
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ ResponseValue = t.Union[
|
|||
]
|
||||
|
||||
# the possible types for an individual HTTP header
|
||||
HeaderValue = str | list[str] | tuple[str, ...]
|
||||
# This should be a Union, but mypy doesn't pass unless it's a TypeVar.
|
||||
HeaderValue = t.Union[str, list[str], tuple[str, ...]]
|
||||
|
||||
# the possible types for HTTP headers
|
||||
HeadersValue = t.Union[
|
||||
|
|
@ -46,29 +47,34 @@ ResponseReturnValue = t.Union[
|
|||
# callback annotated with flask.Response fail type checking.
|
||||
ResponseClass = t.TypeVar("ResponseClass", bound="Response")
|
||||
|
||||
AppOrBlueprintKey = str | None # The App key is None, whereas blueprints are named
|
||||
AfterRequestCallable = (
|
||||
t.Callable[[ResponseClass], ResponseClass]
|
||||
| t.Callable[[ResponseClass], t.Awaitable[ResponseClass]]
|
||||
)
|
||||
BeforeFirstRequestCallable = t.Callable[[], None] | t.Callable[[], t.Awaitable[None]]
|
||||
BeforeRequestCallable = (
|
||||
t.Callable[[], ResponseReturnValue | None]
|
||||
| t.Callable[[], t.Awaitable[ResponseReturnValue | None]]
|
||||
)
|
||||
AppOrBlueprintKey = t.Optional[str] # The App key is None, whereas blueprints are named
|
||||
AfterRequestCallable = t.Union[
|
||||
t.Callable[[ResponseClass], ResponseClass],
|
||||
t.Callable[[ResponseClass], t.Awaitable[ResponseClass]],
|
||||
]
|
||||
BeforeFirstRequestCallable = t.Union[
|
||||
t.Callable[[], None], t.Callable[[], t.Awaitable[None]]
|
||||
]
|
||||
BeforeRequestCallable = t.Union[
|
||||
t.Callable[[], t.Optional[ResponseReturnValue]],
|
||||
t.Callable[[], t.Awaitable[t.Optional[ResponseReturnValue]]],
|
||||
]
|
||||
ShellContextProcessorCallable = t.Callable[[], dict[str, t.Any]]
|
||||
TeardownCallable = (
|
||||
t.Callable[[BaseException | None], None]
|
||||
| t.Callable[[BaseException | None], t.Awaitable[None]]
|
||||
)
|
||||
TemplateContextProcessorCallable = (
|
||||
t.Callable[[], dict[str, t.Any]] | t.Callable[[], t.Awaitable[dict[str, t.Any]]]
|
||||
)
|
||||
TeardownCallable = t.Union[
|
||||
t.Callable[[t.Optional[BaseException]], None],
|
||||
t.Callable[[t.Optional[BaseException]], t.Awaitable[None]],
|
||||
]
|
||||
TemplateContextProcessorCallable = t.Union[
|
||||
t.Callable[[], dict[str, t.Any]],
|
||||
t.Callable[[], t.Awaitable[dict[str, t.Any]]],
|
||||
]
|
||||
TemplateFilterCallable = t.Callable[..., t.Any]
|
||||
TemplateGlobalCallable = t.Callable[..., t.Any]
|
||||
TemplateTestCallable = t.Callable[..., bool]
|
||||
URLDefaultCallable = t.Callable[[str, dict[str, t.Any]], None]
|
||||
URLValuePreprocessorCallable = t.Callable[[str | None, dict[str, t.Any] | None], None]
|
||||
URLValuePreprocessorCallable = t.Callable[
|
||||
[t.Optional[str], t.Optional[dict[str, t.Any]]], None
|
||||
]
|
||||
|
||||
# This should take Exception, but that either breaks typing the argument
|
||||
# with a specific exception, or decorating multiple times with different
|
||||
|
|
@ -76,12 +82,12 @@ URLValuePreprocessorCallable = t.Callable[[str | None, dict[str, t.Any] | None],
|
|||
# https://github.com/pallets/flask/issues/4095
|
||||
# https://github.com/pallets/flask/issues/4295
|
||||
# https://github.com/pallets/flask/issues/4297
|
||||
ErrorHandlerCallable = (
|
||||
t.Callable[[t.Any], ResponseReturnValue]
|
||||
| t.Callable[[t.Any], t.Awaitable[ResponseReturnValue]]
|
||||
)
|
||||
ErrorHandlerCallable = t.Union[
|
||||
t.Callable[[t.Any], ResponseReturnValue],
|
||||
t.Callable[[t.Any], t.Awaitable[ResponseReturnValue]],
|
||||
]
|
||||
|
||||
RouteCallable = (
|
||||
t.Callable[..., ResponseReturnValue]
|
||||
| t.Callable[..., t.Awaitable[ResponseReturnValue]]
|
||||
)
|
||||
RouteCallable = t.Union[
|
||||
t.Callable[..., ResponseReturnValue],
|
||||
t.Callable[..., t.Awaitable[ResponseReturnValue]],
|
||||
]
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import pytest
|
|||
from _pytest import monkeypatch
|
||||
|
||||
from flask import Flask
|
||||
from flask.globals import app_ctx as _app_ctx
|
||||
from flask.globals import request_ctx
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
|
|
@ -83,17 +83,16 @@ 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()
|
||||
|
||||
while _app_ctx:
|
||||
leaks.append(_app_ctx._get_current_object())
|
||||
_app_ctx.pop()
|
||||
|
||||
assert not leaks
|
||||
assert leaks == []
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
import flask
|
||||
from flask.globals import app_ctx
|
||||
from flask.testing import FlaskClient
|
||||
from flask.globals import request_ctx
|
||||
|
||||
|
||||
def test_basic_url_generation(app):
|
||||
|
|
@ -109,8 +107,7 @@ def test_app_tearing_down_with_handled_exception_by_app_handler(app, client):
|
|||
with app.app_context():
|
||||
client.get("/")
|
||||
|
||||
# teardown request context, and with block context
|
||||
assert cleanup_stuff == [None, None]
|
||||
assert cleanup_stuff == [None]
|
||||
|
||||
|
||||
def test_app_tearing_down_with_unhandled_exception(app, client):
|
||||
|
|
@ -129,11 +126,9 @@ def test_app_tearing_down_with_unhandled_exception(app, client):
|
|||
with app.app_context():
|
||||
client.get("/")
|
||||
|
||||
assert len(cleanup_stuff) == 2
|
||||
assert len(cleanup_stuff) == 1
|
||||
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):
|
||||
|
|
@ -183,7 +178,8 @@ def test_context_refcounts(app, client):
|
|||
@app.route("/")
|
||||
def index():
|
||||
with app_ctx:
|
||||
pass
|
||||
with request_ctx:
|
||||
pass
|
||||
|
||||
assert flask.request.environ["werkzeug.request"] is not None
|
||||
return ""
|
||||
|
|
@ -211,55 +207,3 @@ def test_clean_pop(app):
|
|||
|
||||
assert called == ["flask_test", "TEARDOWN"]
|
||||
assert not flask.current_app
|
||||
|
||||
|
||||
def test_robust_teardown(app: flask.Flask, client: FlaskClient) -> None:
|
||||
count = 0
|
||||
|
||||
@app.teardown_request
|
||||
def request_teardown(e: Exception | None) -> None:
|
||||
nonlocal count
|
||||
count += 1
|
||||
raise ValueError("request_teardown")
|
||||
|
||||
@app.teardown_appcontext
|
||||
def app_teardown(e: Exception | None) -> None:
|
||||
nonlocal count
|
||||
count += 1
|
||||
raise ValueError("app_teardown")
|
||||
|
||||
@app.get("/")
|
||||
def index() -> str:
|
||||
return ""
|
||||
|
||||
def request_signal(sender: flask.Flask, exc: Exception | None) -> None:
|
||||
nonlocal count
|
||||
count += 1
|
||||
raise ValueError("request_signal")
|
||||
|
||||
def app_signal(sender: flask.Flask, exc: Exception | None) -> None:
|
||||
nonlocal count
|
||||
count += 1
|
||||
raise ValueError("app_signal")
|
||||
|
||||
with (
|
||||
flask.request_tearing_down.connected_to(request_signal, app),
|
||||
flask.appcontext_tearing_down.connected_to(app_signal, app),
|
||||
):
|
||||
if sys.version_info >= (3, 11):
|
||||
with pytest.raises(ExceptionGroup, match="context teardown") as exc_info: # noqa: F821
|
||||
client.get()
|
||||
|
||||
assert len(exc_info.value.exceptions) == 2
|
||||
eg1, eg2 = exc_info.value.exceptions
|
||||
assert isinstance(eg1, ExceptionGroup) # noqa: F821
|
||||
assert "request teardown" in eg1.message
|
||||
assert len(eg1.exceptions) == 2
|
||||
assert isinstance(eg2, ExceptionGroup) # noqa: F821
|
||||
assert "app teardown" in eg2.message
|
||||
assert len(eg2.exceptions) == 2
|
||||
else:
|
||||
with pytest.raises(ValueError, match="request_teardown"):
|
||||
client.get()
|
||||
|
||||
assert count == 4
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import gc
|
||||
import importlib.metadata
|
||||
import re
|
||||
import typing as t
|
||||
import uuid
|
||||
import warnings
|
||||
import weakref
|
||||
from contextlib import nullcontext
|
||||
from datetime import datetime
|
||||
|
|
@ -20,7 +20,7 @@ from werkzeug.routing import BuildError
|
|||
from werkzeug.routing import RequestRedirect
|
||||
|
||||
import flask
|
||||
from flask.globals import app_ctx
|
||||
from flask.globals import request_ctx
|
||||
from flask.testing import FlaskClient
|
||||
|
||||
require_cpython_gc = pytest.mark.skipif(
|
||||
|
|
@ -69,61 +69,63 @@ def test_method_route_no_methods(app):
|
|||
app.get("/", methods=["GET", "POST"])
|
||||
|
||||
|
||||
def test_provide_automatic_options_attr_disable(
|
||||
app: flask.Flask, client: FlaskClient
|
||||
) -> None:
|
||||
"""Automatic options can be disabled by the view func attribute."""
|
||||
def test_provide_automatic_options_attr():
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
def index():
|
||||
return "Hello World!"
|
||||
|
||||
index.provide_automatic_options = False
|
||||
app.add_url_rule("/", view_func=index)
|
||||
rv = client.options()
|
||||
app.route("/")(index)
|
||||
rv = app.test_client().open("/", method="OPTIONS")
|
||||
assert rv.status_code == 405
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
def test_provide_automatic_options_attr_enable(
|
||||
app: flask.Flask, client: FlaskClient
|
||||
) -> None:
|
||||
"""When default automatic options is disabled in config, it can still be
|
||||
enabled by the view function attribute.
|
||||
"""
|
||||
app.config["PROVIDE_AUTOMATIC_OPTIONS"] = False
|
||||
|
||||
def index():
|
||||
def index2():
|
||||
return "Hello World!"
|
||||
|
||||
index.provide_automatic_options = True
|
||||
app.add_url_rule("/", view_func=index)
|
||||
rv = client.options()
|
||||
assert rv.allow == {"GET", "HEAD", "OPTIONS"}
|
||||
index2.provide_automatic_options = True
|
||||
app.route("/", methods=["OPTIONS"])(index2)
|
||||
rv = app.test_client().open("/", method="OPTIONS")
|
||||
assert sorted(rv.allow) == ["OPTIONS"]
|
||||
|
||||
|
||||
def test_provide_automatic_options_arg_disable(
|
||||
app: flask.Flask, client: FlaskClient
|
||||
) -> None:
|
||||
"""Automatic options can be disabled by the route argument."""
|
||||
|
||||
@app.get("/", provide_automatic_options=False)
|
||||
def test_provide_automatic_options_kwarg(app, client):
|
||||
def index():
|
||||
return "Hello World!"
|
||||
return flask.request.method
|
||||
|
||||
rv = client.options()
|
||||
def more():
|
||||
return flask.request.method
|
||||
|
||||
app.add_url_rule("/", view_func=index, provide_automatic_options=False)
|
||||
app.add_url_rule(
|
||||
"/more",
|
||||
view_func=more,
|
||||
methods=["GET", "POST"],
|
||||
provide_automatic_options=False,
|
||||
)
|
||||
assert client.get("/").data == b"GET"
|
||||
|
||||
rv = client.post("/")
|
||||
assert rv.status_code == 405
|
||||
assert sorted(rv.allow) == ["GET", "HEAD"]
|
||||
|
||||
rv = client.open("/", method="OPTIONS")
|
||||
assert rv.status_code == 405
|
||||
|
||||
rv = client.head("/")
|
||||
assert rv.status_code == 200
|
||||
assert not rv.data # head truncates
|
||||
assert client.post("/more").data == b"POST"
|
||||
assert client.get("/more").data == b"GET"
|
||||
|
||||
def test_provide_automatic_options_method_disable(
|
||||
app: flask.Flask, client: FlaskClient
|
||||
) -> None:
|
||||
"""Automatic options is ignored if the route handles options."""
|
||||
rv = client.delete("/more")
|
||||
assert rv.status_code == 405
|
||||
assert sorted(rv.allow) == ["GET", "HEAD", "POST"]
|
||||
|
||||
@app.route("/", methods=["OPTIONS"])
|
||||
def index():
|
||||
return "", {"X-Test": "test"}
|
||||
|
||||
rv = client.options()
|
||||
assert rv.headers["X-Test"] == "test"
|
||||
rv = client.open("/more", method="OPTIONS")
|
||||
assert rv.status_code == 405
|
||||
|
||||
|
||||
def test_request_dispatching(app, client):
|
||||
|
|
@ -248,29 +250,29 @@ def test_session_accessed(app: flask.Flask, client: FlaskClient) -> None:
|
|||
with client:
|
||||
rv = client.get("/nothing")
|
||||
assert "cookie" not in rv.vary
|
||||
assert not app_ctx._session.accessed
|
||||
assert not app_ctx._session.modified
|
||||
assert not request_ctx._session.accessed
|
||||
assert not request_ctx._session.modified
|
||||
|
||||
with client:
|
||||
rv = client.post(data={"value": "42"})
|
||||
assert rv.text == "value set"
|
||||
assert "cookie" in rv.vary
|
||||
assert app_ctx._session.accessed
|
||||
assert app_ctx._session.modified
|
||||
assert request_ctx._session.accessed
|
||||
assert request_ctx._session.modified
|
||||
|
||||
with client:
|
||||
rv = client.get()
|
||||
assert rv.text == "42"
|
||||
assert "cookie" in rv.vary
|
||||
assert app_ctx._session.accessed
|
||||
assert not app_ctx._session.modified
|
||||
assert request_ctx._session.accessed
|
||||
assert not request_ctx._session.modified
|
||||
|
||||
with client:
|
||||
rv = client.get("/nothing")
|
||||
assert rv.text == ""
|
||||
assert "cookie" not in rv.vary
|
||||
assert not app_ctx._session.accessed
|
||||
assert not app_ctx._session.modified
|
||||
assert not request_ctx._session.accessed
|
||||
assert not request_ctx._session.modified
|
||||
|
||||
|
||||
def test_session_path(app, client):
|
||||
|
|
@ -1420,21 +1422,20 @@ def test_url_for_passes_special_values_to_build_error_handler(app):
|
|||
|
||||
|
||||
def test_static_files(app, client):
|
||||
with client.get("/static/index.html") as rv:
|
||||
assert rv.status_code == 200
|
||||
assert rv.data.strip() == b"<h1>Hello World!</h1>"
|
||||
with app.test_request_context():
|
||||
assert (
|
||||
flask.url_for("static", filename="index.html") == "/static/index.html"
|
||||
)
|
||||
rv = client.get("/static/index.html")
|
||||
assert rv.status_code == 200
|
||||
assert rv.data.strip() == b"<h1>Hello World!</h1>"
|
||||
with app.test_request_context():
|
||||
assert flask.url_for("static", filename="index.html") == "/static/index.html"
|
||||
rv.close()
|
||||
|
||||
|
||||
def test_static_url_path():
|
||||
app = flask.Flask(__name__, static_url_path="/foo")
|
||||
app.testing = True
|
||||
|
||||
with app.test_client().get("/foo/index.html") as rv:
|
||||
assert rv.status_code == 200
|
||||
rv = app.test_client().get("/foo/index.html")
|
||||
assert rv.status_code == 200
|
||||
rv.close()
|
||||
|
||||
with app.test_request_context():
|
||||
assert flask.url_for("static", filename="index.html") == "/foo/index.html"
|
||||
|
|
@ -1443,9 +1444,9 @@ def test_static_url_path():
|
|||
def test_static_url_path_with_ending_slash():
|
||||
app = flask.Flask(__name__, static_url_path="/foo/")
|
||||
app.testing = True
|
||||
|
||||
with app.test_client().get("/foo/index.html") as rv:
|
||||
assert rv.status_code == 200
|
||||
rv = app.test_client().get("/foo/index.html")
|
||||
assert rv.status_code == 200
|
||||
rv.close()
|
||||
|
||||
with app.test_request_context():
|
||||
assert flask.url_for("static", filename="index.html") == "/foo/index.html"
|
||||
|
|
@ -1453,25 +1454,25 @@ def test_static_url_path_with_ending_slash():
|
|||
|
||||
def test_static_url_empty_path(app):
|
||||
app = flask.Flask(__name__, static_folder="", static_url_path="")
|
||||
|
||||
with app.test_client().open("/static/index.html", method="GET") as rv:
|
||||
assert rv.status_code == 200
|
||||
rv = app.test_client().open("/static/index.html", method="GET")
|
||||
assert rv.status_code == 200
|
||||
rv.close()
|
||||
|
||||
|
||||
def test_static_url_empty_path_default(app):
|
||||
app = flask.Flask(__name__, static_folder="")
|
||||
|
||||
with app.test_client().open("/static/index.html", method="GET") as rv:
|
||||
assert rv.status_code == 200
|
||||
rv = app.test_client().open("/static/index.html", method="GET")
|
||||
assert rv.status_code == 200
|
||||
rv.close()
|
||||
|
||||
|
||||
def test_static_folder_with_pathlib_path(app):
|
||||
from pathlib import Path
|
||||
|
||||
app = flask.Flask(__name__, static_folder=Path("static"))
|
||||
|
||||
with app.test_client().open("/static/index.html", method="GET") as rv:
|
||||
assert rv.status_code == 200
|
||||
rv = app.test_client().open("/static/index.html", method="GET")
|
||||
assert rv.status_code == 200
|
||||
rv.close()
|
||||
|
||||
|
||||
def test_static_folder_with_ending_slash():
|
||||
|
|
@ -1488,10 +1489,9 @@ def test_static_folder_with_ending_slash():
|
|||
def test_static_route_with_host_matching():
|
||||
app = flask.Flask(__name__, host_matching=True, static_host="example.com")
|
||||
c = app.test_client()
|
||||
|
||||
with c.get("http://example.com/static/index.html") as rv:
|
||||
assert rv.status_code == 200
|
||||
|
||||
rv = c.get("http://example.com/static/index.html")
|
||||
assert rv.status_code == 200
|
||||
rv.close()
|
||||
with app.test_request_context():
|
||||
rv = flask.url_for("static", filename="index.html", _external=True)
|
||||
assert rv == "http://example.com/static/index.html"
|
||||
|
|
@ -1512,22 +1512,20 @@ def test_request_locals():
|
|||
assert not flask.g
|
||||
|
||||
|
||||
werkzeug_3_2 = importlib.metadata.version("werkzeug") >= "3.2."
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("subdomain_matching", "host_matching", "expect_subdomain", "expect_host"),
|
||||
("subdomain_matching", "host_matching", "expect_base", "expect_abc", "expect_xyz"),
|
||||
[
|
||||
(False, False, "default", "default"),
|
||||
(True, False, "abc", "<invalid>"),
|
||||
(False, True, "abc", "default"),
|
||||
(False, False, "default", "default", "default"),
|
||||
(True, False, "default", "abc", "<invalid>"),
|
||||
(False, True, "default", "abc", "default"),
|
||||
],
|
||||
)
|
||||
def test_server_name_matching(
|
||||
subdomain_matching: bool,
|
||||
host_matching: bool,
|
||||
expect_subdomain: str,
|
||||
expect_host: str,
|
||||
expect_base: str,
|
||||
expect_abc: str,
|
||||
expect_xyz: str,
|
||||
) -> None:
|
||||
app = flask.Flask(
|
||||
__name__,
|
||||
|
|
@ -1545,18 +1543,15 @@ def test_server_name_matching(
|
|||
client = app.test_client()
|
||||
|
||||
r = client.get(base_url="http://example.test")
|
||||
assert r.text == "default"
|
||||
assert r.text == expect_base
|
||||
|
||||
r = client.get(base_url="http://abc.example.test")
|
||||
assert r.text == expect_subdomain
|
||||
assert r.text == expect_abc
|
||||
|
||||
with pytest.warns() if subdomain_matching else nullcontext():
|
||||
r = client.get(base_url="http://xyz.other.test")
|
||||
|
||||
if werkzeug_3_2:
|
||||
assert r.text == "default"
|
||||
else:
|
||||
assert r.text == expect_host
|
||||
assert r.text == expect_xyz
|
||||
|
||||
|
||||
def test_server_name_subdomain():
|
||||
|
|
@ -1592,12 +1587,12 @@ def test_server_name_subdomain():
|
|||
rv = client.get("/", "https://dev.local")
|
||||
assert rv.data == b"default"
|
||||
|
||||
with pytest.warns(match="Current server name"):
|
||||
# suppress Werkzeug 0.15 warning about name mismatch
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings(
|
||||
"ignore", "Current server name", UserWarning, "flask.app"
|
||||
)
|
||||
rv = client.get("/", "http://foo.localhost")
|
||||
|
||||
if werkzeug_3_2:
|
||||
assert rv.status_code == 200
|
||||
else:
|
||||
assert rv.status_code == 404
|
||||
|
||||
rv = client.get("/", "http://foo.dev.local")
|
||||
|
|
@ -1833,13 +1828,13 @@ def test_subdomain_matching_other_name(matching):
|
|||
def index():
|
||||
return "", 204
|
||||
|
||||
with pytest.warns(match="Current server name") if matching else nullcontext():
|
||||
# ip address can't match name, but will fall back to default
|
||||
# suppress Werkzeug 0.15 warning about name mismatch
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings(
|
||||
"ignore", "Current server name", UserWarning, "flask.app"
|
||||
)
|
||||
# ip address can't match name
|
||||
rv = client.get("/", "http://127.0.0.1:3000/")
|
||||
|
||||
if werkzeug_3_2:
|
||||
assert rv.status_code == 204
|
||||
else:
|
||||
assert rv.status_code == 404 if matching else 204
|
||||
|
||||
# allow all subdomains if matching is disabled
|
||||
|
|
|
|||
|
|
@ -184,12 +184,12 @@ def test_templates_and_static(test_apps):
|
|||
assert rv.data == b"Hello from the Admin"
|
||||
rv = client.get("/admin/index2")
|
||||
assert rv.data == b"Hello from the Admin"
|
||||
|
||||
with client.get("/admin/static/test.txt") as rv:
|
||||
assert rv.data.strip() == b"Admin File"
|
||||
|
||||
with client.get("/admin/static/css/test.css") as rv:
|
||||
assert rv.data.strip() == b"/* nested file */"
|
||||
rv = client.get("/admin/static/test.txt")
|
||||
assert rv.data.strip() == b"Admin File"
|
||||
rv.close()
|
||||
rv = client.get("/admin/static/css/test.css")
|
||||
assert rv.data.strip() == b"/* nested file */"
|
||||
rv.close()
|
||||
|
||||
# try/finally, in case other tests use this app for Blueprint tests.
|
||||
max_age_default = app.config["SEND_FILE_MAX_AGE_DEFAULT"]
|
||||
|
|
@ -198,10 +198,10 @@ def test_templates_and_static(test_apps):
|
|||
if app.config["SEND_FILE_MAX_AGE_DEFAULT"] == expected_max_age:
|
||||
expected_max_age = 7200
|
||||
app.config["SEND_FILE_MAX_AGE_DEFAULT"] = expected_max_age
|
||||
|
||||
with client.get("/admin/static/css/test.css") as rv:
|
||||
cc = parse_cache_control_header(rv.headers["Cache-Control"])
|
||||
assert cc.max_age == expected_max_age
|
||||
rv = client.get("/admin/static/css/test.css")
|
||||
cc = parse_cache_control_header(rv.headers["Cache-Control"])
|
||||
assert cc.max_age == expected_max_age
|
||||
rv.close()
|
||||
finally:
|
||||
app.config["SEND_FILE_MAX_AGE_DEFAULT"] = max_age_default
|
||||
|
||||
|
|
@ -220,19 +220,28 @@ def test_templates_and_static(test_apps):
|
|||
assert flask.render_template("nested/nested.txt") == "I'm nested"
|
||||
|
||||
|
||||
def test_default_static_max_age(app: flask.Flask) -> None:
|
||||
def test_default_static_max_age(app):
|
||||
class MyBlueprint(flask.Blueprint):
|
||||
def get_send_file_max_age(self, filename):
|
||||
return 100
|
||||
|
||||
blueprint = MyBlueprint(
|
||||
"blueprint", __name__, url_prefix="/bp", static_folder="static"
|
||||
)
|
||||
blueprint = MyBlueprint("blueprint", __name__, static_folder="static")
|
||||
app.register_blueprint(blueprint)
|
||||
|
||||
with app.test_request_context(), blueprint.send_static_file("index.html") as rv:
|
||||
cc = parse_cache_control_header(rv.headers["Cache-Control"])
|
||||
assert cc.max_age == 100
|
||||
# try/finally, in case other tests use this app for Blueprint tests.
|
||||
max_age_default = app.config["SEND_FILE_MAX_AGE_DEFAULT"]
|
||||
try:
|
||||
with app.test_request_context():
|
||||
unexpected_max_age = 3600
|
||||
if app.config["SEND_FILE_MAX_AGE_DEFAULT"] == unexpected_max_age:
|
||||
unexpected_max_age = 7200
|
||||
app.config["SEND_FILE_MAX_AGE_DEFAULT"] = unexpected_max_age
|
||||
rv = blueprint.send_static_file("index.html")
|
||||
cc = parse_cache_control_header(rv.headers["Cache-Control"])
|
||||
assert cc.max_age == 100
|
||||
rv.close()
|
||||
finally:
|
||||
app.config["SEND_FILE_MAX_AGE_DEFAULT"] = max_age_default
|
||||
|
||||
|
||||
def test_templates_list(test_apps):
|
||||
|
|
@ -357,35 +366,11 @@ def test_template_filter(app):
|
|||
def my_reverse(s):
|
||||
return s[::-1]
|
||||
|
||||
@bp.app_template_filter
|
||||
def my_reverse_2(s):
|
||||
return s[::-1]
|
||||
|
||||
@bp.app_template_filter("my_reverse_custom_name_3")
|
||||
def my_reverse_3(s):
|
||||
return s[::-1]
|
||||
|
||||
@bp.app_template_filter(name="my_reverse_custom_name_4")
|
||||
def my_reverse_4(s):
|
||||
return s[::-1]
|
||||
|
||||
app.register_blueprint(bp, url_prefix="/py")
|
||||
assert "my_reverse" in app.jinja_env.filters.keys()
|
||||
assert app.jinja_env.filters["my_reverse"] == my_reverse
|
||||
assert app.jinja_env.filters["my_reverse"]("abcd") == "dcba"
|
||||
|
||||
assert "my_reverse_2" in app.jinja_env.filters.keys()
|
||||
assert app.jinja_env.filters["my_reverse_2"] == my_reverse_2
|
||||
assert app.jinja_env.filters["my_reverse_2"]("abcd") == "dcba"
|
||||
|
||||
assert "my_reverse_custom_name_3" in app.jinja_env.filters.keys()
|
||||
assert app.jinja_env.filters["my_reverse_custom_name_3"] == my_reverse_3
|
||||
assert app.jinja_env.filters["my_reverse_custom_name_3"]("abcd") == "dcba"
|
||||
|
||||
assert "my_reverse_custom_name_4" in app.jinja_env.filters.keys()
|
||||
assert app.jinja_env.filters["my_reverse_custom_name_4"] == my_reverse_4
|
||||
assert app.jinja_env.filters["my_reverse_custom_name_4"]("abcd") == "dcba"
|
||||
|
||||
|
||||
def test_add_template_filter(app):
|
||||
bp = flask.Blueprint("bp", __name__)
|
||||
|
|
@ -517,35 +502,11 @@ def test_template_test(app):
|
|||
def is_boolean(value):
|
||||
return isinstance(value, bool)
|
||||
|
||||
@bp.app_template_test
|
||||
def boolean_2(value):
|
||||
return isinstance(value, bool)
|
||||
|
||||
@bp.app_template_test("my_boolean_custom_name")
|
||||
def boolean_3(value):
|
||||
return isinstance(value, bool)
|
||||
|
||||
@bp.app_template_test(name="my_boolean_custom_name_2")
|
||||
def boolean_4(value):
|
||||
return isinstance(value, bool)
|
||||
|
||||
app.register_blueprint(bp, url_prefix="/py")
|
||||
assert "is_boolean" in app.jinja_env.tests.keys()
|
||||
assert app.jinja_env.tests["is_boolean"] == is_boolean
|
||||
assert app.jinja_env.tests["is_boolean"](False)
|
||||
|
||||
assert "boolean_2" in app.jinja_env.tests.keys()
|
||||
assert app.jinja_env.tests["boolean_2"] == boolean_2
|
||||
assert app.jinja_env.tests["boolean_2"](False)
|
||||
|
||||
assert "my_boolean_custom_name" in app.jinja_env.tests.keys()
|
||||
assert app.jinja_env.tests["my_boolean_custom_name"] == boolean_3
|
||||
assert app.jinja_env.tests["my_boolean_custom_name"](False)
|
||||
|
||||
assert "my_boolean_custom_name_2" in app.jinja_env.tests.keys()
|
||||
assert app.jinja_env.tests["my_boolean_custom_name_2"] == boolean_4
|
||||
assert app.jinja_env.tests["my_boolean_custom_name_2"](False)
|
||||
|
||||
|
||||
def test_add_template_test(app):
|
||||
bp = flask.Blueprint("bp", __name__)
|
||||
|
|
@ -718,18 +679,6 @@ def test_template_global(app):
|
|||
def get_answer():
|
||||
return 42
|
||||
|
||||
@bp.app_template_global
|
||||
def get_stuff_1():
|
||||
return "get_stuff_1"
|
||||
|
||||
@bp.app_template_global("my_get_stuff_custom_name_2")
|
||||
def get_stuff_2():
|
||||
return "get_stuff_2"
|
||||
|
||||
@bp.app_template_global(name="my_get_stuff_custom_name_3")
|
||||
def get_stuff_3():
|
||||
return "get_stuff_3"
|
||||
|
||||
# Make sure the function is not in the jinja_env already
|
||||
assert "get_answer" not in app.jinja_env.globals.keys()
|
||||
app.register_blueprint(bp)
|
||||
|
|
@ -739,31 +688,10 @@ def test_template_global(app):
|
|||
assert app.jinja_env.globals["get_answer"] is get_answer
|
||||
assert app.jinja_env.globals["get_answer"]() == 42
|
||||
|
||||
assert "get_stuff_1" in app.jinja_env.globals.keys()
|
||||
assert app.jinja_env.globals["get_stuff_1"] == get_stuff_1
|
||||
assert app.jinja_env.globals["get_stuff_1"](), "get_stuff_1"
|
||||
|
||||
assert "my_get_stuff_custom_name_2" in app.jinja_env.globals.keys()
|
||||
assert app.jinja_env.globals["my_get_stuff_custom_name_2"] == get_stuff_2
|
||||
assert app.jinja_env.globals["my_get_stuff_custom_name_2"](), "get_stuff_2"
|
||||
|
||||
assert "my_get_stuff_custom_name_3" in app.jinja_env.globals.keys()
|
||||
assert app.jinja_env.globals["my_get_stuff_custom_name_3"] == get_stuff_3
|
||||
assert app.jinja_env.globals["my_get_stuff_custom_name_3"](), "get_stuff_3"
|
||||
|
||||
with app.app_context():
|
||||
rv = flask.render_template_string("{{ get_answer() }}")
|
||||
assert rv == "42"
|
||||
|
||||
rv = flask.render_template_string("{{ get_stuff_1() }}")
|
||||
assert rv == "get_stuff_1"
|
||||
|
||||
rv = flask.render_template_string("{{ my_get_stuff_custom_name_2() }}")
|
||||
assert rv == "get_stuff_2"
|
||||
|
||||
rv = flask.render_template_string("{{ my_get_stuff_custom_name_3() }}")
|
||||
assert rv == "get_stuff_3"
|
||||
|
||||
|
||||
def test_request_processing(app, client):
|
||||
bp = flask.Blueprint("bp", __name__)
|
||||
|
|
|
|||
|
|
@ -462,7 +462,7 @@ class TestRoutes:
|
|||
|
||||
def expect_order(self, order, output):
|
||||
# skip the header and match the start of each row
|
||||
for expect, line in zip(order, output.splitlines()[2:], strict=False):
|
||||
for expect, line in zip(order, output.splitlines()[2:]):
|
||||
# do this instead of startswith for nicer pytest output
|
||||
assert line[: len(expect)] == expect
|
||||
|
||||
|
|
@ -489,7 +489,6 @@ class TestRoutes:
|
|||
def test_all_methods(self, invoke):
|
||||
output = invoke(["routes"]).output
|
||||
assert "GET, HEAD, OPTIONS, POST" not in output
|
||||
|
||||
output = invoke(["routes", "--all-methods"]).output
|
||||
assert "GET, HEAD, OPTIONS, POST" in output
|
||||
|
||||
|
|
|
|||
|
|
@ -32,39 +32,45 @@ class PyBytesIO:
|
|||
|
||||
class TestSendfile:
|
||||
def test_send_file(self, app, req_ctx):
|
||||
with app.open_resource("static/index.html") as f:
|
||||
expect = f.read()
|
||||
rv = flask.send_file("static/index.html")
|
||||
assert rv.direct_passthrough
|
||||
assert rv.mimetype == "text/html"
|
||||
|
||||
with flask.send_file("static/index.html") as rv:
|
||||
assert rv.direct_passthrough
|
||||
assert rv.mimetype == "text/html"
|
||||
with app.open_resource("static/index.html") as f:
|
||||
rv.direct_passthrough = False
|
||||
assert rv.data == expect
|
||||
assert rv.data == f.read()
|
||||
|
||||
rv.close()
|
||||
|
||||
def test_static_file(self, app, req_ctx):
|
||||
# Default max_age is None.
|
||||
|
||||
# Test with static file handler.
|
||||
with app.send_static_file("index.html") as rv:
|
||||
assert rv.cache_control.max_age is None
|
||||
rv = app.send_static_file("index.html")
|
||||
assert rv.cache_control.max_age is None
|
||||
rv.close()
|
||||
|
||||
# Test with direct use of send_file.
|
||||
with flask.send_file("static/index.html") as rv:
|
||||
assert rv.cache_control.max_age is None
|
||||
rv = flask.send_file("static/index.html")
|
||||
assert rv.cache_control.max_age is None
|
||||
rv.close()
|
||||
|
||||
app.config["SEND_FILE_MAX_AGE_DEFAULT"] = 3600
|
||||
|
||||
# Test with static file handler.
|
||||
with app.send_static_file("index.html") as rv:
|
||||
assert rv.cache_control.max_age == 3600
|
||||
rv = app.send_static_file("index.html")
|
||||
assert rv.cache_control.max_age == 3600
|
||||
rv.close()
|
||||
|
||||
# Test with direct use of send_file.
|
||||
with flask.send_file("static/index.html") as rv:
|
||||
assert rv.cache_control.max_age == 3600
|
||||
rv = flask.send_file("static/index.html")
|
||||
assert rv.cache_control.max_age == 3600
|
||||
rv.close()
|
||||
|
||||
# Test with pathlib.Path.
|
||||
with app.send_static_file(FakePath("index.html")) as rv:
|
||||
assert rv.cache_control.max_age == 3600
|
||||
rv = app.send_static_file(FakePath("index.html"))
|
||||
assert rv.cache_control.max_age == 3600
|
||||
rv.close()
|
||||
|
||||
class StaticFileApp(flask.Flask):
|
||||
def get_send_file_max_age(self, filename):
|
||||
|
|
@ -74,21 +80,23 @@ class TestSendfile:
|
|||
|
||||
with app.test_request_context():
|
||||
# Test with static file handler.
|
||||
with app.send_static_file("index.html") as rv:
|
||||
assert rv.cache_control.max_age == 10
|
||||
rv = app.send_static_file("index.html")
|
||||
assert rv.cache_control.max_age == 10
|
||||
rv.close()
|
||||
|
||||
# Test with direct use of send_file.
|
||||
with flask.send_file("static/index.html") as rv:
|
||||
assert rv.cache_control.max_age == 10
|
||||
rv = flask.send_file("static/index.html")
|
||||
assert rv.cache_control.max_age == 10
|
||||
rv.close()
|
||||
|
||||
def test_send_from_directory(self, app, req_ctx):
|
||||
app.root_path = os.path.join(
|
||||
os.path.dirname(__file__), "test_apps", "subdomaintestmodule"
|
||||
)
|
||||
|
||||
with flask.send_from_directory("static", "hello.txt") as rv:
|
||||
rv.direct_passthrough = False
|
||||
assert rv.data.strip() == b"Hello Subdomain"
|
||||
rv = flask.send_from_directory("static", "hello.txt")
|
||||
rv.direct_passthrough = False
|
||||
assert rv.data.strip() == b"Hello Subdomain"
|
||||
rv.close()
|
||||
|
||||
|
||||
class TestUrlFor:
|
||||
|
|
@ -311,17 +319,15 @@ class TestStreaming:
|
|||
|
||||
# response is closed without reading stream
|
||||
client.get().close()
|
||||
|
||||
# response stream is read
|
||||
with client.get() as rv:
|
||||
assert rv.text == "flask"
|
||||
assert client.get().text == "flask"
|
||||
|
||||
# same as above, but with client context preservation
|
||||
with client:
|
||||
client.get().close()
|
||||
|
||||
with client, client.get() as rv:
|
||||
assert rv.text == "flask"
|
||||
with client:
|
||||
assert client.get().text == "flask"
|
||||
|
||||
|
||||
class TestHelpers:
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import collections.abc as cabc
|
||||
import warnings
|
||||
from concurrent import futures
|
||||
|
||||
import pytest
|
||||
|
||||
import flask
|
||||
from flask.globals import request_ctx
|
||||
from flask.sessions import SecureCookieSessionInterface
|
||||
from flask.sessions import SessionInterface
|
||||
from flask.testing import FlaskClient
|
||||
|
||||
try:
|
||||
from greenlet import greenlet
|
||||
except ImportError:
|
||||
greenlet = None
|
||||
|
||||
|
||||
def test_teardown_on_pop(app):
|
||||
|
|
@ -144,34 +145,61 @@ def test_manual_context_binding(app):
|
|||
index()
|
||||
|
||||
|
||||
def test_copy_context_thread(
|
||||
request: pytest.FixtureRequest, app: flask.Flask, client: FlaskClient
|
||||
) -> None:
|
||||
executor = futures.ThreadPoolExecutor(max_workers=2)
|
||||
request.addfinalizer(lambda: executor.shutdown(cancel_futures=True))
|
||||
result: cabc.Iterator[int] | None = None
|
||||
@pytest.mark.skipif(greenlet is None, reason="greenlet not installed")
|
||||
class TestGreenletContextCopying:
|
||||
def test_greenlet_context_copying(self, app, client):
|
||||
greenlets = []
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
flask.session["fizz"] = "buzz"
|
||||
@app.route("/")
|
||||
def index():
|
||||
flask.session["fizz"] = "buzz"
|
||||
reqctx = request_ctx.copy()
|
||||
|
||||
@flask.copy_current_request_context
|
||||
def work(n: int) -> int:
|
||||
assert flask.current_app == app
|
||||
assert flask.request.path == "/"
|
||||
assert flask.request.args["foo"] == "bar"
|
||||
assert flask.session["fizz"] == "buzz"
|
||||
return n
|
||||
def g():
|
||||
assert not flask.request
|
||||
assert not flask.current_app
|
||||
with reqctx:
|
||||
assert flask.request
|
||||
assert flask.current_app == app
|
||||
assert flask.request.path == "/"
|
||||
assert flask.request.args["foo"] == "bar"
|
||||
assert flask.session.get("fizz") == "buzz"
|
||||
assert not flask.request
|
||||
return 42
|
||||
|
||||
nonlocal result
|
||||
result = executor.map(work, range(10))
|
||||
return "Hello World!"
|
||||
greenlets.append(greenlet(g))
|
||||
return "Hello World!"
|
||||
|
||||
rv = client.get(query_string={"foo": "bar"})
|
||||
assert rv.text == "Hello World!"
|
||||
rv = client.get("/?foo=bar")
|
||||
assert rv.data == b"Hello World!"
|
||||
|
||||
assert result is not None
|
||||
assert set(result) == set(range(10))
|
||||
result = greenlets[0].run()
|
||||
assert result == 42
|
||||
|
||||
def test_greenlet_context_copying_api(self, app, client):
|
||||
greenlets = []
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
flask.session["fizz"] = "buzz"
|
||||
|
||||
@flask.copy_current_request_context
|
||||
def g():
|
||||
assert flask.request
|
||||
assert flask.current_app == app
|
||||
assert flask.request.path == "/"
|
||||
assert flask.request.args["foo"] == "bar"
|
||||
assert flask.session.get("fizz") == "buzz"
|
||||
return 42
|
||||
|
||||
greenlets.append(greenlet(g))
|
||||
return "Hello World!"
|
||||
|
||||
rv = client.get("/?foo=bar")
|
||||
assert rv.data == b"Hello World!"
|
||||
|
||||
result = greenlets[0].run()
|
||||
assert result == 42
|
||||
|
||||
|
||||
def test_session_error_pops_context():
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import flask
|
||||
from flask.globals import app_ctx
|
||||
from flask.globals import request_ctx
|
||||
from flask.sessions import SessionInterface
|
||||
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ def test_open_session_with_endpoint():
|
|||
pass
|
||||
|
||||
def open_session(self, app, request):
|
||||
app_ctx.match_request()
|
||||
request_ctx.match_request()
|
||||
assert request.endpoint is not None
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import flask
|
|||
|
||||
def test_suppressed_exception_logging():
|
||||
class SuppressedFlask(flask.Flask):
|
||||
def log_exception(self, ctx, exc_info):
|
||||
def log_exception(self, exc_info):
|
||||
pass
|
||||
|
||||
out = StringIO()
|
||||
|
|
|
|||
|
|
@ -129,30 +129,6 @@ def test_template_filter(app):
|
|||
assert app.jinja_env.filters["my_reverse"] == my_reverse
|
||||
assert app.jinja_env.filters["my_reverse"]("abcd") == "dcba"
|
||||
|
||||
@app.template_filter
|
||||
def my_reverse_2(s):
|
||||
return s[::-1]
|
||||
|
||||
assert "my_reverse_2" in app.jinja_env.filters.keys()
|
||||
assert app.jinja_env.filters["my_reverse_2"] == my_reverse_2
|
||||
assert app.jinja_env.filters["my_reverse_2"]("abcd") == "dcba"
|
||||
|
||||
@app.template_filter("my_reverse_custom_name_3")
|
||||
def my_reverse_3(s):
|
||||
return s[::-1]
|
||||
|
||||
assert "my_reverse_custom_name_3" in app.jinja_env.filters.keys()
|
||||
assert app.jinja_env.filters["my_reverse_custom_name_3"] == my_reverse_3
|
||||
assert app.jinja_env.filters["my_reverse_custom_name_3"]("abcd") == "dcba"
|
||||
|
||||
@app.template_filter(name="my_reverse_custom_name_4")
|
||||
def my_reverse_4(s):
|
||||
return s[::-1]
|
||||
|
||||
assert "my_reverse_custom_name_4" in app.jinja_env.filters.keys()
|
||||
assert app.jinja_env.filters["my_reverse_custom_name_4"] == my_reverse_4
|
||||
assert app.jinja_env.filters["my_reverse_custom_name_4"]("abcd") == "dcba"
|
||||
|
||||
|
||||
def test_add_template_filter(app):
|
||||
def my_reverse(s):
|
||||
|
|
@ -247,30 +223,6 @@ def test_template_test(app):
|
|||
assert app.jinja_env.tests["boolean"] == boolean
|
||||
assert app.jinja_env.tests["boolean"](False)
|
||||
|
||||
@app.template_test
|
||||
def boolean_2(value):
|
||||
return isinstance(value, bool)
|
||||
|
||||
assert "boolean_2" in app.jinja_env.tests.keys()
|
||||
assert app.jinja_env.tests["boolean_2"] == boolean_2
|
||||
assert app.jinja_env.tests["boolean_2"](False)
|
||||
|
||||
@app.template_test("my_boolean_custom_name")
|
||||
def boolean_3(value):
|
||||
return isinstance(value, bool)
|
||||
|
||||
assert "my_boolean_custom_name" in app.jinja_env.tests.keys()
|
||||
assert app.jinja_env.tests["my_boolean_custom_name"] == boolean_3
|
||||
assert app.jinja_env.tests["my_boolean_custom_name"](False)
|
||||
|
||||
@app.template_test(name="my_boolean_custom_name_2")
|
||||
def boolean_4(value):
|
||||
return isinstance(value, bool)
|
||||
|
||||
assert "my_boolean_custom_name_2" in app.jinja_env.tests.keys()
|
||||
assert app.jinja_env.tests["my_boolean_custom_name_2"] == boolean_4
|
||||
assert app.jinja_env.tests["my_boolean_custom_name_2"](False)
|
||||
|
||||
|
||||
def test_add_template_test(app):
|
||||
def boolean(value):
|
||||
|
|
@ -368,39 +320,6 @@ def test_add_template_global(app, app_ctx):
|
|||
rv = flask.render_template_string("{{ get_stuff() }}")
|
||||
assert rv == "42"
|
||||
|
||||
@app.template_global
|
||||
def get_stuff_1():
|
||||
return "get_stuff_1"
|
||||
|
||||
assert "get_stuff_1" in app.jinja_env.globals.keys()
|
||||
assert app.jinja_env.globals["get_stuff_1"] == get_stuff_1
|
||||
assert app.jinja_env.globals["get_stuff_1"](), "get_stuff_1"
|
||||
|
||||
rv = flask.render_template_string("{{ get_stuff_1() }}")
|
||||
assert rv == "get_stuff_1"
|
||||
|
||||
@app.template_global("my_get_stuff_custom_name_2")
|
||||
def get_stuff_2():
|
||||
return "get_stuff_2"
|
||||
|
||||
assert "my_get_stuff_custom_name_2" in app.jinja_env.globals.keys()
|
||||
assert app.jinja_env.globals["my_get_stuff_custom_name_2"] == get_stuff_2
|
||||
assert app.jinja_env.globals["my_get_stuff_custom_name_2"](), "get_stuff_2"
|
||||
|
||||
rv = flask.render_template_string("{{ my_get_stuff_custom_name_2() }}")
|
||||
assert rv == "get_stuff_2"
|
||||
|
||||
@app.template_global(name="my_get_stuff_custom_name_3")
|
||||
def get_stuff_3():
|
||||
return "get_stuff_3"
|
||||
|
||||
assert "my_get_stuff_custom_name_3" in app.jinja_env.globals.keys()
|
||||
assert app.jinja_env.globals["my_get_stuff_custom_name_3"] == get_stuff_3
|
||||
assert app.jinja_env.globals["my_get_stuff_custom_name_3"](), "get_stuff_3"
|
||||
|
||||
rv = flask.render_template_string("{{ my_get_stuff_custom_name_3() }}")
|
||||
assert rv == "get_stuff_3"
|
||||
|
||||
|
||||
def test_custom_template_loader(client):
|
||||
class MyFlask(flask.Flask):
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import pytest
|
|||
import flask
|
||||
from flask import appcontext_popped
|
||||
from flask.cli import ScriptInfo
|
||||
from flask.globals import _cv_app
|
||||
from flask.globals import _cv_request
|
||||
from flask.json import jsonify
|
||||
from flask.testing import EnvironBuilder
|
||||
from flask.testing import FlaskCliRunner
|
||||
|
|
@ -76,6 +76,7 @@ def test_client_open_environ(app, client, request):
|
|||
return flask.request.remote_addr
|
||||
|
||||
builder = EnvironBuilder(app, path="/index", method="GET")
|
||||
request.addfinalizer(builder.close)
|
||||
|
||||
rv = client.open(builder)
|
||||
assert rv.data == b"127.0.0.1"
|
||||
|
|
@ -381,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_app.get(None) is req_ctx
|
||||
assert _cv_request.get(None) is req_ctx
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import pytest
|
|||
from werkzeug.http import parse_set_header
|
||||
|
||||
import flask.views
|
||||
from flask.testing import FlaskClient
|
||||
|
||||
|
||||
def common_test(app):
|
||||
|
|
@ -99,55 +98,44 @@ def test_view_decorators(app, client):
|
|||
assert rv.data == b"Awesome"
|
||||
|
||||
|
||||
def test_view_provide_automatic_options_attr_disable(
|
||||
app: flask.Flask, client: FlaskClient
|
||||
) -> None:
|
||||
"""Automatic options can be disabled by the view class attribute."""
|
||||
def test_view_provide_automatic_options_attr():
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
class Index(flask.views.View):
|
||||
class Index1(flask.views.View):
|
||||
provide_automatic_options = False
|
||||
|
||||
def dispatch_request(self):
|
||||
return "Hello World!"
|
||||
|
||||
app.add_url_rule("/", view_func=Index.as_view("index"))
|
||||
rv = client.options()
|
||||
app.add_url_rule("/", view_func=Index1.as_view("index"))
|
||||
c = app.test_client()
|
||||
rv = c.open("/", method="OPTIONS")
|
||||
assert rv.status_code == 405
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
def test_view_provide_automatic_options_attr_enable(
|
||||
app: flask.Flask, client: FlaskClient
|
||||
) -> None:
|
||||
"""When default automatic options is disabled in config, it can still be
|
||||
enabled by the view class attribute.
|
||||
"""
|
||||
app.config["PROVIDE_AUTOMATIC_OPTIONS"] = False
|
||||
|
||||
class Index(flask.views.View):
|
||||
class Index2(flask.views.View):
|
||||
methods = ["OPTIONS"]
|
||||
provide_automatic_options = True
|
||||
|
||||
def dispatch_request(self):
|
||||
return "Hello World!"
|
||||
|
||||
app.add_url_rule("/", view_func=Index.as_view("index"))
|
||||
rv = client.options("/")
|
||||
assert rv.allow == {"GET", "HEAD", "OPTIONS"}
|
||||
app.add_url_rule("/", view_func=Index2.as_view("index"))
|
||||
c = app.test_client()
|
||||
rv = c.open("/", method="OPTIONS")
|
||||
assert sorted(rv.allow) == ["OPTIONS"]
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
def test_provide_automatic_options_method_disable(
|
||||
app: flask.Flask, client: FlaskClient
|
||||
) -> None:
|
||||
"""Automatic options is ignored if the route handles options."""
|
||||
|
||||
class Index(flask.views.View):
|
||||
methods = ["OPTIONS"]
|
||||
|
||||
class Index3(flask.views.View):
|
||||
def dispatch_request(self):
|
||||
return "", {"X-Test": "test"}
|
||||
return "Hello World!"
|
||||
|
||||
app.add_url_rule("/", view_func=Index.as_view("index"))
|
||||
rv = client.options()
|
||||
assert rv.headers["X-Test"] == "test"
|
||||
app.add_url_rule("/", view_func=Index3.as_view("index"))
|
||||
c = app.test_client()
|
||||
rv = c.open("/", method="OPTIONS")
|
||||
assert "OPTIONS" in rv.allow
|
||||
|
||||
|
||||
def test_implicit_head(app, client):
|
||||
|
|
@ -192,7 +180,7 @@ def test_endpoint_override(app):
|
|||
app.add_url_rule("/", view_func=Index.as_view("index"))
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
app.add_url_rule("/other", view_func=Index.as_view("index"))
|
||||
app.add_url_rule("/", view_func=Index.as_view("index"))
|
||||
|
||||
# But these tests should still pass. We just log a warning.
|
||||
common_test(app)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue