1
|
|
|
# coding: utf-8 |
2
|
|
|
|
3
|
|
|
from __future__ import unicode_literals |
4
|
|
|
|
5
|
|
|
from django.apps import apps |
6
|
|
|
from django.conf import settings |
7
|
|
|
from django.db import connections |
8
|
|
|
from django.utils.six import string_types |
9
|
|
|
|
10
|
|
|
from .cache import cachalot_caches |
11
|
|
|
from .settings import cachalot_settings |
12
|
|
|
from .signals import post_invalidation |
13
|
|
|
from .transaction import AtomicCache |
14
|
|
|
from .utils import _invalidate_tables |
15
|
|
|
|
16
|
|
|
|
17
|
|
|
__all__ = ('invalidate', 'get_last_invalidation') |
18
|
|
|
|
19
|
|
|
|
20
|
|
|
def _cache_db_tables_iterator(tables, cache_alias, db_alias): |
21
|
|
|
no_tables = not tables |
22
|
|
|
cache_aliases = settings.CACHES if cache_alias is None else (cache_alias,) |
23
|
|
|
db_aliases = settings.DATABASES if db_alias is None else (db_alias,) |
24
|
|
|
for db_alias in db_aliases: |
25
|
|
|
if no_tables: |
26
|
|
|
tables = connections[db_alias].introspection.table_names() |
27
|
|
|
if tables: |
28
|
|
|
for cache_alias in cache_aliases: |
29
|
|
|
yield cache_alias, db_alias, tables |
30
|
|
|
|
31
|
|
|
|
32
|
|
|
def _get_tables(tables_or_models): |
33
|
|
|
for table_or_model in tables_or_models: |
34
|
|
|
if isinstance(table_or_model, string_types) and '.' in table_or_model: |
35
|
|
|
try: |
36
|
|
|
table_or_model = apps.get_model(table_or_model) |
37
|
|
|
except LookupError: |
38
|
|
|
pass |
39
|
|
|
yield (table_or_model if isinstance(table_or_model, string_types) |
40
|
|
|
else table_or_model._meta.db_table) |
41
|
|
|
|
42
|
|
|
|
43
|
|
|
def invalidate(*tables_or_models, **kwargs): |
44
|
|
|
""" |
45
|
|
|
Clears what was cached by django-cachalot implying one or more SQL tables |
46
|
|
|
or models from ``tables_or_models``. |
47
|
|
|
If ``tables_or_models`` is not specified, all tables found in the database |
48
|
|
|
(including those outside Django) are invalidated. |
49
|
|
|
|
50
|
|
|
If ``cache_alias`` is specified, it only clears the SQL queries stored |
51
|
|
|
on this cache, otherwise queries from all caches are cleared. |
52
|
|
|
|
53
|
|
|
If ``db_alias`` is specified, it only clears the SQL queries executed |
54
|
|
|
on this database, otherwise queries from all databases are cleared. |
55
|
|
|
|
56
|
|
|
:arg tables_or_models: SQL tables names, models or models lookups |
57
|
|
|
(or a combination) |
58
|
|
|
:type tables_or_models: tuple of strings or models |
59
|
|
|
:arg cache_alias: Alias from the Django ``CACHES`` setting |
60
|
|
|
:type cache_alias: string or NoneType |
61
|
|
|
:arg db_alias: Alias from the Django ``DATABASES`` setting |
62
|
|
|
:type db_alias: string or NoneType |
63
|
|
|
:returns: Nothing |
64
|
|
|
:rtype: NoneType |
65
|
|
|
""" |
66
|
|
|
# TODO: Replace with positional arguments when we drop Python 2 support. |
67
|
|
|
cache_alias = kwargs.pop('cache_alias', None) |
68
|
|
|
db_alias = kwargs.pop('db_alias', None) |
69
|
|
|
for k in kwargs: |
70
|
|
|
raise TypeError( |
71
|
|
|
"invalidate() got an unexpected keyword argument '%s'" % k) |
72
|
|
|
|
73
|
|
|
send_signal = False |
74
|
|
|
invalidated = set() |
75
|
|
|
for cache_alias, db_alias, tables in _cache_db_tables_iterator( |
76
|
|
|
list(_get_tables(tables_or_models)), cache_alias, db_alias): |
77
|
|
|
cache = cachalot_caches.get_cache(cache_alias, db_alias) |
78
|
|
|
if not isinstance(cache, AtomicCache): |
79
|
|
|
send_signal = True |
80
|
|
|
_invalidate_tables(cache, db_alias, tables) |
81
|
|
|
invalidated.update(tables) |
82
|
|
|
|
83
|
|
|
if send_signal: |
84
|
|
|
for table in invalidated: |
85
|
|
|
post_invalidation.send(table, db_alias=db_alias) |
86
|
|
|
|
87
|
|
|
|
88
|
|
|
def get_last_invalidation(*tables_or_models, **kwargs): |
89
|
|
|
""" |
90
|
|
|
Returns the timestamp of the most recent invalidation of the given |
91
|
|
|
``tables_or_models``. If ``tables_or_models`` is not specified, |
92
|
|
|
all tables found in the database (including those outside Django) are used. |
93
|
|
|
|
94
|
|
|
If ``cache_alias`` is specified, it only fetches invalidations |
95
|
|
|
in this cache, otherwise invalidations in all caches are fetched. |
96
|
|
|
|
97
|
|
|
If ``db_alias`` is specified, it only fetches invalidations |
98
|
|
|
for this database, otherwise invalidations for all databases are fetched. |
99
|
|
|
|
100
|
|
|
:arg tables_or_models: SQL tables names, models or models lookups |
101
|
|
|
(or a combination) |
102
|
|
|
:type tables_or_models: tuple of strings or models |
103
|
|
|
:arg cache_alias: Alias from the Django ``CACHES`` setting |
104
|
|
|
:type cache_alias: string or NoneType |
105
|
|
|
:arg db_alias: Alias from the Django ``DATABASES`` setting |
106
|
|
|
:type db_alias: string or NoneType |
107
|
|
|
:returns: The timestamp of the most recent invalidation |
108
|
|
|
:rtype: float |
109
|
|
|
""" |
110
|
|
|
# TODO: Replace with positional arguments when we drop Python 2 support. |
111
|
|
|
cache_alias = kwargs.pop('cache_alias', None) |
112
|
|
|
db_alias = kwargs.pop('db_alias', None) |
113
|
|
|
for k in kwargs: |
114
|
|
|
raise TypeError("get_last_invalidation() got an unexpected " |
115
|
|
|
"keyword argument '%s'" % k) |
116
|
|
|
|
117
|
|
|
last_invalidation = 0.0 |
118
|
|
|
for cache_alias, db_alias, tables in _cache_db_tables_iterator( |
119
|
|
|
list(_get_tables(tables_or_models)), cache_alias, db_alias): |
120
|
|
|
get_table_cache_key = cachalot_settings.CACHALOT_TABLE_KEYGEN |
121
|
|
|
table_cache_keys = [get_table_cache_key(db_alias, t) for t in tables] |
122
|
|
|
invalidations = cachalot_caches.get_cache( |
123
|
|
|
cache_alias, db_alias).get_many(table_cache_keys).values() |
124
|
|
|
if invalidations: |
125
|
|
|
current_last_invalidation = max(invalidations) |
126
|
|
|
if current_last_invalidation > last_invalidation: |
127
|
|
|
last_invalidation = current_last_invalidation |
128
|
|
|
return last_invalidation |
129
|
|
|
|