initial commit
This commit is contained in:
commit
d4f400855f
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.DS_Store
|
||||||
|
*.py[co]
|
||||||
|
*.pid
|
||||||
|
*.log
|
||||||
|
django_cache
|
28
LICENCE.txt
Normal file
28
LICENCE.txt
Normal file
@ -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.
|
38
README.markdown
Normal file
38
README.markdown
Normal file
@ -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:
|
||||||
|
|
||||||
|
<Keyspace Name="Testing">
|
||||||
|
<ColumnFamily Name="Sessions" CompareWith="UTF8Type"/>
|
||||||
|
<ReplicaPlacementStrategy>org.apache.cassandra.locator.RackUnawareStrategy</ReplicaPlacementStrategy>
|
||||||
|
<ReplicationFactor>1</ReplicationFactor>
|
||||||
|
<EndPointSnitch>org.apache.cassandra.locator.EndPointSnitch</EndPointSnitch>
|
||||||
|
</Keyspace>
|
||||||
|
|
||||||
|
That's it. Hopefully this backend gives you all the better performance while
|
||||||
|
still not sacrificing persistence.
|
105
cassandra_sessions/__init__.py
Normal file
105
cassandra_sessions/__init__.py
Normal file
@ -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()
|
13
cassandra_sessions/settings.py
Normal file
13
cassandra_sessions/settings.py
Normal file
@ -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)
|
46
cassandra_sessions/test.py
Normal file
46
cassandra_sessions/test.py
Normal file
@ -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()
|
67
setup.py
Normal file
67
setup.py
Normal file
@ -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::
|
||||||
|
|
||||||
|
<Keyspace Name="Testing">
|
||||||
|
<ColumnFamily Name="Sessions" CompareWith="UTF8Type"/>
|
||||||
|
<ReplicaPlacementStrategy>org.apache.cassandra.locator.RackUnawareStrategy</ReplicaPlacementStrategy>
|
||||||
|
<ReplicationFactor>1</ReplicationFactor>
|
||||||
|
<EndPointSnitch>org.apache.cassandra.locator.EndPointSnitch</EndPointSnitch>
|
||||||
|
</Keyspace>
|
||||||
|
|
||||||
|
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'],
|
||||||
|
)
|
Reference in New Issue
Block a user