Skip to content

Commit 0e738ab

Browse files
committed
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.
1 parent 1d8b53f commit 0e738ab

File tree

4 files changed

+48
-41
lines changed

4 files changed

+48
-41
lines changed

src/flask/app.py

Lines changed: 42 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,9 @@ def test_cli_runner(self, **kwargs: t.Any) -> FlaskCliRunner:
693693
return cls(self, **kwargs) # type: ignore
694694

695695
def handle_http_exception(
696-
self, e: HTTPException
696+
self,
697+
e: HTTPException,
698+
ctx: RequestContext,
697699
) -> HTTPException | ft.ResponseReturnValue:
698700
"""Handles an HTTP exception. By default this will invoke the
699701
registered error handlers and fall back to returning the
@@ -722,13 +724,15 @@ def handle_http_exception(
722724
if isinstance(e, RoutingException):
723725
return e
724726

725-
handler = self._find_error_handler(e, request.blueprints)
727+
handler = self._find_error_handler(e, ctx.request.blueprints)
726728
if handler is None:
727729
return e
728730
return self.ensure_sync(handler)(e)
729731

730732
def handle_user_exception(
731-
self, e: Exception
733+
self,
734+
e: Exception,
735+
ctx: RequestContext,
732736
) -> HTTPException | ft.ResponseReturnValue:
733737
"""This method is called whenever an exception occurs that
734738
should be handled. A special case is :class:`~werkzeug
@@ -750,16 +754,16 @@ def handle_user_exception(
750754
e.show_exception = True
751755

752756
if isinstance(e, HTTPException) and not self.trap_http_exception(e):
753-
return self.handle_http_exception(e)
757+
return self.handle_http_exception(e, ctx)
754758

755-
handler = self._find_error_handler(e, request.blueprints)
759+
handler = self._find_error_handler(e, ctx.request.blueprints)
756760

757761
if handler is None:
758762
raise
759763

760764
return self.ensure_sync(handler)(e)
761765

762-
def handle_exception(self, e: Exception) -> Response:
766+
def handle_exception(self, e: Exception, ctx: RequestContext) -> Response:
763767
"""Handle an exception that did not have an error handler
764768
associated with it, or that was raised from an error handler.
765769
This always causes a 500 ``InternalServerError``.
@@ -802,19 +806,20 @@ def handle_exception(self, e: Exception) -> Response:
802806

803807
raise e
804808

805-
self.log_exception(exc_info)
809+
self.log_exception(exc_info, ctx)
806810
server_error: InternalServerError | ft.ResponseReturnValue
807811
server_error = InternalServerError(original_exception=e)
808-
handler = self._find_error_handler(server_error, request.blueprints)
812+
handler = self._find_error_handler(server_error, ctx.request.blueprints)
809813

810814
if handler is not None:
811815
server_error = self.ensure_sync(handler)(server_error)
812816

813-
return self.finalize_request(server_error, from_error_handler=True)
817+
return self.finalize_request(server_error, ctx, from_error_handler=True)
814818

815819
def log_exception(
816820
self,
817821
exc_info: (tuple[type, BaseException, TracebackType] | tuple[None, None, None]),
822+
ctx: RequestContext,
818823
) -> None:
819824
"""Logs an exception. This is called by :meth:`handle_exception`
820825
if debugging is disabled and right before the handler is called.
@@ -824,10 +829,10 @@ def log_exception(
824829
.. versionadded:: 0.8
825830
"""
826831
self.logger.error(
827-
f"Exception on {request.path} [{request.method}]", exc_info=exc_info
832+
f"Exception on {ctx.request.path} [{ctx.request.method}]", exc_info=exc_info
828833
)
829834

830-
def dispatch_request(self) -> ft.ResponseReturnValue:
835+
def dispatch_request(self, ctx: RequestContext) -> ft.ResponseReturnValue:
831836
"""Does the request dispatching. Matches the URL and returns the
832837
return value of the view or error handler. This does not have to
833838
be a response object. In order to convert the return value to a
@@ -837,22 +842,21 @@ def dispatch_request(self) -> ft.ResponseReturnValue:
837842
This no longer does the exception handling, this code was
838843
moved to the new :meth:`full_dispatch_request`.
839844
"""
840-
req = request_ctx.request
841-
if req.routing_exception is not None:
842-
self.raise_routing_exception(req)
843-
rule: Rule = req.url_rule # type: ignore[assignment]
845+
if ctx.request.routing_exception is not None:
846+
self.raise_routing_exception(ctx.request)
847+
rule: Rule = ctx.request.url_rule # type: ignore[assignment]
844848
# if we provide automatic options for this URL and the
845849
# request came with the OPTIONS method, reply automatically
846850
if (
847851
getattr(rule, "provide_automatic_options", False)
848-
and req.method == "OPTIONS"
852+
and ctx.request.method == "OPTIONS"
849853
):
850854
return self.make_default_options_response()
851855
# otherwise dispatch to the handler for that endpoint
852-
view_args: dict[str, t.Any] = req.view_args # type: ignore[assignment]
856+
view_args: dict[str, t.Any] = ctx.request.view_args # type: ignore[assignment]
853857
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
854858

855-
def full_dispatch_request(self) -> Response:
859+
def full_dispatch_request(self, ctx: RequestContext) -> Response:
856860
"""Dispatches the request and on top of that performs request
857861
pre and postprocessing as well as HTTP exception catching and
858862
error handling.
@@ -863,16 +867,17 @@ def full_dispatch_request(self) -> Response:
863867

864868
try:
865869
request_started.send(self, _async_wrapper=self.ensure_sync)
866-
rv = self.preprocess_request()
870+
rv = self.preprocess_request(ctx)
867871
if rv is None:
868-
rv = self.dispatch_request()
872+
rv = self.dispatch_request(ctx)
869873
except Exception as e:
870-
rv = self.handle_user_exception(e)
871-
return self.finalize_request(rv)
874+
rv = self.handle_user_exception(e, ctx)
875+
return self.finalize_request(rv, ctx)
872876

873877
def finalize_request(
874878
self,
875879
rv: ft.ResponseReturnValue | HTTPException,
880+
ctx: RequestContext,
876881
from_error_handler: bool = False,
877882
) -> Response:
878883
"""Given the return value from a view function this finalizes
@@ -889,7 +894,7 @@ def finalize_request(
889894
"""
890895
response = self.make_response(rv)
891896
try:
892-
response = self.process_response(response)
897+
response = self.process_response(response, ctx)
893898
request_finished.send(
894899
self, _async_wrapper=self.ensure_sync, response=response
895900
)
@@ -1216,7 +1221,7 @@ def make_response(self, rv: ft.ResponseReturnValue) -> Response:
12161221

12171222
return rv
12181223

1219-
def preprocess_request(self) -> ft.ResponseReturnValue | None:
1224+
def preprocess_request(self, ctx: RequestContext) -> ft.ResponseReturnValue | None:
12201225
"""Called before the request is dispatched. Calls
12211226
:attr:`url_value_preprocessors` registered with the app and the
12221227
current blueprint (if any). Then calls :attr:`before_request_funcs`
@@ -1226,12 +1231,12 @@ def preprocess_request(self) -> ft.ResponseReturnValue | None:
12261231
value is handled as if it was the return value from the view, and
12271232
further request handling is stopped.
12281233
"""
1229-
names = (None, *reversed(request.blueprints))
1234+
names = (None, *reversed(ctx.request.blueprints))
12301235

12311236
for name in names:
12321237
if name in self.url_value_preprocessors:
12331238
for url_func in self.url_value_preprocessors[name]:
1234-
url_func(request.endpoint, request.view_args)
1239+
url_func(ctx.request.endpoint, ctx.request.view_args)
12351240

12361241
for name in names:
12371242
if name in self.before_request_funcs:
@@ -1243,7 +1248,7 @@ def preprocess_request(self) -> ft.ResponseReturnValue | None:
12431248

12441249
return None
12451250

1246-
def process_response(self, response: Response) -> Response:
1251+
def process_response(self, response: Response, ctx: RequestContext) -> Response:
12471252
"""Can be overridden in order to modify the response object
12481253
before it's sent to the WSGI server. By default this will
12491254
call all the :meth:`after_request` decorated functions.
@@ -1256,23 +1261,25 @@ def process_response(self, response: Response) -> Response:
12561261
:return: a new response object or the same, has to be an
12571262
instance of :attr:`response_class`.
12581263
"""
1259-
ctx = request_ctx._get_current_object() # type: ignore[attr-defined]
1260-
12611264
for func in ctx._after_request_functions:
12621265
response = self.ensure_sync(func)(response)
12631266

1264-
for name in chain(request.blueprints, (None,)):
1267+
for name in chain(ctx.request.blueprints, (None,)):
12651268
if name in self.after_request_funcs:
12661269
for func in reversed(self.after_request_funcs[name]):
12671270
response = self.ensure_sync(func)(response)
12681271

12691272
if not self.session_interface.is_null_session(ctx.session):
1270-
self.session_interface.save_session(self, ctx.session, response)
1273+
self.session_interface.save_session(
1274+
self, ctx.session, response # type: ignore[arg-type]
1275+
)
12711276

12721277
return response
12731278

12741279
def do_teardown_request(
1275-
self, exc: BaseException | None = _sentinel # type: ignore
1280+
self,
1281+
ctx: RequestContext,
1282+
exc: BaseException | None = _sentinel, # type: ignore
12761283
) -> None:
12771284
"""Called after the request is dispatched and the response is
12781285
returned, right before the request context is popped.
@@ -1297,7 +1304,7 @@ def do_teardown_request(
12971304
if exc is _sentinel:
12981305
exc = sys.exc_info()[1]
12991306

1300-
for name in chain(request.blueprints, (None,)):
1307+
for name in chain(ctx.request.blueprints, (None,)):
13011308
if name in self.teardown_request_funcs:
13021309
for func in reversed(self.teardown_request_funcs[name]):
13031310
self.ensure_sync(func)(exc)
@@ -1452,10 +1459,10 @@ def wsgi_app(self, environ: dict, start_response: t.Callable) -> t.Any:
14521459
try:
14531460
try:
14541461
ctx.push()
1455-
response = self.full_dispatch_request()
1462+
response = self.full_dispatch_request(ctx)
14561463
except Exception as e:
14571464
error = e
1458-
response = self.handle_exception(e)
1465+
response = self.handle_exception(e, ctx)
14591466
except: # noqa: B001
14601467
error = sys.exc_info()[1]
14611468
raise

src/flask/ctx.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore
398398
if clear_request:
399399
if exc is _sentinel:
400400
exc = sys.exc_info()[1]
401-
self.app.do_teardown_request(exc)
401+
self.app.do_teardown_request(self, exc)
402402

403403
request_close = getattr(self.request, "close", None)
404404
if request_close is not None:

tests/test_reqctx.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -288,8 +288,8 @@ def test_bad_environ_raises_bad_request():
288288
# use a non-printable character in the Host - this is key to this test
289289
environ["HTTP_HOST"] = "\x8a"
290290

291-
with app.request_context(environ):
292-
response = app.full_dispatch_request()
291+
with app.request_context(environ) as ctx:
292+
response = app.full_dispatch_request(ctx)
293293
assert response.status_code == 400
294294

295295

@@ -308,8 +308,8 @@ def index():
308308
# these characters are all IDNA-compatible
309309
environ["HTTP_HOST"] = "ąśźäüжŠßя.com"
310310

311-
with app.request_context(environ):
312-
response = app.full_dispatch_request()
311+
with app.request_context(environ) as ctx:
312+
response = app.full_dispatch_request(ctx)
313313

314314
assert response.status_code == 200
315315

tests/test_subclassing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
def test_suppressed_exception_logging():
77
class SuppressedFlask(flask.Flask):
8-
def log_exception(self, exc_info):
8+
def log_exception(self, exc_info, ctx):
99
pass
1010

1111
out = StringIO()

0 commit comments

Comments
 (0)