add "lrucache" module, extracted from the 3rd-party tkem/cachetools library
functools.lru_cache in the stdlib is not generic enough. That can be used to cache the results of a single pure function, however I have usecases where one function is supposed to populate the cache, while another function consumes it. https://docs.python.org/3/library/functools.html#functools.lru_cache This is stripped down and extracts just the LRUCache from tkem/cachetools. It is relatively short, and very mature code. I don't expect that we have to "follow" upstream, etc. There likely won't be relevant changes upstream. Effectively, we are forking and bundling this code. similar to https://github.com/spesmilo/electrumx/commit/04582cc353b45721e2461176cfce1768003dd5ae
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2014-2022 Thomas Kemmer
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
# this software and associated documentation files (the "Software"), to deal in
|
||||
# the Software without restriction, including without limitation the rights to
|
||||
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
# the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# -----
|
||||
#
|
||||
# This is a stripped down LRU-cache from the "cachetools" library.
|
||||
# https://github.com/tkem/cachetools/blob/d991ac71b4eb6394be5ec572b835434081393215/src/cachetools/__init__.py
|
||||
|
||||
import collections
|
||||
import collections.abc
|
||||
|
||||
|
||||
class _DefaultSize:
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __getitem__(self, _):
|
||||
return 1
|
||||
|
||||
def __setitem__(self, _, value):
|
||||
assert value == 1
|
||||
|
||||
def pop(self, _):
|
||||
return 1
|
||||
|
||||
|
||||
class Cache(collections.abc.MutableMapping):
|
||||
"""Mutable mapping to serve as a simple cache or cache base class."""
|
||||
|
||||
__marker = object()
|
||||
|
||||
__size = _DefaultSize()
|
||||
|
||||
def __init__(self, maxsize, getsizeof=None):
|
||||
if getsizeof:
|
||||
self.getsizeof = getsizeof
|
||||
if self.getsizeof is not Cache.getsizeof:
|
||||
self.__size = dict()
|
||||
self.__data = dict()
|
||||
self.__currsize = 0
|
||||
self.__maxsize = maxsize
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%s, maxsize=%r, currsize=%r)" % (
|
||||
self.__class__.__name__,
|
||||
repr(self.__data),
|
||||
self.__maxsize,
|
||||
self.__currsize,
|
||||
)
|
||||
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
return self.__data[key]
|
||||
except KeyError:
|
||||
return self.__missing__(key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
maxsize = self.__maxsize
|
||||
size = self.getsizeof(value)
|
||||
if size > maxsize:
|
||||
raise ValueError("value too large")
|
||||
if key not in self.__data or self.__size[key] < size:
|
||||
while self.__currsize + size > maxsize:
|
||||
self.popitem()
|
||||
if key in self.__data:
|
||||
diffsize = size - self.__size[key]
|
||||
else:
|
||||
diffsize = size
|
||||
self.__data[key] = value
|
||||
self.__size[key] = size
|
||||
self.__currsize += diffsize
|
||||
|
||||
def __delitem__(self, key):
|
||||
size = self.__size.pop(key)
|
||||
del self.__data[key]
|
||||
self.__currsize -= size
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.__data
|
||||
|
||||
def __missing__(self, key):
|
||||
raise KeyError(key)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.__data)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.__data)
|
||||
|
||||
def get(self, key, default=None):
|
||||
if key in self:
|
||||
return self[key]
|
||||
else:
|
||||
return default
|
||||
|
||||
def pop(self, key, default=__marker):
|
||||
if key in self:
|
||||
value = self[key]
|
||||
del self[key]
|
||||
elif default is self.__marker:
|
||||
raise KeyError(key)
|
||||
else:
|
||||
value = default
|
||||
return value
|
||||
|
||||
def setdefault(self, key, default=None):
|
||||
if key in self:
|
||||
value = self[key]
|
||||
else:
|
||||
self[key] = value = default
|
||||
return value
|
||||
|
||||
@property
|
||||
def maxsize(self):
|
||||
"""The maximum size of the cache."""
|
||||
return self.__maxsize
|
||||
|
||||
@property
|
||||
def currsize(self):
|
||||
"""The current size of the cache."""
|
||||
return self.__currsize
|
||||
|
||||
@staticmethod
|
||||
def getsizeof(value):
|
||||
"""Return the size of a cache element's value."""
|
||||
return 1
|
||||
|
||||
|
||||
class LRUCache(Cache):
|
||||
"""Least Recently Used (LRU) cache implementation."""
|
||||
|
||||
def __init__(self, maxsize, getsizeof=None):
|
||||
Cache.__init__(self, maxsize, getsizeof)
|
||||
self.__order = collections.OrderedDict()
|
||||
|
||||
def __getitem__(self, key, cache_getitem=Cache.__getitem__):
|
||||
value = cache_getitem(self, key)
|
||||
if key in self: # __missing__ may not store item
|
||||
self.__update(key)
|
||||
return value
|
||||
|
||||
def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
|
||||
cache_setitem(self, key, value)
|
||||
self.__update(key)
|
||||
|
||||
def __delitem__(self, key, cache_delitem=Cache.__delitem__):
|
||||
cache_delitem(self, key)
|
||||
del self.__order[key]
|
||||
|
||||
def popitem(self):
|
||||
"""Remove and return the `(key, value)` pair least recently used."""
|
||||
try:
|
||||
key = next(iter(self.__order))
|
||||
except StopIteration:
|
||||
raise KeyError("%s is empty" % type(self).__name__) from None
|
||||
else:
|
||||
return (key, self.pop(key))
|
||||
|
||||
def __update(self, key):
|
||||
try:
|
||||
self.__order.move_to_end(key)
|
||||
except KeyError:
|
||||
self.__order[key] = None
|
||||
Reference in New Issue
Block a user