diff --git a/CHANGES.rst b/CHANGES.rst index 1902cc85..fe9870aa 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -9,6 +9,12 @@ Version 1.1 Unreleased +- :meth:`flask.RequestContext.copy` includes the current session + object in the request context copy. This prevents ``flask.session`` + pointing to an out-of-date object. (`#2935`) + +.. _#2935: https://github.com/pallets/flask/issues/2935 + Version 1.0.3 ------------- diff --git a/flask/ctx.py b/flask/ctx.py index 8472c920..e41adbd2 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -122,7 +122,8 @@ def copy_current_request_context(f): """A helper function that decorates a function to retain the current request context. This is useful when working with greenlets. The moment the function is decorated a copy of the request context is created and - then pushed when the function is called. + then pushed when the function is called. The current session is also + included in the copied request context. Example:: @@ -133,8 +134,8 @@ def copy_current_request_context(f): def index(): @copy_current_request_context def do_some_work(): - # do some work here, it can access flask.request like you - # would otherwise in the view function. + # do some work here, it can access flask.request or + # flask.session like you would otherwise in the view function. ... gevent.spawn(do_some_work) return 'Regular response' @@ -276,14 +277,14 @@ class RequestContext(object): that situation, otherwise your unittests will leak memory. """ - def __init__(self, app, environ, request=None): + def __init__(self, app, environ, request=None, session=None): self.app = app if request is None: request = app.request_class(environ) self.request = request self.url_adapter = app.create_url_adapter(self.request) self.flashes = None - self.session = None + self.session = session # Request contexts can be pushed multiple times and interleaved with # other request contexts. Now only if the last level is popped we @@ -321,10 +322,15 @@ class RequestContext(object): request object is locked. .. versionadded:: 0.10 + + .. versionchanged:: 1.1 + The current session object is used instead of reloading the original + data. This prevents `flask.session` pointing to an out-of-date object. """ return self.__class__(self.app, environ=self.request.environ, - request=self.request + request=self.request, + session=self.session ) def match_request(self): diff --git a/tests/test_reqctx.py b/tests/test_reqctx.py index 75d79c67..d8ef4970 100644 --- a/tests/test_reqctx.py +++ b/tests/test_reqctx.py @@ -156,6 +156,7 @@ class TestGreenletContextCopying(object): @app.route('/') def index(): + flask.session['fizz'] = 'buzz' reqctx = flask._request_ctx_stack.top.copy() def g(): @@ -166,6 +167,7 @@ class TestGreenletContextCopying(object): assert flask.current_app == app assert flask.request.path == '/' assert flask.request.args['foo'] == 'bar' + assert flask.session.get('fizz') == 'buzz' assert not flask.request return 42 @@ -183,6 +185,7 @@ class TestGreenletContextCopying(object): @app.route('/') def index(): + flask.session['fizz'] = 'buzz' reqctx = flask._request_ctx_stack.top.copy() @flask.copy_current_request_context @@ -191,6 +194,7 @@ class TestGreenletContextCopying(object): assert flask.current_app == app assert flask.request.path == '/' assert flask.request.args['foo'] == 'bar' + assert flask.session.get('fizz') == 'buzz' return 42 greenlets.append(greenlet(g))