Version 0.1.0.

This commit is contained in:
Bertrand Bordage 2014-09-26 16:53:44 +02:00
commit 53f8078a55
10 changed files with 231 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
__pycache__/
*.pyc
*.so
build/
dist/
sdist/
*.egg-info/

6
AUTHORS.rst Normal file
View File

@ -0,0 +1,6 @@
Authors
=======
================ ==========================
Bertrand Bordage bordage.bertrand@gmail.com
================ ==========================

27
LICENSE Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2014, Bertrand Bordage
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of django-cachalot nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

39
README.rst Normal file
View File

@ -0,0 +1,39 @@
Django-cachalot
===============
Caches your Django ORM queries and automatically invalidates them.
**In alpha, do not use for production**
.. image:: https://raw.github.com/BertrandBordage/django-cachalot/master/django-cachalot.jpg
Quick start
-----------
Requirements
............
Django-cachalot currently requires Django 1.6
and `django-redis <https://github.com/niwibe/django-redis>`_ as your default
cache backend. It should work with both Python 2 & 3.
Usage
.....
#. `pip install -e git+https://github.com/BertrandBordage/django-cachalot#egg=django-cachalot`
#. Add ``'cachalot',`` to your ``INSTALLED_APPS``
#. Enjoy!
What still needs to be done
---------------------------
- Correctly invalidate ``.extra`` queries
- Handle transactions
- Handle multiple database
- Write tests, including multi-table inheritance, prefetch_related, etc
- Find out if its thread-safe and test it
- Add a ``CACHALOT_ENABLED`` setting
- Add a setting to choose a cache other than ``'default'``
- Add support for other caches like memcached

2
cachalot/__init__.py Normal file
View File

@ -0,0 +1,2 @@
__version__ = (0, 1, 0)
version_string = '.'.join(str(n) for n in __version__)

5
cachalot/models.py Normal file
View File

@ -0,0 +1,5 @@
from .monkey_patch import is_patched, monkey_patch_orm
if not is_patched():
monkey_patch_orm()

101
cachalot/monkey_patch.py Normal file
View File

@ -0,0 +1,101 @@
# coding: utf-8
from __future__ import unicode_literals
from collections import Iterable
from django.core.cache import cache
from django.db.models.query import EmptyResultSet
from django.db.models.sql.compiler import (
SQLCompiler, SQLAggregateCompiler, SQLDateCompiler, SQLDateTimeCompiler,
SQLInsertCompiler, SQLUpdateCompiler, SQLDeleteCompiler)
COMPILERS = (SQLCompiler,
SQLAggregateCompiler, SQLDateCompiler, SQLDateTimeCompiler,
SQLInsertCompiler, SQLUpdateCompiler, SQLDeleteCompiler)
WRITE_COMPILERS = (SQLInsertCompiler, SQLUpdateCompiler, SQLDeleteCompiler)
READ_COMPILERS = [c for c in COMPILERS if c not in WRITE_COMPILERS]
PATCHED = False
MISS_VALUE = '[[The cache key was missed]]'
def _get_tables_cache_keys(compiler):
q = compiler.query
# FIXME: `.extra` (and maybe more) are not in alias_map
tables = q.alias_map.keys()
return ['%s_queries' % t for t in tables]
def _update_tables_queries(compiler, cache_key):
tables_cache_keys = _get_tables_cache_keys(compiler)
tables_queries = cache.get_many(tables_cache_keys)
for k in tables_cache_keys:
queries = tables_queries.get(k, [])
queries.append(cache_key)
tables_queries[k] = queries
cache.set_many(tables_queries)
def _invalidate_tables(compiler):
tables_cache_keys = _get_tables_cache_keys(compiler)
tables_queries = cache.get_many(tables_cache_keys)
queries = []
for k in tables_cache_keys:
queries.extend(tables_queries.get(k, []))
cache.delete_many(queries)
cache.delete_many(tables_cache_keys)
def _monkey_patch_orm_read():
def patch_execute_sql(method):
def inner(compiler, *args, **kwargs):
if isinstance(compiler, WRITE_COMPILERS):
return method(compiler, *args, **kwargs)
try:
cache_key = compiler.as_sql()
except EmptyResultSet:
return method(compiler, *args, **kwargs)
result = cache.get(cache_key, MISS_VALUE)
if result == MISS_VALUE:
result = method(compiler, *args, **kwargs)
if isinstance(result, Iterable) \
and not isinstance(result, (tuple, list)):
result = list(result)
_update_tables_queries(compiler, cache_key)
cache.set(cache_key, result)
return result
return inner
for compiler in READ_COMPILERS:
compiler.execute_sql = patch_execute_sql(compiler.execute_sql)
def _monkey_patch_orm_write():
def patch_execute_sql(method):
def inner(compiler, *args, **kwargs):
_invalidate_tables(compiler)
return method(compiler, *args, **kwargs)
return inner
for compiler in WRITE_COMPILERS:
compiler.execute_sql = patch_execute_sql(compiler.execute_sql)
def monkey_patch_orm():
global PATCHED
_monkey_patch_orm_write()
_monkey_patch_orm_read()
PATCHED = True
def is_patched():
return PATCHED

BIN
django-cachalot.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
Django>=1.6

43
setup.py Normal file
View File

@ -0,0 +1,43 @@
#!/usr/bin/env python
import os
from setuptools import setup, find_packages
from cachalot import version_string
CURRENT_PATH = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(CURRENT_PATH, 'requirements.txt')) as f:
required = f.read().splitlines()
setup(
name='django-cachalot',
version=version_string,
author='Bertrand Bordage',
author_email='bordage.bertrand@gmail.com',
url='https://github.com/BertrandBordage/django-cachalot',
description='Caches your Django ORM queries '
'and automatically invalidates them.',
long_description=open('README.rst').read(),
classifiers=[
'Development Status :: 3 - Alpha',
'Framework :: Django',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Topic :: Internet :: WWW/HTTP',
],
license='BSD',
packages=find_packages(),
install_requires=required,
include_package_data=True,
zip_safe=False,
)