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