diff --git a/CHANGES b/CHANGES index f4684b56..0db9003e 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,10 @@ Version 0.8 Relase date to be decided, codename to be chosen. +- Refactored session support into a session interface so that + the implementation of the sessions can be changed without + having to override the Flask class. + Version 0.7.2 ------------- diff --git a/docs/api.rst b/docs/api.rst index b21ec2ce..99071f87 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -195,9 +195,34 @@ To access the current session you can use the :class:`session` object: session will be deleted when the user closes the browser. +Session Interface +----------------- + +.. versionadded:: 0.7 + +The session interface provides a simple way to replace the session +implementation that Flask is using. + +.. currentmodule:: flask.sessions + +.. autoclass:: SessionInterface + :members: + +.. autoclass:: SecureCookieSessionInterface + :members: + +.. autoclass:: NullSession + :members: + +.. autoclass:: SessionMixin + :members: + + Application Globals ------------------- +.. currentmodule:: flask + To share data that is valid for one request only from one function to another, a global variable is not good enough because it would break in threaded environments. Flask provides you with a special object that diff --git a/flask/__init__.py b/flask/__init__.py index 1c5e3ba2..930859da 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -27,7 +27,6 @@ from .ctx import has_request_context from .module import Module from .blueprints import Blueprint from .templating import render_template, render_template_string -from .session import Session # the signals from .signals import signals_available, template_rendered, request_started, \ diff --git a/flask/app.py b/flask/app.py index dff4272b..162ce4f2 100644 --- a/flask/app.py +++ b/flask/app.py @@ -27,7 +27,7 @@ from .wrappers import Request, Response from .config import ConfigAttribute, Config from .ctx import RequestContext from .globals import _request_ctx_stack, request -from .session import Session, _NullSession +from .sessions import SecureCookieSessionInterface from .module import blueprint_is_module from .templating import DispatchingJinjaLoader, Environment, \ _default_template_ctx_processor @@ -211,6 +211,12 @@ class Flask(_PackageBoundObject): #: .. versionadded:: 0.7 test_client_class = None + #: the session interface to use. By default an instance of + #: :class:`~flask.sessions.SecureCookieSessionInterface` is used here. + #: + #: .. versionadded:: 0.7 + session_interface = SecureCookieSessionInterface() + def __init__(self, import_name, static_path=None, static_url_path=None, static_folder='static', template_folder='templates'): _PackageBoundObject.__init__(self, import_name, @@ -580,32 +586,32 @@ class Flask(_PackageBoundObject): def open_session(self, request): """Creates or opens a new session. Default implementation stores all session data in a signed cookie. This requires that the - :attr:`secret_key` is set. + :attr:`secret_key` is set. Instead of overriding this method + we recommend replacing the :class:`session_interface`. :param request: an instance of :attr:`request_class`. """ - key = self.secret_key - if key is not None: - return Session.load_cookie(request, self.session_cookie_name, - secret_key=key) + return self.session_interface.open_session(self, request) def save_session(self, session, response): """Saves the session if it needs updates. For the default - implementation, check :meth:`open_session`. + implementation, check :meth:`open_session`. Instead of overriding this + method we recommend replacing the :class:`session_interface`. :param session: the session to be saved (a :class:`~werkzeug.contrib.securecookie.SecureCookie` object) :param response: an instance of :attr:`response_class` """ - expires = domain = None - if session.permanent: - expires = datetime.utcnow() + self.permanent_session_lifetime - if self.config['SERVER_NAME'] is not None: - # chop of the port which is usually not supported by browsers - domain = '.' + self.config['SERVER_NAME'].rsplit(':', 1)[0] - session.save_cookie(response, self.session_cookie_name, - expires=expires, httponly=True, domain=domain) + return self.session_interface.save_session(self, session, response) + + def make_null_session(self): + """Creates a new instance of a missing session. Instead of overriding + this method we recommend replacing the :class:`session_interface`. + + .. versionadded:: 0.7 + """ + return self.session_interface.make_null_session(self) def register_module(self, module, **options): """Registers a module with this application. The keyword argument @@ -1184,7 +1190,7 @@ class Flask(_PackageBoundObject): """ ctx = _request_ctx_stack.top bp = ctx.request.blueprint - if not isinstance(ctx.session, _NullSession): + if not self.session_interface.is_null_session(ctx.session): self.save_session(ctx.session, response) funcs = () if bp is not None and bp in self.after_request_funcs: diff --git a/flask/ctx.py b/flask/ctx.py index 3ea43b97..a189b28f 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -122,7 +122,7 @@ class RequestContext(object): # request context (e.g. flask-sqlalchemy). self.session = self.app.open_session(self.request) if self.session is None: - self.session = _NullSession() + self.session = self.app.make_null_session() def pop(self): """Pops the request context and unbinds it by doing that. This will diff --git a/flask/session.py b/flask/session.py index df2d8773..bfe196b0 100644 --- a/flask/session.py +++ b/flask/session.py @@ -3,41 +3,17 @@ flask.session ~~~~~~~~~~~~~ - Implements cookie based sessions based on Werkzeug's secure cookie - system. + This module used to flask with the session global so we moved it + over to flask.sessions :copyright: (c) 2010 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ -from werkzeug.contrib.securecookie import SecureCookie +from warnings import warn +warn(DeprecationWarning('please use flask.sessions instead')) +from .sessions import * -class Session(SecureCookie): - """Expands the session with 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. - """ - - def _fail(self, *args, **kwargs): - raise RuntimeError('the session is unavailable because no secret ' - 'key was set. Set the secret_key on the ' - 'application to something unique and secret.') - __setitem__ = __delitem__ = clear = pop = popitem = \ - update = setdefault = _fail - del _fail +Session = SecureCookieSession +_NullSession = NullSession