Skip to content

Commit

Permalink
Feature generic backend (#9)
Browse files Browse the repository at this point in the history
* Adding expiration feature

* Back to 100%

* Missed a branch

* Adding feature to readme

* First pass at generic backend

* Fixing tests

* Updating cache

* Moving file to __init__.py and changing Backends

* Comments
  • Loading branch information
Alex Kahan committed Oct 14, 2016
1 parent 217c271 commit b172c7b
Show file tree
Hide file tree
Showing 11 changed files with 270 additions and 244 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -9,7 +9,7 @@ Implements high level function caching to Redis with a decorator

## Setup
```python
from redis_cache.redis_cache import RedisCache
from redis_cache import RedisCache
r = RedisCache('localhost', 6379)
```

Expand Down
Empty file added backends/__init__.py
Empty file.
51 changes: 51 additions & 0 deletions backends/backend_base.py
@@ -0,0 +1,51 @@
class Backend(object):
"""
Generic backend base class. Provides an interface to use the cache
with any backend
"""

def __init__(self, *args, **kwargs):
pass

def get_cache(self, key):
"""
Gets the given key from the cache backend
:param key: The cache key to get
:return: The value in the cache in the case of a cache hit,
otherwise None
"""
raise NotImplementedError()

def set_cache(self, key, value):
"""
Sets the given key/value pair in the cache backend
:param key: The cache key
:param value: The cache value
:return: Response from cache backend
"""
raise NotImplementedError()

def set_cache_and_expire(self, key, value, expiration):
"""
Sets the key/value pair in the cache backend with an expiration TTL
:param key: The cache key
:param value: The cache value
:param expiration: The time to live (ttl) in seconds
:return: Response from cache backend
"""
raise NotImplementedError()

def invalidate_key(self, key):
"""
Removes the key from the cache
:param key: The cache key
:return: Response from cache Backend
"""
raise NotImplementedError()


class BackendException(Exception):
"""
Cache backend exception
"""
pass
Empty file added backends/redis/__init__.py
Empty file.
20 changes: 8 additions & 12 deletions redis_cache/redis_client.py → backends/redis/redis_backend.py
@@ -1,12 +1,11 @@
from backends.backend_base import Backend, BackendException
import socket


class RedisClient(object):
"""
Client to communicate with the Redis Server
"""
class RedisBackend(Backend):

def __init__(self, address, port):
super(RedisBackend, self).__init__()
self.delimiter = '\r\n'
self.address = address
self.port = port
Expand All @@ -25,7 +24,7 @@ def _make_request(self, command):
response = self._recv_data(s)
return response
except Exception as e:
raise RedisException(
raise BackendException(
'Unable to make request to Redis: %s' % str(e))
finally:
s.close()
Expand All @@ -49,7 +48,7 @@ def _build_command(self, *args):
command_args.extend(['$%s' % len(str(arg)), str(arg)])
return self.delimiter.join(command_args) + self.delimiter

def delete(self, key):
def invalidate_key(self, key):
"""
DEL method
:param key: The key to delete
Expand All @@ -58,7 +57,7 @@ def delete(self, key):
response = self._make_request(command)
return response.split(self.delimiter)[0]

def get(self, key):
def get_cache(self, key):
"""
GET method
:param key: The key to GET
Expand All @@ -67,7 +66,7 @@ def get(self, key):
response = self._make_request(command)
return response.split(self.delimiter)[1]

def set(self, key, value, **kwargs):
def set_cache(self, key, value, **kwargs):
"""
SET method
:param key: The key to SET
Expand All @@ -77,7 +76,7 @@ def set(self, key, value, **kwargs):
response = self._make_request(command)
return response.split(self.delimiter)[0]

def setex(self, key, value, expiration):
def set_cache_and_expire(self, key, value, expiration):
"""
SETEX command
:param key: The key to SET
Expand All @@ -88,6 +87,3 @@ def setex(self, key, value, expiration):
response = self._make_request(command)
return response.split(self.delimiter)[0]


class RedisException(Exception):
pass
35 changes: 18 additions & 17 deletions tests/test_redis_client.py → backends/redis/test_redis_backend.py
@@ -1,20 +1,20 @@
from redis_cache.redis_client import RedisClient
from backends.redis.redis_backend import RedisBackend
from unittest import TestCase
from mock import Mock, patch
from random import randint


class TestRedisClient(TestCase):
"""
Test cases for redis_client.py
Test cases for redis_backend.py
"""
def setUp(self):
self.address = '8.8.8.8'
self.port = 1234
self.redis_client = RedisClient(self.address, self.port)
self.redis_client = RedisBackend(self.address, self.port)
self.recv_count = 0

@patch('redis_cache.redis_client.socket')
@patch('backends.redis.redis_backend.socket')
def test_bad_send(self, mock_sock_lib):
"""
Tests that an appropriate Exception is raised when sending
Expand All @@ -27,7 +27,7 @@ def test_bad_send(self, mock_sock_lib):
mock_socket.send.side_effect = Exception(mock_error_message)

with self.assertRaises(Exception):
self.redis_client.get(key)
self.redis_client.get_cache(key)

self.assertEqual(mock_socket.recv.call_count, 0)
mock_socket.connect.assert_called_once_with(
Expand All @@ -36,7 +36,7 @@ def test_bad_send(self, mock_sock_lib):
'*2\r\n$3\r\nGET\r\n$%s\r\n%s\r\n' % (len(key), key))
mock_socket.close.assert_called_once_with()

@patch('redis_cache.redis_client.socket')
@patch('backends.redis.redis_backend.socket')
def test_bad_connection(self, mock_sock_lib):
"""
Tests that an appropriate Exception is raised when connecting to
Expand All @@ -49,15 +49,15 @@ def test_bad_connection(self, mock_sock_lib):
mock_socket.connect.side_effect = Exception(mock_error_message)

with self.assertRaises(Exception):
self.redis_client.get(key)
self.redis_client.get_cache(key)

self.assertEqual(mock_socket.recv.call_count, 0)
mock_socket.connect.assert_called_once_with(
(self.address, self.port))
self.assertEqual(mock_socket.send.call_count, 0)
mock_socket.close.assert_called_once_with()

@patch('redis_cache.redis_client.socket')
@patch('backends.redis.redis_backend.socket')
def test_get(self, mock_sock_lib):
"""
Tests GET
Expand All @@ -66,6 +66,7 @@ def test_get(self, mock_sock_lib):
expected_value = 'something that was cached'
mock_socket = Mock()
mock_sock_lib.socket.return_value = mock_socket

def socket_recv_side_effect(*args, **kwargs):
if self.recv_count == 0:
self.recv_count += 1
Expand All @@ -74,7 +75,7 @@ def socket_recv_side_effect(*args, **kwargs):
return ""
mock_socket.recv.side_effect = socket_recv_side_effect

cache_response = self.redis_client.get(key)
cache_response = self.redis_client.get_cache(key)

self.assertEqual(expected_value, cache_response)
mock_socket.recv.assert_called_with(self.redis_client.RECV_SIZE)
Expand All @@ -85,7 +86,7 @@ def socket_recv_side_effect(*args, **kwargs):
'*2\r\n$3\r\nGET\r\n$%s\r\n%s\r\n' % (len(key), key))
mock_socket.close.assert_called_once_with()

@patch('redis_cache.redis_client.socket')
@patch('backends.redis.redis_backend.socket')
def test_get_large_response(self, mock_sock_lib):
"""
Tests that the full large response is received from Redis
Expand All @@ -104,7 +105,7 @@ def socket_recv_side_effect(*args, **kwargs):
return ""

mock_socket.recv.side_effect = socket_recv_side_effect
cache_response = self.redis_client.get(key)
cache_response = self.redis_client.get_cache(key)

# self.assertEqual(expected_value, cache_response)
mock_socket.recv.assert_called_with(self.redis_client.RECV_SIZE)
Expand All @@ -115,7 +116,7 @@ def socket_recv_side_effect(*args, **kwargs):
'*2\r\n$3\r\nGET\r\n$%s\r\n%s\r\n' % (len(key), key))
mock_socket.close.assert_called_once_with()

@patch('redis_cache.redis_client.socket')
@patch('backends.redis.redis_backend.socket')
def test_set(self, mock_sock_lib):
"""
Tests SET
Expand All @@ -133,7 +134,7 @@ def socket_recv_side_effect(*args, **kwargs):
return ""
mock_socket.recv.side_effect = socket_recv_side_effect

cache_response = self.redis_client.set(key, value)
cache_response = self.redis_client.set_cache(key, value)
self.assertEqual(cache_response, '+OK')

mock_socket.recv.assert_called_with(self.redis_client.RECV_SIZE)
Expand All @@ -145,7 +146,7 @@ def socket_recv_side_effect(*args, **kwargs):
(len(key), key, len(value), value))
mock_socket.close.assert_called_once_with()

@patch('redis_cache.redis_client.socket')
@patch('backends.redis.redis_backend.socket')
def test_delete(self, mock_sock_lib):
"""
Tests DELETE
Expand All @@ -157,7 +158,7 @@ def test_delete(self, mock_sock_lib):

mock_socket.recv.return_value = ':%s\r\n' % value

cache_response = self.redis_client.delete(key)
cache_response = self.redis_client.invalidate_key(key)
self.assertEqual(cache_response, ':%s' % value)

mock_socket.recv.assert_called_with(self.redis_client.RECV_SIZE)
Expand All @@ -168,7 +169,7 @@ def test_delete(self, mock_sock_lib):
'*2\r\n$3\r\nDEL\r\n$9\r\n%s\r\n' % key)
mock_socket.close.assert_called_once_with()

@patch('redis_cache.redis_client.socket')
@patch('backends.redis.redis_backend.socket')
def test_setex(self, mock_sock_lib):
"""
Tests SETEX
Expand All @@ -187,7 +188,7 @@ def socket_recv_side_effect(*args, **kwargs):
return ""
mock_socket.recv.side_effect = socket_recv_side_effect

cache_response = self.redis_client.setex(key, value, expiration)
cache_response = self.redis_client.set_cache_and_expire(key, value, expiration)
self.assertEqual(cache_response, '+OK')

mock_socket.recv.assert_called_with(self.redis_client.RECV_SIZE)
Expand Down

0 comments on commit b172c7b

Please sign in to comment.