~launchpad-pqm/launchpad/devel

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
# Copyright 2010 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 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)