122 lines
4.0 KiB
Python
122 lines
4.0 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
__author__ = 'Daniel Greenfeld'
|
|
__email__ = 'pydanny@gmail.com'
|
|
__version__ = '1.1.0'
|
|
__license__ = 'BSD'
|
|
|
|
from time import time
|
|
import threading
|
|
|
|
|
|
class cached_property(object):
|
|
""" A property that is only computed once per instance and then replaces
|
|
itself with an ordinary attribute. Deleting the attribute resets the
|
|
property.
|
|
Source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76
|
|
"""
|
|
|
|
def __init__(self, func):
|
|
self.__doc__ = getattr(func, '__doc__')
|
|
self.func = func
|
|
|
|
def __get__(self, obj, cls):
|
|
if obj is None:
|
|
return self
|
|
value = obj.__dict__[self.func.__name__] = self.func(obj)
|
|
return value
|
|
|
|
|
|
class threaded_cached_property(cached_property):
|
|
""" A cached_property version for use in environments where multiple
|
|
threads might concurrently try to access the property.
|
|
"""
|
|
def __init__(self, func):
|
|
super(threaded_cached_property, self).__init__(func)
|
|
self.lock = threading.RLock()
|
|
|
|
def __get__(self, obj, cls):
|
|
with self.lock:
|
|
# Double check if the value was computed before the lock was
|
|
# acquired.
|
|
prop_name = self.func.__name__
|
|
if prop_name in obj.__dict__:
|
|
return obj.__dict__[prop_name]
|
|
|
|
# If not, do the calculation and release the lock.
|
|
return super(threaded_cached_property, self).__get__(obj, cls)
|
|
|
|
|
|
class cached_property_with_ttl(object):
|
|
""" A property that is only computed once per instance and then replaces
|
|
itself with an ordinary attribute. Setting the ttl to a number expresses
|
|
how long the property will last before being timed out.
|
|
""" # noqa
|
|
|
|
def __init__(self, ttl=None):
|
|
ttl_or_func = ttl
|
|
self.ttl = None
|
|
if callable(ttl_or_func):
|
|
self.prepare_func(ttl_or_func)
|
|
else:
|
|
self.ttl = ttl_or_func
|
|
|
|
def prepare_func(self, func, doc=None):
|
|
'''Prepare to cache object method.'''
|
|
self.func = func
|
|
self.__doc__ = doc or func.__doc__
|
|
self.__name__ = func.__name__
|
|
self.__module__ = func.__module__
|
|
|
|
def __call__(self, func, doc=None):
|
|
self.prepare_func(func, doc)
|
|
return self
|
|
|
|
def __get__(self, obj, cls):
|
|
if obj is None:
|
|
return self
|
|
|
|
now = time()
|
|
try:
|
|
value, last_update = obj._cache[self.__name__]
|
|
if self.ttl and self.ttl > 0 and now - last_update > self.ttl:
|
|
raise AttributeError
|
|
except (KeyError, AttributeError):
|
|
value = self.func(obj)
|
|
try:
|
|
cache = obj._cache
|
|
except AttributeError:
|
|
cache = obj._cache = {}
|
|
cache[self.__name__] = (value, now)
|
|
|
|
return value
|
|
|
|
# Aliases to make cached_property_with_ttl easier to use
|
|
cached_property_ttl = cached_property_with_ttl
|
|
timed_cached_property = cached_property_with_ttl
|
|
|
|
|
|
class threaded_cached_property_with_ttl(cached_property_with_ttl):
|
|
""" A cached_property version for use in environments where multiple
|
|
threads might concurrently try to access the property.
|
|
"""
|
|
def __init__(self, ttl=None):
|
|
super(threaded_cached_property_with_ttl, self).__init__(ttl)
|
|
self.lock = threading.RLock()
|
|
|
|
def __get__(self, obj, cls):
|
|
with self.lock:
|
|
# Double check if the value was computed before the lock was
|
|
# acquired.
|
|
prop_name = self.__name__
|
|
if hasattr(obj, '_cache') and prop_name in obj._cache:
|
|
return obj._cache[prop_name][0]
|
|
|
|
# If not, do the calculation and release the lock.
|
|
return super(threaded_cached_property_with_ttl, self).__get__(obj, cls)
|
|
|
|
# Alias to make threaded_cached_property_with_ttl easier to use
|
|
threaded_cached_property_ttl = threaded_cached_property_with_ttl
|
|
timed_threaded_cached_property = threaded_cached_property_with_ttl
|
|
|