Switch back to CC token
[cached-property.git] / cached_property.py
1 # -*- coding: utf-8 -*-
2
3 __author__ = "Daniel Greenfeld"
4 __email__ = "pydanny@gmail.com"
5 __version__ = "1.5.1"
6 __license__ = "BSD"
7
8 from time import time
9 import threading
10
11 try:
12 import asyncio
13 except (ImportError, SyntaxError):
14 asyncio = None
15
16
17 class cached_property(object):
18 """
19 A property that is only computed once per instance and then replaces itself
20 with an ordinary attribute. Deleting the attribute resets the property.
21 Source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76
22 """ # noqa
23
24 def __init__(self, func):
25 self.__doc__ = getattr(func, "__doc__")
26 self.func = func
27
28 def __get__(self, obj, cls):
29 if obj is None:
30 return self
31
32 if asyncio and asyncio.iscoroutinefunction(self.func):
33 return self._wrap_in_coroutine(obj)
34
35 value = obj.__dict__[self.func.__name__] = self.func(obj)
36 return value
37
38 def _wrap_in_coroutine(self, obj):
39 @asyncio.coroutine
40 def wrapper():
41 future = asyncio.ensure_future(self.func(obj))
42 obj.__dict__[self.func.__name__] = future
43 return future
44
45 return wrapper()
46
47
48 class threaded_cached_property(object):
49 """
50 A cached_property version for use in environments where multiple threads
51 might concurrently try to access the property.
52 """
53
54 def __init__(self, func):
55 self.__doc__ = getattr(func, "__doc__")
56 self.func = func
57 self.lock = threading.RLock()
58
59 def __get__(self, obj, cls):
60 if obj is None:
61 return self
62
63 obj_dict = obj.__dict__
64 name = self.func.__name__
65 with self.lock:
66 try:
67 # check if the value was computed before the lock was acquired
68 return obj_dict[name]
69
70 except KeyError:
71 # if not, do the calculation and release the lock
72 return obj_dict.setdefault(name, self.func(obj))
73
74
75 class cached_property_with_ttl(object):
76 """
77 A property that is only computed once per instance and then replaces itself
78 with an ordinary attribute. Setting the ttl to a number expresses how long
79 the property will last before being timed out.
80 """
81
82 def __init__(self, ttl=None):
83 if callable(ttl):
84 func = ttl
85 ttl = None
86 else:
87 func = None
88 self.ttl = ttl
89 self._prepare_func(func)
90
91 def __call__(self, func):
92 self._prepare_func(func)
93 return self
94
95 def __get__(self, obj, cls):
96 if obj is None:
97 return self
98
99 now = time()
100 obj_dict = obj.__dict__
101 name = self.__name__
102 try:
103 value, last_updated = obj_dict[name]
104 except KeyError:
105 pass
106 else:
107 ttl_expired = self.ttl and self.ttl < now - last_updated
108 if not ttl_expired:
109 return value
110
111 value = self.func(obj)
112 obj_dict[name] = (value, now)
113 return value
114
115 def __delete__(self, obj):
116 obj.__dict__.pop(self.__name__, None)
117
118 def __set__(self, obj, value):
119 obj.__dict__[self.__name__] = (value, time())
120
121 def _prepare_func(self, func):
122 self.func = func
123 if func:
124 self.__doc__ = func.__doc__
125 self.__name__ = func.__name__
126 self.__module__ = func.__module__
127
128
129 # Aliases to make cached_property_with_ttl easier to use
130 cached_property_ttl = cached_property_with_ttl
131 timed_cached_property = cached_property_with_ttl
132
133
134 class threaded_cached_property_with_ttl(cached_property_with_ttl):
135 """
136 A cached_property version for use in environments where multiple threads
137 might concurrently try to access the property.
138 """
139
140 def __init__(self, ttl=None):
141 super(threaded_cached_property_with_ttl, self).__init__(ttl)
142 self.lock = threading.RLock()
143
144 def __get__(self, obj, cls):
145 with self.lock:
146 return super(threaded_cached_property_with_ttl, self).__get__(obj, cls)
147
148
149 # Alias to make threaded_cached_property_with_ttl easier to use
150 threaded_cached_property_ttl = threaded_cached_property_with_ttl
151 timed_threaded_cached_property = threaded_cached_property_with_ttl