Pass the request ctx rather than use the globals in the app
The globals have a performance penalty which can be justified for the convinience in user code. In the app however the ctx can easily be passed through the method calls thereby reducing the performance penalty. This may affect extensions if they have subclassed the app and overridden these methods.
This commit is contained in:
parent
74d923515c
commit
d8262aa58c
5 changed files with 80 additions and 42 deletions
|
|
@ -6,7 +6,8 @@ Unreleased
|
||||||
- Remove previously deprecated code. :pr:`5223`
|
- Remove previously deprecated code. :pr:`5223`
|
||||||
- Restructure the code such that the Flask (app) and Blueprint
|
- Restructure the code such that the Flask (app) and Blueprint
|
||||||
classes have Sans-IO bases. :pr:`5127`
|
classes have Sans-IO bases. :pr:`5127`
|
||||||
|
- Pass the request ctx rather than use the globals in the app class
|
||||||
|
methods. :pr:`5229`
|
||||||
|
|
||||||
Version 2.3.3
|
Version 2.3.3
|
||||||
-------------
|
-------------
|
||||||
|
|
|
||||||
107
src/flask/app.py
107
src/flask/app.py
|
|
@ -692,12 +692,17 @@ class Flask(App):
|
||||||
return cls(self, **kwargs) # type: ignore
|
return cls(self, **kwargs) # type: ignore
|
||||||
|
|
||||||
def handle_http_exception(
|
def handle_http_exception(
|
||||||
self, e: HTTPException
|
self,
|
||||||
|
e: HTTPException,
|
||||||
|
ctx: RequestContext,
|
||||||
) -> HTTPException | ft.ResponseReturnValue:
|
) -> HTTPException | ft.ResponseReturnValue:
|
||||||
"""Handles an HTTP exception. By default this will invoke the
|
"""Handles an HTTP exception. By default this will invoke the
|
||||||
registered error handlers and fall back to returning the
|
registered error handlers and fall back to returning the
|
||||||
exception as response.
|
exception as response.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
The request context, ctx, is now a required argument.
|
||||||
|
|
||||||
.. versionchanged:: 1.0.3
|
.. versionchanged:: 1.0.3
|
||||||
``RoutingException``, used internally for actions such as
|
``RoutingException``, used internally for actions such as
|
||||||
slash redirects during routing, is not passed to error
|
slash redirects during routing, is not passed to error
|
||||||
|
|
@ -721,13 +726,15 @@ class Flask(App):
|
||||||
if isinstance(e, RoutingException):
|
if isinstance(e, RoutingException):
|
||||||
return e
|
return e
|
||||||
|
|
||||||
handler = self._find_error_handler(e, request.blueprints)
|
handler = self._find_error_handler(e, ctx.request.blueprints)
|
||||||
if handler is None:
|
if handler is None:
|
||||||
return e
|
return e
|
||||||
return self.ensure_sync(handler)(e)
|
return self.ensure_sync(handler)(e)
|
||||||
|
|
||||||
def handle_user_exception(
|
def handle_user_exception(
|
||||||
self, e: Exception
|
self,
|
||||||
|
e: Exception,
|
||||||
|
ctx: RequestContext,
|
||||||
) -> HTTPException | ft.ResponseReturnValue:
|
) -> HTTPException | ft.ResponseReturnValue:
|
||||||
"""This method is called whenever an exception occurs that
|
"""This method is called whenever an exception occurs that
|
||||||
should be handled. A special case is :class:`~werkzeug
|
should be handled. A special case is :class:`~werkzeug
|
||||||
|
|
@ -736,6 +743,9 @@ class Flask(App):
|
||||||
return a response value or reraise the exception with the same
|
return a response value or reraise the exception with the same
|
||||||
traceback.
|
traceback.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
The request context, ctx, is now a required argument.
|
||||||
|
|
||||||
.. versionchanged:: 1.0
|
.. versionchanged:: 1.0
|
||||||
Key errors raised from request data like ``form`` show the
|
Key errors raised from request data like ``form`` show the
|
||||||
bad key in debug mode rather than a generic bad request
|
bad key in debug mode rather than a generic bad request
|
||||||
|
|
@ -749,16 +759,16 @@ class Flask(App):
|
||||||
e.show_exception = True
|
e.show_exception = True
|
||||||
|
|
||||||
if isinstance(e, HTTPException) and not self.trap_http_exception(e):
|
if isinstance(e, HTTPException) and not self.trap_http_exception(e):
|
||||||
return self.handle_http_exception(e)
|
return self.handle_http_exception(e, ctx)
|
||||||
|
|
||||||
handler = self._find_error_handler(e, request.blueprints)
|
handler = self._find_error_handler(e, ctx.request.blueprints)
|
||||||
|
|
||||||
if handler is None:
|
if handler is None:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
return self.ensure_sync(handler)(e)
|
return self.ensure_sync(handler)(e)
|
||||||
|
|
||||||
def handle_exception(self, e: Exception) -> Response:
|
def handle_exception(self, e: Exception, ctx: RequestContext) -> Response:
|
||||||
"""Handle an exception that did not have an error handler
|
"""Handle an exception that did not have an error handler
|
||||||
associated with it, or that was raised from an error handler.
|
associated with it, or that was raised from an error handler.
|
||||||
This always causes a 500 ``InternalServerError``.
|
This always causes a 500 ``InternalServerError``.
|
||||||
|
|
@ -775,6 +785,9 @@ class Flask(App):
|
||||||
always receive the ``InternalServerError``. The original
|
always receive the ``InternalServerError``. The original
|
||||||
unhandled exception is available as ``e.original_exception``.
|
unhandled exception is available as ``e.original_exception``.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
The request context, ctx, is now a required argument.
|
||||||
|
|
||||||
.. versionchanged:: 1.1.0
|
.. versionchanged:: 1.1.0
|
||||||
Always passes the ``InternalServerError`` instance to the
|
Always passes the ``InternalServerError`` instance to the
|
||||||
handler, setting ``original_exception`` to the unhandled
|
handler, setting ``original_exception`` to the unhandled
|
||||||
|
|
@ -801,77 +814,87 @@ class Flask(App):
|
||||||
|
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
self.log_exception(exc_info)
|
self.log_exception(exc_info, ctx)
|
||||||
server_error: InternalServerError | ft.ResponseReturnValue
|
server_error: InternalServerError | ft.ResponseReturnValue
|
||||||
server_error = InternalServerError(original_exception=e)
|
server_error = InternalServerError(original_exception=e)
|
||||||
handler = self._find_error_handler(server_error, request.blueprints)
|
handler = self._find_error_handler(server_error, ctx.request.blueprints)
|
||||||
|
|
||||||
if handler is not None:
|
if handler is not None:
|
||||||
server_error = self.ensure_sync(handler)(server_error)
|
server_error = self.ensure_sync(handler)(server_error)
|
||||||
|
|
||||||
return self.finalize_request(server_error, from_error_handler=True)
|
return self.finalize_request(server_error, ctx, from_error_handler=True)
|
||||||
|
|
||||||
def log_exception(
|
def log_exception(
|
||||||
self,
|
self,
|
||||||
exc_info: (tuple[type, BaseException, TracebackType] | tuple[None, None, None]),
|
exc_info: (tuple[type, BaseException, TracebackType] | tuple[None, None, None]),
|
||||||
|
ctx: RequestContext,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Logs an exception. This is called by :meth:`handle_exception`
|
"""Logs an exception. This is called by :meth:`handle_exception`
|
||||||
if debugging is disabled and right before the handler is called.
|
if debugging is disabled and right before the handler is called.
|
||||||
The default implementation logs the exception as error on the
|
The default implementation logs the exception as error on the
|
||||||
:attr:`logger`.
|
:attr:`logger`.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
The request context, ctx, is now a required argument.
|
||||||
|
|
||||||
.. versionadded:: 0.8
|
.. versionadded:: 0.8
|
||||||
"""
|
"""
|
||||||
self.logger.error(
|
self.logger.error(
|
||||||
f"Exception on {request.path} [{request.method}]", exc_info=exc_info
|
f"Exception on {ctx.request.path} [{ctx.request.method}]", exc_info=exc_info
|
||||||
)
|
)
|
||||||
|
|
||||||
def dispatch_request(self) -> ft.ResponseReturnValue:
|
def dispatch_request(self, ctx: RequestContext) -> ft.ResponseReturnValue:
|
||||||
"""Does the request dispatching. Matches the URL and returns the
|
"""Does the request dispatching. Matches the URL and returns the
|
||||||
return value of the view or error handler. This does not have to
|
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
|
be a response object. In order to convert the return value to a
|
||||||
proper response object, call :func:`make_response`.
|
proper response object, call :func:`make_response`.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
The request context, ctx, is now a required argument.
|
||||||
|
|
||||||
.. versionchanged:: 0.7
|
.. versionchanged:: 0.7
|
||||||
This no longer does the exception handling, this code was
|
This no longer does the exception handling, this code was
|
||||||
moved to the new :meth:`full_dispatch_request`.
|
moved to the new :meth:`full_dispatch_request`.
|
||||||
"""
|
"""
|
||||||
req = request_ctx.request
|
if ctx.request.routing_exception is not None:
|
||||||
if req.routing_exception is not None:
|
self.raise_routing_exception(ctx.request)
|
||||||
self.raise_routing_exception(req)
|
rule: Rule = ctx.request.url_rule # type: ignore[assignment]
|
||||||
rule: Rule = req.url_rule # type: ignore[assignment]
|
|
||||||
# if we provide automatic options for this URL and the
|
# if we provide automatic options for this URL and the
|
||||||
# request came with the OPTIONS method, reply automatically
|
# request came with the OPTIONS method, reply automatically
|
||||||
if (
|
if (
|
||||||
getattr(rule, "provide_automatic_options", False)
|
getattr(rule, "provide_automatic_options", False)
|
||||||
and req.method == "OPTIONS"
|
and ctx.request.method == "OPTIONS"
|
||||||
):
|
):
|
||||||
return self.make_default_options_response()
|
return self.make_default_options_response()
|
||||||
# otherwise dispatch to the handler for that endpoint
|
# otherwise dispatch to the handler for that endpoint
|
||||||
view_args: dict[str, t.Any] = req.view_args # type: ignore[assignment]
|
view_args: dict[str, t.Any] = ctx.request.view_args # type: ignore[assignment]
|
||||||
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
|
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
|
||||||
|
|
||||||
def full_dispatch_request(self) -> Response:
|
def full_dispatch_request(self, ctx: RequestContext) -> Response:
|
||||||
"""Dispatches the request and on top of that performs request
|
"""Dispatches the request and on top of that performs request
|
||||||
pre and postprocessing as well as HTTP exception catching and
|
pre and postprocessing as well as HTTP exception catching and
|
||||||
error handling.
|
error handling.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
The request context, ctx, is now a required argument.
|
||||||
|
|
||||||
.. versionadded:: 0.7
|
.. versionadded:: 0.7
|
||||||
"""
|
"""
|
||||||
self._got_first_request = True
|
self._got_first_request = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
request_started.send(self, _async_wrapper=self.ensure_sync)
|
request_started.send(self, _async_wrapper=self.ensure_sync)
|
||||||
rv = self.preprocess_request()
|
rv = self.preprocess_request(ctx)
|
||||||
if rv is None:
|
if rv is None:
|
||||||
rv = self.dispatch_request()
|
rv = self.dispatch_request(ctx)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
rv = self.handle_user_exception(e)
|
rv = self.handle_user_exception(e, ctx)
|
||||||
return self.finalize_request(rv)
|
return self.finalize_request(rv, ctx)
|
||||||
|
|
||||||
def finalize_request(
|
def finalize_request(
|
||||||
self,
|
self,
|
||||||
rv: ft.ResponseReturnValue | HTTPException,
|
rv: ft.ResponseReturnValue | HTTPException,
|
||||||
|
ctx: RequestContext,
|
||||||
from_error_handler: bool = False,
|
from_error_handler: bool = False,
|
||||||
) -> Response:
|
) -> Response:
|
||||||
"""Given the return value from a view function this finalizes
|
"""Given the return value from a view function this finalizes
|
||||||
|
|
@ -884,11 +907,14 @@ class Flask(App):
|
||||||
with the `from_error_handler` flag. If enabled, failures in
|
with the `from_error_handler` flag. If enabled, failures in
|
||||||
response processing will be logged and otherwise ignored.
|
response processing will be logged and otherwise ignored.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
The request context, ctx, is now a required argument.
|
||||||
|
|
||||||
:internal:
|
:internal:
|
||||||
"""
|
"""
|
||||||
response = self.make_response(rv)
|
response = self.make_response(rv)
|
||||||
try:
|
try:
|
||||||
response = self.process_response(response)
|
response = self.process_response(response, ctx)
|
||||||
request_finished.send(
|
request_finished.send(
|
||||||
self, _async_wrapper=self.ensure_sync, response=response
|
self, _async_wrapper=self.ensure_sync, response=response
|
||||||
)
|
)
|
||||||
|
|
@ -1215,7 +1241,7 @@ class Flask(App):
|
||||||
|
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def preprocess_request(self) -> ft.ResponseReturnValue | None:
|
def preprocess_request(self, ctx: RequestContext) -> ft.ResponseReturnValue | None:
|
||||||
"""Called before the request is dispatched. Calls
|
"""Called before the request is dispatched. Calls
|
||||||
:attr:`url_value_preprocessors` registered with the app and the
|
:attr:`url_value_preprocessors` registered with the app and the
|
||||||
current blueprint (if any). Then calls :attr:`before_request_funcs`
|
current blueprint (if any). Then calls :attr:`before_request_funcs`
|
||||||
|
|
@ -1224,13 +1250,16 @@ class Flask(App):
|
||||||
If any :meth:`before_request` handler returns a non-None value, the
|
If any :meth:`before_request` handler returns a non-None value, the
|
||||||
value is handled as if it was the return value from the view, and
|
value is handled as if it was the return value from the view, and
|
||||||
further request handling is stopped.
|
further request handling is stopped.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
The request context, ctx, is now a required argument.
|
||||||
"""
|
"""
|
||||||
names = (None, *reversed(request.blueprints))
|
names = (None, *reversed(ctx.request.blueprints))
|
||||||
|
|
||||||
for name in names:
|
for name in names:
|
||||||
if name in self.url_value_preprocessors:
|
if name in self.url_value_preprocessors:
|
||||||
for url_func in self.url_value_preprocessors[name]:
|
for url_func in self.url_value_preprocessors[name]:
|
||||||
url_func(request.endpoint, request.view_args)
|
url_func(ctx.request.endpoint, ctx.request.view_args)
|
||||||
|
|
||||||
for name in names:
|
for name in names:
|
||||||
if name in self.before_request_funcs:
|
if name in self.before_request_funcs:
|
||||||
|
|
@ -1242,11 +1271,14 @@ class Flask(App):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def process_response(self, response: Response) -> Response:
|
def process_response(self, response: Response, ctx: RequestContext) -> Response:
|
||||||
"""Can be overridden in order to modify the response object
|
"""Can be overridden in order to modify the response object
|
||||||
before it's sent to the WSGI server. By default this will
|
before it's sent to the WSGI server. By default this will
|
||||||
call all the :meth:`after_request` decorated functions.
|
call all the :meth:`after_request` decorated functions.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
The request context, ctx, is now a required argument.
|
||||||
|
|
||||||
.. versionchanged:: 0.5
|
.. versionchanged:: 0.5
|
||||||
As of Flask 0.5 the functions registered for after request
|
As of Flask 0.5 the functions registered for after request
|
||||||
execution are called in reverse order of registration.
|
execution are called in reverse order of registration.
|
||||||
|
|
@ -1255,23 +1287,25 @@ class Flask(App):
|
||||||
:return: a new response object or the same, has to be an
|
:return: a new response object or the same, has to be an
|
||||||
instance of :attr:`response_class`.
|
instance of :attr:`response_class`.
|
||||||
"""
|
"""
|
||||||
ctx = request_ctx._get_current_object() # type: ignore[attr-defined]
|
|
||||||
|
|
||||||
for func in ctx._after_request_functions:
|
for func in ctx._after_request_functions:
|
||||||
response = self.ensure_sync(func)(response)
|
response = self.ensure_sync(func)(response)
|
||||||
|
|
||||||
for name in chain(request.blueprints, (None,)):
|
for name in chain(ctx.request.blueprints, (None,)):
|
||||||
if name in self.after_request_funcs:
|
if name in self.after_request_funcs:
|
||||||
for func in reversed(self.after_request_funcs[name]):
|
for func in reversed(self.after_request_funcs[name]):
|
||||||
response = self.ensure_sync(func)(response)
|
response = self.ensure_sync(func)(response)
|
||||||
|
|
||||||
if not self.session_interface.is_null_session(ctx.session):
|
if not self.session_interface.is_null_session(ctx.session):
|
||||||
self.session_interface.save_session(self, ctx.session, response)
|
self.session_interface.save_session(
|
||||||
|
self, ctx.session, response # type: ignore[arg-type]
|
||||||
|
)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def do_teardown_request(
|
def do_teardown_request(
|
||||||
self, exc: BaseException | None = _sentinel # type: ignore
|
self,
|
||||||
|
ctx: RequestContext,
|
||||||
|
exc: BaseException | None = _sentinel, # type: ignore
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Called after the request is dispatched and the response is
|
"""Called after the request is dispatched and the response is
|
||||||
returned, right before the request context is popped.
|
returned, right before the request context is popped.
|
||||||
|
|
@ -1290,13 +1324,16 @@ class Flask(App):
|
||||||
request. Detected from the current exception information if
|
request. Detected from the current exception information if
|
||||||
not passed. Passed to each teardown function.
|
not passed. Passed to each teardown function.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
The request context, ctx, is now a required argument.
|
||||||
|
|
||||||
.. versionchanged:: 0.9
|
.. versionchanged:: 0.9
|
||||||
Added the ``exc`` argument.
|
Added the ``exc`` argument.
|
||||||
"""
|
"""
|
||||||
if exc is _sentinel:
|
if exc is _sentinel:
|
||||||
exc = sys.exc_info()[1]
|
exc = sys.exc_info()[1]
|
||||||
|
|
||||||
for name in chain(request.blueprints, (None,)):
|
for name in chain(ctx.request.blueprints, (None,)):
|
||||||
if name in self.teardown_request_funcs:
|
if name in self.teardown_request_funcs:
|
||||||
for func in reversed(self.teardown_request_funcs[name]):
|
for func in reversed(self.teardown_request_funcs[name]):
|
||||||
self.ensure_sync(func)(exc)
|
self.ensure_sync(func)(exc)
|
||||||
|
|
@ -1451,10 +1488,10 @@ class Flask(App):
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
ctx.push()
|
ctx.push()
|
||||||
response = self.full_dispatch_request()
|
response = self.full_dispatch_request(ctx)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error = e
|
error = e
|
||||||
response = self.handle_exception(e)
|
response = self.handle_exception(e, ctx)
|
||||||
except: # noqa: B001
|
except: # noqa: B001
|
||||||
error = sys.exc_info()[1]
|
error = sys.exc_info()[1]
|
||||||
raise
|
raise
|
||||||
|
|
|
||||||
|
|
@ -398,7 +398,7 @@ class RequestContext:
|
||||||
if clear_request:
|
if clear_request:
|
||||||
if exc is _sentinel:
|
if exc is _sentinel:
|
||||||
exc = sys.exc_info()[1]
|
exc = sys.exc_info()[1]
|
||||||
self.app.do_teardown_request(exc)
|
self.app.do_teardown_request(self, exc)
|
||||||
|
|
||||||
request_close = getattr(self.request, "close", None)
|
request_close = getattr(self.request, "close", None)
|
||||||
if request_close is not None:
|
if request_close is not None:
|
||||||
|
|
|
||||||
|
|
@ -288,8 +288,8 @@ def test_bad_environ_raises_bad_request():
|
||||||
# use a non-printable character in the Host - this is key to this test
|
# use a non-printable character in the Host - this is key to this test
|
||||||
environ["HTTP_HOST"] = "\x8a"
|
environ["HTTP_HOST"] = "\x8a"
|
||||||
|
|
||||||
with app.request_context(environ):
|
with app.request_context(environ) as ctx:
|
||||||
response = app.full_dispatch_request()
|
response = app.full_dispatch_request(ctx)
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -308,8 +308,8 @@ def test_environ_for_valid_idna_completes():
|
||||||
# these characters are all IDNA-compatible
|
# these characters are all IDNA-compatible
|
||||||
environ["HTTP_HOST"] = "ąśźäüжŠßя.com"
|
environ["HTTP_HOST"] = "ąśźäüжŠßя.com"
|
||||||
|
|
||||||
with app.request_context(environ):
|
with app.request_context(environ) as ctx:
|
||||||
response = app.full_dispatch_request()
|
response = app.full_dispatch_request(ctx)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import flask
|
||||||
|
|
||||||
def test_suppressed_exception_logging():
|
def test_suppressed_exception_logging():
|
||||||
class SuppressedFlask(flask.Flask):
|
class SuppressedFlask(flask.Flask):
|
||||||
def log_exception(self, exc_info):
|
def log_exception(self, exc_info, ctx):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
out = StringIO()
|
out = StringIO()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue