Created
February 2, 2011 21:13
-
-
Save aezell/808452 to your computer and use it in GitHub Desktop.
DB-backed Cached Session in Python
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django.conf import settings | |
from django.contrib.sessions.backends.base import SessionBase | |
from django.core.cache import cache | |
from PHPSerialize import PHPSerialize | |
from PHPUnserialize import PHPUnserialize | |
from session_models import SessionEntry | |
from datetime import timedelta | |
from sqlalchemy import func, text | |
from sharding import Session | |
class SessionStore(SessionBase): | |
"""Custom database-backed sessions with a memcached layer. | |
Also reads & writes the session information in a PHP-compatible format. | |
""" | |
MAX_CACHE_AGE = timedelta(seconds=settings.SESSION_MAX_AGE) | |
MIN_CACHE_AGE = timedelta(seconds=settings.SESSION_MIN_AGE) | |
CACHE_SLEEP = MAX_CACHE_AGE - MIN_CACHE_AGE | |
def __init__(self, session_key=None): | |
super(SessionStore, self).__init__(session_key) | |
def load(self): | |
sess = None | |
content = cache.get(self.session_key) | |
expired = (cache.get(self.expires_key) == None) | |
if not content: | |
sess = self._get_db_session(self.session_key, exclude_expired=True) | |
if sess: | |
content = sess.content | |
expired = sess.expires_in < SessionStore.MIN_CACHE_AGE | |
if not content: | |
return {} | |
cache.set(self.session_key, content, SessionStore.MAX_CACHE_AGE.seconds) | |
session_dictionary = PHPUnserialize().session_decode(content.encode('utf-8')) | |
if expired: | |
if self._should_persist(session_dictionary): | |
sess = sess or self._get_db_session(self.session_key, create_if_missing=True) | |
sess.expires = text("current_timestamp + interval '%d seconds'" % SessionStore.MAX_CACHE_AGE.seconds) | |
sess.content = content | |
sess.flush() | |
cache.set(self.expires_key, "1", SessionStore.CACHE_SLEEP.seconds) | |
else: | |
cache.delete(self.expires_key) | |
return session_dictionary | |
def save(self): | |
session_dictionary = self._get_session() | |
content = PHPSerialize().session_encode(session_dictionary).decode('utf-8') | |
cache.set(self.session_key, content, SessionStore.MAX_CACHE_AGE.seconds) | |
if self._should_persist(session_dictionary): | |
sess = self._get_db_session(self.session_key, create_if_missing=True) | |
sess.expires = text("current_timestamp + interval '%d seconds'" % SessionStore.MAX_CACHE_AGE.seconds) | |
sess.content = content | |
sess.flush() | |
cache.set(self.expires_key, "1", SessionStore.CACHE_SLEEP.seconds) | |
else: | |
cache.delete(self.expires_key) | |
def exists(self, session_key): | |
return cache.get(session_key) is not None or \ | |
self._get_db_session(session_key) is not None | |
def delete(self, session_key): | |
cache.delete(session_key) | |
cache.delete(session_key + '_expires') | |
sess = self._get_db_session(session_key) | |
if sess: | |
sess.delete() | |
def _get_db_session(self, session_key, create_if_missing=False, exclude_expired=False): | |
if not self._database_sessions_available(): | |
return None | |
query = Session.query(SessionEntry).filter(SessionEntry.session_id == session_key) | |
if exclude_expired: | |
query = query.filter(SessionEntry.expires >= func.current_timestamp()) | |
sess = query.first() | |
if not sess and create_if_missing: | |
sess = SessionEntry(session_id = session_key) | |
return sess | |
def _should_persist(self, session_dictionary): | |
return self._database_sessions_available() and \ | |
session_dictionary.get('user_id', 'anon') != 'anon' | |
def _database_sessions_available(self): | |
# Because sharding allows us to move between multiple databases, we | |
# need to know if we've been able to set up a connection to our "home" | |
# shard. If we haven't, we can't try to use any database sessions | |
# because we run the risk of carrying our sessions between shards that | |
# way, and potentially accessing old information. | |
# | |
# When we configure the session, we also set has_home on the | |
# SessionEntry class to specify whether we explicitly bound it to | |
# something. | |
return SessionEntry.has_home | |
@property | |
def expires_key(self): | |
return self.session_key + '_expires' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment