"""This is a session module which uses a database backend.
This session module is appropriate for environments such as CGI and mod_python.
This module itself uses different database backends in order to work with
different databases. aquarium.database.mysql.Session_ comes by default, but
this can easily be ported to other databases. Please see the appropriate
database module for details such as what tables need to be created.
To use this module, you'll need something like the following in
``AquariumProperties``::
SESSION_CONTAINER_MODULE = "DatabaseSessionContainer"
# This is the name of the backend that
# aquarium.session.DatabaseSessionContainer should use. This is a module
# name relative to the database package.
DATABASE_SESSION_CONTAINER_BACKEND = "mysql.Session"
The following attributes are used:
backend
This is an instance of the backend.
.. _aquarium.database.mysql.Session:
aquarium.database.mysql.Session.Session-class.html
"""
__docformat__ = "restructuredtext"
# Created: Fri Jan 14 15:02:39 PST 2005
# Author: Jeffrey Wescott, Shannon -jj Behrens
# Email: jjinux@users.sourceforge.net
#
# Copyright (c) Jeffrey Wescott, Shannon -jj Behrens. All rights reserved.
import cPickle
import random
from aquarium.session.SessionContainer import SessionContainer,Session
import aquarium.conf.AquariumProperties as properties
from aquarium.database.DatabaseAssistant import MissingRecordError
from aquarium.util.AquariumClass import AquariumClass
class DatabaseSessionContainer(SessionContainer, AquariumClass):
"""This is the session container.
I won't bother with any locking. The database can take care of that.
Concerning aquarium.util.AquariumClass_: unlike its parent class, this
class does mixin aquarium.util.AquariumClass and does require a ``ctx``
parameter in its constructor. I need to do this in order to get access to
the database connection.
The following class level constants are defined:
SESSION_CLEANUP_FREQUENCY
If you're using this module, you probably don't have an "application"
scope. Hence, it'll be hard for you to create a thread to call
``cleanup``. Hence, I'll call it for you, randomly, every
SESSION_CLEANUP_FREQUENCY calls to ``open``. Set this 0 to forgo this
behavior.
.. _aquarium.util.AquariumClass:
aquarium.util.AquariumClass.AquariumClass-class.html
"""
SESSION_CLEANUP_FREQUENCY = 100
def __init__(self, ctx):
"""Call ``AquariumClass.__init__``.
Also, instantiate the ``backend``.
"""
AquariumClass.__init__(self, ctx)
self.backend = self._ctx.iLib.aquariumFactory(
"database." + properties.DATABASE_SESSION_CONTAINER_BACKEND)
def open(self, sid=None):
"""Create or open a session.
Also, call ``cleanup`` per ``SESSION_CLEANUP_FREQUENCY``.
"""
if (self.SESSION_CLEANUP_FREQUENCY and
random.randint(1, self.SESSION_CLEANUP_FREQUENCY) == 1):
self.cleanup()
if sid:
try:
return self._loadSession(sid)
except MissingRecordError:
pass
tries = 10
while 1:
sid = self._createSid()
if not self._exists(sid):
break
tries -= 1
if tries <= 0:
raise OverflowError("Could not create an unused sid")
return self._createSession(sid)
def cleanup(self):
"""Delete all of the expired sessions.
It's the application's responsibility to occasionally call this if you
set ``SESSION_CLEANUP_FREQUENCY`` to 0.
"""
self.backend.cleanup()
def _createSession(self, sid):
"""This is a session factory method."""
return DatabaseSession(self.backend, sid)
def _loadSession(self, sid):
"""Load an existing session."""
session = DatabaseSession(self.backend, sid)
session.load()
return session
def _exists(self, sid):
"""Does a session with the given sid exist?
This implies that it is not expired.
"""
return self.backend.exists(sid)
def _isExpired(self, sid):
"""This is ``NotImplemented`` because it's not needed.
If it ``_exists``, then it's not expired.
"""
raise NotImplemented(self._isExpired.__doc__)
class DatabaseSession(Session):
"""This is the session.
The following attributes are used:
backend
This is an instance of the backend.
"""
def __init__(self, backend, sid):
"""Accept a reference to the backend."""
self.backend = backend
Session.__init__(self, sid)
def save(self):
"""Persist a session to the database.
This involves pickling the session. You can change which protocol is
used to pickle the session by setting the Aquarium property
``DATABASE_SESSION_CONTAINER_PICKLE_PROTOCOL``. It's default value is
0. The most optimal setting is ``cPickle.HIGHEST_PROTOCOL``, but that
doesn't always work. See the ``proto`` argument of ``cPickle.dumps``
for more information.
"""
Session.save(self)
backend = self.backend
del self.backend
try:
proto = getattr(properties,
"DATABASE_SESSION_CONTAINER_PICKLE_PROTOCOL", 0)
pickle = cPickle.dumps(self, proto)
finally:
self.backend = backend
self.backend.save(self, pickle)
def load(self):
"""Load a session from the database."""
pickled = self.backend.load(self["sid"])
self.update(cPickle.loads(pickled))
random.seed()
|