Add the concept of a data model cache, for caching of Namedtuple objects from the data model
Will be used to cache blobs, thus removing the need to hit the database in most blob requests
This commit is contained in:
parent
51e67ab7f5
commit
3c72e9878d
3 changed files with 72 additions and 0 deletions
48
data/cache/__init__.py
vendored
Normal file
48
data/cache/__init__.py
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
from datetime import datetime
|
||||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from six import add_metaclass
|
||||
|
||||
from util.expiresdict import ExpiresDict
|
||||
from util.timedeltastring import convert_to_timedelta
|
||||
|
||||
def is_not_none(value):
|
||||
return value is not None
|
||||
|
||||
|
||||
@add_metaclass(ABCMeta)
|
||||
class DataModelCache(object):
|
||||
""" Defines an interface for cache storing and returning tuple data model objects. """
|
||||
|
||||
@abstractmethod
|
||||
def retrieve(self, cache_key, loader, should_cache=is_not_none):
|
||||
""" Checks the cache for the specified cache key and returns the value found (if any). If none
|
||||
found, the loader is called to get a result and populate the cache.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class NoopDataModelCache(DataModelCache):
|
||||
""" Implementation of the data model cache which does nothing. """
|
||||
|
||||
def retrieve(self, cache_key, loader, should_cache=is_not_none):
|
||||
return loader()
|
||||
|
||||
|
||||
class InMemoryDataModelCache(DataModelCache):
|
||||
""" Implementation of the data model cache backed by an in-memory dictionary. """
|
||||
def __init__(self):
|
||||
self.cache = ExpiresDict(rebuilder=lambda: {})
|
||||
|
||||
def retrieve(self, cache_key, loader, should_cache=is_not_none):
|
||||
not_found = [None]
|
||||
result = self.cache.get(cache_key.key, default_value=not_found)
|
||||
if result != not_found:
|
||||
return result
|
||||
|
||||
result = loader()
|
||||
if should_cache(result):
|
||||
expires = convert_to_timedelta(cache_key.expiration) + datetime.now()
|
||||
self.cache.set(cache_key.key, result, expires=expires)
|
||||
|
||||
return result
|
8
data/cache/cache_key.py
vendored
Normal file
8
data/cache/cache_key.py
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
from collections import namedtuple
|
||||
|
||||
class CacheKey(namedtuple('CacheKey', ['key', 'expiration'])):
|
||||
""" Defines a key into the data model cache. """
|
||||
pass
|
||||
|
||||
def for_repository_blob(namespace_name, repo_name, digest):
|
||||
return CacheKey('repository_blob:%s:%s:%s' % (namespace_name, repo_name, digest), '60s')
|
16
data/cache/test/test_cache.py
vendored
Normal file
16
data/cache/test/test_cache.py
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
import pytest
|
||||
|
||||
from data.cache import InMemoryDataModelCache, NoopDataModelCache
|
||||
from data.cache.cache_key import CacheKey
|
||||
|
||||
@pytest.mark.parametrize('cache_type', [
|
||||
(NoopDataModelCache),
|
||||
(InMemoryDataModelCache),
|
||||
])
|
||||
def test_caching(cache_type):
|
||||
key = CacheKey('foo', '60m')
|
||||
cache = cache_type()
|
||||
|
||||
# Perform two retrievals, and make sure both return.
|
||||
assert cache.retrieve(key, lambda: 1234) == 1234
|
||||
assert cache.retrieve(key, lambda: 1234) == 1234
|
Reference in a new issue