Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,24 @@ Supported Caches
----------------
Support currently exists for:

* locmem (default): ``'locmem://[location][/location]'``
* db: ``'db://cache_table[/prefix]'``
* locmem (default): ``'locmem://[NAME]'``
* db: ``'db://TABLE_NAME'``
* dummy: ``'dummy://'``
* file: ``'file:///path/to/file'``
* memcached: ``'memcached://127.0.0.1:11211[/prefix]``
* pymemcached: ``'pymemcached://127.0.0.1:11211[/prefix]'`` For use with the python-memcached library. Useful if you're using Ubuntu <= 10.04.
* djangopylibmc: ``'djangopylibmc://127.0.0.1:11211[/prefix]'`` For use with SASL based setups such as Heroku.
* redis: ``'redis://t@host:port/db[/prefix]'`` or ``'redis:///unix/path/to/socket/file.sock/db[/prefix]'`` For use with django-redis library.
* hiredis ``'hiredis://host:port/db[/prefix]'`` or ``'hiredis:///unix/path/to/socket/file.sock/db[/prefix]'`` For use with django-redis library using
HiredisParser
* file: ``'file:///PATH/TO/FILE'``
* memcached: ``'memcached://HOST:PORT'`` [#memcache]_
* pymemcached: ``'pymemcached://HOST:PORT'`` For use with the `python-memcached`_ library. Useful if you're using Ubuntu <= 10.04.
* djangopylibmc: ``'djangopylibmc://HOST:PORT'`` For use with SASL based setups such as Heroku.
* redis: ``'redis://[USER:PASSWORD@]HOST:PORT[/DB]'`` or ``'redis:///PATH/TO/SOCKET[/DB]'`` For use with `django-redis`_.
* hiredis ``'hiredis://[USER:PASSWORD@]HOST:PORT[/DB]'`` or ``'hiredis:///PATH/TO/SOCKET[/DB]'`` For use with django-redis library using HiredisParser.

All cache urls support optional cache arguments by using a query string, e.g.: ``'memcached://HOST:PORT?key_prefix=site1'``. See the Django `cache arguments documentation`_.

.. [#memcache] To specify multiple instances, separate the the ``HOST:PORT``` pair
by commas, e.g: ``'memcached://HOST1:PORT1,HOST2:PORT2``

.. _django-redis: https://github.com/niwibe/django-redis
.. _python-memcached: https://github.com/linsomniac/python-memcached
.. _cache arguments documentation: https://docs.djangoproject.com/en/dev/topics/cache/#cache-arguments

Installation
------------
Expand Down
114 changes: 42 additions & 72 deletions django_cache_url.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-

import os
import re

try:
import urlparse
Expand All @@ -21,7 +21,7 @@

DEFAULT_ENV = 'CACHE_URL'

CACHE_TYPES = {
BACKENDS = {
'db': 'django.core.cache.backends.db.DatabaseCache',
'dummy': 'django.core.cache.backends.dummy.DummyCache',
'file': 'django.core.cache.backends.filebased.FileBasedCache',
Expand Down Expand Up @@ -51,81 +51,51 @@ def parse(url):
config = {}

url = urlparse.urlparse(url)
# Handle python 2.6 broken url parsing
path, query = url.path, url.query
if '?' in path and query == '':
path, query = path.split('?', 1)

cache_args = dict([(key.upper(), ';'.join(val)) for key, val in
urlparse.parse_qs(query).items()])

# Update with environment configuration.
config['BACKEND'] = CACHE_TYPES[url.scheme]
if url.scheme == 'file':
config['LOCATION'] = url.path
return config
elif url.scheme in ('redis', 'hiredis'):
if url.netloc == 'unix':
location_index = None
bits = list(filter(None, url.path.split('/')))
# find the end of the socket path
for index, bit in enumerate(bits, 1):
if bit.endswith(('.sock', '.socket')):
location_index = index
break

if location_index is None:
# no socket file extension found, using the whole location
location = bits
config['BACKEND'] = BACKENDS[url.scheme]

redis_options = {}
if url.scheme == 'hiredis':
redis_options['PARSER_CLASS'] = 'redis.connection.HiredisParser'

# File based
if not url.netloc:
if url.scheme in ('memcached', 'pymemcached', 'djangopylibmc'):
config['LOCATION'] = 'unix:' + path

elif url.scheme in ('redis', 'hiredis'):
match = re.match(r'.+?(?P<db>\d+)', path)
if match:
db = match.group('db')
path = path[:path.rfind('/')]
else:
# splitting socket path from database and prefix
location = bits[:location_index]
rest = bits[location_index:]
if len(rest) > 0:
try:
# check if first item of the rest is a database
database = int(rest[0])
prefix = rest[1:]
except ValueError:
# or assume the rest is the prefix
database = 0
prefix = rest
else:
database = prefix = None

full_location = (url.netloc, '/' + '/'.join(location))
if database is not None:
full_location += (str(database),)
config['LOCATION'] = ':'.join(full_location)
config['KEY_PREFIX'] = '/'.join(prefix)

db = '0'
config['LOCATION'] = 'unix:%s:%s' % (path, db)
else:
try:
userpass, hostport = url.netloc.split('@')
except ValueError:
userpass, hostport = '', url.netloc

try:
username, password = userpass.split(':')
except ValueError:
pass

path = list(filter(None, url.path.split('/')))
config['LOCATION'] = ':'.join((hostport, path[0]))
config['KEY_PREFIX'] = '/'.join(path[1:])

redis_options = {}

if url.scheme == 'hiredis':
redis_options['PARSER_CLASS'] = 'redis.connection.HiredisParser'
config['LOCATION'] = path
# URL based
else:
# Handle multiple hosts
config['LOCATION'] = ';'.join(url.netloc.split(','))

try:
if password:
redis_options['PASSWORD'] = password
except NameError: # No password defined
pass
if url.scheme in ('redis', 'hiredis'):
if url.password:
redis_options['PASSWORD'] = url.password
# Specifying the database is optional, use db 0 if not specified.
db = path[1:] or '0'
config['LOCATION'] = "%s:%s:%s" % (url.hostname, url.port, db)

if redis_options:
config['OPTIONS'] = redis_options
if redis_options:
config['OPTIONS'] = redis_options

else:
netloc_list = url.netloc.split(',')
if len(netloc_list) > 1:
config['LOCATION'] = netloc_list
else:
config['LOCATION'] = url.netloc
config['KEY_PREFIX'] = url.path[1:]
config.update(cache_args)

return config
40 changes: 20 additions & 20 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def test_locmem_url_returns_locmem_cache(self):
class TestMemcachedCache(Base):
def setUp(self):
super(TestMemcachedCache, self).setUp()
environ['CACHE_URL'] = 'memcached://127.0.0.1:11211/prefix'
environ['CACHE_URL'] = 'memcached://127.0.0.1:11211?key_prefix=site1'

def test_memcached_url_returns_pylibmc_cache(self):
location = 'django.core.cache.backends.memcached.PyLibMCCache'
Expand All @@ -101,18 +101,18 @@ def test_memcached_url_returns_location_from_url(self):

def test_memcached_url_returns_prefix_from_url(self):
config = django_cache_url.config()
assert_equals(config['KEY_PREFIX'], 'prefix')
assert_equals(config['KEY_PREFIX'], 'site1')

def test_memcached_url_multiple_locations(self):
environ['CACHE_URL'] = 'memcached://127.0.0.1:11211,192.168.0.100:11211/prefix'
environ['CACHE_URL'] = 'memcached://127.0.0.1:11211,192.168.0.100:11211?key_prefix=site1'
config = django_cache_url.config()
assert_equals(config['LOCATION'], ['127.0.0.1:11211', '192.168.0.100:11211'])
assert_equals(config['LOCATION'], '127.0.0.1:11211;192.168.0.100:11211')


class TestRedisCache(Base):
def setUp(self):
super(TestRedisCache, self).setUp()
environ['CACHE_URL'] = 'redis://127.0.0.1:6379/0/prefix'
environ['CACHE_URL'] = 'redis://127.0.0.1:6379/0?key_prefix=site1'

def test_redis_url_returns_redis_cache(self):
location = 'redis_cache.cache.RedisCache'
Expand All @@ -125,13 +125,13 @@ def test_redis_url_returns_location_and_port_from_url(self):

def test_redis_url_returns_prefix_from_url(self):
config = django_cache_url.config()
assert_equals(config['KEY_PREFIX'], 'prefix')
assert_equals(config['KEY_PREFIX'], 'site1')


class TestHiredisCache(Base):
def setUp(self):
super(TestHiredisCache, self).setUp()
environ['CACHE_URL'] = 'hiredis://127.0.0.1:6379/0/prefix'
environ['CACHE_URL'] = 'hiredis://127.0.0.1:6379/0?key_prefix=site1'

def test_hiredis_url_returns_redis_cache(self):
location = 'redis_cache.cache.RedisCache'
Expand All @@ -144,7 +144,7 @@ def test_hiredis_url_returns_location_and_port_from_url(self):

def test_hiredis_url_returns_prefix_from_url(self):
config = django_cache_url.config()
assert_equals(config['KEY_PREFIX'], 'prefix')
assert_equals(config['KEY_PREFIX'], 'site1')

def test_hiredis_url_sets_hiredis_parser(self):
config = django_cache_url.config()
Expand All @@ -155,7 +155,7 @@ def test_hiredis_url_sets_hiredis_parser(self):
class TestRedisCacheWithPassword(Base):
def setUp(self):
super(TestRedisCacheWithPassword, self).setUp()
environ['CACHE_URL'] = 'redis://:redispass@127.0.0.1:6379/0/prefix'
environ['CACHE_URL'] = 'redis://:redispass@127.0.0.1:6379/0?key_prefix=site1'

def test_redis_url_returns_redis_cache(self):
location = 'redis_cache.cache.RedisCache'
Expand All @@ -168,7 +168,7 @@ def test_redis_url_returns_location_and_port_from_url(self):

def test_redis_url_returns_prefix_from_url(self):
config = django_cache_url.config()
assert_equals(config['KEY_PREFIX'], 'prefix')
assert_equals(config['KEY_PREFIX'], 'site1')

def test_redis_url_returns_password(self):
config = django_cache_url.config()
Expand All @@ -180,7 +180,7 @@ def test_redis_url_returns_password(self):
class TestRedisBothSocketCache(Base):
def setUp(self):
super(TestRedisBothSocketCache, self).setUp()
environ['CACHE_URL'] = 'redis://unix/path/to/socket/file.sock/1/prefix'
environ['CACHE_URL'] = 'redis:///path/to/socket/1?key_prefix=site1'

def test_socket_url_returns_redis_cache(self):
location = 'redis_cache.cache.RedisCache'
Expand All @@ -189,41 +189,41 @@ def test_socket_url_returns_redis_cache(self):

def test_socket_url_returns_location_and_port_from_url(self):
config = django_cache_url.config()
assert_equals(config['LOCATION'], 'unix:/path/to/socket/file.sock:1')
assert_equals(config['LOCATION'], 'unix:/path/to/socket:1')

def test_socket_url_returns_prefix_from_url(self):
config = django_cache_url.config()
assert_equals(config['KEY_PREFIX'], 'prefix')
assert_equals(config['KEY_PREFIX'], 'site1')


class TestRedisDatabaseSocketCache(TestRedisBothSocketCache):
def setUp(self):
super(TestRedisDatabaseSocketCache, self).setUp()
environ['CACHE_URL'] = 'redis://unix/path/to/socket/file.sock/1'
environ['CACHE_URL'] = 'redis:///path/to/socket/1'

def test_socket_url_returns_prefix_from_url(self):
config = django_cache_url.config()
assert_equals(config['KEY_PREFIX'], '')
assert_equals(config.get('KEY_PREFIX'), None)


class TestRedisPrefixSocketCache(TestRedisBothSocketCache):
def setUp(self):
super(TestRedisPrefixSocketCache, self).setUp()
environ['CACHE_URL'] = 'redis://unix/path/to/socket/file.sock/prefix'
environ['CACHE_URL'] = 'redis:///path/to/socket?key_prefix=site1'

def test_socket_url_returns_location_and_port_from_url(self):
config = django_cache_url.config()
assert_equals(config['LOCATION'], 'unix:/path/to/socket/file.sock:0')
assert_equals(config['LOCATION'], 'unix:/path/to/socket:0')

def test_socket_url_returns_prefix_from_url(self):
config = django_cache_url.config()
assert_equals(config['KEY_PREFIX'], 'prefix')
assert_equals(config['KEY_PREFIX'], 'site1')


class TestHiredisDatabaseSocketCache(TestRedisDatabaseSocketCache):
def setUp(self):
super(TestHiredisDatabaseSocketCache, self).setUp()
environ['CACHE_URL'] = 'hiredis://unix/path/to/socket/file.sock/1'
environ['CACHE_URL'] = 'hiredis:///path/to/socket/1'

def test_hiredis_url_sets_hiredis_parser(self):
config = django_cache_url.config()
Expand All @@ -234,7 +234,7 @@ def test_hiredis_url_sets_hiredis_parser(self):
class TestHiredisPrefixSocketCache(TestRedisPrefixSocketCache):
def setUp(self):
super(TestHiredisPrefixSocketCache, self).setUp()
environ['CACHE_URL'] = 'hiredis://unix/path/to/socket/file.sock/prefix'
environ['CACHE_URL'] = 'hiredis:///path/to/socket?key_prefix=site1'

def test_hiredis_url_sets_hiredis_parser(self):
config = django_cache_url.config()
Expand Down