Merge branch 'main' into pr/emisargent/4969

This commit is contained in:
David Lord 2023-03-12 08:13:33 -07:00
commit 58c52e84cd
No known key found for this signature in database
GPG key ID: 7A1C87E3F5BC42A8
38 changed files with 282 additions and 1063 deletions

View file

@ -33,7 +33,7 @@ jobs:
id-token: write id-token: write
contents: write contents: write
# Can't pin with hash due to how this workflow works. # Can't pin with hash due to how this workflow works.
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.4.0 uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.5.0
with: with:
base64-subjects: ${{ needs.build.outputs.hash }} base64-subjects: ${{ needs.build.outputs.hash }}
create-release: create-release:

View file

@ -49,7 +49,7 @@ jobs:
pip install -U setuptools pip install -U setuptools
python -m pip install -U pip python -m pip install -U pip
- name: cache mypy - name: cache mypy
uses: actions/cache@627f0f41f6904a5b1efbaed9f96d9eb58e92e920 uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0
with: with:
path: ./.mypy_cache path: ./.mypy_cache
key: mypy|${{ matrix.python }}|${{ hashFiles('pyproject.toml') }} key: mypy|${{ matrix.python }}|${{ hashFiles('pyproject.toml') }}

View file

@ -26,7 +26,7 @@ repos:
- flake8-bugbear - flake8-bugbear
- flake8-implicit-str-concat - flake8-implicit-str-concat
- repo: https://github.com/peterdemin/pip-compile-multi - repo: https://github.com/peterdemin/pip-compile-multi
rev: v2.6.1 rev: v2.6.2
hooks: hooks:
- id: pip-compile-multi-verify - id: pip-compile-multi-verify
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks

View file

@ -3,19 +3,56 @@ Version 2.3.0
Unreleased Unreleased
- Remove previously deprecated code. :pr:`4995`
- The ``push`` and ``pop`` methods of the deprecated ``_app_ctx_stack`` and
``_request_ctx_stack`` objects are removed. ``top`` still exists to give
extensions more time to update, but it will be removed.
- The ``FLASK_ENV`` environment variable, ``ENV`` config key, and ``app.env``
property are removed.
- The ``session_cookie_name``, ``send_file_max_age_default``, ``use_x_sendfile``,
``propagate_exceptions``, and ``templates_auto_reload`` properties on ``app``
are removed.
- The ``JSON_AS_ASCII``, ``JSON_SORT_KEYS``, ``JSONIFY_MIMETYPE``, and
``JSONIFY_PRETTYPRINT_REGULAR`` config keys are removed.
- The ``app.before_first_request`` and ``bp.before_app_first_request`` decorators
are removed.
- ``json_encoder`` and ``json_decoder`` attributes on app and blueprint, and the
corresponding ``json.JSONEncoder`` and ``JSONDecoder`` classes, are removed.
- The ``json.htmlsafe_dumps`` and ``htmlsafe_dump`` functions are removed.
- Calling setup methods on blueprints after registration is an error instead of a
warning. :pr:`4997`
- Importing ``escape`` and ``Markup`` from ``flask`` is deprecated. Import them
directly from ``markupsafe`` instead. :pr:`4996`
- The ``app.got_first_request`` property is deprecated. :pr:`4997`
- The ``locked_cached_property`` decorator is deprecated. Use a lock inside the
decorated function if locking is needed. :issue:`4993`
- Remove uses of locks that could cause requests to block each other very briefly.
:issue:`4993`
- Use modern packaging metadata with ``pyproject.toml`` instead of ``setup.cfg``. - Use modern packaging metadata with ``pyproject.toml`` instead of ``setup.cfg``.
:pr:`4947` :pr:`4947`
- Ensure subdomains are applied with nested blueprints. :issue:`4834` - Ensure subdomains are applied with nested blueprints. :issue:`4834`
- ``config.from_file`` can use ``text=False`` to indicate that the parser wants a
binary file instead. :issue:`4989`
- If a blueprint is created with an empty name it raises a ``ValueError``.
:issue:`5010`
Version 2.2.4
-------------
Unreleased
- Update for compatibility with Werkzeug 2.3.
Version 2.2.3 Version 2.2.3
------------- -------------
Unreleased Released 2023-02-15
- Autoescaping is now enabled by default for ``.svg`` files. Inside - Autoescape is enabled by default for ``.svg`` template files. :issue:`4831`
templates this behavior can be changed with the ``autoescape`` tag.
:issue:`4831`
- Fix the type of ``template_folder`` to accept ``pathlib.Path``. :issue:`4892` - Fix the type of ``template_folder`` to accept ``pathlib.Path``. :issue:`4892`
- Add ``--debug`` option to the ``flask run`` command. :issue:`4777` - Add ``--debug`` option to the ``flask run`` command. :issue:`4777`

View file

@ -217,10 +217,6 @@ Useful Functions and Classes
.. autofunction:: send_from_directory .. autofunction:: send_from_directory
.. autofunction:: escape
.. autoclass:: Markup
:members: escape, unescape, striptags
Message Flashing Message Flashing
---------------- ----------------
@ -270,12 +266,6 @@ HTML ``<script>`` tags.
:members: :members:
:member-order: bysource :member-order: bysource
.. autoclass:: JSONEncoder
:members:
.. autoclass:: JSONDecoder
:members:
.. automodule:: flask.json.tag .. automodule:: flask.json.tag

View file

@ -103,6 +103,14 @@ the ``--debug`` option.
* Debugger is active! * Debugger is active!
* Debugger PIN: 223-456-919 * Debugger PIN: 223-456-919
The ``--debug`` option can also be passed to the top level ``flask`` command to enable
debug mode for any command. The following two ``run`` calls are equivalent.
.. code-block:: console
$ flask --app hello --debug run
$ flask --app hello run --debug
Watch and Ignore Files with the Reloader Watch and Ignore Files with the Reloader
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -47,17 +47,17 @@ Debug Mode
The :data:`DEBUG` config value is special because it may behave inconsistently if The :data:`DEBUG` config value is special because it may behave inconsistently if
changed after the app has begun setting up. In order to set debug mode reliably, use the changed after the app has begun setting up. In order to set debug mode reliably, use the
``--debug`` option on the ``flask run`` command. ``flask run`` will use the interactive ``--debug`` option on the ``flask`` or ``flask run`` command. ``flask run`` will use the
debugger and reloader by default in debug mode. interactive debugger and reloader by default in debug mode.
.. code-block:: text .. code-block:: text
$ flask --app hello run --debug $ flask --app hello run --debug
Using the option is recommended. While it is possible to set :data:`DEBUG` in your Using the option is recommended. While it is possible to set :data:`DEBUG` in your
config or code, this is strongly discouraged. It can't be read early by the ``flask run`` config or code, this is strongly discouraged. It can't be read early by the
command, and some systems or extensions may have already configured themselves based on ``flask run`` command, and some systems or extensions may have already configured
a previous value. themselves based on a previous value.
Builtin Configuration Values Builtin Configuration Values
@ -65,18 +65,6 @@ Builtin Configuration Values
The following configuration values are used internally by Flask: The following configuration values are used internally by Flask:
.. py:data:: ENV
What environment the app is running in. The :attr:`~flask.Flask.env` attribute maps
to this config key.
Default: ``'production'``
.. deprecated:: 2.2
Will be removed in Flask 2.3. Use ``--debug`` instead.
.. versionadded:: 1.0
.. py:data:: DEBUG .. py:data:: DEBUG
Whether debug mode is enabled. When using ``flask run`` to start the development Whether debug mode is enabled. When using ``flask run`` to start the development
@ -271,52 +259,6 @@ The following configuration values are used internally by Flask:
Default: ``None`` Default: ``None``
.. py:data:: JSON_AS_ASCII
Serialize objects to ASCII-encoded JSON. If this is disabled, the
JSON returned from ``jsonify`` will contain Unicode characters. This
has security implications when rendering the JSON into JavaScript in
templates, and should typically remain enabled.
Default: ``True``
.. deprecated:: 2.2
Will be removed in Flask 2.3. Set ``app.json.ensure_ascii``
instead.
.. py:data:: JSON_SORT_KEYS
Sort the keys of JSON objects alphabetically. This is useful for caching
because it ensures the data is serialized the same way no matter what
Python's hash seed is. While not recommended, you can disable this for a
possible performance improvement at the cost of caching.
Default: ``True``
.. deprecated:: 2.2
Will be removed in Flask 2.3. Set ``app.json.sort_keys``
instead.
.. py:data:: JSONIFY_PRETTYPRINT_REGULAR
:func:`~flask.jsonify` responses will be output with newlines,
spaces, and indentation for easier reading by humans. Always enabled
in debug mode.
Default: ``False``
.. deprecated:: 2.2
Will be removed in Flask 2.3. Set ``app.json.compact`` instead.
.. py:data:: JSONIFY_MIMETYPE
The mimetype of ``jsonify`` responses.
Default: ``'application/json'``
.. deprecated:: 2.2
Will be removed in Flask 2.3. Set ``app.json.mimetype`` instead.
.. py:data:: TEMPLATES_AUTO_RELOAD .. py:data:: TEMPLATES_AUTO_RELOAD
Reload templates when they are changed. If not set, it will be enabled in Reload templates when they are changed. If not set, it will be enabled in
@ -381,14 +323,13 @@ The following configuration values are used internally by Flask:
.. versionchanged:: 2.2 .. versionchanged:: 2.2
Removed ``PRESERVE_CONTEXT_ON_EXCEPTION``. Removed ``PRESERVE_CONTEXT_ON_EXCEPTION``.
.. versionchanged:: 2.2 .. versionchanged:: 2.3
``JSON_AS_ASCII``, ``JSON_SORT_KEYS``, ``JSON_AS_ASCII``, ``JSON_SORT_KEYS``, ``JSONIFY_MIMETYPE``, and
``JSONIFY_MIMETYPE``, and ``JSONIFY_PRETTYPRINT_REGULAR`` will be ``JSONIFY_PRETTYPRINT_REGULAR`` were removed. The default ``app.json`` provider has
removed in Flask 2.3. The default ``app.json`` provider has
equivalent attributes instead. equivalent attributes instead.
.. versionchanged:: 2.2 .. versionchanged:: 2.3
``ENV`` will be removed in Flask 2.3. Use ``--debug`` instead. ``ENV`` was removed.
Configuring from Python Files Configuring from Python Files

View file

@ -69,7 +69,6 @@ See also:
- Sentry also supports catching errors from a worker queue - Sentry also supports catching errors from a worker queue
(RQ, Celery, etc.) in a similar fashion. See the `Python SDK docs (RQ, Celery, etc.) in a similar fashion. See the `Python SDK docs
<https://docs.sentry.io/platforms/python/>`__ for more information. <https://docs.sentry.io/platforms/python/>`__ for more information.
- `Getting started with Sentry <https://docs.sentry.io/quickstart/?platform=python>`__
- `Flask-specific documentation <https://docs.sentry.io/platforms/python/guides/flask/>`__ - `Flask-specific documentation <https://docs.sentry.io/platforms/python/guides/flask/>`__

View file

