diff options
author | robot-piglet <robot-piglet@yandex-team.com> | 2025-01-16 19:09:30 +0300 |
---|---|---|
committer | robot-piglet <robot-piglet@yandex-team.com> | 2025-01-16 19:38:51 +0300 |
commit | 7a23ad1fa2a5561a3575177d7240d8a1aa499718 (patch) | |
tree | b4932bad31f595149e7a42e88cf729919995d735 /contrib | |
parent | fbb15f5ab8a61fc7c50500e2757af0b47174d825 (diff) | |
download | ydb-7a23ad1fa2a5561a3575177d7240d8a1aa499718.tar.gz |
Intermediate changes
commit_hash:ae9e37c897fc6d514389f7089184df33bf781005
Diffstat (limited to 'contrib')
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 |