|
3 | 3 |
|
4 | 4 | from pymongo.errors import OperationFailure
|
5 | 5 |
|
6 |
| -from ._config import CACHE_COLL, CACHE_DB |
7 |
| - |
8 | 6 | logger = logging.getLogger(__name__)
|
9 | 7 |
|
| 8 | +CACHE_COLL = 'cache' |
| 9 | +CACHE_DB = 'meta_db' |
| 10 | +CACHE_SETTINGS = 'cache_settings' |
| 11 | +DEFAULT_CACHE_EXPIRY = 3600 |
| 12 | + |
10 | 13 |
|
11 | 14 | class Cache:
|
12 |
| - def __init__(self, client, cache_expiry=3600, cache_db=CACHE_DB, cache_col=CACHE_COLL): |
| 15 | + def __init__(self, client, cache_expiry=DEFAULT_CACHE_EXPIRY, cache_db=CACHE_DB, cache_col=CACHE_COLL): |
13 | 16 | self._client = client
|
14 | 17 | self._cachedb = client[cache_db]
|
15 | 18 | self._cachecol = None
|
16 | 19 | try:
|
17 | 20 | if cache_col not in self._cachedb.list_collection_names():
|
18 | 21 | self._cachedb.create_collection(cache_col).create_index("date", expireAfterSeconds=cache_expiry)
|
19 | 22 | except OperationFailure as op:
|
20 |
| - logging.debug("This is fine if you are not admin. The collection should already be created for you: %s", op) |
| 23 | + logging.info("This is fine if you are not admin. The collection should already be created for you: %s", op) |
21 | 24 |
|
22 | 25 | self._cachecol = self._cachedb[cache_col]
|
23 | 26 |
|
24 |
| - def get(self, key, newer_than_secs=-1): |
| 27 | + def _get_cache_settings(self): |
| 28 | + try: |
| 29 | + return self._cachedb[CACHE_SETTINGS].find_one() |
| 30 | + except OperationFailure as op: |
| 31 | + logging.debug("Cannot access %s in db: %s. Error: %s" % (CACHE_SETTINGS, CACHE_DB, op)) |
| 32 | + return None |
| 33 | + |
| 34 | + def set_caching_state(self, enabled): |
| 35 | + """ |
| 36 | + Used to enable or disable the caching globally |
| 37 | + :return: |
| 38 | + """ |
| 39 | + if not isinstance(enabled, bool): |
| 40 | + logging.error("Enabled should be a boolean type.") |
| 41 | + return |
| 42 | + |
| 43 | + if CACHE_SETTINGS not in self._cachedb.list_collection_names(): |
| 44 | + logging.info("Creating %s collection for cache settings" % CACHE_SETTINGS) |
| 45 | + self._cachedb[CACHE_SETTINGS].insert_one({ |
| 46 | + 'enabled': enabled, |
| 47 | + 'cache_expiry': DEFAULT_CACHE_EXPIRY |
| 48 | + }) |
| 49 | + else: |
| 50 | + self._cachedb[CACHE_SETTINGS].update_one({}, {'$set': {'enabled': enabled}}) |
| 51 | + logging.info("Caching set to: %s" % enabled) |
| 52 | + |
| 53 | + def _is_not_expired(self, cached_data, newer_than_secs): |
| 54 | + # Use the expiry period in the settings (or the default) if not overriden by the function argument. |
| 55 | + if newer_than_secs: |
| 56 | + expiry_period = newer_than_secs |
| 57 | + else: |
| 58 | + cache_settings = self._get_cache_settings() |
| 59 | + expiry_period = cache_settings['cache_expiry'] if cache_settings else DEFAULT_CACHE_EXPIRY |
| 60 | + |
| 61 | + return datetime.utcnow() < cached_data['date'] + timedelta(seconds=expiry_period) |
| 62 | + |
| 63 | + def get(self, key, newer_than_secs=None): |
25 | 64 | """
|
26 | 65 |
|
27 | 66 | :param key: Key for the dataset. eg. list_libraries.
|
28 |
| - :param newer_than_secs: -1 to indicate use cache if available. Used to indicate what level of staleness |
| 67 | + :param newer_than_secs: None to indicate use cache if available. Used to indicate what level of staleness |
29 | 68 | in seconds is tolerable.
|
30 | 69 | :return: None unless if there is non stale data present in the cache.
|
31 | 70 | """
|
32 | 71 | try:
|
33 | 72 | if not self._cachecol:
|
34 | 73 | # Collection not created or no permissions to read from it.
|
35 | 74 | return None
|
36 |
| - coll_data = self._cachecol.find_one({"type": key}) |
| 75 | + cached_data = self._cachecol.find_one({"type": key}) |
37 | 76 | # Check that there is data in cache and it's not stale.
|
38 |
| - if coll_data and ( |
39 |
| - newer_than_secs == -1 or |
40 |
| - datetime.utcnow() < coll_data['date'] + timedelta(seconds=newer_than_secs) |
41 |
| - ): |
42 |
| - return coll_data['data'] |
| 77 | + if cached_data and self._is_not_expired(cached_data, newer_than_secs): |
| 78 | + return cached_data['data'] |
43 | 79 | except OperationFailure as op:
|
44 | 80 | logging.warning("Could not read from cache due to: %s. Ask your admin to give read permissions on %s:%s",
|
45 | 81 | op, CACHE_DB, CACHE_COLL)
|
@@ -83,3 +119,10 @@ def update_item_for_key(self, key, old, new):
|
83 | 119 | # This op is not atomic, but given the rarity of renaming a lib, it should not cause issues.
|
84 | 120 | self.delete_item_from_key(key, old)
|
85 | 121 | self.append(key, new)
|
| 122 | + |
| 123 | + def is_caching_enabled(self): |
| 124 | + # Caching is enabled unless explicitly disabled. |
| 125 | + cache_settings = self._get_cache_settings() |
| 126 | + if cache_settings and not cache_settings['enabled']: |
| 127 | + return False |
| 128 | + return True |
0 commit comments