@ -44,7 +44,7 @@ use them if you install them.
* `Watchdog`_ provides a faster, more efficient reloader for the development * `Watchdog`_ provides a faster, more efficient reloader for the development
server. server.
.. _Blinker: https://pythonhosted.org/blinker/ .. _Blinker: https://blinker.readthedocs.io/en/stable/
.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
.. _watchdog: https://pythonhosted.org/watchdog/ .. _watchdog: https://pythonhosted.org/watchdog/

View file

@ -23,7 +23,7 @@ in templates, but there are still other places where you have to be
careful: careful:
- generating HTML without the help of Jinja2 - generating HTML without the help of Jinja2
- calling :class:`~flask.Markup` on data submitted by users - calling :class:`~markupsafe.Markup` on data submitted by users
- sending out HTML from uploaded files, never do that, use the - sending out HTML from uploaded files, never do that, use the
``Content-Disposition: attachment`` header to prevent that problem. ``Content-Disposition: attachment`` header to prevent that problem.
- sending out textfiles from uploaded files. Some browsers are using - sending out textfiles from uploaded files. Some browsers are using

View file

@ -115,7 +115,7 @@ markdown to HTML converter.
There are three ways to accomplish that: There are three ways to accomplish that:
- In the Python code, wrap the HTML string in a :class:`~flask.Markup` - In the Python code, wrap the HTML string in a :class:`~markupsafe.Markup`
object before passing it to the template. This is in general the object before passing it to the template. This is in general the
recommended way. recommended way.
- Inside the template, use the ``|safe`` filter to explicitly mark a - Inside the template, use the ``|safe`` filter to explicitly mark a

View file

@ -19,7 +19,7 @@ In a separate terminal, activate the virtualenv and run the Flask development se
```shell ```shell
$ . ./.venv/bin/activate $ . ./.venv/bin/activate
$ flask -A task_app --debug run $ flask -A task_app run --debug
``` ```
Go to http://localhost:5000/ and use the forms to submit tasks. You can see the polling Go to http://localhost:5000/ and use the forms to submit tasks. You can see the polling

View file

@ -2,7 +2,7 @@
# This file is autogenerated by pip-compile with Python 3.10 # This file is autogenerated by pip-compile with Python 3.10
# by the following command: # by the following command:
# #
# pip-compile pyproject.toml # pip-compile --resolver=backtracking pyproject.toml
# #
amqp==5.1.1 amqp==5.1.1
# via kombu # via kombu
@ -25,7 +25,7 @@ click-plugins==1.1.1
# via celery # via celery
click-repl==0.2.0 click-repl==0.2.0
# via celery # via celery
flask==2.2.2 flask==2.2.3
# via flask-example-celery (pyproject.toml) # via flask-example-celery (pyproject.toml)
itsdangerous==2.1.2 itsdangerous==2.1.2
# via flask # via flask
@ -37,7 +37,7 @@ markupsafe==2.1.2
# via # via
# jinja2 # jinja2
# werkzeug # werkzeug
prompt-toolkit==3.0.36 prompt-toolkit==3.0.37
# via click-repl # via click-repl
pytz==2022.7.1 pytz==2022.7.1
# via celery # via celery
@ -52,5 +52,5 @@ vine==5.0.0
# kombu # kombu
wcwidth==0.2.6 wcwidth==0.2.6
# via prompt-toolkit # via prompt-toolkit
werkzeug==2.2.2 werkzeug==2.2.3
# via flask # via flask

View file

@ -5,13 +5,11 @@
# #
# pip-compile-multi # pip-compile-multi
# #
build==0.9.0 build==0.10.0
# via -r requirements/build.in # via -r requirements/build.in
packaging==23.0 packaging==23.0
# via build # via build
pep517==0.13.0 pyproject-hooks==1.0.0
# via build # via build
tomli==2.0.1 tomli==2.0.1
# via # via build
# build
# pep517

View file

@ -8,9 +8,9 @@
-r docs.txt -r docs.txt
-r tests.txt -r tests.txt
-r typing.txt -r typing.txt
build==0.9.0 build==0.10.0
# via pip-tools # via pip-tools
cachetools==5.2.0 cachetools==5.3.0
# via tox # via tox
cfgv==3.3.1 cfgv==3.3.1
# via pre-commit # via pre-commit
@ -24,37 +24,35 @@ colorama==0.4.6
# via tox # via tox
distlib==0.3.6 distlib==0.3.6
# via virtualenv # via virtualenv
filelock==3.8.2 filelock==3.9.0
# via # via
# tox # tox
# virtualenv # virtualenv
identify==2.5.11 identify==2.5.19
# via pre-commit # via pre-commit
nodeenv==1.7.0 nodeenv==1.7.0
# via pre-commit # via pre-commit
pep517==0.13.0 pip-compile-multi==2.6.2
# via build
pip-compile-multi==2.6.1
# via -r requirements/dev.in # via -r requirements/dev.in
pip-tools==6.12.1 pip-tools==6.12.3
# via pip-compile-multi # via pip-compile-multi
platformdirs==2.6.0 platformdirs==3.1.1
# via # via
# tox # tox
# virtualenv # virtualenv
pre-commit==2.20.0 pre-commit==3.1.1
# via -r requirements/dev.in # via -r requirements/dev.in
pyproject-api==1.2.1 pyproject-api==1.5.0
# via tox # via tox
pyproject-hooks==1.0.0
# via build
pyyaml==6.0 pyyaml==6.0
# via pre-commit # via pre-commit
toml==0.10.2 toposort==1.10
# via pre-commit
toposort==1.7
# via pip-compile-multi # via pip-compile-multi
tox==4.0.16 tox==4.4.6
# via -r requirements/dev.in # via -r requirements/dev.in
virtualenv==20.17.1 virtualenv==20.20.0
# via # via
# pre-commit # pre-commit
# tox # tox

View file

@ -5,13 +5,13 @@
# #
# pip-compile-multi # pip-compile-multi
# #
alabaster==0.7.12 alabaster==0.7.13
# via sphinx # via sphinx
babel==2.11.0 babel==2.12.1
# via sphinx # via sphinx
certifi==2022.12.7 certifi==2022.12.7
# via requests # via requests
charset-normalizer==2.1.1 charset-normalizer==3.1.0
# via requests # via requests
docutils==0.17.1 docutils==0.17.1
# via # via
@ -23,21 +23,19 @@ imagesize==1.4.1
# via sphinx # via sphinx
jinja2==3.1.2 jinja2==3.1.2
# via sphinx # via sphinx
markupsafe==2.1.1 markupsafe==2.1.2
# via jinja2 # via jinja2
packaging==22.0 packaging==23.0
# via # via
# pallets-sphinx-themes # pallets-sphinx-themes
# sphinx # sphinx
pallets-sphinx-themes==2.0.3 pallets-sphinx-themes==2.0.3
# via -r requirements/docs.in # via -r requirements/docs.in
pygments==2.13.0 pygments==2.14.0
# via # via
# sphinx # sphinx
# sphinx-tabs # sphinx-tabs
pytz==2022.7 requests==2.28.2
# via babel
requests==2.28.1
# via sphinx # via sphinx
snowballstemmer==2.2.0 snowballstemmer==2.2.0
# via sphinx # via sphinx
@ -52,11 +50,11 @@ sphinx-issues==3.0.1
# via -r requirements/docs.in # via -r requirements/docs.in
sphinx-tabs==3.3.1 sphinx-tabs==3.3.1
# via -r requirements/docs.in # via -r requirements/docs.in
sphinxcontrib-applehelp==1.0.2 sphinxcontrib-applehelp==1.0.4
# via sphinx # via sphinx
sphinxcontrib-devhelp==1.0.2 sphinxcontrib-devhelp==1.0.2
# via sphinx # via sphinx
sphinxcontrib-htmlhelp==2.0.0 sphinxcontrib-htmlhelp==2.0.1
# via sphinx # via sphinx
sphinxcontrib-jsmath==1.0.1 sphinxcontrib-jsmath==1.0.1
# via sphinx # via sphinx
@ -66,5 +64,5 @@ sphinxcontrib-qthelp==1.0.3
# via sphinx # via sphinx
sphinxcontrib-serializinghtml==1.1.5 sphinxcontrib-serializinghtml==1.1.5
# via sphinx # via sphinx
urllib3==1.26.13 urllib3==1.26.15
# via requests # via requests

View file

@ -2,4 +2,4 @@ pytest
asgiref asgiref
blinker blinker
greenlet ; python_version < "3.11" greenlet ; python_version < "3.11"
python-dotenv python-dotenv>=1; python_version >= "3.8"

View file

@ -1,4 +1,4 @@
# SHA1:69cf1e101a60350e9933c6f1f3b129bd9ed1ea7c # SHA1:30698f5f4f9cba5088318306829a15b0dc123b38
# #
# This file is autogenerated by pip-compile-multi # This file is autogenerated by pip-compile-multi
# To update, run: # To update, run:
@ -11,13 +11,19 @@ attrs==22.2.0
# via pytest # via pytest
blinker==1.5 blinker==1.5
# via -r requirements/tests.in # via -r requirements/tests.in
iniconfig==1.1.1 exceptiongroup==1.1.0
# via pytest # via pytest
packaging==22.0 greenlet==2.0.2 ; python_version < "3.11"
# via -r requirements/tests.in
iniconfig==2.0.0
# via pytest
packaging==23.0
# via pytest # via pytest
pluggy==1.0.0 pluggy==1.0.0
# via pytest # via pytest
pytest==7.2.0 pytest==7.2.2
# via -r requirements/tests.in # via -r requirements/tests.in
python-dotenv==0.21.0 python-dotenv==1.0.0 ; python_version >= "3.8"
# via -r requirements/tests.in # via -r requirements/tests.in
tomli==2.0.1
# via pytest

View file

@ -7,19 +7,21 @@
# #
cffi==1.15.1 cffi==1.15.1
# via cryptography # via cryptography
cryptography==38.0.4 cryptography==39.0.2
# via -r requirements/typing.in # via -r requirements/typing.in
mypy==0.991 mypy==1.1.1
# via -r requirements/typing.in # via -r requirements/typing.in
mypy-extensions==0.4.3 mypy-extensions==1.0.0
# via mypy # via mypy
pycparser==2.21 pycparser==2.21
# via cffi # via cffi
types-contextvars==2.4.7 tomli==2.0.1
# via mypy
types-contextvars==2.4.7.1
# via -r requirements/typing.in # via -r requirements/typing.in
types-dataclasses==0.6.6 types-dataclasses==0.6.6
# via -r requirements/typing.in # via -r requirements/typing.in
types-setuptools==65.6.0.2 types-setuptools==67.6.0.0
# via -r requirements/typing.in # via -r requirements/typing.in
typing-extensions==4.4.0 typing-extensions==4.5.0
# via mypy # via mypy

View file

