From 89475e5d1e3e25ce56c9d9411496528f4a1ba82b Mon Sep 17 00:00:00 2001 From: David Lord Date: Wed, 12 May 2021 12:47:49 -0700 Subject: [PATCH 1/4] mark top-level names as exported --- CHANGES.rst | 2 ++ src/flask/__init__.py | 82 +++++++++++++++++++++---------------------- 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 030f742a..dbcc0667 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,6 +8,8 @@ Unreleased - Re-add the ``filename`` parameter in ``send_from_directory``. The ``filename`` parameter has been renamed to ``path``, the old name is deprecated. :pr:`4019` +- Mark top-level names as exported so type checking understands + imports in user projects. :issue:`4024` Version 2.0.0 diff --git a/src/flask/__init__.py b/src/flask/__init__.py index b53cf10f..008e1a81 100644 --- a/src/flask/__init__.py +++ b/src/flask/__init__.py @@ -1,46 +1,46 @@ from markupsafe import escape from markupsafe import Markup -from werkzeug.exceptions import abort -from werkzeug.utils import redirect +from werkzeug.exceptions import abort as abort +from werkzeug.utils import redirect as redirect -from . import json -from .app import Flask -from .app import Request -from .app import Response -from .blueprints import Blueprint -from .config import Config -from .ctx import after_this_request -from .ctx import copy_current_request_context -from .ctx import has_app_context -from .ctx import has_request_context -from .globals import _app_ctx_stack -from .globals import _request_ctx_stack -from .globals import current_app -from .globals import g -from .globals import request -from .globals import session -from .helpers import flash -from .helpers import get_flashed_messages -from .helpers import get_template_attribute -from .helpers import make_response -from .helpers import safe_join -from .helpers import send_file -from .helpers import send_from_directory -from .helpers import stream_with_context -from .helpers import url_for -from .json import jsonify -from .signals import appcontext_popped -from .signals import appcontext_pushed -from .signals import appcontext_tearing_down -from .signals import before_render_template -from .signals import got_request_exception -from .signals import message_flashed -from .signals import request_finished -from .signals import request_started -from .signals import request_tearing_down -from .signals import signals_available -from .signals import template_rendered -from .templating import render_template -from .templating import render_template_string +from . import json as json +from .app import Flask as Flask +from .app import Request as Request +from .app import Response as Response +from .blueprints import Blueprint as Blueprint +from .config import Config as Config +from .ctx import after_this_request as after_this_request +from .ctx import copy_current_request_context as copy_current_request_context +from .ctx import has_app_context as has_app_context +from .ctx import has_request_context as has_request_context +from .globals import _app_ctx_stack as _app_ctx_stack +from .globals import _request_ctx_stack as _request_ctx_stack +from .globals import current_app as current_app +from .globals import g as g +from .globals import request as request +from .globals import session as session +from .helpers import 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 +from .helpers import url_for as url_for +from .json import jsonify as jsonify +from .signals import appcontext_popped as appcontext_popped +from .signals import appcontext_pushed as appcontext_pushed +from .signals import appcontext_tearing_down as appcontext_tearing_down +from .signals import before_render_template as before_render_template +from .signals import got_request_exception as got_request_exception +from .signals import message_flashed as message_flashed +from .signals import request_finished as request_finished +from .signals import request_started as request_started +from .signals import request_tearing_down as request_tearing_down +from .signals import signals_available as signals_available +from .signals import template_rendered as template_rendered +from .templating import render_template as render_template +from .templating import render_template_string as render_template_string __version__ = "2.0.1.dev0" From 2baaa8fd8ebdd0f7bf7a24bff4855ef53476c92d Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 13 May 2021 11:32:19 -0700 Subject: [PATCH 2/4] fix annotation for g object --- src/flask/globals.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/flask/globals.py b/src/flask/globals.py index 5e6e8c75..6d91c75e 100644 --- a/src/flask/globals.py +++ b/src/flask/globals.py @@ -6,7 +6,7 @@ from werkzeug.local import LocalStack if t.TYPE_CHECKING: from .app import Flask - from .ctx import AppContext + from .ctx import _AppCtxGlobals from .sessions import SessionMixin from .wrappers import Request @@ -53,5 +53,7 @@ _request_ctx_stack = LocalStack() _app_ctx_stack = LocalStack() current_app: "Flask" = LocalProxy(_find_app) # type: ignore request: "Request" = LocalProxy(partial(_lookup_req_object, "request")) # type: ignore -session: "SessionMixin" = LocalProxy(partial(_lookup_req_object, "session")) # type: ignore # noqa: B950 -g: "AppContext" = LocalProxy(partial(_lookup_app_object, "g")) # type: ignore +session: "SessionMixin" = LocalProxy( # type: ignore + partial(_lookup_req_object, "session") +) +g: "_AppCtxGlobals" = LocalProxy(partial(_lookup_app_object, "g")) # type: ignore From 6fe7f45725dd0fc94fb3ce7ab3b3ff477b10401a Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 13 May 2021 11:33:01 -0700 Subject: [PATCH 3/4] inform mypy that g has arbitrary attributes --- CHANGES.rst | 2 ++ src/flask/ctx.py | 22 ++++++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index dbcc0667..2214dc49 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,6 +10,8 @@ Unreleased is deprecated. :pr:`4019` - Mark top-level names as exported so type checking understands imports in user projects. :issue:`4024` +- Fix type annotation for ``g`` and inform mypy that it is a namespace + object that has arbitrary attributes. :issue:`4020` Version 2.0.0 diff --git a/src/flask/ctx.py b/src/flask/ctx.py index 70de8cad..065edd5f 100644 --- a/src/flask/ctx.py +++ b/src/flask/ctx.py @@ -41,6 +41,24 @@ class _AppCtxGlobals: .. versionadded:: 0.10 """ + # Define attr methods to let mypy know this is a namespace object + # that has arbitrary attributes. + + def __getattr__(self, name: str) -> t.Any: + try: + return self.__dict__[name] + except KeyError: + raise AttributeError(name) from None + + def __setattr__(self, name: str, value: t.Any) -> None: + self.__dict__[name] = value + + def __delattr__(self, name: str) -> None: + try: + del self.__dict__[name] + except KeyError: + raise AttributeError(name) from None + def get(self, name: str, default: t.Optional[t.Any] = None) -> t.Any: """Get an attribute by name, or a default value. Like :meth:`dict.get`. @@ -78,10 +96,10 @@ class _AppCtxGlobals: """ return self.__dict__.setdefault(name, default) - def __contains__(self, item: t.Any) -> bool: + def __contains__(self, item: str) -> bool: return item in self.__dict__ - def __iter__(self) -> t.Iterator: + def __iter__(self) -> t.Iterator[str]: return iter(self.__dict__) def __repr__(self) -> str: From 25884c433f1eddf4537694d4c5f9f78cd9a14955 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 13 May 2021 12:53:32 -0700 Subject: [PATCH 4/4] fix typing that wasn't available in Python 3.6.0 --- CHANGES.rst | 1 + src/flask/app.py | 3 ++- src/flask/sessions.py | 3 ++- src/flask/wrappers.py | 3 ++- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 2214dc49..1c4a7558 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,6 +12,7 @@ Unreleased imports in user projects. :issue:`4024` - Fix type annotation for ``g`` and inform mypy that it is a namespace object that has arbitrary attributes. :issue:`4020` +- Fix some types that weren't available in Python 3.6.0. :issue:`4040` Version 2.0.0 diff --git a/src/flask/app.py b/src/flask/app.py index f8856a52..f0f31486 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -72,6 +72,7 @@ from .wrappers import Request from .wrappers import Response if t.TYPE_CHECKING: + import typing_extensions as te from .blueprints import Blueprint from .testing import FlaskClient from .testing import FlaskCliRunner @@ -1441,7 +1442,7 @@ class Flask(Scaffold): f"Exception on {request.path} [{request.method}]", exc_info=exc_info ) - def raise_routing_exception(self, request: Request) -> t.NoReturn: + 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 diff --git a/src/flask/sessions.py b/src/flask/sessions.py index 0e68e884..34b1d0ce 100644 --- a/src/flask/sessions.py +++ b/src/flask/sessions.py @@ -12,6 +12,7 @@ from .helpers import is_ip from .json.tag import TaggedJSONSerializer if t.TYPE_CHECKING: + import typing_extensions as te from .app import Flask from .wrappers import Request, Response @@ -92,7 +93,7 @@ class NullSession(SecureCookieSession): but fail on setting. """ - def _fail(self, *args: t.Any, **kwargs: t.Any) -> t.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 " diff --git a/src/flask/wrappers.py b/src/flask/wrappers.py index 48fcc34b..bfa9d7ce 100644 --- a/src/flask/wrappers.py +++ b/src/flask/wrappers.py @@ -8,6 +8,7 @@ from . import json from .globals import current_app if t.TYPE_CHECKING: + import typing_extensions as te from werkzeug.routing import Rule @@ -91,7 +92,7 @@ class Request(RequestBase): attach_enctype_error_multidict(self) - def on_json_loading_failed(self, e: Exception) -> t.NoReturn: + 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}")