From fa3f0afddc81f895705e3737a7b6adad3cec5fc2 Mon Sep 17 00:00:00 2001 From: Thomas Hooper Date: Mon, 28 Sep 2015 18:05:03 +0100 Subject: [PATCH] Cope with cache connection errors Ensure requests don't get rate limited and don't throw errors when a memcached instance is unavailable. --- .travis.yml | 3 +++ ratelimit/tests.py | 37 +++++++++++++++++++++++++++ ratelimit/utils.py | 7 +++-- test_settings.py | 4 +++ tox.ini | 64 ++++++++++++++++++++++++++++++++++------------ 5 files changed, 97 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index 23eca2e..1021deb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,9 @@ python: - "3.4" - "pypy" install: + - if [[ $TRAVIS_PYTHON_VERSION == 2* ]]; then pip install -q python-memcached>=1.57; fi + - if [[ $TRAVIS_PYTHON_VERSION == 3* ]]; then pip install -q python3-memcached>=1.51; fi + - if [[ $TRAVIS_PYTHON_VERSION == pypy ]]; then pip install -q python-memcached>=1.57; fi - pip install -q "Django>=${DJANGO_VERSION},<${DJANGO_VERSION}.99" flake8 script: - ./run.sh test diff --git a/ratelimit/tests.py b/ratelimit/tests.py index 6a288b6..f307da3 100644 --- a/ratelimit/tests.py +++ b/ratelimit/tests.py @@ -280,6 +280,16 @@ def view(request): with self.assertRaises(InvalidCacheBackendError): view(req) + @override_settings(RATELIMIT_USE_CACHE='connection-errors') + def test_cache_connection_error(self): + + @ratelimit(key='ip', rate='1/m') + def view(request): + return request + + req = rf.post('/') + assert view(req) + def test_user_or_ip(self): """Allow custom functions to set cache keys.""" @@ -389,6 +399,33 @@ def do_increment(request): assert do_increment(req), 'Request should be rate limited.' assert not_increment(req), 'Request should be rate limited.' + @override_settings(RATELIMIT_USE_CACHE='connection-errors') + def test_is_ratelimited_cache_connection_error_without_increment(self): + def get_key(group, request): + return 'test_is_ratelimited_key' + + def not_increment(request): + return is_ratelimited(request, increment=False, + method=is_ratelimited.ALL, key=get_key, + rate='1/m', group='a') + + req = rf.get('/') + assert not not_increment(req) + + @override_settings(RATELIMIT_USE_CACHE='connection-errors') + def test_is_ratelimited_cache_connection_error_with_increment(self): + def get_key(group, request): + return 'test_is_ratelimited_key' + + def do_increment(request): + return is_ratelimited(request, increment=True, + method=is_ratelimited.ALL, key=get_key, + rate='1/m', group='a') + + req = rf.get('/') + assert not do_increment(req) + assert req.limited is False + class RatelimitCBVTests(TestCase): diff --git a/ratelimit/utils.py b/ratelimit/utils.py index 60090a4..98e2df2 100644 --- a/ratelimit/utils.py +++ b/ratelimit/utils.py @@ -153,9 +153,12 @@ def is_ratelimited(request, group=None, fn=None, key=None, rate=None, count = initial_value else: if increment: - count = cache.incr(cache_key) + try: + count = cache.incr(cache_key) + except ValueError: + count = 0 else: - count = cache.get(cache_key) + count = cache.get(cache_key) or 0 limited = count > limit if increment: request.limited = old_limited or limited diff --git a/test_settings.py b/test_settings.py index aab9213..8b31e40 100644 --- a/test_settings.py +++ b/test_settings.py @@ -11,6 +11,10 @@ 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'LOCATION': 'ratelimit-tests', }, + 'connection-errors': { + 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', + 'LOCATION': 'test-connection-errors', + }, } DATABASES = { diff --git a/tox.ini b/tox.ini index 6767faf..d59e660 100644 --- a/tox.ini +++ b/tox.ini @@ -5,70 +5,102 @@ commands = ./run.sh test [testenv:py34-1.8] basepython = python3.4 -deps = Django>=1.8,<1.8.99 +deps = + Django>=1.8,<1.8.99 + python3-memcached>=1.51 [testenv:py34-1.7] basepython = python3.4 -deps = Django>=1.7,<1.7.99 +deps = + Django>=1.7,<1.7.99 + python3-memcached>=1.51 [testenv:py34-1.6] basepython = python3.4 -deps = Django>=1.6,<1.6.99 +deps = + Django>=1.6,<1.6.99 + python3-memcached>=1.51 [testenv:py34-1.5] basepython = python3.4 -deps = Django>=1.5,<1.5.99 +deps = + Django>=1.5,<1.5.99 + python3-memcached>=1.51 # python 3.3 [testenv:py33-1.8] basepython = python3.3 -deps = Django>=1.8,<1.8.99 +deps = + Django>=1.8,<1.8.99 + python3-memcached>=1.51 [testenv:py33-1.7] basepython = python3.3 -deps = Django>=1.7,<1.7.99 +deps = + Django>=1.7,<1.7.99 + python3-memcached>=1.51 [testenv:py33-1.6] basepython = python3.3 -deps = Django>=1.6,<1.6.99 +deps = + Django>=1.6,<1.6.99 + python3-memcached>=1.51 [testenv:py33-1.5] basepython = python3.3 -deps = Django>=1.5,<1.5.99 +deps = + Django>=1.5,<1.5.99 + python3-memcached>=1.51 # python 2.7 [testenv:py27-1.8] basepython = python2.7 -deps = Django>=1.7,<1.7.99 +deps = + Django>=1.7,<1.7.99 + python-memcached>=1.57 [testenv:py27-1.7] basepython = python2.7 -deps = Django>=1.7,<1.7.99 +deps = + Django>=1.7,<1.7.99 + python-memcached>=1.57 [testenv:py27-1.6] basepython = python2.7 -deps = Django>=1.6,<1.6.99 +deps = + Django>=1.6,<1.6.99 + python-memcached>=1.57 [testenv:py27-1.5] basepython = python2.7 -deps = Django>=1.5,<1.5.99 +deps = + Django>=1.5,<1.5.99 + python-memcached>=1.57 [testenv:py27-1.4] basepython = python2.7 -deps = Django>=1.4,<1.4.99 +deps = + Django>=1.4,<1.4.99 + python-memcached>=1.57 # python 2.6 [testenv:py26-1.6] basepython = python2.6 -deps = Django>=1.6,<1.6.99 +deps = + Django>=1.6,<1.6.99 + python-memcached>=1.57 [testenv:py26-1.5] basepython = python2.6 -deps = Django>=1.5,<1.5.99 +deps = + Django>=1.5,<1.5.99 + python-memcached>=1.57 [testenv:py26-1.4] basepython = python2.6 -deps = Django>=1.4,<1.4.99 +deps = + Django>=1.4,<1.4.99 + python-memcached>=1.57