diff --git a/CHANGES.rst b/CHANGES.rst index ddd3f8cc..0589713d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -46,6 +46,7 @@ Unreleased :issue:`5051` - The ``routes`` command shows each rule's ``subdomain`` or ``host`` when domain matching is in use. :issue:`5004` +- Use postponed evaluation of annotations. :pr:`5071` Version 2.2.4 diff --git a/src/flask/app.py b/src/flask/app.py index f3b2126b..4e4c153f 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import functools import inspect import logging @@ -95,7 +97,7 @@ else: return inspect.iscoroutinefunction(func) -def _make_timedelta(value: t.Union[timedelta, int, None]) -> t.Optional[timedelta]: +def _make_timedelta(value: timedelta | int | None) -> timedelta | None: if value is None or isinstance(value, timedelta): return value @@ -280,7 +282,7 @@ class Flask(Scaffold): "PERMANENT_SESSION_LIFETIME", get_converter=_make_timedelta ) - json_provider_class: t.Type[JSONProvider] = DefaultJSONProvider + json_provider_class: 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. @@ -348,7 +350,7 @@ class Flask(Scaffold): #: client class. Defaults to :class:`~flask.testing.FlaskClient`. #: #: .. versionadded:: 0.7 - test_client_class: t.Optional[t.Type["FlaskClient"]] = None + test_client_class: type[FlaskClient] | None = None #: The :class:`~click.testing.CliRunner` subclass, by default #: :class:`~flask.testing.FlaskCliRunner` that is used by @@ -356,7 +358,7 @@ class Flask(Scaffold): #: Flask app object as the first argument. #: #: .. versionadded:: 1.0 - test_cli_runner_class: t.Optional[t.Type["FlaskCliRunner"]] = None + test_cli_runner_class: type[FlaskCliRunner] | None = None #: the session interface to use. By default an instance of #: :class:`~flask.sessions.SecureCookieSessionInterface` is used here. @@ -367,15 +369,15 @@ class Flask(Scaffold): def __init__( self, import_name: str, - static_url_path: t.Optional[str] = None, - static_folder: t.Optional[t.Union[str, os.PathLike]] = "static", - static_host: t.Optional[str] = None, + static_url_path: str | None = None, + static_folder: str | os.PathLike | None = "static", + static_host: str | None = None, host_matching: bool = False, subdomain_matching: bool = False, - template_folder: t.Optional[t.Union[str, os.PathLike]] = "templates", - instance_path: t.Optional[str] = None, + template_folder: str | os.PathLike | None = "templates", + instance_path: str | None = None, instance_relative_config: bool = False, - root_path: t.Optional[str] = None, + root_path: str | None = None, ): super().__init__( import_name=import_name, @@ -435,8 +437,8 @@ class Flask(Scaffold): #: Otherwise, its return value is returned by ``url_for``. #: #: .. versionadded:: 0.9 - self.url_build_error_handlers: t.List[ - t.Callable[[Exception, str, t.Dict[str, t.Any]], str] + self.url_build_error_handlers: list[ + t.Callable[[Exception, str, dict[str, t.Any]], str] ] = [] #: A list of functions that are called when the application context @@ -445,13 +447,13 @@ class Flask(Scaffold): #: from databases. #: #: .. versionadded:: 0.9 - self.teardown_appcontext_funcs: t.List[ft.TeardownCallable] = [] + self.teardown_appcontext_funcs: list[ft.TeardownCallable] = [] #: A list of shell context processor functions that should be run #: when a shell context is created. #: #: .. versionadded:: 0.11 - self.shell_context_processors: t.List[ft.ShellContextProcessorCallable] = [] + self.shell_context_processors: list[ft.ShellContextProcessorCallable] = [] #: Maps registered blueprint names to blueprint objects. The #: dict retains the order the blueprints were registered in. @@ -459,7 +461,7 @@ class Flask(Scaffold): #: not track how often they were attached. #: #: .. versionadded:: 0.7 - self.blueprints: t.Dict[str, "Blueprint"] = {} + self.blueprints: dict[str, Blueprint] = {} #: a place where extensions can store application specific state. For #: example this is where an extension could store database engines and @@ -734,7 +736,7 @@ class Flask(Scaffold): :param context: the context as a dictionary that is updated in place to add extra variables. """ - names: t.Iterable[t.Optional[str]] = (None,) + names: t.Iterable[str | None] = (None,) # A template may be rendered outside a request context. if request: @@ -785,9 +787,9 @@ class Flask(Scaffold): def run( self, - host: t.Optional[str] = None, - port: t.Optional[int] = None, - debug: t.Optional[bool] = None, + host: str | None = None, + port: int | None = None, + debug: bool | None = None, load_dotenv: bool = True, **options: t.Any, ) -> None: @@ -906,7 +908,7 @@ class Flask(Scaffold): # without reloader and that stuff from an interactive shell. self._got_first_request = False - def test_client(self, use_cookies: bool = True, **kwargs: t.Any) -> "FlaskClient": + def test_client(self, use_cookies: bool = True, **kwargs: t.Any) -> FlaskClient: """Creates a test client for this application. For information about unit testing head over to :doc:`/testing`. @@ -964,7 +966,7 @@ class Flask(Scaffold): self, self.response_class, use_cookies=use_cookies, **kwargs ) - def test_cli_runner(self, **kwargs: t.Any) -> "FlaskCliRunner": + def test_cli_runner(self, **kwargs: t.Any) -> FlaskCliRunner: """Create a CLI runner for testing CLI commands. See :ref:`testing-cli`. @@ -982,7 +984,7 @@ class Flask(Scaffold): return cls(self, **kwargs) # type: ignore @setupmethod - def register_blueprint(self, blueprint: "Blueprint", **options: t.Any) -> None: + def register_blueprint(self, blueprint: Blueprint, **options: t.Any) -> None: """Register a :class:`~flask.Blueprint` on the application. Keyword arguments passed to this method will override the defaults set on the blueprint. @@ -1009,7 +1011,7 @@ class Flask(Scaffold): """ blueprint.register(self, options) - def iter_blueprints(self) -> t.ValuesView["Blueprint"]: + def iter_blueprints(self) -> t.ValuesView[Blueprint]: """Iterates over all blueprints by the order they were registered. .. versionadded:: 0.11 @@ -1020,9 +1022,9 @@ class Flask(Scaffold): def add_url_rule( self, rule: str, - endpoint: t.Optional[str] = None, - view_func: t.Optional[ft.RouteCallable] = None, - provide_automatic_options: t.Optional[bool] = None, + endpoint: str | None = None, + view_func: ft.RouteCallable | None = None, + provide_automatic_options: bool | None = None, **options: t.Any, ) -> None: if endpoint is None: @@ -1077,7 +1079,7 @@ class Flask(Scaffold): @setupmethod def template_filter( - self, name: t.Optional[str] = None + self, name: str | None = None ) -> 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 @@ -1099,7 +1101,7 @@ class Flask(Scaffold): @setupmethod def add_template_filter( - self, f: ft.TemplateFilterCallable, name: t.Optional[str] = None + self, f: ft.TemplateFilterCallable, name: str | None = None ) -> None: """Register a custom template filter. Works exactly like the :meth:`template_filter` decorator. @@ -1111,7 +1113,7 @@ class Flask(Scaffold): @setupmethod def template_test( - self, name: t.Optional[str] = None + self, name: str | None = None ) -> 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 @@ -1140,7 +1142,7 @@ class Flask(Scaffold): @setupmethod def add_template_test( - self, f: ft.TemplateTestCallable, name: t.Optional[str] = None + self, f: ft.TemplateTestCallable, name: str | None = None ) -> None: """Register a custom template test. Works exactly like the :meth:`template_test` decorator. @@ -1154,7 +1156,7 @@ class Flask(Scaffold): @setupmethod def template_global( - self, name: t.Optional[str] = None + self, name: str | None = None ) -> 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 @@ -1178,7 +1180,7 @@ class Flask(Scaffold): @setupmethod def add_template_global( - self, f: ft.TemplateGlobalCallable, name: t.Optional[str] = None + self, f: ft.TemplateGlobalCallable, name: str | None = None ) -> None: """Register a custom template global function. Works exactly like the :meth:`template_global` decorator. @@ -1235,7 +1237,7 @@ class Flask(Scaffold): self.shell_context_processors.append(f) return f - def _find_error_handler(self, e: Exception) -> t.Optional[ft.ErrorHandlerCallable]: + def _find_error_handler(self, e: Exception) -> ft.ErrorHandlerCallable | None: """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 @@ -1260,7 +1262,7 @@ class Flask(Scaffold): def handle_http_exception( self, e: HTTPException - ) -> t.Union[HTTPException, ft.ResponseReturnValue]: + ) -> 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. @@ -1330,7 +1332,7 @@ class Flask(Scaffold): def handle_user_exception( self, e: Exception - ) -> t.Union[HTTPException, ft.ResponseReturnValue]: + ) -> 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 @@ -1404,7 +1406,7 @@ class Flask(Scaffold): raise e self.log_exception(exc_info) - server_error: t.Union[InternalServerError, ft.ResponseReturnValue] + server_error: InternalServerError | ft.ResponseReturnValue server_error = InternalServerError(original_exception=e) handler = self._find_error_handler(server_error) @@ -1415,9 +1417,7 @@ class Flask(Scaffold): def log_exception( self, - exc_info: t.Union[ - t.Tuple[type, BaseException, TracebackType], t.Tuple[None, None, None] - ], + exc_info: (tuple[type, BaseException, TracebackType] | tuple[None, None, None]), ) -> None: """Logs an exception. This is called by :meth:`handle_exception` if debugging is disabled and right before the handler is called. @@ -1430,7 +1430,7 @@ class Flask(Scaffold): f"Exception on {request.path} [{request.method}]", exc_info=exc_info ) - def raise_routing_exception(self, request: Request) -> "te.NoReturn": + def raise_routing_exception(self, request: Request) -> te.NoReturn: """Intercept routing exceptions and possibly do something else. In debug mode, intercept a routing redirect and replace it with @@ -1480,7 +1480,7 @@ class Flask(Scaffold): ): return self.make_default_options_response() # otherwise dispatch to the handler for that endpoint - view_args: t.Dict[str, t.Any] = req.view_args # type: ignore[assignment] + view_args: 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: @@ -1503,7 +1503,7 @@ class Flask(Scaffold): def finalize_request( self, - rv: t.Union[ft.ResponseReturnValue, HTTPException], + rv: ft.ResponseReturnValue | HTTPException, from_error_handler: bool = False, ) -> Response: """Given the return value from a view function this finalizes @@ -1545,7 +1545,7 @@ class Flask(Scaffold): rv.allow.update(methods) return rv - def should_ignore_error(self, error: t.Optional[BaseException]) -> bool: + def should_ignore_error(self, error: BaseException | None) -> bool: """This is called to figure out if an error should be ignored or not as far as the teardown system is concerned. If this function returns ``True`` then the teardown handlers will not be @@ -1596,10 +1596,10 @@ class Flask(Scaffold): self, endpoint: str, *, - _anchor: t.Optional[str] = None, - _method: t.Optional[str] = None, - _scheme: t.Optional[str] = None, - _external: t.Optional[bool] = None, + _anchor: str | None = None, + _method: str | None = None, + _scheme: str | None = None, + _external: bool | None = None, **values: t.Any, ) -> str: """Generate a URL to the given endpoint with the given values. @@ -1871,9 +1871,7 @@ class Flask(Scaffold): return rv - def create_url_adapter( - self, request: t.Optional[Request] - ) -> t.Optional[MapAdapter]: + def create_url_adapter(self, request: Request | None) -> MapAdapter | None: """Creates a URL adapter for the given request. The URL adapter is created at a point where the request context is not yet set up so the request is passed explicitly. @@ -1920,7 +1918,7 @@ class Flask(Scaffold): .. versionadded:: 0.7 """ - names: t.Iterable[t.Optional[str]] = (None,) + names: t.Iterable[str | None] = (None,) # url_for may be called outside a request context, parse the # passed endpoint instead of using request.blueprints. @@ -1935,7 +1933,7 @@ class Flask(Scaffold): func(endpoint, values) def handle_url_build_error( - self, error: BuildError, endpoint: str, values: t.Dict[str, t.Any] + self, error: BuildError, endpoint: str, values: dict[str, t.Any] ) -> str: """Called by :meth:`.url_for` if a :exc:`~werkzeug.routing.BuildError` was raised. If this returns @@ -1968,7 +1966,7 @@ class Flask(Scaffold): raise error - def preprocess_request(self) -> t.Optional[ft.ResponseReturnValue]: + def preprocess_request(self) -> ft.ResponseReturnValue | None: """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` @@ -2024,7 +2022,7 @@ class Flask(Scaffold): return response def do_teardown_request( - self, exc: t.Optional[BaseException] = _sentinel # type: ignore + self, exc: BaseException | None = _sentinel # type: ignore ) -> None: """Called after the request is dispatched and the response is returned, right before the request context is popped. @@ -2057,7 +2055,7 @@ class Flask(Scaffold): request_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc) def do_teardown_appcontext( - self, exc: t.Optional[BaseException] = _sentinel # type: ignore + self, exc: BaseException | None = _sentinel # type: ignore ) -> None: """Called right before the application context is popped. @@ -2200,7 +2198,7 @@ class Flask(Scaffold): start the response. """ ctx = self.request_context(environ) - error: t.Optional[BaseException] = None + error: BaseException | None = None try: try: ctx.push() diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index 1aa82562..0407f86f 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import typing as t from collections import defaultdict @@ -38,8 +40,8 @@ class BlueprintSetupState: def __init__( self, - blueprint: "Blueprint", - app: "Flask", + blueprint: Blueprint, + app: Flask, options: t.Any, first_registration: bool, ) -> None: @@ -85,8 +87,8 @@ class BlueprintSetupState: def add_url_rule( self, rule: str, - endpoint: t.Optional[str] = None, - view_func: t.Optional[t.Callable] = None, + endpoint: str | None = None, + view_func: t.Callable | None = None, **options: t.Any, ) -> None: """A helper method to register a rule (and optionally a view function) @@ -173,14 +175,14 @@ class Blueprint(Scaffold): self, name: str, import_name: str, - static_folder: t.Optional[t.Union[str, os.PathLike]] = None, - static_url_path: 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, - root_path: t.Optional[str] = None, - cli_group: t.Optional[str] = _sentinel, # type: ignore + static_folder: str | os.PathLike | None = None, + static_url_path: str | None = None, + template_folder: str | os.PathLike | None = None, + url_prefix: str | None = None, + subdomain: str | None = None, + url_defaults: dict | None = None, + root_path: str | None = None, + cli_group: str | None = _sentinel, # type: ignore ): super().__init__( import_name=import_name, @@ -199,14 +201,14 @@ class Blueprint(Scaffold): self.name = name self.url_prefix = url_prefix self.subdomain = subdomain - self.deferred_functions: t.List[DeferredSetupFunction] = [] + self.deferred_functions: list[DeferredSetupFunction] = [] if url_defaults is None: url_defaults = {} self.url_values_defaults = url_defaults self.cli_group = cli_group - self._blueprints: t.List[t.Tuple["Blueprint", dict]] = [] + self._blueprints: list[tuple[Blueprint, dict]] = [] def _check_setup_finished(self, f_name: str) -> None: if self._got_registered_once: @@ -242,7 +244,7 @@ class Blueprint(Scaffold): self.record(update_wrapper(wrapper, func)) def make_setup_state( - self, app: "Flask", options: dict, first_registration: bool = False + self, app: Flask, options: dict, first_registration: bool = False ) -> BlueprintSetupState: """Creates an instance of :meth:`~flask.blueprints.BlueprintSetupState` object that is later passed to the register callback functions. @@ -251,7 +253,7 @@ class Blueprint(Scaffold): return BlueprintSetupState(self, app, options, first_registration) @setupmethod - def register_blueprint(self, blueprint: "Blueprint", **options: t.Any) -> None: + 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 on the blueprint. @@ -268,7 +270,7 @@ class Blueprint(Scaffold): raise ValueError("Cannot register a blueprint on itself") self._blueprints.append((blueprint, options)) - def register(self, app: "Flask", options: dict) -> None: + def register(self, app: Flask, options: dict) -> None: """Called by :meth:`Flask.register_blueprint` to register all views and callbacks registered on the blueprint with the application. Creates a :class:`.BlueprintSetupState` and calls @@ -408,9 +410,9 @@ class Blueprint(Scaffold): def add_url_rule( self, rule: str, - endpoint: t.Optional[str] = None, - view_func: t.Optional[ft.RouteCallable] = None, - provide_automatic_options: t.Optional[bool] = None, + endpoint: str | None = None, + view_func: ft.RouteCallable | None = None, + provide_automatic_options: bool | None = None, **options: t.Any, ) -> None: """Register a URL rule with the blueprint. See :meth:`.Flask.add_url_rule` for @@ -437,7 +439,7 @@ class Blueprint(Scaffold): @setupmethod def app_template_filter( - self, name: t.Optional[str] = None + self, name: str | None = None ) -> 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`. @@ -454,7 +456,7 @@ class Blueprint(Scaffold): @setupmethod def add_app_template_filter( - self, f: ft.TemplateFilterCallable, name: t.Optional[str] = None + self, f: ft.TemplateFilterCallable, name: str | None = None ) -> None: """Register a template filter, available in any template rendered by the application. Works like the :meth:`app_template_filter` decorator. Equivalent to @@ -471,7 +473,7 @@ class Blueprint(Scaffold): @setupmethod def app_template_test( - self, name: t.Optional[str] = None + self, name: str | None = None ) -> 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`. @@ -490,7 +492,7 @@ class Blueprint(Scaffold): @setupmethod def add_app_template_test( - self, f: ft.TemplateTestCallable, name: t.Optional[str] = None + self, f: ft.TemplateTestCallable, name: str | None = None ) -> None: """Register a template test, available in any template rendered by the application. Works like the :meth:`app_template_test` decorator. Equivalent to @@ -509,7 +511,7 @@ class Blueprint(Scaffold): @setupmethod def app_template_global( - self, name: t.Optional[str] = None + self, name: str | None = None ) -> 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`. @@ -528,7 +530,7 @@ class Blueprint(Scaffold): @setupmethod def add_app_template_global( - self, f: ft.TemplateGlobalCallable, name: t.Optional[str] = None + self, f: ft.TemplateGlobalCallable, name: str | None = None ) -> None: """Register a template global, available in any template rendered by the application. Works like the :meth:`app_template_global` decorator. Equivalent to @@ -589,7 +591,7 @@ class Blueprint(Scaffold): @setupmethod def app_errorhandler( - self, code: t.Union[t.Type[Exception], int] + self, code: type[Exception] | int ) -> 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`. diff --git a/src/flask/cli.py b/src/flask/cli.py index 6cc36219..f7e1f293 100644 --- a/src/flask/cli.py +++ b/src/flask/cli.py @@ -285,7 +285,7 @@ class ScriptInfo: self.create_app = create_app #: A dictionary with arbitrary data that can be associated with #: this script info. - self.data: t.Dict[t.Any, t.Any] = {} + self.data: dict[t.Any, t.Any] = {} self.set_debug_flag = set_debug_flag self._loaded_app: Flask | None = None diff --git a/src/flask/config.py b/src/flask/config.py index 5e48be33..a73dd786 100644 --- a/src/flask/config.py +++ b/src/flask/config.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import errno import json import os @@ -10,7 +12,7 @@ from werkzeug.utils import import_string class ConfigAttribute: """Makes an attribute forward to the config""" - def __init__(self, name: str, get_converter: t.Optional[t.Callable] = None) -> None: + def __init__(self, name: str, get_converter: t.Callable | None = None) -> None: self.__name__ = name self.get_converter = get_converter @@ -70,7 +72,7 @@ class Config(dict): :param defaults: an optional dictionary of default values """ - def __init__(self, root_path: str, defaults: t.Optional[dict] = None) -> None: + def __init__(self, root_path: str, defaults: dict | None = None) -> None: super().__init__(defaults or {}) self.root_path = root_path @@ -191,7 +193,7 @@ class Config(dict): self.from_object(d) return True - def from_object(self, obj: t.Union[object, str]) -> None: + def from_object(self, obj: object | str) -> None: """Updates the values from the given object. An object can be of one of the following two types: @@ -278,7 +280,7 @@ class Config(dict): return self.from_mapping(obj) def from_mapping( - self, mapping: t.Optional[t.Mapping[str, t.Any]] = None, **kwargs: t.Any + self, mapping: t.Mapping[str, t.Any] | None = None, **kwargs: t.Any ) -> bool: """Updates the config like :meth:`update` ignoring items with non-upper keys. @@ -287,7 +289,7 @@ class Config(dict): .. versionadded:: 0.11 """ - mappings: t.Dict[str, t.Any] = {} + mappings: dict[str, t.Any] = {} if mapping is not None: mappings.update(mapping) mappings.update(kwargs) @@ -298,7 +300,7 @@ class Config(dict): def get_namespace( self, namespace: str, lowercase: bool = True, trim_namespace: bool = True - ) -> t.Dict[str, t.Any]: + ) -> dict[str, t.Any]: """Returns a dictionary containing a subset of configuration options that match the specified namespace/prefix. Example usage:: diff --git a/src/flask/ctx.py b/src/flask/ctx.py index 64a0f065..b37e4e04 100644 --- a/src/flask/ctx.py +++ b/src/flask/ctx.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import contextvars import sys import typing as t @@ -60,7 +62,7 @@ class _AppCtxGlobals: except KeyError: raise AttributeError(name) from None - def get(self, name: str, default: t.Optional[t.Any] = None) -> t.Any: + def get(self, name: str, default: t.Any | None = None) -> t.Any: """Get an attribute by name, or a default value. Like :meth:`dict.get`. @@ -233,18 +235,18 @@ class AppContext: running CLI commands. """ - def __init__(self, app: "Flask") -> None: + def __init__(self, app: Flask) -> None: self.app = app self.url_adapter = app.create_url_adapter(None) self.g: _AppCtxGlobals = app.app_ctx_globals_class() - self._cv_tokens: t.List[contextvars.Token] = [] + self._cv_tokens: list[contextvars.Token] = [] def push(self) -> None: """Binds the app context to the current context.""" self._cv_tokens.append(_cv_app.set(self)) appcontext_pushed.send(self.app, _async_wrapper=self.app.ensure_sync) - def pop(self, exc: t.Optional[BaseException] = _sentinel) -> None: # type: ignore + def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore """Pops the app context.""" try: if len(self._cv_tokens) == 1: @@ -262,15 +264,15 @@ class AppContext: appcontext_popped.send(self.app, _async_wrapper=self.app.ensure_sync) - def __enter__(self) -> "AppContext": + def __enter__(self) -> AppContext: self.push() return self def __exit__( self, - exc_type: t.Optional[type], - exc_value: t.Optional[BaseException], - tb: t.Optional[TracebackType], + exc_type: type | None, + exc_value: BaseException | None, + tb: TracebackType | None, ) -> None: self.pop(exc_value) @@ -299,10 +301,10 @@ class RequestContext: def __init__( self, - app: "Flask", + app: Flask, environ: dict, - request: t.Optional["Request"] = None, - session: t.Optional["SessionMixin"] = None, + request: Request | None = None, + session: SessionMixin | None = None, ) -> None: self.app = app if request is None: @@ -314,16 +316,16 @@ class RequestContext: self.url_adapter = app.create_url_adapter(self.request) except HTTPException as e: self.request.routing_exception = e - self.flashes: t.Optional[t.List[t.Tuple[str, str]]] = None - self.session: t.Optional["SessionMixin"] = session + self.flashes: list[tuple[str, str]] | None = None + self.session: SessionMixin | None = 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] = [] + self._after_request_functions: list[ft.AfterRequestCallable] = [] - self._cv_tokens: t.List[t.Tuple[contextvars.Token, t.Optional[AppContext]]] = [] + self._cv_tokens: list[tuple[contextvars.Token, AppContext | None]] = [] - def copy(self) -> "RequestContext": + def copy(self) -> RequestContext: """Creates a copy of this request context with the same request object. This can be used to move a request context to a different greenlet. Because the actual request object is the same this cannot be used to @@ -382,7 +384,7 @@ class RequestContext: if self.url_adapter is not None: self.match_request() - def pop(self, exc: t.Optional[BaseException] = _sentinel) -> None: # type: ignore + def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore """Pops the request context and unbinds it by doing that. This will also trigger the execution of functions registered by the :meth:`~flask.Flask.teardown_request` decorator. @@ -419,15 +421,15 @@ class RequestContext: f"Popped wrong request context. ({ctx!r} instead of {self!r})" ) - def __enter__(self) -> "RequestContext": + def __enter__(self) -> RequestContext: self.push() return self def __exit__( self, - exc_type: t.Optional[type], - exc_value: t.Optional[BaseException], - tb: t.Optional[TracebackType], + exc_type: type | None, + exc_value: BaseException | None, + tb: TracebackType | None, ) -> None: self.pop(exc_value) diff --git a/src/flask/debughelpers.py b/src/flask/debughelpers.py index b0639892..6061441a 100644 --- a/src/flask/debughelpers.py +++ b/src/flask/debughelpers.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import typing as t from .app import Flask diff --git a/src/flask/globals.py b/src/flask/globals.py index e00a0c10..e9cd4acf 100644 --- a/src/flask/globals.py +++ b/src/flask/globals.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import typing as t from contextvars import ContextVar @@ -18,7 +20,7 @@ class _FakeStack: self.cv = cv @property - def top(self) -> t.Optional[t.Any]: + def top(self) -> t.Any | None: import warnings warnings.warn( @@ -38,15 +40,15 @@ This typically means that you attempted to use functionality that needed 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") +_cv_app: ContextVar[AppContext] = ContextVar("flask.app_ctx") __app_ctx_stack = _FakeStack("app", _cv_app) -app_ctx: "AppContext" = LocalProxy( # type: ignore[assignment] +app_ctx: AppContext = LocalProxy( # type: ignore[assignment] _cv_app, unbound_message=_no_app_msg ) -current_app: "Flask" = LocalProxy( # type: ignore[assignment] +current_app: Flask = LocalProxy( # type: ignore[assignment] _cv_app, "app", unbound_message=_no_app_msg ) -g: "_AppCtxGlobals" = LocalProxy( # type: ignore[assignment] +g: _AppCtxGlobals = LocalProxy( # type: ignore[assignment] _cv_app, "g", unbound_message=_no_app_msg ) @@ -57,15 +59,15 @@ 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") +_cv_request: ContextVar[RequestContext] = ContextVar("flask.request_ctx") __request_ctx_stack = _FakeStack("request", _cv_request) -request_ctx: "RequestContext" = LocalProxy( # type: ignore[assignment] +request_ctx: RequestContext = LocalProxy( # type: ignore[assignment] _cv_request, unbound_message=_no_req_msg ) -request: "Request" = LocalProxy( # type: ignore[assignment] +request: Request = LocalProxy( # type: ignore[assignment] _cv_request, "request", unbound_message=_no_req_msg ) -session: "SessionMixin" = LocalProxy( # type: ignore[assignment] +session: SessionMixin = LocalProxy( # type: ignore[assignment] _cv_request, "session", unbound_message=_no_req_msg ) diff --git a/src/flask/helpers.py b/src/flask/helpers.py index a8bf4971..cc77ada9 100644 --- a/src/flask/helpers.py +++ b/src/flask/helpers.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import pkgutil import socket @@ -50,9 +52,9 @@ def get_load_dotenv(default: bool = True) -> bool: def stream_with_context( - generator_or_function: t.Union[ - t.Iterator[t.AnyStr], t.Callable[..., t.Iterator[t.AnyStr]] - ] + generator_or_function: ( + t.Iterator[t.AnyStr] | t.Callable[..., t.Iterator[t.AnyStr]] + ) ) -> t.Iterator[t.AnyStr]: """Request contexts disappear when the response is started on the server. This is done for efficiency reasons and to make it less likely to encounter @@ -128,7 +130,7 @@ def stream_with_context( return wrapped_g -def make_response(*args: t.Any) -> "Response": +def make_response(*args: t.Any) -> Response: """Sometimes it is necessary to set additional headers in a view. Because views do not have to return response objects but can return a value that is converted into a response object by Flask itself, it becomes tricky to @@ -180,10 +182,10 @@ def make_response(*args: t.Any) -> "Response": 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, + _anchor: str | None = None, + _method: str | None = None, + _scheme: str | None = None, + _external: bool | None = None, **values: t.Any, ) -> str: """Generate a URL to the given endpoint with the given values. @@ -232,8 +234,8 @@ def url_for( def redirect( - location: str, code: int = 302, Response: t.Optional[t.Type["BaseResponse"]] = None -) -> "BaseResponse": + location: str, code: int = 302, Response: type[BaseResponse] | None = None +) -> BaseResponse: """Create a redirect response object. If :data:`~flask.current_app` is available, it will use its @@ -255,9 +257,7 @@ def redirect( return _wz_redirect(location, code=code, Response=Response) -def abort( - code: t.Union[int, "BaseResponse"], *args: t.Any, **kwargs: t.Any -) -> "te.NoReturn": +def abort(code: int | BaseResponse, *args: t.Any, **kwargs: t.Any) -> te.NoReturn: """Raise an :exc:`~werkzeug.exceptions.HTTPException` for the given status code. @@ -338,7 +338,7 @@ def flash(message: str, category: str = "message") -> None: def get_flashed_messages( with_categories: bool = False, category_filter: t.Iterable[str] = () -) -> t.Union[t.List[str], t.List[t.Tuple[str, str]]]: +) -> list[str] | list[tuple[str, str]]: """Pulls all flashed messages from the session and returns them. Further calls in the same request to the function will return the same messages. By default just the messages are returned, @@ -378,7 +378,7 @@ def get_flashed_messages( return flashes -def _prepare_send_file_kwargs(**kwargs: t.Any) -> t.Dict[str, t.Any]: +def _prepare_send_file_kwargs(**kwargs: t.Any) -> dict[str, t.Any]: if kwargs.get("max_age") is None: kwargs["max_age"] = current_app.get_send_file_max_age @@ -392,17 +392,15 @@ def _prepare_send_file_kwargs(**kwargs: t.Any) -> t.Dict[str, t.Any]: def send_file( - path_or_file: t.Union[os.PathLike, str, t.BinaryIO], - mimetype: t.Optional[str] = None, + path_or_file: os.PathLike | str | t.BinaryIO, + mimetype: str | None = None, as_attachment: bool = False, - download_name: t.Optional[str] = None, + download_name: str | None = None, conditional: bool = True, - etag: t.Union[bool, str] = True, - 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, -) -> "Response": + etag: bool | str = True, + last_modified: datetime | int | float | None = None, + max_age: None | (int | t.Callable[[str | None], int | None]) = 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 @@ -520,10 +518,10 @@ def send_file( def send_from_directory( - directory: t.Union[os.PathLike, str], - path: t.Union[os.PathLike, str], + directory: os.PathLike | str, + path: os.PathLike | str, **kwargs: t.Any, -) -> "Response": +) -> Response: """Send a file from within a directory using :func:`send_file`. .. code-block:: python @@ -627,8 +625,8 @@ class locked_cached_property(werkzeug.utils.cached_property): def __init__( self, fget: t.Callable[[t.Any], t.Any], - name: t.Optional[str] = None, - doc: t.Optional[str] = None, + name: str | None = None, + doc: str | None = None, ) -> None: import warnings @@ -687,8 +685,8 @@ def is_ip(value: str) -> bool: @lru_cache(maxsize=None) -def _split_blueprint_path(name: str) -> t.List[str]: - out: t.List[str] = [name] +def _split_blueprint_path(name: str) -> list[str]: + out: list[str] = [name] if "." in name: out.extend(_split_blueprint_path(name.rpartition(".")[0])) diff --git a/src/flask/json/provider.py b/src/flask/json/provider.py index 841e6d4d..0edd3d58 100644 --- a/src/flask/json/provider.py +++ b/src/flask/json/provider.py @@ -72,7 +72,7 @@ class JSONProvider: return self.loads(fp.read(), **kwargs) def _prepare_response_obj( - self, args: t.Tuple[t.Any, ...], kwargs: t.Dict[str, t.Any] + self, args: tuple[t.Any, ...], kwargs: dict[str, t.Any] ) -> t.Any: if args and kwargs: raise TypeError("app.json.response() takes either args or kwargs, not both") @@ -204,7 +204,7 @@ class DefaultJSONProvider(JSONProvider): :param kwargs: Treat as a dict to serialize. """ obj = self._prepare_response_obj(args, kwargs) - dump_args: t.Dict[str, t.Any] = {} + dump_args: dict[str, t.Any] = {} if (self.compact is None and self._app.debug) or self.compact is False: dump_args.setdefault("indent", 2) diff --git a/src/flask/json/tag.py b/src/flask/json/tag.py index 97f365a9..91cc4412 100644 --- a/src/flask/json/tag.py +++ b/src/flask/json/tag.py @@ -40,6 +40,8 @@ be processed before ``dict``. app.session_interface.serializer.register(TagOrderedDict, index=0) """ +from __future__ import annotations + import typing as t from base64 import b64decode from base64 import b64encode @@ -61,9 +63,9 @@ class JSONTag: #: The tag to mark the serialized object with. If ``None``, this tag is #: only used as an intermediate step during tagging. - key: t.Optional[str] = None + key: str | None = None - def __init__(self, serializer: "TaggedJSONSerializer") -> None: + def __init__(self, serializer: TaggedJSONSerializer) -> None: """Create a tagger for the given serializer.""" self.serializer = serializer @@ -244,17 +246,17 @@ class TaggedJSONSerializer: ] def __init__(self) -> None: - self.tags: t.Dict[str, JSONTag] = {} - self.order: t.List[JSONTag] = [] + self.tags: dict[str, JSONTag] = {} + self.order: list[JSONTag] = [] for cls in self.default_tags: self.register(cls) def register( self, - tag_class: t.Type[JSONTag], + tag_class: type[JSONTag], force: bool = False, - index: t.Optional[int] = None, + index: int | None = None, ) -> None: """Register a new tag with this serializer. @@ -283,7 +285,7 @@ class TaggedJSONSerializer: else: self.order.insert(index, tag) - def tag(self, value: t.Any) -> t.Dict[str, t.Any]: + def tag(self, value: t.Any) -> dict[str, t.Any]: """Convert a value to a tagged representation if necessary.""" for tag in self.order: if tag.check(value): @@ -291,7 +293,7 @@ class TaggedJSONSerializer: return value - def untag(self, value: t.Dict[str, t.Any]) -> t.Any: + def untag(self, value: dict[str, t.Any]) -> t.Any: """Convert a tagged representation back to the original type.""" if len(value) != 1: return value diff --git a/src/flask/logging.py b/src/flask/logging.py index 8981b820..99f6be85 100644 --- a/src/flask/logging.py +++ b/src/flask/logging.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging import sys import typing as t @@ -50,7 +52,7 @@ default_handler.setFormatter( ) -def create_logger(app: "Flask") -> logging.Logger: +def create_logger(app: Flask) -> logging.Logger: """Get the Flask app's logger and configure it if needed. The logger name will be the same as diff --git a/src/flask/scaffold.py b/src/flask/scaffold.py index bb583e4c..6af6906a 100644 --- a/src/flask/scaffold.py +++ b/src/flask/scaffold.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import importlib.util import os import pathlib @@ -70,16 +72,16 @@ class Scaffold: """ name: str - _static_folder: t.Optional[str] = None - _static_url_path: t.Optional[str] = None + _static_folder: str | None = None + _static_url_path: str | 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[t.Union[str, os.PathLike]] = None, - root_path: t.Optional[str] = None, + static_folder: str | os.PathLike | None = None, + static_url_path: str | None = None, + template_folder: str | os.PathLike | None = None, + root_path: str | None = None, ): #: The name of the package or module that this object belongs #: to. Do not change this once it is set by the constructor. @@ -112,7 +114,7 @@ class Scaffold: #: #: This data structure is internal. It should not be modified #: directly and its format may change at any time. - self.view_functions: t.Dict[str, t.Callable] = {} + self.view_functions: dict[str, t.Callable] = {} #: A data structure of registered error handlers, in the format #: ``{scope: {code: {class: handler}}}``. The ``scope`` key is @@ -127,9 +129,9 @@ 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[ + self.error_handler_spec: dict[ ft.AppOrBlueprintKey, - t.Dict[t.Optional[int], t.Dict[t.Type[Exception], ft.ErrorHandlerCallable]], + dict[int | None, dict[type[Exception], ft.ErrorHandlerCallable]], ] = defaultdict(lambda: defaultdict(dict)) #: A data structure of functions to call at the beginning of @@ -142,8 +144,8 @@ 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[ - ft.AppOrBlueprintKey, t.List[ft.BeforeRequestCallable] + self.before_request_funcs: dict[ + ft.AppOrBlueprintKey, list[ft.BeforeRequestCallable] ] = defaultdict(list) #: A data structure of functions to call at the end of each @@ -156,8 +158,8 @@ 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[ - ft.AppOrBlueprintKey, t.List[ft.AfterRequestCallable] + self.after_request_funcs: dict[ + ft.AppOrBlueprintKey, list[ft.AfterRequestCallable] ] = defaultdict(list) #: A data structure of functions to call at the end of each @@ -171,8 +173,8 @@ 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[ - ft.AppOrBlueprintKey, t.List[ft.TeardownCallable] + self.teardown_request_funcs: dict[ + ft.AppOrBlueprintKey, list[ft.TeardownCallable] ] = defaultdict(list) #: A data structure of functions to call to pass extra context @@ -186,8 +188,8 @@ 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[ - ft.AppOrBlueprintKey, t.List[ft.TemplateContextProcessorCallable] + self.template_context_processors: dict[ + ft.AppOrBlueprintKey, list[ft.TemplateContextProcessorCallable] ] = defaultdict(list, {None: [_default_template_ctx_processor]}) #: A data structure of functions to call to modify the keyword @@ -201,9 +203,9 @@ 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[ + self.url_value_preprocessors: dict[ ft.AppOrBlueprintKey, - t.List[ft.URLValuePreprocessorCallable], + list[ft.URLValuePreprocessorCallable], ] = defaultdict(list) #: A data structure of functions to call to modify the keyword @@ -217,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_default_functions: t.Dict[ - ft.AppOrBlueprintKey, t.List[ft.URLDefaultCallable] + self.url_default_functions: dict[ + ft.AppOrBlueprintKey, list[ft.URLDefaultCallable] ] = defaultdict(list) def __repr__(self) -> str: @@ -228,7 +230,7 @@ class Scaffold: raise NotImplementedError @property - def static_folder(self) -> t.Optional[str]: + def static_folder(self) -> str | None: """The absolute path to the configured static folder. ``None`` if no static folder is set. """ @@ -238,7 +240,7 @@ class Scaffold: return None @static_folder.setter - def static_folder(self, value: t.Optional[t.Union[str, os.PathLike]]) -> None: + def static_folder(self, value: str | os.PathLike | None) -> None: if value is not None: value = os.fspath(value).rstrip(r"\/") @@ -253,7 +255,7 @@ class Scaffold: return self.static_folder is not None @property - def static_url_path(self) -> t.Optional[str]: + def static_url_path(self) -> str | None: """The URL prefix that the static route will be accessible from. If it was not configured during init, it is derived from @@ -269,13 +271,13 @@ class Scaffold: return None @static_url_path.setter - def static_url_path(self, value: t.Optional[str]) -> None: + def static_url_path(self, value: str | None) -> None: if value is not None: value = value.rstrip("/") self._static_url_path = value - def get_send_file_max_age(self, filename: t.Optional[str]) -> t.Optional[int]: + def get_send_file_max_age(self, filename: str | None) -> int | None: """Used by :func:`send_file` to determine the ``max_age`` cache value for a given file path if it wasn't passed. @@ -299,7 +301,7 @@ class Scaffold: return value - def send_static_file(self, filename: str) -> "Response": + def send_static_file(self, filename: str) -> Response: """The view function used to serve files from :attr:`static_folder`. A route is automatically registered for this view at :attr:`static_url_path` if :attr:`static_folder` is @@ -318,7 +320,7 @@ class Scaffold: ) @cached_property - def jinja_loader(self) -> t.Optional[FileSystemLoader]: + def jinja_loader(self) -> FileSystemLoader | None: """The Jinja loader for this object's templates. By default this is a class :class:`jinja2.loaders.FileSystemLoader` to :attr:`template_folder` if it is set. @@ -440,9 +442,9 @@ class Scaffold: def add_url_rule( self, rule: str, - endpoint: t.Optional[str] = None, - view_func: t.Optional[ft.RouteCallable] = None, - provide_automatic_options: t.Optional[bool] = None, + endpoint: str | None = None, + view_func: ft.RouteCallable | None = None, + provide_automatic_options: bool | None = None, **options: t.Any, ) -> None: """Register a rule for routing incoming requests and building @@ -668,7 +670,7 @@ class Scaffold: @setupmethod def errorhandler( - self, code_or_exception: t.Union[t.Type[Exception], int] + self, code_or_exception: type[Exception] | int ) -> t.Callable[[T_error_handler], T_error_handler]: """Register a function to handle errors by code or exception class. @@ -713,7 +715,7 @@ class Scaffold: @setupmethod def register_error_handler( self, - code_or_exception: t.Union[t.Type[Exception], int], + code_or_exception: type[Exception] | int, f: ft.ErrorHandlerCallable, ) -> None: """Alternative error attach function to the :meth:`errorhandler` @@ -727,8 +729,8 @@ class Scaffold: @staticmethod def _get_exc_class_and_code( - exc_class_or_code: t.Union[t.Type[Exception], int] - ) -> t.Tuple[t.Type[Exception], t.Optional[int]]: + exc_class_or_code: type[Exception] | int, + ) -> tuple[type[Exception], int | None]: """Get the exception class being handled. For HTTP status codes or ``HTTPException`` subclasses, return both the exception and status code. @@ -736,7 +738,7 @@ class Scaffold: :param exc_class_or_code: Any exception class, or an HTTP status code as an integer. """ - exc_class: t.Type[Exception] + exc_class: type[Exception] if isinstance(exc_class_or_code, int): try: diff --git a/src/flask/sessions.py b/src/flask/sessions.py index afd49edb..037cb0bd 100644 --- a/src/flask/sessions.py +++ b/src/flask/sessions.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import hashlib import typing as t from collections.abc import MutableMapping @@ -92,7 +94,7 @@ class NullSession(SecureCookieSession): but fail on setting. """ - def _fail(self, *args: t.Any, **kwargs: t.Any) -> "te.NoReturn": + def _fail(self, *args: t.Any, **kwargs: t.Any) -> te.NoReturn: raise RuntimeError( "The session is unavailable because no secret " "key was set. Set the secret_key on the " @@ -153,7 +155,7 @@ class SessionInterface: #: .. versionadded:: 0.10 pickle_based = False - def make_null_session(self, app: "Flask") -> NullSession: + def make_null_session(self, app: Flask) -> NullSession: """Creates a null session which acts as a replacement object if the real session support could not be loaded due to a configuration error. This mainly aids the user experience because the job of the @@ -174,11 +176,11 @@ class SessionInterface: """ return isinstance(obj, self.null_session_class) - def get_cookie_name(self, app: "Flask") -> str: + def get_cookie_name(self, app: Flask) -> str: """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]: + def get_cookie_domain(self, app: Flask) -> str | None: """The value of the ``Domain`` parameter on the session cookie. If not set, browsers will only send the cookie to the exact domain it was set from. Otherwise, they will send it to any subdomain of the given value as well. @@ -191,7 +193,7 @@ class SessionInterface: rv = app.config["SESSION_COOKIE_DOMAIN"] return rv if rv else None - def get_cookie_path(self, app: "Flask") -> str: + def get_cookie_path(self, app: Flask) -> str: """Returns the path for which the cookie should be valid. The default implementation uses the value from the ``SESSION_COOKIE_PATH`` config var if it's set, and falls back to ``APPLICATION_ROOT`` or @@ -199,29 +201,27 @@ class SessionInterface: """ return app.config["SESSION_COOKIE_PATH"] or app.config["APPLICATION_ROOT"] - def get_cookie_httponly(self, app: "Flask") -> bool: + def get_cookie_httponly(self, app: Flask) -> bool: """Returns True if the session cookie should be httponly. This currently just returns the value of the ``SESSION_COOKIE_HTTPONLY`` config var. """ return app.config["SESSION_COOKIE_HTTPONLY"] - def get_cookie_secure(self, app: "Flask") -> bool: + def get_cookie_secure(self, app: Flask) -> bool: """Returns True if the cookie should be secure. This currently just returns the value of the ``SESSION_COOKIE_SECURE`` setting. """ return app.config["SESSION_COOKIE_SECURE"] - def get_cookie_samesite(self, app: "Flask") -> str: + def get_cookie_samesite(self, app: Flask) -> str: """Return ``'Strict'`` or ``'Lax'`` if the cookie should use the ``SameSite`` attribute. This currently just returns the value of the :data:`SESSION_COOKIE_SAMESITE` setting. """ return app.config["SESSION_COOKIE_SAMESITE"] - def get_expiration_time( - self, app: "Flask", session: SessionMixin - ) -> t.Optional[datetime]: + def get_expiration_time(self, app: Flask, session: SessionMixin) -> datetime | None: """A helper method that returns an expiration date for the session or ``None`` if the session is linked to the browser session. The default implementation returns now + the permanent session @@ -231,7 +231,7 @@ class SessionInterface: return datetime.now(timezone.utc) + app.permanent_session_lifetime return None - def should_set_cookie(self, app: "Flask", session: SessionMixin) -> bool: + def should_set_cookie(self, app: Flask, session: SessionMixin) -> bool: """Used by session backends to determine if a ``Set-Cookie`` header should be set for this session cookie for this response. If the session has been modified, the cookie is set. If the session is permanent and @@ -247,9 +247,7 @@ class SessionInterface: session.permanent and app.config["SESSION_REFRESH_EACH_REQUEST"] ) - def open_session( - self, app: "Flask", request: "Request" - ) -> t.Optional[SessionMixin]: + def open_session(self, app: Flask, request: Request) -> SessionMixin | None: """This is called at the beginning of each request, after pushing the request context, before matching the URL. @@ -264,7 +262,7 @@ class SessionInterface: raise NotImplementedError() def save_session( - self, app: "Flask", session: SessionMixin, response: "Response" + self, app: Flask, session: SessionMixin, response: Response ) -> None: """This is called at the end of each request, after generating a response, before removing the request context. It is skipped @@ -295,9 +293,7 @@ class SecureCookieSessionInterface(SessionInterface): serializer = session_json_serializer session_class = SecureCookieSession - def get_signing_serializer( - self, app: "Flask" - ) -> t.Optional[URLSafeTimedSerializer]: + def get_signing_serializer(self, app: Flask) -> URLSafeTimedSerializer | None: if not app.secret_key: return None signer_kwargs = dict( @@ -310,9 +306,7 @@ class SecureCookieSessionInterface(SessionInterface): signer_kwargs=signer_kwargs, ) - def open_session( - self, app: "Flask", request: "Request" - ) -> t.Optional[SecureCookieSession]: + def open_session(self, app: Flask, request: Request) -> SecureCookieSession | None: s = self.get_signing_serializer(app) if s is None: return None @@ -327,7 +321,7 @@ class SecureCookieSessionInterface(SessionInterface): return self.session_class() def save_session( - self, app: "Flask", session: SessionMixin, response: "Response" + self, app: Flask, session: SessionMixin, response: Response ) -> None: name = self.get_cookie_name(app) domain = self.get_cookie_domain(app) diff --git a/src/flask/templating.py b/src/flask/templating.py index 77504c63..769108f7 100644 --- a/src/flask/templating.py +++ b/src/flask/templating.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import typing as t from jinja2 import BaseLoader @@ -18,13 +20,13 @@ if t.TYPE_CHECKING: # pragma: no cover from .scaffold import Scaffold -def _default_template_ctx_processor() -> t.Dict[str, t.Any]: +def _default_template_ctx_processor() -> dict[str, t.Any]: """Default template context processor. Injects `request`, `session` and `g`. """ appctx = _cv_app.get(None) reqctx = _cv_request.get(None) - rv: t.Dict[str, t.Any] = {} + rv: dict[str, t.Any] = {} if appctx is not None: rv["g"] = appctx.g if reqctx is not None: @@ -39,7 +41,7 @@ class Environment(BaseEnvironment): name of the blueprint to referenced templates if necessary. """ - def __init__(self, app: "Flask", **options: t.Any) -> None: + def __init__(self, app: Flask, **options: t.Any) -> None: if "loader" not in options: options["loader"] = app.create_global_jinja_loader() BaseEnvironment.__init__(self, **options) @@ -51,24 +53,22 @@ class DispatchingJinjaLoader(BaseLoader): the blueprint folders. """ - def __init__(self, app: "Flask") -> None: + def __init__(self, app: Flask) -> None: self.app = app def get_source( # type: ignore self, environment: Environment, template: str - ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable]]: + ) -> tuple[str, str | None, t.Callable | None]: if self.app.config["EXPLAIN_TEMPLATE_LOADING"]: return self._get_source_explained(environment, template) return self._get_source_fast(environment, template) def _get_source_explained( self, environment: Environment, template: str - ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable]]: + ) -> tuple[str, str | None, t.Callable | None]: attempts = [] - rv: t.Optional[t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]] - trv: t.Optional[ - t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]] - ] = None + rv: tuple[str, str | None, t.Callable[[], bool] | None] | None + trv: None | (tuple[str, str | None, t.Callable[[], bool] | None]) = None for srcobj, loader in self._iter_loaders(template): try: @@ -89,7 +89,7 @@ class DispatchingJinjaLoader(BaseLoader): def _get_source_fast( self, environment: Environment, template: str - ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable]]: + ) -> tuple[str, str | None, t.Callable | None]: for _srcobj, loader in self._iter_loaders(template): try: return loader.get_source(environment, template) @@ -99,7 +99,7 @@ class DispatchingJinjaLoader(BaseLoader): def _iter_loaders( self, template: str - ) -> t.Generator[t.Tuple["Scaffold", BaseLoader], None, None]: + ) -> t.Generator[tuple[Scaffold, BaseLoader], None, None]: loader = self.app.jinja_loader if loader is not None: yield self.app, loader @@ -109,7 +109,7 @@ class DispatchingJinjaLoader(BaseLoader): if loader is not None: yield blueprint, loader - def list_templates(self) -> t.List[str]: + def list_templates(self) -> list[str]: result = set() loader = self.app.jinja_loader if loader is not None: @@ -124,7 +124,7 @@ class DispatchingJinjaLoader(BaseLoader): return list(result) -def _render(app: "Flask", template: Template, context: t.Dict[str, t.Any]) -> str: +def _render(app: Flask, template: Template, context: dict[str, t.Any]) -> str: app.update_template_context(context) before_render_template.send( app, _async_wrapper=app.ensure_sync, template=template, context=context @@ -137,7 +137,7 @@ def _render(app: "Flask", template: Template, context: t.Dict[str, t.Any]) -> st def render_template( - template_name_or_list: t.Union[str, Template, t.List[t.Union[str, Template]]], + template_name_or_list: str | Template | list[str | Template], **context: t.Any, ) -> str: """Render a template by name with the given context. @@ -164,7 +164,7 @@ def render_template_string(source: str, **context: t.Any) -> str: def _stream( - app: "Flask", template: Template, context: t.Dict[str, t.Any] + app: Flask, template: Template, context: dict[str, t.Any] ) -> t.Iterator[str]: app.update_template_context(context) before_render_template.send( @@ -187,7 +187,7 @@ def _stream( def stream_template( - template_name_or_list: t.Union[str, Template, t.List[t.Union[str, Template]]], + template_name_or_list: str | Template | list[str | Template], **context: t.Any, ) -> t.Iterator[str]: """Render a template by name with the given context as a stream. diff --git a/src/flask/testing.py b/src/flask/testing.py index a972a3f5..21f8aa01 100644 --- a/src/flask/testing.py +++ b/src/flask/testing.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import typing as t from contextlib import contextmanager from contextlib import ExitStack @@ -43,11 +45,11 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder): def __init__( self, - app: "Flask", + app: Flask, path: str = "/", - base_url: t.Optional[str] = None, - subdomain: t.Optional[str] = None, - url_scheme: t.Optional[str] = None, + base_url: str | None = None, + subdomain: str | None = None, + url_scheme: str | None = None, *args: t.Any, **kwargs: t.Any, ) -> None: @@ -104,12 +106,12 @@ class FlaskClient(Client): Basic usage is outlined in the :doc:`/testing` chapter. """ - application: "Flask" + application: Flask 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._new_contexts: list[t.ContextManager[t.Any]] = [] self._context_stack = ExitStack() self.environ_base = { "REMOTE_ADDR": "127.0.0.1", @@ -199,7 +201,7 @@ class FlaskClient(Client): buffered: bool = False, follow_redirects: bool = False, **kwargs: t.Any, - ) -> "TestResponse": + ) -> TestResponse: if args and isinstance( args[0], (werkzeug.test.EnvironBuilder, dict, BaseRequest) ): @@ -238,7 +240,7 @@ class FlaskClient(Client): return response - def __enter__(self) -> "FlaskClient": + def __enter__(self) -> FlaskClient: if self.preserve_context: raise RuntimeError("Cannot nest client invocations") self.preserve_context = True @@ -246,9 +248,9 @@ class FlaskClient(Client): def __exit__( self, - exc_type: t.Optional[type], - exc_value: t.Optional[BaseException], - tb: t.Optional[TracebackType], + exc_type: type | None, + exc_value: BaseException | None, + tb: TracebackType | None, ) -> None: self.preserve_context = False self._context_stack.close() @@ -260,7 +262,7 @@ class FlaskCliRunner(CliRunner): :meth:`~flask.Flask.test_cli_runner`. See :ref:`testing-cli`. """ - def __init__(self, app: "Flask", **kwargs: t.Any) -> None: + def __init__(self, app: Flask, **kwargs: t.Any) -> None: self.app = app super().__init__(**kwargs) diff --git a/src/flask/typing.py b/src/flask/typing.py index 8857598e..50aef7f4 100644 --- a/src/flask/typing.py +++ b/src/flask/typing.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import typing as t if t.TYPE_CHECKING: # pragma: no cover diff --git a/src/flask/views.py b/src/flask/views.py index f86172b4..c7a2b621 100644 --- a/src/flask/views.py +++ b/src/flask/views.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import typing as t from . import typing as ft @@ -45,12 +47,12 @@ class View: #: 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 + methods: t.ClassVar[t.Collection[str] | None] = 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 + provide_automatic_options: t.ClassVar[bool | None] = None #: A list of decorators to apply, in order, to the generated view #: function. Remember that ``@decorator`` syntax is applied bottom @@ -58,7 +60,7 @@ class View: #: decorator. #: #: .. versionadded:: 0.8 - decorators: t.ClassVar[t.List[t.Callable]] = [] + decorators: t.ClassVar[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 diff --git a/src/flask/wrappers.py b/src/flask/wrappers.py index ca2ad017..ef7aa38c 100644 --- a/src/flask/wrappers.py +++ b/src/flask/wrappers.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import typing as t from werkzeug.exceptions import BadRequest @@ -37,20 +39,20 @@ class Request(RequestBase): #: because the request was never internally bound. #: #: .. versionadded:: 0.6 - url_rule: t.Optional["Rule"] = None + url_rule: Rule | None = None #: A dict of view arguments that matched the request. If an exception #: happened when matching, this will be ``None``. - view_args: t.Optional[t.Dict[str, t.Any]] = None + view_args: dict[str, t.Any] | None = None #: If matching the URL failed, this is the exception that will be #: raised / was raised as part of the request handling. This is #: usually a :exc:`~werkzeug.exceptions.NotFound` exception or #: something similar. - routing_exception: t.Optional[Exception] = None + routing_exception: Exception | None = None @property - def max_content_length(self) -> t.Optional[int]: # type: ignore + def max_content_length(self) -> int | None: # type: ignore """Read-only view of the ``MAX_CONTENT_LENGTH`` config key.""" if current_app: return current_app.config["MAX_CONTENT_LENGTH"] @@ -58,7 +60,7 @@ class Request(RequestBase): return None @property - def endpoint(self) -> t.Optional[str]: + def endpoint(self) -> str | None: """The endpoint that matched the request URL. This will be ``None`` if matching failed or has not been @@ -73,7 +75,7 @@ class Request(RequestBase): return None @property - def blueprint(self) -> t.Optional[str]: + def blueprint(self) -> str | None: """The registered name of the current blueprint. This will be ``None`` if the endpoint is not part of a @@ -92,7 +94,7 @@ class Request(RequestBase): return None @property - def blueprints(self) -> t.List[str]: + def blueprints(self) -> list[str]: """The registered names of the current blueprint upwards through parent blueprints. @@ -123,7 +125,7 @@ class Request(RequestBase): attach_enctype_error_multidict(self) - def on_json_loading_failed(self, e: t.Optional[ValueError]) -> t.Any: + def on_json_loading_failed(self, e: ValueError | None) -> t.Any: try: return super().on_json_loading_failed(e) except BadRequest as e: @@ -151,7 +153,7 @@ class Response(ResponseBase): Added :attr:`max_cookie_size`. """ - default_mimetype: t.Optional[str] = "text/html" + default_mimetype: str | None = "text/html" json_module = json