Skip to content

Commit

Permalink
Creates get_last_invalidation(), while simplifying API usage.
Browse files Browse the repository at this point in the history
  • Loading branch information
BertrandBordage committed Oct 5, 2015
1 parent abc00b1 commit 6c13636
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 13 deletions.
60 changes: 54 additions & 6 deletions cachalot/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from .utils import _get_table_cache_key, _invalidate_table_cache_keys


__all__ = ('invalidate',)
__all__ = ('invalidate', 'get_last_invalidation')


def _get_table_cache_keys_per_cache(tables, cache_alias, db_alias):
Expand All @@ -35,30 +35,78 @@ def _get_tables(tables_or_models):
for o in tables_or_models]


def invalidate(tables_or_models=(), cache_alias=None, db_alias=None):
def invalidate(*tables_or_models, **kwargs):
"""
Clears what was cached by django-cachalot implying one or more SQL tables
or models from ``tables_or_models``. If ``tables_or_models``
is not specified, all tables found in the database are invalidated.
or models from ``tables_or_models``.
If ``tables_or_models`` is not specified, all tables found in the database
(including those outside Django) are invalidated.
If ``cache_alias`` is specified, it only clears the SQL queries stored
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.
:arg tables_or_models: SQL tables names
:type tables_or_models: iterable of strings or models, or NoneType
:arg tables_or_models: SQL tables names or models (or combination of both)
:type tables_or_models: tuple of strings or models
:arg cache_alias: Alias from the Django ``CACHES`` setting
:type cache_alias: string or NoneType
:arg db_alias: Alias from the Django ``DATABASES`` setting
:type db_alias: string or NoneType
:returns: Nothing
:rtype: NoneType
"""
# TODO: Replace with positional arguments when we drop Python 2 support.
cache_alias = kwargs.pop('cache_alias', None)
db_alias = kwargs.pop('db_alias', None)
for k in kwargs:
raise TypeError(
"invalidate() got an unexpected keyword argument '%s'" % k)

table_cache_keys_per_cache = _get_table_cache_keys_per_cache(
_get_tables(tables_or_models), cache_alias, db_alias)
for cache_alias, table_cache_keys in table_cache_keys_per_cache.items():
_invalidate_table_cache_keys(cachalot_caches.get_cache(cache_alias),
table_cache_keys)


def get_last_invalidation(*tables_or_models, **kwargs):
"""
Returns the timestamp of the most recent invalidation of the given
``tables_or_models``. If ``tables_or_models`` is not specified,
all tables found in the database (including those outside Django) are used.
If ``cache_alias`` is specified, it only clears the SQL queries stored
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.
:arg tables_or_models: SQL tables names or models (or combination of both)
:type tables_or_models: tuple of strings or models
:arg cache_alias: Alias from the Django ``CACHES`` setting
:type cache_alias: string or NoneType
:arg db_alias: Alias from the Django ``DATABASES`` setting
:type db_alias: string or NoneType
:returns: The timestamp of the most recent invalidation
:rtype: float
"""
# TODO: Replace with positional arguments when we drop Python 2 support.
cache_alias = kwargs.pop('cache_alias', None)
db_alias = kwargs.pop('db_alias', None)
for k in kwargs:
raise TypeError("get_last_invalidation() got an unexpected "
"keyword argument '%s'" % k)

last_invalidation = 0.0
table_cache_keys_per_cache = _get_table_cache_keys_per_cache(
_get_tables(tables_or_models), cache_alias, db_alias)
for cache_alias, table_cache_keys in table_cache_keys_per_cache.items():
invalidations = cachalot_caches.get_cache(
cache_alias).get_many(table_cache_keys).values()
if invalidations:
current_last_invalidation = max(invalidations)
if current_last_invalidation > last_invalidation:
last_invalidation = current_last_invalidation
return last_invalidation
2 changes: 1 addition & 1 deletion cachalot/management/commands/invalidate_cachalot.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@ def handle(self, *args, **options):
cache_str, db_str]))
+ '...')

invalidate(models, cache_alias=cache_alias, db_alias=db_alias)
invalidate(*models, cache_alias=cache_alias, db_alias=db_alias)
if verbosity > 0:
self.stdout.write('Cache keys successfully invalidated.')
4 changes: 2 additions & 2 deletions cachalot/monkey_patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def inner(cursor, sql, *args, **kwargs):
sql = sql.lower()
if 'update' in sql or 'insert' in sql or 'delete' in sql:
tables = _get_tables_from_sql(cursor.db, sql)
invalidate(tables, db_alias=cursor.db.alias)
invalidate(*tables, db_alias=cursor.db.alias)
return out

return inner
Expand Down Expand Up @@ -143,7 +143,7 @@ def inner(self, exc_type, exc_value, traceback):


def _invalidate_on_migration(sender, **kwargs):
invalidate(sender.get_models(), db_alias=kwargs['using'])
invalidate(*sender.get_models(), db_alias=kwargs['using'])


def patch():
Expand Down
22 changes: 20 additions & 2 deletions cachalot/tests/api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# coding: utf-8

from __future__ import unicode_literals
from time import time, sleep
from unittest import skipIf

from django.conf import settings
Expand Down Expand Up @@ -34,7 +35,7 @@ def test_invalidate_tables(self):
data2 = list(Test.objects.values_list('name', flat=True))
self.assertListEqual(data2, ['test1'])

invalidate(['cachalot_test'])
invalidate('cachalot_test')

with self.assertNumQueries(1):
data3 = list(Test.objects.values_list('name', flat=True))
Expand All @@ -55,7 +56,7 @@ def test_invalidate_models(self):
data2 = list(Test.objects.values_list('name', flat=True))
self.assertListEqual(data2, ['test1'])

invalidate([Test])
invalidate(Test)

with self.assertNumQueries(1):
data3 = list(Test.objects.values_list('name', flat=True))
Expand Down Expand Up @@ -89,6 +90,23 @@ def test_invalidate_all_in_atomic(self):
with self.assertNumQueries(1):
Test.objects.get()

def test_get_last_invalidation(self):
invalidate()
timestamp = get_last_invalidation()
self.assertAlmostEqual(timestamp, time(), delta=0.1)

sleep(0.1)

invalidate('cachalot_test')
timestamp = get_last_invalidation('cachalot_test')
self.assertAlmostEqual(timestamp, time(), delta=0.1)

timestamp = get_last_invalidation('cachalot_testparent')
self.assertNotAlmostEqual(timestamp, time(), delta=0.1)
timestamp = get_last_invalidation('cachalot_testparent',
'cachalot_test')
self.assertAlmostEqual(timestamp, time(), delta=0.1)


class CommandTestCase(TransactionTestCase):
multi_db = True
Expand Down
5 changes: 3 additions & 2 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
API
---

Use these tools if the automatic behaviour of django-cachalot is not enough.
See :ref:`Raw queries limits`.
Use these tools to interact with django-cachalot, especially if you face
:ref:`Raw queries limits` or if you need to create a cache key from the
last table invalidation timestamp.

.. automodule:: cachalot.api
:members:

0 comments on commit 6c13636

Please sign in to comment.