├── .gitignore ├── README.rst ├── django_kvstore ├── __init__.py ├── backends │ ├── __init__.py │ ├── base.py │ ├── db.py │ ├── googleappengine.py │ ├── locmem.py │ ├── memcached.py │ ├── redisdj.py │ ├── sdb.py │ └── tokyotyrant.py └── models.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.pyc 3 | ._* 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | An extensible key-value store backend for Django applications. 2 | 3 | This module provides an abstraction layer for accessing a key-value storage. 4 | 5 | Configuring your key-value store is a matter of adding a statement in this 6 | form to your Django settings module:: 7 | 8 | KEY_VALUE_STORE_BACKEND = 'scheme://store?parameters' 9 | 10 | Where ``scheme`` is one of the following, persistent stores: 11 | 12 | * db (local table accessed through Django's database connection) 13 | * googleappengine (Google AppEngine data store) 14 | * sdb (Amazon SimpleDB) 15 | * tokyotyrant (Tokyo Tyrant) 16 | * redis (Redis) 17 | 18 | And some non-persistent stores, provided mainly for testing purposes: 19 | 20 | * locmem 21 | * memcached 22 | 23 | ``store`` and ``parameters`` varies from one backend to another. Refer 24 | to the documentation included in each backend implementation for further 25 | details. 26 | 27 | You can define a django_kvstore-backed custom model, in a fashion similar 28 | to Django models (although it does not support querying, except by primary 29 | key lookup). 30 | 31 | Here's an example of a custom model class using django_kvstore:: 32 | 33 | from django_kvstore import models 34 | 35 | class MyData(models.Model): 36 | my_key = models.Field(pk=True) 37 | foo = models.Field() 38 | bar = models.Field() 39 | 40 | Typical usage for such a model:: 41 | 42 | key = "something_unique" 43 | data = MyData.get(key) 44 | if data is None: 45 | data = MyData(my_key=key) 46 | data.foo = "foo" 47 | data.bar = "bar" 48 | data.save() 49 | 50 | and deletion:: 51 | 52 | key = "something_unique" 53 | data = MyData.get(key) 54 | if data is not None: 55 | data.delete() 56 | -------------------------------------------------------------------------------- /django_kvstore/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | An extensible key-value store backend for Django applications. 3 | 4 | This package defines a set of key-value store backends that all conform to a 5 | simple API. A key-value store is a simple data storage backend that is similar 6 | to a cache. Unlike a cache, items are persisted to disk, and are not lost when 7 | the backend restarts.""" 8 | 9 | __version__ = '1.0' 10 | __date__ = '26 December 2010' 11 | __author__ = 'Six Apart Ltd.' 12 | __credits__ = """Mike Malone 13 | Brad Choate""" 14 | 15 | from cgi import parse_qsl 16 | from django.conf import settings 17 | from django.core import signals 18 | 19 | # Names for use in settings file --> name of module in "backends" directory. 20 | # Any backend scheeme that is not in this dictionary is treated as a Python 21 | # import path to a custom backend. 22 | BACKENDS = { 23 | 'memcached': 'memcached', 24 | 'tokyotyrant': 'tokyotyrant', 25 | 'locmem': 'locmem', 26 | 'db': 'db', 27 | 'simpledb': 'sdb', 28 | 'googleappengine': 'googleappengine', 29 | 'redis': 'redisdj', 30 | } 31 | 32 | class InvalidKeyValueStoreBackend(Exception): pass 33 | 34 | def get_kvstore(backend_uri): 35 | if backend_uri.find(':') == -1: 36 | raise InvalidKeyValueStoreBackend("Backend URI must start with scheme://") 37 | scheme, rest = backend_uri.split(':', 1) 38 | if not rest.startswith('//'): 39 | raise InvalidKeyValueStoreBackend("Backend URI must start with scheme://") 40 | 41 | host = rest[2:] 42 | qpos = rest.find('?') 43 | if qpos != -1: 44 | params = dict(parse_qsl(rest[qpos+1:])) 45 | host = rest[2:qpos] 46 | else: 47 | params = {} 48 | if host.endswith('/'): 49 | host = host[:-1] 50 | 51 | if scheme in BACKENDS: 52 | module = __import__('django_kvstore.backends.%s' % BACKENDS[scheme], {}, {}, ['']) 53 | else: 54 | module = __import__(scheme, {}, {}, ['']) 55 | return getattr(module, 'StorageClass')(host, params) 56 | 57 | kvstore = get_kvstore(settings.KEY_VALUE_STORE_BACKEND) 58 | """A handle to the configured key-value store.""" 59 | 60 | # Some kv store backends need to do a cleanup at the end of 61 | # a request cycle. If the cache provides a close() method, wire 62 | # it up here. 63 | if hasattr(kvstore, 'close'): 64 | signals.request_finished.connect(kvstore.close) 65 | -------------------------------------------------------------------------------- /django_kvstore/backends/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saymedia/django-kvstore/85ea865f5cd6abe5401e720f1ae696e374c3b622/django_kvstore/backends/__init__.py -------------------------------------------------------------------------------- /django_kvstore/backends/base.py: -------------------------------------------------------------------------------- 1 | "Base key-value store abstract class." 2 | 3 | from django.core.exceptions import ImproperlyConfigured 4 | 5 | class InvalidKeyValueStoreBackendError(ImproperlyConfigured): 6 | pass 7 | 8 | class BaseStorage(object): 9 | def __init__(self, *args, **kwargs): 10 | pass 11 | 12 | def set(self, key, value): 13 | """Set a value in the key-value store.""" 14 | raise NotImplementedError 15 | 16 | def delete(self, key): 17 | """Delete a key from the key-value store. Fail silently.""" 18 | raise NotImplementedError 19 | 20 | def has_key(self, key): 21 | """Returns True if the key is in the store.""" 22 | return self.get(key) is not None 23 | 24 | def __contains__(self, key): 25 | """Returns true if the key is in the store.""" 26 | return self.has_key(key) 27 | -------------------------------------------------------------------------------- /django_kvstore/backends/db.py: -------------------------------------------------------------------------------- 1 | """ 2 | Database key-value store backend. 3 | 4 | Example configuration for Django settings: 5 | 6 | KEY_VALUE_STORE_BACKEND = 'db://table_name' 7 | 8 | You will need to create a database table for storing the key-value pairs 9 | when using this backend. The table should have two columns, 'kee' - capable of 10 | storing up to 255 characters, and 'value', which should be a text type 11 | (character blob). You can declare a regular Django model for this table, if 12 | you want Django's ``syncdb`` command to create it for you. Just make sure the 13 | table name Django uses is the same table name provided in the 14 | ``KEY_VALUE_STORE_BACKEND`` setting. 15 | """ 16 | 17 | import base64 18 | import logging 19 | from django_kvstore.backends.base import BaseStorage 20 | from django.db import connection, transaction, DatabaseError 21 | from django.db.utils import IntegrityError 22 | 23 | try: 24 | import cPickle as pickle 25 | except ImportError: 26 | import pickle 27 | 28 | 29 | class StorageClass(BaseStorage): 30 | def __init__(self, table, params): 31 | BaseStorage.__init__(self, params) 32 | self._table = table 33 | self.logger = logging.getLogger(__name__) 34 | 35 | def get(self, key): 36 | cursor = connection.cursor() 37 | cursor.execute("SELECT kee, value FROM %s WHERE kee = %%s" % self._table, [key]) 38 | row = cursor.fetchone() 39 | if row is None: 40 | return None 41 | return pickle.loads(base64.decodestring(row[1])) 42 | 43 | def set(self, key, value): 44 | encoded = base64.encodestring(pickle.dumps(value, 2)).strip() 45 | # report database errors after the atomic transaction has rolled back 46 | try: 47 | with transaction.atomic(): 48 | cursor = connection.cursor() 49 | cursor.execute("SELECT kee FROM %s WHERE kee = %%s" % self._table, [key]) 50 | if cursor.fetchone(): 51 | cursor.execute("UPDATE %s SET value = %%s WHERE kee = %%s" % self._table, [encoded, key]) 52 | else: 53 | cursor.execute("INSERT INTO %s (kee, value) VALUES (%%s, %%s)" % self._table, [key, encoded]) 54 | except (DatabaseError, IntegrityError): 55 | # Report the atomic failure 56 | self.logger.info("set operation for %s failed and has been rolled back", key) 57 | return False 58 | return True 59 | 60 | @transaction.atomic() 61 | def delete(self, key): 62 | cursor = connection.cursor() 63 | cursor.execute("DELETE FROM %s WHERE kee = %%s" % self._table, [key]) 64 | 65 | def has_key(self, key): 66 | cursor = connection.cursor() 67 | cursor.execute("SELECT kee FROM %s WHERE kee = %%s" % self._table, [key]) 68 | return cursor.fetchone() is not None 69 | -------------------------------------------------------------------------------- /django_kvstore/backends/googleappengine.py: -------------------------------------------------------------------------------- 1 | """ 2 | A Google AppEngine key-value store backend. 3 | 4 | Example configuration for Django settings: 5 | 6 | KEY_VALUE_STORE_BACKEND = 'appengine://' 7 | 8 | """ 9 | 10 | import base64 11 | from base import BaseStorage, InvalidKeyValueStoreBackendError 12 | try: 13 | import cPickle as pickle 14 | except ImportError: 15 | import pickle 16 | 17 | try: 18 | from google.appengine.ext import db 19 | except ImportError: 20 | raise InvalidKeyValueStoreBackendError("googleappengine key-value store backend requires google.appengine.ext.db import") 21 | 22 | 23 | class DjangoKVStore(db.Model): 24 | value = db.BlobProperty() 25 | 26 | 27 | class StorageClass(BaseStorage): 28 | def __init__(self, table=None, params=None): 29 | BaseStorage.__init__(self, params) 30 | self._model = DjangoKVStore 31 | 32 | def _get(self, key): 33 | return self._model.get_by_key_name('k:' + key) 34 | 35 | def get(self, key): 36 | row = self._get(key) 37 | if row is None: 38 | return None 39 | return pickle.loads(base64.decodestring(row.value)) 40 | 41 | def set(self, key, value): 42 | encoded = base64.encodestring(pickle.dumps(value, 2)).strip() 43 | row = self._get(key) 44 | if row is None: 45 | row = self._model(key_name='k:'+key) 46 | row.value = encoded 47 | row.save() 48 | return True 49 | 50 | def delete(self, key): 51 | row = self._get(key) 52 | if row is not None: 53 | row.delete() 54 | 55 | def has_key(self, key): 56 | return self._get(key) is not None 57 | -------------------------------------------------------------------------------- /django_kvstore/backends/locmem.py: -------------------------------------------------------------------------------- 1 | """ 2 | Thread-safe in-memory key-value store backend. 3 | 4 | Just for testing. This isn't persistent. Don't actually use it. 5 | 6 | Example configuration for Django settings: 7 | 8 | KEY_VALUE_STORE_BACKEND = 'locmem://' 9 | 10 | """ 11 | 12 | try: 13 | import cPickle as pickle 14 | except ImportError: 15 | import pickle 16 | 17 | from base import BaseStorage 18 | from django.utils.synch import RWLock 19 | 20 | class StorageClass(BaseStorage): 21 | def __init__(self, _, params): 22 | BaseStorage.__init__(self, params) 23 | self._db = {} 24 | self._lock = RWLock() 25 | 26 | def set(self, key, value): 27 | self._lock.writer_enters() 28 | try: 29 | self._db[key] = pickle.dumps(value) 30 | finally: 31 | self._lock.writer_leaves() 32 | 33 | def get(self, key): 34 | self._lock.reader_enters() 35 | # Python 2.3 and 2.4 don't allow combined try-except-finally blocks. 36 | try: 37 | try: 38 | return pickle.loads(self._db[key]) 39 | except KeyError: 40 | return None 41 | finally: 42 | self._lock.reader_leaves() 43 | 44 | def delete(self, key): 45 | self._lock.write_enters() 46 | # Python 2.3 and 2.4 don't allow combined try-except-finally blocks. 47 | try: 48 | try: 49 | del self._db[key] 50 | except KeyError: 51 | pass 52 | finally: 53 | self._lock.writer_leaves() 54 | 55 | def has_key(self, key): 56 | self._lock.reader_enters() 57 | try: 58 | return key in self._db 59 | finally: 60 | self._lcok.reader_leaves() 61 | -------------------------------------------------------------------------------- /django_kvstore/backends/memcached.py: -------------------------------------------------------------------------------- 1 | """ 2 | Memcache key-value store backend 3 | 4 | Just for testing. This isn't persistent. Don't actually use it. 5 | 6 | Example configuration for Django settings: 7 | 8 | KEY_VALUE_STORE_BACKEND = 'memcached://hostname:port' 9 | 10 | """ 11 | 12 | from base import BaseStorage, InvalidKeyValueStoreBackendError 13 | from django.utils.encoding import smart_unicode, smart_str 14 | 15 | try: 16 | import cmemcache as memcache 17 | except ImportError: 18 | try: 19 | import memcache 20 | except: 21 | raise InvalidKeyValueStoreBackendError("Memcached key-value store backend requires either the 'memcache' or 'cmemcache' library") 22 | 23 | class StorageClass(BaseStorage): 24 | def __init__(self, server, params): 25 | BaseStorage.__init__(self, params) 26 | self._db = memcache.Client(server.split(';')) 27 | 28 | def set(self, key, value): 29 | if isinstance(value, unicode): 30 | value = value.encode('utf-8') 31 | self._db.set(smart_str(key), value, 0) 32 | 33 | def get(self, key): 34 | val = self._db.get(smart_str(key)) 35 | if isinstance(val, basestring): 36 | return smart_unicode(val) 37 | else: 38 | return val 39 | 40 | def delete(self, key): 41 | self._db.delete(smart_str(key)) 42 | 43 | def close(self, **kwargs): 44 | self._db.disconnect_all() 45 | -------------------------------------------------------------------------------- /django_kvstore/backends/redisdj.py: -------------------------------------------------------------------------------- 1 | """ 2 | Redis key-value store backend. 3 | 4 | Example configuration for Django settings: 5 | 6 | KEY_VALUE_STORE_BACKEND = 'redis://hostname:port' 7 | 8 | port is optional. If none is given, the port specified in redis.conf will be used. 9 | 10 | """ 11 | import base64 12 | from base import BaseStorage, InvalidKeyValueStoreBackendError 13 | from django.utils.encoding import smart_unicode, smart_str 14 | 15 | try: 16 | import redis 17 | except ImportError: 18 | raise InvalidKeyValueStoreBackendError("The Redis key-value store backend requires the Redis python client.") 19 | 20 | try: 21 | import cPickle as pickle 22 | except ImportError: 23 | import pickle 24 | 25 | class StorageClass(BaseStorage): 26 | 27 | def __init__(self, server, params): 28 | if ':' in server: 29 | host, port = server.split(':') 30 | port = int(port) 31 | else: 32 | host, port = server, None 33 | params['port'] = port 34 | BaseStorage.__init__(self, params) 35 | self._db = redis.Redis(host=host, **params) 36 | 37 | def set(self, key, value): 38 | encoded = base64.encodestring(pickle.dumps(value, 2)).strip() 39 | self._db.set(smart_str(key), encoded) 40 | 41 | def get(self, key): 42 | val = self._db.get(smart_str(key)) 43 | if val is None: 44 | return None 45 | return pickle.loads(base64.decodestring(val)) 46 | 47 | def delete(self, key): 48 | self._db.delete(smart_str(key)) 49 | 50 | def close(self, **kwargs): 51 | pass 52 | -------------------------------------------------------------------------------- /django_kvstore/backends/sdb.py: -------------------------------------------------------------------------------- 1 | """ 2 | Amazon SimpleDB key-value store backend 3 | 4 | Example configuration for Django settings: 5 | 6 | KEY_VALUE_STORE_BACKEND = 'sdb://?aws_access_key=&aws_secret_access_key=' 7 | 8 | """ 9 | 10 | 11 | from base import BaseStorage, InvalidKeyValueStoreBackendError 12 | from django.core.exceptions import ImproperlyConfigured 13 | from django.utils.encoding import smart_unicode, smart_str 14 | from django.utils import simplejson 15 | 16 | try: 17 | import simpledb 18 | except ImportError: 19 | raise InvalidKeyValueStoreBackendError("SipmleDB key-value store backend requires the 'python-simpledb' library") 20 | 21 | class StorageClass(BaseStorage): 22 | def __init__(self, domain, params): 23 | BaseStorage.__init__(self, params) 24 | params = dict(params) 25 | try: 26 | aws_access_key = params['aws_access_key'] 27 | aws_secret_access_key = params['aws_secret_access_key'] 28 | except KeyError: 29 | raise ImproperlyConfigured("Incomplete configuration of SimpleDB key-value store. Required parameters: 'aws_access_key', and 'aws_secret_access_key'.") 30 | self._db = simpledb.SimpleDB(aws_access_key, aws_secret_access_key) 31 | self._domain = self._db[domain] 32 | 33 | def set(self, key, value): 34 | if isinstance(value, unicode): 35 | value = value.encode('utf-8') 36 | self._domain[smart_str(key)] = {'value': simplejson.dumps(value)} 37 | 38 | def get(self, key): 39 | val = self._domain[smart_str(key)].get('value', None) 40 | if isinstance(val, basestring): 41 | return simplejson.loads(val) 42 | else: 43 | return val 44 | 45 | def delete(self, key): 46 | del self._domain[smart_str(key)] 47 | 48 | def close(self, **kwargs): 49 | pass 50 | -------------------------------------------------------------------------------- /django_kvstore/backends/tokyotyrant.py: -------------------------------------------------------------------------------- 1 | """ 2 | Memcache key-value store backend 3 | 4 | Just for testing. This isn't persistent. Don't actually use it. 5 | 6 | Example configuration for Django settings: 7 | 8 | KEY_VALUE_STORE_BACKEND = 'tokyotyrant://hostname:port 9 | 10 | """ 11 | 12 | from base import BaseStorage, InvalidKeyValueStoreBackendError 13 | from django.core.exceptions import ImproperlyConfigured 14 | from django.utils.encoding import smart_unicode, smart_str 15 | from django.utils import simplejson 16 | 17 | try: 18 | import pytyrant 19 | except ImportError: 20 | raise InvalidKeyValueStoreBackendError("Tokyotyrant key-value store backend requires the 'pytyrant' library") 21 | 22 | class StorageClass(BaseStorage): 23 | def __init__(self, server, params): 24 | BaseStorage.__init__(self, params) 25 | host, port = server.split(':') 26 | try: 27 | port = int(port) 28 | except ValueError: 29 | raise ImproperlyConfigured("Invalid port provided for tokyo-tyrant key-value store backend") 30 | self._db = pytyrant.PyTyrant.open(host, port) 31 | 32 | def set(self, key, value): 33 | if isinstance(value, unicode): 34 | value = value.encode('utf-8') 35 | self._db[smart_str(key)] = simplejson.dumps(value) 36 | 37 | def get(self, key): 38 | val = self._db.get(smart_str(key)) 39 | if isinstance(val, basestring): 40 | return simplejson.loads(val) 41 | else: 42 | return val 43 | 44 | def delete(self, key): 45 | del self._db[smart_str(key)] 46 | 47 | def close(self, **kwargs): 48 | pass 49 | # Er, should be closing after each request..? But throws 50 | # a 'Bad File Descriptor' exception if we do (presumably because 51 | # something's trying to use a connection that's already been 52 | # closed... 53 | #self._db.close() 54 | -------------------------------------------------------------------------------- /django_kvstore/models.py: -------------------------------------------------------------------------------- 1 | from django_kvstore import kvstore 2 | 3 | 4 | class FieldError(Exception): pass 5 | 6 | KV_PREFIX = '__KV_STORE_::' 7 | 8 | def generate_key(cls, pk): 9 | return str('%s%s.%s:%s' % (KV_PREFIX, cls.__module__, cls.__name__, pk)) 10 | 11 | 12 | class Field(object): 13 | def __init__(self, default=None, pk=False): 14 | self.default = default 15 | self.pk = pk 16 | 17 | def install(self, name, cls): 18 | setattr(cls, name, self.default) 19 | 20 | def decode(self, value): 21 | """Decodes an object from the datastore into a python object.""" 22 | return value 23 | 24 | def encode(self, value): 25 | """Encodes an object into a value suitable for the backend datastore.""" 26 | return value 27 | 28 | 29 | class ModelMetaclass(type): 30 | """ 31 | Metaclass for `kvstore.models.Model` instances. Installs 32 | `kvstore.models.Field` and `kvstore.models.Key` instances 33 | declared as attributes of the new class. 34 | 35 | """ 36 | 37 | def __new__(cls, name, bases, attrs): 38 | fields = {} 39 | 40 | for base in bases: 41 | if isinstance(base, ModelMetaclass): 42 | fields.update(base.fields) 43 | 44 | new_fields = {} 45 | # Move all the class's attributes that are Fields to the fields set. 46 | for attrname, field in attrs.items(): 47 | if isinstance(field, Field): 48 | new_fields[attrname] = field 49 | if field.pk: 50 | # Add key_field attr so we know what the key is 51 | if 'key_field' in attrs: 52 | raise FieldError("Multiple key fields defined for model '%s'" % name) 53 | attrs['key_field'] = attrname 54 | elif attrname in fields: 55 | # Throw out any parent fields that the subclass defined as 56 | # something other than a field 57 | del fields[attrname] 58 | 59 | fields.update(new_fields) 60 | attrs['fields'] = fields 61 | new_cls = super(ModelMetaclass, cls).__new__(cls, name, bases, attrs) 62 | 63 | for field, value in new_fields.items(): 64 | new_cls.add_to_class(field, value) 65 | 66 | return new_cls 67 | 68 | def add_to_class(cls, name, value): 69 | if hasattr(value, 'install'): 70 | value.install(name, cls) 71 | else: 72 | setattr(cls, name, value) 73 | 74 | 75 | class Model(object): 76 | 77 | __metaclass__ = ModelMetaclass 78 | 79 | def __init__(self, **kwargs): 80 | for name, value in kwargs.items(): 81 | setattr(self, name, value) 82 | 83 | def to_dict(self): 84 | d = {} 85 | for name, field in self.fields.items(): 86 | d[name] = field.encode(getattr(self, name)) 87 | return d 88 | 89 | def save(self): 90 | d = self.to_dict() 91 | kvstore.set(generate_key(self.__class__, self._get_pk_value()), d) 92 | 93 | def delete(self): 94 | kvstore.delete(generate_key(self.__class__, self._get_pk_value())) 95 | 96 | def _get_pk_value(self): 97 | return getattr(self, self.key_field) 98 | 99 | @classmethod 100 | def from_dict(cls, fields): 101 | for name, value in fields.items(): 102 | # Keys can't be unicode to work as **kwargs. Must delete and re-add 103 | # otherwise the dict won't change the type of the key. 104 | if name in cls.fields: 105 | if isinstance(name, unicode): 106 | del fields[name] 107 | name = name.encode('utf-8') 108 | fields[name] = cls.fields[name].decode(value) 109 | else: 110 | del fields[name] 111 | return cls(**fields) 112 | 113 | @classmethod 114 | def get(cls, id): 115 | fields = kvstore.get(generate_key(cls, id)) 116 | if fields is None: 117 | return None 118 | return cls.from_dict(fields) 119 | 120 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (c) 2009 Six Apart Ltd. 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are met: 8 | # 9 | # * Redistributions of source code must retain the above copyright notice, 10 | # this list of conditions and the following disclaimer. 11 | # 12 | # * Redistributions in binary form must reproduce the above copyright notice, 13 | # this list of conditions and the following disclaimer in the documentation 14 | # and/or other materials provided with the distribution. 15 | # 16 | # * Neither the name of Six Apart Ltd. nor the names of its contributors may 17 | # be used to endorse or promote products derived from this software without 18 | # specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | from setuptools import setup, find_packages 33 | from os.path import join, dirname 34 | 35 | try: 36 | long_description = open(join(dirname(__file__), 'README.rst')).read() 37 | except Exception: 38 | long_description = None 39 | 40 | setup( 41 | name='django-kvstore', 42 | version='1.0', 43 | description='An extensible key-value store backend for Django applications.', 44 | author='Six Apart Ltd.', 45 | author_email='python@sixapart.com', 46 | url='http://github.com/sixapart/django-kvstore', 47 | 48 | long_description=long_description, 49 | classifiers=[ 50 | 'Development Status :: 4 - Beta', 51 | 'Environment :: Console', 52 | 'Environment :: Web Environment', 53 | 'Framework :: Django', 54 | 'Intended Audience :: Developers', 55 | 'License :: OSI Approved :: BSD License', 56 | 'Operating System :: MacOS :: MacOS X', 57 | 'Operating System :: POSIX', 58 | 'Programming Language :: Python', 59 | 'Topic :: Internet :: WWW/HTTP', 60 | ], 61 | 62 | packages=find_packages(), 63 | provides=['django_kvstore'], 64 | include_package_data=True, 65 | zip_safe=True, 66 | requires=['Django(>=1.6)'], 67 | install_requires=['Django>=1.6'], 68 | ) 69 | --------------------------------------------------------------------------------