From d4f400855f0a9fd7a74afdace78e3ea1451d1057 Mon Sep 17 00:00:00 2001 From: Ray Slakinski Date: Mon, 10 May 2010 14:30:49 -0400 Subject: [PATCH] initial commit --- .gitignore | 5 ++ LICENCE.txt | 28 +++++++++ README.markdown | 38 ++++++++++++ cassandra_sessions/__init__.py | 105 +++++++++++++++++++++++++++++++++ cassandra_sessions/settings.py | 13 ++++ cassandra_sessions/test.py | 46 +++++++++++++++ setup.py | 67 +++++++++++++++++++++ 7 files changed, 302 insertions(+) create mode 100644 .gitignore create mode 100644 LICENCE.txt create mode 100644 README.markdown create mode 100644 cassandra_sessions/__init__.py create mode 100644 cassandra_sessions/settings.py create mode 100644 cassandra_sessions/test.py create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ceca56f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +*.py[co] +*.pid +*.log +django_cache diff --git a/LICENCE.txt b/LICENCE.txt new file mode 100644 index 0000000..3e08fbe --- /dev/null +++ b/LICENCE.txt @@ -0,0 +1,28 @@ +Copyright (c) 2010, Ray Slakinski +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of the author nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.markdown b/README.markdown new file mode 100644 index 0000000..646d69d --- /dev/null +++ b/README.markdown @@ -0,0 +1,38 @@ +cassandra-sessions +================== + +This is a session backend for Django that stores sessions in Cassandra + +The advantage to using this over other solutions is that your data is persistent +unlike memcached, and Cassandra is designed to store key-value data like +this, so performance is much closer to that of memcached than with a database. + +Installing cassandra-sessions +----------------------------- + +1. Either download the tarball and run 'python setup.py install', or simply + use easy install or pip like so 'easy_install cassandra-sessions'. + + +2. Set 'cassandra-sessions' as your session engine, like so: + + SESSION_ENGINE = 'cassandra-sessions' + + +3. Add settings describing where to connect to Cassandra: + + CASSANDRA_POOL = ('127.0.0.1:9160',) + CASSANDRA_SESSION_KEYSPACE = 'Testing' + + +4. Create the Keyspace and Column Family in Cassandra: + + + + org.apache.cassandra.locator.RackUnawareStrategy + 1 + org.apache.cassandra.locator.EndPointSnitch + + +That's it. Hopefully this backend gives you all the better performance while +still not sacrificing persistence. \ No newline at end of file diff --git a/cassandra_sessions/__init__.py b/cassandra_sessions/__init__.py new file mode 100644 index 0000000..6679d46 --- /dev/null +++ b/cassandra_sessions/__init__.py @@ -0,0 +1,105 @@ +import lazyboy +import time +import logging +from lazyboy.key import Key +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from django.utils.encoding import force_unicode +from django.contrib.sessions.backends.base import SessionBase, CreateError + +CASSANDRA_POOL = getattr(settings, 'CASSANDRA_POOL', None) +CASSANDRA_SESSION_KEYSPACE = getattr(settings, 'CASSANDRA_SESSION_KEYSPACE', None) + +if CASSANDRA_POOL is None or CASSANDRA_SESSION_KEYSPACE is None: + raise ImproperlyConfigured(u'To use cassandra-sessions, you must first set the CASSANDRA_SESSION_KEYSPACE and CASSANDRA_POOL settings in your settings.py') +else: + try: + lazyboy.connection.get_pool(CASSANDRA_SESSION_KEYSPACE) + except lazyboy.exceptions.ErrorCassandraClientNotFound: + lazyboy.connection.add_pool(CASSANDRA_SESSION_KEYSPACE, CASSANDRA_POOL) + +class SessionStore(SessionBase): + """ + A Cassandra-based session store. + """ + + def get_session(self, session_key): + logging.debug('Getting session: %s' % session_key) + session = None + if session_key: + try: + key = Key( + keyspace=CASSANDRA_SESSION_KEYSPACE, + column_family="Sessions", + key=session_key + ) + record = lazyboy.record.Record() + session_record = record.load(key) + session_values = session_record.values() + if session_values: + session = session_values[0] + except lazyboy.exceptions.ErrorNoSuchRecord: + pass + return session + + def load(self): + session_data = self.get_session(self.session_key) + if session_data is not None: + expiry, data = int(session_data[:15]), session_data[15:] + if expiry < time.time(): + return {} + else: + return self.decode(force_unicode(data)) + self.create() + return {} + + def create(self): + while True: + self.session_key = self._get_new_session_key() + try: + self.save() + except CreateError: + continue + self.modified = True + return + + def save(self, must_create=True): + data = self.encode(self._get_session(no_load=True)) + encoded = '%15d%s' % (int(time.time()) + self.get_expiry_age(), data) + key = Key( + keyspace=CASSANDRA_SESSION_KEYSPACE, + column_family="Sessions", + key=self.session_key + ) + record = lazyboy.record.Record() + record.key = key + record[self.session_key] = encoded + record.save() + + def exists(self, session_key): + session_data = self.get_session(session_key) + if session_data is not None: + expiry, data = int(session_data[:15]), session_data[15:] + if expiry < time.time(): + return False + else: + return True + return False + + def delete(self, session_key=None): + if session_key is None: + if self._session_key is None: + return + session_key = self._session_key + key = Key( + keyspace=CASSANDRA_SESSION_KEYSPACE, + column_family="Sessions", + key=session_key + ) + # make this session expire right now + data = self.encode({}) + encoded = '%15d%s' % (int(time.time()), data) + record = lazyboy.record.Record() + record.key = key + record[self.session_key] = encoded + record.save() \ No newline at end of file diff --git a/cassandra_sessions/settings.py b/cassandra_sessions/settings.py new file mode 100644 index 0000000..789fe6f --- /dev/null +++ b/cassandra_sessions/settings.py @@ -0,0 +1,13 @@ +import lazyboy +import logging + +logging.basicConfig( + level = logging.DEBUG, + format = '[%(asctime)s] %(levelname)s: "%(message)s"', + datefmt = '%d/%b/%Y %I:%M:%S' +) + +CASSANDRA_POOL = ('127.0.0.1:9160',) +CASSANDRA_SESSION_KEYSPACE = 'Testing' + +lazyboy.connection.add_pool(CASSANDRA_SESSION_KEYSPACE, CASSANDRA_POOL) \ No newline at end of file diff --git a/cassandra_sessions/test.py b/cassandra_sessions/test.py new file mode 100644 index 0000000..cf2337f --- /dev/null +++ b/cassandra_sessions/test.py @@ -0,0 +1,46 @@ +""" +>>> from django.conf import settings +>>> from cassandra_sessions import SessionStore as CassandraSession +>>> cassandra_session = CassandraSession() +>>> cassandra_session.modified +False +>>> cassandra_session.get('cat') +>>> cassandra_session['cat'] = "dog" +>>> cassandra_session.modified +True +>>> cassandra_session.pop('cat') +'dog' +>>> cassandra_session.pop('some key', 'does not exist') +'does not exist' +>>> cassandra_session.save() +>>> cassandra_session.exists(cassandra_session.session_key) +True +>>> cassandra_session.delete(cassandra_session.session_key) +>>> cassandra_session['foo'] = 'bar' +>>> cassandra_session.save() +>>> cassandra_session.exists(cassandra_session.session_key) +True +>>> prev_key = cassandra_session.session_key +>>> cassandra_session.flush() +>>> cassandra_session.exists(prev_key) +False +>>> cassandra_session.session_key == prev_key +False +>>> cassandra_session.modified, cassandra_session.accessed +(True, True) +>>> cassandra_session['a'], cassandra_session['b'] = 'c', 'd' +>>> cassandra_session.save() +>>> prev_key = cassandra_session.session_key +>>> prev_data = cassandra_session.items() +>>> cassandra_session.cycle_key() +>>> cassandra_session.session_key == prev_key +False +>>> cassandra_session.items() == prev_data +True +""" + +if __name__ == '__main__': + import os + os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' + import doctest + doctest.testmod() \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..876a901 --- /dev/null +++ b/setup.py @@ -0,0 +1,67 @@ +from setuptools import setup, find_packages + +version = '0.1.0' + +LONG_DESCRIPTION = """ +cassandra-sessions +================== + +This is a session backend for Django that stores sessions in Cassandra + +The advantage to using this over other solutions is that your data is persistent +unlike memcached, and Cassandra is designed to store key-value data like +this, so performance is much closer to that of memcached than with a database. + +Installing cassandra-sessions +----------------------------- + +1. Either download the tarball and run ``python setup.py install``, or simply + use easy install or pip like so ``easy_install cassandra-sessions``. + + +2. Set ``cassandra-sessions`` as your session engine, like so:: + + SESSION_ENGINE = 'cassandra-sessions' + + +3. Add settings describing where to connect to Cassandra:: + + CASSANDRA_POOL = ('127.0.0.1:9160',) + CASSANDRA_SESSION_KEYSPACE = 'Testing' + + +4. Create the Keyspace and Column Family in Cassandra:: + + + + org.apache.cassandra.locator.RackUnawareStrategy + 1 + org.apache.cassandra.locator.EndPointSnitch + + +That's it. Hopefully this backend gives you all the better performance while +still not sacrificing persistence. +""" + +setup( + name='cassandra-sessions', + version=version, + description="This is a session backend for Django that stores sessions in Cassandra.", + long_description=LONG_DESCRIPTION, + classifiers=[ + "Programming Language :: Python", + "Topic :: Software Development :: Libraries :: Python Modules", + "Framework :: Django", + "Environment :: Web Environment", + ], + keywords='cassandra,session,lazyboy,django', + author='Ray Slakinski', + author_email='ray.slakinski@gmail.com', + url='http://github.com/rays/cassandra-sessions/tree/master', + license='BSD', + packages=find_packages(), + zip_safe=False, + install_requires=['setuptools'], + include_package_data=True, + setup_requires=['setuptools_git'], +) \ No newline at end of file