diff --git a/README.rst b/README.rst index 8f878d0..edb3d17 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,7 @@ +.. image:: http://unmaintained.tech/badge.svg + :target: http://unmaintained.tech/ + :alt: No Maintenance Intended + ================== Django Redisession ================== diff --git a/redisession/__init__.py b/redisession/__init__.py index 2e20de5..1d8a015 100644 --- a/redisession/__init__.py +++ b/redisession/__init__.py @@ -1 +1,7 @@ -version = '0.3.1' +from __future__ import unicode_literals + +from distutils import version + + +__version__ = '0.5.0' +version_info = version.StrictVersion(__version__).version diff --git a/redisession/backend.py b/redisession/backend.py index e2c69fd..2be38a5 100644 --- a/redisession/backend.py +++ b/redisession/backend.py @@ -1,69 +1,72 @@ -""" -A redis backend for django session, support string and hash mode. -""" +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +import logging import struct import time +from importlib import import_module + +import redis from django.conf import settings -from django.contrib.sessions.backends.base import pickle, CreateError, SessionBase +from django.contrib.sessions.backends.base import CreateError, SessionBase + +try: + from django.utils.six.moves import cPickle as pickle +except ImportError: + import pickle + + +logger = logging.getLogger('redisession') + conf = { '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()), + '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 } -# For session key contains '0-9a-z' in incoming Django 1.5 -# conf['KEY_GENERATOR'] = lambda x: x.decode('base64') -# conf['HASH_KEY_GENERATOR'] = lambda x: x.decode('base64')[:2] conf.update(getattr(settings, 'REDIS_SESSION_CONFIG', {})) -if conf['LOG_KEY_ERROR']: - import logging - logger = logging.getLogger('redisession') - logger.addHandler(logging.StreamHandler()) - logger.setLevel(logging.WARNING) if isinstance(conf['SERVER'], dict): + class GetRedis(object): + def __call__(self, conf): if not hasattr(self, '_redis'): - import redis self._redis = redis.Redis(**conf) return self._redis + get_redis = GetRedis() + else: from redisession.helper import get_redis + if conf['COMPRESS_LIB']: - from django.utils.importlib import import_module compress_lib = import_module(conf['COMPRESS_LIB']) -# TODO: flag for security verification? + FLAG_COMPRESSED = 1 + class SessionStore(SessionBase): + def __init__(self, session_key=None): self._redis = get_redis(conf['SERVER']) super(SessionStore, self).__init__(session_key) - # XXX Try to partially comply w/ session API of newer Django (>= 1.4) for Django 1.3 - # Instead of checking Django version, test the existence directly. - if not hasattr(SessionBase, '_get_or_create_session_key'): - session_key = property(SessionBase._get_session_key) - - def _get_or_create_session_key(self): - if self._session_key is None: - self._session_key = self._get_new_session_key() - return self._session_key + if not hasattr(self, 'serializer'): + self.serializer = lambda: pickle def encode(self, session_dict): - data = pickle.dumps(session_dict, pickle.HIGHEST_PROTOCOL) + data = self.serializer().dumps(session_dict) flag = 0 if conf['COMPRESS_LIB'] and len(data) >= conf['COMPRESS_MIN_LENGTH']: compressed = compress_lib.compress(data) @@ -76,9 +79,10 @@ def decode(self, session_data): flag, data = ord(session_data[:1]), session_data[1:] if flag & FLAG_COMPRESSED: if conf['COMPRESS_LIB']: - return pickle.loads(compress_lib.decompress(data)) - raise ValueError('redisession: found compressed data without COMPRESS_LIB specified.') - return pickle.loads(data) + return self.serializer().loads(compress_lib.decompress(data)) + raise ValueError('redisession: found compressed data without ' + 'COMPRESS_LIB specified.') + return self.serializer().loads(data) def create(self): for i in xrange(10000): @@ -94,10 +98,17 @@ def create(self): if conf['USE_HASH']: def _make_key(self, session_key): try: - return (conf['HASH_KEY_GENERATOR'](session_key), conf['KEY_GENERATOR'](session_key)) - except: + return ( + conf['HASH_KEY_GENERATOR'](session_key), + conf['KEY_GENERATOR'](session_key) + ) + except Exception: if conf['LOG_KEY_ERROR']: - logger.warning('misconfigured key-generator or bad key "%s"' % session_key) + logger.warning( + 'misconfigured key-generator or bad key "{}"'.format( + session_key + ) + ) def save(self, must_create=False): if must_create: @@ -105,13 +116,12 @@ def save(self, must_create=False): else: func = self._redis.hset session_data = self.encode(self._get_session(no_load=must_create)) - expire_date = struct.pack('>I', int(time.time()+self.get_expiry_age())) + expire_date = struct.pack( + '>I', int(time.time() + self.get_expiry_age())) key = self._make_key(self._get_or_create_session_key()) if key is None: - # XXX must_create = True w/ bad key or misconfigured KEY_GENERATOR, - # which has already been logged in _make_key. raise CreateError - result = func(*key, value=expire_date+session_data) + result = func(*key, value=expire_date + session_data) if must_create and not result: raise CreateError @@ -141,13 +151,17 @@ def delete(self, session_key=None): if key is not None: self._redis.hdel(*key) - else: # not conf['USE_HASH'] + else: # not conf['USE_HASH'] def _make_key(self, session_key): try: return conf['KEY_GENERATOR'](session_key) - except: + except Exception: if conf['LOG_KEY_ERROR']: - logger.warning('misconfigured key-generator or bad key "%s"' % session_key) + logger.warning( + 'misconfigured key-generator or bad key "{}"'.format( + session_key + ) + ) def save(self, must_create=False): pipe = self._redis.pipeline() @@ -158,11 +172,13 @@ def save(self, must_create=False): session_data = self.encode(self._get_session(no_load=must_create)) key = self._make_key(self._get_or_create_session_key()) if key is None: - # XXX must_create = True w/ bad key or misconfigured KEY_GENERATOR, - # which has already been logged in _make_key. raise CreateError - result = pipe(key, session_data).expire(key, self.get_expiry_age()).execute() - if must_create and not (result[0] and result[1]): # for Python 2.4 (Django 1.3) + result = pipe(key, session_data).expire( + key, self.get_expiry_age() + ).execute() + + # for Python 2.4 (Django 1.3) + if must_create and not (result[0] and result[1]): raise CreateError def load(self): diff --git a/redisession/helper.py b/redisession/helper.py index 7dd3115..c36d087 100644 --- a/redisession/helper.py +++ b/redisession/helper.py @@ -1,11 +1,17 @@ -from django.conf import settings +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + import redis +from django.conf import settings + _connections = {} + + def get_redis(conf_name='default'): """simple helper for getting global Redis connection instances""" if conf_name not in _connections: - # refs redis.Redis for **v _connections[conf_name] = redis.Redis(**getattr( - settings, 'REDIS_CONFIG', {'default': {}})[conf_name]) + settings, 'REDIS_CONFIG', {'default': {}} + )[conf_name]) return _connections[conf_name] diff --git a/redisession/management/commands/cleanuprs.py b/redisession/management/commands/cleanuprs.py index 97f4598..24ccd2e 100644 --- a/redisession/management/commands/cleanuprs.py +++ b/redisession/management/commands/cleanuprs.py @@ -1,9 +1,16 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + import struct import time + from django.core.management.base import NoArgsCommand + class Command(NoArgsCommand): - help = """Clean out expired sessions from redisession data.""" + + help = '''Clean out expired sessions from redisession data.''' + def handle_noargs(self, **options): from redisession.backend import conf, get_redis # don't bother to check if hash mode is disabled diff --git a/redisession/tests.py b/redisession/tests.py index a1a7146..2a1d1fd 100644 --- a/redisession/tests.py +++ b/redisession/tests.py @@ -1,13 +1,18 @@ +# -*- coding: utf-8 -*- from django.conf import settings from django.contrib.sessions.tests import SessionTestsMixin from django.utils import unittest + conf = getattr(settings, 'REDIS_SESSION_CONFIG', {}) + class RedisSessionTests(SessionTestsMixin, unittest.TestCase): + override_config = { 'USE_HASH': False } + def setUp(self): test_conf = conf.copy() test_conf.update(self.override_config) @@ -16,9 +21,6 @@ def setUp(self): self.backend = SessionStore self.session = self.backend() - def test_decode_django12(self): - "We don't support Django 1.2 ever." - def test_delete(self): self.session.save() self.session.delete(self.session.session_key) @@ -32,6 +34,7 @@ def set_session_key(session): class RedisHashSessionTests(RedisSessionTests): + override_config = { 'USE_HASH': True } diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..3d6d5f4 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,12 @@ +[coverage:run] +omit = + *tests.py + debreach/models.py +include = debreach/* + +[coverage:report] +exclude_lines = + pragma: no cover + def __repr__ + raise NotImplementedError + if __name__ == .__main__.: diff --git a/setup.py b/setup.py index fcfae7c..cc77a16 100644 --- a/setup.py +++ b/setup.py @@ -1,24 +1,74 @@ -from distutils.core import setup +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import re +import sys +from setuptools import setup, find_packages + + +def get_version(package): + ''' + Return package version as listed in `__version__` in `init.py`. + ''' + init_py = open(os.path.join(package, '__init__.py')).read() + return re.search( + '^__version__ = [\'"]([^\'"]+)[\'"]', init_py, re.MULTILINE + ).group(1) + + +version = get_version('redisession') + + +if sys.argv[-1] == 'publish': + os.system('python setup.py sdist upload') + os.system('python setup.py bdist_wheel upload') + args = {'version': version} + print('You probably want to also tag the version now:') + print(' git tag -a {version} -m \'version {version}\''.format( + **args)) + print(' git push --tags') + sys.exit() + setup( - name='django-redisession', - version='0.3.1', + name='django-redisession-ng', + version=version, license='MIT', author='Li Meng', author_email='liokmkoil@gmail.com', - packages=['redisession', 'redisession.management', 'redisession.management.commands'], - description='A Redis-based Django session engine for django.contrib.sessions.', + maintainer='Luke Pomfrey', + maintainer_email='lpomfrey@gmail.com', + packages=find_packages(exclude=('test_project', 'docs')), + description=( + 'A Redis-based Django session engine for django.contrib.sessions.' + ), long_description=open('README.rst').read(), - url='https://github.com/liokm/django-redisession', - download_url='https://github.com/liokm/django-redisession', + url='https://github.com/lpomfrey/django-redisession', + download_url='https://github.com/lpomfrey/django-redisession', + install_requires=[ + 'redis', + ], + tests_require=[ + 'django', + ], classifiers=[ - 'Development Status :: 4 - Beta', 'Environment :: Web Environment', - 'Framework :: Django', 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', + 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', + 'Framework :: Django', + 'Framework :: Django :: 1.8', + 'Framework :: Django :: 1.9', 'Programming Language :: Python', - 'Topic :: Software Development :: Libraries :: Python Modules', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', + 'Topic :: Internet :: WWW/HTTP' ] )