From 0c8180a6e799e5a4097e2f6be09747ac70526aed Mon Sep 17 00:00:00 2001 From: Bertrand Bordage Date: Wed, 18 Feb 2015 03:03:32 +0100 Subject: [PATCH] Adds the `invalidate_cachalot` Django command. --- cachalot/api.py | 32 ++++++------ cachalot/management/__init__.py | 0 cachalot/management/commands/__init__.py | 0 .../commands/invalidate_cachalot.py | 52 +++++++++++++++++++ docs/limits.rst | 4 +- docs/quickstart.rst | 3 ++ docs/todo.rst | 1 - 7 files changed, 74 insertions(+), 18 deletions(-) create mode 100644 cachalot/management/__init__.py create mode 100644 cachalot/management/commands/__init__.py create mode 100644 cachalot/management/commands/invalidate_cachalot.py diff --git a/cachalot/api.py b/cachalot/api.py index a1fbef3..a01f478 100644 --- a/cachalot/api.py +++ b/cachalot/api.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals from django.conf import settings -from django.db import connection from .cache import cachalot_caches from .utils import _get_table_cache_key, _invalidate_table_cache_keys @@ -12,16 +11,24 @@ from .utils import _get_table_cache_key, _invalidate_table_cache_keys __all__ = ('invalidate_tables', 'invalidate_models', 'invalidate_all') +def _aliases_iterator(cache_alias, db_alias): + cache_aliases = settings.CACHES if cache_alias is None else (cache_alias,) + db_aliases = settings.DATABASES if db_alias is None else (db_alias,) + for cache_alias in cache_aliases: + for db_alias in db_aliases: + yield cache_alias, db_alias + + def invalidate_tables(tables, cache_alias=None, db_alias=None): """ Clears what was cached by django-cachalot implying one or more SQL tables from ``tables``. If ``cache_alias`` is specified, it only clears the SQL queries stored - on this cache, otherwise the current cache is cleared. + on this cache, otherwise queries from all caches are cleared. If ``db_alias`` is specified, it only clears the SQL queries executed - on this database, otherwise queries from the current database are cleared. + on this database, otherwise queries from all databases are cleared. :arg tables: SQL tables names :type tables: iterable of strings @@ -33,12 +40,10 @@ def invalidate_tables(tables, cache_alias=None, db_alias=None): :rtype: NoneType """ - if db_alias is None: - db_alias = connection.alias - - table_cache_keys = [_get_table_cache_key(db_alias, t) for t in tables] - cache = cachalot_caches.get_cache(cache_alias) - _invalidate_table_cache_keys(cache, table_cache_keys) + for cache_alias, db_alias in _aliases_iterator(cache_alias, db_alias): + table_cache_keys = [_get_table_cache_key(db_alias, t) for t in tables] + cache = cachalot_caches.get_cache(cache_alias) + _invalidate_table_cache_keys(cache, table_cache_keys) def invalidate_models(models, cache_alias=None, db_alias=None): @@ -65,7 +70,7 @@ def invalidate_all(cache_alias=None, db_alias=None): Clears everything that was cached by django-cachalot. If ``cache_alias`` is specified, it only clears the SQL queries stored - on this cache, otherwise all caches are cleared. + on this cache, otherwise queries from all caches are cleared. If ``db_alias`` is specified, it only clears the SQL queries executed on this database, otherwise queries from all databases are cleared. @@ -78,8 +83,5 @@ def invalidate_all(cache_alias=None, db_alias=None): :rtype: NoneType """ - cache_aliases = settings.CACHES if cache_alias is None else (cache_alias,) - db_aliases = settings.DATABASES if db_alias is None else (db_alias,) - for cache_alias in cache_aliases: - for db_alias in db_aliases: - cachalot_caches.invalidate_all(cache_alias, db_alias) + for cache_alias, db_alias in _aliases_iterator(cache_alias, db_alias): + cachalot_caches.invalidate_all(cache_alias, db_alias) diff --git a/cachalot/management/__init__.py b/cachalot/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cachalot/management/commands/__init__.py b/cachalot/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cachalot/management/commands/invalidate_cachalot.py b/cachalot/management/commands/invalidate_cachalot.py new file mode 100644 index 0000000..4343d80 --- /dev/null +++ b/cachalot/management/commands/invalidate_cachalot.py @@ -0,0 +1,52 @@ +from optparse import make_option +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from django.core.management.base import BaseCommand +from django.db.models import get_app, get_model, get_models +from ...api import invalidate_all, invalidate_models + + +class Command(BaseCommand): + help = 'Invalidates the cache keys set by django-cachalot.' + args = '[app_label[.modelname] [...]]' + option_list = BaseCommand.option_list + ( + make_option('-c', '--cache', action='store', dest='cache_alias', + type='choice', choices=settings.CACHES.keys(), + help='Cache alias from the CACHES setting.'), + make_option('-d', '--db', action='store', dest='db_alias', + type='choice', choices=settings.DATABASES.keys(), + help='Database alias from the DATABASES setting.'), + ) + + def handle(self, *args, **options): + cache_alias = options['cache_alias'] + db_alias = options['db_alias'] + verbosity = options['verbosity'] + + models = [] + for arg in args: + try: + app = get_app(arg) + except ImproperlyConfigured: + app_label = '.'.join(arg.split('.')[:-1]) + model_name = arg.split('.')[-1] + models.append(get_model(app_label, model_name)) + else: + models.extend(get_models(app)) + + cache_str = '' if cache_alias is None else "on cache '%s'" % cache_alias + db_str = '' if db_alias is None else "for database '%s'" % db_alias + keys_str = 'keys for %s models' % len(models) if args else 'all keys' + + if verbosity != '0': + self.stdout.write(' '.join(filter(bool, ['Invalidating', keys_str, + cache_str, db_str])) + + '...') + + if args: + invalidate_models(models, + cache_alias=cache_alias, db_alias=db_alias) + else: + invalidate_all(cache_alias=cache_alias, db_alias=db_alias) + if verbosity != '0': + self.stdout.write('Cache keys successfully invalidated.') diff --git a/docs/limits.rst b/docs/limits.rst index 6d37515..a9a9a1f 100644 --- a/docs/limits.rst +++ b/docs/limits.rst @@ -6,14 +6,14 @@ Limits Locmem ...... -Locmem is a just a dict stored in a single Python process. +Locmem is a just a ``dict`` stored in a single Python process. It’s not shared between processes, so don’t use locmem with django-cachalot in a multi-processes project, if you use RQ or Celery for instance. Redis ..... -By default, Redis will not evict persistent cache keys (those a ``None`` +By default, Redis will not evict persistent cache keys (those with a ``None`` timeout) when the maximum memory has been reached. The cache keys created by django-cachalot are persistent, so if Redis runs out of memory, django-cachalot and all other ``cache.set`` will raise diff --git a/docs/quickstart.rst b/docs/quickstart.rst index bca756f..29f17ba 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -34,6 +34,9 @@ Usage `django-debug-toolbar `_, you can add ``'cachalot.panels.CachalotPanel',`` to your ``DEBUG_TOOLBAR_PANELS`` +#. If you need to invalidate all django-cachalot cache keys from an external script + – typically after restoring a SQL database –, simply run + ``./manage.py invalidate_cachalot`` #. Enjoy! diff --git a/docs/todo.rst b/docs/todo.rst index 95248dd..97ec2c7 100644 --- a/docs/todo.rst +++ b/docs/todo.rst @@ -6,4 +6,3 @@ For version 1.0 - Cache raw queries - Test multi-location caches -- Add a management command to invalidate everything