Merge branch 'pallets:main' into main

This commit is contained in:
Peace 2022-01-12 12:35:24 +02:00 committed by GitHub
commit 0d0817bc30
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
73 changed files with 1086 additions and 715 deletions

View file

@ -1,8 +1,19 @@
version: 2
updates:
- package-ecosystem: pip
- package-ecosystem: "pip"
directory: "/requirements"
target-branch: "2.0.x"
versioning-strategy: "lockfile-only"
schedule:
interval: "monthly"
day: "monday"
time: "16:00"
timezone: "UTC"
open-pull-requests-limit: 99
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: monthly
time: "08:00"
open-pull-requests-limit: 99
interval: "monthly"
day: "monday"
time: "16:00"
timezone: "UTC"

View file

@ -8,8 +8,8 @@ jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v2
- uses: dessant/lock-threads@v3
with:
github-token: ${{ github.token }}
issue-lock-inactive-days: 14
pr-lock-inactive-days: 14
issue-inactive-days: 14
pr-inactive-days: 14

View file

@ -24,14 +24,17 @@ jobs:
fail-fast: false
matrix:
include:
- {name: Linux, python: '3.9', os: ubuntu-latest, tox: py39}
- {name: Windows, python: '3.9', os: windows-latest, tox: py39}
- {name: Mac, python: '3.9', os: macos-latest, tox: py39}
- {name: Linux, python: '3.10', os: ubuntu-latest, tox: py310}
- {name: Windows, python: '3.10', os: windows-latest, tox: py310}
- {name: Mac, python: '3.10', os: macos-latest, tox: py310}
- {name: '3.11-dev', python: '3.11-dev', os: ubuntu-latest, tox: py311}
- {name: '3.9', python: '3.9', os: ubuntu-latest, tox: py39}
- {name: '3.8', python: '3.8', os: ubuntu-latest, tox: py38}
- {name: '3.7', python: '3.7', os: ubuntu-latest, tox: py37}
- {name: '3.6', python: '3.6', os: ubuntu-latest, tox: py36}
- {name: 'PyPy', python: pypy3, os: ubuntu-latest, tox: pypy3}
- {name: Typing, python: '3.9', os: ubuntu-latest, tox: typing}
- {name: 'PyPy', python: 'pypy-3.7', os: ubuntu-latest, tox: pypy37}
- {name: 'Pallets Minimum Versions', python: '3.10', os: ubuntu-latest, tox: py-min}
- {name: 'Pallets Development Versions', python: '3.7', os: ubuntu-latest, tox: py-dev}
- {name: Typing, python: '3.10', os: ubuntu-latest, tox: typing}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2

View file

@ -1,31 +1,32 @@
ci:
autoupdate_branch: "2.0.x"
autoupdate_schedule: monthly
repos:
- repo: https://github.com/asottile/pyupgrade
rev: v2.15.0
rev: v2.29.1
hooks:
- id: pyupgrade
args: ["--py36-plus"]
- repo: https://github.com/asottile/reorder_python_imports
rev: v2.5.0
rev: v2.6.0
hooks:
- id: reorder-python-imports
name: Reorder Python imports (src, tests)
files: "^(?!examples/)"
args: ["--application-directories", "src"]
- repo: https://github.com/psf/black
rev: 21.5b1
rev: 21.12b0
hooks:
- id: black
- repo: https://github.com/PyCQA/flake8
rev: 3.9.2
rev: 4.0.1
hooks:
- id: flake8
additional_dependencies:
- flake8-bugbear
- flake8-implicit-str-concat
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.4.0
rev: v4.0.1
hooks:
- id: fix-byte-order-marker
- id: trailing-whitespace

View file

@ -1,4 +1,8 @@
version: 2
build:
os: ubuntu-20.04
tools:
python: "3.10"
python:
install:
- requirements: requirements/docs.txt

View file

@ -5,19 +5,79 @@ Version 2.1.0
Unreleased
- Update Click dependency to >= 8.0.
- Drop support for Python 3.6. :pr:`4335`
- Update Click dependency to >= 8.0. :pr:`4008`
- Remove previously deprecated code. :pr:`4337`
- The CLI does not pass ``script_info`` to app factory functions.
- ``config.from_json`` is replaced by
``config.from_file(name, load=json.load)``.
- ``json`` functions no longer take an ``encoding`` parameter.
- ``safe_join`` is removed, use ``werkzeug.utils.safe_join``
instead.
- ``total_seconds`` is removed, use ``timedelta.total_seconds``
instead.
- The same blueprint cannot be registered with the same name. Use
``name=`` when registering to specify a unique name.
- Some parameters in ``send_file`` and ``send_from_directory`` were
renamed in 2.0. The deprecation period for the old names is extended
to 2.2. Be sure to test with deprecation warnings visible.
- ``attachment_filename`` is renamed to ``download_name``.
- ``cache_timeout`` is renamed to ``max_age``.
- ``add_etags`` is renamed to ``etag``.
- ``filename`` is renamed to ``path``.
- The ``RequestContext.g`` property is deprecated. Use ``g`` directly
or ``AppContext.g`` instead. :issue:`3898`
- ``copy_current_request_context`` can decorate async functions.
:pr:`4303`
Version 2.0.3
-------------
Unreleased
- The test client's ``as_tuple`` parameter is deprecated and will be
removed in Werkzeug 2.1. It is now also deprecated in Flask, to be
removed in Flask 2.1, while remaining compatible with both in
2.0.x. Use ``response.request.environ`` instead. :pr:`4341`
- Fix type annotation for ``errorhandler`` decorator. :issue:`4295`
- Revert a change to the CLI that caused it to hide ``ImportError``
tracebacks when importing the application. :issue:`4307`
- ``app.json_encoder`` and ``json_decoder`` are only passed to
``dumps`` and ``loads`` if they have custom behavior. This improves
performance, mainly on PyPy. :issue:`4349`
- Clearer error message when ``after_this_request`` is used outside a
request context. :issue:`4333`
Version 2.0.2
-------------
Unreleased
Released 2021-10-04
- Fix type annotation for ``teardown_request``. :issue:`4093`
- Fix type annotation for ``teardown_*`` methods. :issue:`4093`
- Fix type annotation for ``before_request`` and ``before_app_request``
decorators. :issue:`4104`
- Fixed the issue where typing requires template global
decorators to accept functions with no arguments. :issue:`4098`
- Support View and MethodView instances with async handlers. :issue:`4112`
- Enhance typing of ``app.errorhandler`` decorator. :issue:`4095`
- Fix registering a blueprint twice with differing names. :issue:`4124`
- Fix the type of ``static_folder`` to accept ``pathlib.Path``.
:issue:`4150`
- ``jsonify`` handles ``decimal.Decimal`` by encoding to ``str``.
:issue:`4157`
- Correctly handle raising deferred errors in CLI lazy loading.
:issue:`4096`
- The CLI loader handles ``**kwargs`` in a ``create_app`` function.
:issue:`4170`
- Fix the order of ``before_request`` and other callbacks that trigger
before the view returns. They are called from the app down to the
closest nested blueprint. :issue:`4229`
Version 2.0.1

View file

@ -12,14 +12,16 @@ to address bugs and feature requests in Flask itself. Use one of the
following resources for questions about using Flask or issues with your
own code:
- The ``#get-help`` channel on our Discord chat:
- The ``#questions`` channel on our Discord chat:
https://discord.gg/pallets
- The mailing list flask@python.org for long term discussion or larger
issues.
- Ask on `Stack Overflow`_. Search with Google first using:
``site:stackoverflow.com flask {search term, exception message, etc.}``
- Ask on our `GitHub Discussions`_.
.. _Stack Overflow: https://stackoverflow.com/questions/tagged/flask?tab=Frequent
.. _GitHub Discussions: https://github.com/pallets/flask/discussions
Reporting issues
@ -92,7 +94,7 @@ First time setup
.. code-block:: text
git remote add fork https://github.com/{username}/flask
$ git remote add fork https://github.com/{username}/flask
- Create a virtualenv.

View file

@ -26,7 +26,7 @@ Install and update using `pip`_:
$ pip install -U Flask
.. _pip: https://pip.pypa.io/en/stable/quickstart/
.. _pip: https://pip.pypa.io/en/stable/getting-started/
A Simple Example

View file

@ -10,7 +10,7 @@ following conditions are met:
1. Redistributions of source code must retain the above copyright
notice and this list of conditions.
3. Neither the name of the copyright holder nor the names of its
2. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

View file

@ -218,8 +218,6 @@ Useful Functions and Classes
.. autofunction:: send_from_directory
.. autofunction:: safe_join
.. autofunction:: escape
.. autoclass:: Markup
@ -256,7 +254,7 @@ the filter to render data inside ``<script>`` tags.
.. sourcecode:: html+jinja
<script type=text/javascript>
<script>
const names = {{ names|tosjon }};
renderChart(names, {{ axis_data|tojson }});
</script>

View file

@ -8,7 +8,7 @@ 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 the :doc:`/reqcontext`, which keeps track of
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.

View file

@ -17,12 +17,24 @@ defined with ``async def`` and use ``await``.
data = await async_db_query(...)
return jsonify(data)
Pluggable class-based views also support handlers that are implemented as
coroutines. This applies to the :meth:`~flask.views.View.dispatch_request`
method in views that inherit from the :class:`flask.views.View` class, as
well as all the HTTP method handlers in views that inherit from the
:class:`flask.views.MethodView` class.
.. admonition:: Using ``async`` on Windows on Python 3.8
Python 3.8 has a bug related to asyncio on Windows. If you encounter
something like ``ValueError: set_wakeup_fd only works in main thread``,
please upgrade to Python 3.9.
.. admonition:: Using ``async`` with greenlet
When using gevent or eventlet to serve an application or patch the
runtime, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is
required.
Performance
-----------

View file

@ -103,6 +103,11 @@ replaces the :meth:`Flask.run` method in most cases. ::
is provided for convenience, but is not designed to be particularly secure,
stable, or efficient. See :doc:`/deploying/index` for how to run in production.
If another program is already using port 5000, you'll see
``OSError: [Errno 98]`` or ``OSError: [WinError 10013]`` when the
server tries to start. See :ref:`address-already-in-use` for how to
handle that.
Open a Shell
------------
@ -112,10 +117,9 @@ shell with the :func:`shell <cli.shell_command>` command. An application
context will be active, and the app instance will be imported. ::
$ flask shell
Python 3.6.2 (default, Jul 20 2017, 03:52:27)
[GCC 7.1.1 20170630] on linux
App: example
Instance: /home/user/Projects/hello/instance
Python 3.10.0 (default, Oct 27 2021, 06:59:51) [GCC 11.1.0] on linux
App: example [production]
Instance: /home/david/Projects/pallets/flask/instance
>>>
Use :meth:`~Flask.shell_context_processor` to add other automatic imports.

View file

@ -38,7 +38,7 @@ method::
app.config.update(
TESTING=True,
SECRET_KEY=b'_5#y2L"F4Q8z\n\xec]/'
SECRET_KEY='192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'
)
@ -180,8 +180,8 @@ The following configuration values are used internally by Flask:
application. It should be a long random ``bytes`` or ``str``. For
example, copy the output of this to your config::
$ python -c 'import os; print(os.urandom(16))'
b'_5#y2L"F4Q8z\n\xec]/'
$ python -c 'import secrets; print(secrets.token_hex())'
'192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'
**Do not reveal the secret key when posting questions or committing code.**
@ -468,7 +468,7 @@ sure to use uppercase letters for your config keys.
Here is an example of a configuration file::
# Example configuration
SECRET_KEY = b'_5#y2L"F4Q8z\n\xec]/'
SECRET_KEY = '192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'
Make sure to load the configuration very early on, so that extensions have
the ability to access the configuration when starting up. There are other

View file

@ -69,7 +69,7 @@ enables the debugger and reloader.
``FLASK_ENV`` can only be set as an environment variable. When running
from Python code, passing ``debug=True`` enables debug mode, which is
mostly equivalent. Debug mode can be controled separately from
mostly equivalent. Debug mode can be controlled separately from
``FLASK_ENV`` with the ``FLASK_DEBUG`` environment variable as well.
.. code-block:: python

View file

