Skip to content

Commit

Permalink
Added support for long running sessions. This closes #16.
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed Apr 27, 2010
1 parent 2ba88ee commit 36717b0
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 5 deletions.
2 changes: 1 addition & 1 deletion Makefile
@@ -1,4 +1,4 @@
.PHONY: clean-pyc test
.PHONY: clean-pyc test upload-docs

all: clean-pyc test

Expand Down
7 changes: 7 additions & 0 deletions docs/api.rst
Expand Up @@ -174,6 +174,13 @@ To access the current session you can use the :class:`session` object:
# so mark it as modified yourself
session.modified = True

.. attribute:: permanent

If set to `True` the session life for
:attr:`~flask.Flask.permanent_session_lifetime` seconds. The
default is 31 days. If set to `False` (which is the default) the
session will be deleted when the user closes the browser.


Application Globals
-------------------
Expand Down
31 changes: 27 additions & 4 deletions flask.py
Expand Up @@ -13,6 +13,7 @@
import os
import sys
import types
from datetime import datetime, timedelta

from jinja2 import Environment, PackageLoader, FileSystemLoader
from werkzeug import Request as RequestBase, Response as ResponseBase, \
Expand Down Expand Up @@ -86,7 +87,20 @@ class _RequestGlobals(object):
pass


class _NullSession(SecureCookie):
class Session(SecureCookie):
"""Expands the session for support for switching between permanent
and non-permanent sessions.
"""

def _get_permanent(self):
return self.get('_permanent', False)
def _set_permanent(self, value):
self['_permanent'] = bool(value)
permanent = property(_get_permanent, _set_permanent)
del _get_permanent, _set_permanent


class _NullSession(Session):
"""Class used to generate nicer error messages if sessions are not
available. Will still allow read-only access to the empty session
but fail on setting.
Expand Down Expand Up @@ -317,6 +331,11 @@ class Flask(object):
#: The secure cookie uses this for the name of the session cookie
session_cookie_name = 'session'

#: A :class:`~datetime.timedelta` which is used to set the expiration
#: date of a permanent session. The default is 31 days which makes a
#: permanent session survive for roughly one month.
permanent_session_lifetime = timedelta(days=31)

#: options that are passed directly to the Jinja2 environment
jinja_options = ImmutableDict(
autoescape=True,
Expand Down Expand Up @@ -493,8 +512,8 @@ def open_session(self, request):
"""
key = self.secret_key
if key is not None:
return SecureCookie.load_cookie(request, self.session_cookie_name,
secret_key=key)
return Session.load_cookie(request, self.session_cookie_name,
secret_key=key)

def save_session(self, session, response):
"""Saves the session if it needs updates. For the default
Expand All @@ -505,7 +524,11 @@ def save_session(self, session, response):
object)
:param response: an instance of :attr:`response_class`
"""
session.save_cookie(response, self.session_cookie_name)
expires = None
if session.permanent:
expires = datetime.utcnow() + self.permanent_session_lifetime
session.save_cookie(response, self.session_cookie_name,
expires=expires, httponly=True)

def add_url_rule(self, rule, endpoint, view_func=None, **options):
"""Connects a URL rule. Works exactly like the :meth:`route`
Expand Down
27 changes: 27 additions & 0 deletions tests/flask_tests.py
Expand Up @@ -11,11 +11,14 @@
"""
from __future__ import with_statement
import os
import re
import sys
import flask
import unittest
import tempfile
import warnings
from datetime import datetime
from werkzeug import parse_date


example_path = os.path.join(os.path.dirname(__file__), '..', 'examples')
Expand Down Expand Up @@ -118,6 +121,30 @@ def expect_exception(f, *args, **kwargs):
expect_exception(flask.session.__setitem__, 'foo', 42)
expect_exception(flask.session.pop, 'foo')

def test_session_expiration(self):
permanent = True
app = flask.Flask(__name__)
app.secret_key = 'testkey'
@app.route('/')
def index():
flask.session['test'] = 42
flask.session.permanent = permanent
return ''
rv = app.test_client().get('/')
assert 'set-cookie' in rv.headers
match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie'])
expires = parse_date(match.group())
expected = datetime.utcnow() + app.permanent_session_lifetime
assert expires.year == expected.year
assert expires.month == expected.month
assert expires.day == expected.day

permanent = False
rv = app.test_client().get('/')
assert 'set-cookie' in rv.headers
match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie'])
assert match is None

def test_flashes(self):
app = flask.Flask(__name__)
app.secret_key = 'testkey'
Expand Down

0 comments on commit 36717b0

Please sign in to comment.