11from __future__ import annotations
22
33import collections .abc as cabc
4+ import inspect
45import os
56import sys
67import typing as t
78import weakref
89from datetime import timedelta
10+ from functools import update_wrapper
911from inspect import iscoroutinefunction
1012from itertools import chain
1113from types import TracebackType
3032from . import typing as ft
3133from .ctx import AppContext
3234from .globals import _cv_app
35+ from .globals import app_ctx
3336from .globals import g
3437from .globals import request
3538from .globals import session
@@ -73,6 +76,35 @@ def _make_timedelta(value: timedelta | int | None) -> timedelta | None:
7376 return timedelta (seconds = value )
7477
7578
79+ F = t .TypeVar ("F" , bound = t .Callable [..., t .Any ])
80+
81+
82+ # Other methods may call the overridden method with the new ctx arg. Remove it
83+ # and call the method with the remaining args.
84+ def remove_ctx (f : F ) -> F :
85+ def wrapper (self : Flask , * args : t .Any , ** kwargs : t .Any ) -> t .Any :
86+ if args and isinstance (args [0 ], AppContext ):
87+ args = args [1 :]
88+
89+ return f (self , * args , ** kwargs )
90+
91+ return update_wrapper (wrapper , f ) # type: ignore[return-value]
92+
93+
94+ # The overridden method may call super().base_method without the new ctx arg.
95+ # Add it to the args for the call.
96+ def add_ctx (f : F ) -> F :
97+ def wrapper (self : Flask , * args : t .Any , ** kwargs : t .Any ) -> t .Any :
98+ if not args :
99+ args = (app_ctx ._get_current_object (),)
100+ elif not isinstance (args [0 ], AppContext ):
101+ args = (app_ctx ._get_current_object (), * args )
102+
103+ return f (self , * args , ** kwargs )
104+
105+ return update_wrapper (wrapper , f ) # type: ignore[return-value]
106+
107+
76108class Flask (App ):
77109 """The flask object implements a WSGI application and acts as the central
78110 object. It is passed the name of the module or package of the
@@ -218,6 +250,62 @@ class Flask(App):
218250 #: .. versionadded:: 0.8
219251 session_interface : SessionInterface = SecureCookieSessionInterface ()
220252
253+ def __init_subclass__ (cls , ** kwargs : t .Any ) -> None :
254+ import warnings
255+
256+ # These method signatures were updated to take a ctx param. Detect
257+ # overridden methods in subclasses that still have the old signature.
258+ # Show a deprecation warning and wrap to call with correct args.
259+ for method in (
260+ cls .handle_http_exception ,
261+ cls .handle_user_exception ,
262+ cls .handle_exception ,
263+ cls .log_exception ,
264+ cls .dispatch_request ,
265+ cls .full_dispatch_request ,
266+ cls .finalize_request ,
267+ cls .make_default_options_response ,
268+ cls .preprocess_request ,
269+ cls .process_response ,
270+ cls .do_teardown_request ,
271+ cls .do_teardown_appcontext ,
272+ ):
273+ base_method = getattr (Flask , method .__name__ )
274+
275+ if method is base_method :
276+ # not overridden
277+ continue
278+
279+ # get the second parameter (first is self)
280+ iter_params = iter (inspect .signature (method ).parameters .values ())
281+ next (iter_params )
282+ param = next (iter_params , None )
283+
284+ # must have second parameter named ctx or annotated AppContext
285+ if param is None or not (
286+ # no annotation, match name
287+ (param .annotation is inspect .Parameter .empty and param .name == "ctx" )
288+ or (
289+ # string annotation, access path ends with AppContext
290+ isinstance (param .annotation , str )
291+ and param .annotation .rpartition ("." )[2 ] == "AppContext"
292+ )
293+ or (
294+ # class annotation
295+ inspect .isclass (param .annotation )
296+ and issubclass (param .annotation , AppContext )
297+ )
298+ ):
299+ warnings .warn (
300+ f"The '{ method .__name__ } ' method now takes 'ctx: AppContext'"
301+ " as the first parameter. The old signature is deprecated"
302+ " and will not be supported in Flask 4.0." ,
303+ DeprecationWarning ,
304+ stacklevel = 2 ,
305+ )
306+ setattr (cls , method .__name__ , remove_ctx (method ))
307+ setattr (Flask , method .__name__ , add_ctx (base_method ))
308+
221309 def __init__ (
222310 self ,
223311 import_name : str ,
@@ -498,7 +586,9 @@ def raise_routing_exception(self, request: Request) -> t.NoReturn:
498586
499587 raise FormDataRoutingRedirect (request )
500588
501- def update_template_context (self , context : dict [str , t .Any ]) -> None :
589+ def update_template_context (
590+ self , ctx : AppContext , context : dict [str , t .Any ]
591+ ) -> None :
502592 """Update the template context with some commonly used variables.
503593 This injects request, session, config and g into the template
504594 context as well as everything template context processors want
@@ -512,7 +602,7 @@ def update_template_context(self, context: dict[str, t.Any]) -> None:
512602 names : t .Iterable [str | None ] = (None ,)
513603
514604 # A template may be rendered outside a request context.
515- if ( ctx := _cv_app . get ( None )) is not None and ctx .has_request :
605+ if ctx .has_request :
516606 names = chain (names , reversed (ctx .request .blueprints ))
517607
518608 # The values passed to render_template take precedence. Keep a
@@ -737,7 +827,7 @@ def test_cli_runner(self, **kwargs: t.Any) -> FlaskCliRunner:
737827 return cls (self , ** kwargs ) # type: ignore
738828
739829 def handle_http_exception (
740- self , e : HTTPException
830+ self , ctx : AppContext , e : HTTPException
741831 ) -> HTTPException | ft .ResponseReturnValue :
742832 """Handles an HTTP exception. By default this will invoke the
743833 registered error handlers and fall back to returning the
@@ -766,13 +856,13 @@ def handle_http_exception(
766856 if isinstance (e , RoutingException ):
767857 return e
768858
769- handler = self ._find_error_handler (e , request .blueprints )
859+ handler = self ._find_error_handler (e , ctx . request .blueprints )
770860 if handler is None :
771861 return e
772862 return self .ensure_sync (handler )(e ) # type: ignore[no-any-return]
773863
774864 def handle_user_exception (
775- self , e : Exception
865+ self , ctx : AppContext , e : Exception
776866 ) -> HTTPException | ft .ResponseReturnValue :
777867 """This method is called whenever an exception occurs that
778868 should be handled. A special case is :class:`~werkzeug
@@ -794,16 +884,16 @@ def handle_user_exception(
794884 e .show_exception = True
795885
796886 if isinstance (e , HTTPException ) and not self .trap_http_exception (e ):
797- return self .handle_http_exception (e )
887+ return self .handle_http_exception (ctx , e )
798888
799- handler = self ._find_error_handler (e , request .blueprints )
889+ handler = self ._find_error_handler (e , ctx . request .blueprints )
800890
801891 if handler is None :
802892 raise
803893
804894 return self .ensure_sync (handler )(e ) # type: ignore[no-any-return]
805895
806- def handle_exception (self , e : Exception ) -> Response :
896+ def handle_exception (self , ctx : AppContext , e : Exception ) -> Response :
807897 """Handle an exception that did not have an error handler
808898 associated with it, or that was raised from an error handler.
809899 This always causes a 500 ``InternalServerError``.
@@ -846,19 +936,20 @@ def handle_exception(self, e: Exception) -> Response:
846936
847937 raise e
848938
849- self .log_exception (exc_info )
939+ self .log_exception (ctx , exc_info )
850940 server_error : InternalServerError | ft .ResponseReturnValue
851941 server_error = InternalServerError (original_exception = e )
852- handler = self ._find_error_handler (server_error , request .blueprints )
942+ handler = self ._find_error_handler (server_error , ctx . request .blueprints )
853943
854944 if handler is not None :
855945 server_error = self .ensure_sync (handler )(server_error )
856946
857- return self .finalize_request (server_error , from_error_handler = True )
947+ return self .finalize_request (ctx , server_error , from_error_handler = True )
858948
859949 def log_exception (
860950 self ,
861- exc_info : (tuple [type , BaseException , TracebackType ] | tuple [None , None , None ]),
951+ ctx : AppContext ,
952+ exc_info : tuple [type , BaseException , TracebackType ] | tuple [None , None , None ],
862953 ) -> None :
863954 """Logs an exception. This is called by :meth:`handle_exception`
864955 if debugging is disabled and right before the handler is called.
@@ -868,10 +959,10 @@ def log_exception(
868959 .. versionadded:: 0.8
869960 """
870961 self .logger .error (
871- f"Exception on { request .path } [{ request .method } ]" , exc_info = exc_info
962+ f"Exception on { ctx . request .path } [{ ctx . request .method } ]" , exc_info = exc_info
872963 )
873964
874- def dispatch_request (self ) -> ft .ResponseReturnValue :
965+ def dispatch_request (self , ctx : AppContext ) -> ft .ResponseReturnValue :
875966 """Does the request dispatching. Matches the URL and returns the
876967 return value of the view or error handler. This does not have to
877968 be a response object. In order to convert the return value to a
@@ -881,7 +972,7 @@ def dispatch_request(self) -> ft.ResponseReturnValue:
881972 This no longer does the exception handling, this code was
882973 moved to the new :meth:`full_dispatch_request`.
883974 """
884- req = _cv_app . get () .request
975+ req = ctx .request
885976
886977 if req .routing_exception is not None :
887978 self .raise_routing_exception (req )
@@ -892,12 +983,12 @@ def dispatch_request(self) -> ft.ResponseReturnValue:
892983 getattr (rule , "provide_automatic_options" , False )
893984 and req .method == "OPTIONS"
894985 ):
895- return self .make_default_options_response ()
986+ return self .make_default_options_response (ctx )
896987 # otherwise dispatch to the handler for that endpoint
897988 view_args : dict [str , t .Any ] = req .view_args # type: ignore[assignment]
898989 return self .ensure_sync (self .view_functions [rule .endpoint ])(** view_args ) # type: ignore[no-any-return]
899990
900- def full_dispatch_request (self ) -> Response :
991+ def full_dispatch_request (self , ctx : AppContext ) -> Response :
901992 """Dispatches the request and on top of that performs request
902993 pre and postprocessing as well as HTTP exception catching and
903994 error handling.
@@ -908,15 +999,16 @@ def full_dispatch_request(self) -> Response:
908999
9091000 try :
9101001 request_started .send (self , _async_wrapper = self .ensure_sync )
911- rv = self .preprocess_request ()
1002+ rv = self .preprocess_request (ctx )
9121003 if rv is None :
913- rv = self .dispatch_request ()
1004+ rv = self .dispatch_request (ctx )
9141005 except Exception as e :
915- rv = self .handle_user_exception (e )
916- return self .finalize_request (rv )
1006+ rv = self .handle_user_exception (ctx , e )
1007+ return self .finalize_request (ctx , rv )
9171008
9181009 def finalize_request (
9191010 self ,
1011+ ctx : AppContext ,
9201012 rv : ft .ResponseReturnValue | HTTPException ,
9211013 from_error_handler : bool = False ,
9221014 ) -> Response :
@@ -934,7 +1026,7 @@ def finalize_request(
9341026 """
9351027 response = self .make_response (rv )
9361028 try :
937- response = self .process_response (response )
1029+ response = self .process_response (ctx , response )
9381030 request_finished .send (
9391031 self , _async_wrapper = self .ensure_sync , response = response
9401032 )
@@ -946,15 +1038,14 @@ def finalize_request(
9461038 )
9471039 return response
9481040
949- def make_default_options_response (self ) -> Response :
1041+ def make_default_options_response (self , ctx : AppContext ) -> Response :
9501042 """This method is called to create the default ``OPTIONS`` response.
9511043 This can be changed through subclassing to change the default
9521044 behavior of ``OPTIONS`` responses.
9531045
9541046 .. versionadded:: 0.7
9551047 """
956- adapter = _cv_app .get ().url_adapter
957- methods = adapter .allowed_methods () # type: ignore[union-attr]
1048+ methods = ctx .url_adapter .allowed_methods () # type: ignore[union-attr]
9581049 rv = self .response_class ()
9591050 rv .allow .update (methods )
9601051 return rv
@@ -1260,7 +1351,7 @@ def make_response(self, rv: ft.ResponseReturnValue) -> Response:
12601351
12611352 return rv
12621353
1263- def preprocess_request (self ) -> ft .ResponseReturnValue | None :
1354+ def preprocess_request (self , ctx : AppContext ) -> ft .ResponseReturnValue | None :
12641355 """Called before the request is dispatched. Calls
12651356 :attr:`url_value_preprocessors` registered with the app and the
12661357 current blueprint (if any). Then calls :attr:`before_request_funcs`
@@ -1270,7 +1361,7 @@ def preprocess_request(self) -> ft.ResponseReturnValue | None:
12701361 value is handled as if it was the return value from the view, and
12711362 further request handling is stopped.
12721363 """
1273- req = _cv_app . get () .request
1364+ req = ctx .request
12741365 names = (None , * reversed (req .blueprints ))
12751366
12761367 for name in names :
@@ -1288,7 +1379,7 @@ def preprocess_request(self) -> ft.ResponseReturnValue | None:
12881379
12891380 return None
12901381
1291- def process_response (self , response : Response ) -> Response :
1382+ def process_response (self , ctx : AppContext , response : Response ) -> Response :
12921383 """Can be overridden in order to modify the response object
12931384 before it's sent to the WSGI server. By default this will
12941385 call all the :meth:`after_request` decorated functions.
@@ -1301,8 +1392,6 @@ def process_response(self, response: Response) -> Response:
13011392 :return: a new response object or the same, has to be an
13021393 instance of :attr:`response_class`.
13031394 """
1304- ctx = _cv_app .get ()
1305-
13061395 for func in ctx ._after_request_functions :
13071396 response = self .ensure_sync (func )(response )
13081397
@@ -1316,7 +1405,9 @@ def process_response(self, response: Response) -> Response:
13161405
13171406 return response
13181407
1319- def do_teardown_request (self , exc : BaseException | None = None ) -> None :
1408+ def do_teardown_request (
1409+ self , ctx : AppContext , exc : BaseException | None = None
1410+ ) -> None :
13201411 """Called after the request is dispatched and the response is finalized,
13211412 right before the request context is popped. Called by
13221413 :meth:`.AppContext.pop`.
@@ -1331,16 +1422,16 @@ def do_teardown_request(self, exc: BaseException | None = None) -> None:
13311422 .. versionchanged:: 0.9
13321423 Added the ``exc`` argument.
13331424 """
1334- req = _cv_app .get ().request
1335-
1336- for name in chain (req .blueprints , (None ,)):
1425+ for name in chain (ctx .request .blueprints , (None ,)):
13371426 if name in self .teardown_request_funcs :
13381427 for func in reversed (self .teardown_request_funcs [name ]):
13391428 self .ensure_sync (func )(exc )
13401429
13411430 request_tearing_down .send (self , _async_wrapper = self .ensure_sync , exc = exc )
13421431
1343- def do_teardown_appcontext (self , exc : BaseException | None = None ) -> None :
1432+ def do_teardown_appcontext (
1433+ self , ctx : AppContext , exc : BaseException | None = None
1434+ ) -> None :
13441435 """Called right before the application context is popped. Called by
13451436 :meth:`.AppContext.pop`.
13461437
@@ -1473,17 +1564,17 @@ def wsgi_app(
14731564 try :
14741565 try :
14751566 ctx .push ()
1476- response = self .full_dispatch_request ()
1567+ response = self .full_dispatch_request (ctx )
14771568 except Exception as e :
14781569 error = e
1479- response = self .handle_exception (e )
1570+ response = self .handle_exception (ctx , e )
14801571 except : # noqa: B001
14811572 error = sys .exc_info ()[1 ]
14821573 raise
14831574 return response (environ , start_response )
14841575 finally :
14851576 if "werkzeug.debug.preserve_context" in environ :
1486- environ ["werkzeug.debug.preserve_context" ](_cv_app . get () )
1577+ environ ["werkzeug.debug.preserve_context" ](ctx )
14871578
14881579 if error is not None and self .should_ignore_error (error ):
14891580 error = None
0 commit comments