Merge branch 'main' into class-route
This commit is contained in:
commit
e5fb65b171
50 changed files with 902 additions and 534 deletions
8
.github/dependabot.yml
vendored
Normal file
8
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: pip
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: monthly
|
||||||
|
time: "08:00"
|
||||||
|
open-pull-requests-limit: 99
|
||||||
4
.github/workflows/tests.yaml
vendored
4
.github/workflows/tests.yaml
vendored
|
|
@ -2,7 +2,7 @@ name: Tests
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- main
|
||||||
- '*.x'
|
- '*.x'
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
|
|
@ -10,7 +10,7 @@ on:
|
||||||
- '*.rst'
|
- '*.rst'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- main
|
||||||
- '*.x'
|
- '*.x'
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
|
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -4,6 +4,8 @@
|
||||||
*.pyc
|
*.pyc
|
||||||
*.pyo
|
*.pyo
|
||||||
env/
|
env/
|
||||||
|
venv/
|
||||||
|
.venv/
|
||||||
env*
|
env*
|
||||||
dist/
|
dist/
|
||||||
build/
|
build/
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,31 @@
|
||||||
|
ci:
|
||||||
|
autoupdate_schedule: monthly
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v2.13.0
|
rev: v2.23.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: ["--py36-plus"]
|
args: ["--py36-plus"]
|
||||||
- repo: https://github.com/asottile/reorder_python_imports
|
- repo: https://github.com/asottile/reorder_python_imports
|
||||||
rev: v2.5.0
|
rev: v2.6.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: reorder-python-imports
|
- id: reorder-python-imports
|
||||||
name: Reorder Python imports (src, tests)
|
name: Reorder Python imports (src, tests)
|
||||||
files: "^(?!examples/)"
|
files: "^(?!examples/)"
|
||||||
args: ["--application-directories", "src"]
|
args: ["--application-directories", "src"]
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 21.4b0
|
rev: 21.7b0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
- repo: https://github.com/PyCQA/flake8
|
- repo: https://github.com/PyCQA/flake8
|
||||||
rev: 3.9.1
|
rev: 3.9.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
- flake8-bugbear
|
- flake8-bugbear
|
||||||
- flake8-implicit-str-concat
|
- flake8-implicit-str-concat
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v3.4.0
|
rev: v4.0.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: fix-byte-order-marker
|
- id: fix-byte-order-marker
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
|
|
|
||||||
85
CHANGES.rst
85
CHANGES.rst
|
|
@ -1,9 +1,70 @@
|
||||||
.. currentmodule:: flask
|
.. currentmodule:: flask
|
||||||
|
|
||||||
|
Version 2.1.0
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Unreleased
|
||||||
|
|
||||||
|
- Update Click dependency to >= 8.0.
|
||||||
|
|
||||||
|
|
||||||
|
Version 2.0.2
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Unreleased
|
||||||
|
|
||||||
|
- 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`
|
||||||
|
|
||||||
|
|
||||||
|
Version 2.0.1
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Released 2021-05-21
|
||||||
|
|
||||||
|
- Re-add the ``filename`` parameter in ``send_from_directory``. The
|
||||||
|
``filename`` parameter has been renamed to ``path``, the old name
|
||||||
|
is deprecated. :pr:`4019`
|
||||||
|
- Mark top-level names as exported so type checking understands
|
||||||
|
imports in user projects. :issue:`4024`
|
||||||
|
- Fix type annotation for ``g`` and inform mypy that it is a namespace
|
||||||
|
object that has arbitrary attributes. :issue:`4020`
|
||||||
|
- Fix some types that weren't available in Python 3.6.0. :issue:`4040`
|
||||||
|
- Improve typing for ``send_file``, ``send_from_directory``, and
|
||||||
|
``get_send_file_max_age``. :issue:`4044`, :pr:`4026`
|
||||||
|
- Show an error when a blueprint name contains a dot. The ``.`` has
|
||||||
|
special meaning, it is used to separate (nested) blueprint names and
|
||||||
|
the endpoint name. :issue:`4041`
|
||||||
|
- Combine URL prefixes when nesting blueprints that were created with
|
||||||
|
a ``url_prefix`` value. :issue:`4037`
|
||||||
|
- Roll back a change to the order that URL matching was done. The
|
||||||
|
URL is again matched after the session is loaded, so the session is
|
||||||
|
available in custom URL converters. :issue:`4053`
|
||||||
|
- Re-add deprecated ``Config.from_json``, which was accidentally
|
||||||
|
removed early. :issue:`4078`
|
||||||
|
- Improve typing for some functions using ``Callable`` in their type
|
||||||
|
signatures, focusing on decorator factories. :issue:`4060`
|
||||||
|
- Nested blueprints are registered with their dotted name. This allows
|
||||||
|
different blueprints with the same name to be nested at different
|
||||||
|
locations. :issue:`4069`
|
||||||
|
- ``register_blueprint`` takes a ``name`` option to change the
|
||||||
|
(pre-dotted) name the blueprint is registered with. This allows the
|
||||||
|
same blueprint to be registered multiple times with unique names for
|
||||||
|
``url_for``. Registering the same blueprint with the same name
|
||||||
|
multiple times is deprecated. :issue:`1091`
|
||||||
|
- Improve typing for ``stream_with_context``. :issue:`4052`
|
||||||
|
|
||||||
|
|
||||||
Version 2.0.0
|
Version 2.0.0
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
Unreleased
|
Released 2021-05-11
|
||||||
|
|
||||||
- Drop support for Python 2 and 3.5.
|
- Drop support for Python 2 and 3.5.
|
||||||
- Bump minimum versions of other Pallets projects: Werkzeug >= 2,
|
- Bump minimum versions of other Pallets projects: Werkzeug >= 2,
|
||||||
|
|
@ -76,6 +137,8 @@ Unreleased
|
||||||
- Support async views, error handlers, before and after request, and
|
- Support async views, error handlers, before and after request, and
|
||||||
teardown functions. :pr:`3412`
|
teardown functions. :pr:`3412`
|
||||||
- Support nesting blueprints. :issue:`593, 1548`, :pr:`3923`
|
- Support nesting blueprints. :issue:`593, 1548`, :pr:`3923`
|
||||||
|
- Set the default encoding to "UTF-8" when loading ``.env`` and
|
||||||
|
``.flaskenv`` files to allow to use non-ASCII characters. :issue:`3931`
|
||||||
- ``flask shell`` sets up tab and history completion like the default
|
- ``flask shell`` sets up tab and history completion like the default
|
||||||
``python`` shell if ``readline`` is installed. :issue:`3941`
|
``python`` shell if ``readline`` is installed. :issue:`3941`
|
||||||
- ``helpers.total_seconds()`` is deprecated. Use
|
- ``helpers.total_seconds()`` is deprecated. Use
|
||||||
|
|
@ -84,6 +147,26 @@ Unreleased
|
||||||
- Support using the ``route`` decorator on view classes (i.e.
|
- Support using the ``route`` decorator on view classes (i.e.
|
||||||
``View`` and ``MethodView`` subclasses). :issue:`3404`
|
``View`` and ``MethodView`` subclasses). :issue:`3404`
|
||||||
|
|
||||||
|
Version 1.1.4
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Released 2021-05-13
|
||||||
|
|
||||||
|
- Update ``static_folder`` to use ``_compat.fspath`` instead of
|
||||||
|
``os.fspath`` to continue supporting Python < 3.6 :issue:`4050`
|
||||||
|
|
||||||
|
|
||||||
|
Version 1.1.3
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Released 2021-05-13
|
||||||
|
|
||||||
|
- Set maximum versions of Werkzeug, Jinja, Click, and ItsDangerous.
|
||||||
|
:issue:`4043`
|
||||||
|
- Re-add support for passing a ``pathlib.Path`` for ``static_folder``.
|
||||||
|
:pr:`3579`
|
||||||
|
|
||||||
|
|
||||||
Version 1.1.2
|
Version 1.1.2
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
following resources for questions about using Flask or issues with your
|
||||||
own code:
|
own code:
|
||||||
|
|
||||||
- The ``#get-help`` channel on our Discord chat:
|
- The ``#questions`` channel on our Discord chat:
|
||||||
https://discord.gg/pallets
|
https://discord.gg/pallets
|
||||||
- The mailing list flask@python.org for long term discussion or larger
|
- The mailing list flask@python.org for long term discussion or larger
|
||||||
issues.
|
issues.
|
||||||
- Ask on `Stack Overflow`_. Search with Google first using:
|
- Ask on `Stack Overflow`_. Search with Google first using:
|
||||||
``site:stackoverflow.com flask {search term, exception message, etc.}``
|
``site:stackoverflow.com flask {search term, exception message, etc.}``
|
||||||
|
- Ask on our `GitHub Discussions`_.
|
||||||
|
|
||||||
.. _Stack Overflow: https://stackoverflow.com/questions/tagged/flask?tab=Frequent
|
.. _Stack Overflow: https://stackoverflow.com/questions/tagged/flask?tab=Frequent
|
||||||
|
.. _GitHub Discussions: https://github.com/pallets/flask/discussions
|
||||||
|
|
||||||
|
|
||||||
Reporting issues
|
Reporting issues
|
||||||
|
|
@ -92,7 +94,7 @@ First time setup
|
||||||
|
|
||||||
.. code-block:: text
|
.. code-block:: text
|
||||||
|
|
||||||
git remote add fork https://github.com/{username}/flask
|
$ git remote add fork https://github.com/{username}/flask
|
||||||
|
|
||||||
- Create a virtualenv.
|
- Create a virtualenv.
|
||||||
|
|
||||||
|
|
@ -112,6 +114,12 @@ First time setup
|
||||||
> py -3 -m venv env
|
> py -3 -m venv env
|
||||||
> env\Scripts\activate
|
> env\Scripts\activate
|
||||||
|
|
||||||
|
- Upgrade pip and setuptools.
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
$ python -m pip install --upgrade pip setuptools
|
||||||
|
|
||||||
- Install the development dependencies, then install Flask in editable
|
- Install the development dependencies, then install Flask in editable
|
||||||
mode.
|
mode.
|
||||||
|
|
||||||
|
|
@ -129,7 +137,7 @@ First time setup
|
||||||
.. _username: https://docs.github.com/en/github/using-git/setting-your-username-in-git
|
.. _username: https://docs.github.com/en/github/using-git/setting-your-username-in-git
|
||||||
.. _email: https://docs.github.com/en/github/setting-up-and-managing-your-github-user-account/setting-your-commit-email-address
|
.. _email: https://docs.github.com/en/github/setting-up-and-managing-your-github-user-account/setting-your-commit-email-address
|
||||||
.. _GitHub account: https://github.com/join
|
.. _GitHub account: https://github.com/join
|
||||||
.. _Fork: https://github.com/pallets/jinja/fork
|
.. _Fork: https://github.com/pallets/flask/fork
|
||||||
.. _Clone: https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#step-2-create-a-local-clone-of-your-fork
|
.. _Clone: https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#step-2-create-a-local-clone-of-your-fork
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -143,15 +151,15 @@ Start coding
|
||||||
.. code-block:: text
|
.. code-block:: text
|
||||||
|
|
||||||
$ git fetch origin
|
$ git fetch origin
|
||||||
$ git checkout -b your-branch-name origin/1.1.x
|
$ git checkout -b your-branch-name origin/2.0.x
|
||||||
|
|
||||||
If you're submitting a feature addition or change, branch off of the
|
If you're submitting a feature addition or change, branch off of the
|
||||||
"master" branch.
|
"main" branch.
|
||||||
|
|
||||||
.. code-block:: text
|
.. code-block:: text
|
||||||
|
|
||||||
$ git fetch origin
|
$ git fetch origin
|
||||||
$ git checkout -b your-branch-name origin/master
|
$ git checkout -b your-branch-name origin/main
|
||||||
|
|
||||||
- Using your favorite editor, make your changes,
|
- Using your favorite editor, make your changes,
|
||||||
`committing as you go`_.
|
`committing as you go`_.
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ Contributing
|
||||||
For guidance on setting up a development environment and how to make a
|
For guidance on setting up a development environment and how to make a
|
||||||
contribution to Flask, see the `contributing guidelines`_.
|
contribution to Flask, see the `contributing guidelines`_.
|
||||||
|
|
||||||
.. _contributing guidelines: https://github.com/pallets/flask/blob/master/CONTRIBUTING.rst
|
.. _contributing guidelines: https://github.com/pallets/flask/blob/main/CONTRIBUTING.rst
|
||||||
|
|
||||||
|
|
||||||
Donate
|
Donate
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ Using ``async`` and ``await``
|
||||||
|
|
||||||
Routes, error handlers, before request, after request, and teardown
|
Routes, error handlers, before request, after request, and teardown
|
||||||
functions can all be coroutine functions if Flask is installed with the
|
functions can all be coroutine functions if Flask is installed with the
|
||||||
``async`` extra (``pip install flask[async]``). This allows views to be
|
``async`` extra (``pip install flask[async]``). It requires Python 3.7+
|
||||||
|
where ``contextvars.ContextVar`` is available. This allows views to be
|
||||||
defined with ``async def`` and use ``await``.
|
defined with ``async def`` and use ``await``.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
@ -17,6 +18,18 @@ defined with ``async def`` and use ``await``.
|
||||||
data = await async_db_query(...)
|
data = await async_db_query(...)
|
||||||
return jsonify(data)
|
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.
|
||||||
|
|
||||||
|
|
||||||
Performance
|
Performance
|
||||||
-----------
|
-----------
|
||||||
|
|
@ -51,7 +64,7 @@ example via ``asyncio.create_task``.
|
||||||
If you wish to use background tasks it is best to use a task queue to
|
If you wish to use background tasks it is best to use a task queue to
|
||||||
trigger background work, rather than spawn tasks in a view
|
trigger background work, rather than spawn tasks in a view
|
||||||
function. With that in mind you can spawn asyncio tasks by serving
|
function. With that in mind you can spawn asyncio tasks by serving
|
||||||
Flask with a ASGI server and utilising the asgiref WsgiToAsgi adapter
|
Flask with an ASGI server and utilising the asgiref WsgiToAsgi adapter
|
||||||
as described in :ref:`asgi`. This works as the adapter creates an
|
as described in :ref:`asgi`. This works as the adapter creates an
|
||||||
event loop that runs continually.
|
event loop that runs continually.
|
||||||
|
|
||||||
|
|
@ -64,7 +77,7 @@ to the way it is implemented. If you have a mainly async codebase it
|
||||||
would make sense to consider `Quart`_. Quart is a reimplementation of
|
would make sense to consider `Quart`_. Quart is a reimplementation of
|
||||||
Flask based on the `ASGI`_ standard instead of WSGI. This allows it to
|
Flask based on the `ASGI`_ standard instead of WSGI. This allows it to
|
||||||
handle many concurrent requests, long running requests, and websockets
|
handle many concurrent requests, long running requests, and websockets
|
||||||
without requiring individual worker processes or threads.
|
without requiring multiple worker processes or threads.
|
||||||
|
|
||||||
It has also already been possible to run Flask with Gevent or Eventlet
|
It has also already been possible to run Flask with Gevent or Eventlet
|
||||||
to get many of the benefits of async request handling. These libraries
|
to get many of the benefits of async request handling. These libraries
|
||||||
|
|
@ -80,12 +93,27 @@ to understanding the specific needs of your project.
|
||||||
Extensions
|
Extensions
|
||||||
----------
|
----------
|
||||||
|
|
||||||
Existing Flask extensions only expect views to be synchronous. If they
|
Flask extensions predating Flask's async support do not expect async views.
|
||||||
provide decorators to add functionality to views, those will probably
|
If they provide decorators to add functionality to views, those will probably
|
||||||
not work with async views because they will not await the function or be
|
not work with async views because they will not await the function or be
|
||||||
awaitable. Other functions they provide will not be awaitable either and
|
awaitable. Other functions they provide will not be awaitable either and
|
||||||
will probably be blocking if called within an async view.
|
will probably be blocking if called within an async view.
|
||||||
|
|
||||||
|
Extension authors can support async functions by utilising the
|
||||||
|
:meth:`flask.Flask.ensure_sync` method. For example, if the extension
|
||||||
|
provides a view function decorator add ``ensure_sync`` before calling
|
||||||
|
the decorated function,
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def extension(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
... # Extension logic
|
||||||
|
return current_app.ensure_sync(func)(*args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
Check the changelog of the extension you want to use to see if they've
|
Check the changelog of the extension you want to use to see if they've
|
||||||
implemented async support, or make a feature request or PR to them.
|
implemented async support, or make a feature request or PR to them.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -127,8 +127,8 @@ It is possible to register a blueprint on another blueprint.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
parent = Blueprint("parent", __name__, url_prefix="/parent")
|
parent = Blueprint('parent', __name__, url_prefix='/parent')
|
||||||
child = Blueprint("child", __name__, url_prefix="/child)
|
child = Blueprint('child', __name__, url_prefix='/child')
|
||||||
parent.register_blueprint(child)
|
parent.register_blueprint(child)
|
||||||
app.register_blueprint(parent)
|
app.register_blueprint(parent)
|
||||||
|
|
||||||
|
|
|
||||||
10
docs/cli.rst
10
docs/cli.rst
|
|
@ -45,13 +45,13 @@ While ``FLASK_APP`` supports a variety of options for specifying your
|
||||||
application, most use cases should be simple. Here are the typical values:
|
application, most use cases should be simple. Here are the typical values:
|
||||||
|
|
||||||
(nothing)
|
(nothing)
|
||||||
The file :file:`wsgi.py` is imported, automatically detecting an app
|
The name "app" or "wsgi" is imported (as a ".py" file, or package),
|
||||||
(``app``). This provides an easy way to create an app from a factory with
|
automatically detecting an app (``app`` or ``application``) or
|
||||||
extra arguments.
|
factory (``create_app`` or ``make_app``).
|
||||||
|
|
||||||
``FLASK_APP=hello``
|
``FLASK_APP=hello``
|
||||||
The name is imported, automatically detecting an app (``app``) or factory
|
The given name is imported, automatically detecting an app (``app``
|
||||||
(``create_app``).
|
or ``application``) or factory (``create_app`` or ``make_app``).
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ extensions = [
|
||||||
"sphinx_issues",
|
"sphinx_issues",
|
||||||
"sphinx_tabs.tabs",
|
"sphinx_tabs.tabs",
|
||||||
]
|
]
|
||||||
|
autodoc_typehints = "description"
|
||||||
intersphinx_mapping = {
|
intersphinx_mapping = {
|
||||||
"python": ("https://docs.python.org/3/", None),
|
"python": ("https://docs.python.org/3/", None),
|
||||||
"werkzeug": ("https://werkzeug.palletsprojects.com/", None),
|
"werkzeug": ("https://werkzeug.palletsprojects.com/", None),
|
||||||
|
|
@ -48,10 +49,10 @@ html_context = {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
html_sidebars = {
|
html_sidebars = {
|
||||||
"index": ["project.html", "localtoc.html", "searchbox.html"],
|
"index": ["project.html", "localtoc.html", "searchbox.html", "ethicalads.html"],
|
||||||
"**": ["localtoc.html", "relations.html", "searchbox.html"],
|
"**": ["localtoc.html", "relations.html", "searchbox.html", "ethicalads.html"],
|
||||||
}
|
}
|
||||||
singlehtml_sidebars = {"index": ["project.html", "localtoc.html"]}
|
singlehtml_sidebars = {"index": ["project.html", "localtoc.html", "ethicalads.html"]}
|
||||||
html_static_path = ["_static"]
|
html_static_path = ["_static"]
|
||||||
html_favicon = "_static/flask-icon.png"
|
html_favicon = "_static/flask-icon.png"
|
||||||
html_logo = "_static/flask-icon.png"
|
html_logo = "_static/flask-icon.png"
|
||||||
|
|
@ -77,7 +78,7 @@ def github_link(name, rawtext, text, lineno, inliner, options=None, content=None
|
||||||
words = None
|
words = None
|
||||||
|
|
||||||
if packaging.version.parse(release).is_devrelease:
|
if packaging.version.parse(release).is_devrelease:
|
||||||
url = f"{base_url}master/{text}"
|
url = f"{base_url}main/{text}"
|
||||||
else:
|
else:
|
||||||
url = f"{base_url}{release}/{text}"
|
url = f"{base_url}{release}/{text}"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ Hosted options
|
||||||
|
|
||||||
- `Deploying Flask on Heroku <https://devcenter.heroku.com/articles/getting-started-with-python>`_
|
- `Deploying Flask on Heroku <https://devcenter.heroku.com/articles/getting-started-with-python>`_
|
||||||
- `Deploying Flask on Google App Engine <https://cloud.google.com/appengine/docs/standard/python3/runtime>`_
|
- `Deploying Flask on Google App Engine <https://cloud.google.com/appengine/docs/standard/python3/runtime>`_
|
||||||
|
- `Deploying Flask on Google Cloud Run <https://cloud.google.com/run/docs/quickstarts/build-and-deploy/python>`_
|
||||||
- `Deploying Flask on AWS Elastic Beanstalk <https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create-deploy-python-flask.html>`_
|
- `Deploying Flask on AWS Elastic Beanstalk <https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create-deploy-python-flask.html>`_
|
||||||
- `Deploying on Azure (IIS) <https://docs.microsoft.com/en-us/azure/app-service/containers/how-to-configure-python>`_
|
- `Deploying on Azure (IIS) <https://docs.microsoft.com/en-us/azure/app-service/containers/how-to-configure-python>`_
|
||||||
- `Deploying on PythonAnywhere <https://help.pythonanywhere.com/pages/Flask/>`_
|
- `Deploying on PythonAnywhere <https://help.pythonanywhere.com/pages/Flask/>`_
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ Python Version
|
||||||
We recommend using the latest version of Python. Flask supports Python
|
We recommend using the latest version of Python. Flask supports Python
|
||||||
3.6 and newer.
|
3.6 and newer.
|
||||||
|
|
||||||
|
``async`` support in Flask requires Python 3.7+ for ``contextvars.ContextVar``.
|
||||||
|
|
||||||
|
|
||||||
Dependencies
|
Dependencies
|
||||||
------------
|
------------
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ An example task
|
||||||
|
|
||||||
Let's write a task that adds two numbers together and returns the result. We
|
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``
|
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
|
from flask import Flask
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ Here is the code for that decorator::
|
||||||
def decorated_function(*args, **kwargs):
|
def decorated_function(*args, **kwargs):
|
||||||
template_name = template
|
template_name = template
|
||||||
if template_name is None:
|
if template_name is None:
|
||||||
template_name = f"'{request.endpoint.replace('.', '/')}.html'"
|
template_name = f"{request.endpoint.replace('.', '/')}.html"
|
||||||
ctx = f(*args, **kwargs)
|
ctx = f(*args, **kwargs)
|
||||||
if ctx is None:
|
if ctx is None:
|
||||||
ctx = {}
|
ctx = {}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ to tell your terminal the application to work with by exporting the
|
||||||
|
|
||||||
.. code-block:: text
|
.. code-block:: text
|
||||||
|
|
||||||
$ export FLASK_APP=hello.py
|
$ export FLASK_APP=hello
|
||||||
$ flask run
|
$ flask run
|
||||||
* Running on http://127.0.0.1:5000/
|
* Running on http://127.0.0.1:5000/
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ to tell your terminal the application to work with by exporting the
|
||||||
|
|
||||||
.. code-block:: text
|
.. code-block:: text
|
||||||
|
|
||||||
> set FLASK_APP=hello.py
|
> set FLASK_APP=hello
|
||||||
> flask run
|
> flask run
|
||||||
* Running on http://127.0.0.1:5000/
|
* Running on http://127.0.0.1:5000/
|
||||||
|
|
||||||
|
|
@ -66,10 +66,16 @@ to tell your terminal the application to work with by exporting the
|
||||||
|
|
||||||
.. code-block:: text
|
.. code-block:: text
|
||||||
|
|
||||||
> $env:FLASK_APP = "hello.py"
|
> $env:FLASK_APP = "hello"
|
||||||
> flask run
|
> flask run
|
||||||
* Running on http://127.0.0.1:5000/
|
* Running on http://127.0.0.1:5000/
|
||||||
|
|
||||||
|
.. admonition:: Application Discovery Behavior
|
||||||
|
|
||||||
|
As a shortcut, if the file is named ``app.py`` or ``wsgi.py``, you
|
||||||
|
don't have to set the ``FLASK_APP`` environment variable. See
|
||||||
|
:doc:`/cli` for more details.
|
||||||
|
|
||||||
This launches a very simple builtin server, which is good enough for
|
This launches a very simple builtin server, which is good enough for
|
||||||
testing but probably not what you want to use in production. For
|
testing but probably not what you want to use in production. For
|
||||||
deployment options see :doc:`deploying/index`.
|
deployment options see :doc:`deploying/index`.
|
||||||
|
|
@ -240,7 +246,7 @@ of the argument like ``<converter:variable_name>``. ::
|
||||||
@app.route('/user/<username>')
|
@app.route('/user/<username>')
|
||||||
def show_user_profile(username):
|
def show_user_profile(username):
|
||||||
# show the user profile for that user
|
# show the user profile for that user
|
||||||
return f'User {username}'
|
return f'User {escape(username)}'
|
||||||
|
|
||||||
@app.route('/post/<int:post_id>')
|
@app.route('/post/<int:post_id>')
|
||||||
def show_post(post_id):
|
def show_post(post_id):
|
||||||
|
|
@ -250,7 +256,7 @@ of the argument like ``<converter:variable_name>``. ::
|
||||||
@app.route('/path/<path:subpath>')
|
@app.route('/path/<path:subpath>')
|
||||||
def show_subpath(subpath):
|
def show_subpath(subpath):
|
||||||
# show the subpath after /path/
|
# show the subpath after /path/
|
||||||
return f'Subpath {subpath}'
|
return f'Subpath {escape(subpath)}'
|
||||||
|
|
||||||
Converter types:
|
Converter types:
|
||||||
|
|
||||||
|
|
@ -438,9 +444,9 @@ Here is an example template:
|
||||||
<h1>Hello, World!</h1>
|
<h1>Hello, World!</h1>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
Inside templates you also have access to the :class:`~flask.request`,
|
Inside templates you also have access to the :data:`~flask.Flask.config`,
|
||||||
:class:`~flask.session` and :class:`~flask.g` [#]_ objects
|
:class:`~flask.request`, :class:`~flask.session` and :class:`~flask.g` [#]_ objects
|
||||||
as well as the :func:`~flask.get_flashed_messages` function.
|
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
|
Templates are especially useful if inheritance is used. If you want to
|
||||||
know how that works, see :doc:`patterns/templateinheritance`. Basically
|
know how that works, see :doc:`patterns/templateinheritance`. Basically
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ by default:
|
||||||
.. data:: config
|
.. data:: config
|
||||||
:noindex:
|
:noindex:
|
||||||
|
|
||||||
The current configuration object (:data:`flask.config`)
|
The current configuration object (:data:`flask.Flask.config`)
|
||||||
|
|
||||||
.. versionadded:: 0.6
|
.. versionadded:: 0.6
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,20 +48,21 @@ the application for testing and initializes a new database::
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from flaskr import create_app
|
from flaskr import create_app
|
||||||
|
from flaskr.db import init_db
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def client():
|
def client():
|
||||||
db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
|
db_fd, db_path = tempfile.mkstemp()
|
||||||
flaskr.app.config['TESTING'] = True
|
app = create_app({'TESTING': True, 'DATABASE': db_path})
|
||||||
|
|
||||||
with flaskr.app.test_client() as client:
|
with app.test_client() as client:
|
||||||
with flaskr.app.app_context():
|
with app.app_context():
|
||||||
flaskr.init_db()
|
init_db()
|
||||||
yield client
|
yield client
|
||||||
|
|
||||||
os.close(db_fd)
|
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
|
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
|
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
|
:class:`~flask.g` and :class:`~flask.session` objects like in view
|
||||||
functions. Here is a full example that demonstrates this approach::
|
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'):
|
with app.test_request_context('/?name=Peter'):
|
||||||
assert flask.request.path == '/'
|
assert request.path == '/'
|
||||||
assert flask.request.args['name'] == 'Peter'
|
assert request.args['name'] == 'Peter'
|
||||||
|
|
||||||
All the other objects that are context bound can be used in the same
|
All the other objects that are context bound can be used in the same
|
||||||
way.
|
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
|
:meth:`~flask.Flask.before_request` functions to be called as well, you
|
||||||
need to call :meth:`~flask.Flask.preprocess_request` yourself::
|
need to call :meth:`~flask.Flask.preprocess_request` yourself::
|
||||||
|
|
||||||
app = flask.Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
with app.test_request_context('/?name=Peter'):
|
with app.test_request_context('/?name=Peter'):
|
||||||
app.preprocess_request()
|
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
|
need to call into :meth:`~flask.Flask.process_response` which however
|
||||||
requires that you pass it a response object::
|
requires that you pass it a response object::
|
||||||
|
|
||||||
app = flask.Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
with app.test_request_context('/?name=Peter'):
|
with app.test_request_context('/?name=Peter'):
|
||||||
resp = Response('...')
|
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
|
happen. With Flask 0.4 this is possible by using the
|
||||||
:meth:`~flask.Flask.test_client` with a ``with`` block::
|
:meth:`~flask.Flask.test_client` with a ``with`` block::
|
||||||
|
|
||||||
app = flask.Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
with app.test_client() as c:
|
with app.test_client() as c:
|
||||||
rv = c.get('/?tequila=42')
|
rv = c.get('/?tequila=42')
|
||||||
|
|
@ -353,7 +354,7 @@ keep the context around and access :data:`flask.session`::
|
||||||
|
|
||||||
with app.test_client() as c:
|
with app.test_client() as c:
|
||||||
rv = c.get('/')
|
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
|
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
|
access the session before a request was fired. Starting with Flask 0.8 we
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ this structure and take full advantage of Flask's flexibility.
|
||||||
.. image:: flaskr_edit.png
|
.. image:: flaskr_edit.png
|
||||||
:align: center
|
:align: center
|
||||||
:class: screenshot
|
:class: screenshot
|
||||||
:alt: screenshot of login page
|
:alt: screenshot of edit page
|
||||||
|
|
||||||
:gh:`The tutorial project is available as an example in the Flask
|
:gh:`The tutorial project is available as an example in the Flask
|
||||||
repository <examples/tutorial>`, if you want to compare your project
|
repository <examples/tutorial>`, if you want to compare your project
|
||||||
|
|
|
||||||
|
|
@ -113,8 +113,8 @@ Method Based Dispatching
|
||||||
|
|
||||||
For RESTful APIs it's especially helpful to execute a different function
|
For RESTful APIs it's especially helpful to execute a different function
|
||||||
for each HTTP method. With the :class:`flask.views.MethodView` you can
|
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
|
easily do that. Each HTTP method maps to a method of the class with the
|
||||||
(just in lowercase)::
|
same name (just in lowercase)::
|
||||||
|
|
||||||
from flask.views import MethodView
|
from flask.views import MethodView
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ Install
|
||||||
|
|
||||||
**Be sure to use the same version of the code as the version of the docs
|
**Be sure to use the same version of the code as the version of the docs
|
||||||
you're reading.** You probably want the latest tagged version, but the
|
you're reading.** You probably want the latest tagged version, but the
|
||||||
default Git version is the master branch. ::
|
default Git version is the main branch. ::
|
||||||
|
|
||||||
# clone the repository
|
# clone the repository
|
||||||
$ git clone https://github.com/pallets/flask
|
$ git clone https://github.com/pallets/flask
|
||||||
|
|
@ -35,7 +35,7 @@ Install Flaskr::
|
||||||
|
|
||||||
$ pip install -e .
|
$ pip install -e .
|
||||||
|
|
||||||
Or if you are using the master branch, install Flask from source before
|
Or if you are using the main branch, install Flask from source before
|
||||||
installing Flaskr::
|
installing Flaskr::
|
||||||
|
|
||||||
$ pip install -e ../..
|
$ pip install -e ../..
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
-r docs.in
|
-r docs.in
|
||||||
-r tests.in
|
-r tests.in
|
||||||
|
-r typing.in
|
||||||
pip-tools
|
pip-tools
|
||||||
pre-commit
|
pre-commit
|
||||||
tox
|
tox
|
||||||
|
|
|
||||||
|
|
@ -8,33 +8,35 @@ alabaster==0.7.12
|
||||||
# via sphinx
|
# via sphinx
|
||||||
appdirs==1.4.4
|
appdirs==1.4.4
|
||||||
# via virtualenv
|
# via virtualenv
|
||||||
asgiref==3.3.4
|
asgiref==3.4.1
|
||||||
# via -r requirements/tests.in
|
# via -r requirements/tests.in
|
||||||
attrs==20.3.0
|
attrs==21.2.0
|
||||||
# via pytest
|
# via pytest
|
||||||
babel==2.9.0
|
babel==2.9.1
|
||||||
# via sphinx
|
# via sphinx
|
||||||
blinker==1.4
|
blinker==1.4
|
||||||
# via -r requirements/tests.in
|
# via -r requirements/tests.in
|
||||||
certifi==2020.12.5
|
certifi==2020.12.5
|
||||||
# via requests
|
# via requests
|
||||||
cfgv==3.2.0
|
cfgv==3.3.0
|
||||||
# via pre-commit
|
# via pre-commit
|
||||||
chardet==4.0.0
|
chardet==4.0.0
|
||||||
# via requests
|
# via requests
|
||||||
click==7.1.2
|
click==8.0.1
|
||||||
# via pip-tools
|
# via pip-tools
|
||||||
distlib==0.3.1
|
distlib==0.3.1
|
||||||
# via virtualenv
|
# via virtualenv
|
||||||
docutils==0.16
|
docutils==0.16
|
||||||
# via sphinx
|
# via
|
||||||
|
# sphinx
|
||||||
|
# sphinx-tabs
|
||||||
filelock==3.0.12
|
filelock==3.0.12
|
||||||
# via
|
# via
|
||||||
# tox
|
# tox
|
||||||
# virtualenv
|
# virtualenv
|
||||||
greenlet==1.0.0
|
greenlet==1.1.0
|
||||||
# via -r requirements/tests.in
|
# via -r requirements/tests.in
|
||||||
identify==2.2.3
|
identify==2.2.4
|
||||||
# via pre-commit
|
# via pre-commit
|
||||||
idna==2.10
|
idna==2.10
|
||||||
# via requests
|
# via requests
|
||||||
|
|
@ -42,10 +44,14 @@ imagesize==1.2.0
|
||||||
# via sphinx
|
# via sphinx
|
||||||
iniconfig==1.1.1
|
iniconfig==1.1.1
|
||||||
# via pytest
|
# via pytest
|
||||||
jinja2==2.11.3
|
jinja2==3.0.1
|
||||||
# via sphinx
|
# via sphinx
|
||||||
markupsafe==1.1.1
|
markupsafe==2.0.1
|
||||||
# via jinja2
|
# via jinja2
|
||||||
|
mypy==0.910
|
||||||
|
# via -r requirements/typing.in
|
||||||
|
mypy-extensions==0.4.3
|
||||||
|
# via mypy
|
||||||
nodeenv==1.6.0
|
nodeenv==1.6.0
|
||||||
# via pre-commit
|
# via pre-commit
|
||||||
packaging==20.9
|
packaging==20.9
|
||||||
|
|
@ -54,31 +60,31 @@ packaging==20.9
|
||||||
# pytest
|
# pytest
|
||||||
# sphinx
|
# sphinx
|
||||||
# tox
|
# tox
|
||||||
pallets-sphinx-themes==2.0.0rc1
|
pallets-sphinx-themes==2.0.1
|
||||||
# via -r requirements/docs.in
|
# via -r requirements/docs.in
|
||||||
pep517==0.10.0
|
pep517==0.10.0
|
||||||
# via pip-tools
|
# via pip-tools
|
||||||
pip-tools==6.1.0
|
pip-tools==6.2.0
|
||||||
# via -r requirements/dev.in
|
# via -r requirements/dev.in
|
||||||
pluggy==0.13.1
|
pluggy==0.13.1
|
||||||
# via
|
# via
|
||||||
# pytest
|
# pytest
|
||||||
# tox
|
# tox
|
||||||
pre-commit==2.12.0
|
pre-commit==2.13.0
|
||||||
# via -r requirements/dev.in
|
# via -r requirements/dev.in
|
||||||
py==1.10.0
|
py==1.10.0
|
||||||
# via
|
# via
|
||||||
# pytest
|
# pytest
|
||||||
# tox
|
# tox
|
||||||
pygments==2.8.1
|
pygments==2.9.0
|
||||||
# via
|
# via
|
||||||
# sphinx
|
# sphinx
|
||||||
# sphinx-tabs
|
# sphinx-tabs
|
||||||
pyparsing==2.4.7
|
pyparsing==2.4.7
|
||||||
# via packaging
|
# via packaging
|
||||||
pytest==6.2.3
|
pytest==6.2.4
|
||||||
# via -r requirements/tests.in
|
# via -r requirements/tests.in
|
||||||
python-dotenv==0.17.0
|
python-dotenv==0.19.0
|
||||||
# via -r requirements/tests.in
|
# via -r requirements/tests.in
|
||||||
pytz==2021.1
|
pytz==2021.1
|
||||||
# via babel
|
# via babel
|
||||||
|
|
@ -86,28 +92,28 @@ pyyaml==5.4.1
|
||||||
# via pre-commit
|
# via pre-commit
|
||||||
requests==2.25.1
|
requests==2.25.1
|
||||||
# via sphinx
|
# via sphinx
|
||||||
six==1.15.0
|
six==1.16.0
|
||||||
# via
|
# via
|
||||||
# tox
|
# tox
|
||||||
# virtualenv
|
# virtualenv
|
||||||
snowballstemmer==2.1.0
|
snowballstemmer==2.1.0
|
||||||
# via sphinx
|
# via sphinx
|
||||||
sphinx-issues==1.2.0
|
sphinx==4.1.2
|
||||||
# via -r requirements/docs.in
|
|
||||||
sphinx-tabs==2.1.0
|
|
||||||
# via -r requirements/docs.in
|
|
||||||
sphinx==3.5.4
|
|
||||||
# via
|
# via
|
||||||
# -r requirements/docs.in
|
# -r requirements/docs.in
|
||||||
# pallets-sphinx-themes
|
# pallets-sphinx-themes
|
||||||
# sphinx-issues
|
# sphinx-issues
|
||||||
# sphinx-tabs
|
# sphinx-tabs
|
||||||
# sphinxcontrib-log-cabinet
|
# sphinxcontrib-log-cabinet
|
||||||
|
sphinx-issues==1.2.0
|
||||||
|
# via -r requirements/docs.in
|
||||||
|
sphinx-tabs==3.1.0
|
||||||
|
# via -r requirements/docs.in
|
||||||
sphinxcontrib-applehelp==1.0.2
|
sphinxcontrib-applehelp==1.0.2
|
||||||
# via sphinx
|
# via sphinx
|
||||||
sphinxcontrib-devhelp==1.0.2
|
sphinxcontrib-devhelp==1.0.2
|
||||||
# via sphinx
|
# via sphinx
|
||||||
sphinxcontrib-htmlhelp==1.0.3
|
sphinxcontrib-htmlhelp==2.0.0
|
||||||
# via sphinx
|
# via sphinx
|
||||||
sphinxcontrib-jsmath==1.0.1
|
sphinxcontrib-jsmath==1.0.1
|
||||||
# via sphinx
|
# via sphinx
|
||||||
|
|
@ -115,22 +121,27 @@ sphinxcontrib-log-cabinet==1.0.1
|
||||||
# via -r requirements/docs.in
|
# via -r requirements/docs.in
|
||||||
sphinxcontrib-qthelp==1.0.3
|
sphinxcontrib-qthelp==1.0.3
|
||||||
# via sphinx
|
# via sphinx
|
||||||
sphinxcontrib-serializinghtml==1.1.4
|
sphinxcontrib-serializinghtml==1.1.5
|
||||||
# via sphinx
|
# via sphinx
|
||||||
toml==0.10.2
|
toml==0.10.2
|
||||||
# via
|
# via
|
||||||
|
# mypy
|
||||||
# pep517
|
# pep517
|
||||||
# pre-commit
|
# pre-commit
|
||||||
# pytest
|
# pytest
|
||||||
# tox
|
# tox
|
||||||
tox==3.23.0
|
tox==3.24.1
|
||||||
# via -r requirements/dev.in
|
# via -r requirements/dev.in
|
||||||
urllib3==1.26.4
|
typing-extensions==3.10.0.0
|
||||||
|
# via mypy
|
||||||
|
urllib3==1.26.5
|
||||||
# via requests
|
# via requests
|
||||||
virtualenv==20.4.3
|
virtualenv==20.4.6
|
||||||
# via
|
# via
|
||||||
# pre-commit
|
# pre-commit
|
||||||
# tox
|
# tox
|
||||||
|
wheel==0.36.2
|
||||||
|
# via pip-tools
|
||||||
|
|
||||||
# The following packages are considered to be unsafe in a requirements file:
|
# The following packages are considered to be unsafe in a requirements file:
|
||||||
# pip
|
# pip
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
Pallets-Sphinx-Themes >= 2.0.0rc1
|
Pallets-Sphinx-Themes
|
||||||
Sphinx
|
Sphinx
|
||||||
sphinx-issues
|
sphinx-issues
|
||||||
sphinxcontrib-log-cabinet
|
sphinxcontrib-log-cabinet
|
||||||
|
|
|
||||||
|
|
@ -6,29 +6,31 @@
|
||||||
#
|
#
|
||||||
alabaster==0.7.12
|
alabaster==0.7.12
|
||||||
# via sphinx
|
# via sphinx
|
||||||
babel==2.9.0
|
babel==2.9.1
|
||||||
# via sphinx
|
# via sphinx
|
||||||
certifi==2020.12.5
|
certifi==2020.12.5
|
||||||
# via requests
|
# via requests
|
||||||
chardet==4.0.0
|
chardet==4.0.0
|
||||||
# via requests
|
# via requests
|
||||||
docutils==0.16
|
docutils==0.16
|
||||||
# via sphinx
|
# via
|
||||||
|
# sphinx
|
||||||
|
# sphinx-tabs
|
||||||
idna==2.10
|
idna==2.10
|
||||||
# via requests
|
# via requests
|
||||||
imagesize==1.2.0
|
imagesize==1.2.0
|
||||||
# via sphinx
|
# via sphinx
|
||||||
jinja2==2.11.3
|
jinja2==3.0.1
|
||||||
# via sphinx
|
# via sphinx
|
||||||
markupsafe==1.1.1
|
markupsafe==2.0.1
|
||||||
# via jinja2
|
# via jinja2
|
||||||
packaging==20.9
|
packaging==20.9
|
||||||
# via
|
# via
|
||||||
# pallets-sphinx-themes
|
# pallets-sphinx-themes
|
||||||
# sphinx
|
# sphinx
|
||||||
pallets-sphinx-themes==2.0.0rc1
|
pallets-sphinx-themes==2.0.1
|
||||||
# via -r requirements/docs.in
|
# via -r requirements/docs.in
|
||||||
pygments==2.8.1
|
pygments==2.9.0
|
||||||
# via
|
# via
|
||||||
# sphinx
|
# sphinx
|
||||||
# sphinx-tabs
|
# sphinx-tabs
|
||||||
|
|
@ -40,22 +42,22 @@ requests==2.25.1
|
||||||
# via sphinx
|
# via sphinx
|
||||||
snowballstemmer==2.1.0
|
snowballstemmer==2.1.0
|
||||||
# via sphinx
|
# via sphinx
|
||||||
sphinx-issues==1.2.0
|
sphinx==4.1.2
|
||||||
# via -r requirements/docs.in
|
|
||||||
sphinx-tabs==2.1.0
|
|
||||||
# via -r requirements/docs.in
|
|
||||||
sphinx==3.5.4
|
|
||||||
# via
|
# via
|
||||||
# -r requirements/docs.in
|
# -r requirements/docs.in
|
||||||
# pallets-sphinx-themes
|
# pallets-sphinx-themes
|
||||||
# sphinx-issues
|
# sphinx-issues
|
||||||
# sphinx-tabs
|
# sphinx-tabs
|
||||||
# sphinxcontrib-log-cabinet
|
# sphinxcontrib-log-cabinet
|
||||||
|
sphinx-issues==1.2.0
|
||||||
|
# via -r requirements/docs.in
|
||||||
|
sphinx-tabs==3.1.0
|
||||||
|
# via -r requirements/docs.in
|
||||||
sphinxcontrib-applehelp==1.0.2
|
sphinxcontrib-applehelp==1.0.2
|
||||||
# via sphinx
|
# via sphinx
|
||||||
sphinxcontrib-devhelp==1.0.2
|
sphinxcontrib-devhelp==1.0.2
|
||||||
# via sphinx
|
# via sphinx
|
||||||
sphinxcontrib-htmlhelp==1.0.3
|
sphinxcontrib-htmlhelp==2.0.0
|
||||||
# via sphinx
|
# via sphinx
|
||||||
sphinxcontrib-jsmath==1.0.1
|
sphinxcontrib-jsmath==1.0.1
|
||||||
# via sphinx
|
# via sphinx
|
||||||
|
|
@ -63,9 +65,9 @@ sphinxcontrib-log-cabinet==1.0.1
|
||||||
# via -r requirements/docs.in
|
# via -r requirements/docs.in
|
||||||
sphinxcontrib-qthelp==1.0.3
|
sphinxcontrib-qthelp==1.0.3
|
||||||
# via sphinx
|
# via sphinx
|
||||||
sphinxcontrib-serializinghtml==1.1.4
|
sphinxcontrib-serializinghtml==1.1.5
|
||||||
# via sphinx
|
# via sphinx
|
||||||
urllib3==1.26.4
|
urllib3==1.26.5
|
||||||
# via requests
|
# via requests
|
||||||
|
|
||||||
# The following packages are considered to be unsafe in a requirements file:
|
# The following packages are considered to be unsafe in a requirements file:
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@
|
||||||
#
|
#
|
||||||
# pip-compile requirements/tests.in
|
# pip-compile requirements/tests.in
|
||||||
#
|
#
|
||||||
asgiref==3.3.4
|
asgiref==3.4.1
|
||||||
# via -r requirements/tests.in
|
# via -r requirements/tests.in
|
||||||
attrs==20.3.0
|
attrs==21.2.0
|
||||||
# via pytest
|
# via pytest
|
||||||
blinker==1.4
|
blinker==1.4
|
||||||
# via -r requirements/tests.in
|
# via -r requirements/tests.in
|
||||||
greenlet==1.0.0
|
greenlet==1.1.0
|
||||||
# via -r requirements/tests.in
|
# via -r requirements/tests.in
|
||||||
iniconfig==1.1.1
|
iniconfig==1.1.1
|
||||||
# via pytest
|
# via pytest
|
||||||
|
|
@ -22,9 +22,9 @@ py==1.10.0
|
||||||
# via pytest
|
# via pytest
|
||||||
pyparsing==2.4.7
|
pyparsing==2.4.7
|
||||||
# via packaging
|
# via packaging
|
||||||
pytest==6.2.3
|
pytest==6.2.4
|
||||||
# via -r requirements/tests.in
|
# via -r requirements/tests.in
|
||||||
python-dotenv==0.17.0
|
python-dotenv==0.19.0
|
||||||
# via -r requirements/tests.in
|
# via -r requirements/tests.in
|
||||||
toml==0.10.2
|
toml==0.10.2
|
||||||
# via pytest
|
# via pytest
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@
|
||||||
#
|
#
|
||||||
# pip-compile requirements/typing.in
|
# pip-compile requirements/typing.in
|
||||||
#
|
#
|
||||||
|
mypy==0.910
|
||||||
|
# via -r requirements/typing.in
|
||||||
mypy-extensions==0.4.3
|
mypy-extensions==0.4.3
|
||||||
# via mypy
|
# via mypy
|
||||||
mypy==0.812
|
toml==0.10.2
|
||||||
# via -r requirements/typing.in
|
|
||||||
typed-ast==1.4.3
|
|
||||||
# via mypy
|
# via mypy
|
||||||
typing-extensions==3.7.4.3
|
typing-extensions==3.10.0.0
|
||||||
# via mypy
|
# via mypy
|
||||||
|
|
|
||||||
|
|
@ -112,3 +112,6 @@ ignore_missing_imports = True
|
||||||
|
|
||||||
[mypy-dotenv.*]
|
[mypy-dotenv.*]
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
|
|
||||||
|
[mypy-cryptography.*]
|
||||||
|
ignore_missing_imports = True
|
||||||
|
|
|
||||||
10
setup.py
10
setup.py
|
|
@ -4,13 +4,13 @@ from setuptools import setup
|
||||||
setup(
|
setup(
|
||||||
name="Flask",
|
name="Flask",
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"Werkzeug>=2.0.0rc4",
|
"Werkzeug >= 2.0",
|
||||||
"Jinja2>=3.0.0rc1",
|
"Jinja2 >= 3.0",
|
||||||
"itsdangerous>=2.0.0rc2",
|
"itsdangerous >= 2.0",
|
||||||
"click>=7.1.2",
|
"click >= 8.0",
|
||||||
],
|
],
|
||||||
extras_require={
|
extras_require={
|
||||||
"async": ["asgiref>=3.2"],
|
"async": ["asgiref >= 3.2"],
|
||||||
"dotenv": ["python-dotenv"],
|
"dotenv": ["python-dotenv"],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,46 @@
|
||||||
from markupsafe import escape
|
from markupsafe import escape
|
||||||
from markupsafe import Markup
|
from markupsafe import Markup
|
||||||
from werkzeug.exceptions import abort
|
from werkzeug.exceptions import abort as abort
|
||||||
from werkzeug.utils import redirect
|
from werkzeug.utils import redirect as redirect
|
||||||
|
|
||||||
from . import json
|
from . import json as json
|
||||||
from .app import Flask
|
from .app import Flask as Flask
|
||||||
from .app import Request
|
from .app import Request as Request
|
||||||
from .app import Response
|
from .app import Response as Response
|
||||||
from .blueprints import Blueprint
|
from .blueprints import Blueprint as Blueprint
|
||||||
from .config import Config
|
from .config import Config as Config
|
||||||
from .ctx import after_this_request
|
from .ctx import after_this_request as after_this_request
|
||||||
from .ctx import copy_current_request_context
|
from .ctx import copy_current_request_context as copy_current_request_context
|
||||||
from .ctx import has_app_context
|
from .ctx import has_app_context as has_app_context
|
||||||
from .ctx import has_request_context
|
from .ctx import has_request_context as has_request_context
|
||||||
from .globals import _app_ctx_stack
|
from .globals import _app_ctx_stack as _app_ctx_stack
|
||||||
from .globals import _request_ctx_stack
|
from .globals import _request_ctx_stack as _request_ctx_stack
|
||||||
from .globals import current_app
|
from .globals import current_app as current_app
|
||||||
from .globals import g
|
from .globals import g as g
|
||||||
from .globals import request
|
from .globals import request as request
|
||||||
from .globals import session
|
from .globals import session as session
|
||||||
from .helpers import flash
|
from .helpers import flash as flash
|
||||||
from .helpers import get_flashed_messages
|
from .helpers import get_flashed_messages as get_flashed_messages
|
||||||
from .helpers import get_template_attribute
|
from .helpers import get_template_attribute as get_template_attribute
|
||||||
from .helpers import make_response
|
from .helpers import make_response as make_response
|
||||||
from .helpers import safe_join
|
from .helpers import safe_join as safe_join
|
||||||
from .helpers import send_file
|
from .helpers import send_file as send_file
|
||||||
from .helpers import send_from_directory
|
from .helpers import send_from_directory as send_from_directory
|
||||||
from .helpers import stream_with_context
|
from .helpers import stream_with_context as stream_with_context
|
||||||
from .helpers import url_for
|
from .helpers import url_for as url_for
|
||||||
from .json import jsonify
|
from .json import jsonify as jsonify
|
||||||
from .signals import appcontext_popped
|
from .signals import appcontext_popped as appcontext_popped
|
||||||
from .signals import appcontext_pushed
|
from .signals import appcontext_pushed as appcontext_pushed
|
||||||
from .signals import appcontext_tearing_down
|
from .signals import appcontext_tearing_down as appcontext_tearing_down
|
||||||
from .signals import before_render_template
|
from .signals import before_render_template as before_render_template
|
||||||
from .signals import got_request_exception
|
from .signals import got_request_exception as got_request_exception
|
||||||
from .signals import message_flashed
|
from .signals import message_flashed as message_flashed
|
||||||
from .signals import request_finished
|
from .signals import request_finished as request_finished
|
||||||
from .signals import request_started
|
from .signals import request_started as request_started
|
||||||
from .signals import request_tearing_down
|
from .signals import request_tearing_down as request_tearing_down
|
||||||
from .signals import signals_available
|
from .signals import signals_available as signals_available
|
||||||
from .signals import template_rendered
|
from .signals import template_rendered as template_rendered
|
||||||
from .templating import render_template
|
from .templating import render_template as render_template
|
||||||
from .templating import render_template_string
|
from .templating import render_template_string as render_template_string
|
||||||
|
|
||||||
__version__ = "2.0.0rc1"
|
__version__ = "2.1.0.dev0"
|
||||||
|
|
|
||||||
128
src/flask/app.py
128
src/flask/app.py
|
|
@ -16,6 +16,7 @@ from werkzeug.exceptions import BadRequest
|
||||||
from werkzeug.exceptions import BadRequestKeyError
|
from werkzeug.exceptions import BadRequestKeyError
|
||||||
from werkzeug.exceptions import HTTPException
|
from werkzeug.exceptions import HTTPException
|
||||||
from werkzeug.exceptions import InternalServerError
|
from werkzeug.exceptions import InternalServerError
|
||||||
|
from werkzeug.local import ContextVar
|
||||||
from werkzeug.routing import BuildError
|
from werkzeug.routing import BuildError
|
||||||
from werkzeug.routing import Map
|
from werkzeug.routing import Map
|
||||||
from werkzeug.routing import MapAdapter
|
from werkzeug.routing import MapAdapter
|
||||||
|
|
@ -35,12 +36,12 @@ from .globals import _request_ctx_stack
|
||||||
from .globals import g
|
from .globals import g
|
||||||
from .globals import request
|
from .globals import request
|
||||||
from .globals import session
|
from .globals import session
|
||||||
|
from .helpers import _split_blueprint_path
|
||||||
from .helpers import get_debug_flag
|
from .helpers import get_debug_flag
|
||||||
from .helpers import get_env
|
from .helpers import get_env
|
||||||
from .helpers import get_flashed_messages
|
from .helpers import get_flashed_messages
|
||||||
from .helpers import get_load_dotenv
|
from .helpers import get_load_dotenv
|
||||||
from .helpers import locked_cached_property
|
from .helpers import locked_cached_property
|
||||||
from .helpers import run_async
|
|
||||||
from .helpers import url_for
|
from .helpers import url_for
|
||||||
from .json import jsonify
|
from .json import jsonify
|
||||||
from .logging import create_logger
|
from .logging import create_logger
|
||||||
|
|
@ -58,8 +59,8 @@ from .signals import request_tearing_down
|
||||||
from .templating import DispatchingJinjaLoader
|
from .templating import DispatchingJinjaLoader
|
||||||
from .templating import Environment
|
from .templating import Environment
|
||||||
from .typing import AfterRequestCallable
|
from .typing import AfterRequestCallable
|
||||||
|
from .typing import BeforeFirstRequestCallable
|
||||||
from .typing import BeforeRequestCallable
|
from .typing import BeforeRequestCallable
|
||||||
from .typing import ErrorHandlerCallable
|
|
||||||
from .typing import ResponseReturnValue
|
from .typing import ResponseReturnValue
|
||||||
from .typing import TeardownCallable
|
from .typing import TeardownCallable
|
||||||
from .typing import TemplateContextProcessorCallable
|
from .typing import TemplateContextProcessorCallable
|
||||||
|
|
@ -74,9 +75,11 @@ from .wrappers import Request
|
||||||
from .wrappers import Response
|
from .wrappers import Response
|
||||||
|
|
||||||
if t.TYPE_CHECKING:
|
if t.TYPE_CHECKING:
|
||||||
|
import typing_extensions as te
|
||||||
from .blueprints import Blueprint
|
from .blueprints import Blueprint
|
||||||
from .testing import FlaskClient
|
from .testing import FlaskClient
|
||||||
from .testing import FlaskCliRunner
|
from .testing import FlaskCliRunner
|
||||||
|
from .typing import ErrorHandlerCallable
|
||||||
|
|
||||||
if sys.version_info >= (3, 8):
|
if sys.version_info >= (3, 8):
|
||||||
iscoroutinefunction = inspect.iscoroutinefunction
|
iscoroutinefunction = inspect.iscoroutinefunction
|
||||||
|
|
@ -439,7 +442,7 @@ class Flask(Scaffold):
|
||||||
#: :meth:`before_first_request` decorator.
|
#: :meth:`before_first_request` decorator.
|
||||||
#:
|
#:
|
||||||
#: .. versionadded:: 0.8
|
#: .. versionadded:: 0.8
|
||||||
self.before_first_request_funcs: t.List[BeforeRequestCallable] = []
|
self.before_first_request_funcs: t.List[BeforeFirstRequestCallable] = []
|
||||||
|
|
||||||
#: A list of functions that are called when the application context
|
#: A list of functions that are called when the application context
|
||||||
#: is destroyed. Since the application context is also torn down
|
#: is destroyed. Since the application context is also torn down
|
||||||
|
|
@ -706,7 +709,7 @@ class Flask(Scaffold):
|
||||||
session=session,
|
session=session,
|
||||||
g=g,
|
g=g,
|
||||||
)
|
)
|
||||||
rv.policies["json.dumps_function"] = json.dumps # type: ignore
|
rv.policies["json.dumps_function"] = json.dumps
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def create_global_jinja_loader(self) -> DispatchingJinjaLoader:
|
def create_global_jinja_loader(self) -> DispatchingJinjaLoader:
|
||||||
|
|
@ -748,7 +751,7 @@ class Flask(Scaffold):
|
||||||
] = self.template_context_processors[None]
|
] = self.template_context_processors[None]
|
||||||
reqctx = _request_ctx_stack.top
|
reqctx = _request_ctx_stack.top
|
||||||
if reqctx is not None:
|
if reqctx is not None:
|
||||||
for bp in self._request_blueprints():
|
for bp in request.blueprints:
|
||||||
if bp in self.template_context_processors:
|
if bp in self.template_context_processors:
|
||||||
funcs = chain(funcs, self.template_context_processors[bp])
|
funcs = chain(funcs, self.template_context_processors[bp])
|
||||||
orig_ctx = context.copy()
|
orig_ctx = context.copy()
|
||||||
|
|
@ -1019,6 +1022,12 @@ class Flask(Scaffold):
|
||||||
:class:`~flask.blueprints.BlueprintSetupState`. They can be
|
:class:`~flask.blueprints.BlueprintSetupState`. They can be
|
||||||
accessed in :meth:`~flask.Blueprint.record` callbacks.
|
accessed in :meth:`~flask.Blueprint.record` callbacks.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0.1
|
||||||
|
The ``name`` option can be used to change the (pre-dotted)
|
||||||
|
name the blueprint is registered with. This allows the same
|
||||||
|
blueprint to be registered multiple times with unique names
|
||||||
|
for ``url_for``.
|
||||||
|
|
||||||
.. versionadded:: 0.7
|
.. versionadded:: 0.7
|
||||||
"""
|
"""
|
||||||
blueprint.register(self, options)
|
blueprint.register(self, options)
|
||||||
|
|
@ -1090,17 +1099,17 @@ class Flask(Scaffold):
|
||||||
view_func = view_func.as_view(endpoint)
|
view_func = view_func.as_view(endpoint)
|
||||||
if view_func is not None:
|
if view_func is not None:
|
||||||
old_func = self.view_functions.get(endpoint)
|
old_func = self.view_functions.get(endpoint)
|
||||||
if getattr(old_func, "_flask_sync_wrapper", False):
|
|
||||||
old_func = old_func.__wrapped__ # type: ignore
|
|
||||||
if old_func is not None and old_func != view_func:
|
if old_func is not None and old_func != view_func:
|
||||||
raise AssertionError(
|
raise AssertionError(
|
||||||
"View function mapping is overwriting an existing"
|
"View function mapping is overwriting an existing"
|
||||||
f" endpoint function: {endpoint}"
|
f" endpoint function: {endpoint}"
|
||||||
)
|
)
|
||||||
self.view_functions[endpoint] = self.ensure_sync(view_func)
|
self.view_functions[endpoint] = view_func
|
||||||
|
|
||||||
@setupmethod
|
@setupmethod
|
||||||
def template_filter(self, name: t.Optional[str] = None) -> t.Callable:
|
def template_filter(
|
||||||
|
self, name: t.Optional[str] = None
|
||||||
|
) -> t.Callable[[TemplateFilterCallable], TemplateFilterCallable]:
|
||||||
"""A decorator that is used to register custom template filter.
|
"""A decorator that is used to register custom template filter.
|
||||||
You can specify a name for the filter, otherwise the function
|
You can specify a name for the filter, otherwise the function
|
||||||
name will be used. Example::
|
name will be used. Example::
|
||||||
|
|
@ -1132,7 +1141,9 @@ class Flask(Scaffold):
|
||||||
self.jinja_env.filters[name or f.__name__] = f
|
self.jinja_env.filters[name or f.__name__] = f
|
||||||
|
|
||||||
@setupmethod
|
@setupmethod
|
||||||
def template_test(self, name: t.Optional[str] = None) -> t.Callable:
|
def template_test(
|
||||||
|
self, name: t.Optional[str] = None
|
||||||
|
) -> t.Callable[[TemplateTestCallable], TemplateTestCallable]:
|
||||||
"""A decorator that is used to register custom template test.
|
"""A decorator that is used to register custom template test.
|
||||||
You can specify a name for the test, otherwise the function
|
You can specify a name for the test, otherwise the function
|
||||||
name will be used. Example::
|
name will be used. Example::
|
||||||
|
|
@ -1173,7 +1184,9 @@ class Flask(Scaffold):
|
||||||
self.jinja_env.tests[name or f.__name__] = f
|
self.jinja_env.tests[name or f.__name__] = f
|
||||||
|
|
||||||
@setupmethod
|
@setupmethod
|
||||||
def template_global(self, name: t.Optional[str] = None) -> t.Callable:
|
def template_global(
|
||||||
|
self, name: t.Optional[str] = None
|
||||||
|
) -> t.Callable[[TemplateGlobalCallable], TemplateGlobalCallable]:
|
||||||
"""A decorator that is used to register a custom template global function.
|
"""A decorator that is used to register a custom template global function.
|
||||||
You can specify a name for the global function, otherwise the function
|
You can specify a name for the global function, otherwise the function
|
||||||
name will be used. Example::
|
name will be used. Example::
|
||||||
|
|
@ -1209,7 +1222,9 @@ class Flask(Scaffold):
|
||||||
self.jinja_env.globals[name or f.__name__] = f
|
self.jinja_env.globals[name or f.__name__] = f
|
||||||
|
|
||||||
@setupmethod
|
@setupmethod
|
||||||
def before_first_request(self, f: BeforeRequestCallable) -> BeforeRequestCallable:
|
def before_first_request(
|
||||||
|
self, f: BeforeFirstRequestCallable
|
||||||
|
) -> BeforeFirstRequestCallable:
|
||||||
"""Registers a function to be run before the first request to this
|
"""Registers a function to be run before the first request to this
|
||||||
instance of the application.
|
instance of the application.
|
||||||
|
|
||||||
|
|
@ -1218,7 +1233,7 @@ class Flask(Scaffold):
|
||||||
|
|
||||||
.. versionadded:: 0.8
|
.. versionadded:: 0.8
|
||||||
"""
|
"""
|
||||||
self.before_first_request_funcs.append(self.ensure_sync(f))
|
self.before_first_request_funcs.append(f)
|
||||||
return f
|
return f
|
||||||
|
|
||||||
@setupmethod
|
@setupmethod
|
||||||
|
|
@ -1251,7 +1266,7 @@ class Flask(Scaffold):
|
||||||
|
|
||||||
.. versionadded:: 0.9
|
.. versionadded:: 0.9
|
||||||
"""
|
"""
|
||||||
self.teardown_appcontext_funcs.append(self.ensure_sync(f))
|
self.teardown_appcontext_funcs.append(f)
|
||||||
return f
|
return f
|
||||||
|
|
||||||
@setupmethod
|
@setupmethod
|
||||||
|
|
@ -1263,7 +1278,9 @@ class Flask(Scaffold):
|
||||||
self.shell_context_processors.append(f)
|
self.shell_context_processors.append(f)
|
||||||
return 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:
|
"""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 a specific code, app handler for a specific code,
|
||||||
blueprint handler for an exception class, app handler for an exception
|
blueprint handler for an exception class, app handler for an exception
|
||||||
|
|
@ -1271,8 +1288,8 @@ class Flask(Scaffold):
|
||||||
"""
|
"""
|
||||||
exc_class, code = self._get_exc_class_and_code(type(e))
|
exc_class, code = self._get_exc_class_and_code(type(e))
|
||||||
|
|
||||||
for c in [code, None]:
|
for c in [code, None] if code is not None else [None]:
|
||||||
for name in chain(self._request_blueprints(), [None]):
|
for name in chain(request.blueprints, [None]):
|
||||||
handler_map = self.error_handler_spec[name][c]
|
handler_map = self.error_handler_spec[name][c]
|
||||||
|
|
||||||
if not handler_map:
|
if not handler_map:
|
||||||
|
|
@ -1299,7 +1316,7 @@ class Flask(Scaffold):
|
||||||
|
|
||||||
.. versionchanged:: 1.0
|
.. versionchanged:: 1.0
|
||||||
Exceptions are looked up by code *and* by MRO, so
|
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``.
|
handler for the base ``HTTPException``.
|
||||||
|
|
||||||
.. versionadded:: 0.3
|
.. versionadded:: 0.3
|
||||||
|
|
@ -1318,7 +1335,7 @@ class Flask(Scaffold):
|
||||||
handler = self._find_error_handler(e)
|
handler = self._find_error_handler(e)
|
||||||
if handler is None:
|
if handler is None:
|
||||||
return e
|
return e
|
||||||
return handler(e)
|
return self.ensure_sync(handler)(e)
|
||||||
|
|
||||||
def trap_http_exception(self, e: Exception) -> bool:
|
def trap_http_exception(self, e: Exception) -> bool:
|
||||||
"""Checks if an HTTP exception should be trapped or not. By default
|
"""Checks if an HTTP exception should be trapped or not. By default
|
||||||
|
|
@ -1385,7 +1402,7 @@ class Flask(Scaffold):
|
||||||
if handler is None:
|
if handler is None:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
return handler(e)
|
return self.ensure_sync(handler)(e)
|
||||||
|
|
||||||
def handle_exception(self, e: Exception) -> Response:
|
def handle_exception(self, e: Exception) -> Response:
|
||||||
"""Handle an exception that did not have an error handler
|
"""Handle an exception that did not have an error handler
|
||||||
|
|
@ -1432,7 +1449,7 @@ class Flask(Scaffold):
|
||||||
handler = self._find_error_handler(server_error)
|
handler = self._find_error_handler(server_error)
|
||||||
|
|
||||||
if handler is not None:
|
if handler is not None:
|
||||||
server_error = handler(server_error)
|
server_error = self.ensure_sync(handler)(server_error)
|
||||||
|
|
||||||
return self.finalize_request(server_error, from_error_handler=True)
|
return self.finalize_request(server_error, from_error_handler=True)
|
||||||
|
|
||||||
|
|
@ -1453,7 +1470,7 @@ class Flask(Scaffold):
|
||||||
f"Exception on {request.path} [{request.method}]", exc_info=exc_info
|
f"Exception on {request.path} [{request.method}]", exc_info=exc_info
|
||||||
)
|
)
|
||||||
|
|
||||||
def raise_routing_exception(self, request: Request) -> t.NoReturn:
|
def raise_routing_exception(self, request: Request) -> "te.NoReturn":
|
||||||
"""Exceptions that are recording during routing are reraised with
|
"""Exceptions that are recording during routing are reraised with
|
||||||
this method. During debug we are not reraising redirect requests
|
this method. During debug we are not reraising redirect requests
|
||||||
for non ``GET``, ``HEAD``, or ``OPTIONS`` requests and we're raising
|
for non ``GET``, ``HEAD``, or ``OPTIONS`` requests and we're raising
|
||||||
|
|
@ -1494,7 +1511,7 @@ class Flask(Scaffold):
|
||||||
):
|
):
|
||||||
return self.make_default_options_response()
|
return self.make_default_options_response()
|
||||||
# otherwise dispatch to the handler for that endpoint
|
# otherwise dispatch to the handler for that endpoint
|
||||||
return self.view_functions[rule.endpoint](**req.view_args)
|
return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
|
||||||
|
|
||||||
def full_dispatch_request(self) -> Response:
|
def full_dispatch_request(self) -> Response:
|
||||||
"""Dispatches the request and on top of that performs request
|
"""Dispatches the request and on top of that performs request
|
||||||
|
|
@ -1555,7 +1572,7 @@ class Flask(Scaffold):
|
||||||
if self._got_first_request:
|
if self._got_first_request:
|
||||||
return
|
return
|
||||||
for func in self.before_first_request_funcs:
|
for func in self.before_first_request_funcs:
|
||||||
func()
|
self.ensure_sync(func)()
|
||||||
self._got_first_request = True
|
self._got_first_request = True
|
||||||
|
|
||||||
def make_default_options_response(self) -> Response:
|
def make_default_options_response(self) -> Response:
|
||||||
|
|
@ -1591,10 +1608,40 @@ class Flask(Scaffold):
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
"""
|
"""
|
||||||
if iscoroutinefunction(func):
|
if iscoroutinefunction(func):
|
||||||
return run_async(func)
|
return self.async_to_sync(func)
|
||||||
|
|
||||||
return func
|
return func
|
||||||
|
|
||||||
|
def async_to_sync(
|
||||||
|
self, func: t.Callable[..., t.Coroutine]
|
||||||
|
) -> t.Callable[..., t.Any]:
|
||||||
|
"""Return a sync function that will run the coroutine function.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
result = app.async_to_sync(func)(*args, **kwargs)
|
||||||
|
|
||||||
|
Override this method to change how the app converts async code
|
||||||
|
to be synchronously callable.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from asgiref.sync import async_to_sync as asgiref_async_to_sync
|
||||||
|
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."
|
||||||
|
)
|
||||||
|
|
||||||
|
return asgiref_async_to_sync(func)
|
||||||
|
|
||||||
def make_response(self, rv: ResponseReturnValue) -> Response:
|
def make_response(self, rv: ResponseReturnValue) -> Response:
|
||||||
"""Convert the return value from a view function to an instance of
|
"""Convert the return value from a view function to an instance of
|
||||||
:attr:`response_class`.
|
:attr:`response_class`.
|
||||||
|
|
@ -1763,9 +1810,14 @@ class Flask(Scaffold):
|
||||||
.. versionadded:: 0.7
|
.. versionadded:: 0.7
|
||||||
"""
|
"""
|
||||||
funcs: t.Iterable[URLDefaultCallable] = self.url_default_functions[None]
|
funcs: t.Iterable[URLDefaultCallable] = self.url_default_functions[None]
|
||||||
|
|
||||||
if "." in endpoint:
|
if "." in endpoint:
|
||||||
bp = endpoint.rsplit(".", 1)[0]
|
# This is called by url_for, which can be called outside a
|
||||||
funcs = chain(funcs, self.url_default_functions[bp])
|
# 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)
|
||||||
|
|
||||||
for func in funcs:
|
for func in funcs:
|
||||||
func(endpoint, values)
|
func(endpoint, values)
|
||||||
|
|
||||||
|
|
@ -1806,18 +1858,18 @@ class Flask(Scaffold):
|
||||||
funcs: t.Iterable[URLValuePreprocessorCallable] = self.url_value_preprocessors[
|
funcs: t.Iterable[URLValuePreprocessorCallable] = self.url_value_preprocessors[
|
||||||
None
|
None
|
||||||
]
|
]
|
||||||
for bp in self._request_blueprints():
|
for bp in request.blueprints:
|
||||||
if bp in self.url_value_preprocessors:
|
if bp in self.url_value_preprocessors:
|
||||||
funcs = chain(funcs, self.url_value_preprocessors[bp])
|
funcs = chain(funcs, self.url_value_preprocessors[bp])
|
||||||
for func in funcs:
|
for func in funcs:
|
||||||
func(request.endpoint, request.view_args)
|
func(request.endpoint, request.view_args)
|
||||||
|
|
||||||
funcs: t.Iterable[BeforeRequestCallable] = self.before_request_funcs[None]
|
funcs: t.Iterable[BeforeRequestCallable] = self.before_request_funcs[None]
|
||||||
for bp in self._request_blueprints():
|
for bp in request.blueprints:
|
||||||
if bp in self.before_request_funcs:
|
if bp in self.before_request_funcs:
|
||||||
funcs = chain(funcs, self.before_request_funcs[bp])
|
funcs = chain(funcs, self.before_request_funcs[bp])
|
||||||
for func in funcs:
|
for func in funcs:
|
||||||
rv = func()
|
rv = self.ensure_sync(func)()
|
||||||
if rv is not None:
|
if rv is not None:
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
@ -1838,13 +1890,13 @@ class Flask(Scaffold):
|
||||||
"""
|
"""
|
||||||
ctx = _request_ctx_stack.top
|
ctx = _request_ctx_stack.top
|
||||||
funcs: t.Iterable[AfterRequestCallable] = ctx._after_request_functions
|
funcs: t.Iterable[AfterRequestCallable] = ctx._after_request_functions
|
||||||
for bp in self._request_blueprints():
|
for bp in request.blueprints:
|
||||||
if bp in self.after_request_funcs:
|
if bp in self.after_request_funcs:
|
||||||
funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
|
funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
|
||||||
if None in self.after_request_funcs:
|
if None in self.after_request_funcs:
|
||||||
funcs = chain(funcs, reversed(self.after_request_funcs[None]))
|
funcs = chain(funcs, reversed(self.after_request_funcs[None]))
|
||||||
for handler in funcs:
|
for handler in funcs:
|
||||||
response = handler(response)
|
response = self.ensure_sync(handler)(response)
|
||||||
if not self.session_interface.is_null_session(ctx.session):
|
if not self.session_interface.is_null_session(ctx.session):
|
||||||
self.session_interface.save_session(self, ctx.session, response)
|
self.session_interface.save_session(self, ctx.session, response)
|
||||||
return response
|
return response
|
||||||
|
|
@ -1877,11 +1929,11 @@ class Flask(Scaffold):
|
||||||
funcs: t.Iterable[TeardownCallable] = reversed(
|
funcs: t.Iterable[TeardownCallable] = reversed(
|
||||||
self.teardown_request_funcs[None]
|
self.teardown_request_funcs[None]
|
||||||
)
|
)
|
||||||
for bp in self._request_blueprints():
|
for bp in request.blueprints:
|
||||||
if bp in self.teardown_request_funcs:
|
if bp in self.teardown_request_funcs:
|
||||||
funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
|
funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
|
||||||
for func in funcs:
|
for func in funcs:
|
||||||
func(exc)
|
self.ensure_sync(func)(exc)
|
||||||
request_tearing_down.send(self, exc=exc)
|
request_tearing_down.send(self, exc=exc)
|
||||||
|
|
||||||
def do_teardown_appcontext(
|
def do_teardown_appcontext(
|
||||||
|
|
@ -1904,7 +1956,7 @@ class Flask(Scaffold):
|
||||||
if exc is _sentinel:
|
if exc is _sentinel:
|
||||||
exc = sys.exc_info()[1]
|
exc = sys.exc_info()[1]
|
||||||
for func in reversed(self.teardown_appcontext_funcs):
|
for func in reversed(self.teardown_appcontext_funcs):
|
||||||
func(exc)
|
self.ensure_sync(func)(exc)
|
||||||
appcontext_tearing_down.send(self, exc=exc)
|
appcontext_tearing_down.send(self, exc=exc)
|
||||||
|
|
||||||
def app_context(self) -> AppContext:
|
def app_context(self) -> AppContext:
|
||||||
|
|
@ -2049,9 +2101,3 @@ class Flask(Scaffold):
|
||||||
wrapped to apply middleware.
|
wrapped to apply middleware.
|
||||||
"""
|
"""
|
||||||
return self.wsgi_app(environ, start_response)
|
return self.wsgi_app(environ, start_response)
|
||||||
|
|
||||||
def _request_blueprints(self) -> t.Iterable[str]:
|
|
||||||
if _request_ctx_stack.top.request.blueprint is None:
|
|
||||||
return []
|
|
||||||
else:
|
|
||||||
return reversed(_request_ctx_stack.top.request.blueprint.split("."))
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ from .scaffold import _endpoint_from_view_func
|
||||||
from .scaffold import _sentinel
|
from .scaffold import _sentinel
|
||||||
from .scaffold import Scaffold
|
from .scaffold import Scaffold
|
||||||
from .typing import AfterRequestCallable
|
from .typing import AfterRequestCallable
|
||||||
|
from .typing import BeforeFirstRequestCallable
|
||||||
from .typing import BeforeRequestCallable
|
from .typing import BeforeRequestCallable
|
||||||
from .typing import ErrorHandlerCallable
|
|
||||||
from .typing import TeardownCallable
|
from .typing import TeardownCallable
|
||||||
from .typing import TemplateContextProcessorCallable
|
from .typing import TemplateContextProcessorCallable
|
||||||
from .typing import TemplateFilterCallable
|
from .typing import TemplateFilterCallable
|
||||||
|
|
@ -20,6 +20,7 @@ from .views import View
|
||||||
|
|
||||||
if t.TYPE_CHECKING:
|
if t.TYPE_CHECKING:
|
||||||
from .app import Flask
|
from .app import Flask
|
||||||
|
from .typing import ErrorHandlerCallable
|
||||||
|
|
||||||
DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable]
|
DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable]
|
||||||
|
|
||||||
|
|
@ -69,6 +70,7 @@ class BlueprintSetupState:
|
||||||
#: blueprint.
|
#: blueprint.
|
||||||
self.url_prefix = url_prefix
|
self.url_prefix = url_prefix
|
||||||
|
|
||||||
|
self.name = self.options.get("name", blueprint.name)
|
||||||
self.name_prefix = self.options.get("name_prefix", "")
|
self.name_prefix = self.options.get("name_prefix", "")
|
||||||
|
|
||||||
#: A dictionary with URL defaults that is added to each and every
|
#: A dictionary with URL defaults that is added to each and every
|
||||||
|
|
@ -103,9 +105,10 @@ class BlueprintSetupState:
|
||||||
defaults = dict(defaults, **options.pop("defaults"))
|
defaults = dict(defaults, **options.pop("defaults"))
|
||||||
if isinstance(view_func, type) and issubclass(view_func, View):
|
if isinstance(view_func, type) and issubclass(view_func, View):
|
||||||
view_func = view_func.as_view(endpoint)
|
view_func = view_func.as_view(endpoint)
|
||||||
|
|
||||||
self.app.add_url_rule(
|
self.app.add_url_rule(
|
||||||
rule,
|
rule,
|
||||||
f"{self.name_prefix}{self.blueprint.name}.{endpoint}",
|
f"{self.name_prefix}.{self.name}.{endpoint}".lstrip("."),
|
||||||
view_func,
|
view_func,
|
||||||
defaults=defaults,
|
defaults=defaults,
|
||||||
**options,
|
**options,
|
||||||
|
|
@ -195,6 +198,10 @@ class Blueprint(Scaffold):
|
||||||
template_folder=template_folder,
|
template_folder=template_folder,
|
||||||
root_path=root_path,
|
root_path=root_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if "." in name:
|
||||||
|
raise ValueError("'name' may not contain a dot '.' character.")
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.url_prefix = url_prefix
|
self.url_prefix = url_prefix
|
||||||
self.subdomain = subdomain
|
self.subdomain = subdomain
|
||||||
|
|
@ -255,39 +262,74 @@ class Blueprint(Scaffold):
|
||||||
arguments passed to this method will override the defaults set
|
arguments passed to this method will override the defaults set
|
||||||
on the blueprint.
|
on the blueprint.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0.1
|
||||||
|
The ``name`` option can be used to change the (pre-dotted)
|
||||||
|
name the blueprint is registered with. This allows the same
|
||||||
|
blueprint to be registered multiple times with unique names
|
||||||
|
for ``url_for``.
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
"""
|
"""
|
||||||
|
if blueprint is self:
|
||||||
|
raise ValueError("Cannot register a blueprint on itself")
|
||||||
self._blueprints.append((blueprint, options))
|
self._blueprints.append((blueprint, options))
|
||||||
|
|
||||||
def register(self, app: "Flask", options: dict) -> None:
|
def register(self, app: "Flask", options: dict) -> None:
|
||||||
"""Called by :meth:`Flask.register_blueprint` to register all
|
"""Called by :meth:`Flask.register_blueprint` to register all
|
||||||
views and callbacks registered on the blueprint with the
|
views and callbacks registered on the blueprint with the
|
||||||
application. Creates a :class:`.BlueprintSetupState` and calls
|
application. Creates a :class:`.BlueprintSetupState` and calls
|
||||||
each :meth:`record` callbackwith it.
|
each :meth:`record` callback with it.
|
||||||
|
|
||||||
:param app: The application this blueprint is being registered
|
:param app: The application this blueprint is being registered
|
||||||
with.
|
with.
|
||||||
:param options: Keyword arguments forwarded from
|
:param options: Keyword arguments forwarded from
|
||||||
:meth:`~Flask.register_blueprint`.
|
:meth:`~Flask.register_blueprint`.
|
||||||
:param first_registration: Whether this is the first time this
|
|
||||||
blueprint has been registered on the application.
|
.. versionchanged:: 2.0.1
|
||||||
|
Nested blueprints are registered with their dotted name.
|
||||||
|
This allows different blueprints with the same name to be
|
||||||
|
nested at different locations.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0.1
|
||||||
|
The ``name`` option can be used to change the (pre-dotted)
|
||||||
|
name the blueprint is registered with. This allows the same
|
||||||
|
blueprint to be registered multiple times with unique names
|
||||||
|
for ``url_for``.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0.1
|
||||||
|
Registering the same blueprint with the same name multiple
|
||||||
|
times is deprecated and will become an error in Flask 2.1.
|
||||||
"""
|
"""
|
||||||
first_registration = False
|
name_prefix = options.get("name_prefix", "")
|
||||||
|
self_name = options.get("name", self.name)
|
||||||
|
name = f"{name_prefix}.{self_name}".lstrip(".")
|
||||||
|
|
||||||
if self.name in app.blueprints:
|
if name in app.blueprints:
|
||||||
assert app.blueprints[self.name] is self, (
|
existing_at = f" '{name}'" if self_name != name else ""
|
||||||
"A name collision occurred between blueprints"
|
|
||||||
f" {self!r} and {app.blueprints[self.name]!r}."
|
|
||||||
f" Both share the same name {self.name!r}."
|
|
||||||
f" Blueprints that are created on the fly need unique"
|
|
||||||
f" names."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
app.blueprints[self.name] = self
|
|
||||||
first_registration = True
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
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:
|
if self.has_static_folder:
|
||||||
state.add_url_rule(
|
state.add_url_rule(
|
||||||
|
|
@ -297,25 +339,20 @@ class Blueprint(Scaffold):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Merge blueprint data into parent.
|
# Merge blueprint data into parent.
|
||||||
if first_registration:
|
if first_bp_registration or first_name_registration:
|
||||||
|
|
||||||
def extend(bp_dict, parent_dict, ensure_sync=False):
|
def extend(bp_dict, parent_dict):
|
||||||
for key, values in bp_dict.items():
|
for key, values in bp_dict.items():
|
||||||
key = self.name if key is None else f"{self.name}.{key}"
|
key = name if key is None else f"{name}.{key}"
|
||||||
|
|
||||||
if ensure_sync:
|
|
||||||
values = [app.ensure_sync(func) for func in values]
|
|
||||||
|
|
||||||
parent_dict[key].extend(values)
|
parent_dict[key].extend(values)
|
||||||
|
|
||||||
for key, value in self.error_handler_spec.items():
|
for key, value in self.error_handler_spec.items():
|
||||||
key = self.name if key is None else f"{self.name}.{key}"
|
key = name if key is None else f"{name}.{key}"
|
||||||
value = defaultdict(
|
value = defaultdict(
|
||||||
dict,
|
dict,
|
||||||
{
|
{
|
||||||
code: {
|
code: {
|
||||||
exc_class: app.ensure_sync(func)
|
exc_class: func for exc_class, func in code_values.items()
|
||||||
for exc_class, func in code_values.items()
|
|
||||||
}
|
}
|
||||||
for code, code_values in value.items()
|
for code, code_values in value.items()
|
||||||
},
|
},
|
||||||
|
|
@ -323,16 +360,13 @@ class Blueprint(Scaffold):
|
||||||
app.error_handler_spec[key] = value
|
app.error_handler_spec[key] = value
|
||||||
|
|
||||||
for endpoint, func in self.view_functions.items():
|
for endpoint, func in self.view_functions.items():
|
||||||
app.view_functions[endpoint] = app.ensure_sync(func)
|
app.view_functions[endpoint] = func
|
||||||
|
|
||||||
extend(
|
extend(self.before_request_funcs, app.before_request_funcs)
|
||||||
self.before_request_funcs, app.before_request_funcs, ensure_sync=True
|
extend(self.after_request_funcs, app.after_request_funcs)
|
||||||
)
|
|
||||||
extend(self.after_request_funcs, app.after_request_funcs, ensure_sync=True)
|
|
||||||
extend(
|
extend(
|
||||||
self.teardown_request_funcs,
|
self.teardown_request_funcs,
|
||||||
app.teardown_request_funcs,
|
app.teardown_request_funcs,
|
||||||
ensure_sync=True,
|
|
||||||
)
|
)
|
||||||
extend(self.url_default_functions, app.url_default_functions)
|
extend(self.url_default_functions, app.url_default_functions)
|
||||||
extend(self.url_value_preprocessors, app.url_value_preprocessors)
|
extend(self.url_value_preprocessors, app.url_value_preprocessors)
|
||||||
|
|
@ -347,21 +381,29 @@ class Blueprint(Scaffold):
|
||||||
if cli_resolved_group is None:
|
if cli_resolved_group is None:
|
||||||
app.cli.commands.update(self.cli.commands)
|
app.cli.commands.update(self.cli.commands)
|
||||||
elif cli_resolved_group is _sentinel:
|
elif cli_resolved_group is _sentinel:
|
||||||
self.cli.name = self.name
|
self.cli.name = name
|
||||||
app.cli.add_command(self.cli)
|
app.cli.add_command(self.cli)
|
||||||
else:
|
else:
|
||||||
self.cli.name = cli_resolved_group
|
self.cli.name = cli_resolved_group
|
||||||
app.cli.add_command(self.cli)
|
app.cli.add_command(self.cli)
|
||||||
|
|
||||||
for blueprint, bp_options in self._blueprints:
|
for blueprint, bp_options in self._blueprints:
|
||||||
url_prefix = options.get("url_prefix", "")
|
bp_options = bp_options.copy()
|
||||||
if "url_prefix" in bp_options:
|
bp_url_prefix = bp_options.get("url_prefix")
|
||||||
url_prefix = (
|
|
||||||
url_prefix.rstrip("/") + "/" + bp_options["url_prefix"].lstrip("/")
|
|
||||||
)
|
|
||||||
|
|
||||||
bp_options["url_prefix"] = url_prefix
|
if bp_url_prefix is None:
|
||||||
bp_options["name_prefix"] = options.get("name_prefix", "") + self.name + "."
|
bp_url_prefix = blueprint.url_prefix
|
||||||
|
|
||||||
|
if state.url_prefix is not None and bp_url_prefix is not None:
|
||||||
|
bp_options["url_prefix"] = (
|
||||||
|
state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/")
|
||||||
|
)
|
||||||
|
elif bp_url_prefix is not None:
|
||||||
|
bp_options["url_prefix"] = bp_url_prefix
|
||||||
|
elif state.url_prefix is not None:
|
||||||
|
bp_options["url_prefix"] = state.url_prefix
|
||||||
|
|
||||||
|
bp_options["name_prefix"] = name
|
||||||
blueprint.register(app, bp_options)
|
blueprint.register(app, bp_options)
|
||||||
|
|
||||||
def add_url_rule(
|
def add_url_rule(
|
||||||
|
|
@ -369,20 +411,31 @@ class Blueprint(Scaffold):
|
||||||
rule: str,
|
rule: str,
|
||||||
endpoint: t.Optional[str] = None,
|
endpoint: t.Optional[str] = None,
|
||||||
view_func: t.Optional[t.Callable] = None,
|
view_func: t.Optional[t.Callable] = None,
|
||||||
|
provide_automatic_options: t.Optional[bool] = None,
|
||||||
**options: t.Any,
|
**options: t.Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Like :meth:`Flask.add_url_rule` but for a blueprint. The endpoint for
|
"""Like :meth:`Flask.add_url_rule` but for a blueprint. The endpoint for
|
||||||
the :func:`url_for` function is prefixed with the name of the blueprint.
|
the :func:`url_for` function is prefixed with the name of the blueprint.
|
||||||
"""
|
"""
|
||||||
if endpoint:
|
if endpoint and "." in endpoint:
|
||||||
assert "." not in endpoint, "Blueprint endpoints should not contain dots"
|
raise ValueError("'endpoint' may not contain a dot '.' character.")
|
||||||
if view_func and hasattr(view_func, "__name__"):
|
|
||||||
assert (
|
|
||||||
"." not in view_func.__name__
|
|
||||||
), "Blueprint view function name should not contain dots"
|
|
||||||
self.record(lambda s: s.add_url_rule(rule, endpoint, view_func, **options))
|
|
||||||
|
|
||||||
def app_template_filter(self, name: t.Optional[str] = None) -> t.Callable:
|
if view_func and hasattr(view_func, "__name__") and "." in view_func.__name__:
|
||||||
|
raise ValueError("'view_func' name may not contain a dot '.' character.")
|
||||||
|
|
||||||
|
self.record(
|
||||||
|
lambda s: s.add_url_rule(
|
||||||
|
rule,
|
||||||
|
endpoint,
|
||||||
|
view_func,
|
||||||
|
provide_automatic_options=provide_automatic_options,
|
||||||
|
**options,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def app_template_filter(
|
||||||
|
self, name: t.Optional[str] = None
|
||||||
|
) -> t.Callable[[TemplateFilterCallable], TemplateFilterCallable]:
|
||||||
"""Register a custom template filter, available application wide. Like
|
"""Register a custom template filter, available application wide. Like
|
||||||
:meth:`Flask.template_filter` but for a blueprint.
|
:meth:`Flask.template_filter` but for a blueprint.
|
||||||
|
|
||||||
|
|
@ -412,7 +465,9 @@ class Blueprint(Scaffold):
|
||||||
|
|
||||||
self.record_once(register_template)
|
self.record_once(register_template)
|
||||||
|
|
||||||
def app_template_test(self, name: t.Optional[str] = None) -> t.Callable:
|
def app_template_test(
|
||||||
|
self, name: t.Optional[str] = None
|
||||||
|
) -> t.Callable[[TemplateTestCallable], TemplateTestCallable]:
|
||||||
"""Register a custom template test, available application wide. Like
|
"""Register a custom template test, available application wide. Like
|
||||||
:meth:`Flask.template_test` but for a blueprint.
|
:meth:`Flask.template_test` but for a blueprint.
|
||||||
|
|
||||||
|
|
@ -446,7 +501,9 @@ class Blueprint(Scaffold):
|
||||||
|
|
||||||
self.record_once(register_template)
|
self.record_once(register_template)
|
||||||
|
|
||||||
def app_template_global(self, name: t.Optional[str] = None) -> t.Callable:
|
def app_template_global(
|
||||||
|
self, name: t.Optional[str] = None
|
||||||
|
) -> t.Callable[[TemplateGlobalCallable], TemplateGlobalCallable]:
|
||||||
"""Register a custom template global, available application wide. Like
|
"""Register a custom template global, available application wide. Like
|
||||||
:meth:`Flask.template_global` but for a blueprint.
|
:meth:`Flask.template_global` but for a blueprint.
|
||||||
|
|
||||||
|
|
@ -485,21 +542,17 @@ class Blueprint(Scaffold):
|
||||||
before each request, even if outside of a blueprint.
|
before each request, even if outside of a blueprint.
|
||||||
"""
|
"""
|
||||||
self.record_once(
|
self.record_once(
|
||||||
lambda s: s.app.before_request_funcs.setdefault(None, []).append(
|
lambda s: s.app.before_request_funcs.setdefault(None, []).append(f)
|
||||||
s.app.ensure_sync(f)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return f
|
return f
|
||||||
|
|
||||||
def before_app_first_request(
|
def before_app_first_request(
|
||||||
self, f: BeforeRequestCallable
|
self, f: BeforeFirstRequestCallable
|
||||||
) -> BeforeRequestCallable:
|
) -> BeforeFirstRequestCallable:
|
||||||
"""Like :meth:`Flask.before_first_request`. Such a function is
|
"""Like :meth:`Flask.before_first_request`. Such a function is
|
||||||
executed before the first request to the application.
|
executed before the first request to the application.
|
||||||
"""
|
"""
|
||||||
self.record_once(
|
self.record_once(lambda s: s.app.before_first_request_funcs.append(f))
|
||||||
lambda s: s.app.before_first_request_funcs.append(s.app.ensure_sync(f))
|
|
||||||
)
|
|
||||||
return f
|
return f
|
||||||
|
|
||||||
def after_app_request(self, f: AfterRequestCallable) -> AfterRequestCallable:
|
def after_app_request(self, f: AfterRequestCallable) -> AfterRequestCallable:
|
||||||
|
|
@ -507,9 +560,7 @@ class Blueprint(Scaffold):
|
||||||
is executed after each request, even if outside of the blueprint.
|
is executed after each request, even if outside of the blueprint.
|
||||||
"""
|
"""
|
||||||
self.record_once(
|
self.record_once(
|
||||||
lambda s: s.app.after_request_funcs.setdefault(None, []).append(
|
lambda s: s.app.after_request_funcs.setdefault(None, []).append(f)
|
||||||
s.app.ensure_sync(f)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
@ -539,7 +590,9 @@ class Blueprint(Scaffold):
|
||||||
handler is used for all requests, even if outside of the blueprint.
|
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))
|
self.record_once(lambda s: s.app.errorhandler(code)(f))
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
@ -560,14 +613,3 @@ class Blueprint(Scaffold):
|
||||||
lambda s: s.app.url_default_functions.setdefault(None, []).append(f)
|
lambda s: s.app.url_default_functions.setdefault(None, []).append(f)
|
||||||
)
|
)
|
||||||
return f
|
return f
|
||||||
|
|
||||||
def ensure_sync(self, f: t.Callable) -> t.Callable:
|
|
||||||
"""Ensure the function is synchronous.
|
|
||||||
|
|
||||||
Override if you would like custom async to sync behaviour in
|
|
||||||
this blueprint. Otherwise the app's
|
|
||||||
:meth:`~flask.Flask.ensure_sync` is used.
|
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
|
||||||
"""
|
|
||||||
return f
|
|
||||||
|
|
|
||||||
|
|
@ -202,6 +202,31 @@ class Config(dict):
|
||||||
|
|
||||||
return self.from_mapping(obj)
|
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(
|
def from_mapping(
|
||||||
self, mapping: t.Optional[t.Mapping[str, t.Any]] = None, **kwargs: t.Any
|
self, mapping: t.Optional[t.Mapping[str, t.Any]] = None, **kwargs: t.Any
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,24 @@ class _AppCtxGlobals:
|
||||||
.. versionadded:: 0.10
|
.. versionadded:: 0.10
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Define attr methods to let mypy know this is a namespace object
|
||||||
|
# that has arbitrary attributes.
|
||||||
|
|
||||||
|
def __getattr__(self, name: str) -> t.Any:
|
||||||
|
try:
|
||||||
|
return self.__dict__[name]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(name) from None
|
||||||
|
|
||||||
|
def __setattr__(self, name: str, value: t.Any) -> None:
|
||||||
|
self.__dict__[name] = value
|
||||||
|
|
||||||
|
def __delattr__(self, name: str) -> None:
|
||||||
|
try:
|
||||||
|
del self.__dict__[name]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(name) from None
|
||||||
|
|
||||||
def get(self, name: str, default: t.Optional[t.Any] = None) -> t.Any:
|
def get(self, name: str, default: t.Optional[t.Any] = None) -> t.Any:
|
||||||
"""Get an attribute by name, or a default value. Like
|
"""Get an attribute by name, or a default value. Like
|
||||||
:meth:`dict.get`.
|
:meth:`dict.get`.
|
||||||
|
|
@ -78,10 +96,10 @@ class _AppCtxGlobals:
|
||||||
"""
|
"""
|
||||||
return self.__dict__.setdefault(name, default)
|
return self.__dict__.setdefault(name, default)
|
||||||
|
|
||||||
def __contains__(self, item: t.Any) -> bool:
|
def __contains__(self, item: str) -> bool:
|
||||||
return item in self.__dict__
|
return item in self.__dict__
|
||||||
|
|
||||||
def __iter__(self) -> t.Iterator:
|
def __iter__(self) -> t.Iterator[str]:
|
||||||
return iter(self.__dict__)
|
return iter(self.__dict__)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
|
|
@ -377,9 +395,6 @@ class RequestContext:
|
||||||
|
|
||||||
_request_ctx_stack.push(self)
|
_request_ctx_stack.push(self)
|
||||||
|
|
||||||
if self.url_adapter is not None:
|
|
||||||
self.match_request()
|
|
||||||
|
|
||||||
# Open the session at the moment that the request context is available.
|
# Open the session at the moment that the request context is available.
|
||||||
# This allows a custom open_session method to use the request context.
|
# This allows a custom open_session method to use the request context.
|
||||||
# Only open a new session if this is the first time the request was
|
# Only open a new session if this is the first time the request was
|
||||||
|
|
@ -391,6 +406,11 @@ class RequestContext:
|
||||||
if self.session is None:
|
if self.session is None:
|
||||||
self.session = session_interface.make_null_session(self.app)
|
self.session = session_interface.make_null_session(self.app)
|
||||||
|
|
||||||
|
# Match the request URL after loading the session, so that the
|
||||||
|
# session is available in custom URL converters.
|
||||||
|
if self.url_adapter is not None:
|
||||||
|
self.match_request()
|
||||||
|
|
||||||
def pop(self, exc: t.Optional[BaseException] = _sentinel) -> None: # type: ignore
|
def pop(self, exc: t.Optional[BaseException] = _sentinel) -> None: # type: ignore
|
||||||
"""Pops the request context and unbinds it by doing that. This will
|
"""Pops the request context and unbinds it by doing that. This will
|
||||||
also trigger the execution of functions registered by the
|
also trigger the execution of functions registered by the
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from werkzeug.local import LocalStack
|
||||||
|
|
||||||
if t.TYPE_CHECKING:
|
if t.TYPE_CHECKING:
|
||||||
from .app import Flask
|
from .app import Flask
|
||||||
from .ctx import AppContext
|
from .ctx import _AppCtxGlobals
|
||||||
from .sessions import SessionMixin
|
from .sessions import SessionMixin
|
||||||
from .wrappers import Request
|
from .wrappers import Request
|
||||||
|
|
||||||
|
|
@ -53,5 +53,7 @@ _request_ctx_stack = LocalStack()
|
||||||
_app_ctx_stack = LocalStack()
|
_app_ctx_stack = LocalStack()
|
||||||
current_app: "Flask" = LocalProxy(_find_app) # type: ignore
|
current_app: "Flask" = LocalProxy(_find_app) # type: ignore
|
||||||
request: "Request" = LocalProxy(partial(_lookup_req_object, "request")) # type: ignore
|
request: "Request" = LocalProxy(partial(_lookup_req_object, "request")) # type: ignore
|
||||||
session: "SessionMixin" = LocalProxy(partial(_lookup_req_object, "session")) # type: ignore # noqa: B950
|
session: "SessionMixin" = LocalProxy( # type: ignore
|
||||||
g: "AppContext" = LocalProxy(partial(_lookup_app_object, "g")) # type: ignore
|
partial(_lookup_req_object, "session")
|
||||||
|
)
|
||||||
|
g: "_AppCtxGlobals" = LocalProxy(partial(_lookup_app_object, "g")) # type: ignore
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,14 @@ import socket
|
||||||
import sys
|
import sys
|
||||||
import typing as t
|
import typing as t
|
||||||
import warnings
|
import warnings
|
||||||
|
from datetime import datetime
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from functools import lru_cache
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
from functools import wraps
|
|
||||||
from threading import RLock
|
from threading import RLock
|
||||||
|
|
||||||
import werkzeug.utils
|
import werkzeug.utils
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
from werkzeug.local import ContextVar
|
|
||||||
from werkzeug.routing import BuildError
|
from werkzeug.routing import BuildError
|
||||||
from werkzeug.urls import url_quote
|
from werkzeug.urls import url_quote
|
||||||
|
|
||||||
|
|
@ -64,8 +64,10 @@ def get_load_dotenv(default: bool = True) -> bool:
|
||||||
|
|
||||||
|
|
||||||
def stream_with_context(
|
def stream_with_context(
|
||||||
generator_or_function: t.Union[t.Generator, t.Callable]
|
generator_or_function: t.Union[
|
||||||
) -> t.Generator:
|
t.Iterator[t.AnyStr], t.Callable[..., t.Iterator[t.AnyStr]]
|
||||||
|
]
|
||||||
|
) -> t.Iterator[t.AnyStr]:
|
||||||
"""Request contexts disappear when the response is started on the server.
|
"""Request contexts disappear when the response is started on the server.
|
||||||
This is done for efficiency reasons and to make it less likely to encounter
|
This is done for efficiency reasons and to make it less likely to encounter
|
||||||
memory leaks with badly written WSGI middlewares. The downside is that if
|
memory leaks with badly written WSGI middlewares. The downside is that if
|
||||||
|
|
@ -438,14 +440,16 @@ def get_flashed_messages(
|
||||||
|
|
||||||
|
|
||||||
def _prepare_send_file_kwargs(
|
def _prepare_send_file_kwargs(
|
||||||
download_name=None,
|
download_name: t.Optional[str] = None,
|
||||||
attachment_filename=None,
|
attachment_filename: t.Optional[str] = None,
|
||||||
etag=None,
|
etag: t.Optional[t.Union[bool, str]] = None,
|
||||||
add_etags=None,
|
add_etags: t.Optional[t.Union[bool]] = None,
|
||||||
max_age=None,
|
max_age: t.Optional[
|
||||||
cache_timeout=None,
|
t.Union[int, t.Callable[[t.Optional[str]], t.Optional[int]]]
|
||||||
**kwargs,
|
] = None,
|
||||||
):
|
cache_timeout: t.Optional[int] = None,
|
||||||
|
**kwargs: t.Any,
|
||||||
|
) -> t.Dict[str, t.Any]:
|
||||||
if attachment_filename is not None:
|
if attachment_filename is not None:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"The 'attachment_filename' parameter has been renamed to"
|
"The 'attachment_filename' parameter has been renamed to"
|
||||||
|
|
@ -484,23 +488,25 @@ def _prepare_send_file_kwargs(
|
||||||
max_age=max_age,
|
max_age=max_age,
|
||||||
use_x_sendfile=current_app.use_x_sendfile,
|
use_x_sendfile=current_app.use_x_sendfile,
|
||||||
response_class=current_app.response_class,
|
response_class=current_app.response_class,
|
||||||
_root_path=current_app.root_path,
|
_root_path=current_app.root_path, # type: ignore
|
||||||
)
|
)
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
def send_file(
|
def send_file(
|
||||||
path_or_file,
|
path_or_file: t.Union[os.PathLike, str, t.BinaryIO],
|
||||||
mimetype=None,
|
mimetype: t.Optional[str] = None,
|
||||||
as_attachment=False,
|
as_attachment: bool = False,
|
||||||
download_name=None,
|
download_name: t.Optional[str] = None,
|
||||||
attachment_filename=None,
|
attachment_filename: t.Optional[str] = None,
|
||||||
conditional=True,
|
conditional: bool = True,
|
||||||
etag=True,
|
etag: t.Union[bool, str] = True,
|
||||||
add_etags=None,
|
add_etags: t.Optional[bool] = None,
|
||||||
last_modified=None,
|
last_modified: t.Optional[t.Union[datetime, int, float]] = None,
|
||||||
max_age=None,
|
max_age: t.Optional[
|
||||||
cache_timeout=None,
|
t.Union[int, t.Callable[[t.Optional[str]], t.Optional[int]]]
|
||||||
|
] = None,
|
||||||
|
cache_timeout: t.Optional[int] = None,
|
||||||
):
|
):
|
||||||
"""Send the contents of a file to the client.
|
"""Send the contents of a file to the client.
|
||||||
|
|
||||||
|
|
@ -644,7 +650,12 @@ def safe_join(directory: str, *pathnames: str) -> str:
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
def send_from_directory(directory: str, path: str, **kwargs: t.Any) -> "Response":
|
def send_from_directory(
|
||||||
|
directory: t.Union[os.PathLike, str],
|
||||||
|
path: t.Union[os.PathLike, str],
|
||||||
|
filename: t.Optional[str] = None,
|
||||||
|
**kwargs: t.Any,
|
||||||
|
) -> "Response":
|
||||||
"""Send a file from within a directory using :func:`send_file`.
|
"""Send a file from within a directory using :func:`send_file`.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
@ -668,12 +679,24 @@ def send_from_directory(directory: str, path: str, **kwargs: t.Any) -> "Response
|
||||||
``directory``.
|
``directory``.
|
||||||
:param kwargs: Arguments to pass to :func:`send_file`.
|
:param kwargs: Arguments to pass to :func:`send_file`.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0
|
||||||
|
``path`` replaces the ``filename`` parameter.
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
Moved the implementation to Werkzeug. This is now a wrapper to
|
Moved the implementation to Werkzeug. This is now a wrapper to
|
||||||
pass some Flask-specific arguments.
|
pass some Flask-specific arguments.
|
||||||
|
|
||||||
.. versionadded:: 0.5
|
.. versionadded:: 0.5
|
||||||
"""
|
"""
|
||||||
|
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.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
path = filename
|
||||||
|
|
||||||
return werkzeug.utils.send_from_directory( # type: ignore
|
return werkzeug.utils.send_from_directory( # type: ignore
|
||||||
directory, path, **_prepare_send_file_kwargs(**kwargs)
|
directory, path, **_prepare_send_file_kwargs(**kwargs)
|
||||||
)
|
)
|
||||||
|
|
@ -803,49 +826,11 @@ def is_ip(value: str) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def run_async(func: t.Callable[..., t.Coroutine]) -> t.Callable[..., t.Any]:
|
@lru_cache(maxsize=None)
|
||||||
"""Return a sync function that will run the coroutine function *func*."""
|
def _split_blueprint_path(name: str) -> t.List[str]:
|
||||||
try:
|
out: t.List[str] = [name]
|
||||||
from asgiref.sync import async_to_sync
|
|
||||||
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 "." in name:
|
||||||
if ContextVar.__module__ == "werkzeug.local":
|
out.extend(_split_blueprint_path(name.rpartition(".")[0]))
|
||||||
raise RuntimeError(
|
|
||||||
"Async cannot be used with this combination of Python & Greenlet versions."
|
|
||||||
)
|
|
||||||
|
|
||||||
@wraps(func)
|
return out
|
||||||
def outer(*args: t.Any, **kwargs: t.Any) -> t.Any:
|
|
||||||
"""This function grabs the current context for the inner function.
|
|
||||||
|
|
||||||
This is similar to the copy_current_xxx_context functions in the
|
|
||||||
ctx module, except it has an async inner.
|
|
||||||
"""
|
|
||||||
ctx = None
|
|
||||||
|
|
||||||
if _request_ctx_stack.top is not None:
|
|
||||||
ctx = _request_ctx_stack.top.copy()
|
|
||||||
|
|
||||||
@wraps(func)
|
|
||||||
async def inner(*a: t.Any, **k: t.Any) -> t.Any:
|
|
||||||
"""This restores the context before awaiting the func.
|
|
||||||
|
|
||||||
This is required as the function must be awaited within the
|
|
||||||
context. Only calling ``func`` (as per the
|
|
||||||
``copy_current_xxx_context`` functions) doesn't work as the
|
|
||||||
with block will close before the coroutine is awaited.
|
|
||||||
"""
|
|
||||||
if ctx is not None:
|
|
||||||
with ctx:
|
|
||||||
return await func(*a, **k)
|
|
||||||
else:
|
|
||||||
return await func(*a, **k)
|
|
||||||
|
|
||||||
return async_to_sync(inner)(*args, **kwargs)
|
|
||||||
|
|
||||||
outer._flask_sync_wrapper = True # type: ignore
|
|
||||||
return outer
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import uuid
|
||||||
import warnings
|
import warnings
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
from jinja2.utils import htmlsafe_json_dumps as _jinja_htmlsafe_dumps # type: ignore
|
from jinja2.utils import htmlsafe_json_dumps as _jinja_htmlsafe_dumps
|
||||||
from werkzeug.http import http_date
|
from werkzeug.http import http_date
|
||||||
|
|
||||||
from ..globals import current_app
|
from ..globals import current_app
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ from .templating import _default_template_ctx_processor
|
||||||
from .typing import AfterRequestCallable
|
from .typing import AfterRequestCallable
|
||||||
from .typing import AppOrBlueprintKey
|
from .typing import AppOrBlueprintKey
|
||||||
from .typing import BeforeRequestCallable
|
from .typing import BeforeRequestCallable
|
||||||
from .typing import ErrorHandlerCallable
|
from .typing import GenericException
|
||||||
from .typing import TeardownCallable
|
from .typing import TeardownCallable
|
||||||
from .typing import TemplateContextProcessorCallable
|
from .typing import TemplateContextProcessorCallable
|
||||||
from .typing import URLDefaultCallable
|
from .typing import URLDefaultCallable
|
||||||
|
|
@ -29,12 +29,15 @@ from .typing import URLValuePreprocessorCallable
|
||||||
|
|
||||||
if t.TYPE_CHECKING:
|
if t.TYPE_CHECKING:
|
||||||
from .wrappers import Response
|
from .wrappers import Response
|
||||||
|
from .typing import ErrorHandlerCallable
|
||||||
|
|
||||||
# a singleton sentinel value for parameter defaults
|
# a singleton sentinel value for parameter defaults
|
||||||
_sentinel = object()
|
_sentinel = object()
|
||||||
|
|
||||||
|
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
||||||
|
|
||||||
def setupmethod(f: t.Callable) -> t.Callable:
|
|
||||||
|
def setupmethod(f: F) -> F:
|
||||||
"""Wraps a method so that it performs a check in debug mode if the
|
"""Wraps a method so that it performs a check in debug mode if the
|
||||||
first request was already handled.
|
first request was already handled.
|
||||||
"""
|
"""
|
||||||
|
|
@ -53,7 +56,7 @@ def setupmethod(f: t.Callable) -> t.Callable:
|
||||||
)
|
)
|
||||||
return f(self, *args, **kwargs)
|
return f(self, *args, **kwargs)
|
||||||
|
|
||||||
return update_wrapper(wrapper_func, f)
|
return t.cast(F, update_wrapper(wrapper_func, f))
|
||||||
|
|
||||||
|
|
||||||
class Scaffold:
|
class Scaffold:
|
||||||
|
|
@ -142,7 +145,10 @@ class Scaffold:
|
||||||
#: directly and its format may change at any time.
|
#: directly and its format may change at any time.
|
||||||
self.error_handler_spec: t.Dict[
|
self.error_handler_spec: t.Dict[
|
||||||
AppOrBlueprintKey,
|
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))
|
] = defaultdict(lambda: defaultdict(dict))
|
||||||
|
|
||||||
#: A data structure of functions to call at the beginning of
|
#: A data structure of functions to call at the beginning of
|
||||||
|
|
@ -288,7 +294,7 @@ class Scaffold:
|
||||||
|
|
||||||
self._static_url_path = value
|
self._static_url_path = value
|
||||||
|
|
||||||
def get_send_file_max_age(self, filename: str) -> t.Optional[int]:
|
def get_send_file_max_age(self, filename: t.Optional[str]) -> t.Optional[int]:
|
||||||
"""Used by :func:`send_file` to determine the ``max_age`` cache
|
"""Used by :func:`send_file` to determine the ``max_age`` cache
|
||||||
value for a given file path if it wasn't passed.
|
value for a given file path if it wasn't passed.
|
||||||
|
|
||||||
|
|
@ -446,7 +452,7 @@ class Scaffold:
|
||||||
view_func: t.Optional[t.Callable] = None,
|
view_func: t.Optional[t.Callable] = None,
|
||||||
provide_automatic_options: t.Optional[bool] = None,
|
provide_automatic_options: t.Optional[bool] = None,
|
||||||
**options: t.Any,
|
**options: t.Any,
|
||||||
) -> t.Callable:
|
) -> None:
|
||||||
"""Register a rule for routing incoming requests and building
|
"""Register a rule for routing incoming requests and building
|
||||||
URLs. The :meth:`route` decorator is a shortcut to call this
|
URLs. The :meth:`route` decorator is a shortcut to call this
|
||||||
with the ``view_func`` argument. These are equivalent:
|
with the ``view_func`` argument. These are equivalent:
|
||||||
|
|
@ -524,7 +530,7 @@ class Scaffold:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(f):
|
def decorator(f):
|
||||||
self.view_functions[endpoint] = self.ensure_sync(f)
|
self.view_functions[endpoint] = f
|
||||||
return f
|
return f
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
@ -548,7 +554,7 @@ class Scaffold:
|
||||||
return value from the view, and further request handling is
|
return value from the view, and further request handling is
|
||||||
stopped.
|
stopped.
|
||||||
"""
|
"""
|
||||||
self.before_request_funcs.setdefault(None, []).append(self.ensure_sync(f))
|
self.before_request_funcs.setdefault(None, []).append(f)
|
||||||
return f
|
return f
|
||||||
|
|
||||||
@setupmethod
|
@setupmethod
|
||||||
|
|
@ -564,7 +570,7 @@ class Scaffold:
|
||||||
should not be used for actions that must execute, such as to
|
should not be used for actions that must execute, such as to
|
||||||
close resources. Use :meth:`teardown_request` for that.
|
close resources. Use :meth:`teardown_request` for that.
|
||||||
"""
|
"""
|
||||||
self.after_request_funcs.setdefault(None, []).append(self.ensure_sync(f))
|
self.after_request_funcs.setdefault(None, []).append(f)
|
||||||
return f
|
return f
|
||||||
|
|
||||||
@setupmethod
|
@setupmethod
|
||||||
|
|
@ -603,7 +609,7 @@ class Scaffold:
|
||||||
debugger can still access it. This behavior can be controlled
|
debugger can still access it. This behavior can be controlled
|
||||||
by the ``PRESERVE_CONTEXT_ON_EXCEPTION`` configuration variable.
|
by the ``PRESERVE_CONTEXT_ON_EXCEPTION`` configuration variable.
|
||||||
"""
|
"""
|
||||||
self.teardown_request_funcs.setdefault(None, []).append(self.ensure_sync(f))
|
self.teardown_request_funcs.setdefault(None, []).append(f)
|
||||||
return f
|
return f
|
||||||
|
|
||||||
@setupmethod
|
@setupmethod
|
||||||
|
|
@ -644,8 +650,11 @@ class Scaffold:
|
||||||
|
|
||||||
@setupmethod
|
@setupmethod
|
||||||
def errorhandler(
|
def errorhandler(
|
||||||
self, code_or_exception: t.Union[t.Type[Exception], int]
|
self, code_or_exception: t.Union[t.Type[GenericException], int]
|
||||||
) -> t.Callable:
|
) -> t.Callable[
|
||||||
|
["ErrorHandlerCallable[GenericException]"],
|
||||||
|
"ErrorHandlerCallable[GenericException]",
|
||||||
|
]:
|
||||||
"""Register a function to handle errors by code or exception class.
|
"""Register a function to handle errors by code or exception class.
|
||||||
|
|
||||||
A decorator that is used to register a function given an
|
A decorator that is used to register a function given an
|
||||||
|
|
@ -675,7 +684,9 @@ class Scaffold:
|
||||||
an arbitrary exception
|
an arbitrary exception
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(f: ErrorHandlerCallable) -> ErrorHandlerCallable:
|
def decorator(
|
||||||
|
f: "ErrorHandlerCallable[GenericException]",
|
||||||
|
) -> "ErrorHandlerCallable[GenericException]":
|
||||||
self.register_error_handler(code_or_exception, f)
|
self.register_error_handler(code_or_exception, f)
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
@ -684,8 +695,8 @@ class Scaffold:
|
||||||
@setupmethod
|
@setupmethod
|
||||||
def register_error_handler(
|
def register_error_handler(
|
||||||
self,
|
self,
|
||||||
code_or_exception: t.Union[t.Type[Exception], int],
|
code_or_exception: t.Union[t.Type[GenericException], int],
|
||||||
f: ErrorHandlerCallable,
|
f: "ErrorHandlerCallable[GenericException]",
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Alternative error attach function to the :meth:`errorhandler`
|
"""Alternative error attach function to the :meth:`errorhandler`
|
||||||
decorator that is more straightforward to use for non decorator
|
decorator that is more straightforward to use for non decorator
|
||||||
|
|
@ -709,7 +720,9 @@ class Scaffold:
|
||||||
" instead."
|
" instead."
|
||||||
)
|
)
|
||||||
|
|
||||||
self.error_handler_spec[None][code][exc_class] = self.ensure_sync(f)
|
self.error_handler_spec[None][code][exc_class] = t.cast(
|
||||||
|
"ErrorHandlerCallable[Exception]", f
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_exc_class_and_code(
|
def _get_exc_class_and_code(
|
||||||
|
|
@ -737,9 +750,6 @@ class Scaffold:
|
||||||
else:
|
else:
|
||||||
return exc_class, None
|
return exc_class, None
|
||||||
|
|
||||||
def ensure_sync(self, func: t.Callable) -> t.Callable:
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
|
|
||||||
def _endpoint_from_view_func(view_func: t.Callable) -> str:
|
def _endpoint_from_view_func(view_func: t.Callable) -> str:
|
||||||
"""Internal helper that returns the default endpoint for a given
|
"""Internal helper that returns the default endpoint for a given
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ from .helpers import is_ip
|
||||||
from .json.tag import TaggedJSONSerializer
|
from .json.tag import TaggedJSONSerializer
|
||||||
|
|
||||||
if t.TYPE_CHECKING:
|
if t.TYPE_CHECKING:
|
||||||
|
import typing_extensions as te
|
||||||
from .app import Flask
|
from .app import Flask
|
||||||
from .wrappers import Request, Response
|
from .wrappers import Request, Response
|
||||||
|
|
||||||
|
|
@ -92,7 +93,7 @@ class NullSession(SecureCookieSession):
|
||||||
but fail on setting.
|
but fail on setting.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _fail(self, *args: t.Any, **kwargs: t.Any) -> t.NoReturn:
|
def _fail(self, *args: t.Any, **kwargs: t.Any) -> "te.NoReturn":
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"The session is unavailable because no secret "
|
"The session is unavailable because no secret "
|
||||||
"key was set. Set the secret_key on the "
|
"key was set. Set the secret_key on the "
|
||||||
|
|
|
||||||
|
|
@ -51,18 +51,21 @@ class DispatchingJinjaLoader(BaseLoader):
|
||||||
def __init__(self, app: "Flask") -> None:
|
def __init__(self, app: "Flask") -> None:
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
def get_source(
|
def get_source( # type: ignore
|
||||||
self, environment: Environment, template: str
|
self, environment: Environment, template: str
|
||||||
) -> t.Tuple[str, t.Optional[str], t.Callable]:
|
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable]]:
|
||||||
if self.app.config["EXPLAIN_TEMPLATE_LOADING"]:
|
if self.app.config["EXPLAIN_TEMPLATE_LOADING"]:
|
||||||
return self._get_source_explained(environment, template)
|
return self._get_source_explained(environment, template)
|
||||||
return self._get_source_fast(environment, template)
|
return self._get_source_fast(environment, template)
|
||||||
|
|
||||||
def _get_source_explained(
|
def _get_source_explained(
|
||||||
self, environment: Environment, template: str
|
self, environment: Environment, template: str
|
||||||
) -> t.Tuple[str, t.Optional[str], t.Callable]:
|
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable]]:
|
||||||
attempts = []
|
attempts = []
|
||||||
trv = None
|
rv: t.Optional[t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]]
|
||||||
|
trv: t.Optional[
|
||||||
|
t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]
|
||||||
|
] = None
|
||||||
|
|
||||||
for srcobj, loader in self._iter_loaders(template):
|
for srcobj, loader in self._iter_loaders(template):
|
||||||
try:
|
try:
|
||||||
|
|
@ -83,7 +86,7 @@ class DispatchingJinjaLoader(BaseLoader):
|
||||||
|
|
||||||
def _get_source_fast(
|
def _get_source_fast(
|
||||||
self, environment: Environment, template: str
|
self, environment: Environment, template: str
|
||||||
) -> t.Tuple[str, t.Optional[str], t.Callable]:
|
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable]]:
|
||||||
for _srcobj, loader in self._iter_loaders(template):
|
for _srcobj, loader in self._iter_loaders(template):
|
||||||
try:
|
try:
|
||||||
return loader.get_source(environment, template)
|
return loader.get_source(environment, template)
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import typing as t
|
||||||
|
|
||||||
|
|
||||||
if t.TYPE_CHECKING:
|
if t.TYPE_CHECKING:
|
||||||
|
from _typeshed.wsgi import WSGIApplication # noqa: F401
|
||||||
from werkzeug.datastructures import Headers # noqa: F401
|
from werkzeug.datastructures import Headers # noqa: F401
|
||||||
from wsgiref.types import WSGIApplication # noqa: F401
|
|
||||||
from .wrappers import Response # noqa: F401
|
from .wrappers import Response # noqa: F401
|
||||||
from .views import View # noqa: F401
|
from .views import View # noqa: F401
|
||||||
|
|
||||||
|
|
@ -34,15 +34,25 @@ ResponseReturnValue = t.Union[
|
||||||
"WSGIApplication",
|
"WSGIApplication",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
GenericException = t.TypeVar("GenericException", bound=Exception, contravariant=True)
|
||||||
|
|
||||||
AppOrBlueprintKey = t.Optional[str] # The App key is None, whereas blueprints are named
|
AppOrBlueprintKey = t.Optional[str] # The App key is None, whereas blueprints are named
|
||||||
AfterRequestCallable = t.Callable[["Response"], "Response"]
|
AfterRequestCallable = t.Callable[["Response"], "Response"]
|
||||||
BeforeRequestCallable = t.Callable[[], None]
|
BeforeFirstRequestCallable = t.Callable[[], None]
|
||||||
ErrorHandlerCallable = t.Callable[[Exception], ResponseReturnValue]
|
BeforeRequestCallable = t.Callable[[], t.Optional[ResponseReturnValue]]
|
||||||
TeardownCallable = t.Callable[[t.Optional[BaseException]], "Response"]
|
TeardownCallable = t.Callable[[t.Optional[BaseException]], None]
|
||||||
TemplateContextProcessorCallable = t.Callable[[], t.Dict[str, t.Any]]
|
TemplateContextProcessorCallable = t.Callable[[], t.Dict[str, t.Any]]
|
||||||
TemplateFilterCallable = t.Callable[[t.Any], str]
|
TemplateFilterCallable = t.Callable[..., t.Any]
|
||||||
TemplateGlobalCallable = t.Callable[[], t.Any]
|
TemplateGlobalCallable = t.Callable[..., t.Any]
|
||||||
TemplateTestCallable = t.Callable[[t.Any], bool]
|
TemplateTestCallable = t.Callable[..., bool]
|
||||||
URLDefaultCallable = t.Callable[[str, dict], None]
|
URLDefaultCallable = t.Callable[[str, dict], None]
|
||||||
URLValuePreprocessorCallable = t.Callable[[t.Optional[str], t.Optional[dict]], None]
|
URLValuePreprocessorCallable = t.Callable[[t.Optional[str], t.Optional[dict]], None]
|
||||||
ViewFuncArgument = t.Optional[t.Union[t.Callable, t.Type["View"]]]
|
ViewFuncArgument = t.Optional[t.Union[t.Callable, t.Type["View"]]]
|
||||||
|
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
import typing_extensions as te
|
||||||
|
|
||||||
|
class ErrorHandlerCallable(te.Protocol[GenericException]):
|
||||||
|
def __call__(self, error: GenericException) -> ResponseReturnValue:
|
||||||
|
...
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
|
from .globals import current_app
|
||||||
from .globals import request
|
from .globals import request
|
||||||
from .typing import ResponseReturnValue
|
from .typing import ResponseReturnValue
|
||||||
|
|
||||||
|
|
@ -80,7 +81,7 @@ class View:
|
||||||
|
|
||||||
def view(*args: t.Any, **kwargs: t.Any) -> ResponseReturnValue:
|
def view(*args: t.Any, **kwargs: t.Any) -> ResponseReturnValue:
|
||||||
self = view.view_class(*class_args, **class_kwargs) # type: ignore
|
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:
|
if cls.decorators:
|
||||||
view.__name__ = name
|
view.__name__ = name
|
||||||
|
|
@ -154,4 +155,4 @@ class MethodView(View, metaclass=MethodViewType):
|
||||||
meth = getattr(self, "get", None)
|
meth = getattr(self, "get", None)
|
||||||
|
|
||||||
assert meth is not None, f"Unimplemented method {request.method!r}"
|
assert meth is not None, f"Unimplemented method {request.method!r}"
|
||||||
return meth(*args, **kwargs)
|
return current_app.ensure_sync(meth)(*args, **kwargs)
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,10 @@ from werkzeug.wrappers import Response as ResponseBase
|
||||||
|
|
||||||
from . import json
|
from . import json
|
||||||
from .globals import current_app
|
from .globals import current_app
|
||||||
|
from .helpers import _split_blueprint_path
|
||||||
|
|
||||||
if t.TYPE_CHECKING:
|
if t.TYPE_CHECKING:
|
||||||
|
import typing_extensions as te
|
||||||
from werkzeug.routing import Rule
|
from werkzeug.routing import Rule
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -58,23 +60,54 @@ class Request(RequestBase):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def endpoint(self) -> t.Optional[str]:
|
def endpoint(self) -> t.Optional[str]:
|
||||||
"""The endpoint that matched the request. This in combination with
|
"""The endpoint that matched the request URL.
|
||||||
:attr:`view_args` can be used to reconstruct the same or a
|
|
||||||
modified URL. If an exception happened when matching, this will
|
This will be ``None`` if matching failed or has not been
|
||||||
be ``None``.
|
performed yet.
|
||||||
|
|
||||||
|
This in combination with :attr:`view_args` can be used to
|
||||||
|
reconstruct the same URL or a modified URL.
|
||||||
"""
|
"""
|
||||||
if self.url_rule is not None:
|
if self.url_rule is not None:
|
||||||
return self.url_rule.endpoint
|
return self.url_rule.endpoint
|
||||||
else:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def blueprint(self) -> t.Optional[str]:
|
def blueprint(self) -> t.Optional[str]:
|
||||||
"""The name of the current blueprint"""
|
"""The registered name of the current blueprint.
|
||||||
if self.url_rule and "." in self.url_rule.endpoint:
|
|
||||||
return self.url_rule.endpoint.rsplit(".", 1)[0]
|
This will be ``None`` if the endpoint is not part of a
|
||||||
else:
|
blueprint, or if URL matching failed or has not been performed
|
||||||
return None
|
yet.
|
||||||
|
|
||||||
|
This does not necessarily match the name the blueprint was
|
||||||
|
created with. It may have been nested, or registered with a
|
||||||
|
different name.
|
||||||
|
"""
|
||||||
|
endpoint = self.endpoint
|
||||||
|
|
||||||
|
if endpoint is not None and "." in endpoint:
|
||||||
|
return endpoint.rpartition(".")[0]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def blueprints(self) -> t.List[str]:
|
||||||
|
"""The registered names of the current blueprint upwards through
|
||||||
|
parent blueprints.
|
||||||
|
|
||||||
|
This will be an empty list if there is no current blueprint, or
|
||||||
|
if URL matching failed.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0.1
|
||||||
|
"""
|
||||||
|
name = self.blueprint
|
||||||
|
|
||||||
|
if name is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return _split_blueprint_path(name)
|
||||||
|
|
||||||
def _load_form_data(self) -> None:
|
def _load_form_data(self) -> None:
|
||||||
RequestBase._load_form_data(self)
|
RequestBase._load_form_data(self)
|
||||||
|
|
@ -91,7 +124,7 @@ class Request(RequestBase):
|
||||||
|
|
||||||
attach_enctype_error_multidict(self)
|
attach_enctype_error_multidict(self)
|
||||||
|
|
||||||
def on_json_loading_failed(self, e: Exception) -> t.NoReturn:
|
def on_json_loading_failed(self, e: Exception) -> "te.NoReturn":
|
||||||
if current_app and current_app.debug:
|
if current_app and current_app.debug:
|
||||||
raise BadRequest(f"Failed to decode JSON object: {e}")
|
raise BadRequest(f"Failed to decode JSON object: {e}")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@ import pytest
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask.helpers import run_async
|
from flask.views import MethodView
|
||||||
|
from flask.views import View
|
||||||
|
|
||||||
pytest.importorskip("asgiref")
|
pytest.importorskip("asgiref")
|
||||||
|
|
||||||
|
|
@ -19,6 +20,24 @@ class BlueprintError(Exception):
|
||||||
pass
|
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")
|
@pytest.fixture(name="async_app")
|
||||||
def _async_app():
|
def _async_app():
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
@ -54,11 +73,14 @@ def _async_app():
|
||||||
|
|
||||||
app.register_blueprint(blueprint, url_prefix="/bp")
|
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
|
return app
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires Python >= 3.7")
|
@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):
|
def test_async_route(path, async_app):
|
||||||
test_client = async_app.test_client()
|
test_client = async_app.test_client()
|
||||||
response = test_client.get(path)
|
response = test_client.get(path)
|
||||||
|
|
@ -136,5 +158,6 @@ def test_async_before_after_request():
|
||||||
|
|
||||||
@pytest.mark.skipif(sys.version_info >= (3, 7), reason="should only raise Python < 3.7")
|
@pytest.mark.skipif(sys.version_info >= (3, 7), reason="should only raise Python < 3.7")
|
||||||
def test_async_runtime_error():
|
def test_async_runtime_error():
|
||||||
|
app = Flask(__name__)
|
||||||
with pytest.raises(RuntimeError):
|
with pytest.raises(RuntimeError):
|
||||||
run_async(None)
|
app.async_to_sync(None)
|
||||||
|
|
|
||||||
|
|
@ -1448,7 +1448,6 @@ def test_static_url_empty_path_default(app):
|
||||||
rv.close()
|
rv.close()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires Python >= 3.6")
|
|
||||||
def test_static_folder_with_pathlib_path(app):
|
def test_static_folder_with_pathlib_path(app):
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
@ -1631,7 +1630,7 @@ def test_url_processors(app, client):
|
||||||
|
|
||||||
|
|
||||||
def test_inject_blueprint_url_defaults(app):
|
def test_inject_blueprint_url_defaults(app):
|
||||||
bp = flask.Blueprint("foo.bar.baz", __name__, template_folder="template")
|
bp = flask.Blueprint("foo", __name__, template_folder="template")
|
||||||
|
|
||||||
@bp.url_defaults
|
@bp.url_defaults
|
||||||
def bp_defaults(endpoint, values):
|
def bp_defaults(endpoint, values):
|
||||||
|
|
@ -1644,12 +1643,12 @@ def test_inject_blueprint_url_defaults(app):
|
||||||
app.register_blueprint(bp)
|
app.register_blueprint(bp)
|
||||||
|
|
||||||
values = dict()
|
values = dict()
|
||||||
app.inject_url_defaults("foo.bar.baz.view", values)
|
app.inject_url_defaults("foo.view", values)
|
||||||
expected = dict(page="login")
|
expected = dict(page="login")
|
||||||
assert values == expected
|
assert values == expected
|
||||||
|
|
||||||
with app.test_request_context("/somepage"):
|
with app.test_request_context("/somepage"):
|
||||||
url = flask.url_for("foo.bar.baz.view")
|
url = flask.url_for("foo.view")
|
||||||
expected = "/login"
|
expected = "/login"
|
||||||
assert url == expected
|
assert url == expected
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import functools
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from jinja2 import TemplateNotFound
|
from jinja2 import TemplateNotFound
|
||||||
from werkzeug.http import parse_cache_control_header
|
from werkzeug.http import parse_cache_control_header
|
||||||
|
|
@ -142,7 +140,7 @@ def test_blueprint_url_defaults(app, client):
|
||||||
return str(bar)
|
return str(bar)
|
||||||
|
|
||||||
app.register_blueprint(bp, url_prefix="/1", url_defaults={"bar": 23})
|
app.register_blueprint(bp, url_prefix="/1", url_defaults={"bar": 23})
|
||||||
app.register_blueprint(bp, url_prefix="/2", url_defaults={"bar": 19})
|
app.register_blueprint(bp, name="test2", url_prefix="/2", url_defaults={"bar": 19})
|
||||||
|
|
||||||
assert client.get("/1/foo").data == b"23/42"
|
assert client.get("/1/foo").data == b"23/42"
|
||||||
assert client.get("/2/foo").data == b"19/42"
|
assert client.get("/2/foo").data == b"19/42"
|
||||||
|
|
@ -253,28 +251,9 @@ def test_templates_list(test_apps):
|
||||||
assert templates == ["admin/index.html", "frontend/index.html"]
|
assert templates == ["admin/index.html", "frontend/index.html"]
|
||||||
|
|
||||||
|
|
||||||
def test_dotted_names(app, client):
|
def test_dotted_name_not_allowed(app, client):
|
||||||
frontend = flask.Blueprint("myapp.frontend", __name__)
|
with pytest.raises(ValueError):
|
||||||
backend = flask.Blueprint("myapp.backend", __name__)
|
flask.Blueprint("app.ui", __name__)
|
||||||
|
|
||||||
@frontend.route("/fe")
|
|
||||||
def frontend_index():
|
|
||||||
return flask.url_for("myapp.backend.backend_index")
|
|
||||||
|
|
||||||
@frontend.route("/fe2")
|
|
||||||
def frontend_page2():
|
|
||||||
return flask.url_for(".frontend_index")
|
|
||||||
|
|
||||||
@backend.route("/be")
|
|
||||||
def backend_index():
|
|
||||||
return flask.url_for("myapp.frontend.frontend_index")
|
|
||||||
|
|
||||||
app.register_blueprint(frontend)
|
|
||||||
app.register_blueprint(backend)
|
|
||||||
|
|
||||||
assert client.get("/fe").data.strip() == b"/be"
|
|
||||||
assert client.get("/fe2").data.strip() == b"/fe"
|
|
||||||
assert client.get("/be").data.strip() == b"/fe"
|
|
||||||
|
|
||||||
|
|
||||||
def test_dotted_names_from_app(app, client):
|
def test_dotted_names_from_app(app, client):
|
||||||
|
|
@ -343,62 +322,19 @@ def test_route_decorator_custom_endpoint(app, client):
|
||||||
def test_route_decorator_custom_endpoint_with_dots(app, client):
|
def test_route_decorator_custom_endpoint_with_dots(app, client):
|
||||||
bp = flask.Blueprint("bp", __name__)
|
bp = flask.Blueprint("bp", __name__)
|
||||||
|
|
||||||
@bp.route("/foo")
|
with pytest.raises(ValueError):
|
||||||
def foo():
|
bp.route("/", endpoint="a.b")(lambda: "")
|
||||||
return flask.request.endpoint
|
|
||||||
|
|
||||||
try:
|
with pytest.raises(ValueError):
|
||||||
|
bp.add_url_rule("/", endpoint="a.b")
|
||||||
|
|
||||||
@bp.route("/bar", endpoint="bar.bar")
|
def view():
|
||||||
def foo_bar():
|
return ""
|
||||||
return flask.request.endpoint
|
|
||||||
|
|
||||||
except AssertionError:
|
view.__name__ = "a.b"
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise AssertionError("expected AssertionError not raised")
|
|
||||||
|
|
||||||
try:
|
with pytest.raises(ValueError):
|
||||||
|
bp.add_url_rule("/", view_func=view)
|
||||||
@bp.route("/bar/123", endpoint="bar.123")
|
|
||||||
def foo_bar_foo():
|
|
||||||
return flask.request.endpoint
|
|
||||||
|
|
||||||
except AssertionError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise AssertionError("expected AssertionError not raised")
|
|
||||||
|
|
||||||
def foo_foo_foo():
|
|
||||||
pass
|
|
||||||
|
|
||||||
pytest.raises(
|
|
||||||
AssertionError,
|
|
||||||
lambda: bp.add_url_rule("/bar/123", endpoint="bar.123", view_func=foo_foo_foo),
|
|
||||||
)
|
|
||||||
|
|
||||||
pytest.raises(
|
|
||||||
AssertionError, bp.route("/bar/123", endpoint="bar.123"), lambda: None
|
|
||||||
)
|
|
||||||
|
|
||||||
foo_foo_foo.__name__ = "bar.123"
|
|
||||||
|
|
||||||
pytest.raises(
|
|
||||||
AssertionError, lambda: bp.add_url_rule("/bar/123", view_func=foo_foo_foo)
|
|
||||||
)
|
|
||||||
|
|
||||||
bp.add_url_rule(
|
|
||||||
"/bar/456", endpoint="foofoofoo", view_func=functools.partial(foo_foo_foo)
|
|
||||||
)
|
|
||||||
|
|
||||||
app.register_blueprint(bp, url_prefix="/py")
|
|
||||||
|
|
||||||
assert client.get("/py/foo").data == b"bp.foo"
|
|
||||||
# The rule's didn't actually made it through
|
|
||||||
rv = client.get("/py/bar")
|
|
||||||
assert rv.status_code == 404
|
|
||||||
rv = client.get("/py/bar/123")
|
|
||||||
assert rv.status_code == 404
|
|
||||||
|
|
||||||
|
|
||||||
def test_endpoint_decorator(app, client):
|
def test_endpoint_decorator(app, client):
|
||||||
|
|
@ -899,3 +835,89 @@ def test_nested_blueprint(app, client):
|
||||||
assert client.get("/parent/no").data == b"Parent no"
|
assert client.get("/parent/no").data == b"Parent no"
|
||||||
assert client.get("/parent/child/no").data == b"Parent no"
|
assert client.get("/parent/child/no").data == b"Parent no"
|
||||||
assert client.get("/parent/child/grandchild/no").data == b"Grandchild no"
|
assert client.get("/parent/child/grandchild/no").data == b"Grandchild no"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"parent_init, child_init, parent_registration, child_registration",
|
||||||
|
[
|
||||||
|
("/parent", "/child", None, None),
|
||||||
|
("/parent", None, None, "/child"),
|
||||||
|
(None, None, "/parent", "/child"),
|
||||||
|
("/other", "/something", "/parent", "/child"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_nesting_url_prefixes(
|
||||||
|
parent_init,
|
||||||
|
child_init,
|
||||||
|
parent_registration,
|
||||||
|
child_registration,
|
||||||
|
app,
|
||||||
|
client,
|
||||||
|
) -> None:
|
||||||
|
parent = flask.Blueprint("parent", __name__, url_prefix=parent_init)
|
||||||
|
child = flask.Blueprint("child", __name__, url_prefix=child_init)
|
||||||
|
|
||||||
|
@child.route("/")
|
||||||
|
def index():
|
||||||
|
return "index"
|
||||||
|
|
||||||
|
parent.register_blueprint(child, url_prefix=child_registration)
|
||||||
|
app.register_blueprint(parent, url_prefix=parent_registration)
|
||||||
|
|
||||||
|
response = client.get("/parent/child/")
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_unique_blueprint_names(app, client) -> None:
|
||||||
|
bp = flask.Blueprint("bp", __name__)
|
||||||
|
bp2 = flask.Blueprint("bp", __name__)
|
||||||
|
|
||||||
|
app.register_blueprint(bp)
|
||||||
|
|
||||||
|
with pytest.warns(UserWarning):
|
||||||
|
app.register_blueprint(bp) # same bp, same name, warning
|
||||||
|
|
||||||
|
app.register_blueprint(bp, name="again") # same bp, different name, ok
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
app.register_blueprint(bp2) # different bp, same name, error
|
||||||
|
|
||||||
|
app.register_blueprint(bp2, name="alt") # different bp, different name, ok
|
||||||
|
|
||||||
|
|
||||||
|
def test_self_registration(app, client) -> None:
|
||||||
|
bp = flask.Blueprint("bp", __name__)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
bp.register_blueprint(bp)
|
||||||
|
|
||||||
|
|
||||||
|
def test_blueprint_renaming(app, client) -> None:
|
||||||
|
bp = flask.Blueprint("bp", __name__)
|
||||||
|
bp2 = flask.Blueprint("bp2", __name__)
|
||||||
|
|
||||||
|
@bp.get("/")
|
||||||
|
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
|
||||||
|
|
||||||
|
bp.register_blueprint(bp2, url_prefix="/a", name="sub")
|
||||||
|
app.register_blueprint(bp, url_prefix="/a")
|
||||||
|
app.register_blueprint(bp, url_prefix="/b", name="alt")
|
||||||
|
|
||||||
|
assert client.get("/a/").data == b"bp.index"
|
||||||
|
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"
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import ssl
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from pathlib import Path
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
@ -29,8 +30,8 @@ from flask.cli import run_command
|
||||||
from flask.cli import ScriptInfo
|
from flask.cli import ScriptInfo
|
||||||
from flask.cli import with_appcontext
|
from flask.cli import with_appcontext
|
||||||
|
|
||||||
cwd = os.getcwd()
|
cwd = Path.cwd()
|
||||||
test_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "test_apps"))
|
test_path = (Path(__file__) / ".." / "test_apps").resolve()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
@ -152,29 +153,25 @@ def test_find_best_app(test_apps):
|
||||||
(
|
(
|
||||||
("test", cwd, "test"),
|
("test", cwd, "test"),
|
||||||
("test.py", cwd, "test"),
|
("test.py", cwd, "test"),
|
||||||
("a/test", os.path.join(cwd, "a"), "test"),
|
("a/test", cwd / "a", "test"),
|
||||||
("test/__init__.py", cwd, "test"),
|
("test/__init__.py", cwd, "test"),
|
||||||
("test/__init__", cwd, "test"),
|
("test/__init__", cwd, "test"),
|
||||||
# nested package
|
# nested package
|
||||||
(
|
(
|
||||||
os.path.join(test_path, "cliapp", "inner1", "__init__"),
|
test_path / "cliapp" / "inner1" / "__init__",
|
||||||
test_path,
|
test_path,
|
||||||
"cliapp.inner1",
|
"cliapp.inner1",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
os.path.join(test_path, "cliapp", "inner1", "inner2"),
|
test_path / "cliapp" / "inner1" / "inner2",
|
||||||
test_path,
|
test_path,
|
||||||
"cliapp.inner1.inner2",
|
"cliapp.inner1.inner2",
|
||||||
),
|
),
|
||||||
# dotted name
|
# dotted name
|
||||||
("test.a.b", cwd, "test.a.b"),
|
("test.a.b", cwd, "test.a.b"),
|
||||||
(os.path.join(test_path, "cliapp.app"), test_path, "cliapp.app"),
|
(test_path / "cliapp.app", test_path, "cliapp.app"),
|
||||||
# not a Python file, will be caught during import
|
# not a Python file, will be caught during import
|
||||||
(
|
(test_path / "cliapp" / "message.txt", test_path, "cliapp.message.txt"),
|
||||||
os.path.join(test_path, "cliapp", "message.txt"),
|
|
||||||
test_path,
|
|
||||||
"cliapp.message.txt",
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_prepare_import(request, value, path, result):
|
def test_prepare_import(request, value, path, result):
|
||||||
|
|
@ -193,7 +190,7 @@ def test_prepare_import(request, value, path, result):
|
||||||
request.addfinalizer(reset_path)
|
request.addfinalizer(reset_path)
|
||||||
|
|
||||||
assert prepare_import(value) == result
|
assert prepare_import(value) == result
|
||||||
assert sys.path[0] == path
|
assert sys.path[0] == str(path)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
@ -278,9 +275,8 @@ def test_scriptinfo(test_apps, monkeypatch):
|
||||||
assert obj.load_app() is app
|
assert obj.load_app() is app
|
||||||
|
|
||||||
# import app with module's absolute path
|
# import app with module's absolute path
|
||||||
cli_app_path = os.path.abspath(
|
cli_app_path = str(test_path / "cliapp" / "app.py")
|
||||||
os.path.join(os.path.dirname(__file__), "test_apps", "cliapp", "app.py")
|
|
||||||
)
|
|
||||||
obj = ScriptInfo(app_import_path=cli_app_path)
|
obj = ScriptInfo(app_import_path=cli_app_path)
|
||||||
app = obj.load_app()
|
app = obj.load_app()
|
||||||
assert app.name == "testapp"
|
assert app.name == "testapp"
|
||||||
|
|
@ -302,19 +298,13 @@ def test_scriptinfo(test_apps, monkeypatch):
|
||||||
pytest.raises(NoAppException, obj.load_app)
|
pytest.raises(NoAppException, obj.load_app)
|
||||||
|
|
||||||
# import app from wsgi.py in current directory
|
# import app from wsgi.py in current directory
|
||||||
monkeypatch.chdir(
|
monkeypatch.chdir(test_path / "helloworld")
|
||||||
os.path.abspath(
|
|
||||||
os.path.join(os.path.dirname(__file__), "test_apps", "helloworld")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
obj = ScriptInfo()
|
obj = ScriptInfo()
|
||||||
app = obj.load_app()
|
app = obj.load_app()
|
||||||
assert app.name == "hello"
|
assert app.name == "hello"
|
||||||
|
|
||||||
# import app from app.py in current directory
|
# import app from app.py in current directory
|
||||||
monkeypatch.chdir(
|
monkeypatch.chdir(test_path / "cliapp")
|
||||||
os.path.abspath(os.path.join(os.path.dirname(__file__), "test_apps", "cliapp"))
|
|
||||||
)
|
|
||||||
obj = ScriptInfo()
|
obj = ScriptInfo()
|
||||||
app = obj.load_app()
|
app = obj.load_app()
|
||||||
assert app.name == "testapp"
|
assert app.name == "testapp"
|
||||||
|
|
@ -513,7 +503,7 @@ def test_load_dotenv(monkeypatch):
|
||||||
monkeypatch.setenv("EGGS", "3")
|
monkeypatch.setenv("EGGS", "3")
|
||||||
monkeypatch.chdir(test_path)
|
monkeypatch.chdir(test_path)
|
||||||
assert load_dotenv()
|
assert load_dotenv()
|
||||||
assert os.getcwd() == test_path
|
assert Path.cwd() == test_path
|
||||||
# .flaskenv doesn't overwrite .env
|
# .flaskenv doesn't overwrite .env
|
||||||
assert os.environ["FOO"] == "env"
|
assert os.environ["FOO"] == "env"
|
||||||
# set only in .flaskenv
|
# set only in .flaskenv
|
||||||
|
|
@ -533,9 +523,8 @@ def test_dotenv_path(monkeypatch):
|
||||||
for item in ("FOO", "BAR", "EGGS"):
|
for item in ("FOO", "BAR", "EGGS"):
|
||||||
monkeypatch._setitem.append((os.environ, item, notset))
|
monkeypatch._setitem.append((os.environ, item, notset))
|
||||||
|
|
||||||
cwd = os.getcwd()
|
load_dotenv(test_path / ".flaskenv")
|
||||||
load_dotenv(os.path.join(test_path, ".flaskenv"))
|
assert Path.cwd() == cwd
|
||||||
assert os.getcwd() == cwd
|
|
||||||
assert "FOO" in os.environ
|
assert "FOO" in os.environ
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
from werkzeug.routing import BaseConverter
|
from werkzeug.routing import BaseConverter
|
||||||
|
|
||||||
from flask import has_request_context
|
from flask import request
|
||||||
|
from flask import session
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -28,12 +29,13 @@ def test_custom_converters(app, client):
|
||||||
def test_context_available(app, client):
|
def test_context_available(app, client):
|
||||||
class ContextConverter(BaseConverter):
|
class ContextConverter(BaseConverter):
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
assert has_request_context()
|
assert request is not None
|
||||||
|
assert session is not None
|
||||||
return value
|
return value
|
||||||
|
|
||||||
app.url_map.converters["ctx"] = ContextConverter
|
app.url_map.converters["ctx"] = ContextConverter
|
||||||
|
|
||||||
@app.route("/<ctx:name>")
|
@app.get("/<ctx:name>")
|
||||||
def index(name):
|
def index(name):
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,26 @@ import flask
|
||||||
from flask.sessions import SessionInterface
|
from flask.sessions import SessionInterface
|
||||||
|
|
||||||
|
|
||||||
def test_open_session_endpoint_not_none():
|
def test_open_session_with_endpoint():
|
||||||
# Define a session interface that breaks if request.endpoint is None
|
"""If request.endpoint (or other URL matching behavior) is needed
|
||||||
|
while loading the session, RequestContext.match_request() can be
|
||||||
|
called manually.
|
||||||
|
"""
|
||||||
|
|
||||||
class MySessionInterface(SessionInterface):
|
class MySessionInterface(SessionInterface):
|
||||||
def save_session(self):
|
def save_session(self, app, session, response):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def open_session(self, _, request):
|
def open_session(self, app, request):
|
||||||
|
flask._request_ctx_stack.top.match_request()
|
||||||
assert request.endpoint is not None
|
assert request.endpoint is not None
|
||||||
|
|
||||||
def index():
|
|
||||||
return "Hello World!"
|
|
||||||
|
|
||||||
# Confirm a 200 response, indicating that request.endpoint was NOT None
|
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
app.route("/")(index)
|
|
||||||
app.session_interface = MySessionInterface()
|
app.session_interface = MySessionInterface()
|
||||||
response = app.test_client().open("/")
|
|
||||||
|
@app.get("/")
|
||||||
|
def index():
|
||||||
|
return "Hello, World!"
|
||||||
|
|
||||||
|
response = app.test_client().get("/")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
|
||||||
13
tox.ini
13
tox.ini
|
|
@ -11,12 +11,6 @@ skip_missing_interpreters = true
|
||||||
deps =
|
deps =
|
||||||
-r requirements/tests.txt
|
-r requirements/tests.txt
|
||||||
|
|
||||||
https://github.com/pallets/werkzeug/archive/master.tar.gz
|
|
||||||
https://github.com/pallets/markupsafe/archive/master.tar.gz
|
|
||||||
https://github.com/pallets/jinja/archive/master.tar.gz
|
|
||||||
https://github.com/pallets/itsdangerous/archive/master.tar.gz
|
|
||||||
|
|
||||||
!click7: https://github.com/pallets/click/archive/master.tar.gz
|
|
||||||
click7: click<8
|
click7: click<8
|
||||||
|
|
||||||
examples/tutorial[test]
|
examples/tutorial[test]
|
||||||
|
|
@ -30,11 +24,8 @@ commands = pre-commit run --all-files --show-diff-on-failure
|
||||||
|
|
||||||
[testenv:typing]
|
[testenv:typing]
|
||||||
deps = -r requirements/typing.txt
|
deps = -r requirements/typing.txt
|
||||||
commands = mypy
|
commands = mypy --install-types --non-interactive
|
||||||
|
|
||||||
[testenv:docs]
|
[testenv:docs]
|
||||||
deps =
|
deps = -r requirements/docs.txt
|
||||||
-r requirements/docs.txt
|
|
||||||
|
|
||||||
https://github.com/pallets/werkzeug/archive/master.tar.gz
|
|
||||||
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs {envtmpdir}/html
|
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs {envtmpdir}/html
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue