Permalink
Browse files

Added support for memcached.

  • Loading branch information...
1 parent f4c250c commit a7e6bdf9e3b3c730061c441239cd3c6bfb2725b1 @mmerickel mmerickel committed Mar 13, 2012
@@ -0,0 +1,52 @@
+import logging
+
+try:
+ import memcache
+except ImportError: # pragma: no cover
+ # fall back for Google App Engine -- hasnt been tested though
+ from google.appengine.api import memcache
+
+from anykeystore.interfaces import KeyValueStore
+from anykeystore.utils import coerce_timedelta
+
+
+log = logging.getLogger(__name__)
+
+class MemcachedStore(KeyValueStore):
+ def __init__(self,
+ servers=('localhost:11211',),
+ key_prefix='anykeystore.'):
+ self.servers = servers
+ self.key_prefix = key_prefix
+
+ def _get_conn(self):
+ """The Memcached connection, cached for this call"""
+ return memcache.Client(self.servers)
+
+ def _make_key(self, key):
+ return '%s%s' % (self.key_prefix, key)
+
+ def retrieve(self, key):
+ data = self._get_conn().get(self._make_key(key))
+ if data:
+ return data
+ raise KeyError
+
+ def store(self, key, value, expires=None):
+ key = self._make_key(key)
+ log.debug('Servers %s storing %s=%s' % (self.servers, key, value))
+
+ expiration = None
+ if expires is not None:
+ expiration = coerce_timedelta(expires).seconds
+ self._get_conn().set(key, value, expiration or 0)
+
+ def delete(self, key):
+ key = self._make_key(key)
+ log.debug('Deleting %s', key)
+ self._get_conn().delete(key)
+
+ def purge_expired(self):
+ pass
+
+backend = MemcachedStore
@@ -9,7 +9,7 @@
class MongoDBStore(KeyValueStore):
- """ Simple storage via SQLAlchemy.
+ """ Simple storage via MongoDB.
:param db: The name of the mongo database.
:param collection: Optional (default="key_storage").
@@ -8,7 +8,15 @@
class RedisStore(KeyValueStore):
- """Redis Storage for Auth Provider"""
+ """ Simple storage via Redis.
+
+ :param db: The name of the redis database.
+ :param host: Redis server host.
+ :param port: Redis server port.
+ :param key_prefix: Key prefix to avoid colliding with other parts of
+ the redis key/value store.
+ """
+
def __init__(self, db=0, host='localhost', port=6379,
key_prefix='anykeystore.'):
self.host = host
@@ -0,0 +1,42 @@
+import time
+
+import unittest2 as unittest
+
+def setUpModule():
+ try: # pragma: no cover
+ import memcache
+ except ImportError: # pragma: no cover
+ raise unittest.SkipTest(
+ 'must install python-memcached to run memcached tests')
+
+class TestRedisStore(unittest.TestCase):
+
+ def _makeOne(self):
+ from anykeystore.backends.memcached import MemcachedStore
+ store = MemcachedStore(key_prefix='test_anykey.')
+ return store
+
+ def test_it(self):
+ store = self._makeOne()
+ store.store('foo', 'bar')
+ value = store.retrieve('foo')
+ self.assertEqual(value, 'bar')
+
+ def test_it_delete(self):
+ store = self._makeOne()
+ store.store('foo', 'bar')
+ store.delete('foo')
+ self.assertRaises(KeyError, store.retrieve, 'foo')
+
+ def test_it_old(self):
+ store = self._makeOne()
+ store.store('foo', 'bar', expires=1)
+ time.sleep(1.01)
+ self.assertRaises(KeyError, store.retrieve, 'foo')
+
+ def test_it_purge(self):
+ store = self._makeOne()
+ store.store('foo', 'bar', expires=1)
+ time.sleep(1.01)
+ store.purge_expired()
+ self.assertRaises(KeyError, store.retrieve, 'foo')
@@ -12,7 +12,7 @@ class TestRedisStore(unittest.TestCase):
def _makeOne(self):
from anykeystore.backends.redis import RedisStore
- store = RedisStore()
+ store = RedisStore(key_prefix='test_anykey.')
return store
def test_it(self):
View
@@ -9,12 +9,14 @@ deps =
sqlalchemy
pymongo
redis
+ python-memcached
[testenv:py32]
commands =
python setup.py test -q
deps =
sqlalchemy
+ python3-memcached
[testenv:cover]
basepython =
@@ -28,6 +30,7 @@ deps =
sqlalchemy
pymongo
redis
+ python-memcached
# we separate coverage into its own testenv because a) "last run wins" wrt
# cobertura jenkins reporting and b) pypy and jython can't handle any

0 comments on commit a7e6bdf

Please sign in to comment.