Merge branch '2.0.x'

This commit is contained in:
David Lord 2021-11-16 06:30:19 -08:00
commit 6b0c8cdac1
No known key found for this signature in database
GPG key ID: 7A1C87E3F5BC42A8
4 changed files with 29 additions and 20 deletions

View file

@ -39,6 +39,9 @@ Unreleased
removed in Werkzeug 2.1. It is now also deprecated in Flask, to be removed in Werkzeug 2.1. It is now also deprecated in Flask, to be
removed in Flask 2.1, while remaining compatible with both in removed in Flask 2.1, while remaining compatible with both in
2.0.x. Use ``response.request.environ`` instead. :pr:`4341` 2.0.x. Use ``response.request.environ`` instead. :pr:`4341`
- Fix type annotation for ``errorhandler`` decorator. :issue:`4295`
- Revert a change to the CLI that caused it to hide ``ImportError``
tracebacks when importing the application. :issue:`4307`
Version 2.0.2 Version 2.0.2

View file

@ -222,15 +222,16 @@ def locate_app(module_name, app_name, raise_if_not_found=True):
try: try:
__import__(module_name) __import__(module_name)
except ImportError as e: except ImportError:
# Reraise the ImportError if it occurred within the imported module. # Reraise the ImportError if it occurred within the imported module.
# Determine this by checking whether the trace has a depth > 1. # Determine this by checking whether the trace has a depth > 1.
if sys.exc_info()[2].tb_next: if sys.exc_info()[2].tb_next:
raise NoAppException( raise NoAppException(
f"While importing {module_name!r}, an ImportError was raised." f"While importing {module_name!r}, an ImportError was"
) from e f" raised:\n\n{traceback.format_exc()}"
) from None
elif raise_if_not_found: elif raise_if_not_found:
raise NoAppException(f"Could not import {module_name!r}.") from e raise NoAppException(f"Could not import {module_name!r}.") from None
else: else:
return return

View file

@ -131,6 +131,13 @@ class SessionInterface:
app = Flask(__name__) app = Flask(__name__)
app.session_interface = MySessionInterface() app.session_interface = MySessionInterface()
Multiple requests with the same session may be sent and handled
concurrently. When implementing a new session interface, consider
whether reads or writes to the backing store must be synchronized.
There is no guarantee on the order in which the session for each
request is opened or saved, it will occur in the order that requests
begin and end processing.
.. versionadded:: 0.8 .. versionadded:: 0.8
""" """
@ -292,20 +299,25 @@ class SessionInterface:
def open_session( def open_session(
self, app: "Flask", request: "Request" self, app: "Flask", request: "Request"
) -> t.Optional[SessionMixin]: ) -> t.Optional[SessionMixin]:
"""This method has to be implemented and must either return ``None`` """This is called at the beginning of each request, after
in case the loading failed because of a configuration error or an pushing the request context, before matching the URL.
instance of a session object which implements a dictionary like
interface + the methods and attributes on :class:`SessionMixin`. This must return an object which implements a dictionary-like
interface as well as the :class:`SessionMixin` interface.
This will return ``None`` to indicate that loading failed in
some way that is not immediately an error. The request
context will fall back to using :meth:`make_null_session`
in this case.
""" """
raise NotImplementedError() raise NotImplementedError()
def save_session( def save_session(
self, app: "Flask", session: SessionMixin, response: "Response" self, app: "Flask", session: SessionMixin, response: "Response"
) -> None: ) -> None:
"""This is called for actual sessions returned by :meth:`open_session` """This is called at the end of each request, after generating
at the end of the request. This is still called during a request a response, before removing the request context. It is skipped
context so if you absolutely need access to the request you can do if :meth:`is_null_session` returns ``True``.
that.
""" """
raise NotImplementedError() raise NotImplementedError()

View file

@ -46,11 +46,4 @@ TemplateGlobalCallable = t.Callable[..., t.Any]
TemplateTestCallable = t.Callable[..., bool] TemplateTestCallable = t.Callable[..., bool]
URLDefaultCallable = t.Callable[[str, dict], None] URLDefaultCallable = t.Callable[[str, dict], None]
URLValuePreprocessorCallable = t.Callable[[t.Optional[str], t.Optional[dict]], None] URLValuePreprocessorCallable = t.Callable[[t.Optional[str], t.Optional[dict]], None]
ErrorHandlerCallable = t.Callable[[GenericException], ResponseReturnValue]
if t.TYPE_CHECKING:
import typing_extensions as te
class ErrorHandlerCallable(te.Protocol[GenericException]):
def __call__(self, error: GenericException) -> ResponseReturnValue:
...