@ -1,6 +1,3 @@
from markupsafe import escape
from markupsafe import Markup
from . import json as json from . import json as json
from .app import Flask as Flask from .app import Flask as Flask
from .app import Request as Request from .app import Request as Request
@ -51,7 +48,7 @@ def __getattr__(name):
from .globals import __app_ctx_stack from .globals import __app_ctx_stack
warnings.warn( warnings.warn(
"'_app_ctx_stack' is deprecated and will be removed in Flask 2.3.", "'_app_ctx_stack' is deprecated and will be removed in Flask 2.4.",
DeprecationWarning, DeprecationWarning,
stacklevel=2, stacklevel=2,
) )
@ -62,10 +59,34 @@ def __getattr__(name):
from .globals import __request_ctx_stack from .globals import __request_ctx_stack
warnings.warn( warnings.warn(
"'_request_ctx_stack' is deprecated and will be removed in Flask 2.3.", "'_request_ctx_stack' is deprecated and will be removed in Flask 2.4.",
DeprecationWarning, DeprecationWarning,
stacklevel=2, stacklevel=2,
) )
return __request_ctx_stack return __request_ctx_stack
if name == "escape":
import warnings
from markupsafe import escape
warnings.warn(
"'flask.escape' is deprecated and will be removed in Flask 2.4. Import"
" 'markupsafe.escape' instead.",
DeprecationWarning,
stacklevel=2,
)
return escape
if name == "escape":
import warnings
from markupsafe import Markup
warnings.warn(
"'flask.Markup' is deprecated and will be removed in Flask 2.4. Import"
" 'markupsafe.Markup' instead.",
DeprecationWarning,
stacklevel=2,
)
return Markup
raise AttributeError(name) raise AttributeError(name)

View file

