Browse Source

Teach ESI cache about private responses.

master
Ben Kurtovic 7 years ago
parent
commit
75cd3945de
1 changed files with 46 additions and 22 deletions
  1. +46
    -22
      calefaction/eve/esi.py

+ 46
- 22
calefaction/eve/esi.py View File

@@ -18,18 +18,24 @@ class _ESICache:
EXPIRATION_RATE = 0.2 EXPIRATION_RATE = 0.2


def __init__(self): def __init__(self):
self._index = {}
self._public = {}
self._private = {}
self._lock = Lock() self._lock = Lock()


@staticmethod @staticmethod
def _calculate_expiry(resp):
"""Calculate the expiration date for the given response object."""
def _get_cache_policy(resp):
"""Calculate the expiry and availability of the given response."""
if "Cache-Control" not in resp.headers: if "Cache-Control" not in resp.headers:
return None return None


directives = resp.headers["Cache-Control"].lower().split(";") directives = resp.headers["Cache-Control"].lower().split(";")
directives = [d.strip() for d in directives] directives = [d.strip() for d in directives]
if "public" not in directives:

if "public" in directives:
public = True
elif "private" in directives:
public = False
else:
return None return None


expires = resp.headers.get("Expires") expires = resp.headers.get("Expires")
@@ -37,10 +43,12 @@ class _ESICache:
return None return None


try: try:
return datetime.strptime(expires, "%a, %d %b %Y %H:%M:%S GMT")
expiry = datetime.strptime(expires, "%a, %d %b %Y %H:%M:%S GMT")
except ValueError: except ValueError:
return None return None


return public, expiry

@staticmethod @staticmethod
def freeze_dict(d): def freeze_dict(d):
"""Return a hashable string key for the given dictionary.""" """Return a hashable string key for the given dictionary."""
@@ -51,36 +59,52 @@ class _ESICache:


def _expire(self): def _expire(self):
"""Remove old entries from the cache. Assumes lock is acquired.""" """Remove old entries from the cache. Assumes lock is acquired."""
condemned = []
now = datetime.utcnow() now = datetime.utcnow()
for key, (expiry, _) in self._index.items():
if expiry < now:
condemned.append(key)
for key in condemned:
del self._index[key]

def fetch(self, key):
for index in (self._private, self._public):
condemned = []
for key, (expiry, _) in index.items():
if expiry < now:
condemned.append(key)
for key in condemned:
del index[key]

def fetch(self, key, token):
"""Try to look up a key in the cache. Return None if not found. """Try to look up a key in the cache. Return None if not found.


The key should be a string.
The key should be a string. The token is used if a route is cached
privately.


This will periodically clear the cache of expired entries. This will periodically clear the cache of expired entries.
""" """
with self._lock: with self._lock:
if random.random() < self.EXPIRATION_RATE: if random.random() < self.EXPIRATION_RATE:
self._expire() self._expire()
if key in self._index:
expiry, value = self._index[key]

if (key, token) in self._private:
expiry, value = self._private[key, token]
if expiry > datetime.utcnow(): if expiry > datetime.utcnow():
return value return value

if key in self._public:
expiry, value = self._public[key]
if expiry > datetime.utcnow():
return value

return None return None


def insert(self, key, value, response):
def insert(self, key, token, value, response):
"""Store a key-value pair using the response as cache control.""" """Store a key-value pair using the response as cache control."""
expiry = self._calculate_expiry(response)
if expiry and expiry > datetime.utcnow():
policy = self._get_cache_policy(response)
if not policy:
return

public, expiry = policy
if expiry > datetime.utcnow():
with self._lock: with self._lock:
self._index[key] = (expiry, value)
if public:
self._public[key] = (expiry, value)
else:
self._private[key, token] = (expiry, value)




class _ESIQueryBuilder: class _ESIQueryBuilder:
@@ -150,7 +174,7 @@ class EVESwaggerInterface:
if can_cache: if can_cache:
pkey = self._cache.freeze_dict(params) pkey = self._cache.freeze_dict(params)
key = "|".join((method.__name__, self._data_source, query, pkey)) key = "|".join((method.__name__, self._data_source, query, pkey))
cached = self._cache.fetch(key)
cached = self._cache.fetch(key, token)
else: else:
cached = None cached = None


@@ -177,7 +201,7 @@ class EVESwaggerInterface:
raise EVEAPIError() raise EVEAPIError()


if can_cache and result is not None: if can_cache and result is not None:
self._cache.insert(key, result, resp)
self._cache.insert(key, token, result, resp)
return result return result


def get(self, query, token, params=None): def get(self, query, token, params=None):


Loading…
Cancel
Save