aboutsummaryrefslogtreecommitdiffstats
path: root/contrib
diff options
context:
space:
mode:
authorrobot-piglet <robot-piglet@yandex-team.com>2025-01-16 19:09:30 +0300
committerrobot-piglet <robot-piglet@yandex-team.com>2025-01-16 19:38:51 +0300
commit7a23ad1fa2a5561a3575177d7240d8a1aa499718 (patch)
treeb4932bad31f595149e7a42e88cf729919995d735 /contrib
parentfbb15f5ab8a61fc7c50500e2757af0b47174d825 (diff)
downloadydb-7a23ad1fa2a5561a3575177d7240d8a1aa499718.tar.gz
Intermediate changes
commit_hash:ae9e37c897fc6d514389f7089184df33bf781005
Diffstat (limited to 'contrib')
-rw-r--r--contrib/python/Flask/py3/.dist-info/METADATA4
-rw-r--r--contrib/python/Flask/py3/flask/__init__.py36
-rw-r--r--contrib/python/Flask/py3/flask/app.py811
-rw-r--r--contrib/python/Flask/py3/flask/blueprints.py271
-rw-r--r--contrib/python/Flask/py3/flask/cli.py418
-rw-r--r--contrib/python/Flask/py3/flask/config.py5
-rw-r--r--contrib/python/Flask/py3/flask/ctx.py211
-rw-r--r--contrib/python/Flask/py3/flask/debughelpers.py22
-rw-r--r--contrib/python/Flask/py3/flask/globals.py122
-rw-r--r--contrib/python/Flask/py3/flask/helpers.py364
-rw-r--r--contrib/python/Flask/py3/flask/json/__init__.py364
-rw-r--r--contrib/python/Flask/py3/flask/json/provider.py310
-rw-r--r--contrib/python/Flask/py3/flask/logging.py2
-rw-r--r--contrib/python/Flask/py3/flask/scaffold.py241
-rw-r--r--contrib/python/Flask/py3/flask/sessions.py22
-rw-r--r--contrib/python/Flask/py3/flask/templating.py114
-rw-r--r--contrib/python/Flask/py3/flask/testing.py140
-rw-r--r--contrib/python/Flask/py3/flask/typing.py40
-rw-r--r--contrib/python/Flask/py3/flask/views.py188
-rw-r--r--contrib/python/Flask/py3/flask/wrappers.py4
-rw-r--r--contrib/python/Flask/py3/ya.make3
21 files changed, 2363 insertions, 1329 deletions
diff --git a/contrib/python/Flask/py3/.dist-info/METADATA b/contrib/python/Flask/py3/.dist-info/METADATA
index 343d75d101..2526be4af0 100644
--- a/contrib/python/Flask/py3/.dist-info/METADATA
+++ b/contrib/python/Flask/py3/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: Flask
-Version: 2.1.3
+Version: 2.2.5
Summary: A simple framework for building complex web applications.
Home-page: https://palletsprojects.com/p/flask
Author: Armin Ronacher
@@ -29,7 +29,7 @@ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Requires-Python: >=3.7
Description-Content-Type: text/x-rst
License-File: LICENSE.rst
-Requires-Dist: Werkzeug (>=2.0)
+Requires-Dist: Werkzeug (>=2.2.2)
Requires-Dist: Jinja2 (>=3.0)
Requires-Dist: itsdangerous (>=2.0)
Requires-Dist: click (>=8.0)
diff --git a/contrib/python/Flask/py3/flask/__init__.py b/contrib/python/Flask/py3/flask/__init__.py
index a970b8a1a8..19993402ff 100644
--- a/contrib/python/Flask/py3/flask/__init__.py
+++ b/contrib/python/Flask/py3/flask/__init__.py
@@ -1,7 +1,5 @@
from markupsafe import escape
from markupsafe import Markup
-from werkzeug.exceptions import abort as abort
-from werkzeug.utils import redirect as redirect
from . import json as json
from .app import Flask as Flask
@@ -13,16 +11,16 @@ from .ctx import after_this_request as after_this_request
from .ctx import copy_current_request_context as copy_current_request_context
from .ctx import has_app_context as has_app_context
from .ctx import has_request_context as has_request_context
-from .globals import _app_ctx_stack as _app_ctx_stack
-from .globals import _request_ctx_stack as _request_ctx_stack
from .globals import current_app as current_app
from .globals import g as g
from .globals import request as request
from .globals import session as session
+from .helpers import abort as abort
from .helpers import flash as flash
from .helpers import get_flashed_messages as get_flashed_messages
from .helpers import get_template_attribute as get_template_attribute
from .helpers import make_response as make_response
+from .helpers import redirect as redirect
from .helpers import send_file as send_file
from .helpers import send_from_directory as send_from_directory
from .helpers import stream_with_context as stream_with_context
@@ -41,5 +39,33 @@ from .signals import signals_available as signals_available
from .signals import template_rendered as template_rendered
from .templating import render_template as render_template
from .templating import render_template_string as render_template_string
+from .templating import stream_template as stream_template
+from .templating import stream_template_string as stream_template_string
-__version__ = "2.1.3"
+__version__ = "2.2.5"
+
+
+def __getattr__(name):
+ if name == "_app_ctx_stack":
+ import warnings
+ from .globals import __app_ctx_stack
+
+ warnings.warn(
+ "'_app_ctx_stack' is deprecated and will be removed in Flask 2.3.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return __app_ctx_stack
+
+ if name == "_request_ctx_stack":
+ import warnings
+ from .globals import __request_ctx_stack
+
+ warnings.warn(
+ "'_request_ctx_stack' is deprecated and will be removed in Flask 2.3.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return __request_ctx_stack
+
+ raise AttributeError(name)
diff --git a/contrib/python/Flask/py3/flask/app.py b/contrib/python/Flask/py3/flask/app.py
index 6b54918887..d904d6ba38 100644
--- a/contrib/python/Flask/py3/flask/app.py
+++ b/contrib/python/Flask/py3/flask/app.py
@@ -1,17 +1,22 @@
import functools
import inspect
+import json
import logging
import os
import sys
import typing as t
import weakref
+from collections.abc import Iterator as _abc_Iterator
from datetime import timedelta
from itertools import chain
from threading import Lock
from types import TracebackType
+from urllib.parse import quote as _url_quote
+import click
from werkzeug.datastructures import Headers
from werkzeug.datastructures import ImmutableDict
+from werkzeug.exceptions import Aborter
from werkzeug.exceptions import BadRequest
from werkzeug.exceptions import BadRequestKeyError
from werkzeug.exceptions import HTTPException
@@ -22,28 +27,30 @@ from werkzeug.routing import MapAdapter
from werkzeug.routing import RequestRedirect
from werkzeug.routing import RoutingException
from werkzeug.routing import Rule
+from werkzeug.serving import is_running_from_reloader
+from werkzeug.utils import redirect as _wz_redirect
from werkzeug.wrappers import Response as BaseResponse
from . import cli
-from . import json
from . import typing as ft
from .config import Config
from .config import ConfigAttribute
from .ctx import _AppCtxGlobals
from .ctx import AppContext
from .ctx import RequestContext
-from .globals import _request_ctx_stack
+from .globals import _cv_app
+from .globals import _cv_request
from .globals import g
from .globals import request
+from .globals import request_ctx
from .globals import session
from .helpers import _split_blueprint_path
from .helpers import get_debug_flag
-from .helpers import get_env
from .helpers import get_flashed_messages
from .helpers import get_load_dotenv
from .helpers import locked_cached_property
-from .helpers import url_for
-from .json import jsonify
+from .json.provider import DefaultJSONProvider
+from .json.provider import JSONProvider
from .logging import create_logger
from .scaffold import _endpoint_from_view_func
from .scaffold import _sentinel
@@ -62,12 +69,23 @@ from .templating import Environment
from .wrappers import Request
from .wrappers import Response
-if t.TYPE_CHECKING:
+if t.TYPE_CHECKING: # pragma: no cover
import typing_extensions as te
from .blueprints import Blueprint
from .testing import FlaskClient
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", bound=ft.ShellContextProcessorCallable
+)
+T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
+T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable)
+T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable)
+T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable)
+
if sys.version_info >= (3, 8):
iscoroutinefunction = inspect.iscoroutinefunction
else:
@@ -82,7 +100,7 @@ else:
return inspect.iscoroutinefunction(func)
-def _make_timedelta(value: t.Optional[timedelta]) -> t.Optional[timedelta]:
+def _make_timedelta(value: t.Union[timedelta, int, None]) -> t.Optional[timedelta]:
if value is None or isinstance(value, timedelta):
return value
@@ -194,6 +212,16 @@ class Flask(Scaffold):
#: :class:`~flask.Response` for more information.
response_class = Response
+ #: The class of the object assigned to :attr:`aborter`, created by
+ #: :meth:`create_aborter`. That object is called by
+ #: :func:`flask.abort` to raise HTTP errors, and can be
+ #: called directly as well.
+ #:
+ #: Defaults to :class:`werkzeug.exceptions.Aborter`.
+ #:
+ #: .. versionadded:: 2.2
+ aborter_class = Aborter
+
#: The class that is used for the Jinja environment.
#:
#: .. versionadded:: 0.11
@@ -246,11 +274,35 @@ class Flask(Scaffold):
#: :data:`SECRET_KEY` configuration key. Defaults to ``None``.
secret_key = ConfigAttribute("SECRET_KEY")
- #: The secure cookie uses this for the name of the session cookie.
- #:
- #: This attribute can also be configured from the config with the
- #: ``SESSION_COOKIE_NAME`` configuration key. Defaults to ``'session'``
- session_cookie_name = ConfigAttribute("SESSION_COOKIE_NAME")
+ @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
#: date of a permanent session. The default is 31 days which makes a
@@ -263,39 +315,163 @@ class Flask(Scaffold):
"PERMANENT_SESSION_LIFETIME", get_converter=_make_timedelta
)
- #: A :class:`~datetime.timedelta` or number of seconds which is used
- #: as the default ``max_age`` for :func:`send_file`. The default is
- #: ``None``, which tells the browser to use conditional requests
- #: instead of a timed cache.
- #:
- #: Configured with the :data:`SEND_FILE_MAX_AGE_DEFAULT`
- #: configuration key.
- #:
- #: .. versionchanged:: 2.0
- #: Defaults to ``None`` instead of 12 hours.
- send_file_max_age_default = ConfigAttribute(
- "SEND_FILE_MAX_AGE_DEFAULT", 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.
- #: Enable this if you want to use the X-Sendfile feature. Keep in
- #: mind that the server has to support this. This only affects files
- #: sent with the :func:`send_file` method.
- #:
- #: .. versionadded:: 0.2
- #:
- #: This attribute can also be configured from the config with the
- #: ``USE_X_SENDFILE`` configuration key. Defaults to ``False``.
- use_x_sendfile = ConfigAttribute("USE_X_SENDFILE")
+ .. deprecated:: 2.2
+ Will be removed in Flask 2.3. Use
+ ``app.config["SEND_FILE_MAX_AGE_DEFAULT"]`` instead.
- #: The JSON encoder class to use. Defaults to :class:`~flask.json.JSONEncoder`.
- #:
- #: .. versionadded:: 0.10
- json_encoder = json.JSONEncoder
+ .. versionchanged:: 2.0
+ Defaults to ``None`` instead of 12 hours.
+ """
+ import warnings
- #: The JSON decoder class to use. Defaults to :class:`~flask.json.JSONDecoder`.
- #:
- #: .. versionadded:: 0.10
- json_decoder = json.JSONDecoder
+ 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
+ """A subclass of :class:`~flask.json.provider.JSONProvider`. An
+ instance is created and assigned to :attr:`app.json` when creating
+ the app.
+
+ The default, :class:`~flask.json.provider.DefaultJSONProvider`, uses
+ Python's built-in :mod:`json` library. A different provider can use
+ a different JSON library.
+
+ .. versionadded:: 2.2
+ """
#: Options that are passed to the Jinja environment in
#: :meth:`create_jinja_environment`. Changing these options after
@@ -315,7 +491,6 @@ class Flask(Scaffold):
"DEBUG": None,
"TESTING": False,
"PROPAGATE_EXCEPTIONS": None,
- "PRESERVE_CONTEXT_ON_EXCEPTION": None,
"SECRET_KEY": None,
"PERMANENT_SESSION_LIFETIME": timedelta(days=31),
"USE_X_SENDFILE": False,
@@ -334,10 +509,10 @@ class Flask(Scaffold):
"TRAP_HTTP_EXCEPTIONS": False,
"EXPLAIN_TEMPLATE_LOADING": False,
"PREFERRED_URL_SCHEME": "http",
- "JSON_AS_ASCII": True,
- "JSON_SORT_KEYS": True,
- "JSONIFY_PRETTYPRINT_REGULAR": False,
- "JSONIFY_MIMETYPE": "application/json",
+ "JSON_AS_ASCII": None,
+ "JSON_SORT_KEYS": None,
+ "JSONIFY_PRETTYPRINT_REGULAR": None,
+ "JSONIFY_MIMETYPE": None,
"TEMPLATES_AUTO_RELOAD": None,
"MAX_COOKIE_SIZE": 4093,
}
@@ -383,7 +558,7 @@ class Flask(Scaffold):
static_host: t.Optional[str] = None,
host_matching: bool = False,
subdomain_matching: bool = False,
- template_folder: t.Optional[str] = "templates",
+ template_folder: t.Optional[t.Union[str, os.PathLike]] = "templates",
instance_path: t.Optional[str] = None,
instance_relative_config: bool = False,
root_path: t.Optional[str] = None,
@@ -414,21 +589,50 @@ class Flask(Scaffold):
#: to load a config from files.
self.config = self.make_config(instance_relative_config)
- #: A list of functions that are called when :meth:`url_for` raises a
- #: :exc:`~werkzeug.routing.BuildError`. Each function registered here
- #: is called with `error`, `endpoint` and `values`. If a function
- #: returns ``None`` or raises a :exc:`BuildError` the next function is
- #: tried.
+ #: An instance of :attr:`aborter_class` created by
+ #: :meth:`make_aborter`. This is called by :func:`flask.abort`
+ #: to raise HTTP errors, and can be called directly as well.
+ #:
+ #: .. versionadded:: 2.2
+ #: Moved from ``flask.abort``, which calls this object.
+ self.aborter = self.make_aborter()
+
+ self.json: JSONProvider = self.json_provider_class(self)
+ """Provides access to JSON methods. Functions in ``flask.json``
+ will call methods on this provider when the application context
+ is active. Used for handling JSON requests and responses.
+
+ An instance of :attr:`json_provider_class`. Can be customized by
+ changing that attribute on a subclass, or by assigning to this
+ attribute afterwards.
+
+ The default, :class:`~flask.json.provider.DefaultJSONProvider`,
+ uses Python's built-in :mod:`json` library. A different provider
+ can use a different JSON library.
+
+ .. versionadded:: 2.2
+ """
+
+ #: A list of functions that are called by
+ #: :meth:`handle_url_build_error` when :meth:`.url_for` raises a
+ #: :exc:`~werkzeug.routing.BuildError`. Each function is called
+ #: with ``error``, ``endpoint`` and ``values``. If a function
+ #: returns ``None`` or raises a ``BuildError``, it is skipped.
+ #: Otherwise, its return value is returned by ``url_for``.
#:
#: .. versionadded:: 0.9
self.url_build_error_handlers: t.List[
- t.Callable[[Exception, str, dict], 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] = []
@@ -444,7 +648,7 @@ class Flask(Scaffold):
#: when a shell context is created.
#:
#: .. versionadded:: 0.11
- self.shell_context_processors: t.List[t.Callable[[], t.Dict[str, t.Any]]] = []
+ self.shell_context_processors: t.List[ft.ShellContextProcessorCallable] = []
#: Maps registered blueprint names to blueprint objects. The
#: dict retains the order the blueprints were registered in.
@@ -513,8 +717,17 @@ class Flask(Scaffold):
# the app's commands to another CLI tool.
self.cli.name = self.name
- def _is_setup_finished(self) -> bool:
- return self.debug and self._got_first_request
+ def _check_setup_finished(self, f_name: str) -> None:
+ if self._got_first_request:
+ raise AssertionError(
+ f"The setup method '{f_name}' can no longer be called"
+ " on the application. It has already handled its first"
+ " request, any changes will not be applied"
+ " consistently.\n"
+ "Make sure all imports, decorators, functions, etc."
+ " needed to set up the application are done before"
+ " running it."
+ )
@locked_cached_property
def name(self) -> str: # type: ignore
@@ -538,26 +751,23 @@ class Flask(Scaffold):
"""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
- @property
- def preserve_context_on_exception(self) -> bool:
- """Returns the value of the ``PRESERVE_CONTEXT_ON_EXCEPTION``
- configuration value in case it's set, otherwise a sensible default
- is returned.
-
- .. versionadded:: 0.7
- """
- rv = self.config["PRESERVE_CONTEXT_ON_EXCEPTION"]
- if rv is not None:
- return rv
- return self.debug
-
@locked_cached_property
def logger(self) -> logging.Logger:
"""A standard Python :class:`~logging.Logger` for the app, with
@@ -617,10 +827,22 @@ class Flask(Scaffold):
if instance_relative:
root_path = self.instance_path
defaults = dict(self.default_config)
- defaults["ENV"] = get_env()
+ defaults["ENV"] = os.environ.get("FLASK_ENV") or "production"
defaults["DEBUG"] = get_debug_flag()
return self.config_class(root_path, defaults)
+ def make_aborter(self) -> Aborter:
+ """Create the object to assign to :attr:`aborter`. That object
+ is called by :func:`flask.abort` to raise HTTP errors, and can
+ be called directly as well.
+
+ By default, this creates an instance of :attr:`aborter_class`,
+ which defaults to :class:`werkzeug.exceptions.Aborter`.
+
+ .. versionadded:: 2.2
+ """
+ return self.aborter_class()
+
def auto_find_instance_path(self) -> str:
"""Tries to locate the instance path if it was not provided to the
constructor of the application class. It will basically calculate
@@ -649,20 +871,37 @@ class Flask(Scaffold):
@property
def templates_auto_reload(self) -> bool:
"""Reload templates when they are changed. Used by
- :meth:`create_jinja_environment`.
+ :meth:`create_jinja_environment`. It is enabled by default in debug mode.
- This attribute can be configured with :data:`TEMPLATES_AUTO_RELOAD`. If
- not set, it will be enabled 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:
@@ -683,11 +922,16 @@ class Flask(Scaffold):
options["autoescape"] = self.select_jinja_autoescape
if "auto_reload" not in options:
- options["auto_reload"] = self.templates_auto_reload
+ auto_reload = self.config["TEMPLATES_AUTO_RELOAD"]
+
+ if auto_reload is None:
+ auto_reload = self.debug
+
+ options["auto_reload"] = auto_reload
rv = self.jinja_environment(self, **options)
rv.globals.update(
- url_for=url_for,
+ url_for=self.url_for,
get_flashed_messages=get_flashed_messages,
config=self.config,
# request, session and g are normally added with the
@@ -697,7 +941,7 @@ class Flask(Scaffold):
session=session,
g=g,
)
- rv.policies["json.dumps_function"] = json.dumps
+ rv.policies["json.dumps_function"] = self.json.dumps
return rv
def create_global_jinja_loader(self) -> DispatchingJinjaLoader:
@@ -717,11 +961,14 @@ class Flask(Scaffold):
"""Returns ``True`` if autoescaping should be active for the given
template name. If no template name is given, returns `True`.
+ .. versionchanged:: 2.2
+ Autoescaping is now enabled by default for ``.svg`` files.
+
.. versionadded:: 0.5
"""
if filename is None:
return True
- return filename.endswith((".html", ".htm", ".xml", ".xhtml"))
+ return filename.endswith((".html", ".htm", ".xml", ".xhtml", ".svg"))
def update_template_context(self, context: dict) -> None:
"""Update the template context with some commonly used variables.
@@ -763,38 +1010,59 @@ class Flask(Scaffold):
rv.update(processor())
return rv
- #: What environment the app is running in. Flask and extensions may
- #: enable behaviors based on the environment, such as enabling debug
- #: mode. This maps to the :data:`ENV` config key. This is set by the
- #: :envvar:`FLASK_ENV` environment variable and may not behave as
- #: expected if set in code.
- #:
- #: **Do not enable development when deploying in production.**
- #:
- #: Default: ``'production'``
- env = ConfigAttribute("ENV")
+ @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
def debug(self) -> bool:
- """Whether debug mode is enabled. When using ``flask run`` to start
- the development server, an interactive debugger will be shown for
- unhandled exceptions, and the server will be reloaded when code
- changes. This maps to the :data:`DEBUG` config key. This is
- enabled when :attr:`env` is ``'development'`` and is overridden
- by the ``FLASK_DEBUG`` environment variable. It may not behave as
- expected if set in code.
+ """Whether debug mode is enabled. When using ``flask run`` to start the
+ development server, an interactive debugger will be shown for unhandled
+ exceptions, and the server will be reloaded when code changes. This maps to the
+ :data:`DEBUG` config key. It may not behave as expected if set late.
**Do not enable debug mode when deploying in production.**
- Default: ``True`` if :attr:`env` is ``'development'``, or
- ``False`` otherwise.
+ Default: ``False``
"""
return self.config["DEBUG"]
@debug.setter
def debug(self, value: bool) -> None:
self.config["DEBUG"] = value
- self.jinja_env.auto_reload = self.templates_auto_reload
+
+ if self.config["TEMPLATES_AUTO_RELOAD"] is None:
+ self.jinja_env.auto_reload = value
def run(
self,
@@ -851,9 +1119,7 @@ class Flask(Scaffold):
If installed, python-dotenv will be used to load environment
variables from :file:`.env` and :file:`.flaskenv` files.
- If set, the :envvar:`FLASK_ENV` and :envvar:`FLASK_DEBUG`
- environment variables will override :attr:`env` and
- :attr:`debug`.
+ The :envvar:`FLASK_DEBUG` environment variable will override :attr:`debug`.
Threaded mode is enabled by default.
@@ -861,12 +1127,18 @@ class Flask(Scaffold):
The default port is now picked from the ``SERVER_NAME``
variable.
"""
- # Change this into a no-op if the server is invoked from the
- # command line. Have a look at cli.py for more information.
+ # Ignore this call so that it doesn't start another server if
+ # the 'flask run' command is used.
if os.environ.get("FLASK_RUN_FROM_CLI") == "true":
- from .debughelpers import explain_ignored_app_run
+ if not is_running_from_reloader():
+ click.secho(
+ " * Ignoring a call to 'app.run()' that would block"
+ " the current 'flask' CLI command.\n"
+ " Only call 'app.run()' in an 'if __name__ =="
+ ' "__main__"\' guard.',
+ fg="red",
+ )
- explain_ignored_app_run()
return
if get_load_dotenv(load_dotenv):
@@ -874,7 +1146,12 @@ class Flask(Scaffold):
# if set, let env vars override previous values
if "FLASK_ENV" in os.environ:
- self.env = get_env()
+ 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()
@@ -906,7 +1183,7 @@ class Flask(Scaffold):
options.setdefault("use_debugger", self.debug)
options.setdefault("threaded", True)
- cli.show_server_banner(self.env, self.debug, self.name, False)
+ cli.show_server_banner(self.debug, self.name)
from werkzeug.serving import run_simple
@@ -971,7 +1248,7 @@ class Flask(Scaffold):
"""
cls = self.test_client_class
if cls is None:
- from .testing import FlaskClient as cls # type: ignore
+ from .testing import FlaskClient as cls
return cls( # type: ignore
self, self.response_class, use_cookies=use_cookies, **kwargs
)
@@ -989,7 +1266,7 @@ class Flask(Scaffold):
cls = self.test_cli_runner_class
if cls is None:
- from .testing import FlaskCliRunner as cls # type: ignore
+ from .testing import FlaskCliRunner as cls
return cls(self, **kwargs) # type: ignore
@@ -1033,7 +1310,7 @@ class Flask(Scaffold):
self,
rule: str,
endpoint: t.Optional[str] = None,
- view_func: t.Optional[ft.ViewCallable] = None,
+ view_func: t.Optional[ft.RouteCallable] = None,
provide_automatic_options: t.Optional[bool] = None,
**options: t.Any,
) -> None:
@@ -1090,7 +1367,7 @@ class Flask(Scaffold):
@setupmethod
def template_filter(
self, name: t.Optional[str] = None
- ) -> t.Callable[[ft.TemplateFilterCallable], ft.TemplateFilterCallable]:
+ ) -> t.Callable[[T_template_filter], T_template_filter]:
"""A decorator that is used to register custom template filter.
You can specify a name for the filter, otherwise the function
name will be used. Example::
@@ -1103,7 +1380,7 @@ class Flask(Scaffold):
function name will be used.
"""
- def decorator(f: ft.TemplateFilterCallable) -> ft.TemplateFilterCallable:
+ def decorator(f: T_template_filter) -> T_template_filter:
self.add_template_filter(f, name=name)
return f
@@ -1124,7 +1401,7 @@ class Flask(Scaffold):
@setupmethod
def template_test(
self, name: t.Optional[str] = None
- ) -> t.Callable[[ft.TemplateTestCallable], ft.TemplateTestCallable]:
+ ) -> t.Callable[[T_template_test], T_template_test]:
"""A decorator that is used to register custom template test.
You can specify a name for the test, otherwise the function
name will be used. Example::
@@ -1144,7 +1421,7 @@ class Flask(Scaffold):
function name will be used.
"""
- def decorator(f: ft.TemplateTestCallable) -> ft.TemplateTestCallable:
+ def decorator(f: T_template_test) -> T_template_test:
self.add_template_test(f, name=name)
return f
@@ -1167,7 +1444,7 @@ class Flask(Scaffold):
@setupmethod
def template_global(
self, name: t.Optional[str] = None
- ) -> t.Callable[[ft.TemplateGlobalCallable], ft.TemplateGlobalCallable]:
+ ) -> t.Callable[[T_template_global], T_template_global]:
"""A decorator that is used to register a custom template global function.
You can specify a name for the global function, otherwise the function
name will be used. Example::
@@ -1182,7 +1459,7 @@ class Flask(Scaffold):
function name will be used.
"""
- def decorator(f: ft.TemplateGlobalCallable) -> ft.TemplateGlobalCallable:
+ def decorator(f: T_template_global) -> T_template_global:
self.add_template_global(f, name=name)
return f
@@ -1203,45 +1480,57 @@ class Flask(Scaffold):
self.jinja_env.globals[name or f.__name__] = f
@setupmethod
- def before_first_request(
- self, f: ft.BeforeFirstRequestCallable
- ) -> ft.BeforeFirstRequestCallable:
+ 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
- def teardown_appcontext(self, f: ft.TeardownCallable) -> ft.TeardownCallable:
- """Registers a function to be called when the application context
- ends. These functions are typically also called when the request
- context is popped.
+ def teardown_appcontext(self, f: T_teardown) -> T_teardown:
+ """Registers a function to be called when the application
+ context is popped. The application context is typically popped
+ after the request context for each request, at the end of CLI
+ commands, or after a manually pushed context ends.
- Example::
+ .. code-block:: python
- ctx = app.app_context()
- ctx.push()
- ...
- ctx.pop()
+ with app.app_context():
+ ...
- When ``ctx.pop()`` is executed in the above example, the teardown
- functions are called just before the app context moves from the
- stack of active contexts. This becomes relevant if you are using
- such constructs in tests.
+ When the ``with`` block exits (or ``ctx.pop()`` is called), the
+ teardown functions are called just before the app context is
+ made inactive. Since a request context typically also manages an
+ application context it would also be called when you pop a
+ request context.
- Since a request context typically also manages an application
- context it would also be called when you pop a request context.
+ When a teardown function was called because of an unhandled
+ exception it will be passed an error object. If an
+ :meth:`errorhandler` is registered, it will handle the exception
+ and the teardown will not receive it.
- When a teardown function was called because of an unhandled exception
- it will be passed an error object. If an :meth:`errorhandler` is
- registered, it will handle the exception and the teardown will not
- receive it.
+ Teardown functions must avoid raising exceptions. If they
+ execute code that might fail they must surround that code with a
+ ``try``/``except`` block and log any errors.
The return values of teardown functions are ignored.
@@ -1251,7 +1540,9 @@ class Flask(Scaffold):
return f
@setupmethod
- def shell_context_processor(self, f: t.Callable) -> t.Callable:
+ def shell_context_processor(
+ self, f: T_shell_context_processor
+ ) -> T_shell_context_processor:
"""Registers a shell context processor function.
.. versionadded:: 0.11
@@ -1414,8 +1705,12 @@ class Flask(Scaffold):
"""
exc_info = sys.exc_info()
got_request_exception.send(self, exception=e)
+ propagate = self.config["PROPAGATE_EXCEPTIONS"]
+
+ if propagate is None:
+ propagate = self.testing or self.debug
- if self.propagate_exceptions:
+ if propagate:
# Re-raise if called with an active exception, otherwise
# raise the passed in exception.
if exc_info[1] is e:
@@ -1488,10 +1783,10 @@ class Flask(Scaffold):
This no longer does the exception handling, this code was
moved to the new :meth:`full_dispatch_request`.
"""
- req = _request_ctx_stack.top.request
+ req = request_ctx.request
if req.routing_exception is not None:
self.raise_routing_exception(req)
- rule = req.url_rule
+ rule: Rule = req.url_rule # type: ignore[assignment]
# if we provide automatic options for this URL and the
# request came with the OPTIONS method, reply automatically
if (
@@ -1500,7 +1795,8 @@ class Flask(Scaffold):
):
return self.make_default_options_response()
# otherwise dispatch to the handler for that endpoint
- return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
+ view_args: t.Dict[str, t.Any] = req.view_args # type: ignore[assignment]
+ return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
def full_dispatch_request(self) -> Response:
"""Dispatches the request and on top of that performs request
@@ -1509,7 +1805,17 @@ class Flask(Scaffold):
.. versionadded:: 0.7
"""
- self.try_trigger_before_first_request_functions()
+ # 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
+
try:
request_started.send(self)
rv = self.preprocess_request()
@@ -1548,22 +1854,6 @@ class Flask(Scaffold):
)
return response
- def try_trigger_before_first_request_functions(self) -> None:
- """Called before each request and will ensure that it triggers
- the :attr:`before_first_request_funcs` and only exactly once per
- application instance (which means process usually).
-
- :internal:
- """
- if self._got_first_request:
- return
- with self._before_request_lock:
- if self._got_first_request:
- return
- for func in self.before_first_request_funcs:
- self.ensure_sync(func)()
- self._got_first_request = True
-
def make_default_options_response(self) -> Response:
"""This method is called to create the default ``OPTIONS`` response.
This can be changed through subclassing to change the default
@@ -1571,8 +1861,8 @@ class Flask(Scaffold):
.. versionadded:: 0.7
"""
- adapter = _request_ctx_stack.top.url_adapter
- methods = adapter.allowed_methods()
+ adapter = request_ctx.url_adapter
+ methods = adapter.allowed_methods() # type: ignore[union-attr]
rv = self.response_class()
rv.allow.update(methods)
return rv
@@ -1624,6 +1914,145 @@ class Flask(Scaffold):
return asgiref_async_to_sync(func)
+ def url_for(
+ self,
+ endpoint: str,
+ *,
+ _anchor: t.Optional[str] = None,
+ _method: t.Optional[str] = None,
+ _scheme: t.Optional[str] = None,
+ _external: t.Optional[bool] = None,
+ **values: t.Any,
+ ) -> str:
+ """Generate a URL to the given endpoint with the given values.
+
+ This is called by :func:`flask.url_for`, and can be called
+ directly as well.
+
+ An *endpoint* is the name of a URL rule, usually added with
+ :meth:`@app.route() <route>`, and usually the same name as the
+ view function. A route defined in a :class:`~flask.Blueprint`
+ will prepend the blueprint's name separated by a ``.`` to the
+ endpoint.
+
+ In some cases, such as email messages, you want URLs to include
+ the scheme and domain, like ``https://example.com/hello``. When
+ not in an active request, URLs will be external by default, but
+ this requires setting :data:`SERVER_NAME` so Flask knows what
+ domain to use. :data:`APPLICATION_ROOT` and
+ :data:`PREFERRED_URL_SCHEME` should also be configured as
+ needed. This config is only used when not in an active request.
+
+ Functions can be decorated with :meth:`url_defaults` to modify
+ keyword arguments before the URL is built.
+
+ If building fails for some reason, such as an unknown endpoint
+ or incorrect values, the app's :meth:`handle_url_build_error`
+ method is called. If that returns a string, that is returned,
+ otherwise a :exc:`~werkzeug.routing.BuildError` is raised.
+
+ :param endpoint: The endpoint name associated with the URL to
+ generate. If this starts with a ``.``, the current blueprint
+ name (if any) will be used.
+ :param _anchor: If given, append this as ``#anchor`` to the URL.
+ :param _method: If given, generate the URL associated with this
+ method for the endpoint.
+ :param _scheme: If given, the URL will have this scheme if it
+ is external.
+ :param _external: If given, prefer the URL to be internal
+ (False) or require it to be external (True). External URLs
+ include the scheme and domain. When not in an active
+ request, URLs are external by default.
+ :param values: Values to use for the variable parts of the URL
+ rule. Unknown keys are appended as query string arguments,
+ like ``?a=b&c=d``.
+
+ .. versionadded:: 2.2
+ Moved from ``flask.url_for``, which calls this method.
+ """
+ req_ctx = _cv_request.get(None)
+
+ if req_ctx is not None:
+ url_adapter = req_ctx.url_adapter
+ blueprint_name = req_ctx.request.blueprint
+
+ # If the endpoint starts with "." and the request matches a
+ # blueprint, the endpoint is relative to the blueprint.
+ if endpoint[:1] == ".":
+ if blueprint_name is not None:
+ endpoint = f"{blueprint_name}{endpoint}"
+ else:
+ endpoint = endpoint[1:]
+
+ # When in a request, generate a URL without scheme and
+ # domain by default, unless a scheme is given.
+ if _external is None:
+ _external = _scheme is not None
+ else:
+ app_ctx = _cv_app.get(None)
+
+ # If called by helpers.url_for, an app context is active,
+ # use its url_adapter. Otherwise, app.url_for was called
+ # directly, build an adapter.
+ if app_ctx is not None:
+ url_adapter = app_ctx.url_adapter
+ else:
+ url_adapter = self.create_url_adapter(None)
+
+ if url_adapter is None:
+ raise RuntimeError(
+ "Unable to build URLs outside an active request"
+ " without 'SERVER_NAME' configured. Also configure"
+ " 'APPLICATION_ROOT' and 'PREFERRED_URL_SCHEME' as"
+ " needed."
+ )
+
+ # When outside a request, generate a URL with scheme and
+ # domain by default.
+ if _external is None:
+ _external = True
+
+ # It is an error to set _scheme when _external=False, in order
+ # to avoid accidental insecure URLs.
+ if _scheme is not None and not _external:
+ raise ValueError("When specifying '_scheme', '_external' must be True.")
+
+ self.inject_url_defaults(endpoint, values)
+
+ try:
+ rv = url_adapter.build( # type: ignore[union-attr]
+ endpoint,
+ values,
+ method=_method,
+ url_scheme=_scheme,
+ force_external=_external,
+ )
+ except BuildError as error:
+ values.update(
+ _anchor=_anchor, _method=_method, _scheme=_scheme, _external=_external
+ )
+ return self.handle_url_build_error(error, endpoint, values)
+
+ if _anchor is not None:
+ _anchor = _url_quote(_anchor, safe="%!#$&'()*+,/:;=?@")
+ rv = f"{rv}#{_anchor}"
+
+ return rv
+
+ def redirect(self, location: str, code: int = 302) -> BaseResponse:
+ """Create a redirect response object.
+
+ This is called by :func:`flask.redirect`, and can be called
+ directly as well.
+
+ :param location: The URL to redirect to.
+ :param code: The status code for the redirect.
+
+ .. versionadded:: 2.2
+ Moved from ``flask.redirect``, which calls this method.
+ """
+ return _wz_redirect(location, code=code, Response=self.response_class)
+
def make_response(self, rv: ft.ResponseReturnValue) -> Response:
"""Convert the return value from a view function to an instance of
:attr:`response_class`.
@@ -1643,6 +2072,13 @@ class Flask(Scaffold):
``dict``
A dictionary that will be jsonify'd before being returned.
+ ``list``
+ A list that will be jsonify'd before being returned.
+
+ ``generator`` or ``iterator``
+ A generator that returns ``str`` or ``bytes`` to be
+ streamed as the response.
+
``tuple``
Either ``(body, status, headers)``, ``(body, status)``, or
``(body, headers)``, where ``body`` is any of the other types
@@ -1662,6 +2098,13 @@ class Flask(Scaffold):
The function is called as a WSGI application. The result is
used to create a response object.
+ .. versionchanged:: 2.2
+ A generator will be converted to a streaming response.
+ A list will be converted to a JSON response.
+
+ .. versionchanged:: 1.1
+ A dict will be converted to a JSON response.
+
.. versionchanged:: 0.9
Previously a tuple was interpreted as the arguments for the
response object.
@@ -1700,7 +2143,7 @@ class Flask(Scaffold):
# make sure the body is an instance of the response class
if not isinstance(rv, self.response_class):
- if isinstance(rv, (str, bytes, bytearray)):
+ if isinstance(rv, (str, bytes, bytearray)) or isinstance(rv, _abc_Iterator):
# let the response class set the status and headers instead of
# waiting to do it manually, so that the class can handle any
# special logic
@@ -1710,8 +2153,8 @@ class Flask(Scaffold):
headers=headers, # type: ignore[arg-type]
)
status = headers = None
- elif isinstance(rv, dict):
- rv = jsonify(rv)
+ elif isinstance(rv, (dict, list)):
+ rv = self.json.response(rv)
elif isinstance(rv, BaseResponse) or callable(rv):
# evaluate a WSGI callable, or coerce a different response
# class to the correct type
@@ -1723,15 +2166,17 @@ class Flask(Scaffold):
raise TypeError(
f"{e}\nThe view function did not return a valid"
" response. The return type must be a string,"
- " dict, tuple, Response instance, or WSGI"
- f" callable, but it was a {type(rv).__name__}."
+ " dict, list, tuple with headers or status,"
+ " Response instance, or WSGI callable, but it"
+ f" was a {type(rv).__name__}."
).with_traceback(sys.exc_info()[2]) from None
else:
raise TypeError(
"The view function did not return a valid"
" response. The return type must be a string,"
- " dict, tuple, Response instance, or WSGI"
- f" callable, but it was a {type(rv).__name__}."
+ " dict, list, tuple with headers or status,"
+ " Response instance, or WSGI callable, but it was a"
+ f" {type(rv).__name__}."
)
rv = t.cast(Response, rv)
@@ -1812,10 +2257,21 @@ class Flask(Scaffold):
func(endpoint, values)
def handle_url_build_error(
- self, error: Exception, endpoint: str, values: dict
+ self, error: BuildError, endpoint: str, values: t.Dict[str, t.Any]
) -> str:
- """Handle :class:`~werkzeug.routing.BuildError` on
- :meth:`url_for`.
+ """Called by :meth:`.url_for` if a
+ :exc:`~werkzeug.routing.BuildError` was raised. If this returns
+ a value, it will be returned by ``url_for``, otherwise the error
+ will be re-raised.
+
+ Each function in :attr:`url_build_error_handlers` is called with
+ ``error``, ``endpoint`` and ``values``. If a function returns
+ ``None`` or raises a ``BuildError``, it is skipped. Otherwise,
+ its return value is returned by ``url_for``.
+
+ :param error: The active ``BuildError`` being handled.
+ :param endpoint: The endpoint being built.
+ :param values: The keyword arguments passed to ``url_for``.
"""
for handler in self.url_build_error_handlers:
try:
@@ -1874,7 +2330,7 @@ class Flask(Scaffold):
:return: a new response object or the same, has to be an
instance of :attr:`response_class`.
"""
- ctx = _request_ctx_stack.top
+ ctx = request_ctx._get_current_object() # type: ignore[attr-defined]
for func in ctx._after_request_functions:
response = self.ensure_sync(func)(response)
@@ -2079,9 +2535,14 @@ class Flask(Scaffold):
raise
return response(environ, start_response)
finally:
- if self.should_ignore_error(error):
+ if "werkzeug.debug.preserve_context" in environ:
+ environ["werkzeug.debug.preserve_context"](_cv_app.get())
+ environ["werkzeug.debug.preserve_context"](_cv_request.get())
+
+ if error is not None and self.should_ignore_error(error):
error = None
- ctx.auto_pop(error)
+
+ ctx.pop(error)
def __call__(self, environ: dict, start_response: t.Callable) -> t.Any:
"""The WSGI server calls the Flask application object as the
diff --git a/contrib/python/Flask/py3/flask/blueprints.py b/contrib/python/Flask/py3/flask/blueprints.py
index 87617989e0..eb6642358d 100644
--- a/contrib/python/Flask/py3/flask/blueprints.py
+++ b/contrib/python/Flask/py3/flask/blueprints.py
@@ -1,3 +1,4 @@
+import json
import os
import typing as t
from collections import defaultdict
@@ -7,11 +8,29 @@ from . import typing as ft
from .scaffold import _endpoint_from_view_func
from .scaffold import _sentinel
from .scaffold import Scaffold
+from .scaffold import setupmethod
-if t.TYPE_CHECKING:
+if t.TYPE_CHECKING: # pragma: no cover
from .app import Flask
DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable]
+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_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable)
+T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
+T_template_context_processor = t.TypeVar(
+ "T_template_context_processor", bound=ft.TemplateContextProcessorCallable
+)
+T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable)
+T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable)
+T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable)
+T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable)
+T_url_value_preprocessor = t.TypeVar(
+ "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable
+)
class BlueprintSetupState:
@@ -152,15 +171,78 @@ class Blueprint(Scaffold):
.. versionadded:: 0.7
"""
- warn_on_modifications = False
_got_registered_once = False
- #: Blueprint local JSON encoder class to use. Set to ``None`` to use
- #: the app's :class:`~flask.Flask.json_encoder`.
- json_encoder = None
- #: Blueprint local JSON decoder class to use. Set to ``None`` to use
- #: the app's :class:`~flask.Flask.json_decoder`.
- json_decoder = None
+ _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__(
self,
@@ -168,7 +250,7 @@ class Blueprint(Scaffold):
import_name: str,
static_folder: t.Optional[t.Union[str, os.PathLike]] = None,
static_url_path: t.Optional[str] = None,
- template_folder: t.Optional[str] = None,
+ template_folder: t.Optional[t.Union[str, os.PathLike]] = None,
url_prefix: t.Optional[str] = None,
subdomain: t.Optional[str] = None,
url_defaults: t.Optional[dict] = None,
@@ -198,27 +280,33 @@ class Blueprint(Scaffold):
self.cli_group = cli_group
self._blueprints: t.List[t.Tuple["Blueprint", dict]] = []
- def _is_setup_finished(self) -> bool:
- return self.warn_on_modifications and self._got_registered_once
+ def _check_setup_finished(self, f_name: str) -> None:
+ if self._got_registered_once:
+ import warnings
+
+ warnings.warn(
+ f"The setup method '{f_name}' can no longer be called on"
+ f" the blueprint '{self.name}'. It has already been"
+ " registered at least once, any changes will not be"
+ " 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
def record(self, func: t.Callable) -> None:
"""Registers a function that is called when the blueprint is
registered on the application. This function is called with the
state as argument as returned by the :meth:`make_setup_state`
method.
"""
- if self._got_registered_once and self.warn_on_modifications:
- from warnings import warn
-
- warn(
- Warning(
- "The blueprint was already registered once but is"
- " getting modified now. These changes will not show"
- " up."
- )
- )
self.deferred_functions.append(func)
+ @setupmethod
def record_once(self, func: t.Callable) -> None:
"""Works like :meth:`record` but wraps the function in another
function that will ensure the function is only called once. If the
@@ -230,7 +318,7 @@ class Blueprint(Scaffold):
if state.first_registration:
func(state)
- return self.record(update_wrapper(wrapper, func))
+ self.record(update_wrapper(wrapper, func))
def make_setup_state(
self, app: "Flask", options: dict, first_registration: bool = False
@@ -241,6 +329,7 @@ class Blueprint(Scaffold):
"""
return BlueprintSetupState(self, app, options, first_registration)
+ @setupmethod
def register_blueprint(self, blueprint: "Blueprint", **options: t.Any) -> None:
"""Register a :class:`~flask.Blueprint` on this blueprint. Keyword
arguments passed to this method will override the defaults set
@@ -380,16 +469,20 @@ class Blueprint(Scaffold):
bp_options["name_prefix"] = name
blueprint.register(app, bp_options)
+ @setupmethod
def add_url_rule(
self,
rule: str,
endpoint: t.Optional[str] = None,
- view_func: t.Optional[ft.ViewCallable] = None,
+ view_func: t.Optional[ft.RouteCallable] = None,
provide_automatic_options: t.Optional[bool] = None,
**options: t.Any,
) -> None:
- """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.
+ """Register a URL rule with the blueprint. See :meth:`.Flask.add_url_rule` for
+ full documentation.
+
+ The URL rule is prefixed with the blueprint's URL prefix. The endpoint name,
+ used with :func:`url_for`, is prefixed with the blueprint's name.
"""
if endpoint and "." in endpoint:
raise ValueError("'endpoint' may not contain a dot '.' character.")
@@ -407,28 +500,30 @@ class Blueprint(Scaffold):
)
)
+ @setupmethod
def app_template_filter(
self, name: t.Optional[str] = None
- ) -> t.Callable[[ft.TemplateFilterCallable], ft.TemplateFilterCallable]:
- """Register a custom template filter, available application wide. Like
- :meth:`Flask.template_filter` but for a blueprint.
+ ) -> t.Callable[[T_template_filter], T_template_filter]:
+ """Register a template filter, available in any template rendered by the
+ application. Equivalent to :meth:`.Flask.template_filter`.
:param name: the optional name of the filter, otherwise the
function name will be used.
"""
- def decorator(f: ft.TemplateFilterCallable) -> ft.TemplateFilterCallable:
+ def decorator(f: T_template_filter) -> T_template_filter:
self.add_app_template_filter(f, name=name)
return f
return decorator
+ @setupmethod
def add_app_template_filter(
self, f: ft.TemplateFilterCallable, name: t.Optional[str] = None
) -> None:
- """Register a custom template filter, available application wide. Like
- :meth:`Flask.add_template_filter` but for a blueprint. Works exactly
- like the :meth:`app_template_filter` decorator.
+ """Register a template filter, available in any template rendered by the
+ application. Works like the :meth:`app_template_filter` decorator. Equivalent to
+ :meth:`.Flask.add_template_filter`.
:param name: the optional name of the filter, otherwise the
function name will be used.
@@ -439,11 +534,12 @@ class Blueprint(Scaffold):
self.record_once(register_template)
+ @setupmethod
def app_template_test(
self, name: t.Optional[str] = None
- ) -> t.Callable[[ft.TemplateTestCallable], ft.TemplateTestCallable]:
- """Register a custom template test, available application wide. Like
- :meth:`Flask.template_test` but for a blueprint.
+ ) -> t.Callable[[T_template_test], T_template_test]:
+ """Register a template test, available in any template rendered by the
+ application. Equivalent to :meth:`.Flask.template_test`.
.. versionadded:: 0.10
@@ -451,18 +547,19 @@ class Blueprint(Scaffold):
function name will be used.
"""
- def decorator(f: ft.TemplateTestCallable) -> ft.TemplateTestCallable:
+ def decorator(f: T_template_test) -> T_template_test:
self.add_app_template_test(f, name=name)
return f
return decorator
+ @setupmethod
def add_app_template_test(
self, f: ft.TemplateTestCallable, name: t.Optional[str] = None
) -> None:
- """Register a custom template test, available application wide. Like
- :meth:`Flask.add_template_test` but for a blueprint. Works exactly
- like the :meth:`app_template_test` decorator.
+ """Register a template test, available in any template rendered by the
+ application. Works like the :meth:`app_template_test` decorator. Equivalent to
+ :meth:`.Flask.add_template_test`.
.. versionadded:: 0.10
@@ -475,11 +572,12 @@ class Blueprint(Scaffold):
self.record_once(register_template)
+ @setupmethod
def app_template_global(
self, name: t.Optional[str] = None
- ) -> t.Callable[[ft.TemplateGlobalCallable], ft.TemplateGlobalCallable]:
- """Register a custom template global, available application wide. Like
- :meth:`Flask.template_global` but for a blueprint.
+ ) -> t.Callable[[T_template_global], T_template_global]:
+ """Register a template global, available in any template rendered by the
+ application. Equivalent to :meth:`.Flask.template_global`.
.. versionadded:: 0.10
@@ -487,18 +585,19 @@ class Blueprint(Scaffold):
function name will be used.
"""
- def decorator(f: ft.TemplateGlobalCallable) -> ft.TemplateGlobalCallable:
+ def decorator(f: T_template_global) -> T_template_global:
self.add_app_template_global(f, name=name)
return f
return decorator
+ @setupmethod
def add_app_template_global(
self, f: ft.TemplateGlobalCallable, name: t.Optional[str] = None
) -> None:
- """Register a custom template global, available application wide. Like
- :meth:`Flask.add_template_global` but for a blueprint. Works exactly
- like the :meth:`app_template_global` decorator.
+ """Register a template global, available in any template rendered by the
+ application. Works like the :meth:`app_template_global` decorator. Equivalent to
+ :meth:`.Flask.add_template_global`.
.. versionadded:: 0.10
@@ -511,80 +610,102 @@ class Blueprint(Scaffold):
self.record_once(register_template)
- def before_app_request(
- self, f: ft.BeforeRequestCallable
- ) -> ft.BeforeRequestCallable:
- """Like :meth:`Flask.before_request`. Such a function is executed
- before each request, even if outside of a blueprint.
+ @setupmethod
+ def before_app_request(self, f: T_before_request) -> T_before_request:
+ """Like :meth:`before_request`, but before every request, not only those handled
+ by the blueprint. Equivalent to :meth:`.Flask.before_request`.
"""
self.record_once(
lambda s: s.app.before_request_funcs.setdefault(None, []).append(f)
)
return f
+ @setupmethod
def before_app_first_request(
- self, f: ft.BeforeFirstRequestCallable
- ) -> ft.BeforeFirstRequestCallable:
- """Like :meth:`Flask.before_first_request`. Such a function is
- executed before the first request to the application.
+ 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
- def after_app_request(self, f: ft.AfterRequestCallable) -> ft.AfterRequestCallable:
- """Like :meth:`Flask.after_request` but for a blueprint. Such a function
- is executed after each request, even if outside of the blueprint.
+ @setupmethod
+ def after_app_request(self, f: T_after_request) -> T_after_request:
+ """Like :meth:`after_request`, but after every request, not only those handled
+ by the blueprint. Equivalent to :meth:`.Flask.after_request`.
"""
self.record_once(
lambda s: s.app.after_request_funcs.setdefault(None, []).append(f)
)
return f
- def teardown_app_request(self, f: ft.TeardownCallable) -> ft.TeardownCallable:
- """Like :meth:`Flask.teardown_request` but for a blueprint. Such a
- function is executed when tearing down each request, even if outside of
- the blueprint.
+ @setupmethod
+ def teardown_app_request(self, f: T_teardown) -> T_teardown:
+ """Like :meth:`teardown_request`, but after every request, not only those
+ handled by the blueprint. Equivalent to :meth:`.Flask.teardown_request`.
"""
self.record_once(
lambda s: s.app.teardown_request_funcs.setdefault(None, []).append(f)
)
return f
+ @setupmethod
def app_context_processor(
- self, f: ft.TemplateContextProcessorCallable
- ) -> ft.TemplateContextProcessorCallable:
- """Like :meth:`Flask.context_processor` but for a blueprint. Such a
- function is executed each request, even if outside of the blueprint.
+ self, f: T_template_context_processor
+ ) -> T_template_context_processor:
+ """Like :meth:`context_processor`, but for templates rendered by every view, not
+ only by the blueprint. Equivalent to :meth:`.Flask.context_processor`.
"""
self.record_once(
lambda s: s.app.template_context_processors.setdefault(None, []).append(f)
)
return f
+ @setupmethod
def app_errorhandler(
self, code: t.Union[t.Type[Exception], int]
- ) -> t.Callable[[ft.ErrorHandlerDecorator], ft.ErrorHandlerDecorator]:
- """Like :meth:`Flask.errorhandler` but for a blueprint. This
- handler is used for all requests, even if outside of the blueprint.
+ ) -> t.Callable[[T_error_handler], T_error_handler]:
+ """Like :meth:`errorhandler`, but for every request, not only those handled by
+ the blueprint. Equivalent to :meth:`.Flask.errorhandler`.
"""
- def decorator(f: ft.ErrorHandlerDecorator) -> ft.ErrorHandlerDecorator:
+ def decorator(f: T_error_handler) -> T_error_handler:
self.record_once(lambda s: s.app.errorhandler(code)(f))
return f
return decorator
+ @setupmethod
def app_url_value_preprocessor(
- self, f: ft.URLValuePreprocessorCallable
- ) -> ft.URLValuePreprocessorCallable:
- """Same as :meth:`url_value_preprocessor` but application wide."""
+ self, f: T_url_value_preprocessor
+ ) -> T_url_value_preprocessor:
+ """Like :meth:`url_value_preprocessor`, but for every request, not only those
+ handled by the blueprint. Equivalent to :meth:`.Flask.url_value_preprocessor`.
+ """
self.record_once(
lambda s: s.app.url_value_preprocessors.setdefault(None, []).append(f)
)
return f
- def app_url_defaults(self, f: ft.URLDefaultCallable) -> ft.URLDefaultCallable:
- """Same as :meth:`url_defaults` but application wide."""
+ @setupmethod
+ def app_url_defaults(self, f: T_url_defaults) -> T_url_defaults:
+ """Like :meth:`url_defaults`, but for every request, not only those handled by
+ the blueprint. Equivalent to :meth:`.Flask.url_defaults`.
+ """
self.record_once(
lambda s: s.app.url_default_functions.setdefault(None, []).append(f)
)
diff --git a/contrib/python/Flask/py3/flask/cli.py b/contrib/python/Flask/py3/flask/cli.py
index 77c1e25a9c..37a15ff2d8 100644
--- a/contrib/python/Flask/py3/flask/cli.py
+++ b/contrib/python/Flask/py3/flask/cli.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import ast
import inspect
import os
@@ -5,19 +7,23 @@ import platform
import re
import sys
import traceback
+import typing as t
from functools import update_wrapper
from operator import attrgetter
-from threading import Lock
-from threading import Thread
import click
+from click.core import ParameterSource
+from werkzeug import run_simple
+from werkzeug.serving import is_running_from_reloader
from werkzeug.utils import import_string
from .globals import current_app
from .helpers import get_debug_flag
-from .helpers import get_env
from .helpers import get_load_dotenv
+if t.TYPE_CHECKING:
+ from .app import Flask
+
class NoAppException(click.UsageError):
"""Raised if an application cannot be found or loaded."""
@@ -44,8 +50,8 @@ def find_best_app(module):
elif len(matches) > 1:
raise NoAppException(
"Detected multiple Flask applications in module"
- f" {module.__name__!r}. Use 'FLASK_APP={module.__name__}:name'"
- f" to specify the correct one."
+ f" '{module.__name__}'. Use '{module.__name__}:name'"
+ " to specify the correct one."
)
# Search for app factory functions.
@@ -63,15 +69,15 @@ def find_best_app(module):
raise
raise NoAppException(
- f"Detected factory {attr_name!r} in module {module.__name__!r},"
+ f"Detected factory '{attr_name}' in module '{module.__name__}',"
" but could not call it without arguments. Use"
- f" \"FLASK_APP='{module.__name__}:{attr_name}(args)'\""
+ f" '{module.__name__}:{attr_name}(args)'"
" to specify arguments."
) from e
raise NoAppException(
"Failed to find Flask application or factory in module"
- f" {module.__name__!r}. Use 'FLASK_APP={module.__name__}:name'"
+ f" '{module.__name__}'. Use '{module.__name__}:name'"
" to specify one."
)
@@ -208,8 +214,6 @@ def prepare_import(path):
def locate_app(module_name, app_name, raise_if_not_found=True):
- __traceback_hide__ = True # noqa: F841
-
try:
__import__(module_name)
except ImportError:
@@ -251,7 +255,7 @@ def get_version(ctx, param, value):
version_option = click.Option(
["--version"],
- help="Show the flask version",
+ help="Show the Flask version.",
expose_value=False,
callback=get_version,
is_flag=True,
@@ -259,74 +263,6 @@ version_option = click.Option(
)
-class DispatchingApp:
- """Special application that dispatches to a Flask application which
- is imported by name in a background thread. If an error happens
- it is recorded and shown as part of the WSGI handling which in case
- of the Werkzeug debugger means that it shows up in the browser.
- """
-
- def __init__(self, loader, use_eager_loading=None):
- self.loader = loader
- self._app = None
- self._lock = Lock()
- self._bg_loading_exc = None
-
- if use_eager_loading is None:
- use_eager_loading = os.environ.get("WERKZEUG_RUN_MAIN") != "true"
-
- if use_eager_loading:
- self._load_unlocked()
- else:
- self._load_in_background()
-
- def _load_in_background(self):
- # Store the Click context and push it in the loader thread so
- # script_info is still available.
- ctx = click.get_current_context(silent=True)
-
- def _load_app():
- __traceback_hide__ = True # noqa: F841
-
- with self._lock:
- if ctx is not None:
- click.globals.push_context(ctx)
-
- try:
- self._load_unlocked()
- except Exception as e:
- self._bg_loading_exc = e
-
- t = Thread(target=_load_app, args=())
- t.start()
-
- def _flush_bg_loading_exception(self):
- __traceback_hide__ = True # noqa: F841
- exc = self._bg_loading_exc
-
- if exc is not None:
- self._bg_loading_exc = None
- raise exc
-
- def _load_unlocked(self):
- __traceback_hide__ = True # noqa: F841
- self._app = rv = self.loader()
- self._bg_loading_exc = None
- return rv
-
- def __call__(self, environ, start_response):
- __traceback_hide__ = True # noqa: F841
- if self._app is not None:
- return self._app(environ, start_response)
- self._flush_bg_loading_exception()
- with self._lock:
- if self._app is not None:
- rv = self._app
- else:
- rv = self._load_unlocked()
- return rv(environ, start_response)
-
-
class ScriptInfo:
"""Helper object to deal with Flask applications. This is usually not
necessary to interface with as it's used internally in the dispatching
@@ -336,25 +272,28 @@ class ScriptInfo:
onwards as click object.
"""
- def __init__(self, app_import_path=None, create_app=None, set_debug_flag=True):
+ def __init__(
+ self,
+ app_import_path: str | None = None,
+ create_app: t.Callable[..., Flask] | None = None,
+ set_debug_flag: bool = True,
+ ) -> None:
#: Optionally the import path for the Flask application.
- self.app_import_path = app_import_path or os.environ.get("FLASK_APP")
+ self.app_import_path = app_import_path
#: Optionally a function that is passed the script info to create
#: the instance of the application.
self.create_app = create_app
#: A dictionary with arbitrary data that can be associated with
#: this script info.
- self.data = {}
+ self.data: t.Dict[t.Any, t.Any] = {}
self.set_debug_flag = set_debug_flag
- self._loaded_app = None
+ self._loaded_app: Flask | None = None
- def load_app(self):
+ def load_app(self) -> Flask:
"""Loads the Flask app (if not yet loaded) and returns it. Calling
this multiple times will just result in the already loaded app to
be returned.
"""
- __traceback_hide__ = True # noqa: F841
-
if self._loaded_app is not None:
return self._loaded_app
@@ -377,9 +316,10 @@ class ScriptInfo:
if not app:
raise NoAppException(
- "Could not locate a Flask application. You did not provide "
- 'the "FLASK_APP" environment variable, and a "wsgi.py" or '
- '"app.py" module was not found in the current directory.'
+ "Could not locate a Flask application. Use the"
+ " 'flask --app' option, 'FLASK_APP' environment"
+ " variable, or a 'wsgi.py' or 'app.py' file in the"
+ " current directory."
)
if self.set_debug_flag:
@@ -396,15 +336,25 @@ pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True)
def with_appcontext(f):
"""Wraps a callback so that it's guaranteed to be executed with the
- script's application context. If callbacks are registered directly
- to the ``app.cli`` object then they are wrapped with this function
- by default unless it's disabled.
+ script's application context.
+
+ Custom commands (and their options) registered under ``app.cli`` or
+ ``blueprint.cli`` will always have an app context available, this
+ decorator is not required in that case.
+
+ .. versionchanged:: 2.2
+ The app context is active for subcommands as well as the
+ decorated callback. The app context is always available to
+ ``app.cli`` command and parameter callbacks.
"""
@click.pass_context
def decorator(__ctx, *args, **kwargs):
- with __ctx.ensure_object(ScriptInfo).load_app().app_context():
- return __ctx.invoke(f, *args, **kwargs)
+ if not current_app:
+ app = __ctx.ensure_object(ScriptInfo).load_app()
+ __ctx.with_resource(app.app_context())
+
+ return __ctx.invoke(f, *args, **kwargs)
return update_wrapper(decorator, f)
@@ -440,6 +390,94 @@ class AppGroup(click.Group):
return click.Group.group(self, *args, **kwargs)
+def _set_app(ctx: click.Context, param: click.Option, value: str | None) -> str | None:
+ if value is None:
+ return None
+
+ info = ctx.ensure_object(ScriptInfo)
+ info.app_import_path = value
+ return value
+
+
+# This option is eager so the app will be available if --help is given.
+# --help is also eager, so --app must be before it in the param list.
+# no_args_is_help bypasses eager processing, so this option must be
+# processed manually in that case to ensure FLASK_APP gets picked up.
+_app_option = click.Option(
+ ["-A", "--app"],
+ metavar="IMPORT",
+ help=(
+ "The Flask application or factory function to load, in the form 'module:name'."
+ " Module can be a dotted import or file path. Name is not required if it is"
+ " 'app', 'application', 'create_app', or 'make_app', and can be 'name(args)' to"
+ " pass arguments."
+ ),
+ is_eager=True,
+ expose_value=False,
+ callback=_set_app,
+)
+
+
+def _set_debug(ctx: click.Context, param: click.Option, value: bool) -> bool | None:
+ # If the flag isn't provided, it will default to False. Don't use
+ # that, let debug be set by env in that case.
+ source = ctx.get_parameter_source(param.name) # type: ignore[arg-type]
+
+ if source is not None and source in (
+ ParameterSource.DEFAULT,
+ ParameterSource.DEFAULT_MAP,
+ ):
+ return None
+
+ # Set with env var instead of ScriptInfo.load so that it can be
+ # accessed early during a factory function.
+ os.environ["FLASK_DEBUG"] = "1" if value else "0"
+ return value
+
+
+_debug_option = click.Option(
+ ["--debug/--no-debug"],
+ help="Set debug mode.",
+ expose_value=False,
+ callback=_set_debug,
+)
+
+
+def _env_file_callback(
+ ctx: click.Context, param: click.Option, value: str | None
+) -> str | None:
+ if value is None:
+ return None
+
+ import importlib
+
+ try:
+ importlib.import_module("dotenv")
+ except ImportError:
+ raise click.BadParameter(
+ "python-dotenv must be installed to load an env file.",
+ ctx=ctx,
+ param=param,
+ ) from None
+
+ # Don't check FLASK_SKIP_DOTENV, that only disables automatically
+ # loading .env and .flaskenv files.
+ load_dotenv(value)
+ return value
+
+
+# This option is eager so env vars are loaded as early as possible to be
+# used by other options.
+_env_file_option = click.Option(
+ ["-e", "--env-file"],
+ type=click.Path(exists=True, dir_okay=False),
+ help="Load environment variables from this file. python-dotenv must be installed.",
+ is_eager=True,
+ expose_value=False,
+ callback=_env_file_callback,
+)
+
+
class FlaskGroup(AppGroup):
"""Special subclass of the :class:`AppGroup` group that supports
loading more commands from the configured Flask app. Normally a
@@ -455,8 +493,14 @@ class FlaskGroup(AppGroup):
:param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv`
files to set environment variables. Will also change the working
directory to the directory containing the first file found.
- :param set_debug_flag: Set the app's debug flag based on the active
- environment
+ :param set_debug_flag: Set the app's debug flag.
+
+ .. versionchanged:: 2.2
+ Added the ``-A/--app``, ``--debug/--no-debug``, ``-e/--env-file`` options.
+
+ .. versionchanged:: 2.2
+ An app context is pushed when running ``app.cli`` commands, so
+ ``@with_appcontext`` is no longer required for those commands.
.. versionchanged:: 1.0
If installed, python-dotenv will be used to load environment variables
@@ -465,19 +509,30 @@ class FlaskGroup(AppGroup):
def __init__(
self,
- add_default_commands=True,
- create_app=None,
- add_version_option=True,
- load_dotenv=True,
- set_debug_flag=True,
- **extra,
- ):
+ add_default_commands: bool = True,
+ create_app: t.Callable[..., Flask] | None = None,
+ add_version_option: bool = True,
+ load_dotenv: bool = True,
+ set_debug_flag: bool = True,
+ **extra: t.Any,
+ ) -> None:
params = list(extra.pop("params", None) or ())
+ # Processing is done with option callbacks instead of a group
+ # callback. This allows users to make a custom group callback
+ # without losing the behavior. --env-file must come first so
+ # that it is eagerly evaluated before --app.
+ params.extend((_env_file_option, _app_option, _debug_option))
if add_version_option:
params.append(version_option)
- AppGroup.__init__(self, params=params, **extra)
+ if "context_settings" not in extra:
+ extra["context_settings"] = {}
+
+ extra["context_settings"].setdefault("auto_envvar_prefix", "FLASK")
+
+ super().__init__(params=params, **extra)
+
self.create_app = create_app
self.load_dotenv = load_dotenv
self.set_debug_flag = set_debug_flag
@@ -520,9 +575,18 @@ class FlaskGroup(AppGroup):
# Look up commands provided by the app, showing an error and
# continuing if the app couldn't be loaded.
try:
- return info.load_app().cli.get_command(ctx, name)
+ app = info.load_app()
except NoAppException as e:
click.secho(f"Error: {e.format_message()}\n", err=True, fg="red")
+ return None
+
+ # Push an app context for the loaded app unless it is already
+ # active somehow. This makes the context available to parameter
+ # and command callbacks without needing @with_appcontext.
+ if not current_app or current_app._get_current_object() is not app:
+ ctx.with_resource(app.app_context())
+
+ return app.cli.get_command(ctx, name)
def list_commands(self, ctx):
self._load_plugin_commands()
@@ -545,26 +609,39 @@ class FlaskGroup(AppGroup):
return sorted(rv)
- def main(self, *args, **kwargs):
- # Set a global flag that indicates that we were invoked from the
- # command line interface. This is detected by Flask.run to make the
- # call into a no-op. This is necessary to avoid ugly errors when the
- # script that is loaded here also attempts to start a server.
+ def make_context(
+ self,
+ info_name: str | None,
+ args: list[str],
+ parent: click.Context | None = None,
+ **extra: t.Any,
+ ) -> click.Context:
+ # Set a flag to tell app.run to become a no-op. If app.run was
+ # not in a __name__ == __main__ guard, it would start the server
+ # when importing, blocking whatever command is being called.
os.environ["FLASK_RUN_FROM_CLI"] = "true"
+ # Attempt to load .env and .flask env files. The --env-file
+ # option can cause another file to be loaded.
if get_load_dotenv(self.load_dotenv):
load_dotenv()
- obj = kwargs.get("obj")
-
- if obj is None:
- obj = ScriptInfo(
+ if "obj" not in extra and "obj" not in self.context_settings:
+ extra["obj"] = ScriptInfo(
create_app=self.create_app, set_debug_flag=self.set_debug_flag
)
- kwargs["obj"] = obj
- kwargs.setdefault("auto_envvar_prefix", "FLASK")
- return super().main(*args, **kwargs)
+ return super().make_context(info_name, args, parent=parent, **extra)
+
+ def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]:
+ if not args and self.no_args_is_help:
+ # Attempt to load --env-file and --app early in case they
+ # were given as env vars. Otherwise no_args_is_help will not
+ # see commands from app.cli.
+ _env_file_option.handle_parse_result(ctx, {}, [])
+ _app_option.handle_parse_result(ctx, {}, [])
+
+ return super().parse_args(ctx, args)
def _path_is_ancestor(path, other):
@@ -574,7 +651,7 @@ def _path_is_ancestor(path, other):
return os.path.join(path, other[len(path) :].lstrip(os.sep)) == other
-def load_dotenv(path=None):
+def load_dotenv(path: str | os.PathLike | None = None) -> bool:
"""Load "dotenv" files in order of precedence to set environment variables.
If an env var is already set it is not overwritten, so earlier files in the
@@ -587,13 +664,17 @@ def load_dotenv(path=None):
:param path: Load the file at this location instead of searching.
:return: ``True`` if a file was loaded.
- .. versionchanged:: 1.1.0
- Returns ``False`` when python-dotenv is not installed, or when
- the given path isn't a file.
+ .. versionchanged:: 2.0
+ The current directory is not changed to the location of the
+ loaded file.
.. versionchanged:: 2.0
When loading the env files, set the default encoding to UTF-8.
+ .. versionchanged:: 1.1.0
+ Returns ``False`` when python-dotenv is not installed, or when
+ the given path isn't a file.
+
.. versionadded:: 1.0
"""
try:
@@ -609,15 +690,15 @@ def load_dotenv(path=None):
return False
- # if the given path specifies the actual file then return True,
- # else False
+ # Always return after attempting to load a given path, don't load
+ # the default files.
if path is not None:
if os.path.isfile(path):
return dotenv.load_dotenv(path, encoding="utf-8")
return False
- new_dir = None
+ loaded = False
for name in (".env", ".flaskenv"):
path = dotenv.find_dotenv(name, usecwd=True)
@@ -625,38 +706,21 @@ def load_dotenv(path=None):
if not path:
continue
- if new_dir is None:
- new_dir = os.path.dirname(path)
-
dotenv.load_dotenv(path, encoding="utf-8")
+ loaded = True
- return new_dir is not None # at least one file was located and loaded
+ return loaded # True if at least one file was located and loaded.
-def show_server_banner(env, debug, app_import_path, eager_loading):
+def show_server_banner(debug, app_import_path):
"""Show extra startup messages the first time the server is run,
ignoring the reloader.
"""
- if os.environ.get("WERKZEUG_RUN_MAIN") == "true":
+ if is_running_from_reloader():
return
if app_import_path is not None:
- message = f" * Serving Flask app {app_import_path!r}"
-
- if not eager_loading:
- message += " (lazy loading)"
-
- click.echo(message)
-
- click.echo(f" * Environment: {env}")
-
- if env == "production":
- click.secho(
- " WARNING: This is a development server. Do not use it in"
- " a production deployment.",
- fg="red",
- )
- click.secho(" Use a production WSGI server instead.", dim=True)
+ click.echo(f" * Serving Flask app '{app_import_path}'")
if debug is not None:
click.echo(f" * Debug mode: {'on' if debug else 'off'}")
@@ -786,12 +850,6 @@ class SeparatedPathType(click.Path):
"is active if debug is enabled.",
)
@click.option(
- "--eager-loading/--lazy-loading",
- default=None,
- help="Enable or disable eager loading. By default eager "
- "loading is enabled if the reloader is disabled.",
-)
-@click.option(
"--with-threads/--without-threads",
default=True,
help="Enable or disable multithreading.",
@@ -822,7 +880,6 @@ def run_command(
port,
reload,
debugger,
- eager_loading,
with_threads,
cert,
extra_files,
@@ -833,9 +890,26 @@ def run_command(
This server is for development purposes only. It does not provide
the stability, security, or performance of production WSGI servers.
- The reloader and debugger are enabled by default if
- FLASK_ENV=development or FLASK_DEBUG=1.
+ The reloader and debugger are enabled by default with the '--debug'
+ option.
"""
+ try:
+ app = info.load_app()
+ except Exception as e:
+ if is_running_from_reloader():
+ # When reloading, print out the error immediately, but raise
+ # it later so the debugger or server can handle it.
+ traceback.print_exc()
+ err = e
+
+ def app(environ, start_response):
+ raise err from None
+
+ else:
+ # When not reloading, raise the error immediately so the
+ # command fails.
+ raise e from None
+
debug = get_debug_flag()
if reload is None:
@@ -844,10 +918,7 @@ def run_command(
if debugger is None:
debugger = debug
- show_server_banner(get_env(), debug, info.app_import_path, eager_loading)
- app = DispatchingApp(info.load_app, use_eager_loading=eager_loading)
-
- from werkzeug.serving import run_simple
+ show_server_banner(debug, info.app_import_path)
run_simple(
host,
@@ -862,6 +933,9 @@ def run_command(
)
+run_command.params.insert(0, _debug_option)
+
+
@click.command("shell", short_help="Run a shell in the app context.")
@with_appcontext
def shell_command() -> None:
@@ -873,13 +947,11 @@ def shell_command() -> None:
without having to manually configure the application.
"""
import code
- from .globals import _app_ctx_stack
- app = _app_ctx_stack.top.app
banner = (
f"Python {sys.version} on {sys.platform}\n"
- f"App: {app.import_name} [{app.env}]\n"
- f"Instance: {app.instance_path}"
+ f"App: {current_app.import_name}\n"
+ f"Instance: {current_app.instance_path}"
)
ctx: dict = {}
@@ -890,7 +962,7 @@ def shell_command() -> None:
with open(startup) as f:
eval(compile(f.read(), startup, "exec"), ctx)
- ctx.update(app.make_shell_context())
+ ctx.update(current_app.make_shell_context())
# Site, customize, or startup script can set a hook to call when
# entering interactive mode. The default one sets up readline with
@@ -963,22 +1035,14 @@ def routes_command(sort: str, all_methods: bool) -> None:
cli = FlaskGroup(
+ name="flask",
help="""\
A general utility script for Flask applications.
-Provides commands from Flask, extensions, and the application. Loads the
-application defined in the FLASK_APP environment variable, or from a wsgi.py
-file. Setting the FLASK_ENV environment variable to 'development' will enable
-debug mode.
-
-\b
- {prefix}{cmd} FLASK_APP=hello.py
- {prefix}{cmd} FLASK_ENV=development
- {prefix}flask run
-""".format(
- cmd="export" if os.name == "posix" else "set",
- prefix="$ " if os.name == "posix" else "> ",
- )
+An application to load must be given with the '--app' option,
+'FLASK_APP' environment variable, or with a 'wsgi.py' or 'app.py' file
+in the current directory.
+""",
)
diff --git a/contrib/python/Flask/py3/flask/config.py b/contrib/python/Flask/py3/flask/config.py
index 7b6a137ada..d4fc310fe3 100644
--- a/contrib/python/Flask/py3/flask/config.py
+++ b/contrib/python/Flask/py3/flask/config.py
@@ -275,8 +275,9 @@ class Config(dict):
def from_mapping(
self, mapping: t.Optional[t.Mapping[str, t.Any]] = None, **kwargs: t.Any
) -> bool:
- """Updates the config like :meth:`update` ignoring items with non-upper
- keys.
+ """Updates the config like :meth:`update` ignoring items with
+ non-upper keys.
+
:return: Always returns ``True``.
.. versionadded:: 0.11
diff --git a/contrib/python/Flask/py3/flask/ctx.py b/contrib/python/Flask/py3/flask/ctx.py
index e6822b2d97..c79c26dc96 100644
--- a/contrib/python/Flask/py3/flask/ctx.py
+++ b/contrib/python/Flask/py3/flask/ctx.py
@@ -1,3 +1,4 @@
+import contextvars
import sys
import typing as t
from functools import update_wrapper
@@ -6,12 +7,12 @@ from types import TracebackType
from werkzeug.exceptions import HTTPException
from . import typing as ft
-from .globals import _app_ctx_stack
-from .globals import _request_ctx_stack
+from .globals import _cv_app
+from .globals import _cv_request
from .signals import appcontext_popped
from .signals import appcontext_pushed
-if t.TYPE_CHECKING:
+if t.TYPE_CHECKING: # pragma: no cover
from .app import Flask
from .sessions import SessionMixin
from .wrappers import Request
@@ -103,9 +104,9 @@ class _AppCtxGlobals:
return iter(self.__dict__)
def __repr__(self) -> str:
- top = _app_ctx_stack.top
- if top is not None:
- return f"<flask.g of {top.app.name!r}>"
+ ctx = _cv_app.get(None)
+ if ctx is not None:
+ return f"<flask.g of '{ctx.app.name}'>"
return object.__repr__(self)
@@ -130,15 +131,15 @@ def after_this_request(f: ft.AfterRequestCallable) -> ft.AfterRequestCallable:
.. versionadded:: 0.9
"""
- top = _request_ctx_stack.top
+ ctx = _cv_request.get(None)
- if top is None:
+ if ctx is None:
raise RuntimeError(
- "This decorator can only be used when a request context is"
- " active, such as within a view function."
+ "'after_this_request' can only be used when a request"
+ " context is active, such as in a view function."
)
- top._after_request_functions.append(f)
+ ctx._after_request_functions.append(f)
return f
@@ -166,19 +167,19 @@ def copy_current_request_context(f: t.Callable) -> t.Callable:
.. versionadded:: 0.10
"""
- top = _request_ctx_stack.top
+ ctx = _cv_request.get(None)
- if top is None:
+ if ctx is None:
raise RuntimeError(
- "This decorator can only be used when a request context is"
- " active, such as within a view function."
+ "'copy_current_request_context' can only be used when a"
+ " request context is active, such as in a view function."
)
- reqctx = top.copy()
+ ctx = ctx.copy()
def wrapper(*args, **kwargs):
- with reqctx:
- return reqctx.app.ensure_sync(f)(*args, **kwargs)
+ with ctx:
+ return ctx.app.ensure_sync(f)(*args, **kwargs)
return update_wrapper(wrapper, f)
@@ -212,7 +213,7 @@ def has_request_context() -> bool:
.. versionadded:: 0.7
"""
- return _request_ctx_stack.top is not None
+ return _cv_request.get(None) is not None
def has_app_context() -> bool:
@@ -222,44 +223,43 @@ def has_app_context() -> bool:
.. versionadded:: 0.9
"""
- return _app_ctx_stack.top is not None
+ return _cv_app.get(None) is not None
class AppContext:
- """The application context binds an application object implicitly
- to the current thread or greenlet, similar to how the
- :class:`RequestContext` binds request information. The application
- context is also implicitly created if a request context is created
- but the application is not on top of the individual application
- context.
+ """The app context contains application-specific information. An app
+ context is created and pushed at the beginning of each request if
+ one is not already active. An app context is also pushed when
+ running CLI commands.
"""
def __init__(self, app: "Flask") -> None:
self.app = app
self.url_adapter = app.create_url_adapter(None)
- self.g = app.app_ctx_globals_class()
-
- # Like request context, app contexts can be pushed multiple times
- # but there a basic "refcount" is enough to track them.
- self._refcnt = 0
+ self.g: _AppCtxGlobals = app.app_ctx_globals_class()
+ self._cv_tokens: t.List[contextvars.Token] = []
def push(self) -> None:
"""Binds the app context to the current context."""
- self._refcnt += 1
- _app_ctx_stack.push(self)
+ self._cv_tokens.append(_cv_app.set(self))
appcontext_pushed.send(self.app)
def pop(self, exc: t.Optional[BaseException] = _sentinel) -> None: # type: ignore
"""Pops the app context."""
try:
- self._refcnt -= 1
- if self._refcnt <= 0:
+ if len(self._cv_tokens) == 1:
if exc is _sentinel:
exc = sys.exc_info()[1]
self.app.do_teardown_appcontext(exc)
finally:
- rv = _app_ctx_stack.pop()
- assert rv is self, f"Popped wrong app context. ({rv!r} instead of {self!r})"
+ ctx = _cv_app.get()
+ _cv_app.reset(self._cv_tokens.pop())
+
+ if ctx is not self:
+ raise AssertionError(
+ f"Popped wrong app context. ({ctx!r} instead of {self!r})"
+ )
+
appcontext_popped.send(self.app)
def __enter__(self) -> "AppContext":
@@ -276,10 +276,10 @@ class AppContext:
class RequestContext:
- """The request context contains all request relevant information. It is
- created at the beginning of the request and pushed to the
- `_request_ctx_stack` and removed at the end of it. It will create the
- URL adapter and request object for the WSGI environment provided.
+ """The request context contains per-request information. The Flask
+ app creates and pushes it at the beginning of the request, then pops
+ it at the end of the request. It will create the URL adapter and
+ request object for the WSGI environment provided.
Do not attempt to use this class directly, instead use
:meth:`~flask.Flask.test_request_context` and
@@ -289,20 +289,12 @@ class RequestContext:
functions registered on the application for teardown execution
(:meth:`~flask.Flask.teardown_request`).
- The request context is automatically popped at the end of the request
- for you. In debug mode the request context is kept around if
- exceptions happen so that interactive debuggers have a chance to
- introspect the data. With 0.4 this can also be forced for requests
- that did not fail and outside of ``DEBUG`` mode. By setting
- ``'flask._preserve_context'`` to ``True`` on the WSGI environment the
- context will not pop itself at the end of the request. This is used by
- the :meth:`~flask.Flask.test_client` for example to implement the
- deferred cleanup functionality.
-
- You might find this helpful for unittests where you need the
- information from the context local around for a little longer. Make
- sure to properly :meth:`~werkzeug.LocalStack.pop` the stack yourself in
- that situation, otherwise your unittests will leak memory.
+ The request context is automatically popped at the end of the
+ request. When using the interactive debugger, the context will be
+ restored so ``request`` is still accessible. Similarly, the test
+ client can preserve the context after the request ends. However,
+ teardown functions may already have closed some resources such as
+ database connections.
"""
def __init__(
@@ -315,59 +307,21 @@ class RequestContext:
self.app = app
if request is None:
request = app.request_class(environ)
- self.request = request
+ request.json_module = app.json
+ self.request: Request = request
self.url_adapter = None
try:
self.url_adapter = app.create_url_adapter(self.request)
except HTTPException as e:
self.request.routing_exception = e
- self.flashes = None
- self.session = session
-
- # Request contexts can be pushed multiple times and interleaved with
- # other request contexts. Now only if the last level is popped we
- # get rid of them. Additionally if an application context is missing
- # one is created implicitly so for each level we add this information
- self._implicit_app_ctx_stack: t.List[t.Optional["AppContext"]] = []
-
- # indicator if the context was preserved. Next time another context
- # is pushed the preserved context is popped.
- self.preserved = False
-
- # remembers the exception for pop if there is one in case the context
- # preservation kicks in.
- self._preserved_exc = None
-
+ self.flashes: t.Optional[t.List[t.Tuple[str, str]]] = None
+ self.session: t.Optional["SessionMixin"] = session
# Functions that should be executed after the request on the response
# object. These will be called before the regular "after_request"
# functions.
self._after_request_functions: t.List[ft.AfterRequestCallable] = []
- @property
- def g(self) -> _AppCtxGlobals:
- import warnings
-
- warnings.warn(
- "Accessing 'g' on the request context is deprecated and"
- " will be removed in Flask 2.2. Access `g` directly or from"
- "the application context instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return _app_ctx_stack.top.g
-
- @g.setter
- def g(self, value: _AppCtxGlobals) -> None:
- import warnings
-
- warnings.warn(
- "Setting 'g' on the request context is deprecated and"
- " will be removed in Flask 2.2. Set it on the application"
- " context instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- _app_ctx_stack.top.g = value
+ self._cv_tokens: t.List[t.Tuple[contextvars.Token, t.Optional[AppContext]]] = []
def copy(self) -> "RequestContext":
"""Creates a copy of this request context with the same request object.
@@ -400,30 +354,17 @@ class RequestContext:
self.request.routing_exception = e
def push(self) -> None:
- """Binds the request context to the current context."""
- # If an exception occurs in debug mode or if context preservation is
- # activated under exception situations exactly one context stays
- # on the stack. The rationale is that you want to access that
- # information under debug situations. However if someone forgets to
- # pop that context again we want to make sure that on the next push
- # it's invalidated, otherwise we run at risk that something leaks
- # memory. This is usually only a problem in test suite since this
- # functionality is not active in production environments.
- top = _request_ctx_stack.top
- if top is not None and top.preserved:
- top.pop(top._preserved_exc)
-
# Before we push the request context we have to ensure that there
# is an application context.
- app_ctx = _app_ctx_stack.top
- if app_ctx is None or app_ctx.app != self.app:
+ app_ctx = _cv_app.get(None)
+
+ if app_ctx is None or app_ctx.app is not self.app:
app_ctx = self.app.app_context()
app_ctx.push()
- self._implicit_app_ctx_stack.append(app_ctx)
else:
- self._implicit_app_ctx_stack.append(None)
+ app_ctx = None
- _request_ctx_stack.push(self)
+ self._cv_tokens.append((_cv_request.set(self), app_ctx))
# Open the session at the moment that the request context is available.
# This allows a custom open_session method to use the request context.
@@ -449,13 +390,10 @@ class RequestContext:
.. versionchanged:: 0.9
Added the `exc` argument.
"""
- app_ctx = self._implicit_app_ctx_stack.pop()
- clear_request = False
+ clear_request = len(self._cv_tokens) == 1
try:
- if not self._implicit_app_ctx_stack:
- self.preserved = False
- self._preserved_exc = None
+ if clear_request:
if exc is _sentinel:
exc = sys.exc_info()[1]
self.app.do_teardown_request(exc)
@@ -463,31 +401,23 @@ class RequestContext:
request_close = getattr(self.request, "close", None)
if request_close is not None:
request_close()
- clear_request = True
finally:
- rv = _request_ctx_stack.pop()
+ ctx = _cv_request.get()
+ token, app_ctx = self._cv_tokens.pop()
+ _cv_request.reset(token)
# get rid of circular dependencies at the end of the request
# so that we don't require the GC to be active.
if clear_request:
- rv.request.environ["werkzeug.request"] = None
+ ctx.request.environ["werkzeug.request"] = None
- # Get rid of the app as well if necessary.
if app_ctx is not None:
app_ctx.pop(exc)
- assert (
- rv is self
- ), f"Popped wrong request context. ({rv!r} instead of {self!r})"
-
- def auto_pop(self, exc: t.Optional[BaseException]) -> None:
- if self.request.environ.get("flask._preserve_context") or (
- exc is not None and self.app.preserve_context_on_exception
- ):
- self.preserved = True
- self._preserved_exc = exc # type: ignore
- else:
- self.pop(exc)
+ if ctx is not self:
+ raise AssertionError(
+ f"Popped wrong request context. ({ctx!r} instead of {self!r})"
+ )
def __enter__(self) -> "RequestContext":
self.push()
@@ -499,12 +429,7 @@ class RequestContext:
exc_value: t.Optional[BaseException],
tb: t.Optional[TracebackType],
) -> None:
- # do not pop the request stack if we are in debug mode and an
- # exception happened. This will allow the debugger to still
- # access the request object in the interactive shell. Furthermore
- # the context can be force kept alive for the test client.
- # See flask.testing for how this works.
- self.auto_pop(exc_value)
+ self.pop(exc_value)
def __repr__(self) -> str:
return (
diff --git a/contrib/python/Flask/py3/flask/debughelpers.py b/contrib/python/Flask/py3/flask/debughelpers.py
index 27d378c24a..b0639892c4 100644
--- a/contrib/python/Flask/py3/flask/debughelpers.py
+++ b/contrib/python/Flask/py3/flask/debughelpers.py
@@ -1,10 +1,8 @@
-import os
import typing as t
-from warnings import warn
from .app import Flask
from .blueprints import Blueprint
-from .globals import _request_ctx_stack
+from .globals import request_ctx
class UnexpectedUnicodeError(AssertionError, UnicodeError):
@@ -118,9 +116,8 @@ def explain_template_loading_attempts(app: Flask, template, attempts) -> None:
info = [f"Locating template {template!r}:"]
total_found = 0
blueprint = None
- reqctx = _request_ctx_stack.top
- if reqctx is not None and reqctx.request.blueprint is not None:
- blueprint = reqctx.request.blueprint
+ if request_ctx and request_ctx.request.blueprint is not None:
+ blueprint = request_ctx.request.blueprint
for idx, (loader, srcobj, triple) in enumerate(attempts):
if isinstance(srcobj, Flask):
@@ -159,16 +156,3 @@ def explain_template_loading_attempts(app: Flask, template, attempts) -> None:
info.append(" See https://flask.palletsprojects.com/blueprints/#templates")
app.logger.info("\n".join(info))
-
-
-def explain_ignored_app_run() -> None:
- if os.environ.get("WERKZEUG_RUN_MAIN") != "true":
- warn(
- Warning(
- "Silently ignoring app.run() because the application is"
- " run from the flask command line executable. Consider"
- ' putting app.run() behind an if __name__ == "__main__"'
- " guard to silence this warning."
- ),
- stacklevel=3,
- )
diff --git a/contrib/python/Flask/py3/flask/globals.py b/contrib/python/Flask/py3/flask/globals.py
index 6d91c75edd..254da42b98 100644
--- a/contrib/python/Flask/py3/flask/globals.py
+++ b/contrib/python/Flask/py3/flask/globals.py
@@ -1,59 +1,107 @@
import typing as t
-from functools import partial
+from contextvars import ContextVar
from werkzeug.local import LocalProxy
-from werkzeug.local import LocalStack
-if t.TYPE_CHECKING:
+if t.TYPE_CHECKING: # pragma: no cover
from .app import Flask
from .ctx import _AppCtxGlobals
+ from .ctx import AppContext
+ from .ctx import RequestContext
from .sessions import SessionMixin
from .wrappers import Request
-_request_ctx_err_msg = """\
-Working outside of request context.
-This typically means that you attempted to use functionality that needed
-an active HTTP request. Consult the documentation on testing for
-information about how to avoid this problem.\
-"""
-_app_ctx_err_msg = """\
+class _FakeStack:
+ def __init__(self, name: str, cv: ContextVar[t.Any]) -> None:
+ self.name = name
+ self.cv = cv
+
+ def _warn(self):
+ import warnings
+
+ warnings.warn(
+ f"'_{self.name}_ctx_stack' is deprecated and will be"
+ " removed in Flask 2.3. Use 'g' to store data, or"
+ f" '{self.name}_ctx' to access the current context.",
+ DeprecationWarning,
+ stacklevel=3,
+ )
+
+ 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)
+
+
+_no_app_msg = """\
Working outside of application context.
This typically means that you attempted to use functionality that needed
-to interface with the current application object in some way. To solve
-this, set up an application context with app.app_context(). See the
-documentation for more information.\
+the current application. To solve this, set up an application context
+with app.app_context(). See the documentation for more information.\
"""
+_cv_app: ContextVar["AppContext"] = ContextVar("flask.app_ctx")
+__app_ctx_stack = _FakeStack("app", _cv_app)
+app_ctx: "AppContext" = LocalProxy( # type: ignore[assignment]
+ _cv_app, unbound_message=_no_app_msg
+)
+current_app: "Flask" = LocalProxy( # type: ignore[assignment]
+ _cv_app, "app", unbound_message=_no_app_msg
+)
+g: "_AppCtxGlobals" = LocalProxy( # type: ignore[assignment]
+ _cv_app, "g", unbound_message=_no_app_msg
+)
+_no_req_msg = """\
+Working outside of request context.
-def _lookup_req_object(name):
- top = _request_ctx_stack.top
- if top is None:
- raise RuntimeError(_request_ctx_err_msg)
- return getattr(top, name)
+This typically means that you attempted to use functionality that needed
+an active HTTP request. Consult the documentation on testing for
+information about how to avoid this problem.\
+"""
+_cv_request: ContextVar["RequestContext"] = ContextVar("flask.request_ctx")
+__request_ctx_stack = _FakeStack("request", _cv_request)
+request_ctx: "RequestContext" = LocalProxy( # type: ignore[assignment]
+ _cv_request, unbound_message=_no_req_msg
+)
+request: "Request" = LocalProxy( # type: ignore[assignment]
+ _cv_request, "request", unbound_message=_no_req_msg
+)
+session: "SessionMixin" = LocalProxy( # type: ignore[assignment]
+ _cv_request, "session", unbound_message=_no_req_msg
+)
-def _lookup_app_object(name):
- top = _app_ctx_stack.top
- if top is None:
- raise RuntimeError(_app_ctx_err_msg)
- return getattr(top, name)
+def __getattr__(name: str) -> t.Any:
+ if name == "_app_ctx_stack":
+ import warnings
+ warnings.warn(
+ "'_app_ctx_stack' is deprecated and will be removed in Flask 2.3.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return __app_ctx_stack
-def _find_app():
- top = _app_ctx_stack.top
- if top is None:
- raise RuntimeError(_app_ctx_err_msg)
- return top.app
+ if name == "_request_ctx_stack":
+ import warnings
+ warnings.warn(
+ "'_request_ctx_stack' is deprecated and will be removed in Flask 2.3.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return __request_ctx_stack
-# context locals
-_request_ctx_stack = LocalStack()
-_app_ctx_stack = LocalStack()
-current_app: "Flask" = LocalProxy(_find_app) # type: ignore
-request: "Request" = LocalProxy(partial(_lookup_req_object, "request")) # type: ignore
-session: "SessionMixin" = LocalProxy( # type: ignore
- partial(_lookup_req_object, "session")
-)
-g: "_AppCtxGlobals" = LocalProxy(partial(_lookup_app_object, "g")) # type: ignore
+ raise AttributeError(name)
diff --git a/contrib/python/Flask/py3/flask/helpers.py b/contrib/python/Flask/py3/flask/helpers.py
index 1e0732b31a..3833cb8a0a 100644
--- a/contrib/python/Flask/py3/flask/helpers.py
+++ b/contrib/python/Flask/py3/flask/helpers.py
@@ -3,53 +3,73 @@ import pkgutil
import socket
import sys
import typing as t
-import warnings
from datetime import datetime
from functools import lru_cache
from functools import update_wrapper
from threading import RLock
import werkzeug.utils
-from werkzeug.routing import BuildError
-from werkzeug.urls import url_quote
+from werkzeug.exceptions import abort as _wz_abort
+from werkzeug.utils import redirect as _wz_redirect
-from .globals import _app_ctx_stack
-from .globals import _request_ctx_stack
+from .globals import _cv_request
from .globals import current_app
from .globals import request
+from .globals import request_ctx
from .globals import session
from .signals import message_flashed
-if t.TYPE_CHECKING:
+if t.TYPE_CHECKING: # pragma: no cover
+ from werkzeug.wrappers import Response as BaseResponse
from .wrappers import Response
+ 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:
- """Get whether debug mode should be enabled for the app, indicated
- by the :envvar:`FLASK_DEBUG` environment variable. The default is
- ``True`` if :func:`.get_env` returns ``'development'``, or ``False``
- otherwise.
+ """Get whether debug mode should be enabled for the app, indicated by the
+ :envvar:`FLASK_DEBUG` environment variable. The default is ``False``.
"""
val = os.environ.get("FLASK_DEBUG")
if not val:
- return get_env() == "development"
+ 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 val.lower() not in ("0", "false", "no")
+ return False
+
+ return val.lower() not in {"0", "false", "no"}
def get_load_dotenv(default: bool = True) -> bool:
- """Get whether the user has disabled loading dotenv files by setting
- :envvar:`FLASK_SKIP_DOTENV`. The default is ``True``, load the
- files.
+ """Get whether the user has disabled loading default dotenv files by
+ setting :envvar:`FLASK_SKIP_DOTENV`. The default is ``True``, load
+ the files.
:param default: What to return if the env var isn't set.
"""
@@ -110,11 +130,11 @@ def stream_with_context(
return update_wrapper(decorator, generator_or_function) # type: ignore
def generator() -> t.Generator:
- ctx = _request_ctx_stack.top
+ ctx = _cv_request.get(None)
if ctx is None:
raise RuntimeError(
- "Attempted to stream with context but "
- "there was no context in the first place to keep around."
+ "'stream_with_context' can only be used when a request"
+ " context is active, such as in a view function."
)
with ctx:
# Dummy sentinel. Has to be inside the context block or we're
@@ -129,7 +149,7 @@ def stream_with_context(
yield from gen
finally:
if hasattr(gen, "close"):
- gen.close() # type: ignore
+ gen.close()
# The trick is to start the generator. Then the code execution runs until
# the first dummy None is yielded at which point the context was already
@@ -189,155 +209,107 @@ def make_response(*args: t.Any) -> "Response":
return current_app.make_response(args) # type: ignore
-def url_for(endpoint: str, **values: t.Any) -> str:
- """Generates a URL to the given endpoint with the method provided.
-
- Variable arguments that are unknown to the target endpoint are appended
- to the generated URL as query arguments. If the value of a query argument
- is ``None``, the whole pair is skipped. In case blueprints are active
- you can shortcut references to the same blueprint by prefixing the
- local endpoint with a dot (``.``).
-
- This will reference the index function local to the current blueprint::
-
- url_for('.index')
-
- See :ref:`url-building`.
-
- Configuration values ``APPLICATION_ROOT`` and ``SERVER_NAME`` are only used when
- generating URLs outside of a request context.
-
- To integrate applications, :class:`Flask` has a hook to intercept URL build
- errors through :attr:`Flask.url_build_error_handlers`. The `url_for`
- function results in a :exc:`~werkzeug.routing.BuildError` when the current
- app does not have a URL for the given endpoint and values. When it does, the
- :data:`~flask.current_app` calls its :attr:`~Flask.url_build_error_handlers` if
- it is not ``None``, which can return a string to use as the result of
- `url_for` (instead of `url_for`'s default to raise the
- :exc:`~werkzeug.routing.BuildError` exception) or re-raise the exception.
- An example::
-
- def external_url_handler(error, endpoint, values):
- "Looks up an external URL when `url_for` cannot build a URL."
- # This is an example of hooking the build_error_handler.
- # Here, lookup_url is some utility function you've built
- # which looks up the endpoint in some external URL registry.
- url = lookup_url(endpoint, **values)
- if url is None:
- # External lookup did not have a URL.
- # Re-raise the BuildError, in context of original traceback.
- exc_type, exc_value, tb = sys.exc_info()
- if exc_value is error:
- raise exc_type(exc_value).with_traceback(tb)
- else:
- raise error
- # url_for will use this result, instead of raising BuildError.
- return url
-
- app.url_build_error_handlers.append(external_url_handler)
-
- Here, `error` is the instance of :exc:`~werkzeug.routing.BuildError`, and
- `endpoint` and `values` are the arguments passed into `url_for`. Note
- that this is for building URLs outside the current application, and not for
- handling 404 NotFound errors.
-
- .. versionadded:: 0.10
- The `_scheme` parameter was added.
+def url_for(
+ endpoint: str,
+ *,
+ _anchor: t.Optional[str] = None,
+ _method: t.Optional[str] = None,
+ _scheme: t.Optional[str] = None,
+ _external: t.Optional[bool] = None,
+ **values: t.Any,
+) -> str:
+ """Generate a URL to the given endpoint with the given values.
+
+ This requires an active request or application context, and calls
+ :meth:`current_app.url_for() <flask.Flask.url_for>`. See that method
+ for full documentation.
+
+ :param endpoint: The endpoint name associated with the URL to
+ generate. If this starts with a ``.``, the current blueprint
+ name (if any) will be used.
+ :param _anchor: If given, append this as ``#anchor`` to the URL.
+ :param _method: If given, generate the URL associated with this
+ method for the endpoint.
+ :param _scheme: If given, the URL will have this scheme if it is
+ external.
+ :param _external: If given, prefer the URL to be internal (False) or
+ require it to be external (True). External URLs include the
+ scheme and domain. When not in an active request, URLs are
+ external by default.
+ :param values: Values to use for the variable parts of the URL rule.
+ Unknown keys are appended as query string arguments, like
+ ``?a=b&c=d``.
+
+ .. versionchanged:: 2.2
+ Calls ``current_app.url_for``, allowing an app to override the
+ behavior.
+
+ .. versionchanged:: 0.10
+ The ``_scheme`` parameter was added.
- .. versionadded:: 0.9
- The `_anchor` and `_method` parameters were added.
+ .. versionchanged:: 0.9
+ The ``_anchor`` and ``_method`` parameters were added.
- .. versionadded:: 0.9
- Calls :meth:`Flask.handle_build_error` on
- :exc:`~werkzeug.routing.BuildError`.
-
- :param endpoint: the endpoint of the URL (name of the function)
- :param values: the variable arguments of the URL rule
- :param _external: if set to ``True``, an absolute URL is generated. Server
- address can be changed via ``SERVER_NAME`` configuration variable which
- falls back to the `Host` header, then to the IP and port of the request.
- :param _scheme: a string specifying the desired URL scheme. The `_external`
- parameter must be set to ``True`` or a :exc:`ValueError` is raised. The default
- behavior uses the same scheme as the current request, or
- :data:`PREFERRED_URL_SCHEME` if no request context is available.
- This also can be set to an empty string to build protocol-relative
- URLs.
- :param _anchor: if provided this is added as anchor to the URL.
- :param _method: if provided this explicitly specifies an HTTP method.
+ .. versionchanged:: 0.9
+ Calls ``app.handle_url_build_error`` on build errors.
"""
- appctx = _app_ctx_stack.top
- reqctx = _request_ctx_stack.top
-
- if appctx is None:
- raise RuntimeError(
- "Attempted to generate a URL without the application context being"
- " pushed. This has to be executed when application context is"
- " available."
- )
+ return current_app.url_for(
+ endpoint,
+ _anchor=_anchor,
+ _method=_method,
+ _scheme=_scheme,
+ _external=_external,
+ **values,
+ )
- # If request specific information is available we have some extra
- # features that support "relative" URLs.
- if reqctx is not None:
- url_adapter = reqctx.url_adapter
- blueprint_name = request.blueprint
- if endpoint[:1] == ".":
- if blueprint_name is not None:
- endpoint = f"{blueprint_name}{endpoint}"
- else:
- endpoint = endpoint[1:]
+def redirect(
+ location: str, code: int = 302, Response: t.Optional[t.Type["BaseResponse"]] = None
+) -> "BaseResponse":
+ """Create a redirect response object.
- external = values.pop("_external", False)
+ If :data:`~flask.current_app` is available, it will use its
+ :meth:`~flask.Flask.redirect` method, otherwise it will use
+ :func:`werkzeug.utils.redirect`.
- # Otherwise go with the url adapter from the appctx and make
- # the URLs external by default.
- else:
- url_adapter = appctx.url_adapter
+ :param location: The URL to redirect to.
+ :param code: The status code for the redirect.
+ :param Response: The response class to use. Not used when
+ ``current_app`` is active, which uses ``app.response_class``.
- if url_adapter is None:
- raise RuntimeError(
- "Application was not able to create a URL adapter for request"
- " independent URL generation. You might be able to fix this by"
- " setting the SERVER_NAME config variable."
- )
+ .. versionadded:: 2.2
+ Calls ``current_app.redirect`` if available instead of always
+ using Werkzeug's default ``redirect``.
+ """
+ if current_app:
+ return current_app.redirect(location, code=code)
- external = values.pop("_external", True)
+ return _wz_redirect(location, code=code, Response=Response)
- anchor = values.pop("_anchor", None)
- method = values.pop("_method", None)
- scheme = values.pop("_scheme", None)
- appctx.app.inject_url_defaults(endpoint, values)
- # This is not the best way to deal with this but currently the
- # underlying Werkzeug router does not support overriding the scheme on
- # a per build call basis.
- old_scheme = None
- if scheme is not None:
- if not external:
- raise ValueError("When specifying _scheme, _external must be True")
- old_scheme = url_adapter.url_scheme
- url_adapter.url_scheme = scheme
+def abort(
+ code: t.Union[int, "BaseResponse"], *args: t.Any, **kwargs: t.Any
+) -> "te.NoReturn":
+ """Raise an :exc:`~werkzeug.exceptions.HTTPException` for the given
+ status code.
- try:
- try:
- rv = url_adapter.build(
- endpoint, values, method=method, force_external=external
- )
- finally:
- if old_scheme is not None:
- url_adapter.url_scheme = old_scheme
- except BuildError as error:
- # We need to inject the values again so that the app callback can
- # deal with that sort of stuff.
- values["_external"] = external
- values["_anchor"] = anchor
- values["_method"] = method
- values["_scheme"] = scheme
- return appctx.app.handle_url_build_error(error, endpoint, values)
-
- if anchor is not None:
- rv += f"#{url_quote(anchor)}"
- return rv
+ If :data:`~flask.current_app` is available, it will call its
+ :attr:`~flask.Flask.aborter` object, otherwise it will use
+ :func:`werkzeug.exceptions.abort`.
+
+ :param code: The status code for the exception, which must be
+ registered in ``app.aborter``.
+ :param args: Passed to the exception.
+ :param kwargs: Passed to the exception.
+
+ .. versionadded:: 2.2
+ Calls ``current_app.aborter`` if available instead of always
+ using Werkzeug's default ``abort``.
+ """
+ if current_app:
+ current_app.aborter(code, *args, **kwargs)
+
+ _wz_abort(code, *args, **kwargs)
def get_template_attribute(template_name: str, attribute: str) -> t.Any:
@@ -425,11 +397,10 @@ def get_flashed_messages(
:param category_filter: filter of categories to limit return values. Only
categories in the list will be returned.
"""
- flashes = _request_ctx_stack.top.flashes
+ flashes = request_ctx.flashes
if flashes is None:
- _request_ctx_stack.top.flashes = flashes = (
- session.pop("_flashes") if "_flashes" in session else []
- )
+ flashes = session.pop("_flashes") if "_flashes" in session else []
+ request_ctx.flashes = flashes
if category_filter:
flashes = list(filter(lambda f: f[0] in category_filter, flashes))
if not with_categories:
@@ -437,54 +408,13 @@ def get_flashed_messages(
return flashes
-def _prepare_send_file_kwargs(
- download_name: t.Optional[str] = None,
- attachment_filename: t.Optional[str] = None,
- etag: t.Optional[t.Union[bool, str]] = None,
- add_etags: t.Optional[t.Union[bool]] = None,
- max_age: t.Optional[
- t.Union[int, t.Callable[[t.Optional[str]], t.Optional[int]]]
- ] = None,
- cache_timeout: t.Optional[int] = None,
- **kwargs: t.Any,
-) -> t.Dict[str, t.Any]:
- if attachment_filename is not None:
- warnings.warn(
- "The 'attachment_filename' parameter has been renamed to"
- " 'download_name'. The old name will be removed in Flask"
- " 2.2.",
- DeprecationWarning,
- stacklevel=3,
- )
- download_name = attachment_filename
-
- if cache_timeout is not None:
- warnings.warn(
- "The 'cache_timeout' parameter has been renamed to"
- " 'max_age'. The old name will be removed in Flask 2.2.",
- DeprecationWarning,
- stacklevel=3,
- )
- max_age = cache_timeout
-
- if add_etags is not None:
- warnings.warn(
- "The 'add_etags' parameter has been renamed to 'etag'. The"
- " old name will be removed in Flask 2.2.",
- DeprecationWarning,
- stacklevel=3,
- )
- etag = add_etags
-
- if max_age is None:
- max_age = current_app.get_send_file_max_age
+def _prepare_send_file_kwargs(**kwargs: t.Any) -> t.Dict[str, t.Any]:
+ if kwargs.get("max_age") is None:
+ kwargs["max_age"] = current_app.get_send_file_max_age
kwargs.update(
environ=request.environ,
- download_name=download_name,
- etag=etag,
- max_age=max_age,
- use_x_sendfile=current_app.use_x_sendfile,
+ use_x_sendfile=current_app.config["USE_X_SENDFILE"],
response_class=current_app.response_class,
_root_path=current_app.root_path, # type: ignore
)
@@ -496,16 +426,13 @@ def send_file(
mimetype: t.Optional[str] = None,
as_attachment: bool = False,
download_name: t.Optional[str] = None,
- attachment_filename: t.Optional[str] = None,
conditional: bool = True,
etag: t.Union[bool, str] = True,
- add_etags: t.Optional[bool] = None,
last_modified: t.Optional[t.Union[datetime, int, float]] = None,
max_age: t.Optional[
t.Union[int, t.Callable[[t.Optional[str]], t.Optional[int]]]
] = None,
- cache_timeout: t.Optional[int] = None,
-):
+) -> "Response":
"""Send the contents of a file to the client.
The first argument can be a file path or a file-like object. Paths
@@ -607,20 +534,17 @@ def send_file(
.. versionadded:: 0.2
"""
- return werkzeug.utils.send_file(
+ return werkzeug.utils.send_file( # type: ignore[return-value]
**_prepare_send_file_kwargs(
path_or_file=path_or_file,
environ=request.environ,
mimetype=mimetype,
as_attachment=as_attachment,
download_name=download_name,
- attachment_filename=attachment_filename,
conditional=conditional,
etag=etag,
- add_etags=add_etags,
last_modified=last_modified,
max_age=max_age,
- cache_timeout=cache_timeout,
)
)
@@ -628,7 +552,6 @@ def send_file(
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`.
@@ -664,16 +587,7 @@ def send_from_directory(
.. 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.2.",
- DeprecationWarning,
- stacklevel=2,
- )
- path = filename
-
- return werkzeug.utils.send_from_directory( # type: ignore
+ return werkzeug.utils.send_from_directory( # type: ignore[return-value]
directory, path, **_prepare_send_file_kwargs(**kwargs)
)
@@ -703,7 +617,7 @@ def get_root_path(import_name: str) -> str:
return os.getcwd()
if hasattr(loader, "get_filename"):
- filepath = loader.get_filename(import_name) # type: ignore
+ filepath = loader.get_filename(import_name)
else:
# Fall back to imports.
__import__(import_name)
diff --git a/contrib/python/Flask/py3/flask/json/__init__.py b/contrib/python/Flask/py3/flask/json/__init__.py
index adefe02dcd..65d8829acb 100644
--- a/contrib/python/Flask/py3/flask/json/__init__.py
+++ b/contrib/python/Flask/py3/flask/json/__init__.py
@@ -1,17 +1,14 @@
-import dataclasses
-import decimal
+from __future__ import annotations
+
import json as _json
import typing as t
-import uuid
-from datetime import date
from jinja2.utils import htmlsafe_json_dumps as _jinja_htmlsafe_dumps
-from werkzeug.http import http_date
from ..globals import current_app
-from ..globals import request
+from .provider import _default
-if t.TYPE_CHECKING:
+if t.TYPE_CHECKING: # pragma: no cover
from ..app import Flask
from ..wrappers import Response
@@ -32,23 +29,30 @@ class JSONEncoder(_json.JSONEncoder):
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.
"""
- if isinstance(o, date):
- return http_date(o)
- if isinstance(o, (decimal.Decimal, uuid.UUID)):
- return str(o)
- if dataclasses and dataclasses.is_dataclass(o):
- return dataclasses.asdict(o)
- if hasattr(o, "__html__"):
- return str(o.__html__())
- return super().default(o)
+ return _default(o)
class JSONDecoder(_json.JSONDecoder):
@@ -59,144 +63,193 @@ class JSONDecoder(_json.JSONDecoder):
Assign a subclass of this to :attr:`flask.Flask.json_decoder` or
:attr:`flask.Blueprint.json_decoder` to override the default.
- """
-
-
-def _dump_arg_defaults(
- kwargs: t.Dict[str, t.Any], app: t.Optional["Flask"] = None
-) -> None:
- """Inject default arguments for dump functions."""
- if app is None:
- app = current_app
- if app:
- cls = app.json_encoder
- bp = app.blueprints.get(request.blueprint) if request else None # type: ignore
- if bp is not None and bp.json_encoder is not None:
- cls = bp.json_encoder
-
- # Only set a custom encoder if it has custom behavior. This is
- # faster on PyPy.
- if cls is not _json.JSONEncoder:
- kwargs.setdefault("cls", cls)
-
- kwargs.setdefault("cls", cls)
- kwargs.setdefault("ensure_ascii", app.config["JSON_AS_ASCII"])
- kwargs.setdefault("sort_keys", app.config["JSON_SORT_KEYS"])
- else:
- kwargs.setdefault("sort_keys", True)
- kwargs.setdefault("cls", JSONEncoder)
+ .. deprecated:: 2.2
+ Will be removed in Flask 2.3. Use ``app.json`` instead.
+ """
+ def __init__(self, **kwargs) -> None:
+ import warnings
-def _load_arg_defaults(
- kwargs: t.Dict[str, t.Any], app: t.Optional["Flask"] = None
-) -> None:
- """Inject default arguments for load functions."""
- if app is None:
- app = current_app
+ 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)
- if app:
- cls = app.json_decoder
- bp = app.blueprints.get(request.blueprint) if request else None # type: ignore
- if bp is not None and bp.json_decoder is not None:
- cls = bp.json_decoder
- # Only set a custom decoder if it has custom behavior. This is
- # faster on PyPy.
- if cls not in {JSONDecoder, _json.JSONDecoder}:
- kwargs.setdefault("cls", cls)
+def dumps(obj: t.Any, *, app: Flask | None = None, **kwargs: t.Any) -> str:
+ """Serialize data as JSON.
+ If :data:`~flask.current_app` is available, it will use its
+ :meth:`app.json.dumps() <flask.json.provider.JSONProvider.dumps>`
+ method, otherwise it will use :func:`json.dumps`.
-def dumps(obj: t.Any, app: t.Optional["Flask"] = None, **kwargs: t.Any) -> str:
- """Serialize an object to a string of JSON.
+ :param obj: The data to serialize.
+ :param kwargs: Arguments passed to the ``dumps`` implementation.
- Takes the same arguments as the built-in :func:`json.dumps`, with
- some defaults from application configuration.
+ .. versionchanged:: 2.2
+ Calls ``current_app.json.dumps``, allowing an app to override
+ the behavior.
- :param obj: Object to serialize to JSON.
- :param app: Use this app's config instead of the active app context
- or defaults.
- :param kwargs: Extra arguments passed to :func:`json.dumps`.
+ .. versionchanged:: 2.2
+ The ``app`` parameter will be removed in Flask 2.3.
.. versionchanged:: 2.0.2
:class:`decimal.Decimal` is supported by converting to a string.
.. versionchanged:: 2.0
- ``encoding`` is deprecated and will be removed in Flask 2.1.
+ ``encoding`` will be removed in Flask 2.1.
.. versionchanged:: 1.0.3
``app`` can be passed directly, rather than requiring an app
context for configuration.
"""
- _dump_arg_defaults(kwargs, app=app)
+ if app is not None:
+ import warnings
+
+ 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)
return _json.dumps(obj, **kwargs)
def dump(
- obj: t.Any, fp: t.IO[str], app: t.Optional["Flask"] = None, **kwargs: t.Any
+ obj: t.Any, fp: t.IO[str], *, app: Flask | None = None, **kwargs: t.Any
) -> None:
- """Serialize an object to JSON written to a file object.
+ """Serialize data as JSON and write to a file.
+
+ If :data:`~flask.current_app` is available, it will use its
+ :meth:`app.json.dump() <flask.json.provider.JSONProvider.dump>`
+ method, otherwise it will use :func:`json.dump`.
- Takes the same arguments as the built-in :func:`json.dump`, with
- some defaults from application configuration.
+ :param obj: The data to serialize.
+ :param fp: A file opened for writing text. Should use the UTF-8
+ encoding to be valid JSON.
+ :param kwargs: Arguments passed to the ``dump`` implementation.
- :param obj: Object to serialize to JSON.
- :param fp: File object to write JSON to.
- :param app: Use this app's config instead of the active app context
- or defaults.
- :param kwargs: Extra arguments passed to :func:`json.dump`.
+ .. versionchanged:: 2.2
+ Calls ``current_app.json.dump``, allowing an app to override
+ the behavior.
+
+ .. versionchanged:: 2.2
+ The ``app`` parameter will be removed in Flask 2.3.
.. versionchanged:: 2.0
- Writing to a binary file, and the ``encoding`` argument, is
- deprecated and will be removed in Flask 2.1.
+ Writing to a binary file, and the ``encoding`` argument, will be
+ removed in Flask 2.1.
"""
- _dump_arg_defaults(kwargs, app=app)
- _json.dump(obj, fp, **kwargs)
+ if app is not None:
+ import warnings
+
+ 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:
+ kwargs.setdefault("default", _default)
+ _json.dump(obj, fp, **kwargs)
+
+def loads(s: str | bytes, *, app: Flask | None = None, **kwargs: t.Any) -> t.Any:
+ """Deserialize data as JSON.
-def loads(
- s: t.Union[str, bytes],
- app: t.Optional["Flask"] = None,
- **kwargs: t.Any,
-) -> t.Any:
- """Deserialize an object from a string of JSON.
+ If :data:`~flask.current_app` is available, it will use its
+ :meth:`app.json.loads() <flask.json.provider.JSONProvider.loads>`
+ method, otherwise it will use :func:`json.loads`.
- Takes the same arguments as the built-in :func:`json.loads`, with
- some defaults from application configuration.
+ :param s: Text or UTF-8 bytes.
+ :param kwargs: Arguments passed to the ``loads`` implementation.
- :param s: JSON string to deserialize.
- :param app: Use this app's config instead of the active app context
- or defaults.
- :param kwargs: Extra arguments passed to :func:`json.loads`.
+ .. versionchanged:: 2.2
+ Calls ``current_app.json.loads``, allowing an app to override
+ the behavior.
+
+ .. versionchanged:: 2.2
+ The ``app`` parameter will be removed in Flask 2.3.
.. versionchanged:: 2.0
- ``encoding`` is deprecated and will be removed in Flask 2.1. The
- data must be a string or UTF-8 bytes.
+ ``encoding`` will be removed in Flask 2.1. The data must be a
+ string or UTF-8 bytes.
.. versionchanged:: 1.0.3
``app`` can be passed directly, rather than requiring an app
context for configuration.
"""
- _load_arg_defaults(kwargs, app=app)
+ if app is not None:
+ import warnings
+
+ 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)
-def load(fp: t.IO[str], app: t.Optional["Flask"] = None, **kwargs: t.Any) -> t.Any:
- """Deserialize an object from JSON read from a file object.
+def load(fp: t.IO[t.AnyStr], *, app: Flask | None = None, **kwargs: t.Any) -> t.Any:
+ """Deserialize data as JSON read from a file.
+
+ If :data:`~flask.current_app` is available, it will use its
+ :meth:`app.json.load() <flask.json.provider.JSONProvider.load>`
+ method, otherwise it will use :func:`json.load`.
- Takes the same arguments as the built-in :func:`json.load`, with
- some defaults from application configuration.
+ :param fp: A file opened for reading text or UTF-8 bytes.
+ :param kwargs: Arguments passed to the ``load`` implementation.
- :param fp: File object to read JSON from.
- :param app: Use this app's config instead of the active app context
- or defaults.
- :param kwargs: Extra arguments passed to :func:`json.load`.
+ .. versionchanged:: 2.2
+ Calls ``current_app.json.load``, allowing an app to override
+ the behavior.
+
+ .. versionchanged:: 2.2
+ The ``app`` parameter will be removed in Flask 2.3.
.. versionchanged:: 2.0
- ``encoding`` is deprecated and will be removed in Flask 2.1. The
- file must be text mode, or binary mode with UTF-8 bytes.
+ ``encoding`` will be removed in Flask 2.1. The file must be text
+ mode, or binary mode with UTF-8 bytes.
"""
- _load_arg_defaults(kwargs, app=app)
+ if app is not None:
+ import warnings
+
+ 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)
@@ -212,6 +265,9 @@ def htmlsafe_dumps(obj: t.Any, **kwargs: t.Any) -> str:
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`.
@@ -221,6 +277,14 @@ def htmlsafe_dumps(obj: t.Any, **kwargs: t.Any) -> str:
``<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)
@@ -228,77 +292,51 @@ 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`.
- """
- fp.write(htmlsafe_dumps(obj, **kwargs))
+ .. deprecated:: 2.2
+ Will be removed in Flask 2.3.
+ """
+ import warnings
-def jsonify(*args: t.Any, **kwargs: t.Any) -> "Response":
- """Serialize data to JSON and wrap it in a :class:`~flask.Response`
- with the :mimetype:`application/json` mimetype.
-
- Uses :func:`dumps` to serialize the data, but ``args`` and
- ``kwargs`` are treated as data rather than arguments to
- :func:`json.dumps`.
-
- 1. Single argument: Treated as a single value.
- 2. Multiple arguments: Treated as a list of values.
- ``jsonify(1, 2, 3)`` is the same as ``jsonify([1, 2, 3])``.
- 3. Keyword arguments: Treated as a dict of values.
- ``jsonify(data=data, errors=errors)`` is the same as
- ``jsonify({"data": data, "errors": errors})``.
- 4. Passing both arguments and keyword arguments is not allowed as
- it's not clear what should happen.
+ 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))
- .. code-block:: python
- from flask import jsonify
+def jsonify(*args: t.Any, **kwargs: t.Any) -> Response:
+ """Serialize the given arguments as JSON, and return a
+ :class:`~flask.Response` object with the ``application/json``
+ mimetype. A dict or list returned from a view will be converted to a
+ JSON response automatically without needing to call this.
- @app.route("/users/me")
- def get_current_user():
- return jsonify(
- username=g.user.username,
- email=g.user.email,
- id=g.user.id,
- )
+ This requires an active request or application context, and calls
+ :meth:`app.json.response() <flask.json.provider.JSONProvider.response>`.
- Will return a JSON response like this:
+ In debug mode, the output is formatted with indentation to make it
+ easier to read. This may also be controlled by the provider.
- .. code-block:: javascript
+ Either positional or keyword arguments can be given, not both.
+ If no arguments are given, ``None`` is serialized.
- {
- "username": "admin",
- "email": "admin@localhost",
- "id": 42
- }
+ :param args: A single value to serialize, or multiple values to
+ treat as a list to serialize.
+ :param kwargs: Treat as a dict to serialize.
- The default output omits indents and spaces after separators. In
- debug mode or if :data:`JSONIFY_PRETTYPRINT_REGULAR` is ``True``,
- the output will be formatted to be easier to read.
+ .. versionchanged:: 2.2
+ Calls ``current_app.json.response``, allowing an app to override
+ the behavior.
.. versionchanged:: 2.0.2
:class:`decimal.Decimal` is supported by converting to a string.
.. versionchanged:: 0.11
- Added support for serializing top-level arrays. This introduces
- a security risk in ancient browsers. See :ref:`security-json`.
+ Added support for serializing top-level arrays. This was a
+ security risk in ancient browsers. See :ref:`security-json`.
.. versionadded:: 0.2
"""
- indent = None
- separators = (",", ":")
-
- if current_app.config["JSONIFY_PRETTYPRINT_REGULAR"] or current_app.debug:
- indent = 2
- separators = (", ", ": ")
-
- if args and kwargs:
- raise TypeError("jsonify() behavior undefined when passed both args and kwargs")
- elif len(args) == 1: # single args are passed directly to dumps()
- data = args[0]
- else:
- data = args or kwargs
-
- return current_app.response_class(
- f"{dumps(data, indent=indent, separators=separators)}\n",
- mimetype=current_app.config["JSONIFY_MIMETYPE"],
- )
+ return current_app.json.response(*args, **kwargs)
diff --git a/contrib/python/Flask/py3/flask/json/provider.py b/contrib/python/Flask/py3/flask/json/provider.py
new file mode 100644
index 0000000000..cb6aae809b
--- /dev/null
+++ b/contrib/python/Flask/py3/flask/json/provider.py
@@ -0,0 +1,310 @@
+from __future__ import annotations
+
+import dataclasses
+import decimal
+import json
+import typing as t
+import uuid
+import weakref
+from datetime import date
+
+from werkzeug.http import http_date
+
+from ..globals import request
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from ..app import Flask
+ from ..wrappers import Response
+
+
+class JSONProvider:
+ """A standard set of JSON operations for an application. Subclasses
+ of this can be used to customize JSON behavior or use different
+ JSON libraries.
+
+ To implement a provider for a specific library, subclass this base
+ class and implement at least :meth:`dumps` and :meth:`loads`. All
+ other methods have default implementations.
+
+ To use a different provider, either subclass ``Flask`` and set
+ :attr:`~flask.Flask.json_provider_class` to a provider class, or set
+ :attr:`app.json <flask.Flask.json>` to an instance of the class.
+
+ :param app: An application instance. This will be stored as a
+ :class:`weakref.proxy` on the :attr:`_app` attribute.
+
+ .. versionadded:: 2.2
+ """
+
+ def __init__(self, app: Flask) -> None:
+ self._app = weakref.proxy(app)
+
+ def dumps(self, obj: t.Any, **kwargs: t.Any) -> str:
+ """Serialize data as JSON.
+
+ :param obj: The data to serialize.
+ :param kwargs: May be passed to the underlying JSON library.
+ """
+ raise NotImplementedError
+
+ def dump(self, obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None:
+ """Serialize data as JSON and write to a file.
+
+ :param obj: The data to serialize.
+ :param fp: A file opened for writing text. Should use the UTF-8
+ encoding to be valid JSON.
+ :param kwargs: May be passed to the underlying JSON library.
+ """
+ fp.write(self.dumps(obj, **kwargs))
+
+ def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any:
+ """Deserialize data as JSON.
+
+ :param s: Text or UTF-8 bytes.
+ :param kwargs: May be passed to the underlying JSON library.
+ """
+ raise NotImplementedError
+
+ def load(self, fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any:
+ """Deserialize data as JSON read from a file.
+
+ :param fp: A file opened for reading text or UTF-8 bytes.
+ :param kwargs: May be passed to the underlying JSON library.
+ """
+ return self.loads(fp.read(), **kwargs)
+
+ def _prepare_response_obj(
+ self, args: t.Tuple[t.Any, ...], kwargs: t.Dict[str, t.Any]
+ ) -> t.Any:
+ if args and kwargs:
+ raise TypeError("app.json.response() takes either args or kwargs, not both")
+
+ if not args and not kwargs:
+ return None
+
+ if len(args) == 1:
+ return args[0]
+
+ return args or kwargs
+
+ def response(self, *args: t.Any, **kwargs: t.Any) -> Response:
+ """Serialize the given arguments as JSON, and return a
+ :class:`~flask.Response` object with the ``application/json``
+ mimetype.
+
+ The :func:`~flask.json.jsonify` function calls this method for
+ the current application.
+
+ Either positional or keyword arguments can be given, not both.
+ If no arguments are given, ``None`` is serialized.
+
+ :param args: A single value to serialize, or multiple values to
+ treat as a list to serialize.
+ :param kwargs: Treat as a dict to serialize.
+ """
+ obj = self._prepare_response_obj(args, kwargs)
+ return self._app.response_class(self.dumps(obj), mimetype="application/json")
+
+
+def _default(o: t.Any) -> t.Any:
+ if isinstance(o, date):
+ return http_date(o)
+
+ if isinstance(o, (decimal.Decimal, uuid.UUID)):
+ return str(o)
+
+ if dataclasses and dataclasses.is_dataclass(o):
+ return dataclasses.asdict(o)
+
+ if hasattr(o, "__html__"):
+ return str(o.__html__())
+
+ raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
+
+
+class DefaultJSONProvider(JSONProvider):
+ """Provide JSON operations using Python's built-in :mod:`json`
+ library. Serializes the following additional data types:
+
+ - :class:`datetime.datetime` and :class:`datetime.date` are
+ serialized to :rfc:`822` strings. This is the same as the HTTP
+ date format.
+ - :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.
+ """
+
+ default: t.Callable[[t.Any], t.Any] = staticmethod(
+ _default
+ ) # type: ignore[assignment]
+ """Apply this function to any object that :meth:`json.dumps` does
+ not know how to serialize. It should return a valid JSON type or
+ raise a ``TypeError``.
+ """
+
+ ensure_ascii = True
+ """Replace non-ASCII characters with escape sequences. This may be
+ more compatible with some clients, but can be disabled for better
+ performance and size.
+ """
+
+ sort_keys = True
+ """Sort the keys in any serialized dicts. This may be useful for
+ some caching situations, but can be disabled for better performance.
+ When enabled, keys must all be strings, they are not converted
+ before sorting.
+ """
+
+ compact: bool | None = None
+ """If ``True``, or ``None`` out of debug mode, the :meth:`response`
+ output will not add indentation, newlines, or spaces. If ``False``,
+ or ``None`` in debug mode, it will use a non-compact representation.
+ """
+
+ mimetype = "application/json"
+ """The mimetype set in :meth:`response`."""
+
+ def dumps(self, obj: t.Any, **kwargs: t.Any) -> str:
+ """Serialize data as JSON to a string.
+
+ Keyword arguments are passed to :func:`json.dumps`. Sets some
+ parameter defaults from the :attr:`default`,
+ :attr:`ensure_ascii`, and :attr:`sort_keys` attributes.
+
+ :param obj: The data to serialize.
+ :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)
+ else:
+ kwargs.setdefault("default", self.default)
+
+ 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)
+
+ def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any:
+ """Deserialize data as JSON from a string or bytes.
+
+ :param s: Text or UTF-8 bytes.
+ :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)
+
+ def response(self, *args: t.Any, **kwargs: t.Any) -> Response:
+ """Serialize the given arguments as JSON, and return a
+ :class:`~flask.Response` object with it. The response mimetype
+ will be "application/json" and can be changed with
+ :attr:`mimetype`.
+
+ If :attr:`compact` is ``False`` or debug mode is enabled, the
+ output will be formatted to be easier to read.
+
+ Either positional or keyword arguments can be given, not both.
+ If no arguments are given, ``None`` is serialized.
+
+ :param args: A single value to serialize, or multiple values to
+ treat as a list to serialize.
+ :param kwargs: Treat as a dict to serialize.
+ """
+ obj = self._prepare_response_obj(args, kwargs)
+ 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:
+ 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)
+ else:
+ 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(
+ f"{self.dumps(obj, **dump_args)}\n", mimetype=mimetype
+ )
diff --git a/contrib/python/Flask/py3/flask/logging.py b/contrib/python/Flask/py3/flask/logging.py
index 48a5b7ff4c..8981b82055 100644
--- a/contrib/python/Flask/py3/flask/logging.py
+++ b/contrib/python/Flask/py3/flask/logging.py
@@ -6,7 +6,7 @@ from werkzeug.local import LocalProxy
from .globals import request
-if t.TYPE_CHECKING:
+if t.TYPE_CHECKING: # pragma: no cover
from .app import Flask
diff --git a/contrib/python/Flask/py3/flask/scaffold.py b/contrib/python/Flask/py3/flask/scaffold.py
index 8f301d9001..601c1dfd47 100644
--- a/contrib/python/Flask/py3/flask/scaffold.py
+++ b/contrib/python/Flask/py3/flask/scaffold.py
@@ -1,14 +1,14 @@
import importlib.util
import mimetypes
+import json
import os
import pathlib
import pkgutil
import sys
import typing as t
from collections import defaultdict
+from datetime import timedelta
from functools import update_wrapper
-from json import JSONDecoder
-from json import JSONEncoder
from jinja2 import ChoiceLoader, FileSystemLoader, ResourceLoader, PackageLoader
from werkzeug.exceptions import default_exceptions
@@ -23,32 +23,32 @@ from .helpers import send_file
from .helpers import send_from_directory
from .templating import _default_template_ctx_processor
-if t.TYPE_CHECKING:
+if t.TYPE_CHECKING: # pragma: no cover
from .wrappers import Response
# a singleton sentinel value for parameter defaults
_sentinel = object()
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
+T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable)
+T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable)
+T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable)
+T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
+T_template_context_processor = t.TypeVar(
+ "T_template_context_processor", bound=ft.TemplateContextProcessorCallable
+)
+T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable)
+T_url_value_preprocessor = t.TypeVar(
+ "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable
+)
+T_route = t.TypeVar("T_route", bound=ft.RouteCallable)
def setupmethod(f: F) -> F:
- """Wraps a method so that it performs a check in debug mode if the
- first request was already handled.
- """
+ f_name = f.__name__
def wrapper_func(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
- if self._is_setup_finished():
- raise AssertionError(
- "A setup function was called after the first request "
- "was handled. This usually indicates a bug in the"
- " application where a module was not imported and"
- " decorators or other functionality was called too"
- " late.\nTo fix this make sure to import all your view"
- " modules, database models, and everything related at a"
- " central place before the application starts serving"
- " requests."
- )
+ self._check_setup_finished(f_name)
return f(self, *args, **kwargs)
return t.cast(F, update_wrapper(wrapper_func, f))
@@ -78,18 +78,24 @@ class Scaffold:
#: JSON encoder class used by :func:`flask.json.dumps`. If a
#: blueprint sets this, it will be used instead of the app's value.
- json_encoder: t.Optional[t.Type[JSONEncoder]] = None
+ #:
+ #: .. 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.
- json_decoder: t.Optional[t.Type[JSONDecoder]] = None
+ #:
+ #: .. deprecated:: 2.2
+ #: Will be removed in Flask 2.3.
+ json_decoder: t.Union[t.Type[json.JSONDecoder], None] = None
def __init__(
self,
import_name: str,
static_folder: t.Optional[t.Union[str, os.PathLike]] = None,
static_url_path: t.Optional[str] = None,
- template_folder: t.Optional[str] = None,
+ template_folder: t.Optional[t.Union[str, os.PathLike]] = None,
root_path: t.Optional[str] = None,
):
#: The name of the package or module that this object belongs
@@ -241,7 +247,7 @@ class Scaffold:
def __repr__(self) -> str:
return f"<{type(self).__name__} {self.name!r}>"
- def _is_setup_finished(self) -> bool:
+ def _check_setup_finished(self, f_name: str) -> None:
raise NotImplementedError
@property
@@ -306,12 +312,15 @@ class Scaffold:
.. versionadded:: 0.9
"""
- value = current_app.send_file_max_age_default
+ value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"]
if value is None:
return None
- return int(value.total_seconds())
+ if isinstance(value, timedelta):
+ return int(value.total_seconds())
+
+ return value
def send_static_file(self, filename: str) -> "Response":
"""The view function used to serve files from
@@ -393,60 +402,54 @@ class Scaffold:
method: str,
rule: str,
options: dict,
- ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]:
+ ) -> t.Callable[[T_route], T_route]:
if "methods" in options:
raise TypeError("Use the 'route' decorator to use the 'methods' argument.")
return self.route(rule, methods=[method], **options)
- def get(
- self, rule: str, **options: t.Any
- ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]:
+ @setupmethod
+ def get(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
"""Shortcut for :meth:`route` with ``methods=["GET"]``.
.. versionadded:: 2.0
"""
return self._method_route("GET", rule, options)
- def post(
- self, rule: str, **options: t.Any
- ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]:
+ @setupmethod
+ def post(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
"""Shortcut for :meth:`route` with ``methods=["POST"]``.
.. versionadded:: 2.0
"""
return self._method_route("POST", rule, options)
- def put(
- self, rule: str, **options: t.Any
- ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]:
+ @setupmethod
+ def put(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
"""Shortcut for :meth:`route` with ``methods=["PUT"]``.
.. versionadded:: 2.0
"""
return self._method_route("PUT", rule, options)
- def delete(
- self, rule: str, **options: t.Any
- ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]:
+ @setupmethod
+ def delete(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
"""Shortcut for :meth:`route` with ``methods=["DELETE"]``.
.. versionadded:: 2.0
"""
return self._method_route("DELETE", rule, options)
- def patch(
- self, rule: str, **options: t.Any
- ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]:
+ @setupmethod
+ def patch(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
"""Shortcut for :meth:`route` with ``methods=["PATCH"]``.
.. versionadded:: 2.0
"""
return self._method_route("PATCH", rule, options)
- def route(
- self, rule: str, **options: t.Any
- ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]:
+ @setupmethod
+ def route(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
"""Decorate a view function to register it with the given URL
rule and options. Calls :meth:`add_url_rule`, which has more
details about the implementation.
@@ -470,7 +473,7 @@ class Scaffold:
:class:`~werkzeug.routing.Rule` object.
"""
- def decorator(f: ft.RouteDecorator) -> ft.RouteDecorator:
+ def decorator(f: T_route) -> T_route:
endpoint = options.pop("endpoint", None)
self.add_url_rule(rule, endpoint, f, **options)
return f
@@ -482,7 +485,7 @@ class Scaffold:
self,
rule: str,
endpoint: t.Optional[str] = None,
- view_func: t.Optional[ft.ViewCallable] = None,
+ view_func: t.Optional[ft.RouteCallable] = None,
provide_automatic_options: t.Optional[bool] = None,
**options: t.Any,
) -> None:
@@ -545,7 +548,8 @@ class Scaffold:
"""
raise NotImplementedError
- def endpoint(self, endpoint: str) -> t.Callable:
+ @setupmethod
+ def endpoint(self, endpoint: str) -> t.Callable[[F], F]:
"""Decorate a view function to register it for the given
endpoint. Used if a rule is added without a ``view_func`` with
:meth:`add_url_rule`.
@@ -562,14 +566,14 @@ class Scaffold:
function.
"""
- def decorator(f):
+ def decorator(f: F) -> F:
self.view_functions[endpoint] = f
return f
return decorator
@setupmethod
- def before_request(self, f: ft.BeforeRequestCallable) -> ft.BeforeRequestCallable:
+ def before_request(self, f: T_before_request) -> T_before_request:
"""Register a function to run before each request.
For example, this can be used to open a database connection, or
@@ -586,12 +590,17 @@ class Scaffold:
a non-``None`` value, the value is handled as if it was the
return value from the view, and further request handling is
stopped.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ executes before every request. When used on a blueprint, this executes before
+ every request that the blueprint handles. To register with a blueprint and
+ execute before every request, use :meth:`.Blueprint.before_app_request`.
"""
self.before_request_funcs.setdefault(None, []).append(f)
return f
@setupmethod
- def after_request(self, f: ft.AfterRequestCallable) -> ft.AfterRequestCallable:
+ def after_request(self, f: T_after_request) -> T_after_request:
"""Register a function to run after each request to this object.
The function is called with the response object, and must return
@@ -602,61 +611,71 @@ class Scaffold:
``after_request`` functions will not be called. Therefore, this
should not be used for actions that must execute, such as to
close resources. Use :meth:`teardown_request` for that.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ executes after every request. When used on a blueprint, this executes after
+ every request that the blueprint handles. To register with a blueprint and
+ execute after every request, use :meth:`.Blueprint.after_app_request`.
"""
self.after_request_funcs.setdefault(None, []).append(f)
return f
@setupmethod
- def teardown_request(self, f: ft.TeardownCallable) -> ft.TeardownCallable:
- """Register a function to be run at the end of each request,
- regardless of whether there was an exception or not. These functions
- are executed when the request context is popped, even if not an
- actual request was performed.
+ def teardown_request(self, f: T_teardown) -> T_teardown:
+ """Register a function to be called when the request context is
+ popped. Typically this happens at the end of each request, but
+ contexts may be pushed manually as well during testing.
- Example::
+ .. code-block:: python
- ctx = app.test_request_context()
- ctx.push()
- ...
- ctx.pop()
+ with app.test_request_context():
+ ...
- When ``ctx.pop()`` is executed in the above example, the teardown
- functions are called just before the request context moves from the
- stack of active contexts. This becomes relevant if you are using
- such constructs in tests.
+ When the ``with`` block exits (or ``ctx.pop()`` is called), the
+ teardown functions are called just before the request context is
+ made inactive.
- Teardown functions must avoid raising exceptions. If
- they execute code that might fail they
- will have to surround the execution of that code with try/except
- statements and log any errors.
+ When a teardown function was called because of an unhandled
+ exception it will be passed an error object. If an
+ :meth:`errorhandler` is registered, it will handle the exception
+ and the teardown will not receive it.
- When a teardown function was called because of an exception it will
- be passed an error object.
+ Teardown functions must avoid raising exceptions. If they
+ execute code that might fail they must surround that code with a
+ ``try``/``except`` block and log any errors.
The return values of teardown functions are ignored.
- .. admonition:: Debug Note
-
- In debug mode Flask will not tear down a request on an exception
- immediately. Instead it will keep it alive so that the interactive
- debugger can still access it. This behavior can be controlled
- by the ``PRESERVE_CONTEXT_ON_EXCEPTION`` configuration variable.
+ This is available on both app and blueprint objects. When used on an app, this
+ executes after every request. When used on a blueprint, this executes after
+ every request that the blueprint handles. To register with a blueprint and
+ execute after every request, use :meth:`.Blueprint.teardown_app_request`.
"""
self.teardown_request_funcs.setdefault(None, []).append(f)
return f
@setupmethod
def context_processor(
- self, f: ft.TemplateContextProcessorCallable
- ) -> ft.TemplateContextProcessorCallable:
- """Registers a template context processor function."""
+ self,
+ f: T_template_context_processor,
+ ) -> T_template_context_processor:
+ """Registers a template context processor function. These functions run before
+ rendering a template. The keys of the returned dict are added as variables
+ available in the template.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ is called for every rendered template. When used on a blueprint, this is called
+ for templates rendered from the blueprint's views. To register with a blueprint
+ and affect every template, use :meth:`.Blueprint.app_context_processor`.
+ """
self.template_context_processors[None].append(f)
return f
@setupmethod
def url_value_preprocessor(
- self, f: ft.URLValuePreprocessorCallable
- ) -> ft.URLValuePreprocessorCallable:
+ self,
+ f: T_url_value_preprocessor,
+ ) -> T_url_value_preprocessor:
"""Register a URL value preprocessor function for all view
functions in the application. These functions will be called before the
:meth:`before_request` functions.
@@ -668,15 +687,25 @@ class Scaffold:
The function is passed the endpoint name and values dict. The return
value is ignored.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ is called for every request. When used on a blueprint, this is called for
+ requests that the blueprint handles. To register with a blueprint and affect
+ every request, use :meth:`.Blueprint.app_url_value_preprocessor`.
"""
self.url_value_preprocessors[None].append(f)
return f
@setupmethod
- def url_defaults(self, f: ft.URLDefaultCallable) -> ft.URLDefaultCallable:
+ def url_defaults(self, f: T_url_defaults) -> T_url_defaults:
"""Callback function for URL defaults for all view functions of the
application. It's called with the endpoint and values and should
update the values passed in place.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ is called for every request. When used on a blueprint, this is called for
+ requests that the blueprint handles. To register with a blueprint and affect
+ every request, use :meth:`.Blueprint.app_url_defaults`.
"""
self.url_default_functions[None].append(f)
return f
@@ -684,7 +713,7 @@ class Scaffold:
@setupmethod
def errorhandler(
self, code_or_exception: t.Union[t.Type[Exception], int]
- ) -> t.Callable[[ft.ErrorHandlerDecorator], ft.ErrorHandlerDecorator]:
+ ) -> t.Callable[[T_error_handler], T_error_handler]:
"""Register a function to handle errors by code or exception class.
A decorator that is used to register a function given an
@@ -700,6 +729,11 @@ class Scaffold:
def special_exception_handler(error):
return 'Database connection failed', 500
+ This is available on both app and blueprint objects. When used on an app, this
+ can handle errors from every request. When used on a blueprint, this can handle
+ errors from requests that the blueprint handles. To register with a blueprint
+ and affect every request, use :meth:`.Blueprint.app_errorhandler`.
+
.. versionadded:: 0.7
Use :meth:`register_error_handler` instead of modifying
:attr:`error_handler_spec` directly, for application wide error
@@ -714,7 +748,7 @@ class Scaffold:
an arbitrary exception
"""
- def decorator(f: ft.ErrorHandlerDecorator) -> ft.ErrorHandlerDecorator:
+ def decorator(f: T_error_handler) -> T_error_handler:
self.register_error_handler(code_or_exception, f)
return f
@@ -732,22 +766,7 @@ class Scaffold:
.. versionadded:: 0.7
"""
- if isinstance(code_or_exception, HTTPException): # old broken behavior
- raise ValueError(
- "Tried to register a handler for an exception instance"
- f" {code_or_exception!r}. Handlers can only be"
- " registered for exception classes or HTTP error codes."
- )
-
- try:
- exc_class, code = self._get_exc_class_and_code(code_or_exception)
- except KeyError:
- raise KeyError(
- f"'{code_or_exception}' is not a recognized HTTP error"
- " code. Use a subclass of HTTPException with that code"
- " instead."
- ) from None
-
+ exc_class, code = self._get_exc_class_and_code(code_or_exception)
self.error_handler_spec[None][code][exc_class] = f
@staticmethod
@@ -762,14 +781,32 @@ class Scaffold:
code as an integer.
"""
exc_class: t.Type[Exception]
+
if isinstance(exc_class_or_code, int):
- exc_class = default_exceptions[exc_class_or_code]
+ try:
+ exc_class = default_exceptions[exc_class_or_code]
+ except KeyError:
+ raise ValueError(
+ f"'{exc_class_or_code}' is not a recognized HTTP"
+ " error code. Use a subclass of HTTPException with"
+ " that code instead."
+ ) from None
else:
exc_class = exc_class_or_code
- assert issubclass(
- exc_class, Exception
- ), "Custom exceptions must be subclasses of Exception."
+ if isinstance(exc_class, Exception):
+ raise TypeError(
+ f"{exc_class!r} is an instance, not a class. Handlers"
+ " can only be registered for Exception classes or HTTP"
+ " error codes."
+ )
+
+ if not issubclass(exc_class, Exception):
+ raise ValueError(
+ f"'{exc_class.__name__}' is not a subclass of Exception."
+ " Handlers can only be registered for Exception classes"
+ " or HTTP error codes."
+ )
if issubclass(exc_class, HTTPException):
return exc_class, exc_class.code
diff --git a/contrib/python/Flask/py3/flask/sessions.py b/contrib/python/Flask/py3/flask/sessions.py
index 4e19270e0d..201593f2b4 100644
--- a/contrib/python/Flask/py3/flask/sessions.py
+++ b/contrib/python/Flask/py3/flask/sessions.py
@@ -3,6 +3,7 @@ import typing as t
import warnings
from collections.abc import MutableMapping
from datetime import datetime
+from datetime import timezone
from itsdangerous import BadSignature
from itsdangerous import URLSafeTimedSerializer
@@ -11,7 +12,7 @@ from werkzeug.datastructures import CallbackDict
from .helpers import is_ip
from .json.tag import TaggedJSONSerializer
-if t.TYPE_CHECKING:
+if t.TYPE_CHECKING: # pragma: no cover
import typing_extensions as te
from .app import Flask
from .wrappers import Request, Response
@@ -176,11 +177,8 @@ class SessionInterface:
return isinstance(obj, self.null_session_class)
def get_cookie_name(self, app: "Flask") -> str:
- """Returns the name of the session cookie.
-
- Uses ``app.session_cookie_name`` which is set to ``SESSION_COOKIE_NAME``
- """
- return app.session_cookie_name
+ """The name of the session cookie. Uses``app.config["SESSION_COOKIE_NAME"]``."""
+ return app.config["SESSION_COOKIE_NAME"]
def get_cookie_domain(self, app: "Flask") -> t.Optional[str]:
"""Returns the domain that should be set for the session cookie.
@@ -277,7 +275,7 @@ class SessionInterface:
lifetime configured on the application.
"""
if session.permanent:
- return datetime.utcnow() + app.permanent_session_lifetime
+ return datetime.now(timezone.utc) + app.permanent_session_lifetime
return None
def should_set_cookie(self, app: "Flask", session: SessionMixin) -> bool:
@@ -385,6 +383,10 @@ class SecureCookieSessionInterface(SessionInterface):
samesite = self.get_cookie_samesite(app)
httponly = self.get_cookie_httponly(app)
+ # Add a "Vary: Cookie" header if the session was accessed at all.
+ if session.accessed:
+ response.vary.add("Cookie")
+
# If the session is modified to be empty, remove the cookie.
# If the session is empty, return without setting the cookie.
if not session:
@@ -397,13 +399,10 @@ class SecureCookieSessionInterface(SessionInterface):
samesite=samesite,
httponly=httponly,
)
+ response.vary.add("Cookie")
return
- # Add a "Vary: Cookie" header if the session was accessed at all.
- if session.accessed:
- response.vary.add("Cookie")
-
if not self.should_set_cookie(app, session):
return
@@ -419,3 +418,4 @@ class SecureCookieSessionInterface(SessionInterface):
secure=secure,
samesite=samesite,
)
+ response.vary.add("Cookie")
diff --git a/contrib/python/Flask/py3/flask/templating.py b/contrib/python/Flask/py3/flask/templating.py
index 507615c5cc..25cc3f95e6 100644
--- a/contrib/python/Flask/py3/flask/templating.py
+++ b/contrib/python/Flask/py3/flask/templating.py
@@ -5,12 +5,15 @@ from jinja2 import Environment as BaseEnvironment
from jinja2 import Template
from jinja2 import TemplateNotFound
-from .globals import _app_ctx_stack
-from .globals import _request_ctx_stack
+from .globals import _cv_app
+from .globals import _cv_request
+from .globals import current_app
+from .globals import request
+from .helpers import stream_with_context
from .signals import before_render_template
from .signals import template_rendered
-if t.TYPE_CHECKING:
+if t.TYPE_CHECKING: # pragma: no cover
from .app import Flask
from .scaffold import Scaffold
@@ -19,9 +22,9 @@ def _default_template_ctx_processor() -> t.Dict[str, t.Any]:
"""Default template context processor. Injects `request`,
`session` and `g`.
"""
- reqctx = _request_ctx_stack.top
- appctx = _app_ctx_stack.top
- rv = {}
+ appctx = _cv_app.get(None)
+ reqctx = _cv_request.get(None)
+ rv: t.Dict[str, t.Any] = {}
if appctx is not None:
rv["g"] = appctx.g
if reqctx is not None:
@@ -121,9 +124,8 @@ class DispatchingJinjaLoader(BaseLoader):
return list(result)
-def _render(template: Template, context: dict, app: "Flask") -> str:
- """Renders the template and fires the signal"""
-
+def _render(app: "Flask", template: Template, context: t.Dict[str, t.Any]) -> str:
+ app.update_template_context(context)
before_render_template.send(app, template=template, context=context)
rv = template.render(context)
template_rendered.send(app, template=template, context=context)
@@ -134,45 +136,77 @@ def render_template(
template_name_or_list: t.Union[str, Template, t.List[t.Union[str, Template]]],
**context: t.Any
) -> str:
- """Renders a template from the template folder with the given
+ """Render a template by name with the given context.
+
+ :param template_name_or_list: The name of the template to render. If
+ a list is given, the first name to exist will be rendered.
+ :param context: The variables to make available in the template.
+ """
+ app = current_app._get_current_object() # type: ignore[attr-defined]
+ template = app.jinja_env.get_or_select_template(template_name_or_list)
+ return _render(app, template, context)
+
+
+def render_template_string(source: str, **context: t.Any) -> str:
+ """Render a template from the given source string with the given
context.
- :param template_name_or_list: the name of the template to be
- rendered, or an iterable with template names
- the first one existing will be rendered
- :param context: the variables that should be available in the
- context of the template.
+ :param source: The source code of the template to render.
+ :param context: The variables to make available in the template.
"""
- ctx = _app_ctx_stack.top
+ app = current_app._get_current_object() # type: ignore[attr-defined]
+ template = app.jinja_env.from_string(source)
+ return _render(app, template, context)
- if ctx is None:
- raise RuntimeError(
- "This function can only be used when an application context is active."
- )
- ctx.app.update_template_context(context)
- return _render(
- ctx.app.jinja_env.get_or_select_template(template_name_or_list),
- context,
- ctx.app,
- )
+def _stream(
+ app: "Flask", template: Template, context: t.Dict[str, t.Any]
+) -> t.Iterator[str]:
+ app.update_template_context(context)
+ before_render_template.send(app, template=template, context=context)
+ def generate() -> t.Iterator[str]:
+ yield from template.generate(context)
+ template_rendered.send(app, template=template, context=context)
-def render_template_string(source: str, **context: t.Any) -> str:
- """Renders a template from the given template source string
- with the given context. Template variables will be autoescaped.
+ rv = generate()
- :param source: the source code of the template to be
- rendered
- :param context: the variables that should be available in the
- context of the template.
+ # If a request context is active, keep it while generating.
+ if request:
+ rv = stream_with_context(rv)
+
+ return rv
+
+
+def stream_template(
+ template_name_or_list: t.Union[str, Template, t.List[t.Union[str, Template]]],
+ **context: t.Any
+) -> t.Iterator[str]:
+ """Render a template by name with the given context as a stream.
+ This returns an iterator of strings, which can be used as a
+ streaming response from a view.
+
+ :param template_name_or_list: The name of the template to render. If
+ a list is given, the first name to exist will be rendered.
+ :param context: The variables to make available in the template.
+
+ .. versionadded:: 2.2
"""
- ctx = _app_ctx_stack.top
+ app = current_app._get_current_object() # type: ignore[attr-defined]
+ template = app.jinja_env.get_or_select_template(template_name_or_list)
+ return _stream(app, template, context)
+
- if ctx is None:
- raise RuntimeError(
- "This function can only be used when an application context is active."
- )
+def stream_template_string(source: str, **context: t.Any) -> t.Iterator[str]:
+ """Render a template from the given source string with the given
+ context as a stream. This returns an iterator of strings, which can
+ be used as a streaming response from a view.
- ctx.app.update_template_context(context)
- return _render(ctx.app.jinja_env.from_string(source), context, ctx.app)
+ :param source: The source code of the template to render.
+ :param context: The variables to make available in the template.
+
+ .. versionadded:: 2.2
+ """
+ app = current_app._get_current_object() # type: ignore[attr-defined]
+ template = app.jinja_env.from_string(source)
+ return _stream(app, template, context)
diff --git a/contrib/python/Flask/py3/flask/testing.py b/contrib/python/Flask/py3/flask/testing.py
index e07e50e7a2..b78ec6d49d 100644
--- a/contrib/python/Flask/py3/flask/testing.py
+++ b/contrib/python/Flask/py3/flask/testing.py
@@ -1,20 +1,19 @@
import typing as t
from contextlib import contextmanager
+from contextlib import ExitStack
from copy import copy
from types import TracebackType
+from urllib.parse import urlsplit
import werkzeug.test
from click.testing import CliRunner
from werkzeug.test import Client
-from werkzeug.urls import url_parse
from werkzeug.wrappers import Request as BaseRequest
from .cli import ScriptInfo
-from .globals import _request_ctx_stack
-from .json import dumps as json_dumps
from .sessions import SessionMixin
-if t.TYPE_CHECKING:
+if t.TYPE_CHECKING: # pragma: no cover
from werkzeug.test import TestResponse
from .app import Flask
@@ -68,7 +67,7 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder):
if url_scheme is None:
url_scheme = app.config["PREFERRED_URL_SCHEME"]
- url = url_parse(path)
+ url = urlsplit(path)
base_url = (
f"{url.scheme or url_scheme}://{url.netloc or http_host}"
f"/{app_root.lstrip('/')}"
@@ -88,16 +87,14 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder):
The serialization will be configured according to the config associated
with this EnvironBuilder's ``app``.
"""
- kwargs.setdefault("app", self.app)
- return json_dumps(obj, **kwargs)
+ return self.app.json.dumps(obj, **kwargs)
class FlaskClient(Client):
- """Works like a regular Werkzeug test client but has some knowledge about
- how Flask works to defer the cleanup of the request context stack to the
- end of a ``with`` body when used in a ``with`` statement. For general
- information about how to use this class refer to
- :class:`werkzeug.test.Client`.
+ """Works like a regular Werkzeug test client but has knowledge about
+ Flask's contexts to defer the cleanup of the request context until
+ the end of a ``with`` block. For general information about how to
+ use this class refer to :class:`werkzeug.test.Client`.
.. versionchanged:: 0.12
`app.test_client()` includes preset default environment, which can be
@@ -108,10 +105,12 @@ class FlaskClient(Client):
"""
application: "Flask"
- preserve_context = False
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
super().__init__(*args, **kwargs)
+ self.preserve_context = False
+ self._new_contexts: t.List[t.ContextManager[t.Any]] = []
+ self._context_stack = ExitStack()
self.environ_base = {
"REMOTE_ADDR": "127.0.0.1",
"HTTP_USER_AGENT": f"werkzeug/{werkzeug.__version__}",
@@ -137,47 +136,64 @@ class FlaskClient(Client):
:meth:`~flask.Flask.test_request_context` which are directly
passed through.
"""
- if self.cookie_jar is None:
- raise RuntimeError(
- "Session transactions only make sense with cookies enabled."
+ # new cookie interface for Werkzeug >= 2.3
+ cookie_storage = self._cookies if hasattr(self, "_cookies") else self.cookie_jar
+
+ if cookie_storage is None:
+ raise TypeError(
+ "Cookies are disabled. Create a client with 'use_cookies=True'."
)
+
app = self.application
- environ_overrides = kwargs.setdefault("environ_overrides", {})
- self.cookie_jar.inject_wsgi(environ_overrides)
- outer_reqctx = _request_ctx_stack.top
- with app.test_request_context(*args, **kwargs) as c:
- session_interface = app.session_interface
- sess = session_interface.open_session(app, c.request)
- if sess is None:
- raise RuntimeError(
- "Session backend did not open a session. Check the configuration"
- )
+ ctx = app.test_request_context(*args, **kwargs)
- # Since we have to open a new request context for the session
- # handling we want to make sure that we hide out own context
- # from the caller. By pushing the original request context
- # (or None) on top of this and popping it we get exactly that
- # behavior. It's important to not use the push and pop
- # methods of the actual request context object since that would
- # mean that cleanup handlers are called
- _request_ctx_stack.push(outer_reqctx)
- try:
- yield sess
- finally:
- _request_ctx_stack.pop()
+ if hasattr(self, "_add_cookies_to_wsgi"):
+ self._add_cookies_to_wsgi(ctx.request.environ)
+ else:
+ self.cookie_jar.inject_wsgi(ctx.request.environ) # type: ignore[union-attr]
+
+ with ctx:
+ sess = app.session_interface.open_session(app, ctx.request)
- resp = app.response_class()
- if not session_interface.is_null_session(sess):
- session_interface.save_session(app, sess, resp)
- headers = resp.get_wsgi_headers(c.request.environ)
- self.cookie_jar.extract_wsgi(c.request.environ, headers)
+ if sess is None:
+ raise RuntimeError("Session backend did not open a session.")
+
+ yield sess
+ resp = app.response_class()
+
+ if app.session_interface.is_null_session(sess):
+ return
+
+ with ctx:
+ app.session_interface.save_session(app, sess, resp)
+
+ if hasattr(self, "_update_cookies_from_response"):
+ try:
+ # Werkzeug>=2.3.3
+ self._update_cookies_from_response(
+ ctx.request.host.partition(":")[0],
+ ctx.request.path,
+ resp.headers.getlist("Set-Cookie"),
+ )
+ except TypeError:
+ # Werkzeug>=2.3.0,<2.3.3
+ self._update_cookies_from_response( # type: ignore[call-arg]
+ ctx.request.host.partition(":")[0],
+ resp.headers.getlist("Set-Cookie"), # type: ignore[arg-type]
+ )
+ else:
+ # Werkzeug<2.3.0
+ self.cookie_jar.extract_wsgi( # type: ignore[union-attr]
+ ctx.request.environ, resp.headers
+ )
def _copy_environ(self, other):
- return {
- **self.environ_base,
- **other,
- "flask._preserve_context": self.preserve_context,
- }
+ out = {**self.environ_base, **other}
+
+ if self.preserve_context:
+ out["werkzeug.debug.preserve_context"] = self._new_contexts.append
+
+ return out
def _request_from_builder_args(self, args, kwargs):
kwargs["environ_base"] = self._copy_environ(kwargs.get("environ_base", {}))
@@ -214,11 +230,24 @@ class FlaskClient(Client):
# request is None
request = self._request_from_builder_args(args, kwargs)
- return super().open(
+ # Pop any previously preserved contexts. This prevents contexts
+ # from being preserved across redirects or multiple requests
+ # within a single block.
+ self._context_stack.close()
+
+ response = super().open(
request,
buffered=buffered,
follow_redirects=follow_redirects,
)
+ response.json_module = self.application.json # type: ignore[assignment]
+
+ # Re-push contexts that were preserved during the request.
+ while self._new_contexts:
+ cm = self._new_contexts.pop()
+ self._context_stack.enter_context(cm)
+
+ return response
def __enter__(self) -> "FlaskClient":
if self.preserve_context:
@@ -233,18 +262,7 @@ class FlaskClient(Client):
tb: t.Optional[TracebackType],
) -> None:
self.preserve_context = False
-
- # Normally the request context is preserved until the next
- # request in the same thread comes. When the client exits we
- # want to clean up earlier. Pop request contexts until the stack
- # is empty or a non-preserved one is found.
- while True:
- top = _request_ctx_stack.top
-
- if top is not None and top.preserved:
- top.pop()
- else:
- break
+ self._context_stack.close()
class FlaskCliRunner(CliRunner):
diff --git a/contrib/python/Flask/py3/flask/typing.py b/contrib/python/Flask/py3/flask/typing.py
index e6d67f2077..8857598ef8 100644
--- a/contrib/python/Flask/py3/flask/typing.py
+++ b/contrib/python/Flask/py3/flask/typing.py
@@ -1,12 +1,21 @@
import typing as t
-if t.TYPE_CHECKING:
+if t.TYPE_CHECKING: # pragma: no cover
from _typeshed.wsgi import WSGIApplication # noqa: F401
from werkzeug.datastructures import Headers # noqa: F401
from werkzeug.wrappers import Response # noqa: F401
# The possible types that are directly convertible or are a Response object.
-ResponseValue = t.Union["Response", str, bytes, t.Dict[str, t.Any]]
+ResponseValue = t.Union[
+ "Response",
+ str,
+ bytes,
+ t.List[t.Any],
+ # Only dict is actually accepted, but Mapping allows for TypedDict.
+ t.Mapping[str, t.Any],
+ t.Iterator[str],
+ t.Iterator[bytes],
+]
# the possible types for an individual HTTP header
# This should be a Union, but mypy doesn't pass unless it's a TypeVar.
@@ -34,10 +43,22 @@ ResponseReturnValue = t.Union[
ResponseClass = t.TypeVar("ResponseClass", bound="Response")
AppOrBlueprintKey = t.Optional[str] # The App key is None, whereas blueprints are named
-AfterRequestCallable = t.Callable[[ResponseClass], ResponseClass]
-BeforeFirstRequestCallable = t.Callable[[], None]
-BeforeRequestCallable = t.Callable[[], t.Optional[ResponseReturnValue]]
-TeardownCallable = t.Callable[[t.Optional[BaseException]], None]
+AfterRequestCallable = t.Union[
+ t.Callable[[ResponseClass], ResponseClass],
+ t.Callable[[ResponseClass], t.Awaitable[ResponseClass]],
+]
+BeforeFirstRequestCallable = t.Union[
+ t.Callable[[], None], t.Callable[[], t.Awaitable[None]]
+]
+BeforeRequestCallable = t.Union[
+ t.Callable[[], t.Optional[ResponseReturnValue]],
+ t.Callable[[], t.Awaitable[t.Optional[ResponseReturnValue]]],
+]
+ShellContextProcessorCallable = t.Callable[[], t.Dict[str, t.Any]]
+TeardownCallable = t.Union[
+ t.Callable[[t.Optional[BaseException]], None],
+ t.Callable[[t.Optional[BaseException]], t.Awaitable[None]],
+]
TemplateContextProcessorCallable = t.Callable[[], t.Dict[str, t.Any]]
TemplateFilterCallable = t.Callable[..., t.Any]
TemplateGlobalCallable = t.Callable[..., t.Any]
@@ -52,7 +73,8 @@ URLValuePreprocessorCallable = t.Callable[[t.Optional[str], t.Optional[dict]], N
# https://github.com/pallets/flask/issues/4295
# https://github.com/pallets/flask/issues/4297
ErrorHandlerCallable = t.Callable[[t.Any], ResponseReturnValue]
-ErrorHandlerDecorator = t.TypeVar("ErrorHandlerDecorator", bound=ErrorHandlerCallable)
-ViewCallable = t.Callable[..., ResponseReturnValue]
-RouteDecorator = t.TypeVar("RouteDecorator", bound=ViewCallable)
+RouteCallable = t.Union[
+ t.Callable[..., ResponseReturnValue],
+ t.Callable[..., t.Awaitable[ResponseReturnValue]],
+]
diff --git a/contrib/python/Flask/py3/flask/views.py b/contrib/python/Flask/py3/flask/views.py
index 1dd560c62b..f86172b43c 100644
--- a/contrib/python/Flask/py3/flask/views.py
+++ b/contrib/python/Flask/py3/flask/views.py
@@ -11,77 +11,106 @@ http_method_funcs = frozenset(
class View:
- """Alternative way to use view functions. A subclass has to implement
- :meth:`dispatch_request` which is called with the view arguments from
- the URL routing system. If :attr:`methods` is provided the methods
- do not have to be passed to the :meth:`~flask.Flask.add_url_rule`
- method explicitly::
+ """Subclass this class and override :meth:`dispatch_request` to
+ create a generic class-based view. Call :meth:`as_view` to create a
+ view function that creates an instance of the class with the given
+ arguments and calls its ``dispatch_request`` method with any URL
+ variables.
- class MyView(View):
- methods = ['GET']
+ See :doc:`views` for a detailed guide.
- def dispatch_request(self, name):
- return f"Hello {name}!"
+ .. code-block:: python
+
+ class Hello(View):
+ init_every_request = False
- app.add_url_rule('/hello/<name>', view_func=MyView.as_view('myview'))
+ def dispatch_request(self, name):
+ return f"Hello, {name}!"
- When you want to decorate a pluggable view you will have to either do that
- when the view function is created (by wrapping the return value of
- :meth:`as_view`) or you can use the :attr:`decorators` attribute::
+ app.add_url_rule(
+ "/hello/<name>", view_func=Hello.as_view("hello")
+ )
- class SecretView(View):
- methods = ['GET']
- decorators = [superuser_required]
+ Set :attr:`methods` on the class to change what methods the view
+ accepts.
- def dispatch_request(self):
- ...
+ Set :attr:`decorators` on the class to apply a list of decorators to
+ the generated view function. Decorators applied to the class itself
+ will not be applied to the generated view function!
- The decorators stored in the decorators list are applied one after another
- when the view function is created. Note that you can *not* use the class
- based decorators since those would decorate the view class and not the
- generated view function!
+ Set :attr:`init_every_request` to ``False`` for efficiency, unless
+ you need to store request-global data on ``self``.
"""
- #: A list of methods this view can handle.
- methods: t.Optional[t.List[str]] = None
+ #: The methods this view is registered for. Uses the same default
+ #: (``["GET", "HEAD", "OPTIONS"]``) as ``route`` and
+ #: ``add_url_rule`` by default.
+ methods: t.ClassVar[t.Optional[t.Collection[str]]] = None
- #: Setting this disables or force-enables the automatic options handling.
- provide_automatic_options: t.Optional[bool] = None
+ #: Control whether the ``OPTIONS`` method is handled automatically.
+ #: Uses the same default (``True``) as ``route`` and
+ #: ``add_url_rule`` by default.
+ provide_automatic_options: t.ClassVar[t.Optional[bool]] = None
- #: The canonical way to decorate class-based views is to decorate the
- #: return value of as_view(). However since this moves parts of the
- #: logic from the class declaration to the place where it's hooked
- #: into the routing system.
- #:
- #: You can place one or more decorators in this list and whenever the
- #: view function is created the result is automatically decorated.
+ #: A list of decorators to apply, in order, to the generated view
+ #: function. Remember that ``@decorator`` syntax is applied bottom
+ #: to top, so the first decorator in the list would be the bottom
+ #: decorator.
#:
#: .. versionadded:: 0.8
- decorators: t.List[t.Callable] = []
+ decorators: t.ClassVar[t.List[t.Callable]] = []
+
+ #: Create a new instance of this view class for every request by
+ #: default. If a view subclass sets this to ``False``, the same
+ #: instance is used for every request.
+ #:
+ #: A single instance is more efficient, especially if complex setup
+ #: is done during init. However, storing data on ``self`` is no
+ #: longer safe across requests, and :data:`~flask.g` should be used
+ #: instead.
+ #:
+ #: .. versionadded:: 2.2
+ init_every_request: t.ClassVar[bool] = True
def dispatch_request(self) -> ft.ResponseReturnValue:
- """Subclasses have to override this method to implement the
- actual view function code. This method is called with all
- the arguments from the URL rule.
+ """The actual view function behavior. Subclasses must override
+ this and return a valid response. Any variables from the URL
+ rule are passed as keyword arguments.
"""
raise NotImplementedError()
@classmethod
def as_view(
cls, name: str, *class_args: t.Any, **class_kwargs: t.Any
- ) -> t.Callable:
- """Converts the class into an actual view function that can be used
- with the routing system. Internally this generates a function on the
- fly which will instantiate the :class:`View` on each request and call
- the :meth:`dispatch_request` method on it.
-
- The arguments passed to :meth:`as_view` are forwarded to the
- constructor of the class.
+ ) -> ft.RouteCallable:
+ """Convert the class into a view function that can be registered
+ for a route.
+
+ By default, the generated view will create a new instance of the
+ view class for every request and call its
+ :meth:`dispatch_request` method. If the view class sets
+ :attr:`init_every_request` to ``False``, the same instance will
+ be used for every request.
+
+ Except for ``name``, all other arguments passed to this method
+ are forwarded to the view class ``__init__`` method.
+
+ .. versionchanged:: 2.2
+ Added the ``init_every_request`` class attribute.
"""
+ if cls.init_every_request:
+
+ def view(**kwargs: t.Any) -> ft.ResponseReturnValue:
+ self = view.view_class( # type: ignore[attr-defined]
+ *class_args, **class_kwargs
+ )
+ return current_app.ensure_sync(self.dispatch_request)(**kwargs)
+
+ else:
+ self = cls(*class_args, **class_kwargs)
- def view(*args: t.Any, **kwargs: t.Any) -> ft.ResponseReturnValue:
- self = view.view_class(*class_args, **class_kwargs) # type: ignore
- return current_app.ensure_sync(self.dispatch_request)(*args, **kwargs)
+ def view(**kwargs: t.Any) -> ft.ResponseReturnValue:
+ return current_app.ensure_sync(self.dispatch_request)(**kwargs)
if cls.decorators:
view.__name__ = name
@@ -103,50 +132,51 @@ class View:
return view
-class MethodViewType(type):
- """Metaclass for :class:`MethodView` that determines what methods the view
- defines.
+class MethodView(View):
+ """Dispatches request methods to the corresponding instance methods.
+ For example, if you implement a ``get`` method, it will be used to
+ handle ``GET`` requests.
+
+ This can be useful for defining a REST API.
+
+ :attr:`methods` is automatically set based on the methods defined on
+ the class.
+
+ See :doc:`views` for a detailed guide.
+
+ .. code-block:: python
+
+ class CounterAPI(MethodView):
+ def get(self):
+ return str(session.get("counter", 0))
+
+ def post(self):
+ session["counter"] = session.get("counter", 0) + 1
+ return redirect(url_for("counter"))
+
+ app.add_url_rule(
+ "/counter", view_func=CounterAPI.as_view("counter")
+ )
"""
- def __init__(cls, name, bases, d):
- super().__init__(name, bases, d)
+ def __init_subclass__(cls, **kwargs: t.Any) -> None:
+ super().__init_subclass__(**kwargs)
- if "methods" not in d:
+ if "methods" not in cls.__dict__:
methods = set()
- for base in bases:
+ for base in cls.__bases__:
if getattr(base, "methods", None):
- methods.update(base.methods)
+ methods.update(base.methods) # type: ignore[attr-defined]
for key in http_method_funcs:
if hasattr(cls, key):
methods.add(key.upper())
- # If we have no method at all in there we don't want to add a
- # method list. This is for instance the case for the base class
- # or another subclass of a base method view that does not introduce
- # new methods.
if methods:
cls.methods = methods
-
-class MethodView(View, metaclass=MethodViewType):
- """A class-based view that dispatches request methods to the corresponding
- class methods. For example, if you implement a ``get`` method, it will be
- used to handle ``GET`` requests. ::
-
- class CounterAPI(MethodView):
- def get(self):
- return session.get('counter', 0)
-
- def post(self):
- session['counter'] = session.get('counter', 0) + 1
- return 'OK'
-
- app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
- """
-
- def dispatch_request(self, *args: t.Any, **kwargs: t.Any) -> ft.ResponseReturnValue:
+ def dispatch_request(self, **kwargs: t.Any) -> ft.ResponseReturnValue:
meth = getattr(self, request.method.lower(), None)
# If the request method is HEAD and we don't have a handler for it
@@ -155,4 +185,4 @@ class MethodView(View, metaclass=MethodViewType):
meth = getattr(self, "get", None)
assert meth is not None, f"Unimplemented method {request.method!r}"
- return current_app.ensure_sync(meth)(*args, **kwargs)
+ return current_app.ensure_sync(meth)(**kwargs)
diff --git a/contrib/python/Flask/py3/flask/wrappers.py b/contrib/python/Flask/py3/flask/wrappers.py
index 7153876b8d..e36a72cb47 100644
--- a/contrib/python/Flask/py3/flask/wrappers.py
+++ b/contrib/python/Flask/py3/flask/wrappers.py
@@ -8,7 +8,7 @@ from . import json
from .globals import current_app
from .helpers import _split_blueprint_path
-if t.TYPE_CHECKING:
+if t.TYPE_CHECKING: # pragma: no cover
from werkzeug.routing import Rule
@@ -25,7 +25,7 @@ class Request(RequestBase):
specific ones.
"""
- json_module = json
+ json_module: t.Any = json
#: The internal URL rule that matched the request. This can be
#: useful to inspect which methods are allowed for the URL from
diff --git a/contrib/python/Flask/py3/ya.make b/contrib/python/Flask/py3/ya.make
index fb57b6362c..69efae3e7b 100644
--- a/contrib/python/Flask/py3/ya.make
+++ b/contrib/python/Flask/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(2.1.3)
+VERSION(2.2.5)
LICENSE(BSD-3-Clause)
@@ -28,6 +28,7 @@ PY_SRCS(
flask/globals.py
flask/helpers.py
flask/json/__init__.py
+ flask/json/provider.py
flask/json/tag.py
flask/logging.py
flask/scaffold.py