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