diff options
author | robot-piglet <robot-piglet@yandex-team.com> | 2024-05-03 00:01:03 +0300 |
---|---|---|
committer | robot-piglet <robot-piglet@yandex-team.com> | 2024-05-03 00:06:37 +0300 |
commit | 38934cfb0b963a0157cf16c13499977ac87f35b9 (patch) | |
tree | 124d141726b8a860761a75fe2b597eecce078ee7 | |
parent | 616419d508d3ee4a3b07a82e5cae5ab4544bd1e2 (diff) | |
download | ydb-38934cfb0b963a0157cf16c13499977ac87f35b9.tar.gz |
Intermediate changes
-rw-r--r-- | contrib/python/Flask/py3/.dist-info/METADATA | 10 | ||||
-rw-r--r-- | contrib/python/Flask/py3/flask/__init__.py | 3 | ||||
-rw-r--r-- | contrib/python/Flask/py3/flask/app.py | 104 | ||||
-rw-r--r-- | contrib/python/Flask/py3/flask/blueprints.py | 84 | ||||
-rw-r--r-- | contrib/python/Flask/py3/flask/cli.py | 149 | ||||
-rw-r--r-- | contrib/python/Flask/py3/flask/config.py | 96 | ||||
-rw-r--r-- | contrib/python/Flask/py3/flask/ctx.py | 40 | ||||
-rw-r--r-- | contrib/python/Flask/py3/flask/debughelpers.py | 46 | ||||
-rw-r--r-- | contrib/python/Flask/py3/flask/helpers.py | 59 | ||||
-rw-r--r-- | contrib/python/Flask/py3/flask/json/__init__.py | 75 | ||||
-rw-r--r-- | contrib/python/Flask/py3/flask/scaffold.py | 160 | ||||
-rw-r--r-- | contrib/python/Flask/py3/flask/sessions.py | 9 | ||||
-rw-r--r-- | contrib/python/Flask/py3/flask/templating.py | 15 | ||||
-rw-r--r-- | contrib/python/Flask/py3/flask/testing.py | 95 | ||||
-rw-r--r-- | contrib/python/Flask/py3/flask/typing.py | 41 | ||||
-rw-r--r-- | contrib/python/Flask/py3/flask/views.py | 8 | ||||
-rw-r--r-- | contrib/python/Flask/py3/flask/wrappers.py | 16 | ||||
-rw-r--r-- | contrib/python/Flask/py3/ya.make | 2 |
18 files changed, 493 insertions, 519 deletions
diff --git a/contrib/python/Flask/py3/.dist-info/METADATA b/contrib/python/Flask/py3/.dist-info/METADATA index d617f5fba8..343d75d101 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.0.3 +Version: 2.1.3 Summary: A simple framework for building complex web applications. Home-page: https://palletsprojects.com/p/flask Author: Armin Ronacher @@ -15,7 +15,6 @@ Project-URL: Source Code, https://github.com/pallets/flask/ Project-URL: Issue Tracker, https://github.com/pallets/flask/issues/ Project-URL: Twitter, https://twitter.com/PalletsTeam Project-URL: Chat, https://discord.gg/pallets -Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Framework :: Flask @@ -27,13 +26,14 @@ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Internet :: WWW/HTTP :: WSGI Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application Classifier: Topic :: Software Development :: Libraries :: Application Frameworks -Requires-Python: >=3.6 +Requires-Python: >=3.7 Description-Content-Type: text/x-rst License-File: LICENSE.rst Requires-Dist: Werkzeug (>=2.0) Requires-Dist: Jinja2 (>=3.0) Requires-Dist: itsdangerous (>=2.0) -Requires-Dist: click (>=7.1.2) +Requires-Dist: click (>=8.0) +Requires-Dist: importlib-metadata (>=3.6.0) ; python_version < "3.10" Provides-Extra: async Requires-Dist: asgiref (>=3.2) ; extra == 'async' Provides-Extra: dotenv @@ -121,5 +121,3 @@ Links - Website: https://palletsprojects.com/p/flask/ - Twitter: https://twitter.com/PalletsTeam - Chat: https://discord.gg/pallets - - diff --git a/contrib/python/Flask/py3/flask/__init__.py b/contrib/python/Flask/py3/flask/__init__.py index feb5334c31..a970b8a1a8 100644 --- a/contrib/python/Flask/py3/flask/__init__.py +++ b/contrib/python/Flask/py3/flask/__init__.py @@ -23,7 +23,6 @@ from .helpers import flash as flash from .helpers import get_flashed_messages as get_flashed_messages from .helpers import get_template_attribute as get_template_attribute from .helpers import make_response as make_response -from .helpers import safe_join as safe_join from .helpers import send_file as send_file from .helpers import send_from_directory as send_from_directory from .helpers import stream_with_context as stream_with_context @@ -43,4 +42,4 @@ 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 -__version__ = "2.0.3" +__version__ = "2.1.3" diff --git a/contrib/python/Flask/py3/flask/app.py b/contrib/python/Flask/py3/flask/app.py index 23b99e2ca0..6b54918887 100644 --- a/contrib/python/Flask/py3/flask/app.py +++ b/contrib/python/Flask/py3/flask/app.py @@ -16,7 +16,6 @@ from werkzeug.exceptions import BadRequest from werkzeug.exceptions import BadRequestKeyError from werkzeug.exceptions import HTTPException from werkzeug.exceptions import InternalServerError -from werkzeug.local import ContextVar from werkzeug.routing import BuildError from werkzeug.routing import Map from werkzeug.routing import MapAdapter @@ -27,6 +26,7 @@ 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 @@ -51,6 +51,7 @@ from .scaffold import find_package from .scaffold import Scaffold from .scaffold import setupmethod from .sessions import SecureCookieSessionInterface +from .sessions import SessionInterface from .signals import appcontext_tearing_down from .signals import got_request_exception from .signals import request_finished @@ -58,12 +59,6 @@ from .signals import request_started from .signals import request_tearing_down from .templating import DispatchingJinjaLoader from .templating import Environment -from .typing import BeforeFirstRequestCallable -from .typing import ResponseReturnValue -from .typing import TeardownCallable -from .typing import TemplateFilterCallable -from .typing import TemplateGlobalCallable -from .typing import TemplateTestCallable from .wrappers import Request from .wrappers import Response @@ -72,7 +67,6 @@ if t.TYPE_CHECKING: from .blueprints import Blueprint from .testing import FlaskClient from .testing import FlaskCliRunner - from .typing import ErrorHandlerCallable if sys.version_info >= (3, 8): iscoroutinefunction = inspect.iscoroutinefunction @@ -379,7 +373,7 @@ class Flask(Scaffold): #: :class:`~flask.sessions.SecureCookieSessionInterface` is used here. #: #: .. versionadded:: 0.8 - session_interface = SecureCookieSessionInterface() + session_interface: SessionInterface = SecureCookieSessionInterface() def __init__( self, @@ -436,7 +430,7 @@ class Flask(Scaffold): #: :meth:`before_first_request` decorator. #: #: .. versionadded:: 0.8 - self.before_first_request_funcs: t.List[BeforeFirstRequestCallable] = [] + self.before_first_request_funcs: t.List[ft.BeforeFirstRequestCallable] = [] #: A list of functions that are called when the application context #: is destroyed. Since the application context is also torn down @@ -444,7 +438,7 @@ class Flask(Scaffold): #: from databases. #: #: .. versionadded:: 0.9 - self.teardown_appcontext_funcs: t.List[TeardownCallable] = [] + self.teardown_appcontext_funcs: t.List[ft.TeardownCallable] = [] #: A list of shell context processor functions that should be run #: when a shell context is created. @@ -1039,7 +1033,7 @@ class Flask(Scaffold): self, rule: str, endpoint: t.Optional[str] = None, - view_func: t.Optional[t.Callable] = None, + view_func: t.Optional[ft.ViewCallable] = None, provide_automatic_options: t.Optional[bool] = None, **options: t.Any, ) -> None: @@ -1096,7 +1090,7 @@ class Flask(Scaffold): @setupmethod def template_filter( self, name: t.Optional[str] = None - ) -> t.Callable[[TemplateFilterCallable], TemplateFilterCallable]: + ) -> t.Callable[[ft.TemplateFilterCallable], ft.TemplateFilterCallable]: """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:: @@ -1109,7 +1103,7 @@ class Flask(Scaffold): function name will be used. """ - def decorator(f: TemplateFilterCallable) -> TemplateFilterCallable: + def decorator(f: ft.TemplateFilterCallable) -> ft.TemplateFilterCallable: self.add_template_filter(f, name=name) return f @@ -1117,7 +1111,7 @@ class Flask(Scaffold): @setupmethod def add_template_filter( - self, f: TemplateFilterCallable, name: t.Optional[str] = None + self, f: ft.TemplateFilterCallable, name: t.Optional[str] = None ) -> None: """Register a custom template filter. Works exactly like the :meth:`template_filter` decorator. @@ -1130,7 +1124,7 @@ class Flask(Scaffold): @setupmethod def template_test( self, name: t.Optional[str] = None - ) -> t.Callable[[TemplateTestCallable], TemplateTestCallable]: + ) -> t.Callable[[ft.TemplateTestCallable], ft.TemplateTestCallable]: """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:: @@ -1150,7 +1144,7 @@ class Flask(Scaffold): function name will be used. """ - def decorator(f: TemplateTestCallable) -> TemplateTestCallable: + def decorator(f: ft.TemplateTestCallable) -> ft.TemplateTestCallable: self.add_template_test(f, name=name) return f @@ -1158,7 +1152,7 @@ class Flask(Scaffold): @setupmethod def add_template_test( - self, f: TemplateTestCallable, name: t.Optional[str] = None + self, f: ft.TemplateTestCallable, name: t.Optional[str] = None ) -> None: """Register a custom template test. Works exactly like the :meth:`template_test` decorator. @@ -1173,7 +1167,7 @@ class Flask(Scaffold): @setupmethod def template_global( self, name: t.Optional[str] = None - ) -> t.Callable[[TemplateGlobalCallable], TemplateGlobalCallable]: + ) -> t.Callable[[ft.TemplateGlobalCallable], ft.TemplateGlobalCallable]: """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:: @@ -1188,7 +1182,7 @@ class Flask(Scaffold): function name will be used. """ - def decorator(f: TemplateGlobalCallable) -> TemplateGlobalCallable: + def decorator(f: ft.TemplateGlobalCallable) -> ft.TemplateGlobalCallable: self.add_template_global(f, name=name) return f @@ -1196,7 +1190,7 @@ class Flask(Scaffold): @setupmethod def add_template_global( - self, f: TemplateGlobalCallable, name: t.Optional[str] = None + self, f: ft.TemplateGlobalCallable, name: t.Optional[str] = None ) -> None: """Register a custom template global function. Works exactly like the :meth:`template_global` decorator. @@ -1210,8 +1204,8 @@ class Flask(Scaffold): @setupmethod def before_first_request( - self, f: BeforeFirstRequestCallable - ) -> BeforeFirstRequestCallable: + self, f: ft.BeforeFirstRequestCallable + ) -> ft.BeforeFirstRequestCallable: """Registers a function to be run before the first request to this instance of the application. @@ -1224,7 +1218,7 @@ class Flask(Scaffold): return f @setupmethod - def teardown_appcontext(self, f: TeardownCallable) -> TeardownCallable: + 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. @@ -1265,9 +1259,7 @@ class Flask(Scaffold): self.shell_context_processors.append(f) return f - def _find_error_handler( - self, e: Exception - ) -> t.Optional["ErrorHandlerCallable[Exception]"]: + def _find_error_handler(self, e: Exception) -> t.Optional[ft.ErrorHandlerCallable]: """Return a registered error handler for an exception in this order: blueprint handler for a specific code, app handler for a specific code, blueprint handler for an exception class, app handler for an exception @@ -1292,7 +1284,7 @@ class Flask(Scaffold): def handle_http_exception( self, e: HTTPException - ) -> t.Union[HTTPException, ResponseReturnValue]: + ) -> t.Union[HTTPException, ft.ResponseReturnValue]: """Handles an HTTP exception. By default this will invoke the registered error handlers and fall back to returning the exception as response. @@ -1362,7 +1354,7 @@ class Flask(Scaffold): def handle_user_exception( self, e: Exception - ) -> t.Union[HTTPException, ResponseReturnValue]: + ) -> t.Union[HTTPException, ft.ResponseReturnValue]: """This method is called whenever an exception occurs that should be handled. A special case is :class:`~werkzeug .exceptions.HTTPException` which is forwarded to the @@ -1432,7 +1424,7 @@ class Flask(Scaffold): raise e self.log_exception(exc_info) - server_error: t.Union[InternalServerError, ResponseReturnValue] + server_error: t.Union[InternalServerError, ft.ResponseReturnValue] server_error = InternalServerError(original_exception=e) handler = self._find_error_handler(server_error) @@ -1459,17 +1451,26 @@ class Flask(Scaffold): ) def raise_routing_exception(self, request: Request) -> "te.NoReturn": - """Exceptions that are recording during routing are reraised with - this method. During debug we are not reraising redirect requests - for non ``GET``, ``HEAD``, or ``OPTIONS`` requests and we're raising - a different error instead to help debug situations. + """Intercept routing exceptions and possibly do something else. + + In debug mode, intercept a routing redirect and replace it with + an error if the body will be discarded. + + With modern Werkzeug this shouldn't occur, since it now uses a + 308 status which tells the browser to resend the method and + body. + + .. versionchanged:: 2.1 + Don't intercept 307 and 308 redirects. + :meta private: :internal: """ if ( not self.debug or not isinstance(request.routing_exception, RequestRedirect) - or request.method in ("GET", "HEAD", "OPTIONS") + or request.routing_exception.code in {307, 308} + or request.method in {"GET", "HEAD", "OPTIONS"} ): raise request.routing_exception # type: ignore @@ -1477,7 +1478,7 @@ class Flask(Scaffold): raise FormDataRoutingRedirect(request) - def dispatch_request(self) -> ResponseReturnValue: + def dispatch_request(self) -> ft.ResponseReturnValue: """Does the request dispatching. Matches the URL and returns the return value of the view or error handler. This does not have to be a response object. In order to convert the return value to a @@ -1520,7 +1521,7 @@ class Flask(Scaffold): def finalize_request( self, - rv: t.Union[ResponseReturnValue, HTTPException], + rv: t.Union[ft.ResponseReturnValue, HTTPException], from_error_handler: bool = False, ) -> Response: """Given the return value from a view function this finalizes @@ -1621,16 +1622,9 @@ class Flask(Scaffold): "Install Flask with the 'async' extra in order to use async views." ) from None - # Check that Werkzeug isn't using its fallback ContextVar class. - if ContextVar.__module__ == "werkzeug.local": - raise RuntimeError( - "Async cannot be used with this combination of Python " - "and Greenlet versions." - ) - return asgiref_async_to_sync(func) - def make_response(self, rv: ResponseReturnValue) -> Response: + def make_response(self, rv: ft.ResponseReturnValue) -> Response: """Convert the return value from a view function to an instance of :attr:`response_class`. @@ -1681,13 +1675,13 @@ class Flask(Scaffold): # a 3-tuple is unpacked directly if len_rv == 3: - rv, status, headers = rv + rv, status, headers = rv # type: ignore[misc] # decide if a 2-tuple has status or headers elif len_rv == 2: if isinstance(rv[1], (Headers, dict, tuple, list)): rv, headers = rv else: - rv, status = rv + rv, status = rv # type: ignore[assignment,misc] # other sized tuples are not allowed else: raise TypeError( @@ -1710,7 +1704,11 @@ class Flask(Scaffold): # 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 - rv = self.response_class(rv, status=status, headers=headers) + rv = self.response_class( + rv, + status=status, + headers=headers, # type: ignore[arg-type] + ) status = headers = None elif isinstance(rv, dict): rv = jsonify(rv) @@ -1718,7 +1716,9 @@ class Flask(Scaffold): # evaluate a WSGI callable, or coerce a different response # class to the correct type try: - rv = self.response_class.force_type(rv, request.environ) # type: ignore # noqa: B950 + rv = self.response_class.force_type( + rv, request.environ # type: ignore[arg-type] + ) except TypeError as e: raise TypeError( f"{e}\nThe view function did not return a valid" @@ -1738,13 +1738,13 @@ class Flask(Scaffold): # prefer the status if it was provided if status is not None: if isinstance(status, (str, bytes, bytearray)): - rv.status = status # type: ignore + rv.status = status else: rv.status_code = status # extend existing headers with provided headers if headers: - rv.headers.update(headers) + rv.headers.update(headers) # type: ignore[arg-type] return rv @@ -1834,7 +1834,7 @@ class Flask(Scaffold): raise error - def preprocess_request(self) -> t.Optional[ResponseReturnValue]: + def preprocess_request(self) -> t.Optional[ft.ResponseReturnValue]: """Called before the request is dispatched. Calls :attr:`url_value_preprocessors` registered with the app and the current blueprint (if any). Then calls :attr:`before_request_funcs` diff --git a/contrib/python/Flask/py3/flask/blueprints.py b/contrib/python/Flask/py3/flask/blueprints.py index 5c23a735c8..87617989e0 100644 --- a/contrib/python/Flask/py3/flask/blueprints.py +++ b/contrib/python/Flask/py3/flask/blueprints.py @@ -3,23 +3,13 @@ import typing as t from collections import defaultdict from functools import update_wrapper +from . import typing as ft from .scaffold import _endpoint_from_view_func from .scaffold import _sentinel from .scaffold import Scaffold -from .typing import AfterRequestCallable -from .typing import BeforeFirstRequestCallable -from .typing import BeforeRequestCallable -from .typing import TeardownCallable -from .typing import TemplateContextProcessorCallable -from .typing import TemplateFilterCallable -from .typing import TemplateGlobalCallable -from .typing import TemplateTestCallable -from .typing import URLDefaultCallable -from .typing import URLValuePreprocessorCallable if t.TYPE_CHECKING: from .app import Flask - from .typing import ErrorHandlerCallable DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable] @@ -299,24 +289,14 @@ class Blueprint(Scaffold): name = f"{name_prefix}.{self_name}".lstrip(".") if name in app.blueprints: + bp_desc = "this" if app.blueprints[name] is self else "a different" existing_at = f" '{name}'" if self_name != name else "" - if app.blueprints[name] is not self: - raise ValueError( - f"The name '{self_name}' is already registered for" - f" a different blueprint{existing_at}. Use 'name='" - " to provide a unique name." - ) - else: - import warnings - - warnings.warn( - f"The name '{self_name}' is already registered for" - f" this blueprint{existing_at}. Use 'name=' to" - " provide a unique name. This will become an error" - " in Flask 2.1.", - stacklevel=4, - ) + raise ValueError( + f"The name '{self_name}' is already registered for" + f" {bp_desc} blueprint{existing_at}. Use 'name=' to" + f" provide a unique name." + ) first_bp_registration = not any(bp is self for bp in app.blueprints.values()) first_name_registration = name not in app.blueprints @@ -404,7 +384,7 @@ class Blueprint(Scaffold): self, rule: str, endpoint: t.Optional[str] = None, - view_func: t.Optional[t.Callable] = None, + view_func: t.Optional[ft.ViewCallable] = None, provide_automatic_options: t.Optional[bool] = None, **options: t.Any, ) -> None: @@ -429,7 +409,7 @@ class Blueprint(Scaffold): def app_template_filter( self, name: t.Optional[str] = None - ) -> t.Callable[[TemplateFilterCallable], TemplateFilterCallable]: + ) -> t.Callable[[ft.TemplateFilterCallable], ft.TemplateFilterCallable]: """Register a custom template filter, available application wide. Like :meth:`Flask.template_filter` but for a blueprint. @@ -437,14 +417,14 @@ class Blueprint(Scaffold): function name will be used. """ - def decorator(f: TemplateFilterCallable) -> TemplateFilterCallable: + def decorator(f: ft.TemplateFilterCallable) -> ft.TemplateFilterCallable: self.add_app_template_filter(f, name=name) return f return decorator def add_app_template_filter( - self, f: TemplateFilterCallable, name: t.Optional[str] = None + 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 @@ -461,7 +441,7 @@ class Blueprint(Scaffold): def app_template_test( self, name: t.Optional[str] = None - ) -> t.Callable[[TemplateTestCallable], TemplateTestCallable]: + ) -> t.Callable[[ft.TemplateTestCallable], ft.TemplateTestCallable]: """Register a custom template test, available application wide. Like :meth:`Flask.template_test` but for a blueprint. @@ -471,14 +451,14 @@ class Blueprint(Scaffold): function name will be used. """ - def decorator(f: TemplateTestCallable) -> TemplateTestCallable: + def decorator(f: ft.TemplateTestCallable) -> ft.TemplateTestCallable: self.add_app_template_test(f, name=name) return f return decorator def add_app_template_test( - self, f: TemplateTestCallable, name: t.Optional[str] = None + 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 @@ -497,7 +477,7 @@ class Blueprint(Scaffold): def app_template_global( self, name: t.Optional[str] = None - ) -> t.Callable[[TemplateGlobalCallable], TemplateGlobalCallable]: + ) -> t.Callable[[ft.TemplateGlobalCallable], ft.TemplateGlobalCallable]: """Register a custom template global, available application wide. Like :meth:`Flask.template_global` but for a blueprint. @@ -507,14 +487,14 @@ class Blueprint(Scaffold): function name will be used. """ - def decorator(f: TemplateGlobalCallable) -> TemplateGlobalCallable: + def decorator(f: ft.TemplateGlobalCallable) -> ft.TemplateGlobalCallable: self.add_app_template_global(f, name=name) return f return decorator def add_app_template_global( - self, f: TemplateGlobalCallable, name: t.Optional[str] = None + 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 @@ -531,7 +511,9 @@ class Blueprint(Scaffold): self.record_once(register_template) - def before_app_request(self, f: BeforeRequestCallable) -> BeforeRequestCallable: + 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. """ @@ -541,15 +523,15 @@ class Blueprint(Scaffold): return f def before_app_first_request( - self, f: BeforeFirstRequestCallable - ) -> BeforeFirstRequestCallable: + 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.record_once(lambda s: s.app.before_first_request_funcs.append(f)) return f - def after_app_request(self, f: AfterRequestCallable) -> AfterRequestCallable: + 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. """ @@ -558,7 +540,7 @@ class Blueprint(Scaffold): ) return f - def teardown_app_request(self, f: TeardownCallable) -> TeardownCallable: + 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. @@ -569,8 +551,8 @@ class Blueprint(Scaffold): return f def app_context_processor( - self, f: TemplateContextProcessorCallable - ) -> TemplateContextProcessorCallable: + 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. """ @@ -579,29 +561,29 @@ class Blueprint(Scaffold): ) return f - def app_errorhandler(self, code: t.Union[t.Type[Exception], int]) -> t.Callable: + 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. """ - def decorator( - f: "ErrorHandlerCallable[Exception]", - ) -> "ErrorHandlerCallable[Exception]": + def decorator(f: ft.ErrorHandlerDecorator) -> ft.ErrorHandlerDecorator: self.record_once(lambda s: s.app.errorhandler(code)(f)) return f return decorator def app_url_value_preprocessor( - self, f: URLValuePreprocessorCallable - ) -> URLValuePreprocessorCallable: + self, f: ft.URLValuePreprocessorCallable + ) -> ft.URLValuePreprocessorCallable: """Same as :meth:`url_value_preprocessor` but application wide.""" self.record_once( lambda s: s.app.url_value_preprocessors.setdefault(None, []).append(f) ) return f - def app_url_defaults(self, f: URLDefaultCallable) -> URLDefaultCallable: + def app_url_defaults(self, f: ft.URLDefaultCallable) -> ft.URLDefaultCallable: """Same as :meth:`url_defaults` but application wide.""" 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 8e215322e7..77c1e25a9c 100644 --- a/contrib/python/Flask/py3/flask/cli.py +++ b/contrib/python/Flask/py3/flask/cli.py @@ -5,7 +5,6 @@ import platform import re import sys import traceback -import warnings from functools import update_wrapper from operator import attrgetter from threading import Lock @@ -19,22 +18,12 @@ from .helpers import get_debug_flag from .helpers import get_env from .helpers import get_load_dotenv -try: - import dotenv -except ImportError: - dotenv = None - -try: - import ssl -except ImportError: - ssl = None # type: ignore - class NoAppException(click.UsageError): """Raised if an application cannot be found or loaded.""" -def find_best_app(script_info, module): +def find_best_app(module): """Given a module instance this tries to find the best possible application in the module or raises an exception. """ @@ -65,7 +54,7 @@ def find_best_app(script_info, module): if inspect.isfunction(app_factory): try: - app = call_factory(script_info, app_factory) + app = app_factory() if isinstance(app, Flask): return app @@ -87,42 +76,6 @@ def find_best_app(script_info, module): ) -def call_factory(script_info, app_factory, args=None, kwargs=None): - """Takes an app factory, a ``script_info` object and optionally a tuple - of arguments. Checks for the existence of a script_info argument and calls - the app_factory depending on that and the arguments provided. - """ - sig = inspect.signature(app_factory) - args = [] if args is None else args - kwargs = {} if kwargs is None else kwargs - - if "script_info" in sig.parameters: - warnings.warn( - "The 'script_info' argument is deprecated and will not be" - " passed to the app factory function in Flask 2.1.", - DeprecationWarning, - ) - kwargs["script_info"] = script_info - - if not args and len(sig.parameters) == 1: - first_parameter = next(iter(sig.parameters.values())) - - if ( - first_parameter.default is inspect.Parameter.empty - # **kwargs is reported as an empty default, ignore it - and first_parameter.kind is not inspect.Parameter.VAR_KEYWORD - ): - warnings.warn( - "Script info is deprecated and will not be passed as the" - " single argument to the app factory function in Flask" - " 2.1.", - DeprecationWarning, - ) - args.append(script_info) - - return app_factory(*args, **kwargs) - - def _called_with_wrong_args(f): """Check whether calling a function raised a ``TypeError`` because the call failed or because something in the factory raised the @@ -149,7 +102,7 @@ def _called_with_wrong_args(f): del tb -def find_app_by_string(script_info, module, app_name): +def find_app_by_string(module, app_name): """Check if the given string is a variable name or a function. Call a function to get the app instance, or return the variable directly. """ @@ -166,7 +119,8 @@ def find_app_by_string(script_info, module, app_name): if isinstance(expr, ast.Name): name = expr.id - args = kwargs = None + args = [] + kwargs = {} elif isinstance(expr, ast.Call): # Ensure the function name is an attribute name only. if not isinstance(expr.func, ast.Name): @@ -202,7 +156,7 @@ def find_app_by_string(script_info, module, app_name): # to get the real application. if inspect.isfunction(attr): try: - app = call_factory(script_info, attr, args, kwargs) + app = attr(*args, **kwargs) except TypeError as e: if not _called_with_wrong_args(attr): raise @@ -253,7 +207,7 @@ def prepare_import(path): return ".".join(module_name[::-1]) -def locate_app(script_info, module_name, app_name, raise_if_not_found=True): +def locate_app(module_name, app_name, raise_if_not_found=True): __traceback_hide__ = True # noqa: F841 try: @@ -274,9 +228,9 @@ def locate_app(script_info, module_name, app_name, raise_if_not_found=True): module = sys.modules[module_name] if app_name is None: - return find_best_app(script_info, module) + return find_best_app(module) else: - return find_app_by_string(script_info, module, app_name) + return find_app_by_string(module, app_name) def get_version(ctx, param, value): @@ -327,9 +281,17 @@ class DispatchingApp: 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: @@ -397,18 +359,18 @@ class ScriptInfo: return self._loaded_app if self.create_app is not None: - app = call_factory(self, self.create_app) + app = self.create_app() else: if self.app_import_path: path, name = ( re.split(r":(?![\\/])", self.app_import_path, 1) + [None] )[:2] import_name = prepare_import(path) - app = locate_app(self, import_name, name) + app = locate_app(import_name, name) else: for path in ("wsgi.py", "app.py"): import_name = prepare_import(path) - app = locate_app(self, import_name, None, raise_if_not_found=False) + app = locate_app(import_name, None, raise_if_not_found=False) if app: break @@ -530,14 +492,18 @@ class FlaskGroup(AppGroup): def _load_plugin_commands(self): if self._loaded_plugin_commands: return - try: - import pkg_resources - except ImportError: - self._loaded_plugin_commands = True - return - for ep in pkg_resources.iter_entry_points("flask.commands"): + if sys.version_info >= (3, 10): + from importlib import metadata + else: + # Use a backport on Python < 3.10. We technically have + # importlib.metadata on 3.8+, but the API changed in 3.10, + # so use the backport for consistency. + import importlib_metadata as metadata + + for ep in metadata.entry_points(group="flask.commands"): self.add_command(ep.load(), ep.name) + self._loaded_plugin_commands = True def get_command(self, ctx, name): @@ -630,7 +596,9 @@ def load_dotenv(path=None): .. versionadded:: 1.0 """ - if dotenv is None: + try: + import dotenv + except ImportError: if path or os.path.isfile(".env") or os.path.isfile(".flaskenv"): click.secho( " * Tip: There are .env or .flaskenv files present." @@ -706,12 +674,14 @@ class CertParamType(click.ParamType): self.path_type = click.Path(exists=True, dir_okay=False, resolve_path=True) def convert(self, value, param, ctx): - if ssl is None: + try: + import ssl + except ImportError: raise click.BadParameter( 'Using "--cert" requires Python to be compiled with SSL support.', ctx, param, - ) + ) from None try: return self.path_type(value, param, ctx) @@ -744,7 +714,13 @@ def _validate_key(ctx, param, value): """ cert = ctx.params.get("cert") is_adhoc = cert == "adhoc" - is_context = ssl and isinstance(cert, ssl.SSLContext) + + try: + import ssl + except ImportError: + is_context = False + else: + is_context = isinstance(cert, ssl.SSLContext) if value is not None: if is_adhoc: @@ -785,7 +761,10 @@ class SeparatedPathType(click.Path): @click.option("--host", "-h", default="127.0.0.1", help="The interface to bind to.") @click.option("--port", "-p", default=5000, help="The port to bind to.") @click.option( - "--cert", type=CertParamType(), help="Specify a certificate file to use HTTPS." + "--cert", + type=CertParamType(), + help="Specify a certificate file to use HTTPS.", + is_eager=True, ) @click.option( "--key", @@ -826,9 +805,28 @@ class SeparatedPathType(click.Path): f" are separated by {os.path.pathsep!r}." ), ) +@click.option( + "--exclude-patterns", + default=None, + type=SeparatedPathType(), + help=( + "Files matching these fnmatch patterns will not trigger a reload" + " on change. Multiple patterns are separated by" + f" {os.path.pathsep!r}." + ), +) @pass_script_info def run_command( - info, host, port, reload, debugger, eager_loading, with_threads, cert, extra_files + info, + host, + port, + reload, + debugger, + eager_loading, + with_threads, + cert, + extra_files, + exclude_patterns, ): """Run a local development server. @@ -860,6 +858,7 @@ def run_command( threaded=with_threads, ssl_context=cert, extra_files=extra_files, + exclude_patterns=exclude_patterns, ) @@ -984,15 +983,7 @@ debug mode. def main() -> None: - if int(click.__version__[0]) < 8: - warnings.warn( - "Using the `flask` cli with Click 7 is deprecated and" - " will not be supported starting with Flask 2.1." - " Please upgrade to Click 8 as soon as possible.", - DeprecationWarning, - ) - # TODO omit sys.argv once https://github.com/pallets/click/issues/536 is fixed - cli.main(args=sys.argv[1:]) + cli.main() if __name__ == "__main__": diff --git a/contrib/python/Flask/py3/flask/config.py b/contrib/python/Flask/py3/flask/config.py index ca769022f6..7b6a137ada 100644 --- a/contrib/python/Flask/py3/flask/config.py +++ b/contrib/python/Flask/py3/flask/config.py @@ -1,4 +1,5 @@ import errno +import json import os import types import typing as t @@ -70,7 +71,7 @@ class Config(dict): """ def __init__(self, root_path: str, defaults: t.Optional[dict] = None) -> None: - dict.__init__(self, defaults or {}) + super().__init__(defaults or {}) self.root_path = root_path def from_envvar(self, variable_name: str, silent: bool = False) -> bool: @@ -97,6 +98,70 @@ class Config(dict): ) return self.from_pyfile(rv, silent=silent) + def from_prefixed_env( + self, prefix: str = "FLASK", *, loads: t.Callable[[str], t.Any] = json.loads + ) -> bool: + """Load any environment variables that start with ``FLASK_``, + dropping the prefix from the env key for the config key. Values + are passed through a loading function to attempt to convert them + to more specific types than strings. + + Keys are loaded in :func:`sorted` order. + + The default loading function attempts to parse values as any + valid JSON type, including dicts and lists. + + Specific items in nested dicts can be set by separating the + keys with double underscores (``__``). If an intermediate key + doesn't exist, it will be initialized to an empty dict. + + :param prefix: Load env vars that start with this prefix, + separated with an underscore (``_``). + :param loads: Pass each string value to this function and use + the returned value as the config value. If any error is + raised it is ignored and the value remains a string. The + default is :func:`json.loads`. + + .. versionadded:: 2.1 + """ + prefix = f"{prefix}_" + len_prefix = len(prefix) + + for key in sorted(os.environ): + if not key.startswith(prefix): + continue + + value = os.environ[key] + + try: + value = loads(value) + except Exception: + # Keep the value as a string if loading failed. + pass + + # Change to key.removeprefix(prefix) on Python >= 3.9. + key = key[len_prefix:] + + if "__" not in key: + # A non-nested key, set directly. + self[key] = value + continue + + # Traverse nested dictionaries with keys separated by "__". + current = self + *parts, tail = key.split("__") + + for part in parts: + # If an intermediate dict does not exist, create it. + if part not in current: + current[part] = {} + + current = current[part] + + current[tail] = value + + return True + def from_pyfile(self, filename: str, silent: bool = False) -> bool: """Updates the values in the config from a Python file. This function behaves as if the file was imported as module with the @@ -176,6 +241,9 @@ class Config(dict): .. code-block:: python + import json + app.config.from_file("config.json", load=json.load) + import toml app.config.from_file("config.toml", load=toml.load) @@ -204,32 +272,6 @@ class Config(dict): return self.from_mapping(obj) - def from_json(self, filename: str, silent: bool = False) -> bool: - """Update the values in the config from a JSON file. The loaded - data is passed to the :meth:`from_mapping` method. - - :param filename: The path to the JSON file. This can be an - absolute path or relative to the config root path. - :param silent: Ignore the file if it doesn't exist. - :return: ``True`` if the file was loaded successfully. - - .. deprecated:: 2.0.0 - Will be removed in Flask 2.1. Use :meth:`from_file` instead. - This was removed early in 2.0.0, was added back in 2.0.1. - - .. versionadded:: 0.11 - """ - import warnings - from . import json - - warnings.warn( - "'from_json' is deprecated and will be removed in Flask" - " 2.1. Use 'from_file(path, json.load)' instead.", - DeprecationWarning, - stacklevel=2, - ) - return self.from_file(filename, json.load, silent=silent) - def from_mapping( self, mapping: t.Optional[t.Mapping[str, t.Any]] = None, **kwargs: t.Any ) -> bool: diff --git a/contrib/python/Flask/py3/flask/ctx.py b/contrib/python/Flask/py3/flask/ctx.py index 47465fd4e1..e6822b2d97 100644 --- a/contrib/python/Flask/py3/flask/ctx.py +++ b/contrib/python/Flask/py3/flask/ctx.py @@ -5,11 +5,11 @@ 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 .signals import appcontext_popped from .signals import appcontext_pushed -from .typing import AfterRequestCallable if t.TYPE_CHECKING: from .app import Flask @@ -109,7 +109,7 @@ class _AppCtxGlobals: return object.__repr__(self) -def after_this_request(f: AfterRequestCallable) -> AfterRequestCallable: +def after_this_request(f: ft.AfterRequestCallable) -> ft.AfterRequestCallable: """Executes a function after this request. This is useful to modify response objects. The function is passed the response object and has to return the same or a new one. @@ -178,7 +178,7 @@ def copy_current_request_context(f: t.Callable) -> t.Callable: def wrapper(*args, **kwargs): with reqctx: - return f(*args, **kwargs) + return reqctx.app.ensure_sync(f)(*args, **kwargs) return update_wrapper(wrapper, f) @@ -267,7 +267,10 @@ class AppContext: return self def __exit__( - self, exc_type: type, exc_value: BaseException, tb: TracebackType + self, + exc_type: t.Optional[type], + exc_value: t.Optional[BaseException], + tb: t.Optional[TracebackType], ) -> None: self.pop(exc_value) @@ -338,14 +341,32 @@ class RequestContext: # 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[AfterRequestCallable] = [] + self._after_request_functions: t.List[ft.AfterRequestCallable] = [] @property - def g(self) -> AppContext: + def g(self) -> _AppCtxGlobals: + import warnings + + warnings.warn( + "Accessing 'g' on the request context is deprecated and" + " will be removed in Flask 2.2. Access `g` directly or from" + "the application context instead.", + DeprecationWarning, + stacklevel=2, + ) return _app_ctx_stack.top.g @g.setter - def g(self, value: AppContext) -> None: + def g(self, value: _AppCtxGlobals) -> None: + import warnings + + warnings.warn( + "Setting 'g' on the request context is deprecated and" + " will be removed in Flask 2.2. Set it on the application" + " context instead.", + DeprecationWarning, + stacklevel=2, + ) _app_ctx_stack.top.g = value def copy(self) -> "RequestContext": @@ -473,7 +494,10 @@ class RequestContext: return self def __exit__( - self, exc_type: type, exc_value: BaseException, tb: TracebackType + self, + exc_type: t.Optional[type], + 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 diff --git a/contrib/python/Flask/py3/flask/debughelpers.py b/contrib/python/Flask/py3/flask/debughelpers.py index 212f7d7ee8..27d378c24a 100644 --- a/contrib/python/Flask/py3/flask/debughelpers.py +++ b/contrib/python/Flask/py3/flask/debughelpers.py @@ -41,53 +41,55 @@ class DebugFilesKeyError(KeyError, AssertionError): class FormDataRoutingRedirect(AssertionError): - """This exception is raised by Flask in debug mode if it detects a - redirect caused by the routing system when the request method is not - GET, HEAD or OPTIONS. Reasoning: form data will be dropped. + """This exception is raised in debug mode if a routing redirect + would cause the browser to drop the method or body. This happens + when method is not GET, HEAD or OPTIONS and the status code is not + 307 or 308. """ def __init__(self, request): exc = request.routing_exception buf = [ - f"A request was sent to this URL ({request.url}) but a" - " redirect was issued automatically by the routing system" - f" to {exc.new_url!r}." + f"A request was sent to '{request.url}', but routing issued" + f" a redirect to the canonical URL '{exc.new_url}'." ] - # In case just a slash was appended we can be extra helpful - if f"{request.base_url}/" == exc.new_url.split("?")[0]: + if f"{request.base_url}/" == exc.new_url.partition("?")[0]: buf.append( - " The URL was defined with a trailing slash so Flask" - " will automatically redirect to the URL with the" - " trailing slash if it was accessed without one." + " The URL was defined with a trailing slash. Flask" + " will redirect to the URL with a trailing slash if it" + " was accessed without one." ) buf.append( - " Make sure to directly send your" - f" {request.method}-request to this URL since we can't make" - " browsers or HTTP clients redirect with form data reliably" - " or without user interaction." + " Send requests to the canonical URL, or use 307 or 308 for" + " routing redirects. Otherwise, browsers will drop form" + " data.\n\n" + "This exception is only raised in debug mode." ) - buf.append("\n\nNote: this exception is only raised in debug mode") - AssertionError.__init__(self, "".join(buf).encode("utf-8")) + super().__init__("".join(buf)) def attach_enctype_error_multidict(request): - """Since Flask 0.8 we're monkeypatching the files object in case a - request is detected that does not use multipart form data but the files - object is accessed. + """Patch ``request.files.__getitem__`` to raise a descriptive error + about ``enctype=multipart/form-data``. + + :param request: The request to patch. + :meta private: """ oldcls = request.files.__class__ class newcls(oldcls): def __getitem__(self, key): try: - return oldcls.__getitem__(self, key) + return super().__getitem__(key) except KeyError as e: if key not in request.form: raise - raise DebugFilesKeyError(request, key) from e + raise DebugFilesKeyError(request, key).with_traceback( + e.__traceback__ + ) from None newcls.__name__ = oldcls.__name__ newcls.__module__ = oldcls.__module__ diff --git a/contrib/python/Flask/py3/flask/helpers.py b/contrib/python/Flask/py3/flask/helpers.py index 435978012f..1e0732b31a 100644 --- a/contrib/python/Flask/py3/flask/helpers.py +++ b/contrib/python/Flask/py3/flask/helpers.py @@ -5,13 +5,11 @@ import sys import typing as t import warnings from datetime import datetime -from datetime import timedelta from functools import lru_cache from functools import update_wrapper from threading import RLock import werkzeug.utils -from werkzeug.exceptions import NotFound from werkzeug.routing import BuildError from werkzeug.urls import url_quote @@ -188,7 +186,7 @@ def make_response(*args: t.Any) -> "Response": return current_app.response_class() if len(args) == 1: args = args[0] - return current_app.make_response(args) + return current_app.make_response(args) # type: ignore def url_for(endpoint: str, **values: t.Any) -> str: @@ -454,7 +452,7 @@ def _prepare_send_file_kwargs( warnings.warn( "The 'attachment_filename' parameter has been renamed to" " 'download_name'. The old name will be removed in Flask" - " 2.1.", + " 2.2.", DeprecationWarning, stacklevel=3, ) @@ -463,7 +461,7 @@ def _prepare_send_file_kwargs( if cache_timeout is not None: warnings.warn( "The 'cache_timeout' parameter has been renamed to" - " 'max_age'. The old name will be removed in Flask 2.1.", + " 'max_age'. The old name will be removed in Flask 2.2.", DeprecationWarning, stacklevel=3, ) @@ -472,7 +470,7 @@ def _prepare_send_file_kwargs( if add_etags is not None: warnings.warn( "The 'add_etags' parameter has been renamed to 'etag'. The" - " old name will be removed in Flask 2.1.", + " old name will be removed in Flask 2.2.", DeprecationWarning, stacklevel=3, ) @@ -627,29 +625,6 @@ def send_file( ) -def safe_join(directory: str, *pathnames: str) -> str: - """Safely join zero or more untrusted path components to a base - directory to avoid escaping the base directory. - - :param directory: The trusted base directory. - :param pathnames: The untrusted path components relative to the - base directory. - :return: A safe path, otherwise ``None``. - """ - warnings.warn( - "'flask.helpers.safe_join' is deprecated and will be removed in" - " Flask 2.1. Use 'werkzeug.utils.safe_join' instead.", - DeprecationWarning, - stacklevel=2, - ) - path = werkzeug.utils.safe_join(directory, *pathnames) - - if path is None: - raise NotFound() - - return path - - def send_from_directory( directory: t.Union[os.PathLike, str], path: t.Union[os.PathLike, str], @@ -674,7 +649,8 @@ def send_from_directory( If the final path does not point to an existing regular file, raises a 404 :exc:`~werkzeug.exceptions.NotFound` error. - :param directory: The directory that ``path`` must be located under. + :param directory: The directory that ``path`` must be located under, + relative to the current application's root path. :param path: The path to the file to send, relative to ``directory``. :param kwargs: Arguments to pass to :func:`send_file`. @@ -691,7 +667,7 @@ def send_from_directory( if filename is not None: warnings.warn( "The 'filename' parameter has been renamed to 'path'. The" - " old name will be removed in Flask 2.1.", + " old name will be removed in Flask 2.2.", DeprecationWarning, stacklevel=2, ) @@ -785,27 +761,6 @@ class locked_cached_property(werkzeug.utils.cached_property): super().__delete__(obj) -def total_seconds(td: timedelta) -> int: - """Returns the total seconds from a timedelta object. - - :param timedelta td: the timedelta to be converted in seconds - - :returns: number of seconds - :rtype: int - - .. deprecated:: 2.0 - Will be removed in Flask 2.1. Use - :meth:`timedelta.total_seconds` instead. - """ - warnings.warn( - "'total_seconds' is deprecated and will be removed in Flask" - " 2.1. Use 'timedelta.total_seconds' instead.", - DeprecationWarning, - stacklevel=2, - ) - return td.days * 60 * 60 * 24 + td.seconds - - def is_ip(value: str) -> bool: """Determine if the given string is an IP address. diff --git a/contrib/python/Flask/py3/flask/json/__init__.py b/contrib/python/Flask/py3/flask/json/__init__.py index ccb9efb174..adefe02dcd 100644 --- a/contrib/python/Flask/py3/flask/json/__init__.py +++ b/contrib/python/Flask/py3/flask/json/__init__.py @@ -1,9 +1,8 @@ +import dataclasses import decimal -import io import json as _json import typing as t import uuid -import warnings from datetime import date from jinja2.utils import htmlsafe_json_dumps as _jinja_htmlsafe_dumps @@ -16,12 +15,6 @@ if t.TYPE_CHECKING: from ..app import Flask from ..wrappers import Response -try: - import dataclasses -except ImportError: - # Python < 3.7 - dataclasses = None # type: ignore - class JSONEncoder(_json.JSONEncoder): """The default JSON encoder. Handles extra types compared to the @@ -30,6 +23,7 @@ class JSONEncoder(_json.JSONEncoder): - :class:`datetime.datetime` and :class:`datetime.date` are serialized to :rfc:`822` strings. This is the same as the HTTP date format. + - :class:`decimal.Decimal` is serialized to a string. - :class:`uuid.UUID` is serialized to a string. - :class:`dataclasses.dataclass` is passed to :func:`dataclasses.asdict`. @@ -135,20 +129,7 @@ def dumps(obj: t.Any, app: t.Optional["Flask"] = None, **kwargs: t.Any) -> str: context for configuration. """ _dump_arg_defaults(kwargs, app=app) - encoding = kwargs.pop("encoding", None) - rv = _json.dumps(obj, **kwargs) - - if encoding is not None: - warnings.warn( - "'encoding' is deprecated and will be removed in Flask 2.1.", - DeprecationWarning, - stacklevel=2, - ) - - if isinstance(rv, str): - return rv.encode(encoding) # type: ignore - - return rv + return _json.dumps(obj, **kwargs) def dump( @@ -170,27 +151,14 @@ def dump( deprecated and will be removed in Flask 2.1. """ _dump_arg_defaults(kwargs, app=app) - encoding = kwargs.pop("encoding", None) - show_warning = encoding is not None - - try: - fp.write("") - except TypeError: - show_warning = True - fp = io.TextIOWrapper(fp, encoding or "utf-8") # type: ignore - - if show_warning: - warnings.warn( - "Writing to a binary file, and the 'encoding' argument, is" - " deprecated and will be removed in Flask 2.1.", - DeprecationWarning, - stacklevel=2, - ) - _json.dump(obj, fp, **kwargs) -def loads(s: str, app: t.Optional["Flask"] = None, **kwargs: t.Any) -> t.Any: +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. Takes the same arguments as the built-in :func:`json.loads`, with @@ -210,19 +178,6 @@ def loads(s: str, app: t.Optional["Flask"] = None, **kwargs: t.Any) -> t.Any: context for configuration. """ _load_arg_defaults(kwargs, app=app) - encoding = kwargs.pop("encoding", None) - - if encoding is not None: - warnings.warn( - "'encoding' is deprecated and will be removed in Flask 2.1." - " The data must be a string or UTF-8 bytes.", - DeprecationWarning, - stacklevel=2, - ) - - if isinstance(s, bytes): - s = s.decode(encoding) - return _json.loads(s, **kwargs) @@ -242,20 +197,6 @@ def load(fp: t.IO[str], app: t.Optional["Flask"] = None, **kwargs: t.Any) -> t.A file must be text mode, or binary mode with UTF-8 bytes. """ _load_arg_defaults(kwargs, app=app) - encoding = kwargs.pop("encoding", None) - - if encoding is not None: - warnings.warn( - "'encoding' is deprecated and will be removed in Flask 2.1." - " The file must be text mode, or binary mode with UTF-8" - " bytes.", - DeprecationWarning, - stacklevel=2, - ) - - if isinstance(fp.read(0), bytes): - fp = io.TextIOWrapper(fp, encoding) # type: ignore - return _json.load(fp, **kwargs) diff --git a/contrib/python/Flask/py3/flask/scaffold.py b/contrib/python/Flask/py3/flask/scaffold.py index 8c6f8de3bb..a58941c01d 100644 --- a/contrib/python/Flask/py3/flask/scaffold.py +++ b/contrib/python/Flask/py3/flask/scaffold.py @@ -1,6 +1,7 @@ import importlib.util import mimetypes import os +import pathlib import pkgutil import sys import typing as t @@ -13,6 +14,7 @@ from jinja2 import ChoiceLoader, FileSystemLoader, ResourceLoader from werkzeug.exceptions import default_exceptions from werkzeug.exceptions import HTTPException +from . import typing as ft from .cli import AppGroup from .globals import current_app from .helpers import get_root_path @@ -20,18 +22,9 @@ from .helpers import locked_cached_property from .helpers import send_file from .helpers import send_from_directory from .templating import _default_template_ctx_processor -from .typing import AfterRequestCallable -from .typing import AppOrBlueprintKey -from .typing import BeforeRequestCallable -from .typing import GenericException -from .typing import TeardownCallable -from .typing import TemplateContextProcessorCallable -from .typing import URLDefaultCallable -from .typing import URLValuePreprocessorCallable if t.TYPE_CHECKING: from .wrappers import Response - from .typing import ErrorHandlerCallable # a singleton sentinel value for parameter defaults _sentinel = object() @@ -139,7 +132,7 @@ class Scaffold: self.view_functions: t.Dict[str, t.Callable] = {} #: A data structure of registered error handlers, in the format - #: ``{scope: {code: {class: handler}}}```. The ``scope`` key is + #: ``{scope: {code: {class: handler}}}``. The ``scope`` key is #: the name of a blueprint the handlers are active for, or #: ``None`` for all requests. The ``code`` key is the HTTP #: status code for ``HTTPException``, or ``None`` for @@ -152,11 +145,8 @@ class Scaffold: #: This data structure is internal. It should not be modified #: directly and its format may change at any time. self.error_handler_spec: t.Dict[ - AppOrBlueprintKey, - t.Dict[ - t.Optional[int], - t.Dict[t.Type[Exception], "ErrorHandlerCallable[Exception]"], - ], + ft.AppOrBlueprintKey, + t.Dict[t.Optional[int], t.Dict[t.Type[Exception], ft.ErrorHandlerCallable]], ] = defaultdict(lambda: defaultdict(dict)) #: A data structure of functions to call at the beginning of @@ -170,7 +160,7 @@ class Scaffold: #: This data structure is internal. It should not be modified #: directly and its format may change at any time. self.before_request_funcs: t.Dict[ - AppOrBlueprintKey, t.List[BeforeRequestCallable] + ft.AppOrBlueprintKey, t.List[ft.BeforeRequestCallable] ] = defaultdict(list) #: A data structure of functions to call at the end of each @@ -184,7 +174,7 @@ class Scaffold: #: This data structure is internal. It should not be modified #: directly and its format may change at any time. self.after_request_funcs: t.Dict[ - AppOrBlueprintKey, t.List[AfterRequestCallable] + ft.AppOrBlueprintKey, t.List[ft.AfterRequestCallable] ] = defaultdict(list) #: A data structure of functions to call at the end of each @@ -199,7 +189,7 @@ class Scaffold: #: This data structure is internal. It should not be modified #: directly and its format may change at any time. self.teardown_request_funcs: t.Dict[ - AppOrBlueprintKey, t.List[TeardownCallable] + ft.AppOrBlueprintKey, t.List[ft.TeardownCallable] ] = defaultdict(list) #: A data structure of functions to call to pass extra context @@ -214,7 +204,7 @@ class Scaffold: #: This data structure is internal. It should not be modified #: directly and its format may change at any time. self.template_context_processors: t.Dict[ - AppOrBlueprintKey, t.List[TemplateContextProcessorCallable] + ft.AppOrBlueprintKey, t.List[ft.TemplateContextProcessorCallable] ] = defaultdict(list, {None: [_default_template_ctx_processor]}) #: A data structure of functions to call to modify the keyword @@ -229,8 +219,8 @@ class Scaffold: #: This data structure is internal. It should not be modified #: directly and its format may change at any time. self.url_value_preprocessors: t.Dict[ - AppOrBlueprintKey, - t.List[URLValuePreprocessorCallable], + ft.AppOrBlueprintKey, + t.List[ft.URLValuePreprocessorCallable], ] = defaultdict(list) #: A data structure of functions to call to modify the keyword @@ -245,7 +235,7 @@ class Scaffold: #: This data structure is internal. It should not be modified #: directly and its format may change at any time. self.url_default_functions: t.Dict[ - AppOrBlueprintKey, t.List[URLDefaultCallable] + ft.AppOrBlueprintKey, t.List[ft.URLDefaultCallable] ] = defaultdict(list) def __repr__(self) -> str: @@ -397,48 +387,65 @@ class Scaffold: return open(os.path.join(self.root_path, resource), mode) - def _method_route(self, method: str, rule: str, options: dict) -> t.Callable: + def _method_route( + self, + method: str, + rule: str, + options: dict, + ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]: 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: + def get( + self, rule: str, **options: t.Any + ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]: """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: + def post( + self, rule: str, **options: t.Any + ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]: """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: + def put( + self, rule: str, **options: t.Any + ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]: """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: + def delete( + self, rule: str, **options: t.Any + ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]: """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: + def patch( + self, rule: str, **options: t.Any + ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]: """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: + def route( + self, rule: str, **options: t.Any + ) -> t.Callable[[ft.RouteDecorator], ft.RouteDecorator]: """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. @@ -462,7 +469,7 @@ class Scaffold: :class:`~werkzeug.routing.Rule` object. """ - def decorator(f: t.Callable) -> t.Callable: + def decorator(f: ft.RouteDecorator) -> ft.RouteDecorator: endpoint = options.pop("endpoint", None) self.add_url_rule(rule, endpoint, f, **options) return f @@ -474,7 +481,7 @@ class Scaffold: self, rule: str, endpoint: t.Optional[str] = None, - view_func: t.Optional[t.Callable] = None, + view_func: t.Optional[ft.ViewCallable] = None, provide_automatic_options: t.Optional[bool] = None, **options: t.Any, ) -> None: @@ -561,7 +568,7 @@ class Scaffold: return decorator @setupmethod - def before_request(self, f: BeforeRequestCallable) -> BeforeRequestCallable: + def before_request(self, f: ft.BeforeRequestCallable) -> ft.BeforeRequestCallable: """Register a function to run before each request. For example, this can be used to open a database connection, or @@ -583,7 +590,7 @@ class Scaffold: return f @setupmethod - def after_request(self, f: AfterRequestCallable) -> AfterRequestCallable: + def after_request(self, f: ft.AfterRequestCallable) -> ft.AfterRequestCallable: """Register a function to run after each request to this object. The function is called with the response object, and must return @@ -599,7 +606,7 @@ class Scaffold: return f @setupmethod - def teardown_request(self, f: TeardownCallable) -> TeardownCallable: + 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 @@ -617,10 +624,10 @@ class Scaffold: stack of active contexts. This becomes relevant if you are using such constructs in tests. - Teardown functions must avoid raising exceptions, since they . If they - execute code that might fail they - will have to surround the execution of these code by try/except - statements and log occurring errors. + Teardown functions must avoid raising exceptions. If + they execute code that might fail they + will have to surround the execution of that code with try/except + statements and log any errors. When a teardown function was called because of an exception it will be passed an error object. @@ -639,16 +646,16 @@ class Scaffold: @setupmethod def context_processor( - self, f: TemplateContextProcessorCallable - ) -> TemplateContextProcessorCallable: + self, f: ft.TemplateContextProcessorCallable + ) -> ft.TemplateContextProcessorCallable: """Registers a template context processor function.""" self.template_context_processors[None].append(f) return f @setupmethod def url_value_preprocessor( - self, f: URLValuePreprocessorCallable - ) -> URLValuePreprocessorCallable: + self, f: ft.URLValuePreprocessorCallable + ) -> ft.URLValuePreprocessorCallable: """Register a URL value preprocessor function for all view functions in the application. These functions will be called before the :meth:`before_request` functions. @@ -665,7 +672,7 @@ class Scaffold: return f @setupmethod - def url_defaults(self, f: URLDefaultCallable) -> URLDefaultCallable: + def url_defaults(self, f: ft.URLDefaultCallable) -> ft.URLDefaultCallable: """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. @@ -675,11 +682,8 @@ class Scaffold: @setupmethod def errorhandler( - self, code_or_exception: t.Union[t.Type[GenericException], int] - ) -> t.Callable[ - ["ErrorHandlerCallable[GenericException]"], - "ErrorHandlerCallable[GenericException]", - ]: + self, code_or_exception: t.Union[t.Type[Exception], int] + ) -> t.Callable[[ft.ErrorHandlerDecorator], ft.ErrorHandlerDecorator]: """Register a function to handle errors by code or exception class. A decorator that is used to register a function given an @@ -709,9 +713,7 @@ class Scaffold: an arbitrary exception """ - def decorator( - f: "ErrorHandlerCallable[GenericException]", - ) -> "ErrorHandlerCallable[GenericException]": + def decorator(f: ft.ErrorHandlerDecorator) -> ft.ErrorHandlerDecorator: self.register_error_handler(code_or_exception, f) return f @@ -720,8 +722,8 @@ class Scaffold: @setupmethod def register_error_handler( self, - code_or_exception: t.Union[t.Type[GenericException], int], - f: "ErrorHandlerCallable[GenericException]", + code_or_exception: t.Union[t.Type[Exception], int], + f: ft.ErrorHandlerCallable, ) -> None: """Alternative error attach function to the :meth:`errorhandler` decorator that is more straightforward to use for non decorator @@ -745,9 +747,7 @@ class Scaffold: " instead." ) from None - self.error_handler_spec[None][code][exc_class] = t.cast( - "ErrorHandlerCallable[Exception]", f - ) + self.error_handler_spec[None][code][exc_class] = f @staticmethod def _get_exc_class_and_code( @@ -809,30 +809,55 @@ def _matching_loader_thinks_module_is_package(loader, mod_name): ) -def _find_package_path(root_mod_name): +def _path_is_relative_to(path: pathlib.PurePath, base: str) -> bool: + # Path.is_relative_to doesn't exist until Python 3.9 + try: + path.relative_to(base) + return True + except ValueError: + return False + + +def _find_package_path(import_name): """Find the path that contains the package or module.""" + root_mod_name, _, _ = import_name.partition(".") + try: - spec = importlib.util.find_spec(root_mod_name) + root_spec = importlib.util.find_spec(root_mod_name) - if spec is None: + if root_spec is None: raise ValueError("not found") # ImportError: the machinery told us it does not exist # ValueError: # - the module name was invalid # - the module name is __main__ - # - *we* raised `ValueError` due to `spec` being `None` + # - *we* raised `ValueError` due to `root_spec` being `None` except (ImportError, ValueError): pass # handled below else: # namespace package - if spec.origin in {"namespace", None}: - return os.path.dirname(next(iter(spec.submodule_search_locations))) + if root_spec.origin in {"namespace", None}: + package_spec = importlib.util.find_spec(import_name) + if package_spec is not None and package_spec.submodule_search_locations: + # Pick the path in the namespace that contains the submodule. + package_path = pathlib.Path( + os.path.commonpath(package_spec.submodule_search_locations) + ) + search_locations = ( + location + for location in root_spec.submodule_search_locations + if _path_is_relative_to(package_path, location) + ) + else: + # Pick the first path. + search_locations = iter(root_spec.submodule_search_locations) + return os.path.dirname(next(search_locations)) # a package (with __init__.py) - elif spec.submodule_search_locations: - return os.path.dirname(os.path.dirname(spec.origin)) + elif root_spec.submodule_search_locations: + return os.path.dirname(os.path.dirname(root_spec.origin)) # just a normal module else: - return os.path.dirname(spec.origin) + return os.path.dirname(root_spec.origin) # we were unable to find the `package_path` using PEP 451 loaders loader = pkgutil.get_loader(root_mod_name) @@ -874,12 +899,11 @@ def find_package(import_name: str): for import. If the package is not installed, it's assumed that the package was imported from the current working directory. """ - root_mod_name, _, _ = import_name.partition(".") - package_path = _find_package_path(root_mod_name) + package_path = _find_package_path(import_name) py_prefix = os.path.abspath(sys.prefix) # installed to the system - if package_path.startswith(py_prefix): + if _path_is_relative_to(pathlib.PurePath(package_path), py_prefix): return py_prefix, package_path site_parent, site_folder = os.path.split(package_path) diff --git a/contrib/python/Flask/py3/flask/sessions.py b/contrib/python/Flask/py3/flask/sessions.py index 20648deadb..4e19270e0d 100644 --- a/contrib/python/Flask/py3/flask/sessions.py +++ b/contrib/python/Flask/py3/flask/sessions.py @@ -383,13 +383,19 @@ class SecureCookieSessionInterface(SessionInterface): path = self.get_cookie_path(app) secure = self.get_cookie_secure(app) samesite = self.get_cookie_samesite(app) + httponly = self.get_cookie_httponly(app) # If the session is modified to be empty, remove the cookie. # If the session is empty, return without setting the cookie. if not session: if session.modified: response.delete_cookie( - name, domain=domain, path=path, secure=secure, samesite=samesite + name, + domain=domain, + path=path, + secure=secure, + samesite=samesite, + httponly=httponly, ) return @@ -401,7 +407,6 @@ class SecureCookieSessionInterface(SessionInterface): if not self.should_set_cookie(app, session): return - httponly = self.get_cookie_httponly(app) expires = self.get_expiration_time(app, session) val = self.get_signing_serializer(app).dumps(dict(session)) # type: ignore response.set_cookie( diff --git a/contrib/python/Flask/py3/flask/templating.py b/contrib/python/Flask/py3/flask/templating.py index bb3e7fd5dd..507615c5cc 100644 --- a/contrib/python/Flask/py3/flask/templating.py +++ b/contrib/python/Flask/py3/flask/templating.py @@ -131,7 +131,8 @@ def _render(template: Template, context: dict, app: "Flask") -> str: def render_template( - template_name_or_list: t.Union[str, t.List[str]], **context: t.Any + 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 context. @@ -143,6 +144,12 @@ def render_template( context of the template. """ ctx = _app_ctx_stack.top + + 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), @@ -161,5 +168,11 @@ def render_template_string(source: str, **context: t.Any) -> str: context of the template. """ ctx = _app_ctx_stack.top + + 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.from_string(source), context, ctx.app) diff --git a/contrib/python/Flask/py3/flask/testing.py b/contrib/python/Flask/py3/flask/testing.py index 1b35cc7a0b..e07e50e7a2 100644 --- a/contrib/python/Flask/py3/flask/testing.py +++ b/contrib/python/Flask/py3/flask/testing.py @@ -172,6 +172,22 @@ class FlaskClient(Client): headers = resp.get_wsgi_headers(c.request.environ) self.cookie_jar.extract_wsgi(c.request.environ, headers) + def _copy_environ(self, other): + return { + **self.environ_base, + **other, + "flask._preserve_context": self.preserve_context, + } + + def _request_from_builder_args(self, args, kwargs): + kwargs["environ_base"] = self._copy_environ(kwargs.get("environ_base", {})) + builder = EnvironBuilder(self.application, *args, **kwargs) + + try: + return builder.get_request() + finally: + builder.close() + def open( self, *args: t.Any, @@ -179,64 +195,30 @@ class FlaskClient(Client): follow_redirects: bool = False, **kwargs: t.Any, ) -> "TestResponse": - as_tuple = kwargs.pop("as_tuple", None) - - # Same logic as super.open, but apply environ_base and preserve_context. - request = None - - def copy_environ(other): - return { - **self.environ_base, - **other, - "flask._preserve_context": self.preserve_context, - } - - if not kwargs and len(args) == 1: - arg = args[0] - - if isinstance(arg, werkzeug.test.EnvironBuilder): - builder = copy(arg) - builder.environ_base = copy_environ(builder.environ_base or {}) + if args and isinstance( + args[0], (werkzeug.test.EnvironBuilder, dict, BaseRequest) + ): + if isinstance(args[0], werkzeug.test.EnvironBuilder): + builder = copy(args[0]) + builder.environ_base = self._copy_environ(builder.environ_base or {}) request = builder.get_request() - elif isinstance(arg, dict): + elif isinstance(args[0], dict): request = EnvironBuilder.from_environ( - arg, app=self.application, environ_base=copy_environ({}) + args[0], app=self.application, environ_base=self._copy_environ({}) ).get_request() - elif isinstance(arg, BaseRequest): - request = copy(arg) - request.environ = copy_environ(request.environ) - - if request is None: - kwargs["environ_base"] = copy_environ(kwargs.get("environ_base", {})) - builder = EnvironBuilder(self.application, *args, **kwargs) - - try: - request = builder.get_request() - finally: - builder.close() - - if as_tuple is not None: - import warnings - - warnings.warn( - "'as_tuple' is deprecated and will be removed in" - " Werkzeug 2.1 and Flask 2.1. Use" - " 'response.request.environ' instead.", - DeprecationWarning, - stacklevel=3, - ) - return super().open( - request, - as_tuple=as_tuple, - buffered=buffered, - follow_redirects=follow_redirects, - ) + else: + # isinstance(args[0], BaseRequest) + request = copy(args[0]) + request.environ = self._copy_environ(request.environ) else: - return super().open( - request, - buffered=buffered, - follow_redirects=follow_redirects, - ) + # request is None + request = self._request_from_builder_args(args, kwargs) + + return super().open( + request, + buffered=buffered, + follow_redirects=follow_redirects, + ) def __enter__(self) -> "FlaskClient": if self.preserve_context: @@ -245,7 +227,10 @@ class FlaskClient(Client): return self def __exit__( - self, exc_type: type, exc_value: BaseException, tb: TracebackType + self, + exc_type: t.Optional[type], + exc_value: t.Optional[BaseException], + tb: t.Optional[TracebackType], ) -> None: self.preserve_context = False diff --git a/contrib/python/Flask/py3/flask/typing.py b/contrib/python/Flask/py3/flask/typing.py index 93896f806c..e6d67f2077 100644 --- a/contrib/python/Flask/py3/flask/typing.py +++ b/contrib/python/Flask/py3/flask/typing.py @@ -1,42 +1,40 @@ import typing as t - if t.TYPE_CHECKING: from _typeshed.wsgi import WSGIApplication # noqa: F401 from werkzeug.datastructures import Headers # noqa: F401 - from .wrappers import Response # 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", - t.AnyStr, - t.Dict[str, t.Any], # any jsonify-able dict - t.Generator[t.AnyStr, None, None], -] -StatusCode = int +ResponseValue = t.Union["Response", str, bytes, t.Dict[str, t.Any]] # the possible types for an individual HTTP header -HeaderName = str +# This should be a Union, but mypy doesn't pass unless it's a TypeVar. HeaderValue = t.Union[str, t.List[str], t.Tuple[str, ...]] # the possible types for HTTP headers HeadersValue = t.Union[ - "Headers", t.Dict[HeaderName, HeaderValue], t.List[t.Tuple[HeaderName, HeaderValue]] + "Headers", + t.Mapping[str, HeaderValue], + t.Sequence[t.Tuple[str, HeaderValue]], ] # The possible types returned by a route function. ResponseReturnValue = t.Union[ ResponseValue, t.Tuple[ResponseValue, HeadersValue], - t.Tuple[ResponseValue, StatusCode], - t.Tuple[ResponseValue, StatusCode, HeadersValue], + t.Tuple[ResponseValue, int], + t.Tuple[ResponseValue, int, HeadersValue], "WSGIApplication", ] -GenericException = t.TypeVar("GenericException", bound=Exception, contravariant=True) +# Allow any subclass of werkzeug.Response, such as the one from Flask, +# as a callback argument. Using werkzeug.Response directly makes a +# callback annotated with flask.Response fail type checking. +ResponseClass = t.TypeVar("ResponseClass", bound="Response") AppOrBlueprintKey = t.Optional[str] # The App key is None, whereas blueprints are named -AfterRequestCallable = t.Callable[["Response"], "Response"] +AfterRequestCallable = t.Callable[[ResponseClass], ResponseClass] BeforeFirstRequestCallable = t.Callable[[], None] BeforeRequestCallable = t.Callable[[], t.Optional[ResponseReturnValue]] TeardownCallable = t.Callable[[t.Optional[BaseException]], None] @@ -46,4 +44,15 @@ TemplateGlobalCallable = t.Callable[..., t.Any] TemplateTestCallable = t.Callable[..., bool] URLDefaultCallable = t.Callable[[str, dict], None] URLValuePreprocessorCallable = t.Callable[[t.Optional[str], t.Optional[dict]], None] -ErrorHandlerCallable = t.Callable[[GenericException], ResponseReturnValue] + +# This should take Exception, but that either breaks typing the argument +# with a specific exception, or decorating multiple times with different +# exceptions (and using a union type on the argument). +# https://github.com/pallets/flask/issues/4095 +# 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) diff --git a/contrib/python/Flask/py3/flask/views.py b/contrib/python/Flask/py3/flask/views.py index 1bd5c68b06..1dd560c62b 100644 --- a/contrib/python/Flask/py3/flask/views.py +++ b/contrib/python/Flask/py3/flask/views.py @@ -1,8 +1,8 @@ import typing as t +from . import typing as ft from .globals import current_app from .globals import request -from .typing import ResponseReturnValue http_method_funcs = frozenset( @@ -59,7 +59,7 @@ class View: #: .. versionadded:: 0.8 decorators: t.List[t.Callable] = [] - def dispatch_request(self) -> ResponseReturnValue: + 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. @@ -79,7 +79,7 @@ class View: constructor of the class. """ - def view(*args: t.Any, **kwargs: t.Any) -> ResponseReturnValue: + 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) @@ -146,7 +146,7 @@ class MethodView(View, metaclass=MethodViewType): app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter')) """ - def dispatch_request(self, *args: t.Any, **kwargs: t.Any) -> ResponseReturnValue: + def dispatch_request(self, *args: t.Any, **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 diff --git a/contrib/python/Flask/py3/flask/wrappers.py b/contrib/python/Flask/py3/flask/wrappers.py index 47dbe5c8d8..7153876b8d 100644 --- a/contrib/python/Flask/py3/flask/wrappers.py +++ b/contrib/python/Flask/py3/flask/wrappers.py @@ -9,7 +9,6 @@ from .globals import current_app from .helpers import _split_blueprint_path if t.TYPE_CHECKING: - import typing_extensions as te from werkzeug.routing import Rule @@ -110,7 +109,7 @@ class Request(RequestBase): return _split_blueprint_path(name) def _load_form_data(self) -> None: - RequestBase._load_form_data(self) + super()._load_form_data() # In debug mode we're replacing the files multidict with an ad-hoc # subclass that raises a different error for key errors. @@ -124,11 +123,14 @@ class Request(RequestBase): attach_enctype_error_multidict(self) - def on_json_loading_failed(self, e: Exception) -> "te.NoReturn": - if current_app and current_app.debug: - raise BadRequest(f"Failed to decode JSON object: {e}") + def on_json_loading_failed(self, e: t.Optional[ValueError]) -> t.Any: + try: + return super().on_json_loading_failed(e) + except BadRequest as e: + if current_app and current_app.debug: + raise - raise BadRequest() + raise BadRequest() from e class Response(ResponseBase): @@ -153,6 +155,8 @@ class Response(ResponseBase): json_module = json + autocorrect_location_header = False + @property def max_cookie_size(self) -> int: # type: ignore """Read-only view of the :data:`MAX_COOKIE_SIZE` config key. diff --git a/contrib/python/Flask/py3/ya.make b/contrib/python/Flask/py3/ya.make index 1a447d7cbf..fb57b6362c 100644 --- a/contrib/python/Flask/py3/ya.make +++ b/contrib/python/Flask/py3/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(2.0.3) +VERSION(2.1.3) LICENSE(BSD-3-Clause) |