Skip to content

Commit 82c2e03

Browse files
committed
remove uses of LocalStack
1 parent d597db6 commit 82c2e03

13 files changed

+115
-132
lines changed

src/flask/app.py

+15-13
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,11 @@
3838
from .ctx import _AppCtxGlobals
3939
from .ctx import AppContext
4040
from .ctx import RequestContext
41-
from .globals import _app_ctx_stack
42-
from .globals import _request_ctx_stack
41+
from .globals import _cv_app
42+
from .globals import _cv_req
4343
from .globals import g
4444
from .globals import request
45+
from .globals import request_ctx
4546
from .globals import session
4647
from .helpers import _split_blueprint_path
4748
from .helpers import get_debug_flag
@@ -1554,10 +1555,10 @@ def dispatch_request(self) -> ft.ResponseReturnValue:
15541555
This no longer does the exception handling, this code was
15551556
moved to the new :meth:`full_dispatch_request`.
15561557
"""
1557-
req = _request_ctx_stack.top.request
1558+
req = request_ctx.request
15581559
if req.routing_exception is not None:
15591560
self.raise_routing_exception(req)
1560-
rule = req.url_rule
1561+
rule: Rule = req.url_rule # type: ignore[assignment]
15611562
# if we provide automatic options for this URL and the
15621563
# request came with the OPTIONS method, reply automatically
15631564
if (
@@ -1566,7 +1567,8 @@ def dispatch_request(self) -> ft.ResponseReturnValue:
15661567
):
15671568
return self.make_default_options_response()
15681569
# otherwise dispatch to the handler for that endpoint
1569-
return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
1570+
view_args: t.Dict[str, t.Any] = req.view_args # type: ignore[assignment]
1571+
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
15701572

15711573
def full_dispatch_request(self) -> Response:
15721574
"""Dispatches the request and on top of that performs request
@@ -1631,8 +1633,8 @@ def make_default_options_response(self) -> Response:
16311633
16321634
.. versionadded:: 0.7
16331635
"""
1634-
adapter = _request_ctx_stack.top.url_adapter
1635-
methods = adapter.allowed_methods()
1636+
adapter = request_ctx.url_adapter
1637+
methods = adapter.allowed_methods() # type: ignore[union-attr]
16361638
rv = self.response_class()
16371639
rv.allow.update(methods)
16381640
return rv
@@ -1740,7 +1742,7 @@ def url_for(
17401742
.. versionadded:: 2.2
17411743
Moved from ``flask.url_for``, which calls this method.
17421744
"""
1743-
req_ctx = _request_ctx_stack.top
1745+
req_ctx = _cv_req.get(None)
17441746

