~launchpad-pqm/launchpad/devel

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)