Skip to content
This repository was archived by the owner on Jun 16, 2022. It is now read-only.

Commit 1883be8

Browse files
hugorodgerbrownjaysonsantos
authored andcommitted
Pass contents of OPTIONS to memcached client (#14)
* Remove BMemcached._cache property This property overrides the underlying django.core.cache.backends.memcached.BaseMemcachedCache._cache property, but it's not clear that it's doing much different, other than cherry- picking certain options (username, password) and then ignoring the rest, when creating the uncerlying Client object. This means that the OPTIONS dict in the Django settings is being ignored, which in turn prevents us from setting properties like the pickle_protocol. * Add test for custom config * Update README * Convert tests to tox and update support * Add tox.ini * Fix tox.ini (remove py34-django21) * Add InvalidCacheOptions error on initialisation * Update InvalidCacheOptions error message * Simplify invalid options checking using sets * Fix Python 2 error * Update readme
1 parent 4c2b108 commit 1883be8

File tree

7 files changed

+161
-23
lines changed

7 files changed

+161
-23
lines changed

.travis-runs-tests.sh

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
#!/bin/bash
22
sudo service memcached start
3-
nosetests --version
4-
DJANGO_SETTINGS_MODULE=tests.settings nosetests --with-coverage --cover-package=django_bmemcached
3+
tox
54
exit $?

.travis.yml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@ python:
66
- "3.5"
77
- "3.6"
88
- "3.7"
9-
env:
10-
- DJANGO_VERSION=">=1.10,<1.11"
11-
- DJANGO_VERSION=">=1.9,<1.10"
12-
- DJANGO_VERSION=">=1.8,<1.9"
9+
- "3.8"
1310

14-
install: pip install -r requirements_test.txt Django$DJANGO_VERSION python-binary-memcached
11+
install: pip install -r requirements_test.txt tox-travis
1512
script: ./.travis-runs-tests.sh

README.md

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,33 @@
11
[![Build Status](https://secure.travis-ci.org/jaysonsantos/django-bmemcached.png?branch=master)](http://travis-ci.org/jaysonsantos/django-bmemcached)
2+
23
# Django-BMemcached
3-
A django cache backend to use [bmemcached] (https://github.com/jaysonsantos/python-binary-memcached) module which supports memcached binary protocol with authentication.
4+
5+
A django cache backend to use [bmemcached](https://github.com/jaysonsantos/python-binary-memcached)
6+
module which supports memcached binary protocol with authentication.
47

58
## Installing
9+
610
Use pip:
711

812
```bash
913
pip install django-bmemcached
1014
```
1115

1216
## Using
13-
In your settings.py add bmemcached as backend.
17+
18+
In your settings.py add bmemcached as backend:
19+
20+
```python
21+
CACHES = {
22+
'default': {
23+
'BACKEND': 'django_bmemcached.memcached.BMemcached',
24+
'LOCATION': 'your_server:port',
25+
}
26+
}
27+
```
28+
29+
If you are using Django 1.11 or above, you can also add `OPTIONS` which will be
30+
passed through to the client. The options available for `BMemcached` are as follows:
1431

1532
```python
1633
CACHES = {
@@ -19,13 +36,34 @@ CACHES = {
1936
'LOCATION': 'your_server:port',
2037
'OPTIONS': {
2138
'username': 'user',
22-
'password': 'password'
39+
'password': 'password',
40+
'compression': None,
41+
'socket_timeout': bmemcached.client.constants.SOCKET_TIMEOUT,
42+
'pickler': pickle.Pickler,
43+
'unpickler': pickle.Unpickler,
44+
'pickle_protocol': 0
2345
}
2446
}
2547
}
2648
```
2749

50+
NB If you have options in your configuration that are not supported (see the sample
51+
above, or `django_bmemcached.memcached.VALID_CACHE_OPTIONS`)
52+
the cache will raise an `InvalidCacheOptions` error the first time it is accessed:
53+
54+
```python
55+
>>> from django_bmemcached import BMemcached
56+
>>> client = BMemcached(None, {'OPTIONS': {'bad_option': True}})
57+
>>> client.get("foo")
58+
Traceback (most recent call last):
59+
...
60+
django_bmemcached.memcached.InvalidCacheOptions: Error initialising BMemcached - invalid options detected: {'bad_option'}
61+
Please check your CACHES config contains only valid OPTIONS: {'username', 'unpickler', 'compression', 'pickle_protocol', 'password', 'pickler', 'socket_timeout'}
62+
>>>
63+
```
64+
2865
### Using in Heroku
66+
2967
Just add bmemcached as backend. It will work automagically if you have added memcached as Heroku addon.
3068

3169
```python
@@ -36,9 +74,6 @@ CACHES = {
3674
}
3775
```
3876

39-
40-
41-
4277
## Testing
4378

4479
```bash

django_bmemcached/memcached.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,36 @@
11
import os
2+
3+
import bmemcached
24
from django.core.cache.backends import memcached
5+
from django.core.exceptions import ImproperlyConfigured
6+
7+
# keys that are acceptable in the Django
8+
# settings.CACHES[...]["OPTIONS"] dictionary.
9+
VALID_CACHE_OPTIONS = {
10+
"username",
11+
"password",
12+
"compression",
13+
"socket_timeout",
14+
"pickler",
15+
"unpickler",
16+
"pickle_protocol",
17+
}
18+
19+
20+
class InvalidCacheOptions(ImproperlyConfigured):
21+
"""Custom error raised when Client initialisation fails."""
22+
23+
def __init__(self, options):
24+
# in Python 3 you can compare a list and set, but in Python 2
25+
# you cannot, so need to cast the options.keys() explicitly to a set.
26+
# TODO: remove set() once Python 2 support is dropped.
27+
invalid_options = set(options.keys()) - VALID_CACHE_OPTIONS
28+
msg = (
29+
"Error initialising BMemcached - invalid options detected: %s\n"
30+
"Please check your CACHES config contains only valid OPTIONS: %s"
31+
% (invalid_options, VALID_CACHE_OPTIONS)
32+
)
33+
super(InvalidCacheOptions, self).__init__(msg)
334

435

536
class BMemcached(memcached.BaseMemcachedCache):
@@ -8,7 +39,6 @@ class BMemcached(memcached.BaseMemcachedCache):
839
A.K.A BMemcached.
940
"""
1041
def __init__(self, server, params):
11-
import bmemcached
1242
params.setdefault('OPTIONS', {})
1343

1444
username = params['OPTIONS'].get('username', params.get('USERNAME', os.environ.get('MEMCACHE_USERNAME')))
@@ -38,12 +68,12 @@ def _cache(self):
3868
return client
3969

4070
if self._options:
41-
client = self._lib.Client(
42-
self._servers, self._options.get('username', None),
43-
self._options.get('password', None)
44-
)
71+
try:
72+
client = self._lib.Client(self._servers, **self._options)
73+
except TypeError:
74+
raise InvalidCacheOptions(self._options)
4575
else:
46-
client = self._lib.Client(self._servers,)
76+
client = self._lib.Client(self._servers)
4777

4878
self._client = client
4979

requirements_test.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
nose
22
coverage
3+
python-binary-memcached
4+
tox

tests/__init__.py

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
1+
import bz2
12
import os
3+
import pickle
24
import unittest
35

6+
import django.conf
7+
import django.core.cache
48
import django_bmemcached
9+
from django.test import override_settings
10+
from django_bmemcached import BMemcached
11+
from django_bmemcached.memcached import InvalidCacheOptions
12+
from pickle import Pickler
513

614

715
class TestWithExplicitAuth(unittest.TestCase):
816
def setUp(self):
9-
self.client = django_bmemcached.BMemcached(
17+
self.client = BMemcached(
1018
('127.0.0.1:11211',),
1119
{'OPTIONS': {'username': 'user', 'password': 'password'}}
1220
)
@@ -31,7 +39,7 @@ def testPropertyCacheRetunsAwaysSameServer(self):
3139

3240
class TestWithTopLevelAuthParams(TestWithExplicitAuth):
3341
def setUp(self):
34-
self.client = django_bmemcached.BMemcached(
42+
self.client = BMemcached(
3543
('127.0.0.1:11211',),
3644
{'USERNAME': 'user', 'PASSWORD': 'password'}
3745
)
@@ -42,7 +50,7 @@ def setUp(self):
4250
os.environ['MEMCACHE_SERVERS'] = '127.0.0.1'
4351
os.environ['MEMCACHE_USERNAME'] = 'user'
4452
os.environ['MEMCACHE_PASSWORD'] = 'password'
45-
self.client = django_bmemcached.BMemcached(None, {})
53+
self.client = BMemcached(None, {})
4654

4755
def tearDown(self):
4856
del os.environ['MEMCACHE_SERVERS']
@@ -52,4 +60,49 @@ def tearDown(self):
5260

5361
class TestWithoutAuth(TestWithExplicitAuth):
5462
def setUp(self):
55-
self.client = django_bmemcached.BMemcached(('127.0.0.1:11211',), {})
63+
self.client = BMemcached(('127.0.0.1:11211',), {})
64+
65+
66+
class TestPickler(Pickler):
67+
pass
68+
69+
class TestUnpickler(pickle.Unpickler):
70+
pass
71+
72+
73+
class TestOptions(unittest.TestCase):
74+
75+
# these options are all non-default - so that we can confirm that
76+
# they are passed through to the client.
77+
TEST_OPTIONS = {
78+
"compression": bz2,
79+
"pickle_protocol": 2,
80+
"socket_timeout": 1.0,
81+
"pickler": TestPickler,
82+
"unpickler": TestUnpickler
83+
}
84+
TEST_CACHE_CONFIG = {
85+
"default": {
86+
"BACKEND": "django_bmemcached.memcached.BMemcached",
87+
"BINARY": True,
88+
"OPTIONS": TEST_OPTIONS
89+
}
90+
}
91+
92+
@unittest.skipIf(django.get_version() < '1.11', "OPTIONS did not exist pre-1.11")
93+
@override_settings(CACHES=TEST_CACHE_CONFIG)
94+
def testWithOptions(self):
95+
client = django.core.cache.caches["default"]
96+
options = self.TEST_OPTIONS
97+
self.assertIsInstance(client, BMemcached)
98+
self.assertEqual(client._cache.pickle_protocol, options["pickle_protocol"])
99+
self.assertEqual(client._cache.compression, options["compression"])
100+
self.assertEqual(client._cache.socket_timeout, options["socket_timeout"])
101+
self.assertEqual(client._cache.pickler, options["pickler"])
102+
self.assertEqual(client._cache.unpickler, options["unpickler"])
103+
104+
def testInvalidOptions(self):
105+
"""Check that InvalidCacheOptions is raised."""
106+
client = BMemcached(None, {'OPTIONS': {'bad_option': True}})
107+
with self.assertRaises(InvalidCacheOptions):
108+
client._cache

tox.ini

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[tox]
2+
skip_install = true
3+
envlist =
4+
py{27}-django{19,110,111}
5+
py{34,35}-django{19,110,111,20}
6+
py{36,37}-django{111,20,21,22,30}
7+
py{38}-django{30}
8+
9+
[testenv]
10+
deps = -rrequirements_test.txt
11+
django18: Django>=1.8,<1.9
12+
django19: Django>=1.9,<1.10
13+
django110: Django>=1.10,<1.11
14+
django111: Django>=1.11,<2.0
15+
django20: Django>=2.0,<2.1
16+
django21: Django>=2.1,<2.2
17+
django22: Django>=2.2,<2.3
18+
django30: Django>=3.0,<3.1
19+
setenv =
20+
DJANGO_SETTINGS_MODULE = tests.settings
21+
22+
commands = nosetests --with-coverage --cover-package=django_bmemcached

0 commit comments

Comments
 (0)