@ -1,6 +1,5 @@
import functools import functools
import inspect import inspect
import json
import logging import logging
import os import os
import sys import sys
@ -9,8 +8,8 @@ import weakref
from collections.abc import Iterator as _abc_Iterator from collections.abc import Iterator as _abc_Iterator
from datetime import timedelta from datetime import timedelta
from itertools import chain from itertools import chain
from threading import Lock
from types import TracebackType from types import TracebackType
from urllib.parse import quote as _url_quote
import click import click
from werkzeug.datastructures import Headers from werkzeug.datastructures import Headers
@ -27,7 +26,7 @@ from werkzeug.routing import RequestRedirect
from werkzeug.routing import RoutingException from werkzeug.routing import RoutingException
from werkzeug.routing import Rule from werkzeug.routing import Rule
from werkzeug.serving import is_running_from_reloader from werkzeug.serving import is_running_from_reloader
from werkzeug.urls import url_quote from werkzeug.utils import cached_property
from werkzeug.utils import redirect as _wz_redirect from werkzeug.utils import redirect as _wz_redirect
from werkzeug.wrappers import Response as BaseResponse from werkzeug.wrappers import Response as BaseResponse
@ -48,7 +47,6 @@ from .helpers import _split_blueprint_path
from .helpers import get_debug_flag from .helpers import get_debug_flag
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 .json.provider import DefaultJSONProvider from .json.provider import DefaultJSONProvider
from .json.provider import JSONProvider from .json.provider import JSONProvider
from .logging import create_logger from .logging import create_logger
@ -75,9 +73,6 @@ if t.TYPE_CHECKING: # pragma: no cover
from .testing import FlaskClient from .testing import FlaskClient
from .testing import FlaskCliRunner from .testing import FlaskCliRunner
T_before_first_request = t.TypeVar(
"T_before_first_request", bound=ft.BeforeFirstRequestCallable
)
T_shell_context_processor = t.TypeVar( T_shell_context_processor = t.TypeVar(
"T_shell_context_processor", bound=ft.ShellContextProcessorCallable "T_shell_context_processor", bound=ft.ShellContextProcessorCallable
) )
@ -274,36 +269,6 @@ class Flask(Scaffold):
#: :data:`SECRET_KEY` configuration key. Defaults to ``None``. #: :data:`SECRET_KEY` configuration key. Defaults to ``None``.
secret_key = ConfigAttribute("SECRET_KEY") secret_key = ConfigAttribute("SECRET_KEY")
@property
def session_cookie_name(self) -> str:
"""The name of the cookie set by the session interface.
.. deprecated:: 2.2
Will be removed in Flask 2.3. Use ``app.config["SESSION_COOKIE_NAME"]``
instead.
"""
import warnings
warnings.warn(
"'session_cookie_name' is deprecated and will be removed in Flask 2.3. Use"
" 'SESSION_COOKIE_NAME' in 'app.config' instead.",
DeprecationWarning,
stacklevel=2,
)
return self.config["SESSION_COOKIE_NAME"]
@session_cookie_name.setter
def session_cookie_name(self, value: str) -> None:
import warnings
warnings.warn(
"'session_cookie_name' is deprecated and will be removed in Flask 2.3. Use"
" 'SESSION_COOKIE_NAME' in 'app.config' instead.",
DeprecationWarning,
stacklevel=2,
)
self.config["SESSION_COOKIE_NAME"] = value
#: A :class:`~datetime.timedelta` which is used to set the expiration #: A :class:`~datetime.timedelta` which is used to set the expiration
#: date of a permanent session. The default is 31 days which makes a #: date of a permanent session. The default is 31 days which makes a
#: permanent session survive for roughly one month. #: permanent session survive for roughly one month.
@ -315,152 +280,6 @@ class Flask(Scaffold):
"PERMANENT_SESSION_LIFETIME", get_converter=_make_timedelta "PERMANENT_SESSION_LIFETIME", get_converter=_make_timedelta
) )
@property
def send_file_max_age_default(self) -> t.Optional[timedelta]:
"""The default value for ``max_age`` for :func:`~flask.send_file`. The default
is ``None``, which tells the browser to use conditional requests instead of a
timed cache.
.. deprecated:: 2.2
Will be removed in Flask 2.3. Use
``app.config["SEND_FILE_MAX_AGE_DEFAULT"]`` instead.
.. versionchanged:: 2.0
Defaults to ``None`` instead of 12 hours.
"""
import warnings
warnings.warn(
"'send_file_max_age_default' is deprecated and will be removed in Flask"
" 2.3. Use 'SEND_FILE_MAX_AGE_DEFAULT' in 'app.config' instead.",
DeprecationWarning,
stacklevel=2,
)
return _make_timedelta(self.config["SEND_FILE_MAX_AGE_DEFAULT"])
@send_file_max_age_default.setter
def send_file_max_age_default(self, value: t.Union[int, timedelta, None]) -> None:
import warnings
warnings.warn(
"'send_file_max_age_default' is deprecated and will be removed in Flask"
" 2.3. Use 'SEND_FILE_MAX_AGE_DEFAULT' in 'app.config' instead.",
DeprecationWarning,
stacklevel=2,
)
self.config["SEND_FILE_MAX_AGE_DEFAULT"] = _make_timedelta(value)
@property
def use_x_sendfile(self) -> bool:
"""Enable this to use the ``X-Sendfile`` feature, assuming the server supports
it, from :func:`~flask.send_file`.
.. deprecated:: 2.2
Will be removed in Flask 2.3. Use ``app.config["USE_X_SENDFILE"]`` instead.
"""
import warnings
warnings.warn(
"'use_x_sendfile' is deprecated and will be removed in Flask 2.3. Use"
" 'USE_X_SENDFILE' in 'app.config' instead.",
DeprecationWarning,
stacklevel=2,
)
return self.config["USE_X_SENDFILE"]
@use_x_sendfile.setter
def use_x_sendfile(self, value: bool) -> None:
import warnings
warnings.warn(
"'use_x_sendfile' is deprecated and will be removed in Flask 2.3. Use"
" 'USE_X_SENDFILE' in 'app.config' instead.",
DeprecationWarning,
stacklevel=2,
)
self.config["USE_X_SENDFILE"] = value
_json_encoder: t.Union[t.Type[json.JSONEncoder], None] = None
_json_decoder: t.Union[t.Type[json.JSONDecoder], None] = None
@property # type: ignore[override]
def json_encoder(self) -> t.Type[json.JSONEncoder]:
"""The JSON encoder class to use. Defaults to
:class:`~flask.json.JSONEncoder`.
.. deprecated:: 2.2
Will be removed in Flask 2.3. Customize
:attr:`json_provider_class` instead.
.. versionadded:: 0.10
"""
import warnings
warnings.warn(
"'app.json_encoder' is deprecated and will be removed in Flask 2.3."
" Customize 'app.json_provider_class' or 'app.json' instead.",
DeprecationWarning,
stacklevel=2,
)
if self._json_encoder is None:
from . import json
return json.JSONEncoder
return self._json_encoder
@json_encoder.setter
def json_encoder(self, value: t.Type[json.JSONEncoder]) -> None:
import warnings
warnings.warn(
"'app.json_encoder' is deprecated and will be removed in Flask 2.3."
" Customize 'app.json_provider_class' or 'app.json' instead.",
DeprecationWarning,
stacklevel=2,
)
self._json_encoder = value
@property # type: ignore[override]
def json_decoder(self) -> t.Type[json.JSONDecoder]:
"""The JSON decoder class to use. Defaults to
:class:`~flask.json.JSONDecoder`.
.. deprecated:: 2.2
Will be removed in Flask 2.3. Customize
:attr:`json_provider_class` instead.
.. versionadded:: 0.10
"""
import warnings
warnings.warn(
"'app.json_decoder' is deprecated and will be removed in Flask 2.3."
" Customize 'app.json_provider_class' or 'app.json' instead.",
DeprecationWarning,
stacklevel=2,
)
if self._json_decoder is None:
from . import json
return json.JSONDecoder
return self._json_decoder
@json_decoder.setter
def json_decoder(self, value: t.Type[json.JSONDecoder]) -> None:
import warnings
warnings.warn(
"'app.json_decoder' is deprecated and will be removed in Flask 2.3."
" Customize 'app.json_provider_class' or 'app.json' instead.",
DeprecationWarning,
stacklevel=2,
)
self._json_decoder = value
json_provider_class: t.Type[JSONProvider] = DefaultJSONProvider json_provider_class: t.Type[JSONProvider] = DefaultJSONProvider
"""A subclass of :class:`~flask.json.provider.JSONProvider`. An """A subclass of :class:`~flask.json.provider.JSONProvider`. An
instance is created and assigned to :attr:`app.json` when creating instance is created and assigned to :attr:`app.json` when creating
@ -487,7 +306,6 @@ class Flask(Scaffold):
#: Default configuration parameters. #: Default configuration parameters.
default_config = ImmutableDict( default_config = ImmutableDict(
{ {
"ENV": None,
"DEBUG": None, "DEBUG": None,
"TESTING": False, "TESTING": False,
"PROPAGATE_EXCEPTIONS": None, "PROPAGATE_EXCEPTIONS": None,
@ -509,10 +327,6 @@ class Flask(Scaffold):
"TRAP_HTTP_EXCEPTIONS": False, "TRAP_HTTP_EXCEPTIONS": False,
"EXPLAIN_TEMPLATE_LOADING": False, "EXPLAIN_TEMPLATE_LOADING": False,
"PREFERRED_URL_SCHEME": "http", "PREFERRED_URL_SCHEME": "http",
"JSON_AS_ASCII": None,
"JSON_SORT_KEYS": None,
"JSONIFY_PRETTYPRINT_REGULAR": None,
"JSONIFY_MIMETYPE": None,
"TEMPLATES_AUTO_RELOAD": None, "TEMPLATES_AUTO_RELOAD": None,
"MAX_COOKIE_SIZE": 4093, "MAX_COOKIE_SIZE": 4093,
} }
@ -625,17 +439,6 @@ class Flask(Scaffold):
t.Callable[[Exception, str, t.Dict[str, t.Any]], str] t.Callable[[Exception, str, t.Dict[str, t.Any]], str]
] = [] ] = []
#: A list of functions that will be called at the beginning of the
#: first request to this instance. To register a function, use the
#: :meth:`before_first_request` decorator.
#:
#: .. deprecated:: 2.2
#: Will be removed in Flask 2.3. Run setup code when
#: creating the application instead.
#:
#: .. versionadded:: 0.8
self.before_first_request_funcs: t.List[ft.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
#: if the request ends this is the place to store code that disconnects #: if the request ends this is the place to store code that disconnects
@ -692,7 +495,6 @@ class Flask(Scaffold):
# tracks internally if the application already handled at least one # tracks internally if the application already handled at least one
# request. # request.
self._got_first_request = False self._got_first_request = False
self._before_request_lock = Lock()
# Add a static route using the provided static_url_path, static_host, # Add a static route using the provided static_url_path, static_host,
# and static_folder if there is a configured static_folder. # and static_folder if there is a configured static_folder.
@ -729,7 +531,7 @@ class Flask(Scaffold):
" running it." " running it."
) )
@locked_cached_property @cached_property
def name(self) -> str: # type: ignore def name(self) -> str: # type: ignore
"""The name of the application. This is usually the import name """The name of the application. This is usually the import name
with the difference that it's guessed from the run file if the with the difference that it's guessed from the run file if the
@ -746,29 +548,7 @@ class Flask(Scaffold):
return os.path.splitext(os.path.basename(fn))[0] return os.path.splitext(os.path.basename(fn))[0]
return self.import_name return self.import_name
@property @cached_property
def propagate_exceptions(self) -> bool:
"""Returns the value of the ``PROPAGATE_EXCEPTIONS`` configuration
value in case it's set, otherwise a sensible default is returned.
.. deprecated:: 2.2
Will be removed in Flask 2.3.
.. versionadded:: 0.7
"""
import warnings
warnings.warn(
"'propagate_exceptions' is deprecated and will be removed in Flask 2.3.",
DeprecationWarning,
stacklevel=2,
)
rv = self.config["PROPAGATE_EXCEPTIONS"]
if rv is not None:
return rv
return self.testing or self.debug
@locked_cached_property
def logger(self) -> logging.Logger: def logger(self) -> logging.Logger:
"""A standard Python :class:`~logging.Logger` for the app, with """A standard Python :class:`~logging.Logger` for the app, with
the same name as :attr:`name`. the same name as :attr:`name`.
@ -795,7 +575,7 @@ class Flask(Scaffold):
""" """
return create_logger(self) return create_logger(self)
@locked_cached_property @cached_property
def jinja_env(self) -> Environment: def jinja_env(self) -> Environment:
"""The Jinja environment used to load templates. """The Jinja environment used to load templates.
@ -810,8 +590,18 @@ class Flask(Scaffold):
"""This attribute is set to ``True`` if the application started """This attribute is set to ``True`` if the application started
handling the first request. handling the first request.
.. deprecated:: 2.3
Will be removed in Flask 2.4.
.. versionadded:: 0.8 .. versionadded:: 0.8
""" """
import warnings
warnings.warn(
"'got_first_request' is deprecated and will be removed in Flask 2.4.",
DeprecationWarning,
stacklevel=2,
)
return self._got_first_request return self._got_first_request
def make_config(self, instance_relative: bool = False) -> Config: def make_config(self, instance_relative: bool = False) -> Config:
@ -827,7 +617,6 @@ class Flask(Scaffold):
if instance_relative: if instance_relative:
root_path = self.instance_path root_path = self.instance_path
defaults = dict(self.default_config) defaults = dict(self.default_config)
defaults["ENV"] = os.environ.get("FLASK_ENV") or "production"
defaults["DEBUG"] = get_debug_flag() defaults["DEBUG"] = get_debug_flag()
return self.config_class(root_path, defaults) return self.config_class(root_path, defaults)
@ -868,42 +657,6 @@ class Flask(Scaffold):
""" """
return open(os.path.join(self.instance_path, resource), mode) return open(os.path.join(self.instance_path, resource), mode)
@property
def templates_auto_reload(self) -> bool:
"""Reload templates when they are changed. Used by
:meth:`create_jinja_environment`. It is enabled by default in debug mode.
.. deprecated:: 2.2
Will be removed in Flask 2.3. Use ``app.config["TEMPLATES_AUTO_RELOAD"]``
instead.
.. versionadded:: 1.0
This property was added but the underlying config and behavior
already existed.
"""
import warnings
warnings.warn(
"'templates_auto_reload' is deprecated and will be removed in Flask 2.3."
" Use 'TEMPLATES_AUTO_RELOAD' in 'app.config' instead.",
DeprecationWarning,
stacklevel=2,
)
rv = self.config["TEMPLATES_AUTO_RELOAD"]
return rv if rv is not None else self.debug
@templates_auto_reload.setter
def templates_auto_reload(self, value: bool) -> None:
import warnings
warnings.warn(
"'templates_auto_reload' is deprecated and will be removed in Flask 2.3."
" Use 'TEMPLATES_AUTO_RELOAD' in 'app.config' instead.",
DeprecationWarning,
stacklevel=2,
)
self.config["TEMPLATES_AUTO_RELOAD"] = value
def create_jinja_environment(self) -> Environment: def create_jinja_environment(self) -> Environment:
"""Create the Jinja environment based on :attr:`jinja_options` """Create the Jinja environment based on :attr:`jinja_options`
and the various Jinja-related methods of the app. Changing and the various Jinja-related methods of the app. Changing
@ -1010,40 +763,6 @@ class Flask(Scaffold):
rv.update(processor()) rv.update(processor())
return rv return rv
@property
def env(self) -> str:
"""What environment the app is running in. This maps to the :data:`ENV` config
key.
**Do not enable development when deploying in production.**
Default: ``'production'``
.. deprecated:: 2.2
Will be removed in Flask 2.3.
"""
import warnings
warnings.warn(
"'app.env' is deprecated and will be removed in Flask 2.3."
" Use 'app.debug' instead.",
DeprecationWarning,
stacklevel=2,
)
return self.config["ENV"]
@env.setter
def env(self, value: str) -> None:
import warnings
warnings.warn(
"'app.env' is deprecated and will be removed in Flask 2.3."
" Use 'app.debug' instead.",
DeprecationWarning,
stacklevel=2,
)
self.config["ENV"] = value
@property @property
def debug(self) -> bool: def debug(self) -> bool:
"""Whether debug mode is enabled. When using ``flask run`` to start the """Whether debug mode is enabled. When using ``flask run`` to start the
@ -1144,16 +863,8 @@ class Flask(Scaffold):
if get_load_dotenv(load_dotenv): if get_load_dotenv(load_dotenv):
cli.load_dotenv() cli.load_dotenv()
# if set, let env vars override previous values # if set, env var overrides existing value
if "FLASK_ENV" in os.environ: if "FLASK_DEBUG" in os.environ:
print(
"'FLASK_ENV' is deprecated and will not be used in"
" Flask 2.3. Use 'FLASK_DEBUG' instead.",
file=sys.stderr,
)
self.config["ENV"] = os.environ.get("FLASK_ENV") or "production"
self.debug = get_debug_flag()
elif "FLASK_DEBUG" in os.environ:
self.debug = get_debug_flag() self.debug = get_debug_flag()
# debug passed to method overrides all other sources # debug passed to method overrides all other sources
@ -1248,7 +959,7 @@ class Flask(Scaffold):
""" """
cls = self.test_client_class cls = self.test_client_class
if cls is None: if cls is None:
from .testing import FlaskClient as cls # type: ignore from .testing import FlaskClient as cls
return cls( # type: ignore return cls( # type: ignore
self, self.response_class, use_cookies=use_cookies, **kwargs self, self.response_class, use_cookies=use_cookies, **kwargs
) )
@ -1266,7 +977,7 @@ class Flask(Scaffold):
cls = self.test_cli_runner_class cls = self.test_cli_runner_class
if cls is None: if cls is None:
from .testing import FlaskCliRunner as cls # type: ignore from .testing import FlaskCliRunner as cls
return cls(self, **kwargs) # type: ignore return cls(self, **kwargs) # type: ignore
@ -1479,32 +1190,6 @@ class Flask(Scaffold):
""" """
self.jinja_env.globals[name or f.__name__] = f self.jinja_env.globals[name or f.__name__] = f
@setupmethod
def before_first_request(self, f: T_before_first_request) -> T_before_first_request:
"""Registers a function to be run before the first request to this
instance of the application.
The function will be called without any arguments and its return
value is ignored.
.. deprecated:: 2.2
Will be removed in Flask 2.3. Run setup code when creating
the application instead.
.. versionadded:: 0.8
"""
import warnings
warnings.warn(
"'before_first_request' is deprecated and will be removed"
" in Flask 2.3. Run setup code while creating the"
" application instead.",
DeprecationWarning,
stacklevel=2,
)
self.before_first_request_funcs.append(f)
return f
@setupmethod @setupmethod
def teardown_appcontext(self, f: T_teardown) -> T_teardown: def teardown_appcontext(self, f: T_teardown) -> T_teardown:
"""Registers a function to be called when the application """Registers a function to be called when the application
@ -1682,7 +1367,7 @@ class Flask(Scaffold):
Always sends the :data:`got_request_exception` signal. Always sends the :data:`got_request_exception` signal.
If :attr:`propagate_exceptions` is ``True``, such as in debug If :data:`PROPAGATE_EXCEPTIONS` is ``True``, such as in debug
mode, the error will be re-raised so that the debugger can mode, the error will be re-raised so that the debugger can
display it. Otherwise, the original exception is logged, and display it. Otherwise, the original exception is logged, and
an :exc:`~werkzeug.exceptions.InternalServerError` is returned. an :exc:`~werkzeug.exceptions.InternalServerError` is returned.
@ -1805,15 +1490,6 @@ class Flask(Scaffold):
.. versionadded:: 0.7 .. versionadded:: 0.7
""" """
# Run before_first_request functions if this is the thread's first request.
# Inlined to avoid a method call on subsequent requests.
# This is deprecated, will be removed in Flask 2.3.
if not self._got_first_request:
with self._before_request_lock:
if not self._got_first_request:
for func in self.before_first_request_funcs:
self.ensure_sync(func)()
self._got_first_request = True self._got_first_request = True
try: try:
@ -2034,7 +1710,8 @@ class Flask(Scaffold):
return self.handle_url_build_error(error, endpoint, values) return self.handle_url_build_error(error, endpoint, values)
if _anchor is not None: if _anchor is not None:
rv = f"{rv}#{url_quote(_anchor)}" _anchor = _url_quote(_anchor, safe="%!#$&'()*+,/:;=?@")
rv = f"{rv}#{_anchor}"
return rv return rv

View file

