django-redisession 0.3.1
A Redis-based Django session engine for django.contrib.sessions.
License: MIT

==================
Django Redisession
==================
A Redis-based Django session engine for django.contrib.sessions.

This engine supports storing session data as a Redis String, or distributing sessions to Redis hashs for more efficient usage of Redis storage(without much extra CPU cost on Redis server. Refs http://antirez.com/post/redis-weekly-update-7.html and http://redis.io/topics/memory-optimization , you may need to tweak Redis conf for actual usage. The tech is referred as hash mode below).

Installation
============
#. Require Django version >= 1.3
#. ``pip install django-redisession`` or in source ``python setup.py install``.
#. Add `redisession` to INSTALLED_APPS in your current settings file.
#. Set ``SESSION_ENGINE='redisession.backend'`` in the settings file.
    
Configuration
=============
Configuration options to the django-redisession can be placed in dict `REDIS_SESSION_CONFIG`:
    - `SERVER` -- If set to a dict then the value will be used as arguments for redis.Redis() to make a redis connection instance. If set to a string then the django-redisession will get redisession.helper.get_redis to lookup the value in `REDIS_CONFIG` for existing connection instance, see below.
    - `USE_HASH` -- If set to True (hash mode), the django-redisession will store sessions as items of Redis Hashes. `HASH_KEY_GENERATOR` will be used to generate corresponding Redis keys. If set to False, the data of a session will be stored as Redis String value of the Redis key which is generated by `KEY_GENERATOR`. session keys generated by md5 is not as well distribudated as those by sha1, but still acceptable.
    - `KEY_GENERATOR` -- The callable to generate Redis key, or Redis Hash field name when in hash mode, for a session. The callable accepts session_key as the only parameter. The default generates the binary string, 16 bytes for md5, from session_key which is normally a hex string. **Note** if you are customizing it, open `LOG_KEY_ERROR` and run test `./manage.py test redisession` to "ensure" `KEY_GENERATOR` works with session keys. For session key contains '0-9a-z' in incoming Django 1.5, you could check comments in `redisession/backend.py`. 
    - `HASH_KEY_GENERATOR` -- The callable to generate Redis key for Redis Hashes, in hash mode. The default uses the first two bytes of the binary string of a session_key, and thus distributes sessions to 65536 hashes. Other dispatching logics or key prefix could be achieved by customizing this option. **Note** if you are customizing it, open `LOG_KEY_ERROR` and run test `./manage.py test redisession` to "ensure" `HASH_KEY_GENERATOR` works with session keys. For session key contains '0-9a-z' in incoming Django 1.5, you could check comments in `redisession/backend.py`. 
    - `HASH_KEYS_CHECK_FOR_EXPIRY` -- The callable to generate Redis keys to check for deleting expired sessions, in hash mode. The callable accepts redis connection instance as parameter. The default applys Redis.randomkey 100 times to sample keys. 
    - `COMPRESS_LIB` -- The module name of the compress library used to compress session data, leave blank to disable compress. Defaults to 'snappy', refs `snappy <http://code.google.com/p/snappy/>`_ and `python-snappy <https://github.com/andrix/python-snappy>`_ . The compress library should support compress and decompress, such as zlib, lzo, etc. A slow library may have higher compression ratio, which implies higher load on web server, but less IO on communicating w/ Redis. So decide it by your tradeoff.
    - `COMPRESS_MIN_LENGTH` -- The minimal length of the session data to try compress. Defaults to 400.
    - `LOG_KEY_ERROR` -- Whether log key error caused by bad key or misconfigured `HASH_KEY_GENERATOR` or `KEY_GENERATOR`. Defaults to False.

Examples:

::

    # default: distributes sessions to 65536 Redis Hashes,
    #          use binary string form of session_key,
    #          applys Redis.randomkey 100 times to sample hash keys
    #           to check for deleting expired session,
    #          try to compress data (length >= 400) by using snappy. 
    REDIS_SESSION_CONFIG = {
        'SERVER': {},
        'USE_HASH': True,
        'KEY_GENERATOR': lambda x: x.decode('hex'),
        'HASH_KEY_GENERATOR': lambda x: x[:4].decode('hex'),
        'HASH_KEYS_CHECK_FOR_EXPIRY': lambda r: (reduce(lambda p,y :p.randomkey(),
            xrange(100), r.pipeline()).execute()),
        'COMPRESS_LIB': 'snappy',
        'COMPRESS_MIN_LENGTH': 400,
        'LOG_KEY_ERROR': False
    }
    
::

    # use session thru Redis get/set. store session key just as it is, hex string.
    REDIS_SESSION_CONFIG = {
        'USE_HASH': False,
        'KEY_GENERATOR': lambda x: x
    }

::

    # almost as above, but add key prefix 's:' for Redis keys
    REDIS_SESSION_CONFIG = {
        'USE_HASH': False,
        'KEY_GENERATOR': lambda x: 's:'+x
    }

::

    # use Redis connection instance through redisession.helper.get_redis
    # here django-redisession will try to use Redis connection instance 'foo'
    REDIS_SESSION_CONFIG = {
        'SERVER': 'foo'
    }

    # see following
    REDIS_CONFIG = {
        'default': {'db':1},
        'foo': {'db':2, unix_socket_path='/tmp/bar'}
    }

::

    # Here are some other possible settings of 'HASH_KEYS_CHECK_FOR_EXPIRY'

    # Fetching and scaning ALL keys and ALL of their fields. Use with caution. 
    lambda r: r.keys()

    # Sampling 100 hash keys out of 65536 possible slots.
    # This is useful if the number of actually used slots is closer to 65536.
    lambda r: (map(lambda x:('%04x'%x).decode('hex'),
        random.sample(xrange(65536), 100)))



You could use ``redisession.helper.get_redis`` to create and get global Redis connection instance by name. First, setting `REDIS_CONFIG` in the settings file, which is similar to DBs settings in django. For example:

::

    REDIS_CONFIG = {
        # 'name': arguments passed to redis.Redis to build a connection instance, as dict items
        'default': {'port':63790, 'db':1},
        'foo': {'db':2, unix_socket_path='/tmp/bar'},
        'session': {'db':3},
    }

then

::

    >>> from redisession.helper import get_redis
    >>> r = get_redis() # get Redis connection instance of name 'default'
    >>> r = get_redis('foo') # or of name 'foo'
    >>> r = get_redis('session') # use db 3 for sessions only
    >>> r.info()

Security
========
Isolate the Redis server of storing sessions from other usages is RECOMMENDED.
You could achieve this by using a seperate db, like conf 'session' in REDIS_CONFIG above,
or by specifying a unique key prefix in KEY_GENERATOR.
For possible security issue, refs https://www.djangoproject.com/weblog/2011/sep/09/security-releases-issued/
and https://code.djangoproject.com/changeset/16759


Cleanup expired sessions in hash mode
=====================================
    run django command `cleanuprs` in shell or cronjob.

Test
====
    ``python manager.py test redisession``. It uses your REDIS_SESSION_CONFIG settings for tests, so corresponding Redis server should be available.