This repository has been archived by the owner on Apr 27, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from pior/master
Add a caching extension for redis
- Loading branch information
Showing
18 changed files
with
260 additions
and
110 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
|
||
|
||
def includeme(config): | ||
config.include('.versioners') | ||
config.include('.versioner') | ||
config.include('.key_versioner') | ||
config.include('.serializers') | ||
config.include('.cache') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
|
||
|
||
class Base(Exception): | ||
"""Base exception for pyramid_caching""" | ||
|
||
class CacheError(Base): | ||
"""Base exception for cache client""" | ||
|
||
class CacheKeyAlreadyExists(CacheError): | ||
"""Trying to create an existing key in cache""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
from __future__ import absolute_import | ||
|
||
import os | ||
|
||
from redis import StrictRedis | ||
|
||
from pyramid_caching.exc import (CacheError, CacheKeyAlreadyExists) | ||
|
||
def includeme(config): | ||
include_cache_store(config) | ||
include_version_store(config) | ||
|
||
def include_cache_store(config): | ||
uri = os.environ['CACHE_STORE_REDIS_URI'] | ||
client = StrictRedis.from_url(uri) | ||
config.add_cache_client(RedisCacheWrapper(client)) | ||
|
||
def include_version_store(config): | ||
uri = os.environ['VERSION_STORE_REDIS_URI'] | ||
client = StrictRedis.from_url(uri) | ||
config.add_key_version_client(RedisVersionWrapper(client)) | ||
|
||
|
||
class RedisCacheWrapper(object): | ||
|
||
def __init__(self, client): | ||
self.default_expiration = 3600 * 24 * 7 # 7 days | ||
self.client = client | ||
|
||
def add(self, key, value, expiration=None): | ||
""" | ||
Note: Redis will only LRU evince volatile keys. | ||
Default expiration: 7 days | ||
""" | ||
if expiration is None: | ||
expiration = self.default_expiration | ||
|
||
rvalue = self.client.set(key, value, ex=expiration, nx=True) | ||
if rvalue is None: | ||
raise CacheKeyAlreadyExists(key) | ||
|
||
def get(self, key): | ||
return self.client.get(key) | ||
|
||
def flush_all(self): | ||
self.client.flushall() | ||
|
||
|
||
class RedisVersionWrapper(object): | ||
"""Redis implementation of the IKeyVersioner interface. | ||
This KeyVersioner use Redis ability to namespace content with seperate | ||
db, thus not prefixing the keys with anything like 'version:'. | ||
The default value for this implementation is 0 since the INCR operation | ||
enforce a value of 1 after executing on a non-existing key. | ||
See `Redis documentation for INCR <http://redis.io/commands/incr>`_ | ||
Note: the return value are always string type. | ||
""" | ||
|
||
def __init__(self, client): | ||
self.client = client | ||
|
||
def get(self, key): | ||
value = self.client.get(key) | ||
return value if value is not None else '0' | ||
|
||
def get_multi(self, keys): | ||
return [v if v is not None else '0' | ||
for v in self.client.mget(keys)] | ||
|
||
def incr(self, key): | ||
self.client.incr(key) | ||
|
||
def flush_all(self): | ||
self.client.flushall() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import logging | ||
|
||
from zope.interface import classImplements | ||
|
||
from pyramid_caching.interfaces import IKeyVersioner | ||
|
||
log = logging.getLogger(__name__) | ||
|
||
|
||
def includeme(config): | ||
config.add_directive('get_key_version_client', get_key_version_client, | ||
action_wrap=False) | ||
config.add_directive('add_key_version_client', add_key_version_client) | ||
|
||
|
||
def get_key_version_client(config_or_request): | ||
return config_or_request.registry.getUtility(IKeyVersioner) | ||
|
||
|
||
def add_key_version_client(config, key_versioner): | ||
if not IKeyVersioner.implementedBy(key_versioner.__class__): | ||
log.debug('assuming %r implements %r', key_versioner.__class__, IKeyVersioner) | ||
classImplements(key_versioner.__class__, IKeyVersioner) | ||
|
||
def register(): | ||
log.debug('registering KeyVersioner %r', key_versioner) | ||
config.registry.registerUtility(key_versioner, IKeyVersioner) | ||
|
||
config.action((__name__, 'key_version_client'), register, order=0) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import unittest | ||
|
||
from nose_parameterized import parameterized | ||
|
||
from pyramid_caching.ext.redis import RedisVersionWrapper | ||
|
||
from redis import StrictRedis | ||
|
||
def get_redis(): | ||
return RedisVersionWrapper(StrictRedis()) | ||
|
||
KEY_VERSIONERS = [ | ||
("redis", get_redis, '0'), | ||
] | ||
|
||
class TestKeyVersioner(unittest.TestCase): | ||
|
||
TEST_VALUE = '4' # Best creative testing value | ||
|
||
def setUp(self): | ||
get_redis().flush_all() | ||
|
||
@parameterized.expand(KEY_VERSIONERS) | ||
def test_interface(self, name, get_key_versioner, default_value): | ||
from zope.interface.verify import verifyObject | ||
from pyramid_caching.interfaces import IKeyVersioner | ||
|
||
key_versioner = get_key_versioner() | ||
|
||
self.assertTrue(verifyObject(IKeyVersioner, key_versioner)) | ||
|
||
@parameterized.expand(KEY_VERSIONERS) | ||
def test_get(self, name, get_key_versioner, default_value): | ||
key_versioner = get_key_versioner() | ||
|
||
|
||
self.assertEqual(key_versioner.get('FOO'), default_value) | ||
|
||
self.assertEqual(key_versioner.get('FOO'), default_value) | ||
|
||
for _ in range(int(self.TEST_VALUE)): | ||
key_versioner.incr('FOO') | ||
|
||
self.assertEqual(key_versioner.get('FOO'), self.TEST_VALUE) | ||
|
||
self.assertEqual(key_versioner.get('FOO'), self.TEST_VALUE) | ||
|
||
@parameterized.expand(KEY_VERSIONERS) | ||
def test_get_multi(self, name, get_key_versioner, default_value): | ||
key_versioner = get_key_versioner() | ||
|
||
KEYS = ['FOO', 'BAR', '2000'] | ||
VERSIONS = ['0', '0', '0'] | ||
|
||
self.assertEqual(key_versioner.get_multi(KEYS), VERSIONS) | ||
|
||
self.assertEqual(key_versioner.get_multi(KEYS), VERSIONS) | ||
|
||
for _ in range(int(self.TEST_VALUE)): | ||
key_versioner.incr('BAR') | ||
VERSIONS = ['0', str(self.TEST_VALUE), '0'] | ||
|
||
self.assertEqual(key_versioner.get_multi(KEYS), VERSIONS) | ||
|
||
self.assertEqual(key_versioner.get_multi(KEYS), VERSIONS) | ||
|
||
for _ in range(42): | ||
key_versioner.incr('2000') | ||
VERSIONS = ['0', str(self.TEST_VALUE), '42'] | ||
|
||
self.assertEqual(key_versioner.get_multi(KEYS), VERSIONS) | ||
|
||
self.assertEqual(key_versioner.get_multi(KEYS), VERSIONS) |
Oops, something went wrong.