Source code for pyowm.caches.lrucache
"""
Module containing LRU cache related class
"""
from pyowm.abstractions import owmcache
from pyowm.commons import frontlinkedlist
from pyowm.utils import timeutils
[docs]class LRUCache(owmcache.OWMCache):
"""
This cache is made out of a 'table' dict and the 'usage_recency' linked
list.'table' maps uses requests' URLs as keys and stores JSON raw responses
as values. 'usage_recency' tracks down the "recency" of the OWM Weather API
requests: the more recent a request, the more the element will be far from
the "death" point of the recency list. Items in 'usage_recency' are the
requests' URLs themselves.
The implemented LRU caching mechanism is the following:
- cached elements must expire after a certain time passed into the cache.
So when an element is looked up and found in the cache, its insertion
timestamp is compared to the current one: if the difference is higher
than a prefixed value, then the lookup is considered a MISS: the
element is removed either from 'table' and from 'usage_recency' and must
be requested again to the OWM Weather API. If the time difference is ok,
then the lookup is considered a HIT.
- when a GET results in a HIT, promote the element to the front of the
recency list updating its cache insertion timestamp and return the
data to the cache clients
- when a GET results in a MISS, return ``None``
- when a SET is issued, check if the maximum size of the cache has
been reached: if so, discard the least recently used item from the
recency list and the dict; then add the element to 'table' recording its
timestamp and finally add it at the front of the recency list.
:param cache_max_size: the maximum size of the cache in terms of cached
OWM Weather API responses. A reasonable default value is provided.
:type cache_max_size: int
:param item_lifetime_millis: the maximum lifetime allowed for a cache item
in milliseconds. A reasonable default value is provided.
:type item_lifetime_millis: int
:returns: a new *LRUCache* instance
"""
_CACHE_MAX_SIZE = 20 # Maximum number of elements that fit the cache
_ITEM_LIFETIME_MILLISECONDS = 1000 * 60 * 10 # Ten minutes
def __init__(self, cache_max_size=_CACHE_MAX_SIZE,
item_lifetime_millis=_ITEM_LIFETIME_MILLISECONDS):
assert cache_max_size > 0 and item_lifetime_millis > 0, \
"wrong cache init parameters"
self._table = {}
self._usage_recency = frontlinkedlist.FrontLinkedList()
self._max_size = cache_max_size
self._item_lifetime = item_lifetime_millis
[docs] def get(self, request_url):
"""
In case of a hit, returns the JSON string which represents the OWM web
API response to the request being identified by a specific string URL
and updates the recency of this request.
:param request_url: an URL that uniquely identifies the request whose
response is to be looked up
:type request_url: str
:returns: a JSON str in case of cache hit or ``None`` otherwise
"""
try:
cached_item = self._table[request_url]
cur_time = timeutils.now('unix')
if cur_time - cached_item['insertion_time'] > self._item_lifetime:
# Cache item has expired
self._clean_item(request_url)
return None
cached_item['insertion_time'] = cur_time # Update insertion time
self._promote(request_url)
return cached_item['data']
except:
return None
[docs] def set(self, request_url, response_json):
"""
Checks if the maximum size of the cache has been reached and in case
discards the least recently used item from 'usage_recency' and 'table';
then adds the response_json to be cached to the 'table' dict using as
a lookup key the request_url of the request that generated the value;
finally adds it at the front of 'usage_recency'
:param request_url: the request URL that uniquely identifies the
request whose response is to be cached
:type request_url: str
:param response_json: the response JSON to be cached
:type response_json: str
"""
if self.size() == self._max_size:
popped = self._usage_recency.pop()
del self._table[popped]
current_time = timeutils.now('unix')
if request_url not in self._table:
self._table[request_url] = {'data': response_json,
'insertion_time': current_time}
self._usage_recency.add(request_url)
else:
self._table[request_url]['insertion_time'] = current_time
self._promote(request_url)
def _promote(self, request_url):
"""
Moves the cache item specified by request_url to the front of the
'usage_recency' list
"""
self._usage_recency.remove(request_url)
self._usage_recency.add(request_url)
def _clean_item(self, request_url):
"""
Removes the specified item from the cache's datastructures
:param request_url: the request URL
:type request_url: str
"""
del self._table[request_url]
self._usage_recency.remove(request_url)
[docs] def clean(self):
"""
Empties the cache
"""
self._table.clear()
for item in self._usage_recency:
self._usage_recency.remove(item)
[docs] def size(self):
"""
Returns the number of elements that are currently stored into the cache
:returns: an int
"""
return len(self._table)
def __repr__(self):
return "<%s.%s - size=%s, max size=%s, item lifetime=%s>" % \
(__name__, self.__class__.__name__, str(self.size()),
self._max_size, self._item_lifetime)