Skip to content
This repository has been archived by the owner on Sep 22, 2020. It is now read-only.

Commit

Permalink
Add RedisCacheBackend
Browse files Browse the repository at this point in the history
  • Loading branch information
relekang committed Mar 23, 2015
1 parent 951403c commit 4ef1532
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 12 deletions.
4 changes: 4 additions & 0 deletions docs/cache-backends.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ directly in ``get_thumbnail`` if the cache returns it.
.. autoclass:: thumbnails.cache_backends.DjangoCacheBackend
:members:
:private-members:

.. autoclass:: thumbnails.cache_backends.RedisCacheBackend
:members:
:private-members:
24 changes: 20 additions & 4 deletions tests/test_cache_backends.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# -*- coding: utf-8 -*-
import unittest

from thumbnails.cache_backends import BaseCacheBackend, DjangoCacheBackend, SimpleCacheBackend
from thumbnails.cache_backends import (BaseCacheBackend, DjangoCacheBackend, RedisCacheBackend,
SimpleCacheBackend)
from thumbnails.images import Thumbnail

from .compat import mock
from .utils import has_django
from .utils import has_django, has_redis


class CacheBackendTestMixin(object):

def setUp(self):
self.backend = self.BACKEND()

Expand All @@ -25,7 +25,6 @@ def test_set_and_get(self):


class BaseCacheBackendTestCase(unittest.TestCase):

def setUp(self):
self.backend = BaseCacheBackend()

Expand All @@ -52,3 +51,20 @@ class SimpleCacheBackendTestCase(CacheBackendTestMixin, unittest.TestCase):
@unittest.skipIf(not has_django(), 'Django is not installed')
class DjangoCacheBackendTestCase(CacheBackendTestMixin, unittest.TestCase):
BACKEND = DjangoCacheBackend


@unittest.skipIf(not has_redis(), 'Redis not installed')
class RedisCacheBackendTestCase(CacheBackendTestMixin, unittest.TestCase):
BACKEND = RedisCacheBackend

def test_get_settings(self):
settings = self.backend.get_settings()
self.assertEqual(settings['host'], '127.0.0.1')
self.assertEqual(settings['port'], 6379)
self.assertEqual(settings['db'], 0)

self.backend.connection_uri = 'redis://example.com:1234/9'
settings = self.backend.get_settings()
self.assertEqual(settings['host'], 'example.com')
self.assertEqual(settings['port'], 1234)
self.assertEqual(settings['db'], 9)
19 changes: 12 additions & 7 deletions tests/utils.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
# -*- coding: utf-8 -*-
import importlib


def has_django():
def has_installed(dependency):
try:
import django # noqa isort:skip
importlib.import_module(dependency)
return True
except ImportError:
return False


def has_django():
return has_installed('django')


def has_pillow():
try:
from PIL import Image # noqa isort:skip
return True
except ImportError:
return False
return has_installed('PIL.Image')


def has_redis():
return has_installed('redis')
59 changes: 59 additions & 0 deletions thumbnails/cache_backends.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*-
import pickle
import re

from thumbnails import settings


Expand Down Expand Up @@ -77,3 +80,59 @@ def _get(self, thumbnail_name):

def _set(self, thumbnail_name, thumbnail):
self.cache.set(thumbnail_name.replace('/', ''), thumbnail, timeout=self.TIMEOUT)


class RedisCacheBackend(BaseCacheBackend):
"""
Cache backend that connects to Redis with redis-py. It uses the
``THUMBNAIL_CACHE_CONNECTION_URI`` setting as connection configuration.
The setting should contain a string on the form: ``redis://host:port/db``,
example: ``redis://127.0.0.1:6379/0`` will give the default settings.
This backend does not sett time-to-live on the cached items, thus they will
be in redis until they are deleted.
If the settings string is not good enough for your configuration, it is possible to extend
this backend and override ``get_settings``.
"""

def __init__(self):
import redis
super(RedisCacheBackend, self).__init__()
self.connection_uri = getattr(settings, 'THUMBNAIL_CACHE_CONNECTION_URI', None)
self.redis = redis.StrictRedis(**self.get_settings())

def _get(self, thumbnail_name):
thumbnail = self.redis.get(thumbnail_name)
if thumbnail is not None:
return pickle.loads(thumbnail)

def _set(self, thumbnail_name, thumbnail):
return self.redis.set(thumbnail_name, pickle.dumps(thumbnail))

def get_settings(self):
"""
This creates a dict with keyword arguments used to create the redis client.
It is used like ``redis.StrictClient(**self.get_settings())``. Thus, if the
settings string is not enough to generate the wanted setting you can override
this function.
:return: A dict with keyword arguments for the redis client constructor.
"""
host = '127.0.0.1'
port = 6379
db = 0
if self.connection_uri is not None:
match = re.match(r'redis://(?:([\w]+)@)?([\w\d\.]+):(\d+)(?:/(\d+))?', self.connection_uri)
if match:
if match.group(2):
host = match.group(2)
if match.group(3):
port = int(match.group(3))
if match.group(4):
db = int(match.group(4))

return {
'host': host,
'port': port,
'db': db
}
1 change: 1 addition & 0 deletions thumbnails/conf/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
THUMBNAIL_ENGINE = 'thumbnails.engines.PillowEngine'
THUMBNAIL_CACHE_BACKEND = 'thumbnails.cache_backends.SimpleCacheBackend'
THUMBNAIL_CACHE_TIMEOUT = 60 * 60 * 24 * 365
THUMBNAIL_CACHE_CONNECTION_URI = None
THUMBNAIL_STORAGE_BACKEND = 'thumbnails.storage_backends.FilesystemStorageBackend'

THUMBNAIL_SCALE_UP = False
Expand Down
3 changes: 2 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[tox]
envlist =
{py27,py34},
{py27,py34}-{django17,django18},
{py27,py34}-{django17,django18,redis},
py34-docs
skipsdist = True

Expand Down Expand Up @@ -31,6 +31,7 @@ deps =
django16: Django>=1.6,<1.7
django17: Django>=1.7,<1.8
django18: Django==1.8c1
redis: redis
docs: -r{toxinidir}/docs/requirements.txt

[testenv:py34-docs]
Expand Down

0 comments on commit 4ef1532

Please sign in to comment.