@ -5,7 +5,7 @@ ASGI
If you'd like to use an ASGI server you will need to utilise WSGI to
ASGI middleware. The asgiref
[WsgiToAsgi](https://github.com/django/asgiref#wsgi-to-asgi-adapter)
`WsgiToAsgi <https://github.com/django/asgiref#wsgi-to-asgi-adapter>`_
adapter is recommended as it integrates with the event loop used for
Flask's :ref:`async_await` support. You can use the adapter by
wrapping the Flask app,
@ -21,7 +21,7 @@ wrapping the Flask app,
asgi_app = WsgiToAsgi(app)
and then serving the ``asgi_app`` with the asgi server, e.g. using
and then serving the ``asgi_app`` with the ASGI server, e.g. using
`Hypercorn <https://gitlab.com/pgjones/hypercorn>`_,
.. sourcecode:: text

View file

@ -234,5 +234,5 @@ python path. Common problems are:
.. _nginx: https://nginx.org/
.. _lighttpd: https://www.lighttpd.net/
.. _cherokee: http://cherokee-project.com/
.. _cherokee: https://cherokee-project.com/
.. _flup: https://pypi.org/project/flup/

View file

@ -37,7 +37,7 @@ If your application is accessible at root level, you can use a
single ``/`` instead of ``/yourapplication``. ``myapp`` refers to the name of
the file of your flask application (without extension) or the module which
provides ``app``. ``app`` is the callable inside of your application (usually
the line reads ``app = Flask(__name__)``.
the line reads ``app = Flask(__name__)``).
If you want to deploy your flask application inside of a virtual environment,
you need to also add ``--virtualenv /path/to/virtual/environment``. You might
@ -67,5 +67,5 @@ to have it in the URL root its a bit simpler::
.. _nginx: https://nginx.org/
.. _lighttpd: https://www.lighttpd.net/
.. _cherokee: http://cherokee-project.com/
.. _cherokee: https://cherokee-project.com/
.. _uwsgi: https://uwsgi-docs.readthedocs.io/en/latest/

View file

@ -1,67 +1,171 @@
Standalone WSGI Containers
==========================
Standalone WSGI Servers
=======================
Most WSGI servers also provide HTTP servers, so they can run a WSGI
application and make it available externally.
It may still be a good idea to run the server behind a dedicated HTTP
server such as Apache or Nginx. See :ref:`deploying-proxy-setups` if you
run into issues with that.
There are popular servers written in Python that contain WSGI applications and
serve HTTP. These servers stand alone when they run; you can proxy to them
from your web server. Note the section on :ref:`deploying-proxy-setups` if you
run into issues.
Gunicorn
--------
`Gunicorn`_ 'Green Unicorn' is a WSGI HTTP Server for UNIX. It's a pre-fork
worker model ported from Ruby's Unicorn project. It supports both `eventlet`_
and `greenlet`_. Running a Flask application on this server is quite simple::
`Gunicorn`_ is a WSGI and HTTP server for UNIX. To run a Flask
application, tell Gunicorn how to import your Flask app object.
$ gunicorn myproject:app
.. code-block:: text
`Gunicorn`_ provides many command-line options -- see ``gunicorn -h``.
For example, to run a Flask application with 4 worker processes (``-w
4``) binding to localhost port 4000 (``-b 127.0.0.1:4000``)::
$ gunicorn -w 4 -b 0.0.0.0:5000 your_project:app
$ gunicorn -w 4 -b 127.0.0.1:4000 myproject:app
The ``-w 4`` option uses 4 workers to handle 4 requests at once. The
``-b 0.0.0.0:5000`` serves the application on all interfaces on port
5000.
The ``gunicorn`` command expects the names of your application module or
package and the application instance within the module. If you use the
application factory pattern, you can pass a call to that::
Gunicorn provides many options for configuring the server, either
through a configuration file or with command line options. Use
``gunicorn --help`` or see the docs for more information.
The command expects the name of your module or package to import and
the application instance within the module. If you use the application
factory pattern, you can pass a call to that.
.. code-block:: text
$ gunicorn -w 4 -b 0.0.0.0:5000 "myproject:create_app()"
Async with Gevent or Eventlet
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The default sync worker is appropriate for many use cases. If you need
asynchronous support, Gunicorn provides workers using either `gevent`_
or `eventlet`_. This is not the same as Python's ``async/await``, or the
ASGI server spec.
When using either gevent or eventlet, greenlet>=1.0 is required,
otherwise context locals such as ``request`` will not work as expected.
When using PyPy, PyPy>=7.3.7 is required.
To use gevent:
.. code-block:: text
$ gunicorn -k gevent -b 0.0.0.0:5000 your_project:app
To use eventlet:
.. code-block:: text
$ gunicorn -k eventlet -b 0.0.0.0:5000 your_project:app
$ gunicorn "myproject:create_app()"
.. _Gunicorn: https://gunicorn.org/
.. _gevent: http://www.gevent.org/
.. _eventlet: https://eventlet.net/
.. _greenlet: https://greenlet.readthedocs.io/en/latest/
uWSGI
--------
-----
`uWSGI`_ is a fast application server written in C. It is very configurable
which makes it more complicated to setup than gunicorn.
`uWSGI`_ is a fast application server written in C. It is very
configurable, which makes it more complicated to setup than Gunicorn.
It also provides many other utilities for writing robust web
applications. To run a Flask application, tell Gunicorn how to import
your Flask app object.
Running `uWSGI HTTP Router`_::
.. code-block:: text
$ uwsgi --http 127.0.0.1:5000 --module myproject:app
$ uwsgi --master -p 4 --http 0.0.0.0:5000 -w your_project:app
For a more optimized setup, see :doc:`configuring uWSGI and NGINX <uwsgi>`.
The ``-p 4`` option uses 4 workers to handle 4 requests at once. The
``--http 0.0.0.0:5000`` serves the application on all interfaces on port
5000.
uWSGI has optimized integration with Nginx and Apache instead of using
a standard HTTP proxy. See :doc:`configuring uWSGI and Nginx <uwsgi>`.
Async with Gevent
~~~~~~~~~~~~~~~~~
The default sync worker is appropriate for many use cases. If you need
asynchronous support, uWSGI provides workers using `gevent`_. It also
supports other async modes, see the docs for more information. This is
not the same as Python's ``async/await``, or the ASGI server spec.
When using gevent, greenlet>=1.0 is required, otherwise context locals
such as ``request`` will not work as expected. When using PyPy,
PyPy>=7.3.7 is required.
.. code-block:: text
$ uwsgi --master --gevent 100 --http 0.0.0.0:5000 -w your_project:app
.. _uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/
.. _uWSGI HTTP Router: https://uwsgi-docs.readthedocs.io/en/latest/HTTP.html#the-uwsgi-http-https-router
Gevent
-------
------
`Gevent`_ is a coroutine-based Python networking library that uses
`greenlet`_ to provide a high-level synchronous API on top of `libev`_
event loop::
Prefer using `Gunicorn`_ with Gevent workers rather than using Gevent
directly. Gunicorn provides a much more configurable and
production-tested server. See the section on Gunicorn above.
`Gevent`_ allows writing asynchronous, coroutine-based code that looks
like standard synchronous Python. It uses `greenlet`_ to enable task
switching without writing ``async/await`` or using ``asyncio``.
It provides a WSGI server that can handle many connections at once
instead of one per worker process.
`Eventlet`_, described below, is another library that does the same
thing. Certain dependencies you have, or other consideration, may affect
which of the two you choose to use
To use gevent to serve your application, import its ``WSGIServer`` and
use it to run your ``app``.
.. code-block:: python
from gevent.pywsgi import WSGIServer
from yourapplication import app
from your_project import app
http_server = WSGIServer(('', 5000), app)
http_server = WSGIServer(("", 5000), app)
http_server.serve_forever()
.. _Gevent: http://www.gevent.org/
.. _greenlet: https://greenlet.readthedocs.io/en/latest/
.. _libev: http://software.schmorp.de/pkg/libev.html
Eventlet
--------
Prefer using `Gunicorn`_ with Eventlet workers rather than using
Eventlet directly. Gunicorn provides a much more configurable and
production-tested server. See the section on Gunicorn above.
`Eventlet`_ allows writing asynchronous, coroutine-based code that looks
like standard synchronous Python. It uses `greenlet`_ to enable task
switching without writing ``async/await`` or using ``asyncio``.
It provides a WSGI server that can handle many connections at once
instead of one per worker process.
`Gevent`_, described above, is another library that does the same
thing. Certain dependencies you have, or other consideration, may affect
which of the two you choose to use
To use eventlet to serve your application, import its ``wsgi.server``
and use it to run your ``app``.
.. code-block:: python
import eventlet
from eventlet import wsgi
from your_project import app
wsgi.server(eventlet.listen(("", 5000), app)
Twisted Web
-----------
@ -69,7 +173,9 @@ Twisted Web
`Twisted Web`_ is the web server shipped with `Twisted`_, a mature,
non-blocking event-driven networking library. Twisted Web comes with a
standard WSGI container which can be controlled from the command line using
the ``twistd`` utility::
the ``twistd`` utility:
.. code-block:: text
$ twistd web --wsgi myproject.app
@ -79,13 +185,16 @@ This example will run a Flask application called ``app`` from a module named
Twisted Web supports many flags and options, and the ``twistd`` utility does
as well; see ``twistd -h`` and ``twistd web -h`` for more information. For
example, to run a Twisted Web server in the foreground, on port 8080, with an
application from ``myproject``::
application from ``myproject``:
.. code-block:: text
$ twistd -n web --port tcp:8080 --wsgi myproject.app
.. _Twisted: https://twistedmatrix.com/trac/
.. _Twisted Web: https://twistedmatrix.com/trac/wiki/TwistedWeb
.. _deploying-proxy-setups:
Proxy Setups

View file

@ -322,9 +322,9 @@ ecosystem remain consistent and compatible.
`Official Pallets Themes`_. A link to the documentation or project
website must be in the PyPI metadata or the readme.
7. For maximum compatibility, the extension should support the same
versions of Python that Flask supports. 3.6+ is recommended as of
2020. Use ``python_requires=">= 3.6"`` in ``setup.py`` to indicate
supported versions.
versions of Python that Flask supports. 3.7+ is recommended as of
December 2021. Use ``python_requires=">= 3.7"`` in ``setup.py`` to
indicate supported versions.
.. _PyPI: https://pypi.org/search/?c=Framework+%3A%3A+Flask
.. _mailinglist: https://mail.python.org/mailman/listinfo/flask

View file

@ -6,7 +6,7 @@ Python Version
--------------
We recommend using the latest version of Python. Flask supports Python
3.6 and newer.
3.7 and newer.
Dependencies
@ -49,6 +49,18 @@ use them if you install them.
.. _watchdog: https://pythonhosted.org/watchdog/
greenlet
~~~~~~~~
You may choose to use gevent or eventlet with your application. In this
case, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is
required.
These are not minimum supported versions, they only indicate the first
versions that added necessary features. You should use the latest
versions of each.
Virtual environments
--------------------

View file

@ -64,7 +64,7 @@ An example task
Let's write a task that adds two numbers together and returns the result. We
configure Celery's broker and backend to use Redis, create a ``celery``
application using the factor from above, and then use it to define the task. ::
application using the factory from above, and then use it to define the task. ::
from flask import Flask

View file

@ -175,9 +175,8 @@ are JavaScript libraries like jQuery_ that have form plugins to ease the
construction of progress bar.
Because the common pattern for file uploads exists almost unchanged in all
applications dealing with uploads, there is also a Flask extension called
`Flask-Uploads`_ that implements a full fledged upload mechanism that
allows controlling which file extensions are allowed to be uploaded.
applications dealing with uploads, there are also some Flask extensions that
implement a full fledged upload mechanism that allows controlling which
file extensions are allowed to be uploaded.
.. _jQuery: https://jquery.com/
.. _Flask-Uploads: https://flask-uploads.readthedocs.io/en/latest/

View file

@ -23,8 +23,7 @@ to add a script statement to the bottom of your ``<body>`` to load jQuery:
.. sourcecode:: html
<script type=text/javascript src="{{
url_for('static', filename='jquery.js') }}"></script>
<script src="{{ url_for('static', filename='jquery.js') }}"></script>
Another method is using Google's `AJAX Libraries API
<https://developers.google.com/speed/libraries/>`_ to load jQuery:
@ -59,7 +58,7 @@ like this:
.. sourcecode:: html+jinja
<script type=text/javascript>
<script>
$SCRIPT_ROOT = {{ request.script_root|tojson }};
</script>
@ -109,7 +108,7 @@ usually a better idea to have that in a separate script file:
.. sourcecode:: html
<script type=text/javascript>
<script>
$(function() {
$('a#calculate').bind('click', function() {
$.getJSON($SCRIPT_ROOT + '/_add_numbers', {

View file

@ -113,16 +113,16 @@ raw cursor and connection objects.
Here is how you can use it::
for user in query_db('select * from users'):
print user['username'], 'has the id', user['user_id']
print(user['username'], 'has the id', user['user_id'])
Or if you just want a single result::
user = query_db('select * from users where username = ?',
[the_username], one=True)
if user is None:
print 'No such user'
print('No such user')
else:
print the_username, 'has the id', user['user_id']
print(the_username, 'has the id', user['user_id'])
To pass variable parts to the SQL statement, use a question mark in the
statement and pass in the arguments as a list. Never directly add them to

View file

@ -19,7 +19,7 @@ forms.
fun. You can get it from `PyPI
<https://pypi.org/project/Flask-WTF/>`_.
.. _Flask-WTF: https://flask-wtf.readthedocs.io/en/stable/
.. _Flask-WTF: https://flask-wtf.readthedocs.io/
The Forms
---------

View file

@ -83,6 +83,11 @@ deployment options see :doc:`deploying/index`.
Now head over to http://127.0.0.1:5000/, and you should see your hello
world greeting.
If another program is already using port 5000, you'll see
``OSError: [Errno 98]`` or ``OSError: [WinError 10013]`` when the
server tries to start. See :ref:`address-already-in-use` for how to
handle that.
.. _public-server:
.. admonition:: Externally Visible Server
@ -444,9 +449,9 @@ Here is an example template:
<h1>Hello, World!</h1>
{% endif %}
Inside templates you also have access to the :class:`~flask.request`,
:class:`~flask.session` and :class:`~flask.g` [#]_ objects
as well as the :func:`~flask.get_flashed_messages` function.
Inside templates you also have access to the :data:`~flask.Flask.config`,
:class:`~flask.request`, :class:`~flask.session` and :class:`~flask.g` [#]_ objects
as well as the :func:`~flask.url_for` and :func:`~flask.get_flashed_messages` functions.
Templates are especially useful if inheritance is used. If you want to
know how that works, see :doc:`patterns/templateinheritance`. Basically
@ -468,7 +473,7 @@ Here is a basic introduction to how the :class:`~markupsafe.Markup` class works:
>>> Markup.escape('<blink>hacker</blink>')
Markup('&lt;blink&gt;hacker&lt;/blink&gt;')
>>> Markup('<em>Marked up</em> &raquo; HTML').striptags()
'Marked up \xbb HTML'
'Marked up » HTML'
.. versionchanged:: 0.5
@ -626,7 +631,7 @@ Werkzeug provides for you::
def upload_file():
if request.method == 'POST':
file = request.files['the_file']
file.save(f"/var/www/uploads/{secure_filename(f.filename)}")
file.save(f"/var/www/uploads/{secure_filename(file.filename)}")
...
For some better examples, see :doc:`patterns/fileuploads`.
@ -847,8 +852,8 @@ sessions work::
generator. Use the following command to quickly generate a value for
:attr:`Flask.secret_key` (or :data:`SECRET_KEY`)::
$ python -c 'import os; print(os.urandom(16))'
b'_5#y2L"F4Q8z\n\xec]/'
$ python -c 'import secrets; print(secrets.token_hex())'
'192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'
A note on cookie-based sessions: Flask will take the values you put into the
session object and serialize them into a cookie. If you are finding some

View file

@ -8,7 +8,7 @@ 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 the :doc:`/appcontext`, which keeps track of the
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.
@ -33,8 +33,8 @@ Lifetime of the Context
-----------------------
When a Flask application begins handling a request, it pushes a request
context, which also pushes an :doc:`/appcontext`. When the request ends
it pops the request context then the application context.
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

View file

@ -64,6 +64,54 @@ and using the CLI.
above.
.. _address-already-in-use:
Address already in use
~~~~~~~~~~~~~~~~~~~~~~
If another program is already using port 5000, you'll see an ``OSError``
when the server tries to start. It may have one of the following
messages:
- ``OSError: [Errno 98] Address already in use``
- ``OSError: [WinError 10013] An attempt was made to access a socket
in a way forbidden by its access permissions``
Either identify and stop the other program, or use
``flask run --port 5001`` to pick a different port.
You can use ``netstat`` or ``lsof`` to identify what process id is using
a port, then use other operating system tools stop that process. The
following example shows that process id 6847 is using port 5000.
.. tabs::
.. tab:: ``netstat`` (Linux)
.. code-block:: text
$ netstat -nlp | grep 5000
tcp 0 0 127.0.0.1:5000 0.0.0.0:* LISTEN 6847/python
.. tab:: ``lsof`` (macOS / Linux)
.. code-block:: text
$ lsof -P -i :5000
Python 6847 IPv4 TCP localhost:5000 (LISTEN)
.. tab:: ``netstat`` (Windows)
.. code-block:: text
> netstat -ano | findstr 5000
TCP 127.0.0.1:5000 0.0.0.0:0 LISTENING 6847
macOS Monterey and later automatically starts a service that uses port
5000. To disable the service, go to System Preferences, Sharing, and
disable "AirPlay Receiver".
Lazy or Eager Loading
~~~~~~~~~~~~~~~~~~~~~

View file

@ -21,8 +21,7 @@ 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 the :doc:`reqcontext`
chapter of the documentation first.
Generally it's recommended that you read :doc:`reqcontext` first.
Command Line Interface
----------------------

View file

@ -177,7 +177,7 @@ With Blinker 1.1 you can also easily subscribe to signals by using the new
@template_rendered.connect_via(app)
def when_template_rendered(sender, template, context, **extra):
print f'Template {template.name} is rendered with {context}'
print(f'Template {template.name} is rendered with {context}')
Core Signals
------------

View file

@ -37,7 +37,7 @@ by default:
.. data:: config
:noindex:
The current configuration object (:data:`flask.config`)
The current configuration object (:data:`flask.Flask.config`)
.. versionadded:: 0.6

View file

@ -48,20 +48,21 @@ the application for testing and initializes a new database::
import pytest
from flaskr import create_app
from flaskr.db import init_db
@pytest.fixture
def client():
db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
flaskr.app.config['TESTING'] = True
db_fd, db_path = tempfile.mkstemp()
app = create_app({'TESTING': True, 'DATABASE': db_path})
with flaskr.app.test_client() as client:
with flaskr.app.app_context():
flaskr.init_db()
with app.test_client() as client:
with app.app_context():
init_db()
yield client
os.close(db_fd)
os.unlink(flaskr.app.config['DATABASE'])
os.unlink(db_path)
This client fixture will be called by each individual test. It gives us a
simple interface to the application, where we can trigger test requests to the
@ -224,13 +225,13 @@ temporarily. With this you can access the :class:`~flask.request`,
:class:`~flask.g` and :class:`~flask.session` objects like in view
functions. Here is a full example that demonstrates this approach::
import flask
from flask import Flask, request
app = flask.Flask(__name__)
app = Flask(__name__)
with app.test_request_context('/?name=Peter'):
assert flask.request.path == '/'
assert flask.request.args['name'] == 'Peter'
assert request.path == '/'
assert request.args['name'] == 'Peter'
All the other objects that are context bound can be used in the same
way.
@ -247,7 +248,7 @@ the test request context leaves the ``with`` block. If you do want the
:meth:`~flask.Flask.before_request` functions to be called as well, you
need to call :meth:`~flask.Flask.preprocess_request` yourself::
app = flask.Flask(__name__)
app = Flask(__name__)
with app.test_request_context('/?name=Peter'):
app.preprocess_request()
@ -260,7 +261,7 @@ If you want to call the :meth:`~flask.Flask.after_request` functions you
need to call into :meth:`~flask.Flask.process_response` which however
requires that you pass it a response object::
app = flask.Flask(__name__)
app = Flask(__name__)
with app.test_request_context('/?name=Peter'):
resp = Response('...')
@ -329,7 +330,7 @@ context around for a little longer so that additional introspection can
happen. With Flask 0.4 this is possible by using the
:meth:`~flask.Flask.test_client` with a ``with`` block::
app = flask.Flask(__name__)
app = Flask(__name__)
with app.test_client() as c:
rv = c.get('/?tequila=42')
@ -353,7 +354,7 @@ keep the context around and access :data:`flask.session`::
with app.test_client() as c:
rv = c.get('/')
assert flask.session['foo'] == 42
assert session['foo'] == 42
This however does not make it possible to also modify the session or to
access the session before a request was fired. Starting with Flask 0.8 we

View file

@ -88,9 +88,9 @@ You can use the following command to output a random secret key:
.. code-block:: none
$ python -c 'import os; print(os.urandom(16))'
$ python -c 'import secrets; print(secrets.token_hex())'
b'_5#y2L"F4Q8z\n\xec]/'
'192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'
Create the ``config.py`` file in the instance folder, which the factory
will read from if it exists. Copy the generated value into it.
@ -98,7 +98,7 @@ will read from if it exists. Copy the generated value into it.
.. code-block:: python
:caption: ``venv/var/flaskr-instance/config.py``
SECRET_KEY = b'_5#y2L"F4Q8z\n\xec]/'
SECRET_KEY = '192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'
You can also set any other necessary configuration here, although
``SECRET_KEY`` is the only one needed for Flaskr.

View file

@ -177,4 +177,9 @@ Visit http://127.0.0.1:5000/hello in a browser and you should see the
"Hello, World!" message. Congratulations, you're now running your Flask
web application!
If another program is already using port 5000, you'll see
``OSError: [Errno 98]`` or ``OSError: [WinError 10013]`` when the
server tries to start. See :ref:`address-already-in-use` for how to
handle that.
Continue to :doc:`database`.

View file

@ -57,29 +57,29 @@ By the end, your project layout will look like this:
/home/user/Projects/flask-tutorial
├── flaskr/
   ├── __init__.py
   ├── db.py
   ├── schema.sql
   ├── auth.py
   ├── blog.py
   ├── templates/
   │ ├── base.html
   │ ├── auth/
   │ │   ├── login.html
   │ │   └── register.html
   │ └── blog/
   │ ├── create.html
   │ ├── index.html
   │ └── update.html
   └── static/
      └── style.css
├── __init__.py
├── db.py
├── schema.sql
├── auth.py
├── blog.py
├── templates/
│ ├── base.html
│ ├── auth/
│ │ ├── login.html
│ │ └── register.html
│ └── blog/
│ ├── create.html
│ ├── index.html
│ └── update.html
└── static/
└── style.css
├── tests/
   ├── conftest.py
  ├── data.sql
   ├── test_factory.py
   ├── test_db.py
  ├── test_auth.py
  └── test_blog.py
├── conftest.py
├── data.sql
├── test_factory.py
├── test_db.py
├── test_auth.py
└── test_blog.py
├── venv/
├── setup.py
└── MANIFEST.in

View file

@ -270,7 +270,7 @@ messages.
with app.app_context():
assert get_db().execute(
"select * from user where username = 'a'",
"SELECT * FROM user WHERE username = 'a'",
).fetchone() is not None

View file

@ -91,18 +91,18 @@ write templates to generate the HTML form.
error = 'Username is required.'
elif not password:
error = 'Password is required.'
elif db.execute(
'SELECT id FROM user WHERE username = ?', (username,)
).fetchone() is not None:
error = f"User {username} is already registered."
if error is None:
db.execute(
'INSERT INTO user (username, password) VALUES (?, ?)',
(username, generate_password_hash(password))
)
db.commit()
return redirect(url_for('auth.login'))
try:
db.execute(
"INSERT INTO user (username, password) VALUES (?, ?)",
(username, generate_password_hash(password)),
)
db.commit()
except db.IntegrityError:
error = f"User {username} is already registered."
else:
return redirect(url_for("auth.login"))
flash(error)
@ -125,26 +125,25 @@ Here's what the ``register`` view function is doing:
#. Validate that ``username`` and ``password`` are not empty.
#. Validate that ``username`` is not already registered by querying the
database and checking if a result is returned.
:meth:`db.execute <sqlite3.Connection.execute>` takes a SQL query
with ``?`` placeholders for any user input, and a tuple of values
to replace the placeholders with. The database library will take
care of escaping the values so you are not vulnerable to a
*SQL injection attack*.
:meth:`~sqlite3.Cursor.fetchone` returns one row from the query.
If the query returned no results, it returns ``None``. Later,
:meth:`~sqlite3.Cursor.fetchall` is used, which returns a list of
all results.
#. If validation succeeds, insert the new user data into the database.
For security, passwords should never be stored in the database
directly. Instead,
:func:`~werkzeug.security.generate_password_hash` is used to
securely hash the password, and that hash is stored. Since this
query modifies data, :meth:`db.commit() <sqlite3.Connection.commit>`
needs to be called afterwards to save the changes.
- :meth:`db.execute <sqlite3.Connection.execute>` takes a SQL
query with ``?`` placeholders for any user input, and a tuple of
values to replace the placeholders with. The database library
will take care of escaping the values so you are not vulnerable
to a *SQL injection attack*.
- For security, passwords should never be stored in the database
directly. Instead,
:func:`~werkzeug.security.generate_password_hash` is used to
securely hash the password, and that hash is stored. Since this
query modifies data,
:meth:`db.commit() <sqlite3.Connection.commit>` needs to be
called afterwards to save the changes.
- An :exc:`sqlite3.IntegrityError` will occur if the username
already exists, which should be shown to the user as another
validation error.
#. After storing the user, they are redirected to the login page.
:func:`url_for` generates the URL for the login view based on its
@ -200,6 +199,11 @@ There are a few differences from the ``register`` view:
#. The user is queried first and stored in a variable for later use.
:meth:`~sqlite3.Cursor.fetchone` returns one row from the query.
If the query returned no results, it returns ``None``. Later,
:meth:`~sqlite3.Cursor.fetchall` will be used, which returns a list
of all results.
#. :func:`~werkzeug.security.check_password_hash` hashes the submitted
password in the same way as the stored hash and securely compares
them. If they match, the password is valid.

View file

@ -113,8 +113,8 @@ Method Based Dispatching
For RESTful APIs it's especially helpful to execute a different function
for each HTTP method. With the :class:`flask.views.MethodView` you can
easily do that. Each HTTP method maps to a function with the same name
(just in lowercase)::
easily do that. Each HTTP method maps to a method of the class with the
same name (just in lowercase)::
from flask.views import MethodView

View file

@ -60,21 +60,21 @@ def register():
error = "Username is required."
elif not password:
error = "Password is required."
elif (
db.execute("SELECT id FROM user WHERE username = ?", (username,)).fetchone()
is not None
):
error = f"User {username} is already registered."
if error is None:
# the name is available, store it in the database and go to
# the login page
db.execute(
"INSERT INTO user (username, password) VALUES (?, ?)",
(username, generate_password_hash(password)),
)
db.commit()
return redirect(url_for("auth.login"))
try:
db.execute(
"INSERT INTO user (username, password) VALUES (?, ?)",
(username, generate_password_hash(password)),
)
db.commit()
except db.IntegrityError:
# The username was already taken, which caused the
# commit to fail. Show a validation error.
error = f"User {username} is already registered."
else:
# Success, go to the login page.
return redirect(url_for("auth.login"))
flash(error)

View file

@ -16,7 +16,7 @@ def test_register(client, app):
# test that the user was inserted into the database
with app.app_context():
assert (
get_db().execute("select * from user where username = 'a'").fetchone()
get_db().execute("SELECT * FROM user WHERE username = 'a'").fetchone()
is not None
)

View file

@ -1,146 +1,163 @@
#
# This file is autogenerated by pip-compile
# This file is autogenerated by pip-compile with python 3.10
# To update, run:
#
# pip-compile requirements/dev.in
#
alabaster==0.7.12
# via sphinx
appdirs==1.4.4
# via virtualenv
asgiref==3.3.4
# via -r tests.in
asgiref==3.4.1
# via -r requirements/tests.in
attrs==21.2.0
# via pytest
babel==2.9.1
# via sphinx
backports.entry-points-selectable==1.1.1
# via virtualenv
blinker==1.4
# via -r tests.in
certifi==2020.12.5
# via -r requirements/tests.in
certifi==2021.10.8
# via requests
cfgv==3.3.0
cffi==1.15.0
# via cryptography
cfgv==3.3.1
# via pre-commit
chardet==4.0.0
charset-normalizer==2.0.9
# via requests
click==8.0.1
click==8.0.3
# via pip-tools
distlib==0.3.1
cryptography==36.0.1
# via -r requirements/typing.in
distlib==0.3.4
# via virtualenv
docutils==0.16
# via
# sphinx
# sphinx-tabs
filelock==3.0.12
filelock==3.4.0
# via
# tox
# virtualenv
greenlet==1.1.0
# via -r tests.in
identify==2.2.4
greenlet==1.1.2 ; python_version < "3.11"
# via -r requirements/tests.in
identify==2.4.0
# via pre-commit
idna==2.10
idna==3.3
# via requests
imagesize==1.2.0
imagesize==1.3.0
# via sphinx
iniconfig==1.1.1
# via pytest
jinja2==3.0.1
jinja2==3.0.3
# via sphinx
markupsafe==2.0.1
# via jinja2
mypy==0.930
# via -r requirements/typing.in
mypy-extensions==0.4.3
# via mypy
mypy==0.812
# via -r typing.in
nodeenv==1.6.0
# via pre-commit
packaging==20.9
packaging==21.3
# via
# pallets-sphinx-themes
# pytest
# sphinx
# tox
pallets-sphinx-themes==2.0.1
# via -r docs.in
pep517==0.10.0
pallets-sphinx-themes==2.0.2
# via -r requirements/docs.in
pep517==0.12.0
# via pip-tools
pip-tools==6.1.0
# via -r dev.in
pluggy==0.13.1
pip-tools==6.4.0
# via -r requirements/dev.in
platformdirs==2.4.0
# via virtualenv
pluggy==1.0.0
# via
# pytest
# tox
pre-commit==2.13.0
# via -r dev.in
py==1.10.0
pre-commit==2.16.0
# via -r requirements/dev.in
py==1.11.0
# via
# pytest
# tox
pygments==2.9.0
pycparser==2.21
# via cffi
pygments==2.10.0
# via
# sphinx
# sphinx-tabs
pyparsing==2.4.7
pyparsing==3.0.6
# via packaging
pytest==6.2.4
# via -r tests.in
python-dotenv==0.17.1
# via -r tests.in
pytz==2021.1
pytest==6.2.5
# via -r requirements/tests.in
python-dotenv==0.19.2
# via -r requirements/tests.in
pytz==2021.3
# via babel
pyyaml==5.4.1
pyyaml==6.0
# via pre-commit
requests==2.25.1
requests==2.26.0
# via sphinx
six==1.16.0
# via
# tox
# virtualenv
snowballstemmer==2.1.0
snowballstemmer==2.2.0
# via sphinx
sphinx-issues==1.2.0
# via -r docs.in
sphinx-tabs==3.0.0
# via -r docs.in
sphinx==4.0.2
sphinx==4.3.2
# via
# -r docs.in
# -r requirements/docs.in
# pallets-sphinx-themes
# sphinx-issues
# sphinx-tabs
# sphinxcontrib-log-cabinet
sphinx-issues==1.2.0
# via -r requirements/docs.in
sphinx-tabs==3.2.0
# via -r requirements/docs.in
sphinxcontrib-applehelp==1.0.2
# via sphinx
sphinxcontrib-devhelp==1.0.2
# via sphinx
sphinxcontrib-htmlhelp==1.0.3
sphinxcontrib-htmlhelp==2.0.0
# via sphinx
sphinxcontrib-jsmath==1.0.1
# via sphinx
sphinxcontrib-log-cabinet==1.0.1
# via -r docs.in
# via -r requirements/docs.in
sphinxcontrib-qthelp==1.0.3
# via sphinx
sphinxcontrib-serializinghtml==1.1.4
sphinxcontrib-serializinghtml==1.1.5
# via sphinx
toml==0.10.2
# via
# pep517
# pre-commit
# pytest
# tox
tox==3.23.1
# via -r dev.in
typed-ast==1.4.3
tomli==2.0.0
# via
# mypy
# pep517
tox==3.24.4
# via -r requirements/dev.in
types-contextvars==2.4.0
# via -r requirements/typing.in
types-dataclasses==0.6.1
# via -r requirements/typing.in
types-setuptools==57.4.4
# via -r requirements/typing.in
typing-extensions==4.0.1
# via mypy
typing-extensions==3.10.0.0
# via mypy
urllib3==1.26.5
urllib3==1.26.7
# via requests
virtualenv==20.4.6
virtualenv==20.10.0
# via
# pre-commit
# tox
wheel==0.37.1
# via pip-tools
# The following packages are considered to be unsafe in a requirements file:
# pip

View file

@ -1,5 +1,5 @@
#
# This file is autogenerated by pip-compile
# This file is autogenerated by pip-compile with python 3.10
# To update, run:
#
# pip-compile requirements/docs.in
@ -8,66 +8,66 @@ alabaster==0.7.12
# via sphinx
babel==2.9.1
# via sphinx
certifi==2020.12.5
certifi==2021.10.8
# via requests
chardet==4.0.0
charset-normalizer==2.0.9
# via requests
docutils==0.16
# via
# sphinx
# sphinx-tabs
idna==2.10
idna==3.3
# via requests
imagesize==1.2.0
imagesize==1.3.0
# via sphinx
jinja2==3.0.1
jinja2==3.0.3
# via sphinx
markupsafe==2.0.1
# via jinja2
packaging==20.9
packaging==21.3
# via
# pallets-sphinx-themes
# sphinx
pallets-sphinx-themes==2.0.1
# via -r docs.in
pygments==2.9.0
pallets-sphinx-themes==2.0.2
# via -r requirements/docs.in
pygments==2.10.0
# via
# sphinx
# sphinx-tabs
pyparsing==2.4.7
pyparsing==3.0.6
# via packaging
pytz==2021.1
pytz==2021.3
# via babel
requests==2.25.1
requests==2.26.0
# via sphinx
snowballstemmer==2.1.0
snowballstemmer==2.2.0
# via sphinx
sphinx-issues==1.2.0
# via -r docs.in
sphinx-tabs==3.0.0
# via -r docs.in
sphinx==4.0.2
sphinx==4.3.2
# via
# -r docs.in
# -r requirements/docs.in
# pallets-sphinx-themes
# sphinx-issues
# sphinx-tabs
# sphinxcontrib-log-cabinet
sphinx-issues==1.2.0
# via -r requirements/docs.in
sphinx-tabs==3.2.0
# via -r requirements/docs.in
sphinxcontrib-applehelp==1.0.2
# via sphinx
sphinxcontrib-devhelp==1.0.2
# via sphinx
sphinxcontrib-htmlhelp==1.0.3
sphinxcontrib-htmlhelp==2.0.0
# via sphinx
sphinxcontrib-jsmath==1.0.1
# via sphinx
sphinxcontrib-log-cabinet==1.0.1
# via -r docs.in
# via -r requirements/docs.in
sphinxcontrib-qthelp==1.0.3
# via sphinx
sphinxcontrib-serializinghtml==1.1.4
sphinxcontrib-serializinghtml==1.1.5
# via sphinx
urllib3==1.26.5
urllib3==1.26.7
# via requests
# The following packages are considered to be unsafe in a requirements file:

View file

@ -0,0 +1,5 @@
https://github.com/pallets/werkzeug/archive/refs/heads/main.tar.gz
https://github.com/pallets/jinja/archive/refs/heads/main.tar.gz
https://github.com/pallets/markupsafe/archive/refs/heads/main.tar.gz
https://github.com/pallets/itsdangerous/archive/refs/heads/main.tar.gz
https://github.com/pallets/click/archive/refs/heads/main.tar.gz

View file

@ -0,0 +1,18 @@
#
# This file is autogenerated by pip-compile with python 3.10
# To update, run:
#
# pip-compile requirements/tests-pallets-dev.in
#
click @ https://github.com/pallets/click/archive/refs/heads/main.tar.gz
# via -r requirements/tests-pallets-dev.in
itsdangerous @ https://github.com/pallets/itsdangerous/archive/refs/heads/main.tar.gz
# via -r requirements/tests-pallets-dev.in
jinja2 @ https://github.com/pallets/jinja/archive/refs/heads/main.tar.gz
# via -r requirements/tests-pallets-dev.in
markupsafe @ https://github.com/pallets/markupsafe/archive/refs/heads/main.tar.gz
# via
# -r requirements/tests-pallets-dev.in
# jinja2
werkzeug @ https://github.com/pallets/werkzeug/archive/refs/heads/main.tar.gz
# via -r requirements/tests-pallets-dev.in

View file

@ -0,0 +1,5 @@
Werkzeug==2.0.0
Jinja2==3.0.0
MarkupSafe==2.0.0
itsdangerous==2.0.0
click==8.0.0

View file

@ -0,0 +1,18 @@
#
# This file is autogenerated by pip-compile with python 3.10
# To update, run:
#
# pip-compile requirements/tests-pallets-min.in
#
click==8.0.0
# via -r requirements/tests-pallets-min.in
itsdangerous==2.0.0
# via -r requirements/tests-pallets-min.in
jinja2==3.0.0
# via -r requirements/tests-pallets-min.in
markupsafe==2.0.0
# via
# -r requirements/tests-pallets-min.in
# jinja2
werkzeug==2.0.0
# via -r requirements/tests-pallets-min.in

View file

@ -1,5 +1,5 @@
pytest
asgiref
blinker
greenlet
greenlet ; python_version < "3.11"
python-dotenv

View file

@ -1,30 +1,30 @@
#
# This file is autogenerated by pip-compile
# This file is autogenerated by pip-compile with python 3.10
# To update, run:
#
# pip-compile requirements/tests.in
#
asgiref==3.3.4
asgiref==3.4.1
# via -r requirements/tests.in
attrs==21.2.0
# via pytest
blinker==1.4
# via -r requirements/tests.in
greenlet==1.1.0
greenlet==1.1.2 ; python_version < "3.11"
# via -r requirements/tests.in
iniconfig==1.1.1
# via pytest
packaging==20.9
packaging==21.3
# via pytest
pluggy==0.13.1
pluggy==1.0.0
# via pytest
py==1.10.0
py==1.11.0
# via pytest
pyparsing==2.4.7
pyparsing==3.0.6
# via packaging
pytest==6.2.4
pytest==6.2.5
# via -r requirements/tests.in
python-dotenv==0.17.1
python-dotenv==0.19.2
# via -r requirements/tests.in
toml==0.10.2
# via pytest

View file

@ -1 +1,5 @@
mypy
types-contextvars
types-dataclasses
types-setuptools
cryptography

View file

@ -1,14 +1,26 @@
#
# This file is autogenerated by pip-compile
# This file is autogenerated by pip-compile with python 3.10
# To update, run:
#
# pip-compile requirements/typing.in
#
cffi==1.15.0
# via cryptography
cryptography==36.0.1
# via -r requirements/typing.in
mypy==0.930
# via -r requirements/typing.in
mypy-extensions==0.4.3
# via mypy
mypy==0.812
pycparser==2.21
# via cffi
tomli==2.0.0
# via mypy
types-contextvars==2.4.0
# via -r requirements/typing.in
typed-ast==1.4.3
# via mypy
typing-extensions==3.10.0.0
types-dataclasses==0.6.1
# via -r requirements/typing.in
types-setuptools==57.4.4
# via -r requirements/typing.in
typing-extensions==4.0.1
# via mypy

View file

@ -35,7 +35,7 @@ classifiers =
packages = find:
package_dir = = src
include_package_data = true
python_requires = >= 3.6
python_requires = >= 3.7
# Dependencies are in setup.py for GitHub's dependency graph.
[options.packages.find]
@ -88,7 +88,7 @@ per-file-ignores =
[mypy]
files = src/flask
python_version = 3.6
python_version = 3.7
allow_redefinition = True
disallow_subclassing_any = True
# disallow_untyped_calls = True
@ -112,3 +112,6 @@ ignore_missing_imports = True
[mypy-dotenv.*]
ignore_missing_imports = True
[mypy-cryptography.*]
ignore_missing_imports = True

View file

@ -23,7 +23,6 @@ from .helpers import flash as flash
from .helpers import get_flashed_messages as get_flashed_messages
from .helpers import get_template_attribute as get_template_attribute
from .helpers import make_response as make_response
from .helpers import safe_join as safe_join
from .helpers import send_file as send_file
from .helpers import send_from_directory as send_from_directory
from .helpers import stream_with_context as stream_with_context

View file

@ -16,7 +16,6 @@ from werkzeug.exceptions import BadRequest
from werkzeug.exceptions import BadRequestKeyError
from werkzeug.exceptions import HTTPException
from werkzeug.exceptions import InternalServerError
from werkzeug.local import ContextVar
from werkzeug.routing import BuildError
from werkzeug.routing import Map
from werkzeug.routing import MapAdapter
@ -58,18 +57,12 @@ from .signals import request_started
from .signals import request_tearing_down
from .templating import DispatchingJinjaLoader
from .templating import Environment
from .typing import AfterRequestCallable
from .typing import BeforeFirstRequestCallable
from .typing import BeforeRequestCallable
from .typing import ErrorHandlerCallable
from .typing import ResponseReturnValue
from .typing import TeardownCallable
from .typing import TemplateContextProcessorCallable
from .typing import TemplateFilterCallable
from .typing import TemplateGlobalCallable
from .typing import TemplateTestCallable
from .typing import URLDefaultCallable
from .typing import URLValuePreprocessorCallable
from .wrappers import Request
from .wrappers import Response
@ -78,6 +71,7 @@ if t.TYPE_CHECKING:
from .blueprints import Blueprint
from .testing import FlaskClient
from .testing import FlaskCliRunner
from .typing import ErrorHandlerCallable
if sys.version_info >= (3, 8):
iscoroutinefunction = inspect.iscoroutinefunction
@ -366,7 +360,8 @@ class Flask(Scaffold):
#: .. versionadded:: 1.1.0
url_map_class = Map
#: the test client that is used with when `test_client` is used.
#: The :meth:`test_client` method creates an instance of this test
#: client class. Defaults to :class:`~flask.testing.FlaskClient`.
#:
#: .. versionadded:: 0.7
test_client_class: t.Optional[t.Type["FlaskClient"]] = None
@ -389,7 +384,7 @@ class Flask(Scaffold):
self,
import_name: str,
static_url_path: t.Optional[str] = None,
static_folder: t.Optional[str] = "static",
static_folder: t.Optional[t.Union[str, os.PathLike]] = "static",
static_host: t.Optional[str] = None,
host_matching: bool = False,
subdomain_matching: bool = False,
@ -744,20 +739,21 @@ class Flask(Scaffold):
:param context: the context as a dictionary that is updated in place
to add extra variables.
"""
funcs: t.Iterable[
TemplateContextProcessorCallable
] = self.template_context_processors[None]
reqctx = _request_ctx_stack.top
if reqctx is not None:
for bp in request.blueprints:
if bp in self.template_context_processors:
funcs = chain(funcs, self.template_context_processors[bp])
names: t.Iterable[t.Optional[str]] = (None,)
# A template may be rendered outside a request context.
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.
orig_ctx = context.copy()
for func in funcs:
context.update(func())
# make sure the original values win. This makes it possible to
# easier add new variables in context processors without breaking
# existing views.
for name in names:
if name in self.template_context_processors:
for func in self.template_context_processors[name]:
context.update(func())
context.update(orig_ctx)
def make_shell_context(self) -> dict:
@ -1268,16 +1264,19 @@ class Flask(Scaffold):
self.shell_context_processors.append(f)
return f
def _find_error_handler(self, e: Exception) -> t.Optional[ErrorHandlerCallable]:
def _find_error_handler(
self, e: Exception
) -> t.Optional["ErrorHandlerCallable[Exception]"]:
"""Return a registered error handler for an exception in this order:
blueprint handler for a specific code, app handler for a specific code,
blueprint handler for an exception class, app handler for an exception
class, or ``None`` if a suitable handler is not found.
"""
exc_class, code = self._get_exc_class_and_code(type(e))
names = (*request.blueprints, None)
for c in [code, None]:
for name in chain(request.blueprints, [None]):
for c in (code, None) if code is not None else (None,):
for name in names:
handler_map = self.error_handler_spec[name][c]
if not handler_map:
@ -1304,7 +1303,7 @@ class Flask(Scaffold):
.. versionchanged:: 1.0
Exceptions are looked up by code *and* by MRO, so
``HTTPExcpetion`` subclasses can be handled with a catch-all
``HTTPException`` subclasses can be handled with a catch-all
handler for the base ``HTTPException``.
.. versionadded:: 0.3
@ -1619,14 +1618,7 @@ class Flask(Scaffold):
except ImportError:
raise RuntimeError(
"Install Flask with the 'async' extra in order to use async views."
)
# Check that Werkzeug isn't using its fallback ContextVar class.
if ContextVar.__module__ == "werkzeug.local":
raise RuntimeError(
"Async cannot be used with this combination of Python "
"and Greenlet versions."
)
) from None
return asgiref_async_to_sync(func)
@ -1725,7 +1717,7 @@ class Flask(Scaffold):
" response. The return type must be a string,"
" dict, tuple, Response instance, or WSGI"
f" callable, but it was a {type(rv).__name__}."
).with_traceback(sys.exc_info()[2])
).with_traceback(sys.exc_info()[2]) from None
else:
raise TypeError(
"The view function did not return a valid"
@ -1797,17 +1789,19 @@ class Flask(Scaffold):
.. versionadded:: 0.7
"""
funcs: t.Iterable[URLDefaultCallable] = self.url_default_functions[None]
names: t.Iterable[t.Optional[str]] = (None,)
# url_for may be called outside a request context, parse the
# passed endpoint instead of using request.blueprints.
if "." in endpoint:
# This is called by url_for, which can be called outside a
# request, can't use request.blueprints.
bps = _split_blueprint_path(endpoint.rpartition(".")[0])
bp_funcs = chain.from_iterable(self.url_default_functions[bp] for bp in bps)
funcs = chain(funcs, bp_funcs)
names = chain(
names, reversed(_split_blueprint_path(endpoint.rpartition(".")[0]))
)
for func in funcs:
func(endpoint, values)
for name in names:
if name in self.url_default_functions:
for func in self.url_default_functions[name]:
func(endpoint, values)
def handle_url_build_error(
self, error: Exception, endpoint: str, values: dict
@ -1842,24 +1836,20 @@ class Flask(Scaffold):
value is handled as if it was the return value from the view, and
further request handling is stopped.
"""
names = (None, *reversed(request.blueprints))
funcs: t.Iterable[URLValuePreprocessorCallable] = self.url_value_preprocessors[
None
]
for bp in request.blueprints:
if bp in self.url_value_preprocessors:
funcs = chain(funcs, self.url_value_preprocessors[bp])
for func in funcs:
func(request.endpoint, request.view_args)
for name in names:
if name in self.url_value_preprocessors:
for url_func in self.url_value_preprocessors[name]:
url_func(request.endpoint, request.view_args)
funcs: t.Iterable[BeforeRequestCallable] = self.before_request_funcs[None]
for bp in request.blueprints:
if bp in self.before_request_funcs:
funcs = chain(funcs, self.before_request_funcs[bp])
for func in funcs:
rv = self.ensure_sync(func)()
if rv is not None:
return rv
for name in names:
if name in self.before_request_funcs:
for before_func in self.before_request_funcs[name]:
rv = self.ensure_sync(before_func)()
if rv is not None:
return rv
return None
@ -1877,16 +1867,18 @@ class Flask(Scaffold):
instance of :attr:`response_class`.
"""
ctx = _request_ctx_stack.top
funcs: t.Iterable[AfterRequestCallable] = ctx._after_request_functions
for bp in request.blueprints:
if bp in self.after_request_funcs:
funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
if None in self.after_request_funcs:
funcs = chain(funcs, reversed(self.after_request_funcs[None]))
for handler in funcs:
response = self.ensure_sync(handler)(response)
for func in ctx._after_request_functions:
response = self.ensure_sync(func)(response)
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.session):
self.session_interface.save_session(self, ctx.session, response)
return response
def do_teardown_request(
@ -1914,14 +1906,12 @@ class Flask(Scaffold):
"""
if exc is _sentinel:
exc = sys.exc_info()[1]
funcs: t.Iterable[TeardownCallable] = reversed(
self.teardown_request_funcs[None]
)
for bp in request.blueprints:
if bp in self.teardown_request_funcs:
funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
for func in funcs:
self.ensure_sync(func)(exc)
for name in chain(request.blueprints, (None,)):
if name in self.teardown_request_funcs:
for func in reversed(self.teardown_request_funcs[name]):
self.ensure_sync(func)(exc)
request_tearing_down.send(self, exc=exc)
def do_teardown_appcontext(
@ -1943,8 +1933,10 @@ class Flask(Scaffold):
"""
if exc is _sentinel:
exc = sys.exc_info()[1]
for func in reversed(self.teardown_appcontext_funcs):
self.ensure_sync(func)(exc)
appcontext_tearing_down.send(self, exc=exc)
def app_context(self) -> AppContext:

View file

@ -1,3 +1,4 @@
import os
import typing as t
from collections import defaultdict
from functools import update_wrapper
@ -8,7 +9,6 @@ from .scaffold import Scaffold
from .typing import AfterRequestCallable
from .typing import BeforeFirstRequestCallable
from .typing import BeforeRequestCallable
from .typing import ErrorHandlerCallable
from .typing import TeardownCallable
from .typing import TemplateContextProcessorCallable
from .typing import TemplateFilterCallable
@ -19,6 +19,7 @@ from .typing import URLValuePreprocessorCallable
if t.TYPE_CHECKING:
from .app import Flask
from .typing import ErrorHandlerCallable
DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable]
@ -175,7 +176,7 @@ class Blueprint(Scaffold):
self,
name: str,
import_name: str,
static_folder: t.Optional[str] = None,
static_folder: t.Optional[t.Union[str, os.PathLike]] = None,
static_url_path: t.Optional[str] = None,
template_folder: t.Optional[str] = None,
url_prefix: t.Optional[str] = None,
@ -293,34 +294,26 @@ class Blueprint(Scaffold):
Registering the same blueprint with the same name multiple
times is deprecated and will become an error in Flask 2.1.
"""
first_registration = not any(bp is self for bp in app.blueprints.values())
name_prefix = options.get("name_prefix", "")
self_name = options.get("name", self.name)
name = f"{name_prefix}.{self_name}".lstrip(".")
if name in app.blueprints:
bp_desc = "this" if app.blueprints[name] is self else "a different"
existing_at = f" '{name}'" if self_name != name else ""
if app.blueprints[name] is not self:
raise ValueError(
f"The name '{self_name}' is already registered for"
f" a different blueprint{existing_at}. Use 'name='"
" to provide a unique name."
)
else:
import warnings
raise ValueError(
f"The name '{self_name}' is already registered for"
f" {bp_desc} blueprint{existing_at}. Use 'name=' to"
f" provide a unique name."
)
warnings.warn(
f"The name '{self_name}' is already registered for"
f" this blueprint{existing_at}. Use 'name=' to"
" provide a unique name. This will become an error"
" in Flask 2.1.",
stacklevel=4,
)
first_bp_registration = not any(bp is self for bp in app.blueprints.values())
first_name_registration = name not in app.blueprints
app.blueprints[name] = self
self._got_registered_once = True
state = self.make_setup_state(app, options, first_registration)
state = self.make_setup_state(app, options, first_bp_registration)
if self.has_static_folder:
state.add_url_rule(
@ -330,7 +323,7 @@ class Blueprint(Scaffold):
)
# Merge blueprint data into parent.
if first_registration:
if first_bp_registration or first_name_registration:
def extend(bp_dict, parent_dict):
for key, values in bp_dict.items():
@ -581,7 +574,9 @@ class Blueprint(Scaffold):
handler is used for all requests, even if outside of the blueprint.
"""
def decorator(f: ErrorHandlerCallable) -> ErrorHandlerCallable:
def decorator(
f: "ErrorHandlerCallable[Exception]",
) -> "ErrorHandlerCallable[Exception]":
self.record_once(lambda s: s.app.errorhandler(code)(f))
return f

View file

@ -5,7 +5,6 @@ import platform
import re
import sys
import traceback
import warnings
from functools import update_wrapper
from operator import attrgetter
from threading import Lock
@ -34,7 +33,7 @@ class NoAppException(click.UsageError):
"""Raised if an application cannot be found or loaded."""
def find_best_app(script_info, module):
def find_best_app(module):
"""Given a module instance this tries to find the best possible
application in the module or raises an exception.
"""
@ -65,19 +64,20 @@ def find_best_app(script_info, module):
if inspect.isfunction(app_factory):
try:
app = call_factory(script_info, app_factory)
app = app_factory()
if isinstance(app, Flask):
return app
except TypeError:
except TypeError as e:
if not _called_with_wrong_args(app_factory):
raise
raise NoAppException(
f"Detected factory {attr_name!r} in module {module.__name__!r},"
" but could not call it without arguments. Use"
f" \"FLASK_APP='{module.__name__}:{attr_name}(args)'\""
" to specify arguments."
)
) from e
raise NoAppException(
"Failed to find Flask application or factory in module"
@ -86,39 +86,6 @@ def find_best_app(script_info, module):
)
def call_factory(script_info, app_factory, args=None, kwargs=None):
"""Takes an app factory, a ``script_info` object and optionally a tuple
of arguments. Checks for the existence of a script_info argument and calls
the app_factory depending on that and the arguments provided.
"""
sig = inspect.signature(app_factory)
args = [] if args is None else args
kwargs = {} if kwargs is None else kwargs
if "script_info" in sig.parameters:
warnings.warn(
"The 'script_info' argument is deprecated and will not be"
" passed to the app factory function in Flask 2.1.",
DeprecationWarning,
)
kwargs["script_info"] = script_info
if (
not args
and len(sig.parameters) == 1
and next(iter(sig.parameters.values())).default is inspect.Parameter.empty
):
warnings.warn(
"Script info is deprecated and will not be passed as the"
" single argument to the app factory function in Flask"
" 2.1.",
DeprecationWarning,
)
args.append(script_info)
return app_factory(*args, **kwargs)
def _called_with_wrong_args(f):
"""Check whether calling a function raised a ``TypeError`` because
the call failed or because something in the factory raised the
@ -145,7 +112,7 @@ def _called_with_wrong_args(f):
del tb
def find_app_by_string(script_info, module, app_name):
def find_app_by_string(module, app_name):
"""Check if the given string is a variable name or a function. Call
a function to get the app instance, or return the variable directly.
"""
@ -158,11 +125,12 @@ def find_app_by_string(script_info, module, app_name):
except SyntaxError:
raise NoAppException(
f"Failed to parse {app_name!r} as an attribute name or function call."
)
) from None
if isinstance(expr, ast.Name):
name = expr.id
args = kwargs = None
args = []
kwargs = {}
elif isinstance(expr, ast.Call):
# Ensure the function name is an attribute name only.
if not isinstance(expr.func, ast.Name):
@ -181,7 +149,7 @@ def find_app_by_string(script_info, module, app_name):
# message with the full expression instead.
raise NoAppException(
f"Failed to parse arguments as literal values: {app_name!r}."
)
) from None
else:
raise NoAppException(
f"Failed to parse {app_name!r} as an attribute name or function call."
@ -189,17 +157,17 @@ def find_app_by_string(script_info, module, app_name):
try:
attr = getattr(module, name)
except AttributeError:
except AttributeError as e:
raise NoAppException(
f"Failed to find attribute {name!r} in {module.__name__!r}."
)
) from e
# If the attribute is a function, call it with any args and kwargs
# to get the real application.
if inspect.isfunction(attr):
try:
app = call_factory(script_info, attr, args, kwargs)
except TypeError:
app = attr(*args, **kwargs)
except TypeError as e:
if not _called_with_wrong_args(attr):
raise
@ -207,7 +175,7 @@ def find_app_by_string(script_info, module, app_name):
f"The factory {app_name!r} in module"
f" {module.__name__!r} could not be called with the"
" specified arguments."
)
) from e
else:
app = attr
@ -249,7 +217,7 @@ def prepare_import(path):
return ".".join(module_name[::-1])
def locate_app(script_info, module_name, app_name, raise_if_not_found=True):
def locate_app(module_name, app_name, raise_if_not_found=True):
__traceback_hide__ = True # noqa: F841
try:
@ -261,18 +229,18 @@ def locate_app(script_info, module_name, app_name, raise_if_not_found=True):
raise NoAppException(
f"While importing {module_name!r}, an ImportError was"
f" raised:\n\n{traceback.format_exc()}"
)
) from None
elif raise_if_not_found:
raise NoAppException(f"Could not import {module_name!r}.")
raise NoAppException(f"Could not import {module_name!r}.") from None
else:
return
module = sys.modules[module_name]
if app_name is None:
return find_best_app(script_info, module)
return find_best_app(module)
else:
return find_app_by_string(script_info, module, app_name)
return find_app_by_string(module, app_name)
def get_version(ctx, param, value):
@ -312,7 +280,7 @@ class DispatchingApp:
self.loader = loader
self._app = None
self._lock = Lock()
self._bg_loading_exc_info = None
self._bg_loading_exc = None
if use_eager_loading is None:
use_eager_loading = os.environ.get("WERKZEUG_RUN_MAIN") != "true"
@ -328,23 +296,24 @@ class DispatchingApp:
with self._lock:
try:
self._load_unlocked()
except Exception:
self._bg_loading_exc_info = sys.exc_info()
except Exception as e:
self._bg_loading_exc = e
t = Thread(target=_load_app, args=())
t.start()
def _flush_bg_loading_exception(self):
__traceback_hide__ = True # noqa: F841
exc_info = self._bg_loading_exc_info
if exc_info is not None:
self._bg_loading_exc_info = None
raise exc_info
exc = self._bg_loading_exc
if exc is not None:
self._bg_loading_exc = None
raise exc
def _load_unlocked(self):
__traceback_hide__ = True # noqa: F841
self._app = rv = self.loader()
self._bg_loading_exc_info = None
self._bg_loading_exc = None
return rv
def __call__(self, environ, start_response):
@ -392,18 +361,18 @@ class ScriptInfo:
return self._loaded_app
if self.create_app is not None:
app = call_factory(self, self.create_app)
app = self.create_app()
else:
if self.app_import_path:
path, name = (
re.split(r":(?![\\/])", self.app_import_path, 1) + [None]
)[:2]
import_name = prepare_import(path)
app = locate_app(self, import_name, name)
app = locate_app(import_name, name)
else:
for path in ("wsgi.py", "app.py"):
import_name = prepare_import(path)
app = locate_app(self, import_name, None, raise_if_not_found=False)
app = locate_app(import_name, None, raise_if_not_found=False)
if app:
break
@ -721,7 +690,7 @@ class CertParamType(click.ParamType):
"Using ad-hoc certificates requires the cryptography library.",
ctx,
param,
)
) from None
return value
@ -979,15 +948,7 @@ debug mode.
def main() -> None:
if int(click.__version__[0]) < 8:
warnings.warn(
"Using the `flask` cli with Click 7 is deprecated and"
" will not be supported starting with Flask 2.1."
" Please upgrade to Click 8 as soon as possible.",
DeprecationWarning,
)
# TODO omit sys.argv once https://github.com/pallets/click/issues/536 is fixed
cli.main(args=sys.argv[1:])
cli.main()
if __name__ == "__main__":

View file

@ -83,7 +83,7 @@ class Config(dict):
:param variable_name: name of the environment variable
:param silent: set to ``True`` if you want silent failure for missing
files.
:return: bool. ``True`` if able to load config, ``False`` otherwise.
:return: ``True`` if the file was loaded successfully.
"""
rv = os.environ.get(variable_name)
if not rv:
@ -107,6 +107,7 @@ class Config(dict):
root path.
:param silent: set to ``True`` if you want silent failure for missing
files.
:return: ``True`` if the file was loaded successfully.
.. versionadded:: 0.7
`silent` parameter.
@ -175,6 +176,9 @@ class Config(dict):
.. code-block:: python
import json
app.config.from_file("config.json", load=json.load)
import toml
app.config.from_file("config.toml", load=toml.load)
@ -185,6 +189,7 @@ class Config(dict):
:type load: ``Callable[[Reader], Mapping]`` where ``Reader``
implements a ``read`` method.
:param silent: Ignore the file if it doesn't exist.
:return: ``True`` if the file was loaded successfully.
.. versionadded:: 2.0
"""
@ -202,36 +207,12 @@ class Config(dict):
return self.from_mapping(obj)
def from_json(self, filename: str, silent: bool = False) -> bool:
"""Update the values in the config from a JSON file. The loaded
data is passed to the :meth:`from_mapping` method.
:param filename: The path to the JSON file. This can be an
absolute path or relative to the config root path.
:param silent: Ignore the file if it doesn't exist.
.. deprecated:: 2.0.0
Will be removed in Flask 2.1. Use :meth:`from_file` instead.
This was removed early in 2.0.0, was added back in 2.0.1.
.. versionadded:: 0.11
"""
import warnings
from . import json
warnings.warn(
"'from_json' is deprecated and will be removed in Flask"
" 2.1. Use 'from_file(path, json.load)' instead.",
DeprecationWarning,
stacklevel=2,
)
return self.from_file(filename, json.load, silent=silent)
def from_mapping(
self, mapping: t.Optional[t.Mapping[str, t.Any]] = None, **kwargs: t.Any
) -> bool:
"""Updates the config like :meth:`update` ignoring items with non-upper
keys.
:return: Always returns ``True``.
.. versionadded:: 0.11
"""

View file

@ -130,7 +130,15 @@ def after_this_request(f: AfterRequestCallable) -> AfterRequestCallable:
.. versionadded:: 0.9
"""
_request_ctx_stack.top._after_request_functions.append(f)
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(
"This decorator can only be used when a request context is"
" active, such as within a view function."
)
top._after_request_functions.append(f)
return f
@ -159,17 +167,18 @@ def copy_current_request_context(f: t.Callable) -> t.Callable:
.. versionadded:: 0.10
"""
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(
"This decorator can only be used at local scopes "
"when a request context is on the stack. For instance within "
"view functions."
"This decorator can only be used when a request context is"
" active, such as within a view function."
)
reqctx = top.copy()
def wrapper(*args, **kwargs):
with reqctx:
return f(*args, **kwargs)
return reqctx.app.ensure_sync(f)(*args, **kwargs)
return update_wrapper(wrapper, f)
@ -332,11 +341,29 @@ class RequestContext:
self._after_request_functions: t.List[AfterRequestCallable] = []
@property
def g(self) -> AppContext:
def g(self) -> _AppCtxGlobals:
import warnings
warnings.warn(
"Accessing 'g' on the request context is deprecated and"
" will be removed in Flask 2.2. Access `g` directly or from"
"the application context instead.",
DeprecationWarning,
stacklevel=2,
)
return _app_ctx_stack.top.g
@g.setter
def g(self, value: AppContext) -> None:
def g(self, value: _AppCtxGlobals) -> None:
import warnings
warnings.warn(
"Setting 'g' on the request context is deprecated and"
" will be removed in Flask 2.2. Set it on the application"
" context instead.",
DeprecationWarning,
stacklevel=2,
)
_app_ctx_stack.top.g = value
def copy(self) -> "RequestContext":

View file

@ -83,10 +83,11 @@ def attach_enctype_error_multidict(request):
def __getitem__(self, key):
try:
return oldcls.__getitem__(self, key)
except KeyError:
except KeyError as e:
if key not in request.form:
raise
raise DebugFilesKeyError(request, key)
raise DebugFilesKeyError(request, key) from e
newcls.__name__ = oldcls.__name__
newcls.__module__ = oldcls.__module__

View file

@ -5,13 +5,11 @@ import sys
import typing as t
import warnings
from datetime import datetime
from datetime import timedelta
from functools import lru_cache
from functools import update_wrapper
from threading import RLock
import werkzeug.utils
from werkzeug.exceptions import NotFound
from werkzeug.routing import BuildError
from werkzeug.urls import url_quote
@ -454,7 +452,7 @@ def _prepare_send_file_kwargs(
warnings.warn(
"The 'attachment_filename' parameter has been renamed to"
" 'download_name'. The old name will be removed in Flask"
" 2.1.",
" 2.2.",
DeprecationWarning,
stacklevel=3,
)
@ -463,7 +461,7 @@ def _prepare_send_file_kwargs(
if cache_timeout is not None:
warnings.warn(
"The 'cache_timeout' parameter has been renamed to"
" 'max_age'. The old name will be removed in Flask 2.1.",
" 'max_age'. The old name will be removed in Flask 2.2.",
DeprecationWarning,
stacklevel=3,
)
@ -472,7 +470,7 @@ def _prepare_send_file_kwargs(
if add_etags is not None:
warnings.warn(
"The 'add_etags' parameter has been renamed to 'etag'. The"
" old name will be removed in Flask 2.1.",
" old name will be removed in Flask 2.2.",
DeprecationWarning,
stacklevel=3,
)
@ -627,29 +625,6 @@ def send_file(
)
def safe_join(directory: str, *pathnames: str) -> str:
"""Safely join zero or more untrusted path components to a base
directory to avoid escaping the base directory.
:param directory: The trusted base directory.
:param pathnames: The untrusted path components relative to the
base directory.
:return: A safe path, otherwise ``None``.
"""
warnings.warn(
"'flask.helpers.safe_join' is deprecated and will be removed in"
" Flask 2.1. Use 'werkzeug.utils.safe_join' instead.",
DeprecationWarning,
stacklevel=2,
)
path = werkzeug.utils.safe_join(directory, *pathnames)
if path is None:
raise NotFound()
return path
def send_from_directory(
directory: t.Union[os.PathLike, str],
path: t.Union[os.PathLike, str],
@ -691,7 +666,7 @@ def send_from_directory(
if filename is not None:
warnings.warn(
"The 'filename' parameter has been renamed to 'path'. The"
" old name will be removed in Flask 2.1.",
" old name will be removed in Flask 2.2.",
DeprecationWarning,
stacklevel=2,
)
@ -714,7 +689,7 @@ def get_root_path(import_name: str) -> str:
# Module already imported and has a file attribute. Use that first.
mod = sys.modules.get(import_name)
if mod is not None and hasattr(mod, "__file__"):
if mod is not None and hasattr(mod, "__file__") and mod.__file__ is not None:
return os.path.dirname(os.path.abspath(mod.__file__))
# Next attempt: check the loader.
@ -785,27 +760,6 @@ class locked_cached_property(werkzeug.utils.cached_property):
super().__delete__(obj)
def total_seconds(td: timedelta) -> int:
"""Returns the total seconds from a timedelta object.
:param timedelta td: the timedelta to be converted in seconds
:returns: number of seconds
:rtype: int
.. deprecated:: 2.0
Will be removed in Flask 2.1. Use
:meth:`timedelta.total_seconds` instead.
"""
warnings.warn(
"'total_seconds' is deprecated and will be removed in Flask"
" 2.1. Use 'timedelta.total_seconds' instead.",
DeprecationWarning,
stacklevel=2,
)
return td.days * 60 * 60 * 24 + td.seconds
def is_ip(value: str) -> bool:
"""Determine if the given string is an IP address.

View file

@ -1,8 +1,8 @@
import io
import dataclasses
import decimal
import json as _json
import typing as t
import uuid
import warnings
from datetime import date
from jinja2.utils import htmlsafe_json_dumps as _jinja_htmlsafe_dumps
@ -15,12 +15,6 @@ if t.TYPE_CHECKING:
from ..app import Flask
from ..wrappers import Response
try:
import dataclasses
except ImportError:
# Python < 3.7
dataclasses = None # type: ignore
class JSONEncoder(_json.JSONEncoder):
"""The default JSON encoder. Handles extra types compared to the
@ -47,7 +41,7 @@ class JSONEncoder(_json.JSONEncoder):
"""
if isinstance(o, date):
return http_date(o)
if isinstance(o, uuid.UUID):
if isinstance(o, (decimal.Decimal, uuid.UUID)):
return str(o)
if dataclasses and dataclasses.is_dataclass(o):
return dataclasses.asdict(o)
@ -80,6 +74,11 @@ def _dump_arg_defaults(
if bp is not None and bp.json_encoder is not None:
cls = bp.json_encoder
# Only set a custom encoder if it has custom behavior. This is
# faster on PyPy.
if cls is not _json.JSONEncoder:
kwargs.setdefault("cls", cls)
kwargs.setdefault("cls", cls)
kwargs.setdefault("ensure_ascii", app.config["JSON_AS_ASCII"])
kwargs.setdefault("sort_keys", app.config["JSON_SORT_KEYS"])
@ -101,9 +100,10 @@ def _load_arg_defaults(
if bp is not None and bp.json_decoder is not None:
cls = bp.json_decoder
kwargs.setdefault("cls", cls)
else:
kwargs.setdefault("cls", JSONDecoder)
# Only set a custom decoder if it has custom behavior. This is
# faster on PyPy.
if cls not in {JSONDecoder, _json.JSONDecoder}:
kwargs.setdefault("cls", cls)
def dumps(obj: t.Any, app: t.Optional["Flask"] = None, **kwargs: t.Any) -> str:
@ -117,6 +117,9 @@ def dumps(obj: t.Any, app: t.Optional["Flask"] = None, **kwargs: t.Any) -> str:
or defaults.
:param kwargs: Extra arguments passed to :func:`json.dumps`.
.. versionchanged:: 2.0.2
:class:`decimal.Decimal` is supported by converting to a string.
.. versionchanged:: 2.0
``encoding`` is deprecated and will be removed in Flask 2.1.
@ -125,20 +128,7 @@ def dumps(obj: t.Any, app: t.Optional["Flask"] = None, **kwargs: t.Any) -> str:
context for configuration.
"""
_dump_arg_defaults(kwargs, app=app)
encoding = kwargs.pop("encoding", None)
rv = _json.dumps(obj, **kwargs)
if encoding is not None:
warnings.warn(
"'encoding' is deprecated and will be removed in Flask 2.1.",
DeprecationWarning,
stacklevel=2,
)
if isinstance(rv, str):
return rv.encode(encoding) # type: ignore
return rv
return _json.dumps(obj, **kwargs)
def dump(
@ -160,23 +150,6 @@ def dump(
deprecated and will be removed in Flask 2.1.
"""
_dump_arg_defaults(kwargs, app=app)
encoding = kwargs.pop("encoding", None)
show_warning = encoding is not None
try:
fp.write("")
except TypeError:
show_warning = True
fp = io.TextIOWrapper(fp, encoding or "utf-8") # type: ignore
if show_warning:
warnings.warn(
"Writing to a binary file, and the 'encoding' argument, is"
" deprecated and will be removed in Flask 2.1.",
DeprecationWarning,
stacklevel=2,
)
_json.dump(obj, fp, **kwargs)
@ -200,19 +173,6 @@ def loads(s: str, app: t.Optional["Flask"] = None, **kwargs: t.Any) -> t.Any:
context for configuration.
"""
_load_arg_defaults(kwargs, app=app)
encoding = kwargs.pop("encoding", None)
if encoding is not None:
warnings.warn(
"'encoding' is deprecated and will be removed in Flask 2.1."
" The data must be a string or UTF-8 bytes.",
DeprecationWarning,
stacklevel=2,
)
if isinstance(s, bytes):
s = s.decode(encoding)
return _json.loads(s, **kwargs)
@ -232,20 +192,6 @@ def load(fp: t.IO[str], app: t.Optional["Flask"] = None, **kwargs: t.Any) -> t.A
file must be text mode, or binary mode with UTF-8 bytes.
"""
_load_arg_defaults(kwargs, app=app)
encoding = kwargs.pop("encoding", None)
if encoding is not None:
warnings.warn(
"'encoding' is deprecated and will be removed in Flask 2.1."
" The file must be text mode, or binary mode with UTF-8"
" bytes.",
DeprecationWarning,
stacklevel=2,
)
if isinstance(fp.read(0), bytes):
fp = io.TextIOWrapper(fp, encoding) # type: ignore
return _json.load(fp, **kwargs)
@ -324,6 +270,9 @@ def jsonify(*args: t.Any, **kwargs: t.Any) -> "Response":
debug mode or if :data:`JSONIFY_PRETTYPRINT_REGULAR` is ``True``,
the output will be formatted to be easier to read.
.. versionchanged:: 2.0.2
:class:`decimal.Decimal` is supported by converting to a string.
.. versionchanged:: 0.11
Added support for serializing top-level arrays. This introduces
a security risk in ancient browsers. See :ref:`security-json`.

View file

@ -21,7 +21,7 @@ from .templating import _default_template_ctx_processor
from .typing import AfterRequestCallable
from .typing import AppOrBlueprintKey
from .typing import BeforeRequestCallable
from .typing import ErrorHandlerCallable
from .typing import GenericException
from .typing import TeardownCallable
from .typing import TemplateContextProcessorCallable
from .typing import URLDefaultCallable
@ -29,6 +29,7 @@ from .typing import URLValuePreprocessorCallable
if t.TYPE_CHECKING:
from .wrappers import Response
from .typing import ErrorHandlerCallable
# a singleton sentinel value for parameter defaults
_sentinel = object()
@ -91,7 +92,7 @@ class Scaffold:
def __init__(
self,
import_name: str,
static_folder: t.Optional[str] = None,
static_folder: t.Optional[t.Union[str, os.PathLike]] = None,
static_url_path: t.Optional[str] = None,
template_folder: t.Optional[str] = None,
root_path: t.Optional[str] = None,
@ -100,7 +101,7 @@ class Scaffold:
#: to. Do not change this once it is set by the constructor.
self.import_name = import_name
self.static_folder = static_folder
self.static_folder = static_folder # type: ignore
self.static_url_path = static_url_path
#: The path to the templates folder, relative to
@ -144,7 +145,10 @@ class Scaffold:
#: directly and its format may change at any time.
self.error_handler_spec: t.Dict[
AppOrBlueprintKey,
t.Dict[t.Optional[int], t.Dict[t.Type[Exception], ErrorHandlerCallable]],
t.Dict[
t.Optional[int],
t.Dict[t.Type[Exception], "ErrorHandlerCallable[Exception]"],
],
] = defaultdict(lambda: defaultdict(dict))
#: A data structure of functions to call at the beginning of
@ -253,7 +257,7 @@ class Scaffold:
return None
@static_folder.setter
def static_folder(self, value: t.Optional[str]) -> None:
def static_folder(self, value: t.Optional[t.Union[str, os.PathLike]]) -> None:
if value is not None:
value = os.fspath(value).rstrip(r"\/")
@ -585,10 +589,10 @@ class Scaffold:
stack of active contexts. This becomes relevant if you are using
such constructs in tests.
Teardown functions must avoid raising exceptions, since they . If they
execute code that might fail they
will have to surround the execution of these code by try/except
statements and log occurring errors.
Teardown functions must avoid raising exceptions. If
they execute code that might fail they
will have to surround the execution of that code with try/except
statements and log any errors.
When a teardown function was called because of an exception it will
be passed an error object.
@ -643,8 +647,11 @@ class Scaffold:
@setupmethod
def errorhandler(
self, code_or_exception: t.Union[t.Type[Exception], int]
) -> t.Callable[[ErrorHandlerCallable], ErrorHandlerCallable]:
self, code_or_exception: t.Union[t.Type[GenericException], int]
) -> t.Callable[
["ErrorHandlerCallable[GenericException]"],
"ErrorHandlerCallable[GenericException]",
]:
"""Register a function to handle errors by code or exception class.
A decorator that is used to register a function given an
@ -674,7 +681,9 @@ class Scaffold:
an arbitrary exception
"""
def decorator(f: ErrorHandlerCallable) -> ErrorHandlerCallable:
def decorator(
f: "ErrorHandlerCallable[GenericException]",
) -> "ErrorHandlerCallable[GenericException]":
self.register_error_handler(code_or_exception, f)
return f
@ -683,8 +692,8 @@ class Scaffold:
@setupmethod
def register_error_handler(
self,
code_or_exception: t.Union[t.Type[Exception], int],
f: ErrorHandlerCallable,
code_or_exception: t.Union[t.Type[GenericException], int],
f: "ErrorHandlerCallable[GenericException]",
) -> None:
"""Alternative error attach function to the :meth:`errorhandler`
decorator that is more straightforward to use for non decorator
@ -706,9 +715,11 @@ class Scaffold:
f"'{code_or_exception}' is not a recognized HTTP error"
" code. Use a subclass of HTTPException with that code"
" instead."
)
) from None
self.error_handler_spec[None][code][exc_class] = f
self.error_handler_spec[None][code][exc_class] = t.cast(
"ErrorHandlerCallable[Exception]", f
)
@staticmethod
def _get_exc_class_and_code(

View file

@ -131,6 +131,13 @@ class SessionInterface:
app = Flask(__name__)
app.session_interface = MySessionInterface()
Multiple requests with the same session may be sent and handled
concurrently. When implementing a new session interface, consider
whether reads or writes to the backing store must be synchronized.
There is no guarantee on the order in which the session for each
request is opened or saved, it will occur in the order that requests
begin and end processing.
.. versionadded:: 0.8
"""
@ -292,20 +299,25 @@ class SessionInterface:
def open_session(
self, app: "Flask", request: "Request"
) -> t.Optional[SessionMixin]:
"""This method has to be implemented and must either return ``None``
in case the loading failed because of a configuration error or an
instance of a session object which implements a dictionary like
interface + the methods and attributes on :class:`SessionMixin`.
"""This is called at the beginning of each request, after
pushing the request context, before matching the URL.
This must return an object which implements a dictionary-like
interface as well as the :class:`SessionMixin` interface.
This will return ``None`` to indicate that loading failed in
some way that is not immediately an error. The request
context will fall back to using :meth:`make_null_session`
in this case.
"""
raise NotImplementedError()
def save_session(
self, app: "Flask", session: SessionMixin, response: "Response"
) -> None:
"""This is called for actual sessions returned by :meth:`open_session`
at the end of the request. This is still called during a request
context so if you absolutely need access to the request you can do
that.
"""This is called at the end of each request, after generating
a response, before removing the request context. It is skipped
if :meth:`is_null_session` returns ``True``.
"""
raise NotImplementedError()

View file

@ -29,7 +29,7 @@ except ImportError:
raise RuntimeError(
"Signalling support is unavailable because the blinker"
" library is not installed."
)
) from None
connect = connect_via = connected_to = temporarily_connected_to = _fail
disconnect = _fail

View file

@ -9,14 +9,15 @@ from werkzeug.test import Client
from werkzeug.urls import url_parse
from werkzeug.wrappers import Request as BaseRequest
from . import _request_ctx_stack
from .cli import ScriptInfo
from .globals import _request_ctx_stack
from .json import dumps as json_dumps
from .sessions import SessionMixin
if t.TYPE_CHECKING:
from werkzeug.test import TestResponse
from .app import Flask
from .wrappers import Response
class EnvironBuilder(werkzeug.test.EnvironBuilder):
@ -171,14 +172,15 @@ class FlaskClient(Client):
headers = resp.get_wsgi_headers(c.request.environ)
self.cookie_jar.extract_wsgi(c.request.environ, headers)
def open( # type: ignore
def open(
self,
*args: t.Any,
as_tuple: bool = False,
buffered: bool = False,
follow_redirects: bool = False,
**kwargs: t.Any,
) -> "Response":
) -> "TestResponse":
as_tuple = kwargs.pop("as_tuple", None)
# Same logic as super.open, but apply environ_base and preserve_context.
request = None
@ -213,12 +215,28 @@ class FlaskClient(Client):
finally:
builder.close()
return super().open( # type: ignore
request,
as_tuple=as_tuple,
buffered=buffered,
follow_redirects=follow_redirects,
)
if as_tuple is not None:
import warnings
warnings.warn(
"'as_tuple' is deprecated and will be removed in"
" Werkzeug 2.1 and Flask 2.1. Use"
" 'response.request.environ' instead.",
DeprecationWarning,
stacklevel=3,
)
return super().open(
request,
as_tuple=as_tuple,
buffered=buffered,
follow_redirects=follow_redirects,
)
else:
return super().open(
request,
buffered=buffered,
follow_redirects=follow_redirects,
)
def __enter__(self) -> "FlaskClient":
if self.preserve_context:
@ -272,7 +290,7 @@ class FlaskCliRunner(CliRunner):
:return: a :class:`~click.testing.Result` object.
"""
if cli is None:
cli = self.app.cli
cli = self.app.cli # type: ignore
if "obj" not in kwargs:
kwargs["obj"] = ScriptInfo(create_app=lambda: self.app)

View file

@ -33,11 +33,12 @@ ResponseReturnValue = t.Union[
"WSGIApplication",
]
GenericException = t.TypeVar("GenericException", bound=Exception, contravariant=True)
AppOrBlueprintKey = t.Optional[str] # The App key is None, whereas blueprints are named
AfterRequestCallable = t.Callable[["Response"], "Response"]
BeforeFirstRequestCallable = t.Callable[[], None]
BeforeRequestCallable = t.Callable[[], t.Optional[ResponseReturnValue]]
ErrorHandlerCallable = t.Callable[[Exception], ResponseReturnValue]
TeardownCallable = t.Callable[[t.Optional[BaseException]], None]
TemplateContextProcessorCallable = t.Callable[[], t.Dict[str, t.Any]]
TemplateFilterCallable = t.Callable[..., t.Any]
@ -45,3 +46,4 @@ TemplateGlobalCallable = t.Callable[..., t.Any]
TemplateTestCallable = t.Callable[..., bool]
URLDefaultCallable = t.Callable[[str, dict], None]
URLValuePreprocessorCallable = t.Callable[[t.Optional[str], t.Optional[dict]], None]
ErrorHandlerCallable = t.Callable[[GenericException], ResponseReturnValue]

View file

@ -1,5 +1,6 @@
import typing as t
from .globals import current_app
from .globals import request
from .typing import ResponseReturnValue
@ -80,7 +81,7 @@ class View:
def view(*args: t.Any, **kwargs: t.Any) -> ResponseReturnValue:
self = view.view_class(*class_args, **class_kwargs) # type: ignore
return self.dispatch_request(*args, **kwargs)
return current_app.ensure_sync(self.dispatch_request)(*args, **kwargs)
if cls.decorators:
view.__name__ = name
@ -154,4 +155,4 @@ class MethodView(View, metaclass=MethodViewType):
meth = getattr(self, "get", None)
assert meth is not None, f"Unimplemented method {request.method!r}"
return meth(*args, **kwargs)
return current_app.ensure_sync(meth)(*args, **kwargs)

View file

@ -1,11 +1,12 @@
import asyncio
import sys
import pytest
from flask import Blueprint
from flask import Flask
from flask import request
from flask.views import MethodView
from flask.views import View
pytest.importorskip("asgiref")
@ -18,6 +19,24 @@ class BlueprintError(Exception):
pass
class AsyncView(View):
methods = ["GET", "POST"]
async def dispatch_request(self):
await asyncio.sleep(0)
return request.method
class AsyncMethodView(MethodView):
async def get(self):
await asyncio.sleep(0)
return "GET"
async def post(self):
await asyncio.sleep(0)
return "POST"
@pytest.fixture(name="async_app")
def _async_app():
app = Flask(__name__)
@ -53,11 +72,13 @@ def _async_app():
app.register_blueprint(blueprint, url_prefix="/bp")
app.add_url_rule("/view", view_func=AsyncView.as_view("view"))
app.add_url_rule("/methodview", view_func=AsyncMethodView.as_view("methodview"))
return app
@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires Python >= 3.7")
@pytest.mark.parametrize("path", ["/", "/home", "/bp/"])
@pytest.mark.parametrize("path", ["/", "/home", "/bp/", "/view", "/methodview"])
def test_async_route(path, async_app):
test_client = async_app.test_client()
response = test_client.get(path)
@ -66,7 +87,6 @@ def test_async_route(path, async_app):
assert b"POST" in response.get_data()
@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires Python >= 3.7")
@pytest.mark.parametrize("path", ["/error", "/bp/error"])
def test_async_error_handler(path, async_app):
test_client = async_app.test_client()
@ -74,7 +94,6 @@ def test_async_error_handler(path, async_app):
assert response.status_code == 412
@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires Python >= 3.7")
def test_async_before_after_request():
app_first_called = False
app_before_called = False
@ -131,10 +150,3 @@ def test_async_before_after_request():
test_client.get("/bp/")
assert bp_before_called
assert bp_after_called
@pytest.mark.skipif(sys.version_info >= (3, 7), reason="should only raise Python < 3.7")
def test_async_runtime_error():
app = Flask(__name__)
with pytest.raises(RuntimeError):
app.async_to_sync(None)

View file

@ -1,6 +1,5 @@
import gc
import re
import sys
import time
import uuid
import weakref
@ -1323,7 +1322,6 @@ def test_jsonify_mimetype(app, req_ctx):
assert rv.mimetype == "application/vnd.api+json"
@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires Python >= 3.7")
def test_json_dump_dataclass(app, req_ctx):
from dataclasses import make_dataclass

View file

@ -837,6 +837,86 @@ def test_nested_blueprint(app, client):
assert client.get("/parent/child/grandchild/no").data == b"Grandchild no"
def test_nested_callback_order(app, client):
parent = flask.Blueprint("parent", __name__)
child = flask.Blueprint("child", __name__)
@app.before_request
def app_before1():
flask.g.setdefault("seen", []).append("app_1")
@app.teardown_request
def app_teardown1(e=None):
assert flask.g.seen.pop() == "app_1"
@app.before_request
def app_before2():
flask.g.setdefault("seen", []).append("app_2")
@app.teardown_request
def app_teardown2(e=None):
assert flask.g.seen.pop() == "app_2"
@app.context_processor
def app_ctx():
return dict(key="app")
@parent.before_request
def parent_before1():
flask.g.setdefault("seen", []).append("parent_1")
@parent.teardown_request
def parent_teardown1(e=None):
assert flask.g.seen.pop() == "parent_1"
@parent.before_request
def parent_before2():
flask.g.setdefault("seen", []).append("parent_2")
@parent.teardown_request
def parent_teardown2(e=None):
assert flask.g.seen.pop() == "parent_2"
@parent.context_processor
def parent_ctx():
return dict(key="parent")
@child.before_request
def child_before1():
flask.g.setdefault("seen", []).append("child_1")
@child.teardown_request
def child_teardown1(e=None):
assert flask.g.seen.pop() == "child_1"
@child.before_request
def child_before2():
flask.g.setdefault("seen", []).append("child_2")
@child.teardown_request
def child_teardown2(e=None):
assert flask.g.seen.pop() == "child_2"
@child.context_processor
def child_ctx():
return dict(key="child")
@child.route("/a")
def a():
return ", ".join(flask.g.seen)
@child.route("/b")
def b():
return flask.render_template_string("{{ key }}")
parent.register_blueprint(child)
app.register_blueprint(parent)
assert (
client.get("/a").data == b"app_1, app_2, parent_1, parent_2, child_1, child_2"
)
assert client.get("/b").data == b"child"
@pytest.mark.parametrize(
"parent_init, child_init, parent_registration, child_registration",
[
@ -874,8 +954,8 @@ def test_unique_blueprint_names(app, client) -> None:
app.register_blueprint(bp)
with pytest.warns(UserWarning):
app.register_blueprint(bp) # same bp, same name, warning
with pytest.raises(ValueError):
app.register_blueprint(bp) # same bp, same name, error
app.register_blueprint(bp, name="again") # same bp, different name, ok
@ -899,6 +979,14 @@ def test_blueprint_renaming(app, client) -> None:
def index():
return flask.request.endpoint
@bp.get("/error")
def error():
flask.abort(403)
@bp.errorhandler(403)
def forbidden(_: Exception):
return "Error", 403
@bp2.get("/")
def index2():
return flask.request.endpoint
@ -911,3 +999,5 @@ def test_blueprint_renaming(app, client) -> None:
assert client.get("/b/").data == b"alt.index"
assert client.get("/a/a/").data == b"bp.sub.index2"
assert client.get("/b/a/").data == b"alt.sub.index2"
assert client.get("/a/error").data == b"Error"
assert client.get("/b/error").data == b"Error"

View file

@ -1,6 +1,7 @@
# This file was part of Flask-CLI and was modified under the terms of
# its Revised BSD License. Copyright © 2015 CERN.
import os
import platform
import ssl
import sys
import types
@ -17,6 +18,7 @@ from flask import Blueprint
from flask import current_app
from flask import Flask
from flask.cli import AppGroup
from flask.cli import DispatchingApp
from flask.cli import dotenv
from flask.cli import find_best_app
from flask.cli import FlaskGroup
@ -47,51 +49,36 @@ def test_cli_name(test_apps):
def test_find_best_app(test_apps):
script_info = ScriptInfo()
class Module:
app = Flask("appname")
assert find_best_app(script_info, Module) == Module.app
assert find_best_app(Module) == Module.app
class Module:
application = Flask("appname")
assert find_best_app(script_info, Module) == Module.application
assert find_best_app(Module) == Module.application
class Module:
myapp = Flask("appname")
assert find_best_app(script_info, Module) == Module.myapp
assert find_best_app(Module) == Module.myapp
class Module:
@staticmethod
def create_app():
return Flask("appname")
app = find_best_app(script_info, Module)
app = find_best_app(Module)
assert isinstance(app, Flask)
assert app.name == "appname"
class Module:
@staticmethod
def create_app(foo):
def create_app(**kwargs):
return Flask("appname")
with pytest.deprecated_call(match="Script info"):
app = find_best_app(script_info, Module)
assert isinstance(app, Flask)
assert app.name == "appname"
class Module:
@staticmethod
def create_app(foo=None, script_info=None):
return Flask("appname")
with pytest.deprecated_call(match="script_info"):
app = find_best_app(script_info, Module)
app = find_best_app(Module)
assert isinstance(app, Flask)
assert app.name == "appname"
@ -100,7 +87,7 @@ def test_find_best_app(test_apps):
def make_app():
return Flask("appname")
app = find_best_app(script_info, Module)
app = find_best_app(Module)
assert isinstance(app, Flask)
assert app.name == "appname"
@ -111,7 +98,7 @@ def test_find_best_app(test_apps):
def create_app():
return Flask("appname2")
assert find_best_app(script_info, Module) == Module.myapp
assert find_best_app(Module) == Module.myapp
class Module:
myapp = Flask("appname1")
@ -120,32 +107,32 @@ def test_find_best_app(test_apps):
def create_app():
return Flask("appname2")
assert find_best_app(script_info, Module) == Module.myapp
assert find_best_app(Module) == Module.myapp
class Module:
pass
pytest.raises(NoAppException, find_best_app, script_info, Module)
pytest.raises(NoAppException, find_best_app, Module)
class Module:
myapp1 = Flask("appname1")
myapp2 = Flask("appname2")
pytest.raises(NoAppException, find_best_app, script_info, Module)
pytest.raises(NoAppException, find_best_app, Module)
class Module:
@staticmethod
def create_app(foo, bar):
return Flask("appname2")
pytest.raises(NoAppException, find_best_app, script_info, Module)
pytest.raises(NoAppException, find_best_app, Module)
class Module:
@staticmethod
def create_app():
raise TypeError("bad bad factory!")
pytest.raises(TypeError, find_best_app, script_info, Module)
pytest.raises(TypeError, find_best_app, Module)
@pytest.mark.parametrize(
@ -209,8 +196,7 @@ def test_prepare_import(request, value, path, result):
),
)
def test_locate_app(test_apps, iname, aname, result):
info = ScriptInfo()
assert locate_app(info, iname, aname).name == result
assert locate_app(iname, aname).name == result
@pytest.mark.parametrize(
@ -232,20 +218,17 @@ def test_locate_app(test_apps, iname, aname, result):
),
)
def test_locate_app_raises(test_apps, iname, aname):
info = ScriptInfo()
with pytest.raises(NoAppException):
locate_app(info, iname, aname)
locate_app(iname, aname)
def test_locate_app_suppress_raise(test_apps):
info = ScriptInfo()
app = locate_app(info, "notanapp.py", None, raise_if_not_found=False)
app = locate_app("notanapp.py", None, raise_if_not_found=False)
assert app is None
# only direct import error is suppressed
with pytest.raises(NoAppException):
locate_app(info, "cliapp.importerrorapp", None, raise_if_not_found=False)
locate_app("cliapp.importerrorapp", None, raise_if_not_found=False)
def test_get_version(test_apps, capsys):
@ -310,6 +293,26 @@ def test_scriptinfo(test_apps, monkeypatch):
assert app.name == "testapp"
@pytest.mark.xfail(platform.python_implementation() == "PyPy", reason="flaky on pypy")
def test_lazy_load_error(monkeypatch):
"""When using lazy loading, the correct exception should be
re-raised.
"""
class BadExc(Exception):
pass
def bad_load():
raise BadExc
lazy = DispatchingApp(bad_load, use_eager_loading=False)
with pytest.raises(BadExc):
# reduce flakiness by waiting for the internal loading lock
with lazy._lock:
lazy._flush_bg_loading_exception()
def test_with_appcontext(runner):
@click.command()
@with_appcontext

View file

@ -1,4 +1,5 @@
import datetime
import decimal
import io
import uuid
@ -187,6 +188,11 @@ def test_jsonify_uuid_types(app, client):
assert rv_uuid == test_uuid
def test_json_decimal():
rv = flask.json.dumps(decimal.Decimal("0.003"))
assert rv == '"0.003"'
def test_json_attr(app, client):
@app.route("/add", methods=["POST"])
def add():

View file

@ -1,7 +1,8 @@
[tox]
envlist =
py{39,38,37,36,py3}
py39-click7
py3{11,10,9,8,7},pypy3{8,7}
py310-min
py37-dev
style
typing
docs
@ -10,8 +11,8 @@ skip_missing_interpreters = true
[testenv]
deps =
-r requirements/tests.txt
click7: click<8
min: -r requirements/tests-pallets-min.txt
dev: -r requirements/tests-pallets-dev.txt
examples/tutorial[test]
examples/javascript[test]