diff --git a/docs/index.rst b/docs/index.rst index 023c6dd..2b1d0cd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -160,3 +160,29 @@ u'djsdfjklsdf9sd8f908sdf9sd9f8sd9f8sdf8sp9fd89psdf89spdf89spdf89spdf89p' >>> eve.refr_authorize(refresh_token) + +.. highlight:: none + +Prevent PyCrest from using cache +-------------------------------- + +**No cache for everything in PyCrest** +This will disable the cache for everything you will do using PyCrest. No call or response will be stored. + +.. highlight:: python + +>>> pycrest_no_cache = EVE(cache=None) + +.. highlight:: none + +**Disable caching on demand** +You can disable the caching for a specific ``get()`` call you don't want to cache, by simply adding ``caching=False|None`` to the call parameters. +For example: + +.. highlight:: python + +>>> crest_with_caching = EVE() +>>> crest_root_not_cached = crest_with_caching(caching=False) +>>> regions = crest_root.regions(caching=False) + +.. highlight:: none \ No newline at end of file diff --git a/pycrest/eve.py b/pycrest/eve.py index be0f712..ff28ac7 100644 --- a/pycrest/eve.py +++ b/pycrest/eve.py @@ -108,6 +108,22 @@ def put(self, key, value): def invalidate(self, key): self._dict.pop(key, None) + +class DummyCache(APICache): + """ Provide a fake cache class to allow a "no cache" + use without breaking everything within APIConnection """ + def __init__(self): + self._dict = {} + + def get(self, key): + return None + + def put(self, key, value): + pass + + def invalidate(self, key): + pass + class MemcachedCache(APICache): @@ -134,7 +150,7 @@ def __init__( additional_headers=None, user_agent=None, cache_dir=None, - cache=None): + cache=DictCache): # Set up a Requests Session session = requests.Session() if additional_headers is None: @@ -148,16 +164,16 @@ def __init__( }) session.headers.update(additional_headers) self._session = session - if cache: + if cache_dir: + self.cache_dir = cache_dir + self.cache = FileCache(self.cache_dir) + elif cache: if isinstance(cache, APICache): self.cache = cache # Inherit from parents elif isinstance(cache, type): self.cache = cache() # Instantiate a new cache - elif cache_dir: - self.cache_dir = cache_dir - self.cache = FileCache(self.cache_dir) else: - self.cache = DictCache() + self.cache = DummyCache() def _parse_parameters(self, resource, params): '''Creates a dictionary from query_string and `params` @@ -179,10 +195,10 @@ def _parse_parameters(self, resource, params): prms[key] = params[key] return resource, prms - def get(self, resource, params={}): + def get(self, resource, params={}, caching=True): logger.debug('Getting resource %s', resource) resource, prms = self._parse_parameters(resource, params) - + # check cache key = ( resource, frozenset( @@ -218,13 +234,14 @@ def get(self, resource, params={}): ret = res.json() - # cache result + # cache result only if caching = True (default) key = ( resource, frozenset( self._session.headers.items()), frozenset( prms.items())) + expires = self._get_expires(res) - if expires > 0: + if expires > 0 and caching: self.cache.put( key, { 'expires': time.time() + expires, 'payload': ret}) @@ -300,9 +317,9 @@ def __init__(self, **kwargs): self._data = None APIConnection.__init__(self, **kwargs) - def __call__(self): + def __call__(self, caching=True): if not self._data: - self._data = APIObject(self.get(self._endpoint), self) + self._data = APIObject(self.get(self._endpoint, caching=caching), self) return self._data def __getattr__(self, item): @@ -397,9 +414,9 @@ def __init__( self._session.headers.update( {"Authorization": "Bearer %s" % self.token}) - def __call__(self): + def __call__(self, caching=True): if not self._data: - self._data = APIObject(self.get(self._endpoint), self) + self._data = APIObject(self.get(self._endpoint, caching=caching), self) return self._data def whoami(self): @@ -420,10 +437,10 @@ def refresh(self): {"Authorization": "Bearer %s" % self.token}) return self # for backwards compatibility - def get(self, resource, params={}): + def get(self, resource, params={}, caching=True): if int(time.time()) >= self.expires: self.refresh() - return super(self.__class__, self).get(resource, params) + return super(self.__class__, self).get(resource, params, caching) class APIObject(object): @@ -477,6 +494,7 @@ def __call__(self, **kwargs): if 'href' in self._dict: method = kwargs.pop('method', 'get')#default to get: historic behaviour data = kwargs.pop('data', {}) + caching = kwargs.pop('caching', True) # default caching to true, for get requests #retain compatibility with historic method of passing parameters. #Slightly unsatisfactory - what if data is dict-like but not a dict? @@ -491,7 +509,7 @@ def __call__(self, **kwargs): elif method == 'delete': return APIObject(self.connection.delete(self._dict['href'] ), self.connection) elif method == 'get': - return APIObject(self.connection.get(self._dict['href'], params=data), self.connection) + return APIObject(self.connection.get(self._dict['href'], params=data, caching=caching), self.connection) else: raise UnsupportedHTTPMethodException(method) else: diff --git a/tests/test_pycrest.py b/tests/test_pycrest.py index 4e571e5..cd2e22a 100644 --- a/tests/test_pycrest.py +++ b/tests/test_pycrest.py @@ -4,7 +4,7 @@ @author: henk ''' import sys -from pycrest.eve import EVE, DictCache, APICache, FileCache, APIObject,\ +from pycrest.eve import EVE, DictCache, DummyCache, APICache, FileCache, APIObject,\ MemcachedCache import httmock import pycrest @@ -263,6 +263,10 @@ def check_custom_headers(url, request): def test_default_cache(self): self.assertTrue(isinstance(self.api.cache, DictCache)) + def test_no_cache(self): + eve = EVE(cache=None) + self.assertTrue(isinstance(eve.cache, DummyCache)) + def test_callable_cache(self): class CustomCache(object): pass @@ -319,7 +323,6 @@ def key_mock(url, request): dict(key='value2')) def test_cache_hit(self): - @httmock.all_requests def prime_cache(url, request): headers = {'content-type': 'application/json', @@ -337,6 +340,38 @@ def cached_request(url, request): with httmock.HTTMock(cached_request): self.api._data = None self.assertEqual(self.api()._dict, {}) + + def test_caching_arg_hit(self): + """ Test the caching argument for ApiConnection and ApiObject __call__() """ + + @httmock.urlmatch( + scheme="https", + netloc=r"(api-sisi\.test)?(crest-tq\.)?eveonline\.com$", + path=r"^/market/prices/?$") + def market_prices_cached_mock(url, request): + headers = {'content-type': 'application/json', + 'Cache-Control': 'max-age=300;'} + return httmock.response( + status_code=200, + headers=headers, + content='{}'.encode('utf-8')) + + with httmock.HTTMock(root_mock, market_prices_cached_mock): + self.assertEqual(self.api.cache._dict, {}) + + self.api(caching=False) + self.assertEqual(self.api.cache._dict, {}) + + self.api._data = None + self.api() + self.assertEqual(len(self.api.cache._dict), 1) + + self.assertEqual(self.api().marketData(caching=False)._dict, {}) + self.assertEqual(len(self.api.cache._dict), 1) + + self.assertEqual(self.api().marketData()._dict, {}) + self.assertEqual(len(self.api.cache._dict), 2) + def test_cache_invalidate(self): @httmock.all_requests @@ -447,6 +482,22 @@ def test_invalidate(self): def test_cache_dir(self): pass + +class TestDummyCache(unittest.TestCase): + + def setUp(self): + self.c = DummyCache() + self.c.put('never_stored', True) + + def test_put(self): + self.assertNotIn('never_stored', self.c._dict) + + def test_get(self): + self.assertEqual(self.c.get('never_stored'), None) + + def test_invalidate(self): + self.c.invalidate('never_stored') + self.assertIsNone(self.c.get('never_stored')) class TestFileCache(unittest.TestCase):