diff --git a/CHANGES.rst b/CHANGES.rst index 030f742a..1c4a7558 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,6 +8,11 @@ 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` +- 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/__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" 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/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: 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 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}")