1
+ import contextvars
1
2
import sys
2
3
import typing as t
3
4
from functools import update_wrapper
7
8
8
9
from . import typing as ft
9
10
from .globals import _app_ctx_stack
11
+ from .globals import _cv_app
12
+ from .globals import _cv_req
10
13
from .globals import _request_ctx_stack
11
14
from .signals import appcontext_popped
12
15
from .signals import appcontext_pushed
@@ -212,7 +215,7 @@ def __init__(self, username, remote_addr=None):
212
215
213
216
.. versionadded:: 0.7
214
217
"""
215
- return _request_ctx_stack . top is not None
218
+ return _cv_app . get ( None ) is not None
216
219
217
220
218
221
def has_app_context () -> bool :
@@ -222,7 +225,7 @@ def has_app_context() -> bool:
222
225
223
226
.. versionadded:: 0.9
224
227
"""
225
- return _app_ctx_stack . top is not None
228
+ return _cv_req . get ( None ) is not None
226
229
227
230
228
231
class AppContext :
@@ -238,28 +241,29 @@ def __init__(self, app: "Flask") -> None:
238
241
self .app = app
239
242
self .url_adapter = app .create_url_adapter (None )
240
243
self .g = app .app_ctx_globals_class ()
241
-
242
- # Like request context, app contexts can be pushed multiple times
243
- # but there a basic "refcount" is enough to track them.
244
- self ._refcnt = 0
244
+ self ._cv_tokens : t .List [contextvars .Token ] = []
245
245
246
246
def push (self ) -> None :
247
247
"""Binds the app context to the current context."""
248
- self ._refcnt += 1
249
- _app_ctx_stack .push (self )
248
+ self ._cv_tokens .append (_cv_app .set (self ))
250
249
appcontext_pushed .send (self .app )
251
250
252
251
def pop (self , exc : t .Optional [BaseException ] = _sentinel ) -> None : # type: ignore
253
252
"""Pops the app context."""
254
253
try :
255
- self ._refcnt -= 1
256
- if self ._refcnt <= 0 :
254
+ if len (self ._cv_tokens ) == 1 :
257
255
if exc is _sentinel :
258
256
exc = sys .exc_info ()[1 ]
259
257
self .app .do_teardown_appcontext (exc )
260
258
finally :
261
- rv = _app_ctx_stack .pop ()
262
- assert rv is self , f"Popped wrong app context. ({ rv !r} instead of { self !r} )"
259
+ ctx = _cv_app .get ()
260
+ _cv_app .reset (self ._cv_tokens .pop ())
261
+
262
+ if ctx is not self :
263
+ raise AssertionError (
264
+ f"Popped wrong app context. ({ ctx !r} instead of { self !r} )"
265
+ )
266
+
263
267
appcontext_popped .send (self .app )
264
268
265
269
def __enter__ (self ) -> "AppContext" :
@@ -315,18 +319,13 @@ def __init__(
315
319
self .request .routing_exception = e
316
320
self .flashes = None
317
321
self .session = session
318
-
319
- # Request contexts can be pushed multiple times and interleaved with
320
- # other request contexts. Now only if the last level is popped we
321
- # get rid of them. Additionally if an application context is missing
322
- # one is created implicitly so for each level we add this information
323
- self ._implicit_app_ctx_stack : t .List [t .Optional ["AppContext" ]] = []
324
-
325
322
# Functions that should be executed after the request on the response
326
323
# object. These will be called before the regular "after_request"
327
324
# functions.
328
325
self ._after_request_functions : t .List [ft .AfterRequestCallable ] = []
329
326
327
+ self ._cv_tokens : t .List [t .Tuple [contextvars .Token , t .Optional [AppContext ]]] = []
328
+
330
329
def copy (self ) -> "RequestContext" :
331
330
"""Creates a copy of this request context with the same request object.
332
331
This can be used to move a request context to a different greenlet.
@@ -360,15 +359,15 @@ def match_request(self) -> None:
360
359
def push (self ) -> None :
361
360
# Before we push the request context we have to ensure that there
362
361
# is an application context.
363
- app_ctx = _app_ctx_stack .top
364
- if app_ctx is None or app_ctx .app != self .app :
362
+ app_ctx = _cv_app .get (None )
363
+
364
+ if app_ctx is None or app_ctx .app is not self .app :
365
365
app_ctx = self .app .app_context ()
366
366
app_ctx .push ()
367
- self ._implicit_app_ctx_stack .append (app_ctx )
368
367
else :
369
- self . _implicit_app_ctx_stack . append ( None )
368
+ app_ctx = None
370
369
371
- _request_ctx_stack . push ( self )
370
+ self . _cv_tokens . append (( _cv_req . set ( self ), app_ctx ) )
372
371
373
372
# Open the session at the moment that the request context is available.
374
373
# This allows a custom open_session method to use the request context.
@@ -394,48 +393,34 @@ def pop(self, exc: t.Optional[BaseException] = _sentinel) -> None: # type: igno
394
393
.. versionchanged:: 0.9
395
394
Added the `exc` argument.
396
395
"""
397
- app_ctx = self ._implicit_app_ctx_stack .pop ()
398
- clear_request = False
396
+ clear_request = len (self ._cv_tokens ) == 1
399
397
400
398
try :
401
- if not self . _implicit_app_ctx_stack :
399
+ if clear_request :
402
400
if exc is _sentinel :
403
401
exc = sys .exc_info ()[1 ]
404
402
self .app .do_teardown_request (exc )
405
403
406
404
request_close = getattr (self .request , "close" , None )
407
405
if request_close is not None :
408
406
request_close ()
409
- clear_request = True
410
407
finally :
411
- rv = _request_ctx_stack .pop ()
408
+ ctx = _cv_req .get ()
409
+ token , app_ctx = self ._cv_tokens .pop ()
410
+ _cv_req .reset (token )
412
411
413
412
# get rid of circular dependencies at the end of the request
414
413
# so that we don't require the GC to be active.
415
414
if clear_request :
416
- rv .request .environ ["werkzeug.request" ] = None
415
+ ctx .request .environ ["werkzeug.request" ] = None
417
416
418
- # Get rid of the app as well if necessary.
419
417
if app_ctx is not None :
420
418
app_ctx .pop (exc )
421
419
422
- assert (
423
- rv is self
424
- ), f"Popped wrong request context. ({ rv !r} instead of { self !r} )"
425
-
426
- def auto_pop (self , exc : t .Optional [BaseException ]) -> None :
427
- """
428
- .. deprecated:: 2.2
429
- Will be removed in Flask 2.3.
430
- """
431
- import warnings
432
-
433
- warnings .warn (
434
- "'ctx.auto_pop' is deprecated and will be removed in Flask 2.3." ,
435
- DeprecationWarning ,
436
- stacklevel = 2 ,
437
- )
438
- self .pop (exc )
420
+ if ctx is not self :
421
+ raise AssertionError (
422
+ f"Popped wrong request context. ({ ctx !r} instead of { self !r} )"
423
+ )
439
424
440
425
def __enter__ (self ) -> "RequestContext" :
441
426
self .push ()
0 commit comments