debian-django-cachalot/cachalot/monkey_patch.py

169 lines
5.5 KiB
Python
Raw Normal View History

2016-07-21 23:59:11 +02:00
# coding: utf-8
2014-09-26 16:53:44 +02:00
from __future__ import unicode_literals
from collections import Iterable
from functools import wraps
from time import time
2015-04-11 01:57:13 +02:00
from django.db.backends.utils import CursorWrapper
2017-06-03 22:17:22 +02:00
from django.db.models.query import EmptyResultSet
2015-04-11 01:57:13 +02:00
from django.db.models.signals import post_migrate
2014-09-26 16:53:44 +02:00
from django.db.models.sql.compiler import (
SQLCompiler, SQLInsertCompiler, SQLUpdateCompiler, SQLDeleteCompiler,
)
2014-10-20 23:43:10 +02:00
from django.db.transaction import Atomic, get_connection
2016-07-21 23:59:11 +02:00
from django.utils.six import binary_type
from .api import invalidate
from .cache import cachalot_caches
from .settings import cachalot_settings, ITERABLES
from .utils import (
_get_table_cache_keys, _get_tables_from_sql,
UncachableQuery, is_cachable, filter_cachable,
)
2014-09-26 16:53:44 +02:00
2016-07-21 23:59:11 +02:00
2014-09-26 16:53:44 +02:00
WRITE_COMPILERS = (SQLInsertCompiler, SQLUpdateCompiler, SQLDeleteCompiler)
2014-11-04 01:17:35 +01:00
def _unset_raw_connection(original):
def inner(compiler, *args, **kwargs):
compiler.connection.raw = False
out = original(compiler, *args, **kwargs)
compiler.connection.raw = True
return out
return inner
2014-09-26 16:53:44 +02:00
def _get_result_or_execute_query(execute_query_func, cache,
cache_key, table_cache_keys):
data = cache.get_many(table_cache_keys + [cache_key])
new_table_cache_keys = set(table_cache_keys)
new_table_cache_keys.difference_update(data)
if new_table_cache_keys:
2014-12-13 19:13:22 +01:00
now = time()
2016-09-06 21:57:26 +02:00
cache.set_many({k: now for k in new_table_cache_keys},
cachalot_settings.CACHALOT_TIMEOUT)
elif cache_key in data:
2014-12-14 08:35:52 +01:00
timestamp, result = data.pop(cache_key)
table_times = data.values()
if table_times and timestamp > max(table_times):
return result
result = execute_query_func()
if result.__class__ not in ITERABLES and isinstance(result, Iterable):
result = list(result)
2016-09-06 21:57:26 +02:00
cache.set(cache_key, (time(), result), cachalot_settings.CACHALOT_TIMEOUT)
return result
2014-11-04 01:17:35 +01:00
def _patch_compiler(original):
@wraps(original)
@_unset_raw_connection
def inner(compiler, *args, **kwargs):
execute_query_func = lambda: original(compiler, *args, **kwargs)
db_alias = compiler.using
2014-11-04 01:17:35 +01:00
if not cachalot_settings.CACHALOT_ENABLED \
or db_alias not in cachalot_settings.CACHALOT_DATABASES \
2015-06-19 18:06:20 +02:00
or isinstance(compiler, WRITE_COMPILERS):
return execute_query_func()
2014-11-04 01:17:35 +01:00
try:
cache_key = cachalot_settings.CACHALOT_QUERY_KEYGEN(compiler)
table_cache_keys = _get_table_cache_keys(compiler)
except (EmptyResultSet, UncachableQuery):
return execute_query_func()
2014-11-04 01:17:35 +01:00
return _get_result_or_execute_query(
execute_query_func,
cachalot_caches.get_cache(db_alias=db_alias),
cache_key, table_cache_keys)
2014-11-04 01:17:35 +01:00
return inner
def _patch_write_compiler(original):
@wraps(original)
@_unset_raw_connection
2015-05-24 19:34:24 +02:00
def inner(write_compiler, *args, **kwargs):
db_alias = write_compiler.using
table = write_compiler.query.get_meta().db_table
2016-09-07 00:44:51 +02:00
if is_cachable(table):
invalidate(table, db_alias=db_alias,
cache_alias=cachalot_settings.CACHALOT_CACHE)
2015-05-24 19:34:24 +02:00
return original(write_compiler, *args, **kwargs)
2014-11-04 01:17:35 +01:00
return inner
def _patch_orm():
SQLCompiler.execute_sql = _patch_compiler(SQLCompiler.execute_sql)
for compiler in WRITE_COMPILERS:
compiler.execute_sql = _patch_write_compiler(compiler.execute_sql)
def _patch_cursor():
def _patch_cursor_execute(original):
@wraps(original)
2014-12-07 02:44:50 +01:00
def inner(cursor, sql, *args, **kwargs):
out = original(cursor, sql, *args, **kwargs)
connection = cursor.db
if getattr(connection, 'raw', True) \
2014-11-04 01:17:35 +01:00
and cachalot_settings.CACHALOT_INVALIDATE_RAW:
2016-07-21 23:59:11 +02:00
if isinstance(sql, binary_type):
sql = sql.decode('utf-8')
2014-12-07 02:44:50 +01:00
sql = sql.lower()
2017-06-03 22:02:23 +02:00
if 'update' in sql or 'insert' in sql or 'delete' in sql \
or 'alter' in sql or 'create' in sql or 'drop' in sql:
2016-09-07 00:44:51 +02:00
tables = filter_cachable(
_get_tables_from_sql(connection, sql))
2016-09-07 00:44:51 +02:00
if tables:
invalidate(*tables, db_alias=connection.alias,
cache_alias=cachalot_settings.CACHALOT_CACHE)
2014-11-04 01:17:35 +01:00
return out
2014-09-26 16:53:44 +02:00
return inner
2014-11-04 01:17:35 +01:00
CursorWrapper.execute = _patch_cursor_execute(CursorWrapper.execute)
2014-12-07 02:44:50 +01:00
CursorWrapper.executemany = _patch_cursor_execute(CursorWrapper.executemany)
2014-09-26 16:53:44 +02:00
2014-09-29 19:16:53 +02:00
def _patch_atomic():
def patch_enter(original):
@wraps(original)
2014-09-29 19:16:53 +02:00
def inner(self):
cachalot_caches.enter_atomic(self.using)
2014-09-29 19:16:53 +02:00
original(self)
return inner
def patch_exit(original):
@wraps(original)
2014-09-29 19:16:53 +02:00
def inner(self, exc_type, exc_value, traceback):
2014-10-20 23:43:10 +02:00
needs_rollback = get_connection(self.using).needs_rollback
original(self, exc_type, exc_value, traceback)
cachalot_caches.exit_atomic(
self.using, exc_type is None and not needs_rollback)
2014-09-29 19:16:53 +02:00
return inner
Atomic.__enter__ = patch_enter(Atomic.__enter__)
Atomic.__exit__ = patch_exit(Atomic.__exit__)
def _invalidate_on_migration(sender, **kwargs):
invalidate(*sender.get_models(), db_alias=kwargs['using'],
cache_alias=cachalot_settings.CACHALOT_CACHE)
2014-09-29 19:16:53 +02:00
def patch():
post_migrate.connect(_invalidate_on_migration)
2014-11-04 01:17:35 +01:00
_patch_cursor()
2014-10-21 21:47:50 +02:00
_patch_atomic()
2014-11-04 01:17:35 +01:00
_patch_orm()