diff --git a/cachalot/apps.py b/cachalot/apps.py index 5e31b28..34a9f06 100644 --- a/cachalot/apps.py +++ b/cachalot/apps.py @@ -87,11 +87,6 @@ def check_databases_compatibility(app_configs, **kwargs): class CachalotConfig(AppConfig): name = 'cachalot' - patched = False def ready(self): cachalot_settings.load() - - if not self.patched: - patch() - self.patched = True diff --git a/cachalot/monkey_patch.py b/cachalot/monkey_patch.py index b0b2c54..265cf82 100644 --- a/cachalot/monkey_patch.py +++ b/cachalot/monkey_patch.py @@ -42,21 +42,19 @@ def _get_result_or_execute_query(execute_query_func, cache, new_table_cache_keys = set(table_cache_keys) new_table_cache_keys.difference_update(data) - if new_table_cache_keys: - now = time() - cache.set_many({k: now for k in new_table_cache_keys}, - cachalot_settings.CACHALOT_TIMEOUT) - elif cache_key in data: + if not new_table_cache_keys and cache_key in data: timestamp, result = data.pop(cache_key) - table_times = data.values() - if table_times and timestamp > max(table_times): + if timestamp >= max(data.values()): return result result = execute_query_func() if result.__class__ not in ITERABLES and isinstance(result, Iterable): result = list(result) - cache.set(cache_key, (time(), result), cachalot_settings.CACHALOT_TIMEOUT) + now = time() + to_be_set = {k: now for k in new_table_cache_keys} + to_be_set[cache_key] = (now, result) + cache.set_many(to_be_set, cachalot_settings.CACHALOT_TIMEOUT) return result @@ -67,8 +65,7 @@ def _patch_compiler(original): def inner(compiler, *args, **kwargs): execute_query_func = lambda: original(compiler, *args, **kwargs) db_alias = compiler.using - if not cachalot_settings.CACHALOT_ENABLED \ - or db_alias not in cachalot_settings.CACHALOT_DATABASES \ + if db_alias not in cachalot_settings.CACHALOT_DATABASES \ or isinstance(compiler, WRITE_COMPILERS): return execute_query_func() @@ -101,19 +98,26 @@ def _patch_write_compiler(original): def _patch_orm(): - SQLCompiler.execute_sql = _patch_compiler(SQLCompiler.execute_sql) + if cachalot_settings.CACHALOT_ENABLED: + SQLCompiler.execute_sql = _patch_compiler(SQLCompiler.execute_sql) for compiler in WRITE_COMPILERS: compiler.execute_sql = _patch_write_compiler(compiler.execute_sql) +def _unpatch_orm(): + if hasattr(SQLCompiler.execute_sql, '__wrapped__'): + SQLCompiler.execute_sql = SQLCompiler.execute_sql.__wrapped__ + for compiler in WRITE_COMPILERS: + compiler.execute_sql = compiler.execute_sql.__wrapped__ + + def _patch_cursor(): def _patch_cursor_execute(original): @wraps(original) def inner(cursor, sql, *args, **kwargs): out = original(cursor, sql, *args, **kwargs) connection = cursor.db - if getattr(connection, 'raw', True) \ - and cachalot_settings.CACHALOT_INVALIDATE_RAW: + if getattr(connection, 'raw', True): if isinstance(sql, binary_type): sql = sql.decode('utf-8') sql = sql.lower() @@ -128,8 +132,16 @@ def _patch_cursor(): return inner - CursorWrapper.execute = _patch_cursor_execute(CursorWrapper.execute) - CursorWrapper.executemany = _patch_cursor_execute(CursorWrapper.executemany) + if cachalot_settings.CACHALOT_INVALIDATE_RAW: + CursorWrapper.execute = _patch_cursor_execute(CursorWrapper.execute) + CursorWrapper.executemany = \ + _patch_cursor_execute(CursorWrapper.executemany) + + +def _unpatch_cursor(): + if hasattr(CursorWrapper.execute, '__wrapped__'): + CursorWrapper.execute = CursorWrapper.execute.__wrapped__ + CursorWrapper.executemany = CursorWrapper.executemany.__wrapped__ def _patch_atomic(): @@ -155,6 +167,11 @@ def _patch_atomic(): Atomic.__exit__ = patch_exit(Atomic.__exit__) +def _unpatch_atomic(): + Atomic.__enter__ = Atomic.__enter__.__wrapped__ + Atomic.__exit__ = Atomic.__exit__.__wrapped__ + + def _invalidate_on_migration(sender, **kwargs): invalidate(*sender.get_models(), db_alias=kwargs['using'], cache_alias=cachalot_settings.CACHALOT_CACHE) @@ -166,3 +183,11 @@ def patch(): _patch_cursor() _patch_atomic() _patch_orm() + + +def unpatch(): + post_migrate.disconnect(_invalidate_on_migration) + + _unpatch_cursor() + _unpatch_atomic() + _unpatch_orm() diff --git a/cachalot/panels.py b/cachalot/panels.py index c995cf7..f2d4437 100644 --- a/cachalot/panels.py +++ b/cachalot/panels.py @@ -33,9 +33,11 @@ class CachalotPanel(Panel): def enable_instrumentation(self): settings.CACHALOT_ENABLED = True + cachalot_settings.reload() def disable_instrumentation(self): settings.CACHALOT_ENABLED = False + cachalot_settings.reload() def process_response(self, request, response): self.collect_invalidations() diff --git a/cachalot/settings.py b/cachalot/settings.py index 6bf507b..43e3104 100644 --- a/cachalot/settings.py +++ b/cachalot/settings.py @@ -35,6 +35,7 @@ ITERABLES = {tuple, list, frozenset, set} class Settings(object): + patched = False converters = {} CACHALOT_ENABLED = True @@ -68,6 +69,21 @@ class Settings(object): value = converter(value) setattr(self, name, value) + if not self.patched: + from .monkey_patch import patch + patch() + self.patched = True + + def unload(self): + if self.patched: + from .monkey_patch import unpatch + unpatch() + self.patched = False + + def reload(self): + self.unload() + self.load() + @Settings.add_converter('CACHALOT_DATABASES') def convert(value): diff --git a/cachalot/tests/__init__.py b/cachalot/tests/__init__.py index 1f08614..714b869 100644 --- a/cachalot/tests/__init__.py +++ b/cachalot/tests/__init__.py @@ -16,4 +16,4 @@ from .debug_toolbar import DebugToolbarTestCase @receiver(setting_changed) def reload_settings(sender, **kwargs): - cachalot_settings.load() + cachalot_settings.reload() diff --git a/cachalot/utils.py b/cachalot/utils.py index de7b38b..f33cc4a 100644 --- a/cachalot/utils.py +++ b/cachalot/utils.py @@ -114,6 +114,7 @@ def _find_subqueries(children): if child_class is WhereNode: for grand_child in _find_subqueries(child.children): yield grand_child + # TODO: Remove this condition when we drop Django 1.8 support. elif child_class is SubqueryConstraint: query_object = child.query_object yield (query_object if query_object.__class__ is Query @@ -155,8 +156,8 @@ def filter_cachable(tables): def _get_tables(db_alias, query): if query.select_for_update or ( - '?' in query.order_by - and not cachalot_settings.CACHALOT_CACHE_RANDOM): + not cachalot_settings.CACHALOT_CACHE_RANDOM + and '?' in query.order_by): raise UncachableQuery try: