1
# Copyright (C) 2008 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""A simple first-in-first-out (FIFO) cache."""
19
from collections import deque
22
class FIFOCache(dict):
23
"""A class which manages a cache of entries, removing old ones."""
25
def __init__(self, max_cache=100, after_cleanup_count=None):
27
self._max_cache = max_cache
28
if after_cleanup_count is None:
29
self._after_cleanup_count = self._max_cache * 8 / 10
31
self._after_cleanup_count = min(after_cleanup_count,
33
self._cleanup = {} # map to cleanup functions when items are removed
34
self._queue = deque() # Track when things are accessed
36
def __setitem__(self, key, value):
37
"""Add a value to the cache, there will be no cleanup function."""
38
self.add(key, value, cleanup=None)
40
def __delitem__(self, key):
41
self._queue.remove(key)
44
def add(self, key, value, cleanup=None):
45
"""Add a new value to the cache.
47
Also, if the entry is ever removed from the queue, call cleanup.
48
Passing it the key and value being removed.
50
:param key: The key to store it under
51
:param value: The object to store
52
:param cleanup: None or a function taking (key, value) to indicate
53
'value' should be cleaned up
56
# Remove the earlier reference to this key, adding it again bumps
57
# it to the end of the queue
59
self._queue.append(key)
60
dict.__setitem__(self, key, value)
61
if len(self) > self._max_cache:
63
if cleanup is not None:
64
self._cleanup[key] = cleanup
67
"""Clear the cache until it shrinks to the requested size.
69
This does not completely wipe the cache, just makes sure it is under
70
the after_cleanup_count.
72
# Make sure the cache is shrunk to the correct size
73
while len(self) > self._after_cleanup_count:
77
"""Clear out all of the cache."""
78
# Clean up in FIFO order
82
def _remove(self, key):
83
"""Remove an entry, making sure to call any cleanup function."""
84
cleanup = self._cleanup.pop(key, None)
85
# We override self.pop() because it doesn't play well with cleanup
87
val = dict.pop(self, key)
88
if cleanup is not None:
92
def _remove_oldest(self):
93
"""Remove the oldest entry."""
94
key = self._queue.popleft()
97
# raise NotImplementedError on dict functions that would mutate the cache
98
# which have not been properly implemented yet.
100
raise NotImplementedError(self.copy)
102
def pop(self, key, default=None):
103
# If there is a cleanup() function, than it is unclear what pop()
104
# should do. Specifically, we would have to call the cleanup on the
105
# value before we return it, which should cause whatever resources were
106
# allocated to be removed, which makes the return value fairly useless.
107
# So instead, we just don't implement it.
108
raise NotImplementedError(self.pop)
112
raise NotImplementedError(self.popitem)
114
def setdefault(self):
115
raise NotImplementedError(self.setdefault)
117
def update(self, *args, **kwargs):
118
raise NotImplementedError(self.update)