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