debian-cached-property/cached_property.py

132 lines
3.8 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
__author__ = 'Daniel Greenfeld'
__email__ = 'pydanny@gmail.com'
2016-01-08 13:24:53 +01:00
__version__ = '1.3.0'
__license__ = 'BSD'
from time import time
import threading
class cached_property(object):
2015-05-12 14:07:22 +02:00
"""
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
""" # noqa
2015-04-26 23:17:02 +02:00
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
2015-05-12 14:07:22 +02:00
class threaded_cached_property(object):
"""
A cached_property version for use in environments where multiple threads
might concurrently try to access the property.
"""
2015-04-26 23:17:02 +02:00
def __init__(self, func):
2015-05-12 14:07:22 +02:00
self.__doc__ = getattr(func, '__doc__')
self.func = func
2015-04-26 23:17:02 +02:00
self.lock = threading.RLock()
def __get__(self, obj, cls):
2015-05-12 14:07:22 +02:00
if obj is None:
return self
2015-04-26 23:17:02 +02:00
2015-05-12 14:07:22 +02:00
obj_dict = obj.__dict__
name = self.func.__name__
with self.lock:
try:
# check if the value was computed before the lock was acquired
return obj_dict[name]
except KeyError:
# if not, do the calculation and release the lock
return obj_dict.setdefault(name, self.func(obj))
2015-04-26 23:17:02 +02:00
class cached_property_with_ttl(object):
2015-05-12 14:07:22 +02:00
"""
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.
"""
def __init__(self, ttl=None):
2015-05-12 14:07:22 +02:00
if callable(ttl):
func = ttl
ttl = None
else:
2015-05-12 14:07:22 +02:00
func = None
self.ttl = ttl
self._prepare_func(func)
2015-05-12 14:07:22 +02:00
def __call__(self, func):
self._prepare_func(func)
return self
def __get__(self, obj, cls):
if obj is None:
return self
now = time()
2015-05-12 14:07:22 +02:00
obj_dict = obj.__dict__
name = self.__name__
try:
2015-05-12 14:07:22 +02:00
value, last_updated = obj_dict[name]
except KeyError:
pass
else:
ttl_expired = self.ttl and self.ttl < now - last_updated
if not ttl_expired:
return value
2015-05-12 14:07:22 +02:00
value = self.func(obj)
obj_dict[name] = (value, now)
return value
2015-05-12 14:07:22 +02:00
def __delete__(self, obj):
obj.__dict__.pop(self.__name__, None)
def __set__(self, obj, value):
obj.__dict__[self.__name__] = (value, time())
def _prepare_func(self, func):
self.func = func
if func:
self.__doc__ = func.__doc__
self.__name__ = func.__name__
self.__module__ = func.__module__
2015-04-26 23:17:02 +02:00
# Aliases to make cached_property_with_ttl easier to use
cached_property_ttl = cached_property_with_ttl
timed_cached_property = cached_property_with_ttl
2015-04-26 23:17:02 +02:00
class threaded_cached_property_with_ttl(cached_property_with_ttl):
2015-05-12 14:07:22 +02:00
"""
A cached_property version for use in environments where multiple threads
might concurrently try to access the property.
"""
def __init__(self, ttl=None):
2015-04-26 23:17:02 +02:00
super(threaded_cached_property_with_ttl, self).__init__(ttl)
self.lock = threading.RLock()
def __get__(self, obj, cls):
with self.lock:
2015-05-12 14:07:22 +02:00
return super(threaded_cached_property_with_ttl, self).__get__(obj,
cls)
2015-04-26 23:17:02 +02:00
# 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