Bump for 1.1.0
[cached-property.git] / cached_property.py
1 # -*- coding: utf-8 -*-
2
3 __author__ = 'Daniel Greenfeld'
4 __email__ = 'pydanny@gmail.com'
5 __version__ = '1.1.0'
6 __license__ = 'BSD'
7
8 from time import time
9 import threading
10
11
12 class cached_property(object):
13 """ A property that is only computed once per instance and then replaces
14 itself with an ordinary attribute. Deleting the attribute resets the
15 property.
16 Source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76
17 """
18
19 def __init__(self, func):
20 self.__doc__ = getattr(func, '__doc__')
21 self.func = func
22
23 def __get__(self, obj, cls):
24 if obj is None:
25 return self
26 value = obj.__dict__[self.func.__name__] = self.func(obj)
27 return value
28
29
30 class threaded_cached_property(cached_property):
31 """ A cached_property version for use in environments where multiple
32 threads might concurrently try to access the property.
33 """
34 def __init__(self, func):
35 super(threaded_cached_property, self).__init__(func)
36 self.lock = threading.RLock()
37
38 def __get__(self, obj, cls):
39 with self.lock:
40 # Double check if the value was computed before the lock was
41 # acquired.
42 prop_name = self.func.__name__
43 if prop_name in obj.__dict__:
44 return obj.__dict__[prop_name]
45
46 # If not, do the calculation and release the lock.
47 return super(threaded_cached_property, self).__get__(obj, cls)
48
49
50 class cached_property_with_ttl(object):
51 """ A property that is only computed once per instance and then replaces
52 itself with an ordinary attribute. Setting the ttl to a number expresses
53 how long the property will last before being timed out.
54 """ # noqa
55
56 def __init__(self, ttl=None):
57 ttl_or_func = ttl
58 self.ttl = None
59 if callable(ttl_or_func):
60 self.prepare_func(ttl_or_func)
61 else:
62 self.ttl = ttl_or_func
63
64 def prepare_func(self, func, doc=None):
65 '''Prepare to cache object method.'''
66 self.func = func
67 self.__doc__ = doc or func.__doc__
68 self.__name__ = func.__name__
69 self.__module__ = func.__module__
70
71 def __call__(self, func, doc=None):
72 self.prepare_func(func, doc)
73 return self
74
75 def __get__(self, obj, cls):
76 if obj is None:
77 return self
78
79 now = time()
80 try:
81 value, last_update = obj._cache[self.__name__]
82 if self.ttl and self.ttl > 0 and now - last_update > self.ttl:
83 raise AttributeError
84 except (KeyError, AttributeError):
85 value = self.func(obj)
86 try:
87 cache = obj._cache
88 except AttributeError:
89 cache = obj._cache = {}
90 cache[self.__name__] = (value, now)
91
92 return value
93
94 # Aliases to make cached_property_with_ttl easier to use
95 cached_property_ttl = cached_property_with_ttl
96 timed_cached_property = cached_property_with_ttl
97
98
99 class threaded_cached_property_with_ttl(cached_property_with_ttl):
100 """ A cached_property version for use in environments where multiple
101 threads might concurrently try to access the property.
102 """
103 def __init__(self, ttl=None):
104 super(threaded_cached_property_with_ttl, self).__init__(ttl)
105 self.lock = threading.RLock()
106
107 def __get__(self, obj, cls):
108 with self.lock:
109 # Double check if the value was computed before the lock was
110 # acquired.
111 prop_name = self.__name__
112 if hasattr(obj, '_cache') and prop_name in obj._cache:
113 return obj._cache[prop_name][0]
114
115 # If not, do the calculation and release the lock.
116 return super(threaded_cached_property_with_ttl, self).__get__(obj, cls)
117
118 # Alias to make threaded_cached_property_with_ttl easier to use
119 threaded_cached_property_ttl = threaded_cached_property_with_ttl
120 timed_threaded_cached_property = threaded_cached_property_with_ttl
121