Changeset - eba27c16ae78
[Not reviewed]
0 3 2
Brett Smith - 7 years ago 2017-05-12 15:58:35
brettcsmith@brettcsmith.org
cache: Refactor out CacheBase from FileCache.

Now we can implement CacheWriter from the same base.
5 files changed with 102 insertions and 23 deletions:
0 comments (0 inline, 0 general)
oxrlib/cache.py
Show inline comments
 
new file 100644
 
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}
oxrlib/errors.py
Show inline comments
 
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
oxrlib/loaders.py
Show inline comments
...
 
@@ -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:
tests/filecache/1200-12-31_USD_cache.json/.empty
Show inline comments
 
new file 100644
tests/test_FileCache.py
Show inline comments
...
 
@@ -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:
 
        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_file = dummycache.historical(date, base)
 
    except oxrlib.errors.LoaderNoDataError:
 
        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"
0 comments (0 inline, 0 general)