17451747
if req_ctx is not None:
17461748
url_adapter = req_ctx.url_adapter
@@ -1759,7 +1761,7 @@ def url_for(
17591761
if _external is None:
17601762
_external = _scheme is not None
17611763
else:
1762-
app_ctx = _app_ctx_stack.top
1764+
app_ctx = _cv_app.get(None)
17631765

17641766
# If called by helpers.url_for, an app context is active,
17651767
# use its url_adapter. Otherwise, app.url_for was called
@@ -1790,7 +1792,7 @@ def url_for(
17901792
self.inject_url_defaults(endpoint, values)
17911793

17921794
try:
1793-
rv = url_adapter.build(
1795+
rv = url_adapter.build( # type: ignore[union-attr]
17941796
endpoint,
17951797
values,
17961798
method=_method,
@@ -2099,7 +2101,7 @@ def process_response(self, response: Response) -> Response:
20992101
:return: a new response object or the same, has to be an
21002102
instance of :attr:`response_class`.
21012103
"""
2102-
ctx = _request_ctx_stack.top
2104+
ctx = request_ctx._get_current_object() # type: ignore[attr-defined]
21032105

21042106
for func in ctx._after_request_functions:
21052107
response = self.ensure_sync(func)(response)
@@ -2305,8 +2307,8 @@ def wsgi_app(self, environ: dict, start_response: t.Callable) -> t.Any:
23052307
return response(environ, start_response)
23062308
finally:
23072309
if "werkzeug.debug.preserve_context" in environ:
2308-
environ["werkzeug.debug.preserve_context"](_app_ctx_stack.top)
2309-
environ["werkzeug.debug.preserve_context"](_request_ctx_stack.top)
2310+
environ["werkzeug.debug.preserve_context"](_cv_app.get())
2311+
environ["werkzeug.debug.preserve_context"](_cv_req.get())
23102312

23112313
if error is not None and self.should_ignore_error(error):
23122314
error = None

src/flask/cli.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -1051,13 +1051,11 @@ def shell_command() -> None:
10511051
without having to manually configure the application.
10521052
"""
10531053
import code
1054-
from .globals import _app_ctx_stack
10551054

1056-
app = _app_ctx_stack.top.app
10571055
banner = (
10581056
f"Python {sys.version} on {sys.platform}\n"
1059-
f"App: {app.import_name} [{app.env}]\n"
1060-
f"Instance: {app.instance_path}"
1057+
f"App: {current_app.import_name} [{current_app.env}]\n"
1058+
f"Instance: {current_app.instance_path}"
10611059
)
10621060
ctx: dict = {}
10631061

@@ -1068,7 +1066,7 @@ def shell_command() -> None:
10681066
with open(startup) as f:
10691067
eval(compile(f.read(), startup, "exec"), ctx)
10701068

1071-
ctx.update(app.make_shell_context())
1069+
ctx.update(current_app.make_shell_context())
10721070

10731071
# Site, customize, or startup script can set a hook to call when
10741072
# entering interactive mode. The default one sets up readline with

src/flask/ctx.py

+19-21
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@
77
from werkzeug.exceptions import HTTPException
88

99
from . import typing as ft
10-
from .globals import _app_ctx_stack
1110
from .globals import _cv_app
1211
from .globals import _cv_req
13-
from .globals import _request_ctx_stack
1412
from .signals import appcontext_popped
1513
from .signals import appcontext_pushed
1614

@@ -106,9 +104,9 @@ def __iter__(self) -> t.Iterator[str]:
106104
return iter(self.__dict__)
107105

108106
def __repr__(self) -> str:
109-
top = _app_ctx_stack.top
110-
if top is not None:
111-
return f"<flask.g of {top.app.name!r}>"
107+
ctx = _cv_app.get(None)
108+
if ctx is not None:
109+
return f"<flask.g of '{ctx.app.name}'>"
112110
return object.__repr__(self)
113111

114112

@@ -133,15 +131,15 @@ def add_header(response):
133131
134132
.. versionadded:: 0.9
135133
"""
136-
top = _request_ctx_stack.top
134+
ctx = _cv_req.get(None)
137135

138-
if top is None:
136+
if ctx is None:
139137
raise RuntimeError(
140-
"This decorator can only be used when a request context is"
141-
" active, such as within a view function."
138+
"'after_this_request' can only be used when a request"
139+
" context is active, such as in a view function."
142140
)
143141

144-
top._after_request_functions.append(f)
142+
ctx._after_request_functions.append(f)
145143
return f
146144

147145

@@ -169,19 +167,19 @@ def do_some_work():
169167
170168
.. versionadded:: 0.10
171169
"""
172-
top = _request_ctx_stack.top
170+
ctx = _cv_req.get(None)
173171

174-
if top is None:
172+
if ctx is None:
175173
raise RuntimeError(
176-
"This decorator can only be used when a request context is"
177-
" active, such as within a view function."
174+
"'copy_current_request_context' can only be used when a"
175+
" request context is active, such as in a view function."
178176
)
179177

180-
reqctx = top.copy()
178+
ctx = ctx.copy()
181179

182180
def wrapper(*args, **kwargs):
183-
with reqctx:
184-
return reqctx.app.ensure_sync(f)(*args, **kwargs)
181+
with ctx:
182+
return ctx.app.ensure_sync(f)(*args, **kwargs)
185183

186184
return update_wrapper(wrapper, f)
187185

@@ -240,7 +238,7 @@ class AppContext:
240238
def __init__(self, app: "Flask") -> None:
241239
self.app = app
242240
self.url_adapter = app.create_url_adapter(None)
243-
self.g = app.app_ctx_globals_class()
241+
self.g: _AppCtxGlobals = app.app_ctx_globals_class()
244242
self._cv_tokens: t.List[contextvars.Token] = []
245243

246244
def push(self) -> None:
@@ -311,14 +309,14 @@ def __init__(
311309
self.app = app
312310
if request is None:
313311
request = app.request_class(environ)
314-
self.request = request
312+
self.request: Request = request
315313
self.url_adapter = None
316314
try:
317315
self.url_adapter = app.create_url_adapter(self.request)
318316
except HTTPException as e:
319317
self.request.routing_exception = e
320-
self.flashes = None
321-
self.session = session
318+
self.flashes: t.Optional[t.List[t.Tuple[str, str]]] = None
319+
self.session: t.Optional["SessionMixin"] = session
322320
# Functions that should be executed after the request on the response
323321
# object. These will be called before the regular "after_request"
324322
# functions.

src/flask/debughelpers.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from .app import Flask
44
from .blueprints import Blueprint
5-
from .globals import _request_ctx_stack
5+
from .globals import request_ctx
66

77

88
class UnexpectedUnicodeError(AssertionError, UnicodeError):
@@ -116,9 +116,8 @@ def explain_template_loading_attempts(app: Flask, template, attempts) -> None:
116116
info = [f"Locating template {template!r}:"]
117117
total_found = 0
118118
blueprint = None
119-
reqctx = _request_ctx_stack.top
120-
if reqctx is not None and reqctx.request.blueprint is not None:
121-
blueprint = reqctx.request.blueprint
119+
if request_ctx and request_ctx.request.blueprint is not None:
120+
blueprint = request_ctx.request.blueprint
122121

123122
for idx, (loader, srcobj, triple) in enumerate(attempts):
124123
if isinstance(srcobj, Flask):

src/flask/helpers.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212
from werkzeug.exceptions import abort as _wz_abort
1313
from werkzeug.utils import redirect as _wz_redirect
1414

15-
from .globals import _request_ctx_stack
15+
from .globals import _cv_req
1616
from .globals import current_app
1717
from .globals import request
18+
from .globals import request_ctx
1819
from .globals import session
1920
from .signals import message_flashed
2021

@@ -110,11 +111,11 @@ def decorator(*args: t.Any, **kwargs: t.Any) -> t.Any:
110111
return update_wrapper(decorator, generator_or_function) # type: ignore
111112

112113
def generator() -> t.Generator:
113-
ctx = _request_ctx_stack.top
114+
ctx = _cv_req.get(None)
114115
if ctx is None:
115116
raise RuntimeError(
116-
"Attempted to stream with context but "
117-
"there was no context in the first place to keep around."
117+
"'stream_with_context' can only be used when a request"
118+
" context is active, such as in a view function."
118119
)
119120
with ctx:
120121
# Dummy sentinel. Has to be inside the context block or we're
@@ -377,11 +378,10 @@ def get_flashed_messages(
377378
:param category_filter: filter of categories to limit return values. Only
378379
categories in the list will be returned.
379380
"""
380-
flashes = _request_ctx_stack.top.flashes
381+
flashes = request_ctx.flashes
381382
if flashes is None:
382-
_request_ctx_stack.top.flashes = flashes = (
383-
session.pop("_flashes") if "_flashes" in session else []
384-
)
383+
flashes = session.pop("_flashes") if "_flashes" in session else []
384+
request_ctx.flashes = flashes
385385
if category_filter:
386386
flashes = list(filter(lambda f: f[0] in category_filter, flashes))
387387
if not with_categories:

src/flask/templating.py

+21-29
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
from jinja2 import Template
66
from jinja2 import TemplateNotFound
77

8-
from .globals import _app_ctx_stack
9-
from .globals import _request_ctx_stack
8+
from .globals import _cv_app
9+
from .globals import _cv_req
1010
from .globals import current_app
1111
from .globals import request
1212
from .helpers import stream_with_context
@@ -22,9 +22,9 @@ def _default_template_ctx_processor() -> t.Dict[str, t.Any]:
2222
"""Default template context processor. Injects `request`,
2323
`session` and `g`.
2424
"""
25-
reqctx = _request_ctx_stack.top
26-
appctx = _app_ctx_stack.top
27-
rv = {}
25+
appctx = _cv_app.get(None)
26+
reqctx = _cv_req.get(None)
27+
rv: t.Dict[str, t.Any] = {}
2828
if appctx is not None:
2929
rv["g"] = appctx.g
3030
if reqctx is not None:
@@ -124,7 +124,8 @@ def list_templates(self) -> t.List[str]:
124124
return list(result)
125125

126126

127-
def _render(template: Template, context: dict, app: "Flask") -> str:
127+
def _render(app: "Flask", template: Template, context: t.Dict[str, t.Any]) -> str:
128+
app.update_template_context(context)
128129
before_render_template.send(app, template=template, context=context)
129130
rv = template.render(context)
130131
template_rendered.send(app, template=template, context=context)
@@ -135,36 +136,27 @@ def render_template(
135136
template_name_or_list: t.Union[str, Template, t.List[t.Union[str, Template]]],
136137
**context: t.Any
137138
) -> str:
138-
"""Renders a template from the template folder with the given
139-
context.
139+
"""Render a template by name with the given context.
140140
141-
:param template_name_or_list: the name of the template to be
142-
rendered, or an iterable with template names
143-
the first one existing will be rendered
144-
:param context: the variables that should be available in the
145-
context of the template.
141+
:param template_name_or_list: The name of the template to render. If
142+
a list is given, the first name to exist will be rendered.
143+
:param context: The variables to make available in the template.
146144
"""
147-
ctx = _app_ctx_stack.top
148-
ctx.app.update_template_context(context)
149-
return _render(
150-
ctx.app.jinja_env.get_or_select_template(template_name_or_list),
151-
context,
152-
ctx.app,
153-
)
145+
app = current_app._get_current_object() # type: ignore[attr-defined]
146+
template = app.jinja_env.get_or_select_template(template_name_or_list)
147+
return _render(app, template, context)
154148

155149

156150
def render_template_string(source: str, **context: t.Any) -> str:
157-
"""Renders a template from the given template source string
158-
with the given context. Template variables will be autoescaped.
151+
"""Render a template from the given source string with the given
152+
context.
159153
160-
:param source: the source code of the template to be
161-
rendered
162-
:param context: the variables that should be available in the
163-
context of the template.
154+
:param source: The source code of the template to render.
155+
:param context: The variables to make available in the template.
164156
"""
165-
ctx = _app_ctx_stack.top
166-
ctx.app.update_template_context(context)
167-
return _render(ctx.app.jinja_env.from_string(source), context, ctx.app)
157+
app = current_app._get_current_object() # type: ignore[attr-defined]
158+
template = app.jinja_env.from_string(source)
159+
return _render(app, template, context)
168160

169161

170162
def _stream(

src/flask/testing.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def session_transaction(
163163
# behavior. It's important to not use the push and pop
164164
# methods of the actual request context object since that would
165165
# mean that cleanup handlers are called
166-
token = _cv_req.set(outer_reqctx)
166+
token = _cv_req.set(outer_reqctx) # type: ignore[arg-type]
167167
try:
168168
yield sess
169169
finally:

0 commit comments

Comments
 (0)