aboutsummaryrefslogtreecommitdiff
path: root/cachetools/ttlcache.py
blob: b0dbf3813e16d0ec6fb544648dc40db77118d376 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
from .lrucache import LRUCache
from .decorators import cachedfunc
from .link import Link
from .lock import RLock

import time

_marker = object()


class TTLCache(LRUCache):
    """Cache implementation with per-item time-to-live (TTL) value.

    This class associates a time-to-live value with each item.  Items
    that expire because they have exceeded their time-to-live will be
    removed automatically.  If no expired items are there to remove,
    the least recently used items will be discarded first to make
    space when necessary.

    """

    def __init__(self, maxsize, ttl, timer=time.time, getsizeof=None):
        if getsizeof is None:
            LRUCache.__init__(self, maxsize)
        else:
            LRUCache.__init__(self, maxsize, lambda e: getsizeof(e[0]))
            self.getsizeof = getsizeof
        root = Link()
        root.prev = root.next = root
        self.__root = root
        self.__timer = timer
        self.__ttl = ttl

    def __repr__(self, cache_getitem=LRUCache.__getitem__):
        return '%s(%r, maxsize=%d, currsize=%d)' % (
            self.__class__.__name__,
            [(key, cache_getitem(self, key)[0]) for key in self],
            self.maxsize,
            self.currsize,
        )

    def __getitem__(self, key, cache_getitem=LRUCache.__getitem__):
        value, link = cache_getitem(self, key)
        if link.data[1] < self.__timer():
            raise KeyError('%r has expired' % key)
        return value

    def __setitem__(self, key, value,
                    cache_getitem=LRUCache.__getitem__,
                    cache_setitem=LRUCache.__setitem__,
                    cache_delitem=LRUCache.__delitem__):
        time = self.__timer()
        self.expire(time)
        try:
            _, link = cache_getitem(self, key)
        except KeyError:
            link = Link()
        cache_setitem(self, key, (value, link))
        try:
            link.prev.next = link.next
            link.next.prev = link.prev
        except AttributeError:
            pass
        root = self.__root
        link.data = (key, time + self.__ttl)
        link.prev = tail = root.prev
        link.next = root
        tail.next = root.prev = link

    def __delitem__(self, key,
                    cache_getitem=LRUCache.__getitem__,
                    cache_delitem=LRUCache.__delitem__):
        _, link = cache_getitem(self, key)
        cache_delitem(self, key)
        link.unlink()
        self.expire()

    @property
    def ttl(self):
        """Return the time-to-live of the cache."""
        return self.__ttl

    @property
    def timer(self):
        """Return the timer used by the cache."""
        return self.__timer

    def pop(self, key, default=_marker):
        try:
            value, link = LRUCache.__getitem__(self, key)
        except KeyError:
            if default is _marker:
                raise
            return default
        LRUCache.__delitem__(self, key)
        link.unlink()
        self.expire()
        return value

    def expire(self, time=None, cache_delitem=LRUCache.__delitem__):
        if time is None:
            time = self.__timer()
        root = self.__root
        head = root.next
        while head is not root and head.data[1] < time:
            cache_delitem(self, head.data[0])
            head.next.prev = root
            head = root.next = head.next


def ttl_cache(maxsize=128, ttl=600, timer=time.time, typed=False,
              getsizeof=None, lock=RLock):
    """Decorator to wrap a function with a memoizing callable that saves
    up to `maxsize` results based on a Least Recently Used (LRU)
    algorithm with a per-item time-to-live (TTL) value.
    """
    return cachedfunc(TTLCache(maxsize, ttl, timer, getsizeof), typed, lock())