1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
# Copyright 2010-2011 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""
Cached properties for situations where a property is computed once and then
returned each time it is asked for.
See `doc/propertycache.txt` for documentation.
"""
__metaclass__ = type
__all__ = [
'cachedproperty',
'clear_property_cache',
'get_property_cache',
]
from functools import partial
from zope.interface import (
implements,
Interface,
)
from zope.security.proxy import removeSecurityProxy
class IPropertyCache(Interface):
def __getattr__(name):
"""Return the cached value corresponding to `name`.
Raise `AttributeError` if no value is cached.
"""
def __setattr__(name, value):
"""Cache `value` for `name`."""
def __delattr__(name):
"""Delete value for `name`.
If no value is cached for `name` this is a no-op.
"""
def __contains__(name):
"""Whether or not `name` is cached."""
def __iter__():
"""Iterate over the cached names."""
class DefaultPropertyCache:
"""A simple cache."""
implements(IPropertyCache)
# __getattr__ -- well, __getattribute__ -- and __setattr__ are inherited
# from object.
def __delattr__(self, name):
"""See `IPropertyCache`."""
self.__dict__.pop(name, None)
def __contains__(self, name):
"""See `IPropertyCache`."""
return name in self.__dict__
def __iter__(self):
"""See `IPropertyCache`."""
return iter(self.__dict__)
def get_property_cache(target):
"""Obtain a `DefaultPropertyCache` for any object."""
if IPropertyCache.providedBy(target):
return target
else:
naked_target = removeSecurityProxy(target)
try:
return naked_target._property_cache
except AttributeError:
naked_target._property_cache = DefaultPropertyCache()
return naked_target._property_cache
def clear_property_cache(target):
"""Clear the property cache."""
get_property_cache(target).__dict__.clear()
class CachedProperty:
"""Cached property descriptor.
Provides only the `__get__` part of the descriptor protocol. Setting and
clearing cached values should be done explicitly via `IPropertyCache`
instances.
"""
def __init__(self, populate, name):
"""Initialize this instance.
`populate` is a callable responsible for providing the value when this
property has not yet been cached.
`name` is the name under which this property will cache itself.
"""
self.populate = populate
self.name = name
def __get__(self, instance, cls):
if instance is None:
return self
cache = get_property_cache(instance)
try:
return getattr(cache, self.name)
except AttributeError:
value = self.populate(instance)
setattr(cache, self.name, value)
return value
def __set__(self, instance, value):
raise AttributeError(
"%s cannot be set here; instead set explicitly with "
"get_property_cache(object).%s = %r" % (
self.name, self.name, value))
def __delete__(self, instance):
raise AttributeError(
"%s cannot be deleted here; instead delete explicitly "
"with del get_property_cache(object).%s" % (
self.name, self.name))
def cachedproperty(name_or_function):
"""Decorator to create a cached property.
See `doc/propertycache.txt` for usage.
"""
if isinstance(name_or_function, basestring):
name = name_or_function
return partial(CachedProperty, name=name)
else:
name = name_or_function.__name__
populate = name_or_function
return CachedProperty(name=name, populate=populate)
|