@ -1,4 +1,3 @@
import json
import os import os
import typing as t import typing as t
from collections import defaultdict from collections import defaultdict
@ -15,9 +14,6 @@ if t.TYPE_CHECKING: # pragma: no cover
DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable] DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable]
T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable) T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable)
T_before_first_request = t.TypeVar(
"T_before_first_request", bound=ft.BeforeFirstRequestCallable
)
T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable) T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable)
T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable) T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable)
T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable) T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
@ -173,77 +169,6 @@ class Blueprint(Scaffold):
_got_registered_once = False _got_registered_once = False
_json_encoder: t.Union[t.Type[json.JSONEncoder], None] = None
_json_decoder: t.Union[t.Type[json.JSONDecoder], None] = None
@property
def json_encoder(
self,
) -> t.Union[t.Type[json.JSONEncoder], None]:
"""Blueprint-local JSON encoder class to use. Set to ``None`` to use the app's.
.. deprecated:: 2.2
Will be removed in Flask 2.3. Customize
:attr:`json_provider_class` instead.
.. versionadded:: 0.10
"""
import warnings
warnings.warn(
"'bp.json_encoder' is deprecated and will be removed in Flask 2.3."
" Customize 'app.json_provider_class' or 'app.json' instead.",
DeprecationWarning,
stacklevel=2,
)
return self._json_encoder
@json_encoder.setter
def json_encoder(self, value: t.Union[t.Type[json.JSONEncoder], None]) -> None:
import warnings
warnings.warn(
"'bp.json_encoder' is deprecated and will be removed in Flask 2.3."
" Customize 'app.json_provider_class' or 'app.json' instead.",
DeprecationWarning,
stacklevel=2,
)
self._json_encoder = value
@property
def json_decoder(
self,
) -> t.Union[t.Type[json.JSONDecoder], None]:
"""Blueprint-local JSON decoder class to use. Set to ``None`` to use the app's.
.. deprecated:: 2.2
Will be removed in Flask 2.3. Customize
:attr:`json_provider_class` instead.
.. versionadded:: 0.10
"""
import warnings
warnings.warn(
"'bp.json_decoder' is deprecated and will be removed in Flask 2.3."
" Customize 'app.json_provider_class' or 'app.json' instead.",
DeprecationWarning,
stacklevel=2,
)
return self._json_decoder
@json_decoder.setter
def json_decoder(self, value: t.Union[t.Type[json.JSONDecoder], None]) -> None:
import warnings
warnings.warn(
"'bp.json_decoder' is deprecated and will be removed in Flask 2.3."
" Customize 'app.json_provider_class' or 'app.json' instead.",
DeprecationWarning,
stacklevel=2,
)
self._json_decoder = value
def __init__( def __init__(
self, self,
name: str, name: str,
@ -265,6 +190,9 @@ class Blueprint(Scaffold):
root_path=root_path, root_path=root_path,
) )
if not name:
raise ValueError("'name' may not be empty.")
if "." in name: if "." in name:
raise ValueError("'name' may not contain a dot '.' character.") raise ValueError("'name' may not contain a dot '.' character.")
@ -282,19 +210,12 @@ class Blueprint(Scaffold):
def _check_setup_finished(self, f_name: str) -> None: def _check_setup_finished(self, f_name: str) -> None:
if self._got_registered_once: if self._got_registered_once:
import warnings raise AssertionError(
f"The setup method '{f_name}' can no longer be called on the blueprint"
warnings.warn( f" '{self.name}'. It has already been registered at least once, any"
f"The setup method '{f_name}' can no longer be called on" " changes will not be applied consistently.\n"
f" the blueprint '{self.name}'. It has already been" "Make sure all imports, decorators, functions, etc. needed to set up"
" registered at least once, any changes will not be" " the blueprint are done before registering it."
" applied consistently.\n"
"Make sure all imports, decorators, functions, etc."
" needed to set up the blueprint are done before"
" registering it.\n"
"This warning will become an exception in Flask 2.3.",
UserWarning,
stacklevel=3,
) )
@setupmethod @setupmethod
@ -361,6 +282,10 @@ class Blueprint(Scaffold):
.. versionchanged:: 2.3 .. versionchanged:: 2.3
Nested blueprints now correctly apply subdomains. Nested blueprints now correctly apply subdomains.
.. versionchanged:: 2.1
Registering the same blueprint with the same name multiple
times is an error.
.. versionchanged:: 2.0.1 .. versionchanged:: 2.0.1
Nested blueprints are registered with their dotted name. Nested blueprints are registered with their dotted name.
This allows different blueprints with the same name to be This allows different blueprints with the same name to be
@ -371,10 +296,6 @@ class Blueprint(Scaffold):
name the blueprint is registered with. This allows the same name the blueprint is registered with. This allows the same
blueprint to be registered multiple times with unique names blueprint to be registered multiple times with unique names
for ``url_for``. 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.
""" """
name_prefix = options.get("name_prefix", "") name_prefix = options.get("name_prefix", "")
self_name = options.get("name", self.name) self_name = options.get("name", self.name)
@ -634,29 +555,6 @@ class Blueprint(Scaffold):
) )
return f return f
@setupmethod
def before_app_first_request(
self, f: T_before_first_request
) -> T_before_first_request:
"""Register a function to run before the first request to the application is
handled by the worker. Equivalent to :meth:`.Flask.before_first_request`.
.. deprecated:: 2.2
Will be removed in Flask 2.3. Run setup code when creating
the application instead.
"""
import warnings
warnings.warn(
"'before_app_first_request' is deprecated and will be"
" removed in Flask 2.3. Use 'record_once' instead to run"
" setup code when registering the blueprint.",
DeprecationWarning,
stacklevel=2,
)
self.record_once(lambda s: s.app.before_first_request_funcs.append(f))
return f
@setupmethod @setupmethod
def after_app_request(self, f: T_after_request) -> T_after_request: def after_app_request(self, f: T_after_request) -> T_after_request:
"""Like :meth:`after_request`, but after every request, not only those handled """Like :meth:`after_request`, but after every request, not only those handled

View file

@ -234,6 +234,7 @@ class Config(dict):
filename: str, filename: str,
load: t.Callable[[t.IO[t.Any]], t.Mapping], load: t.Callable[[t.IO[t.Any]], t.Mapping],
silent: bool = False, silent: bool = False,
text: bool = True,
) -> bool: ) -> bool:
"""Update the values in the config from a file that is loaded """Update the values in the config from a file that is loaded
using the ``load`` parameter. The loaded data is passed to the using the ``load`` parameter. The loaded data is passed to the
@ -244,8 +245,8 @@ class Config(dict):
import json import json
app.config.from_file("config.json", load=json.load) app.config.from_file("config.json", load=json.load)
import toml import tomllib
app.config.from_file("config.toml", load=toml.load) app.config.from_file("config.toml", load=tomllib.load, text=False)
:param filename: The path to the data file. This can be an :param filename: The path to the data file. This can be an
absolute path or relative to the config root path. absolute path or relative to the config root path.
@ -254,14 +255,18 @@ class Config(dict):
:type load: ``Callable[[Reader], Mapping]`` where ``Reader`` :type load: ``Callable[[Reader], Mapping]`` where ``Reader``
implements a ``read`` method. implements a ``read`` method.
:param silent: Ignore the file if it doesn't exist. :param silent: Ignore the file if it doesn't exist.
:param text: Open the file in text or binary mode.
:return: ``True`` if the file was loaded successfully. :return: ``True`` if the file was loaded successfully.
.. versionchanged:: 2.3
The ``text`` parameter was added.
.. versionadded:: 2.0 .. versionadded:: 2.0
""" """
filename = os.path.join(self.root_path, filename) filename = os.path.join(self.root_path, filename)
try: try:
with open(filename) as f: with open(filename, "r" if text else "rb") as f:
obj = load(f) obj = load(f)
except OSError as e: except OSError as e:
if silent and e.errno in (errno.ENOENT, errno.EISDIR): if silent and e.errno in (errno.ENOENT, errno.EISDIR):

View file

@ -17,30 +17,17 @@ class _FakeStack:
self.name = name self.name = name
self.cv = cv self.cv = cv
def _warn(self): @property
def top(self) -> t.Optional[t.Any]:
import warnings import warnings
warnings.warn( warnings.warn(
f"'_{self.name}_ctx_stack' is deprecated and will be" f"'_{self.name}_ctx_stack' is deprecated and will be removed in Flask 2.4."
" removed in Flask 2.3. Use 'g' to store data, or" f" Use 'g' to store data, or '{self.name}_ctx' to access the current"
f" '{self.name}_ctx' to access the current context.", " context.",
DeprecationWarning, DeprecationWarning,
stacklevel=3, stacklevel=2,
) )
def push(self, obj: t.Any) -> None:
self._warn()
self.cv.set(obj)
def pop(self) -> t.Any:
self._warn()
ctx = self.cv.get(None)
self.cv.set(None)
return ctx
@property
def top(self) -> t.Optional[t.Any]:
self._warn()
return self.cv.get(None) return self.cv.get(None)
@ -88,7 +75,7 @@ def __getattr__(name: str) -> t.Any:
import warnings import warnings
warnings.warn( warnings.warn(
"'_app_ctx_stack' is deprecated and will be removed in Flask 2.3.", "'_app_ctx_stack' is deprecated and will be removed in Flask 2.4.",
DeprecationWarning, DeprecationWarning,
stacklevel=2, stacklevel=2,
) )
@ -98,7 +85,7 @@ def __getattr__(name: str) -> t.Any:
import warnings import warnings
warnings.warn( warnings.warn(
"'_request_ctx_stack' is deprecated and will be removed in Flask 2.3.", "'_request_ctx_stack' is deprecated and will be removed in Flask 2.4.",
DeprecationWarning, DeprecationWarning,
stacklevel=2, stacklevel=2,
) )

View file

@ -25,45 +25,12 @@ if t.TYPE_CHECKING: # pragma: no cover
import typing_extensions as te import typing_extensions as te
def get_env() -> str:
"""Get the environment the app is running in, indicated by the
:envvar:`FLASK_ENV` environment variable. The default is
``'production'``.
.. deprecated:: 2.2
Will be removed in Flask 2.3.
"""
import warnings
warnings.warn(
"'FLASK_ENV' and 'get_env' are deprecated and will be removed"
" in Flask 2.3. Use 'FLASK_DEBUG' instead.",
DeprecationWarning,
stacklevel=2,
)
return os.environ.get("FLASK_ENV") or "production"
def get_debug_flag() -> bool: def get_debug_flag() -> bool:
"""Get whether debug mode should be enabled for the app, indicated by the """Get whether debug mode should be enabled for the app, indicated by the
:envvar:`FLASK_DEBUG` environment variable. The default is ``False``. :envvar:`FLASK_DEBUG` environment variable. The default is ``False``.
""" """
val = os.environ.get("FLASK_DEBUG") val = os.environ.get("FLASK_DEBUG")
return bool(val and val.lower() not in {"0", "false", "no"})
if not val:
env = os.environ.get("FLASK_ENV")
if env is not None:
print(
"'FLASK_ENV' is deprecated and will not be used in"
" Flask 2.3. Use 'FLASK_DEBUG' instead.",
file=sys.stderr,
)
return env == "development"
return False
return val.lower() not in {"0", "false", "no"}
def get_load_dotenv(default: bool = True) -> bool: def get_load_dotenv(default: bool = True) -> bool:
@ -646,6 +613,10 @@ class locked_cached_property(werkzeug.utils.cached_property):
:class:`werkzeug.utils.cached_property` except access uses a lock :class:`werkzeug.utils.cached_property` except access uses a lock
for thread safety. for thread safety.
.. deprecated:: 2.3
Will be removed in Flask 2.4. Use a lock inside the decorated function if
locking is needed.
.. versionchanged:: 2.0 .. versionchanged:: 2.0
Inherits from Werkzeug's ``cached_property`` (and ``property``). Inherits from Werkzeug's ``cached_property`` (and ``property``).
""" """
@ -656,6 +627,14 @@ class locked_cached_property(werkzeug.utils.cached_property):
name: t.Optional[str] = None, name: t.Optional[str] = None,
doc: t.Optional[str] = None, doc: t.Optional[str] = None,
) -> None: ) -> None:
import warnings
warnings.warn(
"'locked_cached_property' is deprecated and will be removed in Flask 2.4."
" Use a lock inside the decorated function if locking is needed.",
DeprecationWarning,
stacklevel=2,
)
super().__init__(fget, name=name, doc=doc) super().__init__(fget, name=name, doc=doc)
self.lock = RLock() self.lock = RLock()

