├── .envrc ├── .gitignore ├── .travis.yml ├── AUTHORS.rst ├── CHANGELOG.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── django_cache_url.py ├── setup.cfg ├── setup.py ├── tests ├── test_config_options.py ├── test_core.py ├── test_memcached.py ├── test_redis.py └── test_uwsgicache.py └── tox.ini /.envrc: -------------------------------------------------------------------------------- 1 | layout python python3 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | *.egg-info 4 | .tox 5 | .python-version 6 | 7 | # coverage 8 | .coverage 9 | htmlcov 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | sudo: false 4 | 5 | python: 3.5 6 | 7 | env: 8 | matrix: 9 | - TOXENV=py27 10 | - TOXENV=py33 11 | - TOXENV=py34 12 | - TOXENV=py35 13 | - TOXENV=flake8 14 | 15 | script: 16 | - make test 17 | 18 | install: 19 | - travis_retry pip install tox 20 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | Django Cache URL is written and maintained by George Hickman and various contributors: 2 | 3 | 4 | Lead 5 | ---- 6 | 7 | - George Hickman 8 | 9 | 10 | Contributors 11 | ------------ 12 | 13 | - Max Peterson 14 | - Charlie Denton 15 | - Brandon Adams 16 | - Allan Mercado 17 | - Jannis Leidel 18 | - Mjumbe Wawatu Ukweli 19 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | ========= 3 | 4 | v3.0.0 5 | ------ 6 | 7 | - Deprecate project in favour of `Django Environ `_. 8 | 9 | 10 | v2.0.0 11 | ------ 12 | 13 | - **Backwards Incompatible** Remove Python 2.6 support 14 | - Fix urls without a port getting their port set to "None" instead (thanks to Linus Lewandowski) 15 | 16 | 17 | v1.4.0 18 | ------ 19 | 20 | - Add django-elasticache support (thanks to Alex Couper) 21 | 22 | 23 | v1.3.1 24 | ------ 25 | 26 | - Fix django-redis support (thanks to Manatsawin Hanmongkolchai) 27 | 28 | 29 | v1.3.0 30 | ------ 31 | 32 | - Support for django-redis >= 4.50 (thanks to Egor Yurtaev) 33 | 34 | 35 | v1.2.0 36 | ------ 37 | 38 | - Run tests on Python 3.5 (thanks to Anton Egorov) 39 | - Add support for MAX_ENTRIES and CULL_FREQUENCY options (thanks to Anton Egorov) 40 | 41 | 42 | v1.1.0 43 | ------ 44 | 45 | - Add support for uwsgi caching (thanks to Alan Justino da Silva) 46 | 47 | 48 | v1.0.0 49 | ------ 50 | 51 | - **Backwards Incompatible** Improve Redis URL parsing, making PREFIX parsing much easier and automatically converting query args into Django Cache settings (thanks to Russell Davies) 52 | - **Backwards Incompatible** Switch to ``django-redis``'s new import name (thanks to Michael Warkentin) 53 | - Switch to Tox for running tests with different pythons 54 | - Switch to pytest 55 | 56 | 57 | v0.8.0 58 | ------ 59 | 60 | - Add support for password in redis urls (thanks to Mjumbe Wawatu Ukweli) 61 | 62 | 63 | v0.7.0 64 | ------ 65 | 66 | - Add support for UNIX sockets in redis urls (thanks to Jannis Leidel) 67 | 68 | 69 | v0.6.0 70 | ------ 71 | 72 | - Fix Python 3 support 73 | 74 | 75 | v0.5.0 76 | ------ 77 | 78 | - Add multiple memcache locations 79 | 80 | 81 | v0.4.0 82 | ------ 83 | 84 | - Add redis and hiredis support 85 | 86 | 87 | v0.3.4 88 | ------ 89 | 90 | - Fix Python 3 compatibility import bug 91 | 92 | 93 | v0.3.3 94 | ------ 95 | 96 | - Add Python 3 compatibility 97 | 98 | 99 | v0.3.2 100 | ------ 101 | 102 | - Fix setting name bug 103 | 104 | 105 | v0.3.1 106 | ------ 107 | 108 | - Remove underscore from django pylibmc scheme 109 | 110 | 111 | v0.3.0 112 | ------ 113 | 114 | - Add python memcached support 115 | - Add django pylibmc support 116 | 117 | 118 | v0.2.0 119 | ------ 120 | 121 | - Add prefix support 122 | - Split up tests 123 | - Tidy up examples 124 | 125 | 126 | v0.1.0 127 | ------ 128 | 129 | - Initial release 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 George Hickman 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 17 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 18 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst LICENSE 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | help: 4 | @echo 'Makefile for django-cache-url' 5 | @echo '' 6 | @echo 'Usage:' 7 | @echo ' make release push to the PyPI' 8 | @echo ' make test run the test suite' 9 | @echo '' 10 | 11 | release: 12 | rm -rf dist/* 13 | python setup.py register bdist_wheel sdist 14 | twine upload dist/* 15 | 16 | test: 17 | tox 18 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | Deprecated 3 | ========== 4 | 5 | This project has found a new home at ``_. 6 | -------------------------------------------------------------------------------- /django_cache_url.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import re 4 | 5 | try: 6 | import urllib.parse as urlparse 7 | except ImportError: # python 2 8 | import urlparse 9 | 10 | 11 | # Register cache schemes in URLs. 12 | urlparse.uses_netloc.append('db') 13 | urlparse.uses_netloc.append('dummy') 14 | urlparse.uses_netloc.append('file') 15 | urlparse.uses_netloc.append('locmem') 16 | urlparse.uses_netloc.append('uwsgicache') 17 | urlparse.uses_netloc.append('memcached') 18 | urlparse.uses_netloc.append('elasticache') 19 | urlparse.uses_netloc.append('djangopylibmc') 20 | urlparse.uses_netloc.append('pymemcached') 21 | urlparse.uses_netloc.append('redis') 22 | urlparse.uses_netloc.append('hiredis') 23 | 24 | DEFAULT_ENV = 'CACHE_URL' 25 | 26 | BACKENDS = { 27 | 'db': 'django.core.cache.backends.db.DatabaseCache', 28 | 'dummy': 'django.core.cache.backends.dummy.DummyCache', 29 | 'elasticache': 'django_elasticache.memcached.ElastiCache', 30 | 'file': 'django.core.cache.backends.filebased.FileBasedCache', 31 | 'locmem': 'django.core.cache.backends.locmem.LocMemCache', 32 | 'uwsgicache': 'uwsgicache.UWSGICache', 33 | 'memcached': 'django.core.cache.backends.memcached.PyLibMCCache', 34 | 'djangopylibmc': 'django_pylibmc.memcached.PyLibMCCache', 35 | 'pymemcached': 'django.core.cache.backends.memcached.MemcachedCache', 36 | 'redis': 'django_redis.cache.RedisCache', 37 | 'hiredis': 'django_redis.cache.RedisCache', 38 | } 39 | 40 | 41 | def config(env=DEFAULT_ENV, default='locmem://'): 42 | """Returns configured CACHES dictionary from CACHE_URL""" 43 | config = {} 44 | 45 | s = os.environ.get(env, default) 46 | 47 | if s: 48 | config = parse(s) 49 | 50 | return config 51 | 52 | 53 | def parse(url): 54 | """Parses a cache URL.""" 55 | config = {} 56 | 57 | url = urlparse.urlparse(url) 58 | # Handle python 2.6 broken url parsing 59 | path, query = url.path, url.query 60 | if '?' in path and query == '': 61 | path, query = path.split('?', 1) 62 | 63 | cache_args = dict([(key.upper(), ';'.join(val)) for key, val in 64 | urlparse.parse_qs(query).items()]) 65 | 66 | # Update with environment configuration. 67 | backend = BACKENDS.get(url.scheme) 68 | if not backend: 69 | raise Exception('Unknown backend: "{0}"'.format(url.scheme)) 70 | 71 | config['BACKEND'] = BACKENDS[url.scheme] 72 | 73 | redis_options = {} 74 | if url.scheme == 'hiredis': 75 | redis_options['PARSER_CLASS'] = 'redis.connection.HiredisParser' 76 | 77 | # File based 78 | if not url.netloc: 79 | if url.scheme in ('memcached', 'pymemcached', 'djangopylibmc'): 80 | config['LOCATION'] = 'unix:' + path 81 | 82 | elif url.scheme in ('redis', 'hiredis'): 83 | match = re.match(r'.+?(?P\d+)', path) 84 | if match: 85 | db = match.group('db') 86 | path = path[:path.rfind('/')] 87 | else: 88 | db = '0' 89 | config['LOCATION'] = 'unix:%s:%s' % (path, db) 90 | else: 91 | config['LOCATION'] = path 92 | # URL based 93 | else: 94 | # Handle multiple hosts 95 | config['LOCATION'] = ';'.join(url.netloc.split(',')) 96 | 97 | if url.scheme in ('redis', 'hiredis'): 98 | if url.password: 99 | redis_options['PASSWORD'] = url.password 100 | # Specifying the database is optional, use db 0 if not specified. 101 | db = path[1:] or '0' 102 | port = url.port if url.port else 6379 103 | config['LOCATION'] = "redis://%s:%s/%s" % (url.hostname, port, db) 104 | 105 | if redis_options: 106 | config.setdefault('OPTIONS', {}).update(redis_options) 107 | 108 | if url.scheme == 'uwsgicache': 109 | config['LOCATION'] = config.get('LOCATION', 'default') or 'default' 110 | 111 | # Pop special options from cache_args 112 | # https://docs.djangoproject.com/en/1.10/topics/cache/#cache-arguments 113 | options = {} 114 | for key in ['MAX_ENTRIES', 'CULL_FREQUENCY']: 115 | val = cache_args.pop(key, None) 116 | if val is not None: 117 | options[key] = int(val) 118 | if options: 119 | config.setdefault('OPTIONS', {}).update(options) 120 | 121 | config.update(cache_args) 122 | 123 | return config 124 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [wheel] 2 | universal = 1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | django-cache-url 4 | ~~~~~~~~~~~~~~~~ 5 | 6 | This simple Django utility allows you to utilize the 7 | `12factor `_ inspired 8 | ``CACHE_URL`` environment variable to configure your Django application. 9 | 10 | 11 | Usage 12 | ----- 13 | 14 | Configure your cache in ``settings.py``:: 15 | 16 | CACHES={'default': django_cache_url.config()} 17 | 18 | Nice and simple. 19 | """ 20 | from setuptools import setup 21 | 22 | 23 | setup( 24 | name='django-cache-url', 25 | version='3.0.0', 26 | url='http://github.com/ghickman/django-cache-url', 27 | license='MIT', 28 | author='George Hickman', 29 | author_email='george@ghickman.co.uk', 30 | description='Use Cache URLs in your Django application.', 31 | long_description=__doc__, 32 | py_modules=('django_cache_url',), 33 | classifiers=[ 34 | 'Development Status :: 7 - Inactive', 35 | 'Framework :: Django', 36 | 'Intended Audience :: Developers', 37 | 'License :: OSI Approved :: MIT License', 38 | 'Operating System :: OS Independent', 39 | 'Programming Language :: Python :: 2', 40 | 'Programming Language :: Python :: 2.7', 41 | 'Programming Language :: Python :: 3', 42 | 'Programming Language :: Python :: 3.3', 43 | 'Programming Language :: Python :: 3.4', 44 | 'Programming Language :: Python :: 3.5', 45 | ], 46 | ) 47 | -------------------------------------------------------------------------------- /tests/test_config_options.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import django_cache_url 4 | 5 | LOCATION = 'django.core.cache.backends.memcached.PyLibMCCache' 6 | 7 | 8 | def test_setting_default_var(): 9 | config = django_cache_url.config(default='memcached://127.0.0.1:11211') 10 | assert config['BACKEND'] == LOCATION 11 | assert config['LOCATION'] == '127.0.0.1:11211' 12 | 13 | 14 | def test_setting_env_var_name(): 15 | os.environ['HERP'] = 'memcached://127.0.0.1:11211' 16 | config = django_cache_url.config(env='HERP') 17 | assert config['BACKEND'] == LOCATION 18 | assert config['LOCATION'] == '127.0.0.1:11211' 19 | 20 | 21 | def test_setting_env_var(): 22 | os.environ['CACHE_URL'] = 'redis://127.0.0.1:6379/0?key_prefix=site1' 23 | config = django_cache_url.config() 24 | 25 | assert config['BACKEND'] == 'django_redis.cache.RedisCache' 26 | assert config['LOCATION'] == 'redis://127.0.0.1:6379/0' 27 | -------------------------------------------------------------------------------- /tests/test_core.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | import django_cache_url 6 | 7 | 8 | def test_config_defaults_to_locmem(): 9 | if os.environ.get('CACHE_URL'): 10 | del os.environ['CACHE_URL'] 11 | config = django_cache_url.config() 12 | assert config['BACKEND'] == 'django.core.cache.backends.locmem.LocMemCache' 13 | 14 | 15 | def test_db_url_returns_database_cache_backend(): 16 | url = 'db://super_caching_table' 17 | config = django_cache_url.parse(url) 18 | 19 | assert config['BACKEND'] == 'django.core.cache.backends.db.DatabaseCache' 20 | assert config['LOCATION'] == 'super_caching_table' 21 | 22 | 23 | def test_dummy_url_returns_dummy_cache(): 24 | config = django_cache_url.parse('dummy://') 25 | assert config['BACKEND'] == 'django.core.cache.backends.dummy.DummyCache' 26 | 27 | 28 | def test_file_url_returns_file_cache_backend(): 29 | config = django_cache_url.parse('file:///herp') 30 | 31 | assert config['BACKEND'] == 'django.core.cache.backends.filebased.FileBasedCache' 32 | assert config['LOCATION'] == '/herp' 33 | 34 | 35 | def test_locmem_url_returns_locmem_cache(): 36 | config = django_cache_url.parse('locmem://') 37 | assert config['BACKEND'] == 'django.core.cache.backends.locmem.LocMemCache' 38 | 39 | 40 | def test_query_string_params_are_converted_to_cache_arguments(): 41 | url = 'redis:///path/to/socket?key_prefix=foo&bar=herp' 42 | config = django_cache_url.parse(url) 43 | 44 | assert config['KEY_PREFIX'] == 'foo' 45 | assert config['BAR'] == 'herp' 46 | 47 | 48 | def test_query_string_params_are_converted_to_cache_options(): 49 | url = 'db://my_cache_table?max_entries=1000&cull_frequency=2' 50 | config = django_cache_url.parse(url) 51 | 52 | assert 'OPTIONS' in config 53 | assert config['OPTIONS']['MAX_ENTRIES'] == 1000 54 | assert config['OPTIONS']['CULL_FREQUENCY'] == 2 55 | 56 | 57 | def test_unknown_cache_backend(): 58 | with pytest.raises(Exception): 59 | django_cache_url.parse('donkey://127.0.0.1/foo') 60 | -------------------------------------------------------------------------------- /tests/test_memcached.py: -------------------------------------------------------------------------------- 1 | import django_cache_url 2 | 3 | 4 | def test_memcached_url_returns_pylibmc_cache(): 5 | url = 'memcached://127.0.0.1:11211?key_prefix=site1' 6 | config = django_cache_url.parse(url) 7 | 8 | assert config['BACKEND'] == 'django.core.cache.backends.memcached.PyLibMCCache' 9 | assert config['LOCATION'] == '127.0.0.1:11211' 10 | assert config['KEY_PREFIX'] == 'site1' 11 | 12 | 13 | def test_memcached_url_multiple_locations(): 14 | url = 'memcached://127.0.0.1:11211,192.168.0.100:11211?key_prefix=site1' 15 | config = django_cache_url.parse(url) 16 | assert config['LOCATION'] == '127.0.0.1:11211;192.168.0.100:11211' 17 | 18 | 19 | def test_memcached_socket_url(): 20 | url = 'memcached:///path/to/socket/' 21 | config = django_cache_url.parse(url) 22 | assert config['LOCATION'] == 'unix:/path/to/socket/' 23 | 24 | 25 | def test_elasticache_url(): 26 | url = 'elasticache://cache-a.a.cfg.use1.cache.amazonaws.com:11211' 27 | config = django_cache_url.parse(url) 28 | 29 | assert config['LOCATION'] == 'cache-a.a.cfg.use1.cache.amazonaws.com:11211' 30 | assert config['BACKEND'] == 'django_elasticache.memcached.ElastiCache' 31 | -------------------------------------------------------------------------------- /tests/test_redis.py: -------------------------------------------------------------------------------- 1 | import django_cache_url 2 | 3 | 4 | # 5 | # HIREDIS 6 | # 7 | 8 | def test_hiredis(): 9 | url = 'hiredis://127.0.0.1:6379/0?key_prefix=site1' 10 | config = django_cache_url.parse(url) 11 | 12 | assert config['BACKEND'] == 'django_redis.cache.RedisCache' 13 | assert config['LOCATION'] == 'redis://127.0.0.1:6379/0' 14 | assert config['OPTIONS']['PARSER_CLASS'] == 'redis.connection.HiredisParser' 15 | 16 | 17 | def test_hiredis_socket(): 18 | url = 'hiredis:///path/to/socket/1?key_prefix=site1' 19 | config = django_cache_url.parse(url) 20 | 21 | assert config['BACKEND'] == 'django_redis.cache.RedisCache' 22 | assert config['LOCATION'] == 'unix:/path/to/socket:1' 23 | assert config['OPTIONS']['PARSER_CLASS'] == 'redis.connection.HiredisParser' 24 | 25 | 26 | # 27 | # REDIS 28 | # 29 | 30 | def test_redis(): 31 | url = 'redis://127.0.0.1:6379/0?key_prefix=site1' 32 | config = django_cache_url.parse(url) 33 | 34 | assert config['BACKEND'] == 'django_redis.cache.RedisCache' 35 | assert config['LOCATION'] == 'redis://127.0.0.1:6379/0' 36 | 37 | 38 | def test_redis_socket(): 39 | url = 'redis:///path/to/socket/1?key_prefix=site1' 40 | config = django_cache_url.parse(url) 41 | 42 | assert config['BACKEND'] == 'django_redis.cache.RedisCache' 43 | assert config['LOCATION'] == 'unix:/path/to/socket:1' 44 | assert 'OPTIONS' not in config 45 | 46 | 47 | def test_redis_with_password(): 48 | url = 'redis://:redispass@127.0.0.1:6379/0' 49 | config = django_cache_url.parse(url) 50 | 51 | assert config['BACKEND'] == 'django_redis.cache.RedisCache' 52 | assert config['LOCATION'] == 'redis://127.0.0.1:6379/0' 53 | assert config['OPTIONS']['PASSWORD'] == 'redispass' 54 | -------------------------------------------------------------------------------- /tests/test_uwsgicache.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import django_cache_url 4 | 5 | 6 | def test_uwsgicache_url_returns_uwsgicache_cache(): 7 | url = 'uwsgicache://cachename/' 8 | config = django_cache_url.parse(url) 9 | 10 | assert config['BACKEND'] == 'uwsgicache.UWSGICache' 11 | assert config['LOCATION'] == 'cachename' 12 | 13 | 14 | def test_uwsgicache_default_location(): 15 | url = 'uwsgicache://' 16 | config = django_cache_url.parse(url) 17 | assert config['LOCATION'] == 'default' 18 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist=py26, py27, py33, py34, py35, pypy, flake8, coverage 3 | 4 | [testenv] 5 | commands=py.test {posargs} tests 6 | deps= 7 | pytest 8 | 9 | [testenv:coverage] 10 | basepython = python3.5 11 | deps = 12 | coverage 13 | {[testenv]deps} 14 | commands = 15 | coverage run -m pytest tests --strict {posargs} 16 | coverage report --include=django_cache_url.py 17 | coverage html --include=django_cache_url.py 18 | 19 | [testenv:flake8] 20 | basepython = python2.7 21 | deps = 22 | flake8 23 | commands = 24 | flake8 django_cache_url.py tests --max-line-length=100 25 | --------------------------------------------------------------------------------