Pylons includes a session object: a session is a server-side, semi-permanent storage for data associated with a client.
The Pylons session object is available at pylons.session. Controller modules created via paster controller/restcontroller import the session object by default.
The basic session API is simple, it implements a dict-like interface with a few additional methods. The following is an example of using the session to store a token identifying if a client is logged in.
class LoginController(BaseController): def authenicate(self): name = request.POST['name'] password = request.POST['password'] user = Session.query(User).filter_by(name=name, password=password).first() if user: msg = 'Successfully logged in as %s' % name location = url('index') session['logged_in'] = True session.save() else: msg = 'Invalid username/password' location = url('login') flash(msg) redirect(location) def logout(self): # Clear all values in the session associated with the client session.clear() session.save()
Subsequent requests can then determine if a client is logged in or not by checking the session:
if not session.get('logged_in'): flash('Please login') redirect(url('login'))
The session object acts lazily: it does not load the session data (from disk or whichever backend is used) until the data is first accessed. This lazyness is facilitated via an intermediary beaker.session.SessionObject that wraps the actual beaker.session.Session object. Furthermore the session will not write changes to its backend without an explicit call to its beaker.session.Session.save() method (unless configured with the auto option).
Session data is generally serialized for storage via the Python pickle module, so anything stored in the session must be pickleable.
The lightweight SessionObject wrapper is created by the: beaker.middleware.SessionMiddleware WSGI middleware. SessionMiddleware stores the wrapper in the WSGI environ where Pylons sets a reference to it from pylons.session.
Sessions are associated with a client via a client-side cookie. The WSGI middleware is also responsible for sending said cookie to the client.
The basic session defaults are:
Pylons projects by default sets the following couple of session options via their .ini files. All Beaker specific session options in the ini file are prefixed with beaker.session:
cache_dir = %(here)s/data beaker.session.key = foo beaker.session.secret = somesecret
cache_dir acts a base directory for both session and cache storage. Session data is stored in this location under a sessions/ sub-directory.
session.key is the name attribute of the cookie sent to the browser. This defaults to your project’s name.
session.secret is the secret token used to hash the cookie data sent to the client. This should be a secret, ideally randomly generated value on production environments. paster make-config will generate a random secret for you when creating a production ini file.
Some other commonly used session options are:
To enable pure Cookie-based Sessions and force the cookie domain to be valid for all sub-domains of ‘example.com’, add the following to your Pylons ini file:
beaker.session.type = cookie beaker.session.cookie_domain = .example.com
See the Beaker Configuration Documentation for an exhaustive list of Session options.
Mapped objects from SQLAlchemy can be serialized into the beaker session, but care must be taken when retrieving these objects back from the beaker session. They will not be associated with the SQLAlchemy Unit-of-Work Session, however these objects can be reconciled via the SQLAlchemy Session’s merge method, as follows:
address = DBSession.query(Address).get(id) session[id] = address ... address = session.get(id) address = DBSession.merge(address)
Care should be taken when deciding in which layer to place custom middleware. In most cases middleware should be placed between the Pylons WSGI application instantiation and the Routes middleware; however, if the middleware should run before the session object or routing is handled:
# Routing/Session Middleware app = RoutesMiddleware(app, config['routes.map']) app = SessionMiddleware(app, config) # MyMiddleware can only see the cache object, nothing *above* here app = MyMiddleware(app) app = CacheMiddleware(app, config)
Some of the Pylons middleware layers such as the Session, Routes, and Cache middleware, only add objects to the environ dict, or add HTTP headers to the response (the Session middleware for example adds the session cookie header). Others, such as the Status Code Redirect, and the Error Handler may fully intercept the request entirely, and change how its responded to.
How to set the language used in a controller on the fly.
For example this could be used to allow a user to set which language they wanted your application to work in. Save the value to the session object:
session['lang'] = 'en' session.save()
then on each controller call the language to be used could be read from the session and set in the controller’s __before__() method so that the pages remained in the same language that was previously set:
def __before__(self): if 'lang' in session: set_lang(session['lang'])
Authorization tokens are stored in the client’s session. The web app can then verify the request’s submitted authorization token with the value in the client’s session.
This ensures the request came from the originating page. See the wikipedia entry for Cross-site request forgery for more information.
Pylons provides an authenticate_form decorator that does this verification on the behalf of controllers.
These helpers depend on Pylons’ session object. Most of them can be easily ported to another framework by changing the API calls.
How to allow called WSGI apps to share a common session management utility.
(From a paste #616 baked by Mark Luffel)
# Here's an example of configuring multiple apps to use a common # middleware filter # The [app:home] section is a standard pylons app # The ``/servicebroker`` and ``/proxy`` apps both want to be able # to use the same session management [server:main] use = egg:Paste#http host = 0.0.0.0 port = 5000 [filter-app:main] use = egg:Beaker#beaker_session next = sessioned beaker.session.key = my_project_key beaker.session.secret = i_wear_two_layers_of_socks [composite:sessioned] use = egg:Paste#urlmap / = home /servicebroker = servicebroker /proxy = cross_domain_proxy [app:servicebroker] use = egg:Appcelerator#service_broker [app:cross_domain_proxy] use = egg:Appcelerator#cross_domain_proxy [app:home] use = egg:my_project full_stack = true cache_dir = %(here)s/data