Source code for aioxmpp.cache
"""
:mod:`~aioxmpp.cache` --- Utilities for implementing caches
###########################################################
.. versionadded:: 0.9
This module was added in version 0.9.
.. autoclass:: LRUDict
"""
import collections.abc
[docs]class LRUDict(collections.abc.MutableMapping):
"""
Size-restricted dictionary with Least Recently Used expiry policy.
.. versionadded:: 0.9
The :class:`LRUDict` supports normal dictionary-style access and implements
:class:`collections.abc.MutableMapping`.
When the :attr:`maxsize` is exceeded, as many entries as needed to get
below the :attr:`maxsize` are removed from the dict. Least recently used
entries are purged first. Setting an entry does *not* count as use!
.. autoattribute:: maxsize
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.__data = {}
self.__data_used = {}
self.__maxsize = 1
self.__ctr = 0
def _purge_old(self, n):
keys_in_age_order = sorted(
self.__data_used.items(),
key=lambda x: x[1]
)
keys_to_delete = keys_in_age_order[:n]
for key, _ in keys_to_delete:
del self.__data[key]
del self.__data_used[key]
keys_to_keep = keys_in_age_order[n:]
# avoid the counter becoming large
self.__ctr = len(keys_to_keep)
for i, (key, _) in enumerate(keys_to_keep):
self.__data_used[key] = i
@property
def maxsize(self):
"""
Maximum size of the cache. Changing this property purges overhanging
entries immediately.
If set to :data:`None`, no limit on the number of entries is imposed.
Do **not** use a limit of :data:`None` for data where the `key` is
under control of a remote entity.
Use cases for :data:`None` are those where you only need the explicit
expiry feature, but not the LRU feature.
"""
return self.__maxsize
@maxsize.setter
[docs] def maxsize(self, value):
if value is not None and value <= 0:
raise ValueError("maxsize must be positive integer or None")
self.__maxsize = value
if self.__maxsize is not None and len(self.__data) > self.__maxsize:
self._purge_old(len(self.__data) - self.__maxsize)
def __len__(self):
return len(self.__data)
def __iter__(self):
return iter(self.__data)
def __setitem__(self, key, value):
if self.__maxsize is not None and len(self.__data) >= self.__maxsize:
self._purge_old(len(self.__data) - (self.__maxsize-1))
self.__data[key] = value
self.__data_used[key] = self.__ctr
def __getitem__(self, key):
result = self.__data[key]
counter = self.__ctr
counter += 1
self.__ctr = counter
self.__data_used[key] = counter
return result
def __delitem__(self, key):
del self.__data[key]
del self.__data_used[key]