diff --git a/flask/sessions.py b/flask/sessions.py index 82b588bc..8f111718 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -10,6 +10,7 @@ """ import hashlib import warnings +from collections import MutableMapping from datetime import datetime from itsdangerous import BadSignature, URLSafeTimedSerializer @@ -19,43 +20,55 @@ from flask.helpers import is_ip, total_seconds from flask.json.tag import TaggedJSONSerializer -class SessionMixin(object): - """Expands a basic dictionary with an accessors that are expected - by Flask extensions and users for the session. - """ +class SessionMixin(MutableMapping): + """Expands a basic dictionary with session attributes.""" - def _get_permanent(self): + @property + def permanent(self): + """This reflects the ``'_permanent'`` key in the dict.""" return self.get('_permanent', False) - def _set_permanent(self, value): + @permanent.setter + def permanent(self, value): self['_permanent'] = bool(value) - #: this reflects the ``'_permanent'`` key in the dict. - permanent = property(_get_permanent, _set_permanent) - del _get_permanent, _set_permanent - - #: some session backends can tell you if a session is new, but that is - #: not necessarily guaranteed. Use with caution. The default mixin - #: implementation just hardcodes ``False`` in. + #: Some implementations can detect whether a session is newly + #: created, but that is not guaranteed. Use with caution. The mixin + # default is hard-coded ``False``. new = False - #: for some backends this will always be ``True``, but some backends will - #: default this to false and detect changes in the dictionary for as - #: long as changes do not happen on mutable structures in the session. - #: The default mixin implementation just hardcodes ``True`` in. + #: Some implementations can detect changes to the session and set + #: this when that happens. The mixin default is hard coded to + #: ``True``. modified = True - #: the accessed variable indicates whether or not the session object has - #: been accessed in that request. This allows flask to append a `Vary: - #: Cookie` header to the response if the session is being accessed. This - #: allows caching proxy servers, like Varnish, to use both the URL and the - #: session cookie as keys when caching pages, preventing multiple users - #: from being served the same cache. + #: Some implementations can detect when session data is read or + #: written and set this when that happens. The mixin default is hard + #: coded to ``True``. accessed = True class SecureCookieSession(CallbackDict, SessionMixin): - """Base class for sessions based on signed cookies.""" + """Base class for sessions based on signed cookies. + + This session backend will set the :attr:`modified` and + :attr:`accessed` attributes. It cannot reliably track whether a + session is new (vs. empty), so :attr:`new` remains hard coded to + ``False``. + """ + + #: When data is changed, this is set to ``True``. Only the session + #: dictionary itself is tracked; if the session contains mutable + #: data (for example a nested dict) then this must be set to + #: ``True`` manually when modifying that data. The session cookie + #: will only be written to the response if this is ``True``. + modified = False + + #: When data is read or written, this is set to ``True``. Used by + # :class:`.SecureCookieSessionInterface` to add a ``Vary: Cookie`` + #: header, which allows caching proxies to cache different pages for + #: different users. + accessed = False def __init__(self, initial=None): def on_update(self): @@ -63,8 +76,6 @@ class SecureCookieSession(CallbackDict, SessionMixin): self.accessed = True super(SecureCookieSession, self).__init__(initial, on_update) - self.modified = False - self.accessed = False def __getitem__(self, key): self.accessed = True diff --git a/tests/test_basic.py b/tests/test_basic.py index 0e3076df..d7406baf 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -221,12 +221,21 @@ def test_endpoint_decorator(app, client): def test_session(app, client): @app.route('/set', methods=['POST']) def set(): + assert not flask.session.accessed + assert not flask.session.modified flask.session['value'] = flask.request.form['value'] + assert flask.session.accessed + assert flask.session.modified return 'value set' @app.route('/get') def get(): - return flask.session['value'] + assert not flask.session.accessed + assert not flask.session.modified + v = flask.session.get('value', 'None') + assert flask.session.accessed + assert not flask.session.modified + return v assert client.post('/set', data={'value': '42'}).data == b'value set' assert client.get('/get').data == b'42'