├── .gitignore ├── .travis.yml ├── MANIFEST.in ├── README.md ├── UNLICENSE ├── assets └── memcacheify.jpg ├── memcacheify.py ├── requirements.txt ├── setup.cfg ├── setup.py └── tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | *.egg-info 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.5" 5 | install: 6 | - python setup.py develop 7 | - pip install -r requirements.txt 8 | script: 9 | - flake8 --show-source 10 | - nosetests 11 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md requirements.txt tests.py UNLICENSE 2 | recursive-include assets *.jpg 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # django-heroku-memcacheify 2 | 3 | Automatic Django memcached configuration on Heroku. 4 | 5 | 6 | ![Deploying memcached is easy](https://github.com/rdegges/django-heroku-memcacheify/raw/master/assets/memcacheify.jpg) 7 | 8 | 9 | ## Install 10 | 11 | To install ``django-heroku-memcacheify``, simply run 12 | ``pip install django-heroku-memcacheify`` and you'll get the latest version 13 | installed automatically. 14 | 15 | **NOTE**: If you'd like to install this locally, you'll need to have the 16 | ``libmemcached-dev`` libraries installed for this to compile properly. On 17 | Debian and Ubuntu you can install this by running ``sudo aptitude -y install 18 | libmemcached-dev``. If you're using a Mac, you can use 19 | [homebrew](http://mxcl.github.com/homebrew/) and run ``brew install libmemcached``. 20 | 21 | 22 | ## Usage 23 | 24 | Modify your Django ``settings.py`` file, and set: 25 | 26 | ``` python 27 | from memcacheify import memcacheify 28 | 29 | CACHES = memcacheify() 30 | ``` 31 | 32 | Next, ensure pylibmc is present in your ``requirements.txt`` file (or one 33 | included from it), so the Heroku Python buildpack will detect the necessary 34 | C dependencies and 'bootstrap' your application. 35 | 36 | Assuming you have a memcache server available to your application on Heroku, it 37 | will instantly be available. If you have no memcache addon provisioned for your 38 | app, ``memcacheify`` will default to using local memory caching as a backup :) 39 | 40 | 41 | ## Heroku Setup 42 | 43 | Now that you've got Django configured to use memcache, all you need to do is 44 | install one memcache addons that Heroku provides! 45 | 46 | I personally recommend [MemCachier](https://addons.heroku.com/memcachier) -- 47 | they're stable, cheap, great! 48 | 49 | Let's say I want to install the ``memcachier`` addon, I could simply run: 50 | 51 | ``` bash 52 | $ heroku addons:add memcachier:25 53 | $ heroku config 54 | ... 55 | MEMCACHIER_SERVERS => memcachier1.example.net 56 | MEMCACHIER_USERNAME => bobslob 57 | MEMCACHIER_PASSWORD => l0nGr4ndoMstr1Ngo5strang3CHaR4cteRS 58 | ... 59 | ``` 60 | 61 | The example above will provision a *free* 25m memcache server for your 62 | application. Assuming everything worked, ``heroku config``'s output should show 63 | that you now have 3 new environment variables set. 64 | 65 | 66 | ## Local Development 67 | If you have a memcached server locally for development that doesn't support 68 | authentication, you can still use memcache by setting an environment variable 69 | `MEMCACHEIFY_USE_LOCAL=True`. 70 | 71 | This will set the default cache to `django_pylibmc.memcached.PyLibMCCache` 72 | 73 | If there are no environment variables for memcache or memcacheify, the default 74 | cache will be local memory `django.core.cache.backends.locmem.LocMemCache`. 75 | 76 | 77 | ## Testing Your Cache 78 | 79 | If you don't trust me, and want to make sure your caching is working as 80 | expected, you may do the following: 81 | 82 | ``` bash 83 | $ heroku run python manage.py shell 84 | Running python manage.py shell attached to terminal... up, run.1 85 | Python 2.7.2 (default, Oct 31 2011, 16:22:04) 86 | [GCC 4.4.3] on linux2 87 | Type "help", "copyright", "credits" or "license" for more information. 88 | (InteractiveConsole) 89 | >>> from django.core.cache import cache 90 | >>> cache.set('memcache', 'ify!') 91 | True 92 | >>> cache.get('memcache') 93 | 'ify!' 94 | >>> 95 | ``` 96 | 97 | Assuming everything is working, you should be able to set and retrieve cache 98 | keys. 99 | 100 | 101 | ## References 102 | 103 | If you're confused, you should probably read: 104 | 105 | - [Heroku's Getting Started Guide](http://devcenter.heroku.com/articles/django) 106 | - [Heroku's memcachier Addon Documentation](https://devcenter.heroku.com/articles/memcachier) 107 | 108 | 109 | ## Tests 110 | 111 | [![Build Status](https://secure.travis-ci.org/rdegges/django-heroku-memcacheify.png?branch=master)](http://travis-ci.org/rdegges/django-heroku-memcacheify) 112 | 113 | Want to run the tests? No problem: 114 | 115 | ``` bash 116 | $ git clone git://github.com/rdegges/django-heroku-memcacheify.git 117 | $ cd django-heroku-memcacheify 118 | $ python setup.py develop 119 | ... 120 | $ pip install -r requirements.txt # Install test dependencies. 121 | $ flake8 122 | $ nosetests 123 | ............. 124 | ---------------------------------------------------------------------- 125 | Ran 13 tests in 0.166s 126 | 127 | OK 128 | ``` 129 | 130 | 131 | ## Changelog 132 | 133 | v1.0.1: 10-10-2021 134 | 135 | - Fixing PyPI description 136 | 137 | v1.0.0: 01-04-2016 138 | 139 | - Update django-pylibmc dependency to >=0.6.1. 140 | - Officially support Python 3.5. 141 | - Stop testing on Python 2.6. 142 | 143 | v0.8: 11-12-2014 144 | 145 | - Adding support for memcachedcloud! 146 | 147 | v0.7: 9-22-2014 148 | 149 | - Upgrading dependencies (again)! 150 | 151 | v0.6: 9-20-2014 152 | 153 | - Upgrading dependencies. 154 | 155 | v0.5: 12-31-2013 156 | 157 | - Making the timeout option configurable. 158 | - Removing Python 2.5 support. 159 | - Adding an option to use memcached locally without SASL. 160 | - Updating the README, explaining how to use memcached locally. 161 | 162 | v0.4: 12-5-2012 163 | 164 | - Update which allows memcachier users to support multiple servers >:) 165 | Thanks @alexlod! 166 | 167 | v0.3: 6-27-2012 168 | 169 | - Fixing broken memcachier support. 170 | 171 | v0.2: 5-22-2012 172 | 173 | - Adding support for memcachier Heroku addon. 174 | - Updating documentation. 175 | - Refactoring implementation for clarity. 176 | - Adding better tests. 177 | 178 | v0.1: 5-2-2012 179 | 180 | - Initial release! 181 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /assets/memcacheify.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdegges/django-heroku-memcacheify/5c8c71b72ea2cc30e23f2aa6cd095d9f42f4e0f6/assets/memcacheify.jpg -------------------------------------------------------------------------------- /memcacheify.py: -------------------------------------------------------------------------------- 1 | from os import environ 2 | 3 | 4 | # Memcache addon environment variables. 5 | # See: https://addons.heroku.com/memcache 6 | MEMCACHE_ENV_VARS = ( 7 | 'MEMCACHE_PASSWORD', 8 | 'MEMCACHE_SERVERS', 9 | 'MEMCACHE_USERNAME', 10 | ) 11 | 12 | 13 | # MemCachier addon environment variables. 14 | # See: https://addons.heroku.com/memcachier 15 | MEMCACHIER_ENV_VARS = ( 16 | 'MEMCACHIER_PASSWORD', 17 | 'MEMCACHIER_SERVERS', 18 | 'MEMCACHIER_USERNAME', 19 | ) 20 | 21 | 22 | # Memcached Cloud 23 | # See: https://addons.heroku.com/memcachedcloud 24 | MEMCACHEDCLOUD_ENV_VARS = ( 25 | 'MEMCACHEDCLOUD_PASSWORD', 26 | 'MEMCACHEDCLOUD_SERVERS', 27 | 'MEMCACHEDCLOUD_USERNAME', 28 | ) 29 | 30 | # Memcache defaults 31 | # Both Memcachier and MemcacheCloud share these 32 | CACHE_DEFAULTS = { 33 | 'BACKEND': 'django_pylibmc.memcached.PyLibMCCache', 34 | 'BINARY': True, 35 | 'OPTIONS': { 36 | 'ketama': True, 37 | 'tcp_nodelay': True, 38 | }, 39 | } 40 | 41 | 42 | def memcacheify(timeout=500): 43 | """Return a fully configured Django ``CACHES`` setting. We do this by 44 | analyzing all environment variables on Heorku, scanning for an available 45 | memcache addon, and then building the settings dict properly. 46 | 47 | If no memcache servers can be found, we'll revert to building a local 48 | memory cache. 49 | 50 | Returns a fully configured caches dict. 51 | """ 52 | caches = {} 53 | 54 | if all((environ.get(e, '') for e in MEMCACHE_ENV_VARS)): 55 | caches['default'] = CACHE_DEFAULTS 56 | caches['default'].update({ 57 | 'LOCATION': 'localhost:11211', 58 | 'TIMEOUT': timeout, 59 | }) 60 | elif all((environ.get(e, '') for e in MEMCACHIER_ENV_VARS)): 61 | servers = environ.get('MEMCACHIER_SERVERS').replace(',', ';') 62 | environ['MEMCACHE_SERVERS'] = servers 63 | environ['MEMCACHE_USERNAME'] = environ.get('MEMCACHIER_USERNAME') 64 | environ['MEMCACHE_PASSWORD'] = environ.get('MEMCACHIER_PASSWORD') 65 | caches['default'] = CACHE_DEFAULTS 66 | caches['default'].update({ 67 | 'LOCATION': servers, 68 | 'TIMEOUT': timeout, 69 | }) 70 | elif all((environ.get(e, '') for e in MEMCACHEDCLOUD_ENV_VARS)): 71 | servers = environ.get('MEMCACHEDCLOUD_SERVERS').replace(',', ';') 72 | environ['MEMCACHE_SERVERS'] = servers 73 | environ['MEMCACHE_USERNAME'] = environ.get('MEMCACHEDCLOUD_USERNAME') 74 | environ['MEMCACHE_PASSWORD'] = environ.get('MEMCACHEDCLOUD_PASSWORD') 75 | caches['default'] = CACHE_DEFAULTS 76 | caches['default'].update({ 77 | 'LOCATION': servers, 78 | 'TIMEOUT': timeout, 79 | }) 80 | elif environ.get('MEMCACHEIFY_USE_LOCAL', False): 81 | caches['default'] = { 82 | 'BACKEND': 'django_pylibmc.memcached.PyLibMCCache', 83 | } 84 | else: 85 | caches['default'] = { 86 | 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 87 | } 88 | 89 | return caches 90 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flake8==2.5.1 2 | nose==1.1.2 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 100 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from os.path import abspath, dirname, join, normpath 2 | 3 | from setuptools import setup 4 | 5 | 6 | setup( 7 | 8 | # Basic package information: 9 | name='django-heroku-memcacheify', 10 | version='1.0.1', 11 | py_modules=('memcacheify',), 12 | 13 | # Packaging options: 14 | zip_safe=False, 15 | include_package_data=True, 16 | 17 | # Package dependencies: 18 | install_requires=['django-pylibmc>=0.6.1'], 19 | 20 | # Metadata for PyPI: 21 | author='Randall Degges', 22 | author_email='r@rdegges.com', 23 | license='UNLICENSE', 24 | url='https://github.com/rdegges/django-heroku-memcacheify', 25 | keywords='django heroku cloud cache memcache memcached awesome epic', 26 | classifiers=[ 27 | 'Framework :: Django', 28 | 'Programming Language :: Python :: 2', 29 | 'Programming Language :: Python :: 2.7', 30 | 'Programming Language :: Python :: 3', 31 | 'Programming Language :: Python :: 3.5', 32 | ], 33 | description='Automatic Django memcached configuration on Heroku.', 34 | long_description=open(normpath(join(dirname(abspath(__file__)), 35 | 'README.md'))).read(), 36 | long_description_content_type='text/markdown' 37 | 38 | ) 39 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | from os import environ 2 | from unittest import TestCase 3 | 4 | from memcacheify import memcacheify 5 | 6 | 7 | class Memcacheify(TestCase): 8 | 9 | def test_uses_local_memory_backend_if_no_memcache_addon_is_available(self): 10 | self.assertEqual(memcacheify(), { 11 | 'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'} 12 | }) 13 | 14 | def tests_uses_local_memory_backend_if_one_of_the_memcache_env_vars_is_missing(self): 15 | environ['MEMCACHE_PASSWORD'] = 'GCnQ9DhfEJqNDlo1' 16 | environ['MEMCACHE_SERVERS'] = 'mc3.ec2.northscale.net' 17 | self.assertEqual(memcacheify(), { 18 | 'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'} 19 | }) 20 | del environ['MEMCACHE_PASSWORD'] 21 | del environ['MEMCACHE_SERVERS'] 22 | 23 | def test_sets_proper_backend_when_memcache_addon_is_available(self): 24 | environ['MEMCACHE_PASSWORD'] = 'GCnQ9DhfEJqNDlo1' 25 | environ['MEMCACHE_SERVERS'] = 'mc3.ec2.northscale.net' 26 | environ['MEMCACHE_USERNAME'] = 'appxxxxx%40heroku.com' 27 | self.assertEqual(memcacheify()['default']['BACKEND'], 28 | 'django_pylibmc.memcached.PyLibMCCache') 29 | del environ['MEMCACHE_PASSWORD'] 30 | del environ['MEMCACHE_SERVERS'] 31 | del environ['MEMCACHE_USERNAME'] 32 | 33 | def test_uses_local_memory_backend_if_no_memcachier_addon_is_available(self): 34 | environ['MEMCACHIER_PASSWORD'] = 'xxx' 35 | environ['MEMCACHIER_SERVERS'] = 'mc1.ec2.memcachier.com' 36 | self.assertEqual(memcacheify(), { 37 | 'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'} 38 | }) 39 | del environ['MEMCACHIER_PASSWORD'] 40 | del environ['MEMCACHIER_SERVERS'] 41 | 42 | def test_sets_proper_backend_when_memcachier_addon_is_available(self): 43 | environ['MEMCACHIER_PASSWORD'] = 'xxx' 44 | environ['MEMCACHIER_SERVERS'] = 'mc1.ec2.memcachier.com' 45 | environ['MEMCACHIER_USERNAME'] = 'xxx' 46 | 47 | caches = memcacheify() 48 | self.assertEqual(caches['default']['BACKEND'], 'django_pylibmc.memcached.PyLibMCCache') 49 | self.assertEqual(environ['MEMCACHE_SERVERS'], environ['MEMCACHIER_SERVERS']) 50 | self.assertEqual(environ['MEMCACHE_USERNAME'], environ['MEMCACHIER_USERNAME']) 51 | self.assertEqual(environ['MEMCACHE_PASSWORD'], environ['MEMCACHIER_PASSWORD']) 52 | 53 | del environ['MEMCACHIER_PASSWORD'] 54 | del environ['MEMCACHIER_SERVERS'] 55 | del environ['MEMCACHIER_USERNAME'] 56 | del environ['MEMCACHE_PASSWORD'] 57 | del environ['MEMCACHE_SERVERS'] 58 | del environ['MEMCACHE_USERNAME'] 59 | 60 | def test_sets_proper_backend_when_memcachedcloud_addon_is_available(self): 61 | environ['MEMCACHEDCLOUD_PASSWORD'] = 'xyz' 62 | environ['MEMCACHEDCLOUD_SERVERS'] = 'zzzz' 63 | environ['MEMCACHEDCLOUD_USERNAME'] = 'xyzzy' 64 | 65 | caches = memcacheify() 66 | self.assertEqual(caches['default']['BACKEND'], 'django_pylibmc.memcached.PyLibMCCache') 67 | self.assertEqual(environ['MEMCACHE_SERVERS'], environ['MEMCACHEDCLOUD_SERVERS']) 68 | self.assertEqual(environ['MEMCACHE_USERNAME'], environ['MEMCACHEDCLOUD_USERNAME']) 69 | self.assertEqual(environ['MEMCACHE_PASSWORD'], environ['MEMCACHEDCLOUD_PASSWORD']) 70 | 71 | del environ['MEMCACHEDCLOUD_PASSWORD'] 72 | del environ['MEMCACHEDCLOUD_SERVERS'] 73 | del environ['MEMCACHEDCLOUD_USERNAME'] 74 | del environ['MEMCACHE_PASSWORD'] 75 | del environ['MEMCACHE_SERVERS'] 76 | del environ['MEMCACHE_USERNAME'] 77 | --------------------------------------------------------------------------------