cache: Refactor out CacheBase from FileCache.

Now we can implement CacheWriter from the same base.
This commit is contained in:
Brett Smith 2017-05-12 10:15:12 -04:00
parent 4cbd890992
commit eba27c16ae
5 changed files with 102 additions and 23 deletions

64
oxrlib/cache.py Normal file
View file

@ -0,0 +1,64 @@
import functools
from . import errors
class CacheFileBase:
def __init__(self, path, *args, **kwargs):
self.path = path
self.open = functools.partial(path.open, *args, **kwargs)
def _translate_error(self, error, when):
for orig_type, mapped_type in self.ERRORS_MAP:
if isinstance(error, orig_type):
raise mapped_type(self.path) from error
raise error
def __enter__(self):
try:
self.open_file = self.open()
except OSError as error:
self._translate_error(error, 'enter')
else:
return self.open_file
def __exit__(self, exc_type, exc_value, exc_tb):
try:
self.open_file.close()
except OSError as error:
self._translate_error(error, 'exit')
class CacheBase:
ConfigurationError = errors.CacheConfigurationError
def __init__(self, dir_path, **kwargs):
self.dir_path = dir_path
self.fn_patterns = {}
self.setup(**kwargs)
def setup(self, **kwargs):
for method_name, pattern in kwargs.items():
try:
is_api_method = getattr(self, method_name).is_api_method
except AttributeError:
is_api_method = False
if not is_api_method:
raise errors.CacheConfigurationError(method_name)
self.fn_patterns[method_name] = pattern
def _wrap_api_method(orig_func):
@functools.wraps(orig_func)
def api_method_wrapper(self, *args, **kwargs):
try:
fn_pattern = self.fn_patterns[orig_func.__name__]
except KeyError:
raise self.ConfigurationError(orig_func.__name__) from None
pattern_kwargs = orig_func(self, *args, **kwargs)
path = self.dir_path / fn_pattern.format(**pattern_kwargs)
return self.open(path)
api_method_wrapper.is_api_method = True
return api_method_wrapper
@_wrap_api_method
def historical(self, date, base):
return {'date': date.isoformat(), 'base': base}

View file

@ -1,5 +1,9 @@
class CacheError(Exception): pass
class CacheConfigurationError(CacheError): pass
class LoaderError(Exception): pass
class LoaderConfigurationError(LoaderError): pass
class LoaderNoDataError(LoaderError): pass
class LoaderBadRequestError(LoaderError): pass
class LoaderSourceError(LoaderError): pass
class CacheLoaderConfigurationError(CacheConfigurationError, LoaderConfigurationError): pass
class NoLoadersError(Exception): pass

View file

@ -4,19 +4,20 @@ import io
import urllib.request
import urllib.parse
from . import errors
from . import cache, errors
class FileCache:
def __init__(self, dir_path, filename_pattern):
self.dir_path = dir_path
self.pattern = filename_pattern
class ReadCacheFile(cache.CacheFileBase):
ERRORS_MAP = [
(FileNotFoundError, errors.LoaderNoDataError),
(OSError, errors.LoaderSourceError),
]
def historical(self, date, base):
path = self.dir_path / self.pattern.format(date=date.isoformat(), base=base)
try:
return path.open()
except FileNotFoundError as error:
raise errors.LoaderNoDataError(path) from error
class FileCache(cache.CacheBase):
ConfigurationError = errors.CacheLoaderConfigurationError
def open(self, path):
return ReadCacheFile(path)
class OXRAPIRequest:

View file

@ -3,35 +3,45 @@ import pathlib
import pytest
from . import relpath
from . import any_date, relpath
import oxrlib.errors
import oxrlib.loaders
CACHE_PATH = relpath('filecache')
CACHE_PATTERN = '{date}_{base}_cache.json'
HISTORICAL_PATTERN = '{date}_{base}_cache.json'
@pytest.fixture
def dummycache():
return oxrlib.loaders.FileCache(CACHE_PATH, CACHE_PATTERN)
cache = oxrlib.loaders.FileCache(CACHE_PATH)
cache.setup(historical=HISTORICAL_PATTERN)
return cache
@pytest.mark.parametrize('date,base', [
(datetime.date(1999, 2, 1), 'USD'),
(datetime.date(1999, 3, 1), 'EUR'),
])
def test_cache_success(dummycache, date, base):
expect_name = CACHE_PATH / CACHE_PATTERN.format(date=date.isoformat(), base=base)
expect_name = CACHE_PATH / HISTORICAL_PATTERN.format(date=date.isoformat(), base=base)
with dummycache.historical(date, base) as cache_file:
assert pathlib.Path(cache_file.name) == expect_name
@pytest.mark.parametrize('date,base', [
(datetime.date(1999, 2, 1), 'EUR'),
(datetime.date(1999, 3, 1), 'USD'),
@pytest.mark.parametrize('date,base,exc_type', [
(datetime.date(1999, 2, 1), 'EUR', oxrlib.errors.LoaderNoDataError),
(datetime.date(1999, 3, 1), 'USD', oxrlib.errors.LoaderNoDataError),
(datetime.date(1200, 12, 31), 'USD', oxrlib.errors.LoaderSourceError),
])
def test_cache_not_found(dummycache, date, base):
def test_cache_read_error(dummycache, date, base, exc_type):
try:
cache_file = dummycache.historical(date, base)
except oxrlib.errors.LoaderNoDataError:
with dummycache.historical(date, base):
assert False, "{e.__name__} not raised".format(e=exc_type)
except exc_type:
pass
def test_cache_unconfigured(any_date):
cache = oxrlib.loaders.FileCache(CACHE_PATH)
try:
cache.historical(any_date, 'USD')
except oxrlib.errors.CacheLoaderConfigurationError:
pass
else:
cache_file.close()
assert False, "cache file found when unexpected"
assert False, "CacheLoaderConfigurationError not raised"