View file

@ -3,85 +3,14 @@ from __future__ import annotations
import json as _json import json as _json
import typing as t import typing as t
from jinja2.utils import htmlsafe_json_dumps as _jinja_htmlsafe_dumps
from ..globals import current_app from ..globals import current_app
from .provider import _default from .provider import _default
if t.TYPE_CHECKING: # pragma: no cover if t.TYPE_CHECKING: # pragma: no cover
from ..app import Flask
from ..wrappers import Response from ..wrappers import Response
class JSONEncoder(_json.JSONEncoder): def dumps(obj: t.Any, **kwargs: t.Any) -> str:
"""The default JSON encoder. Handles extra types compared to the
built-in :class:`json.JSONEncoder`.
- :class:`datetime.datetime` and :class:`datetime.date` are
serialized to :rfc:`822` strings. This is the same as the HTTP
date format.
- :class:`decimal.Decimal` is serialized to a string.
- :class:`uuid.UUID` is serialized to a string.
- :class:`dataclasses.dataclass` is passed to
:func:`dataclasses.asdict`.
- :class:`~markupsafe.Markup` (or any object with a ``__html__``
method) will call the ``__html__`` method to get a string.
Assign a subclass of this to :attr:`flask.Flask.json_encoder` or
:attr:`flask.Blueprint.json_encoder` to override the default.
.. deprecated:: 2.2
Will be removed in Flask 2.3. Use ``app.json`` instead.
"""
def __init__(self, **kwargs) -> None:
import warnings
warnings.warn(
"'JSONEncoder' is deprecated and will be removed in"
" Flask 2.3. Use 'Flask.json' to provide an alternate"
" JSON implementation instead.",
DeprecationWarning,
stacklevel=3,
)
super().__init__(**kwargs)
def default(self, o: t.Any) -> t.Any:
"""Convert ``o`` to a JSON serializable type. See
:meth:`json.JSONEncoder.default`. Python does not support
overriding how basic types like ``str`` or ``list`` are
serialized, they are handled before this method.
"""
return _default(o)
class JSONDecoder(_json.JSONDecoder):
"""The default JSON decoder.
This does not change any behavior from the built-in
:class:`json.JSONDecoder`.
Assign a subclass of this to :attr:`flask.Flask.json_decoder` or
:attr:`flask.Blueprint.json_decoder` to override the default.
.. deprecated:: 2.2
Will be removed in Flask 2.3. Use ``app.json`` instead.
"""
def __init__(self, **kwargs) -> None:
import warnings
warnings.warn(
"'JSONDecoder' is deprecated and will be removed in"
" Flask 2.3. Use 'Flask.json' to provide an alternate"
" JSON implementation instead.",
DeprecationWarning,
stacklevel=3,
)
super().__init__(**kwargs)
def dumps(obj: t.Any, *, app: Flask | None = None, **kwargs: t.Any) -> str:
"""Serialize data as JSON. """Serialize data as JSON.
If :data:`~flask.current_app` is available, it will use its If :data:`~flask.current_app` is available, it will use its
@ -91,13 +20,13 @@ def dumps(obj: t.Any, *, app: Flask | None = None, **kwargs: t.Any) -> str:
:param obj: The data to serialize. :param obj: The data to serialize.
:param kwargs: Arguments passed to the ``dumps`` implementation. :param kwargs: Arguments passed to the ``dumps`` implementation.
.. versionchanged:: 2.3
The ``app`` parameter was removed.
.. versionchanged:: 2.2 .. versionchanged:: 2.2
Calls ``current_app.json.dumps``, allowing an app to override Calls ``current_app.json.dumps``, allowing an app to override
the behavior. the behavior.
.. versionchanged:: 2.2
The ``app`` parameter will be removed in Flask 2.3.
.. versionchanged:: 2.0.2 .. versionchanged:: 2.0.2
:class:`decimal.Decimal` is supported by converting to a string. :class:`decimal.Decimal` is supported by converting to a string.
@ -108,28 +37,14 @@ def dumps(obj: t.Any, *, app: Flask | None = None, **kwargs: t.Any) -> str:
``app`` can be passed directly, rather than requiring an app ``app`` can be passed directly, rather than requiring an app
context for configuration. context for configuration.
""" """
if app is not None: if current_app:
import warnings return current_app.json.dumps(obj, **kwargs)
warnings.warn(
"The 'app' parameter is deprecated and will be removed in"
" Flask 2.3. Call 'app.json.dumps' directly instead.",
DeprecationWarning,
stacklevel=2,
)
else:
app = current_app
if app:
return app.json.dumps(obj, **kwargs)
kwargs.setdefault("default", _default) kwargs.setdefault("default", _default)
return _json.dumps(obj, **kwargs) return _json.dumps(obj, **kwargs)
def dump( def dump(obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None:
obj: t.Any, fp: t.IO[str], *, app: Flask | None = None, **kwargs: t.Any
) -> None:
"""Serialize data as JSON and write to a file. """Serialize data as JSON and write to a file.
If :data:`~flask.current_app` is available, it will use its If :data:`~flask.current_app` is available, it will use its
@ -141,37 +56,25 @@ def dump(
encoding to be valid JSON. encoding to be valid JSON.
:param kwargs: Arguments passed to the ``dump`` implementation. :param kwargs: Arguments passed to the ``dump`` implementation.
.. versionchanged:: 2.3
The ``app`` parameter was removed.
.. versionchanged:: 2.2 .. versionchanged:: 2.2
Calls ``current_app.json.dump``, allowing an app to override Calls ``current_app.json.dump``, allowing an app to override
the behavior. the behavior.
.. versionchanged:: 2.2
The ``app`` parameter will be removed in Flask 2.3.
.. versionchanged:: 2.0 .. versionchanged:: 2.0
Writing to a binary file, and the ``encoding`` argument, will be Writing to a binary file, and the ``encoding`` argument, will be
removed in Flask 2.1. removed in Flask 2.1.
""" """
if app is not None: if current_app:
import warnings current_app.json.dump(obj, fp, **kwargs)
warnings.warn(
"The 'app' parameter is deprecated and will be removed in"
" Flask 2.3. Call 'app.json.dump' directly instead.",
DeprecationWarning,
stacklevel=2,
)
else:
app = current_app
if app:
app.json.dump(obj, fp, **kwargs)
else: else:
kwargs.setdefault("default", _default) kwargs.setdefault("default", _default)
_json.dump(obj, fp, **kwargs) _json.dump(obj, fp, **kwargs)
def loads(s: str | bytes, *, app: Flask | None = None, **kwargs: t.Any) -> t.Any: def loads(s: str | bytes, **kwargs: t.Any) -> t.Any:
"""Deserialize data as JSON. """Deserialize data as JSON.
If :data:`~flask.current_app` is available, it will use its If :data:`~flask.current_app` is available, it will use its
@ -181,13 +84,13 @@ def loads(s: str | bytes, *, app: Flask | None = None, **kwargs: t.Any) -> t.Any
:param s: Text or UTF-8 bytes. :param s: Text or UTF-8 bytes.
:param kwargs: Arguments passed to the ``loads`` implementation. :param kwargs: Arguments passed to the ``loads`` implementation.
.. versionchanged:: 2.3
The ``app`` parameter was removed.
.. versionchanged:: 2.2 .. versionchanged:: 2.2
Calls ``current_app.json.loads``, allowing an app to override Calls ``current_app.json.loads``, allowing an app to override
the behavior. the behavior.
.. versionchanged:: 2.2
The ``app`` parameter will be removed in Flask 2.3.
.. versionchanged:: 2.0 .. versionchanged:: 2.0
``encoding`` will be removed in Flask 2.1. The data must be a ``encoding`` will be removed in Flask 2.1. The data must be a
string or UTF-8 bytes. string or UTF-8 bytes.
@ -196,25 +99,13 @@ def loads(s: str | bytes, *, app: Flask | None = None, **kwargs: t.Any) -> t.Any
``app`` can be passed directly, rather than requiring an app ``app`` can be passed directly, rather than requiring an app
context for configuration. context for configuration.
""" """
if app is not None: if current_app:
import warnings return current_app.json.loads(s, **kwargs)
warnings.warn(
"The 'app' parameter is deprecated and will be removed in"
" Flask 2.3. Call 'app.json.loads' directly instead.",
DeprecationWarning,
stacklevel=2,
)
else:
app = current_app
if app:
return app.json.loads(s, **kwargs)
return _json.loads(s, **kwargs) return _json.loads(s, **kwargs)
def load(fp: t.IO[t.AnyStr], *, app: Flask | None = None, **kwargs: t.Any) -> t.Any: def load(fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any:
"""Deserialize data as JSON read from a file. """Deserialize data as JSON read from a file.
If :data:`~flask.current_app` is available, it will use its If :data:`~flask.current_app` is available, it will use its
@ -224,6 +115,9 @@ def load(fp: t.IO[t.AnyStr], *, app: Flask | None = None, **kwargs: t.Any) -> t.
:param fp: A file opened for reading text or UTF-8 bytes. :param fp: A file opened for reading text or UTF-8 bytes.
:param kwargs: Arguments passed to the ``load`` implementation. :param kwargs: Arguments passed to the ``load`` implementation.
.. versionchanged:: 2.3
The ``app`` parameter was removed.
.. versionchanged:: 2.2 .. versionchanged:: 2.2
Calls ``current_app.json.load``, allowing an app to override Calls ``current_app.json.load``, allowing an app to override
the behavior. the behavior.
@ -235,78 +129,12 @@ def load(fp: t.IO[t.AnyStr], *, app: Flask | None = None, **kwargs: t.Any) -> t.
``encoding`` will be removed in Flask 2.1. The file must be text ``encoding`` will be removed in Flask 2.1. The file must be text
mode, or binary mode with UTF-8 bytes. mode, or binary mode with UTF-8 bytes.
""" """
if app is not None: if current_app:
import warnings return current_app.json.load(fp, **kwargs)
warnings.warn(
"The 'app' parameter is deprecated and will be removed in"
" Flask 2.3. Call 'app.json.load' directly instead.",
DeprecationWarning,
stacklevel=2,
)
else:
app = current_app
if app:
return app.json.load(fp, **kwargs)
return _json.load(fp, **kwargs) return _json.load(fp, **kwargs)
def htmlsafe_dumps(obj: t.Any, **kwargs: t.Any) -> str:
"""Serialize an object to a string of JSON with :func:`dumps`, then
replace HTML-unsafe characters with Unicode escapes and mark the
result safe with :class:`~markupsafe.Markup`.
This is available in templates as the ``|tojson`` filter.
The returned string is safe to render in HTML documents and
``<script>`` tags. The exception is in HTML attributes that are
double quoted; either use single quotes or the ``|forceescape``
filter.
.. deprecated:: 2.2
Will be removed in Flask 2.3. This is built-in to Jinja now.
.. versionchanged:: 2.0
Uses :func:`jinja2.utils.htmlsafe_json_dumps`. The returned
value is marked safe by wrapping in :class:`~markupsafe.Markup`.
.. versionchanged:: 0.10
Single quotes are escaped, making this safe to use in HTML,
``<script>`` tags, and single-quoted attributes without further
escaping.
"""
import warnings
warnings.warn(
"'htmlsafe_dumps' is deprecated and will be removed in Flask"
" 2.3. Use 'jinja2.utils.htmlsafe_json_dumps' instead.",
DeprecationWarning,
stacklevel=2,
)
return _jinja_htmlsafe_dumps(obj, dumps=dumps, **kwargs)
def htmlsafe_dump(obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None:
"""Serialize an object to JSON written to a file object, replacing
HTML-unsafe characters with Unicode escapes. See
:func:`htmlsafe_dumps` and :func:`dumps`.
.. deprecated:: 2.2
Will be removed in Flask 2.3.
"""
import warnings
warnings.warn(
"'htmlsafe_dump' is deprecated and will be removed in Flask"
" 2.3. Use 'jinja2.utils.htmlsafe_json_dumps' instead.",
DeprecationWarning,
stacklevel=2,
)
fp.write(htmlsafe_dumps(obj, **kwargs))
def jsonify(*args: t.Any, **kwargs: t.Any) -> Response: def jsonify(*args: t.Any, **kwargs: t.Any) -> Response:
"""Serialize the given arguments as JSON, and return a """Serialize the given arguments as JSON, and return a
:class:`~flask.Response` object with the ``application/json`` :class:`~flask.Response` object with the ``application/json``

View file

@ -10,8 +10,6 @@ from datetime import date
from werkzeug.http import http_date from werkzeug.http import http_date
from ..globals import request
if t.TYPE_CHECKING: # pragma: no cover if t.TYPE_CHECKING: # pragma: no cover
from ..app import Flask from ..app import Flask
from ..wrappers import Response from ..wrappers import Response
@ -176,57 +174,9 @@ class DefaultJSONProvider(JSONProvider):
:param obj: The data to serialize. :param obj: The data to serialize.
:param kwargs: Passed to :func:`json.dumps`. :param kwargs: Passed to :func:`json.dumps`.
""" """
cls = self._app._json_encoder
bp = self._app.blueprints.get(request.blueprint) if request else None
if bp is not None and bp._json_encoder is not None:
cls = bp._json_encoder
if cls is not None:
import warnings
warnings.warn(
"Setting 'json_encoder' on the app or a blueprint is"
" deprecated and will be removed in Flask 2.3."
" Customize 'app.json' instead.",
DeprecationWarning,
)
kwargs.setdefault("cls", cls)
if "default" not in cls.__dict__:
kwargs.setdefault("default", self.default) kwargs.setdefault("default", self.default)
else: kwargs.setdefault("ensure_ascii", self.ensure_ascii)
kwargs.setdefault("default", self.default) kwargs.setdefault("sort_keys", self.sort_keys)
ensure_ascii = self._app.config["JSON_AS_ASCII"]
sort_keys = self._app.config["JSON_SORT_KEYS"]
if ensure_ascii is not None:
import warnings
warnings.warn(
"The 'JSON_AS_ASCII' config key is deprecated and will"
" be removed in Flask 2.3. Set 'app.json.ensure_ascii'"
" instead.",
DeprecationWarning,
)
else:
ensure_ascii = self.ensure_ascii
if sort_keys is not None:
import warnings
warnings.warn(
"The 'JSON_SORT_KEYS' config key is deprecated and will"
" be removed in Flask 2.3. Set 'app.json.sort_keys'"
" instead.",
DeprecationWarning,
)
else:
sort_keys = self.sort_keys
kwargs.setdefault("ensure_ascii", ensure_ascii)
kwargs.setdefault("sort_keys", sort_keys)
return json.dumps(obj, **kwargs) return json.dumps(obj, **kwargs)
def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any: def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any:
@ -235,23 +185,6 @@ class DefaultJSONProvider(JSONProvider):
:param s: Text or UTF-8 bytes. :param s: Text or UTF-8 bytes.
:param kwargs: Passed to :func:`json.loads`. :param kwargs: Passed to :func:`json.loads`.
""" """
cls = self._app._json_decoder
bp = self._app.blueprints.get(request.blueprint) if request else None
if bp is not None and bp._json_decoder is not None:
cls = bp._json_decoder
if cls is not None:
import warnings
warnings.warn(
"Setting 'json_decoder' on the app or a blueprint is"
" deprecated and will be removed in Flask 2.3."
" Customize 'app.json' instead.",
DeprecationWarning,
)
kwargs.setdefault("cls", cls)
return json.loads(s, **kwargs) return json.loads(s, **kwargs)
def response(self, *args: t.Any, **kwargs: t.Any) -> Response: def response(self, *args: t.Any, **kwargs: t.Any) -> Response:
@ -272,39 +205,12 @@ class DefaultJSONProvider(JSONProvider):
""" """
obj = self._prepare_response_obj(args, kwargs) obj = self._prepare_response_obj(args, kwargs)
dump_args: t.Dict[str, t.Any] = {} dump_args: t.Dict[str, t.Any] = {}
pretty = self._app.config["JSONIFY_PRETTYPRINT_REGULAR"]
mimetype = self._app.config["JSONIFY_MIMETYPE"]
if pretty is not None: if (self.compact is None and self._app.debug) or self.compact is False:
import warnings
warnings.warn(
"The 'JSONIFY_PRETTYPRINT_REGULAR' config key is"
" deprecated and will be removed in Flask 2.3. Set"
" 'app.json.compact' instead.",
DeprecationWarning,
)
compact: bool | None = not pretty
else:
compact = self.compact
if (compact is None and self._app.debug) or compact is False:
dump_args.setdefault("indent", 2) dump_args.setdefault("indent", 2)
else: else:
dump_args.setdefault("separators", (",", ":")) dump_args.setdefault("separators", (",", ":"))
if mimetype is not None:
import warnings
warnings.warn(
"The 'JSONIFY_MIMETYPE' config key is deprecated and"
" will be removed in Flask 2.3. Set 'app.json.mimetype'"
" instead.",
DeprecationWarning,
)
else:
mimetype = self.mimetype
return self._app.response_class( return self._app.response_class(
f"{self.dumps(obj, **dump_args)}\n", mimetype=mimetype f"{self.dumps(obj, **dump_args)}\n", mimetype=self.mimetype
) )

View file

@ -1,5 +1,4 @@
import importlib.util import importlib.util
import json
import os import os
import pathlib import pathlib
import pkgutil import pkgutil
@ -12,12 +11,12 @@ from functools import update_wrapper
from jinja2 import FileSystemLoader from jinja2 import FileSystemLoader
from werkzeug.exceptions import default_exceptions from werkzeug.exceptions import default_exceptions
from werkzeug.exceptions import HTTPException from werkzeug.exceptions import HTTPException
from werkzeug.utils import cached_property
from . import typing as ft from . import typing as ft
from .cli import AppGroup from .cli import AppGroup
from .globals import current_app from .globals import current_app
from .helpers import get_root_path from .helpers import get_root_path
from .helpers import locked_cached_property
from .helpers import send_from_directory from .helpers import send_from_directory
from .templating import _default_template_ctx_processor from .templating import _default_template_ctx_processor
@ -74,20 +73,6 @@ class Scaffold:
_static_folder: t.Optional[str] = None _static_folder: t.Optional[str] = None
_static_url_path: t.Optional[str] = None _static_url_path: t.Optional[str] = None
#: JSON encoder class used by :func:`flask.json.dumps`. If a
#: blueprint sets this, it will be used instead of the app's value.
#:
#: .. deprecated:: 2.2
#: Will be removed in Flask 2.3.
json_encoder: t.Union[t.Type[json.JSONEncoder], None] = None
#: JSON decoder class used by :func:`flask.json.loads`. If a
#: blueprint sets this, it will be used instead of the app's value.
#:
#: .. deprecated:: 2.2
#: Will be removed in Flask 2.3.
json_decoder: t.Union[t.Type[json.JSONDecoder], None] = None
def __init__( def __init__(
self, self,
import_name: str, import_name: str,
@ -332,7 +317,7 @@ class Scaffold:
t.cast(str, self.static_folder), filename, max_age=max_age t.cast(str, self.static_folder), filename, max_age=max_age
) )
@locked_cached_property @cached_property
def jinja_loader(self) -> t.Optional[FileSystemLoader]: def jinja_loader(self) -> t.Optional[FileSystemLoader]:
"""The Jinja loader for this object's templates. By default this """The Jinja loader for this object's templates. By default this
is a class :class:`jinja2.loaders.FileSystemLoader` to is a class :class:`jinja2.loaders.FileSystemLoader` to

View file

@ -3,11 +3,11 @@ from contextlib import contextmanager
from contextlib import ExitStack from contextlib import ExitStack
from copy import copy from copy import copy
from types import TracebackType from types import TracebackType
from urllib.parse import urlsplit
import werkzeug.test import werkzeug.test
from click.testing import CliRunner from click.testing import CliRunner
from werkzeug.test import Client from werkzeug.test import Client
from werkzeug.urls import url_parse
from werkzeug.wrappers import Request as BaseRequest from werkzeug.wrappers import Request as BaseRequest
from .cli import ScriptInfo from .cli import ScriptInfo
@ -68,7 +68,7 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder):
if url_scheme is None: if url_scheme is None:
url_scheme = app.config["PREFERRED_URL_SCHEME"] url_scheme = app.config["PREFERRED_URL_SCHEME"]
url = url_parse(path) url = urlsplit(path)
base_url = ( base_url = (
f"{url.scheme or url_scheme}://{url.netloc or http_host}" f"{url.scheme or url_scheme}://{url.netloc or http_host}"
f"/{app_root.lstrip('/')}" f"/{app_root.lstrip('/')}"

View file

@ -20,7 +20,6 @@ def _standard_os_environ():
out = ( out = (
(os.environ, "FLASK_ENV_FILE", monkeypatch.notset), (os.environ, "FLASK_ENV_FILE", monkeypatch.notset),
(os.environ, "FLASK_APP", monkeypatch.notset), (os.environ, "FLASK_APP", monkeypatch.notset),
(os.environ, "FLASK_ENV", monkeypatch.notset),
(os.environ, "FLASK_DEBUG", monkeypatch.notset), (os.environ, "FLASK_DEBUG", monkeypatch.notset),
(os.environ, "FLASK_RUN_FROM_CLI", monkeypatch.notset), (os.environ, "FLASK_RUN_FROM_CLI", monkeypatch.notset),
(os.environ, "WERKZEUG_RUN_MAIN", monkeypatch.notset), (os.environ, "WERKZEUG_RUN_MAIN", monkeypatch.notset),

2
tests/static/config.toml Normal file
View file

@ -0,0 +1,2 @@
TEST_KEY="foo"
SECRET_KEY="config"

View file

@ -95,7 +95,6 @@ def test_async_error_handler(path, async_app):
def test_async_before_after_request(): def test_async_before_after_request():
app_first_called = False
app_before_called = False app_before_called = False
app_after_called = False app_after_called = False
bp_before_called = False bp_before_called = False
@ -107,13 +106,6 @@ def test_async_before_after_request():
def index(): def index():
return "" return ""
with pytest.deprecated_call():
@app.before_first_request
async def before_first():
nonlocal app_first_called
app_first_called = True
@app.before_request @app.before_request
async def before(): async def before():
nonlocal app_before_called nonlocal app_before_called
@ -146,7 +138,6 @@ def test_async_before_after_request():
test_client = app.test_client() test_client = app.test_client()
test_client.get("/") test_client.get("/")
assert app_first_called
assert app_before_called assert app_before_called
assert app_after_called assert app_after_called
test_client.get("/bp/") test_client.get("/bp/")

View file

@ -1,16 +1,15 @@
import gc import gc
import re import re
import time
import uuid import uuid
import warnings import warnings
import weakref import weakref
from datetime import datetime from datetime import datetime
from datetime import timezone from datetime import timezone
from platform import python_implementation from platform import python_implementation
from threading import Thread
import pytest import pytest
import werkzeug.serving import werkzeug.serving
from markupsafe import Markup
from werkzeug.exceptions import BadRequest from werkzeug.exceptions import BadRequest
from werkzeug.exceptions import Forbidden from werkzeug.exceptions import Forbidden
from werkzeug.exceptions import NotFound from werkzeug.exceptions import NotFound
@ -474,7 +473,7 @@ def test_session_special_types(app, client):
def dump_session_contents(): def dump_session_contents():
flask.session["t"] = (1, 2, 3) flask.session["t"] = (1, 2, 3)
flask.session["b"] = b"\xff" flask.session["b"] = b"\xff"
flask.session["m"] = flask.Markup("<html>") flask.session["m"] = Markup("<html>")
flask.session["u"] = the_uuid flask.session["u"] = the_uuid
flask.session["d"] = now flask.session["d"] = now
flask.session["t_tag"] = {" t": "not-a-tuple"} flask.session["t_tag"] = {" t": "not-a-tuple"}
@ -488,8 +487,8 @@ def test_session_special_types(app, client):
assert s["t"] == (1, 2, 3) assert s["t"] == (1, 2, 3)
assert type(s["b"]) == bytes assert type(s["b"]) == bytes
assert s["b"] == b"\xff" assert s["b"] == b"\xff"
assert type(s["m"]) == flask.Markup assert type(s["m"]) == Markup
assert s["m"] == flask.Markup("<html>") assert s["m"] == Markup("<html>")
assert s["u"] == the_uuid assert s["u"] == the_uuid
assert s["d"] == now assert s["d"] == now
assert s["t_tag"] == {" t": "not-a-tuple"} assert s["t_tag"] == {" t": "not-a-tuple"}
@ -613,7 +612,7 @@ def test_extended_flashing(app):
def index(): def index():
flask.flash("Hello World") flask.flash("Hello World")
flask.flash("Hello World", "error") flask.flash("Hello World", "error")
flask.flash(flask.Markup("<em>Testing</em>"), "warning") flask.flash(Markup("<em>Testing</em>"), "warning")
return "" return ""
@app.route("/test/") @app.route("/test/")
@ -622,7 +621,7 @@ def test_extended_flashing(app):
assert list(messages) == [ assert list(messages) == [
"Hello World", "Hello World",
"Hello World", "Hello World",
flask.Markup("<em>Testing</em>"), Markup("<em>Testing</em>"),
] ]
return "" return ""
@ -633,7 +632,7 @@ def test_extended_flashing(app):
assert list(messages) == [ assert list(messages) == [
("message", "Hello World"), ("message", "Hello World"),
("error", "Hello World"), ("error", "Hello World"),
("warning", flask.Markup("<em>Testing</em>")), ("warning", Markup("<em>Testing</em>")),
] ]
return "" return ""
@ -652,7 +651,7 @@ def test_extended_flashing(app):
) )
assert list(messages) == [ assert list(messages) == [
("message", "Hello World"), ("message", "Hello World"),
("warning", flask.Markup("<em>Testing</em>")), ("warning", Markup("<em>Testing</em>")),
] ]
return "" return ""
@ -661,7 +660,7 @@ def test_extended_flashing(app):
messages = flask.get_flashed_messages(category_filter=["message", "warning"]) messages = flask.get_flashed_messages(category_filter=["message", "warning"])
assert len(messages) == 2 assert len(messages) == 2
assert messages[0] == "Hello World" assert messages[0] == "Hello World"
assert messages[1] == flask.Markup("<em>Testing</em>") assert messages[1] == Markup("<em>Testing</em>")
return "" return ""
# Create new test client on each test to clean flashed messages. # Create new test client on each test to clean flashed messages.
@ -1658,7 +1657,6 @@ def test_no_setup_after_first_request(app, client):
def index(): def index():
return "Awesome" return "Awesome"
assert not app.got_first_request
assert client.get("/").data == b"Awesome" assert client.get("/").data == b"Awesome"
with pytest.raises(AssertionError) as exc_info: with pytest.raises(AssertionError) as exc_info:
@ -1667,43 +1665,6 @@ def test_no_setup_after_first_request(app, client):
assert "setup method 'add_url_rule'" in str(exc_info.value) assert "setup method 'add_url_rule'" in str(exc_info.value)
def test_before_first_request_functions(app, client):
got = []
with pytest.deprecated_call():
@app.before_first_request
def foo():
got.append(42)
client.get("/")
assert got == [42]
client.get("/")
assert got == [42]
assert app.got_first_request
def test_before_first_request_functions_concurrent(app, client):
got = []
with pytest.deprecated_call():
@app.before_first_request
def foo():
time.sleep(0.2)
got.append(42)
def get_and_assert():
client.get("/")
assert got == [42]
t = Thread(target=get_and_assert)
t.start()
get_and_assert()
t.join()
assert app.got_first_request
def test_routing_redirect_debugging(monkeypatch, app, client): def test_routing_redirect_debugging(monkeypatch, app, client):
app.config["DEBUG"] = True app.config["DEBUG"] = True

View file

@ -256,6 +256,11 @@ def test_dotted_name_not_allowed(app, client):
flask.Blueprint("app.ui", __name__) flask.Blueprint("app.ui", __name__)
def test_empty_name_not_allowed(app, client):
with pytest.raises(ValueError):
flask.Blueprint("", __name__)
def test_dotted_names_from_app(app, client): def test_dotted_names_from_app(app, client):
test = flask.Blueprint("test", __name__) test = flask.Blueprint("test", __name__)
@ -722,12 +727,6 @@ def test_app_request_processing(app, client):
bp = flask.Blueprint("bp", __name__) bp = flask.Blueprint("bp", __name__)
evts = [] evts = []
with pytest.deprecated_call():
@bp.before_app_first_request
def before_first_request():
evts.append("first")
@bp.before_app_request @bp.before_app_request
def before_app(): def before_app():
evts.append("before") evts.append("before")
@ -755,12 +754,12 @@ def test_app_request_processing(app, client):
# first request # first request
resp = client.get("/").data resp = client.get("/").data
assert resp == b"request|after" assert resp == b"request|after"
assert evts == ["first", "before", "after", "teardown"] assert evts == ["before", "after", "teardown"]
# second request # second request
resp = client.get("/").data resp = client.get("/").data
assert resp == b"request|after" assert resp == b"request|after"
assert evts == ["first"] + ["before", "after", "teardown"] * 2 assert evts == ["before", "after", "teardown"] * 2
def test_app_url_processors(app, client): def test_app_url_processors(app, client):

View file

@ -6,7 +6,6 @@ import pytest
import flask import flask
# config keys used for the TestConfig # config keys used for the TestConfig
TEST_KEY = "foo" TEST_KEY = "foo"
SECRET_KEY = "config" SECRET_KEY = "config"
@ -30,13 +29,23 @@ def test_config_from_object():
common_object_test(app) common_object_test(app)
def test_config_from_file(): def test_config_from_file_json():
app = flask.Flask(__name__) app = flask.Flask(__name__)
current_dir = os.path.dirname(os.path.abspath(__file__)) current_dir = os.path.dirname(os.path.abspath(__file__))
app.config.from_file(os.path.join(current_dir, "static", "config.json"), json.load) app.config.from_file(os.path.join(current_dir, "static", "config.json"), json.load)
common_object_test(app) common_object_test(app)
def test_config_from_file_toml():
tomllib = pytest.importorskip("tomllib", reason="tomllib added in 3.11")
app = flask.Flask(__name__)
current_dir = os.path.dirname(os.path.abspath(__file__))
app.config.from_file(
os.path.join(current_dir, "static", "config.toml"), tomllib.load, text=False
)
common_object_test(app)
def test_from_prefixed_env(monkeypatch): def test_from_prefixed_env(monkeypatch):
monkeypatch.setenv("FLASK_STRING", "value") monkeypatch.setenv("FLASK_STRING", "value")
monkeypatch.setenv("FLASK_BOOL", "true") monkeypatch.setenv("FLASK_BOOL", "true")

View file

@ -302,24 +302,18 @@ class TestStreaming:
class TestHelpers: class TestHelpers:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"debug, expected_flag, expected_default_flag", ("debug", "expect"),
[ [
("", False, False), ("", False),
("0", False, False), ("0", False),
("False", False, False), ("False", False),
("No", False, False), ("No", False),
("True", True, True), ("True", True),
], ],
) )
def test_get_debug_flag( def test_get_debug_flag(self, monkeypatch, debug, expect):
self, monkeypatch, debug, expected_flag, expected_default_flag
):
monkeypatch.setenv("FLASK_DEBUG", debug) monkeypatch.setenv("FLASK_DEBUG", debug)
if expected_flag is None: assert get_debug_flag() == expect
assert get_debug_flag() is None
else:
assert get_debug_flag() == expected_flag
assert get_debug_flag() == expected_default_flag
def test_make_response(self): def test_make_response(self):
app = flask.Flask(__name__) app = flask.Flask(__name__)

View file

@ -3,8 +3,8 @@ from datetime import timezone
from uuid import uuid4 from uuid import uuid4
import pytest import pytest
from markupsafe import Markup
from flask import Markup
from flask.json.tag import JSONTag from flask.json.tag import JSONTag
from flask.json.tag import TaggedJSONSerializer from flask.json.tag import TaggedJSONSerializer

View file

@ -3,6 +3,7 @@ import logging
import pytest import pytest
import werkzeug.serving import werkzeug.serving
from jinja2 import TemplateNotFound from jinja2 import TemplateNotFound
from markupsafe import Markup
import flask import flask
@ -73,7 +74,7 @@ def test_escaping(app, client):
@app.route("/") @app.route("/")
def index(): def index():
return flask.render_template( return flask.render_template(
"escaping_template.html", text=text, html=flask.Markup(text) "escaping_template.html", text=text, html=Markup(text)
) )
lines = client.get("/").data.splitlines() lines = client.get("/").data.splitlines()
@ -93,7 +94,7 @@ def test_no_escaping(app, client):
@app.route("/") @app.route("/")
def index(): def index():
return flask.render_template( return flask.render_template(
"non_escaping_template.txt", text=text, html=flask.Markup(text) "non_escaping_template.txt", text=text, html=Markup(text)
) )
lines = client.get("/").data.splitlines() lines = client.get("/").data.splitlines()