Skip to content

Commit 06ea505

Browse files
committed
separate copy per call
1 parent 2ac8988 commit 06ea505

4 files changed

Lines changed: 38 additions & 129 deletions

File tree

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ pre-commit = [
5757
]
5858
tests = [
5959
"asgiref",
60-
"greenlet",
6160
"pytest",
6261
"python-dotenv",
6362
]

src/flask/ctx.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -189,18 +189,18 @@ def do_some_work():
189189
190190
.. versionadded:: 0.10
191191
"""
192-
ctx = _cv_app.get(None)
192+
# Store the context that was active when the decorator was applied.
193+
original = _cv_app.get(None)
193194

194-
if ctx is None:
195+
if original is None:
195196
raise RuntimeError(
196197
"'copy_current_request_context' can only be used when a"
197198
" request context is active, such as in a view function."
198199
)
199200

200-
ctx = ctx.copy()
201-
202201
def wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
203-
with ctx:
202+
# Copy the context before pushing, so each worker acts independently.
203+
with original.copy() as ctx:
204204
return ctx.app.ensure_sync(f)(*args, **kwargs)
205205

206206
return update_wrapper(wrapper, f) # type: ignore[return-value]

tests/test_reqctx.py

Lines changed: 33 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1+
from __future__ import annotations
2+
3+
import collections.abc as cabc
14
import warnings
5+
from concurrent import futures
26

37
import pytest
48

59
import flask
6-
from flask.globals import app_ctx
710
from flask.sessions import SecureCookieSessionInterface
811
from flask.sessions import SessionInterface
9-
10-
try:
11-
from greenlet import greenlet
12-
except ImportError:
13-
greenlet = None
12+
from flask.testing import FlaskClient
1413

1514

1615
def test_teardown_on_pop(app):
@@ -145,61 +144,34 @@ def index():
145144
index()
146145

147146

148-
@pytest.mark.skipif(greenlet is None, reason="greenlet not installed")
149-
class TestGreenletContextCopying:
150-
def test_greenlet_context_copying(self, app, client):
151-
greenlets = []
152-
153-
@app.route("/")
154-
def index():
155-
flask.session["fizz"] = "buzz"
156-
ctx = app_ctx.copy()
157-
158-
def g():
159-
assert not flask.request
160-
assert not flask.current_app
161-
with ctx:
162-
assert flask.request
163-
assert flask.current_app == app
164-
assert flask.request.path == "/"
165-
assert flask.request.args["foo"] == "bar"
166-
assert flask.session.get("fizz") == "buzz"
167-
assert not flask.request
168-
return 42
169-
170-
greenlets.append(greenlet(g))
171-
return "Hello World!"
172-
173-
rv = client.get("/?foo=bar")
174-
assert rv.data == b"Hello World!"
175-
176-
result = greenlets[0].run()
177-
assert result == 42
178-
179-
def test_greenlet_context_copying_api(self, app, client):
180-
greenlets = []
181-
182-
@app.route("/")
183-
def index():
184-
flask.session["fizz"] = "buzz"
185-
186-
@flask.copy_current_request_context
187-
def g():
188-
assert flask.request
189-
assert flask.current_app == app
190-
assert flask.request.path == "/"
191-
assert flask.request.args["foo"] == "bar"
192-
assert flask.session.get("fizz") == "buzz"
193-
return 42
194-
195-
greenlets.append(greenlet(g))
196-
return "Hello World!"
197-
198-
rv = client.get("/?foo=bar")
199-
assert rv.data == b"Hello World!"
200-
201-
result = greenlets[0].run()
202-
assert result == 42
147+
def test_copy_context_thread(
148+
request: pytest.FixtureRequest, app: flask.Flask, client: FlaskClient
149+
) -> None:
150+
executor = futures.ThreadPoolExecutor(max_workers=2)
151+
request.addfinalizer(lambda: executor.shutdown(cancel_futures=True))
152+
result: cabc.Iterator[int] | None = None
153+
154+
@app.route("/")
155+
def index():
156+
flask.session["fizz"] = "buzz"
157+
158+
@flask.copy_current_request_context
159+
def work(n: int) -> int:
160+
assert flask.current_app == app
161+
assert flask.request.path == "/"
162+
assert flask.request.args["foo"] == "bar"
163+
assert flask.session["fizz"] == "buzz"
164+
return n
165+
166+
nonlocal result
167+
result = executor.map(work, range(10))
168+
return "Hello World!"
169+
170+
rv = client.get(query_string={"foo": "bar"})
171+
assert rv.text == "Hello World!"
172+
173+
assert result is not None
174+
assert set(result) == set(range(10))
203175

204176

205177
def test_session_error_pops_context():

0 commit comments

Comments
 (0)