Browse Source

Teach ESI cache about private responses.

master
Ben Kurtovic 4 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

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

@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:
return None

directives = resp.headers["Cache-Control"].lower().split(";")
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

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

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:
return None

return public, expiry

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

def _expire(self):
"""Remove old entries from the cache. Assumes lock is acquired."""
condemned = []
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.

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.
"""
with self._lock:
if random.random() < self.EXPIRATION_RATE:
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():
return value

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

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."""
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:
self._index[key] = (expiry, value)
if public:
self._public[key] = (expiry, value)
else:
self._private[key, token] = (expiry, value)


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

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

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

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


Loading…
Cancel
Save