Merge lp:~lifeless/launchpad/cachedproperty into lp:launchpad
- cachedproperty
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Michael Hudson-Doyle |
Approved revision: | no longer in the source branch. |
Merged at revision: | 11366 |
Proposed branch: | lp:~lifeless/launchpad/cachedproperty |
Merge into: | lp:launchpad |
Diff against target: |
419 lines (+179/-20) 9 files modified
lib/canonical/cachedproperty.py (+145/-4) lib/canonical/database/sqlbase.py (+12/-0) lib/canonical/launchpad/database/librarian.py (+1/-0) lib/lp/registry/doc/teammembership.txt (+2/-1) lib/lp/registry/model/distribution.py (+4/-3) lib/lp/registry/model/person.py (+10/-10) lib/lp/registry/model/product.py (+1/-2) lib/lp/translations/model/potemplate.py (+1/-0) lib/lp/translations/model/potmsgset.py (+3/-0) |
To merge this branch: | bzr merge lp:~lifeless/launchpad/cachedproperty |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Hudson-Doyle | Approve | ||
Review via email: mp+32728@code.launchpad.net |
Commit message
Automatically clear cachedproperties on SQLBase derived classes when storm invalidates an object.
Description of the change
Make invalidation of cached properties on SQLBase derived classes automatic. This addresses many of the risks associated with using them in the test suite (the server already discards all such objects between transactions), leaving just read-write-readback cases to handle as and when they occur.
Jeroen T. Vermeulen (jtv) wrote : | # |
Is there or will there be a similar mechanism for pure Storm classes? Otherwise this adds a nasty pitfall to Storm migration.
Robert Collins (lifeless) wrote : | # |
On Mon, Aug 16, 2010 at 3:51 PM, Jeroen T. Vermeulen <email address hidden> wrote:
> Is there or will there be a similar mechanism for pure Storm classes? Otherwise this adds a nasty pitfall to Storm migration.
Its a two liner:
def __storm_
clear_
SQLBase is storm to all intents and purposes these days.
Preview Diff
1 | === modified file 'lib/canonical/cachedproperty.py' | |||
2 | --- lib/canonical/cachedproperty.py 2010-01-11 21:29:33 +0000 | |||
3 | +++ lib/canonical/cachedproperty.py 2010-08-17 01:31:02 +0000 | |||
4 | @@ -3,10 +3,24 @@ | |||
5 | 3 | 3 | ||
6 | 4 | """Cached properties for situations where a property is computed once and | 4 | """Cached properties for situations where a property is computed once and |
7 | 5 | then returned each time it is asked for. | 5 | then returned each time it is asked for. |
8 | 6 | |||
9 | 7 | The clear_cachedproperties function can be used to wipe the cache of properties | ||
10 | 8 | from an instance. | ||
11 | 6 | """ | 9 | """ |
12 | 7 | 10 | ||
13 | 8 | __metaclass__ = type | 11 | __metaclass__ = type |
14 | 9 | 12 | ||
15 | 13 | __all__ = [ | ||
16 | 14 | 'cache_property', | ||
17 | 15 | 'cachedproperty', | ||
18 | 16 | 'clear_cachedproperties', | ||
19 | 17 | 'clear_property', | ||
20 | 18 | ] | ||
21 | 19 | |||
22 | 20 | from zope.security.proxy import removeSecurityProxy | ||
23 | 21 | |||
24 | 22 | from canonical.lazr.utils import safe_hasattr | ||
25 | 23 | |||
26 | 10 | # XXX: JonathanLange 2010-01-11 bug=505731: Move this to lp.services. | 24 | # XXX: JonathanLange 2010-01-11 bug=505731: Move this to lp.services. |
27 | 11 | 25 | ||
28 | 12 | def cachedproperty(attrname_or_fn): | 26 | def cachedproperty(attrname_or_fn): |
29 | @@ -17,6 +31,10 @@ | |||
30 | 17 | 31 | ||
31 | 18 | If you don't provide a name, the mangled name of the property is used. | 32 | If you don't provide a name, the mangled name of the property is used. |
32 | 19 | 33 | ||
33 | 34 | cachedproperty is not threadsafe - it should not be used on objects which | ||
34 | 35 | are shared across threads / external locking should be used on those | ||
35 | 36 | objects. | ||
36 | 37 | |||
37 | 20 | >>> class CachedPropertyTest(object): | 38 | >>> class CachedPropertyTest(object): |
38 | 21 | ... | 39 | ... |
39 | 22 | ... @cachedproperty('_foo_cache') | 40 | ... @cachedproperty('_foo_cache') |
40 | @@ -44,6 +62,10 @@ | |||
41 | 44 | 69 | 62 | 69 |
42 | 45 | >>> cpt._bar_cached_value | 63 | >>> cpt._bar_cached_value |
43 | 46 | 69 | 64 | 69 |
44 | 65 | |||
45 | 66 | Cached properties are listed on instances. | ||
46 | 67 | >>> sorted(cpt._cached_properties) | ||
47 | 68 | ['_bar_cached_value', '_foo_cache'] | ||
48 | 47 | 69 | ||
49 | 48 | """ | 70 | """ |
50 | 49 | if isinstance(attrname_or_fn, basestring): | 71 | if isinstance(attrname_or_fn, basestring): |
51 | @@ -54,8 +76,125 @@ | |||
52 | 54 | attrname = '_%s_cached_value' % fn.__name__ | 76 | attrname = '_%s_cached_value' % fn.__name__ |
53 | 55 | return CachedProperty(attrname, fn) | 77 | return CachedProperty(attrname, fn) |
54 | 56 | 78 | ||
55 | 79 | def cache_property(instance, attrname, value): | ||
56 | 80 | """Cache value on instance as attrname. | ||
57 | 81 | |||
58 | 82 | instance._cached_properties is updated with attrname. | ||
59 | 83 | |||
60 | 84 | >>> class CachedPropertyTest(object): | ||
61 | 85 | ... | ||
62 | 86 | ... @cachedproperty('_foo_cache') | ||
63 | 87 | ... def foo(self): | ||
64 | 88 | ... return 23 | ||
65 | 89 | ... | ||
66 | 90 | >>> instance = CachedPropertyTest() | ||
67 | 91 | >>> cache_property(instance, '_foo_cache', 42) | ||
68 | 92 | >>> instance.foo | ||
69 | 93 | 42 | ||
70 | 94 | >>> instance._cached_properties | ||
71 | 95 | ['_foo_cache'] | ||
72 | 96 | |||
73 | 97 | Caching a new value does not duplicate the cache keys. | ||
74 | 98 | |||
75 | 99 | >>> cache_property(instance, '_foo_cache', 84) | ||
76 | 100 | >>> instance._cached_properties | ||
77 | 101 | ['_foo_cache'] | ||
78 | 102 | |||
79 | 103 | And does update the cached value. | ||
80 | 104 | |||
81 | 105 | >>> instance.foo | ||
82 | 106 | 84 | ||
83 | 107 | """ | ||
84 | 108 | naked_instance = removeSecurityProxy(instance) | ||
85 | 109 | clear_property(naked_instance, attrname) | ||
86 | 110 | setattr(naked_instance, attrname, value) | ||
87 | 111 | cached_properties = getattr(naked_instance, '_cached_properties', []) | ||
88 | 112 | cached_properties.append(attrname) | ||
89 | 113 | naked_instance._cached_properties = cached_properties | ||
90 | 114 | |||
91 | 115 | |||
92 | 116 | def clear_property(instance, attrname): | ||
93 | 117 | """Remove a cached attribute from instance. | ||
94 | 118 | |||
95 | 119 | The attribute name is removed from instance._cached_properties. | ||
96 | 120 | |||
97 | 121 | If the property is not cached, nothing happens. | ||
98 | 122 | |||
99 | 123 | :seealso clear_cachedproperties: For clearing all cached items at once. | ||
100 | 124 | |||
101 | 125 | >>> class CachedPropertyTest(object): | ||
102 | 126 | ... | ||
103 | 127 | ... @cachedproperty('_foo_cache') | ||
104 | 128 | ... def foo(self): | ||
105 | 129 | ... return 23 | ||
106 | 130 | ... | ||
107 | 131 | >>> instance = CachedPropertyTest() | ||
108 | 132 | >>> instance.foo | ||
109 | 133 | 23 | ||
110 | 134 | >>> clear_property(instance, '_foo_cache') | ||
111 | 135 | >>> instance._cached_properties | ||
112 | 136 | [] | ||
113 | 137 | >>> is_cached(instance, '_foo_cache') | ||
114 | 138 | False | ||
115 | 139 | >>> clear_property(instance, '_foo_cache') | ||
116 | 140 | """ | ||
117 | 141 | naked_instance = removeSecurityProxy(instance) | ||
118 | 142 | if not is_cached(naked_instance, attrname): | ||
119 | 143 | return | ||
120 | 144 | delattr(naked_instance, attrname) | ||
121 | 145 | naked_instance._cached_properties.remove(attrname) | ||
122 | 146 | |||
123 | 147 | |||
124 | 148 | def clear_cachedproperties(instance): | ||
125 | 149 | """Clear cached properties from an object. | ||
126 | 150 | |||
127 | 151 | >>> class CachedPropertyTest(object): | ||
128 | 152 | ... | ||
129 | 153 | ... @cachedproperty('_foo_cache') | ||
130 | 154 | ... def foo(self): | ||
131 | 155 | ... return 23 | ||
132 | 156 | ... | ||
133 | 157 | >>> instance = CachedPropertyTest() | ||
134 | 158 | >>> instance.foo | ||
135 | 159 | 23 | ||
136 | 160 | >>> instance._cached_properties | ||
137 | 161 | ['_foo_cache'] | ||
138 | 162 | >>> clear_cachedproperties(instance) | ||
139 | 163 | >>> instance._cached_properties | ||
140 | 164 | [] | ||
141 | 165 | >>> hasattr(instance, '_foo_cache') | ||
142 | 166 | False | ||
143 | 167 | """ | ||
144 | 168 | naked_instance = removeSecurityProxy(instance) | ||
145 | 169 | cached_properties = getattr(naked_instance, '_cached_properties', []) | ||
146 | 170 | for property_name in cached_properties: | ||
147 | 171 | delattr(naked_instance, property_name) | ||
148 | 172 | naked_instance._cached_properties = [] | ||
149 | 173 | |||
150 | 174 | |||
151 | 175 | def is_cached(instance, attrname): | ||
152 | 176 | """Return True if attrname is cached on instance. | ||
153 | 177 | |||
154 | 178 | >>> class CachedPropertyTest(object): | ||
155 | 179 | ... | ||
156 | 180 | ... @cachedproperty('_foo_cache') | ||
157 | 181 | ... def foo(self): | ||
158 | 182 | ... return 23 | ||
159 | 183 | ... | ||
160 | 184 | >>> instance = CachedPropertyTest() | ||
161 | 185 | >>> instance.foo | ||
162 | 186 | 23 | ||
163 | 187 | >>> is_cached(instance, '_foo_cache') | ||
164 | 188 | True | ||
165 | 189 | >>> is_cached(instance, '_var_cache') | ||
166 | 190 | False | ||
167 | 191 | """ | ||
168 | 192 | naked_instance = removeSecurityProxy(instance) | ||
169 | 193 | return safe_hasattr(naked_instance, attrname) | ||
170 | 194 | |||
171 | 57 | 195 | ||
172 | 58 | class CachedPropertyForAttr: | 196 | class CachedPropertyForAttr: |
173 | 197 | """Curry a decorator to provide arguments to the CachedProperty.""" | ||
174 | 59 | 198 | ||
175 | 60 | def __init__(self, attrname): | 199 | def __init__(self, attrname): |
176 | 61 | self.attrname = attrname | 200 | self.attrname = attrname |
177 | @@ -66,18 +205,20 @@ | |||
178 | 66 | 205 | ||
179 | 67 | class CachedProperty: | 206 | class CachedProperty: |
180 | 68 | 207 | ||
181 | 208 | # Used to detect not-yet-cached properties. | ||
182 | 209 | sentinel = object() | ||
183 | 210 | |||
184 | 69 | def __init__(self, attrname, fn): | 211 | def __init__(self, attrname, fn): |
185 | 70 | self.fn = fn | 212 | self.fn = fn |
186 | 71 | self.attrname = attrname | 213 | self.attrname = attrname |
187 | 72 | self.marker = object() | ||
188 | 73 | 214 | ||
189 | 74 | def __get__(self, inst, cls=None): | 215 | def __get__(self, inst, cls=None): |
190 | 75 | if inst is None: | 216 | if inst is None: |
191 | 76 | return self | 217 | return self |
194 | 77 | cachedresult = getattr(inst, self.attrname, self.marker) | 218 | cachedresult = getattr(inst, self.attrname, CachedProperty.sentinel) |
195 | 78 | if cachedresult is self.marker: | 219 | if cachedresult is CachedProperty.sentinel: |
196 | 79 | result = self.fn(inst) | 220 | result = self.fn(inst) |
198 | 80 | setattr(inst, self.attrname, result) | 221 | cache_property(inst, self.attrname, result) |
199 | 81 | return result | 222 | return result |
200 | 82 | else: | 223 | else: |
201 | 83 | return cachedresult | 224 | return cachedresult |
202 | 84 | 225 | ||
203 | === modified file 'lib/canonical/database/sqlbase.py' | |||
204 | --- lib/canonical/database/sqlbase.py 2010-05-21 13:08:18 +0000 | |||
205 | +++ lib/canonical/database/sqlbase.py 2010-08-17 01:31:02 +0000 | |||
206 | @@ -30,6 +30,7 @@ | |||
207 | 30 | 30 | ||
208 | 31 | from lazr.restful.interfaces import IRepresentationCache | 31 | from lazr.restful.interfaces import IRepresentationCache |
209 | 32 | 32 | ||
210 | 33 | from canonical.cachedproperty import clear_cachedproperties | ||
211 | 33 | from canonical.config import config, dbconfig | 34 | from canonical.config import config, dbconfig |
212 | 34 | from canonical.database.interfaces import ISQLBase | 35 | from canonical.database.interfaces import ISQLBase |
213 | 35 | 36 | ||
214 | @@ -175,6 +176,9 @@ | |||
215 | 175 | correct master Store. | 176 | correct master Store. |
216 | 176 | """ | 177 | """ |
217 | 177 | from canonical.launchpad.interfaces import IMasterStore | 178 | from canonical.launchpad.interfaces import IMasterStore |
218 | 179 | # Make it simple to write dumb-invalidators - initialised | ||
219 | 180 | # _cached_properties to a valid list rather than just-in-time creation. | ||
220 | 181 | self._cached_properties = [] | ||
221 | 178 | store = IMasterStore(self.__class__) | 182 | store = IMasterStore(self.__class__) |
222 | 179 | 183 | ||
223 | 180 | # The constructor will fail if objects from a different Store | 184 | # The constructor will fail if objects from a different Store |
224 | @@ -253,6 +257,14 @@ | |||
225 | 253 | cache = getUtility(IRepresentationCache) | 257 | cache = getUtility(IRepresentationCache) |
226 | 254 | cache.delete(self) | 258 | cache.delete(self) |
227 | 255 | 259 | ||
228 | 260 | def __storm_invalidated__(self): | ||
229 | 261 | """Flush cached properties.""" | ||
230 | 262 | # Note this is not directly tested; but the entire test suite blows up | ||
231 | 263 | # awesomely if its broken : its entirely unclear where tests for this | ||
232 | 264 | # should be -- RBC 20100816. | ||
233 | 265 | clear_cachedproperties(self) | ||
234 | 266 | |||
235 | 267 | |||
236 | 256 | alreadyInstalledMsg = ("A ZopelessTransactionManager with these settings is " | 268 | alreadyInstalledMsg = ("A ZopelessTransactionManager with these settings is " |
237 | 257 | "already installed. This is probably caused by calling initZopeless twice.") | 269 | "already installed. This is probably caused by calling initZopeless twice.") |
238 | 258 | 270 | ||
239 | 259 | 271 | ||
240 | === modified file 'lib/canonical/launchpad/database/librarian.py' | |||
241 | --- lib/canonical/launchpad/database/librarian.py 2010-07-06 16:12:46 +0000 | |||
242 | +++ lib/canonical/launchpad/database/librarian.py 2010-08-17 01:31:02 +0000 | |||
243 | @@ -194,6 +194,7 @@ | |||
244 | 194 | 194 | ||
245 | 195 | def __storm_invalidated__(self): | 195 | def __storm_invalidated__(self): |
246 | 196 | """Make sure that the file is closed across transaction boundary.""" | 196 | """Make sure that the file is closed across transaction boundary.""" |
247 | 197 | super(LibraryFileAlias, self).__storm_invalidated__() | ||
248 | 197 | self.close() | 198 | self.close() |
249 | 198 | 199 | ||
250 | 199 | 200 | ||
251 | 200 | 201 | ||
252 | === modified file 'lib/lp/registry/doc/teammembership.txt' | |||
253 | --- lib/lp/registry/doc/teammembership.txt 2010-07-14 15:28:42 +0000 | |||
254 | +++ lib/lp/registry/doc/teammembership.txt 2010-08-17 01:31:02 +0000 | |||
255 | @@ -1007,7 +1007,8 @@ | |||
256 | 1007 | >>> from canonical.launchpad.interfaces import IMasterObject | 1007 | >>> from canonical.launchpad.interfaces import IMasterObject |
257 | 1008 | >>> IMasterObject(bad_user.account).status = AccountStatus.SUSPENDED | 1008 | >>> IMasterObject(bad_user.account).status = AccountStatus.SUSPENDED |
258 | 1009 | >>> IMasterObject(bad_user.preferredemail).status = EmailAddressStatus.OLD | 1009 | >>> IMasterObject(bad_user.preferredemail).status = EmailAddressStatus.OLD |
260 | 1010 | >>> removeSecurityProxy(bad_user)._preferredemail_cached = None | 1010 | >>> from canonical.cachedproperty import clear_property |
261 | 1011 | >>> clear_property(removeSecurityProxy(bad_user), '_preferredemail_cached') | ||
262 | 1011 | >>> transaction.commit() | 1012 | >>> transaction.commit() |
263 | 1012 | 1013 | ||
264 | 1013 | >>> [m.displayname for m in t3.allmembers] | 1014 | >>> [m.displayname for m in t3.allmembers] |
265 | 1014 | 1015 | ||
266 | === modified file 'lib/lp/registry/model/distribution.py' | |||
267 | --- lib/lp/registry/model/distribution.py 2010-08-11 02:17:14 +0000 | |||
268 | +++ lib/lp/registry/model/distribution.py 2010-08-17 01:31:02 +0000 | |||
269 | @@ -19,7 +19,7 @@ | |||
270 | 19 | from zope.interface import alsoProvides, implements | 19 | from zope.interface import alsoProvides, implements |
271 | 20 | 20 | ||
272 | 21 | from lp.archivepublisher.debversion import Version | 21 | from lp.archivepublisher.debversion import Version |
274 | 22 | from canonical.cachedproperty import cachedproperty | 22 | from canonical.cachedproperty import cachedproperty, clear_property |
275 | 23 | from canonical.database.constants import UTC_NOW | 23 | from canonical.database.constants import UTC_NOW |
276 | 24 | 24 | ||
277 | 25 | from canonical.database.datetimecol import UtcDateTimeCol | 25 | from canonical.database.datetimecol import UtcDateTimeCol |
278 | @@ -1617,8 +1617,9 @@ | |||
279 | 1617 | # This driver is a release manager. | 1617 | # This driver is a release manager. |
280 | 1618 | series.driver = owner | 1618 | series.driver = owner |
281 | 1619 | 1619 | ||
284 | 1620 | if safe_hasattr(self, '_cached_series'): | 1620 | # May wish to add this to the series rather than clearing the cache -- |
285 | 1621 | del self._cached_series | 1621 | # RBC 20100816. |
286 | 1622 | clear_property(self, '_cached_series') | ||
287 | 1622 | return series | 1623 | return series |
288 | 1623 | 1624 | ||
289 | 1624 | @property | 1625 | @property |
290 | 1625 | 1626 | ||
291 | === modified file 'lib/lp/registry/model/person.py' | |||
292 | --- lib/lp/registry/model/person.py 2010-08-13 08:26:15 +0000 | |||
293 | +++ lib/lp/registry/model/person.py 2010-08-17 01:31:02 +0000 | |||
294 | @@ -59,7 +59,7 @@ | |||
295 | 59 | from canonical.database.sqlbase import ( | 59 | from canonical.database.sqlbase import ( |
296 | 60 | cursor, quote, quote_like, sqlvalues, SQLBase) | 60 | cursor, quote, quote_like, sqlvalues, SQLBase) |
297 | 61 | 61 | ||
299 | 62 | from canonical.cachedproperty import cachedproperty | 62 | from canonical.cachedproperty import cachedproperty, cache_property, clear_property |
300 | 63 | 63 | ||
301 | 64 | from canonical.lazr.utils import get_current_browser_request, safe_hasattr | 64 | from canonical.lazr.utils import get_current_browser_request, safe_hasattr |
302 | 65 | 65 | ||
303 | @@ -390,13 +390,12 @@ | |||
304 | 390 | 390 | ||
305 | 391 | Order them by name if necessary. | 391 | Order them by name if necessary. |
306 | 392 | """ | 392 | """ |
309 | 393 | self._languages_cache = sorted( | 393 | cache_property(self, '_languages_cache', sorted( |
310 | 394 | languages, key=attrgetter('englishname')) | 394 | languages, key=attrgetter('englishname'))) |
311 | 395 | 395 | ||
312 | 396 | def deleteLanguagesCache(self): | 396 | def deleteLanguagesCache(self): |
313 | 397 | """Delete this person's cached languages, if it exists.""" | 397 | """Delete this person's cached languages, if it exists.""" |
316 | 398 | if safe_hasattr(self, '_languages_cache'): | 398 | clear_property(self, '_languages_cache') |
315 | 399 | del self._languages_cache | ||
317 | 400 | 399 | ||
318 | 401 | def addLanguage(self, language): | 400 | def addLanguage(self, language): |
319 | 402 | """See `IPerson`.""" | 401 | """See `IPerson`.""" |
320 | @@ -1462,7 +1461,7 @@ | |||
321 | 1462 | include_teams=include_teams) | 1461 | include_teams=include_teams) |
322 | 1463 | person_list = [] | 1462 | person_list = [] |
323 | 1464 | for person, email in result: | 1463 | for person, email in result: |
325 | 1465 | person._preferredemail_cached = email | 1464 | cache_property(person, '_preferredemail_cached', email) |
326 | 1466 | person_list.append(person) | 1465 | person_list.append(person) |
327 | 1467 | return person_list | 1466 | return person_list |
328 | 1468 | 1467 | ||
329 | @@ -1737,7 +1736,7 @@ | |||
330 | 1737 | self.account_status = AccountStatus.DEACTIVATED | 1736 | self.account_status = AccountStatus.DEACTIVATED |
331 | 1738 | self.account_status_comment = comment | 1737 | self.account_status_comment = comment |
332 | 1739 | IMasterObject(self.preferredemail).status = EmailAddressStatus.NEW | 1738 | IMasterObject(self.preferredemail).status = EmailAddressStatus.NEW |
334 | 1740 | self._preferredemail_cached = None | 1739 | clear_property(self, '_preferredemail_cached') |
335 | 1741 | base_new_name = self.name + '-deactivatedaccount' | 1740 | base_new_name = self.name + '-deactivatedaccount' |
336 | 1742 | self.name = self._ensureNewName(base_new_name) | 1741 | self.name = self._ensureNewName(base_new_name) |
337 | 1743 | 1742 | ||
338 | @@ -2070,7 +2069,7 @@ | |||
339 | 2070 | if email_address is not None: | 2069 | if email_address is not None: |
340 | 2071 | email_address.status = EmailAddressStatus.VALIDATED | 2070 | email_address.status = EmailAddressStatus.VALIDATED |
341 | 2072 | email_address.syncUpdate() | 2071 | email_address.syncUpdate() |
343 | 2073 | self._preferredemail_cached = None | 2072 | clear_property(self, '_preferredemail_cached') |
344 | 2074 | 2073 | ||
345 | 2075 | def setPreferredEmail(self, email): | 2074 | def setPreferredEmail(self, email): |
346 | 2076 | """See `IPerson`.""" | 2075 | """See `IPerson`.""" |
347 | @@ -2107,7 +2106,7 @@ | |||
348 | 2107 | IMasterObject(email).syncUpdate() | 2106 | IMasterObject(email).syncUpdate() |
349 | 2108 | 2107 | ||
350 | 2109 | # Now we update our cache of the preferredemail. | 2108 | # Now we update our cache of the preferredemail. |
352 | 2110 | self._preferredemail_cached = email | 2109 | cache_property(self, '_preferredemail_cached', email) |
353 | 2111 | 2110 | ||
354 | 2112 | @cachedproperty('_preferredemail_cached') | 2111 | @cachedproperty('_preferredemail_cached') |
355 | 2113 | def preferredemail(self): | 2112 | def preferredemail(self): |
356 | @@ -2622,7 +2621,8 @@ | |||
357 | 2622 | # Populate the previously empty 'preferredemail' cached | 2621 | # Populate the previously empty 'preferredemail' cached |
358 | 2623 | # property, so the Person record is up-to-date. | 2622 | # property, so the Person record is up-to-date. |
359 | 2624 | if master_email.status == EmailAddressStatus.PREFERRED: | 2623 | if master_email.status == EmailAddressStatus.PREFERRED: |
361 | 2625 | account_person._preferredemail_cached = master_email | 2624 | cache_property(account_person, '_preferredemail_cached', |
362 | 2625 | master_email) | ||
363 | 2626 | return account_person | 2626 | return account_person |
364 | 2627 | # There is no associated `Person` to the email `Account`. | 2627 | # There is no associated `Person` to the email `Account`. |
365 | 2628 | # This is probably because the account was created externally | 2628 | # This is probably because the account was created externally |
366 | 2629 | 2629 | ||
367 | === modified file 'lib/lp/registry/model/product.py' | |||
368 | --- lib/lp/registry/model/product.py 2010-08-06 14:49:35 +0000 | |||
369 | +++ lib/lp/registry/model/product.py 2010-08-17 01:31:02 +0000 | |||
370 | @@ -509,9 +509,8 @@ | |||
371 | 509 | 509 | ||
372 | 510 | def __storm_invalidated__(self): | 510 | def __storm_invalidated__(self): |
373 | 511 | """Clear cached non-storm attributes when the transaction ends.""" | 511 | """Clear cached non-storm attributes when the transaction ends.""" |
374 | 512 | super(Product, self).__storm_invalidated__() | ||
375 | 512 | self._cached_licenses = None | 513 | self._cached_licenses = None |
376 | 513 | if safe_hasattr(self, '_commercial_subscription_cached'): | ||
377 | 514 | del self._commercial_subscription_cached | ||
378 | 515 | 514 | ||
379 | 516 | def _getLicenses(self): | 515 | def _getLicenses(self): |
380 | 517 | """Get the licenses as a tuple.""" | 516 | """Get the licenses as a tuple.""" |
381 | 518 | 517 | ||
382 | === modified file 'lib/lp/translations/model/potemplate.py' | |||
383 | --- lib/lp/translations/model/potemplate.py 2010-08-06 09:32:02 +0000 | |||
384 | +++ lib/lp/translations/model/potemplate.py 2010-08-17 01:31:02 +0000 | |||
385 | @@ -185,6 +185,7 @@ | |||
386 | 185 | _uses_english_msgids = None | 185 | _uses_english_msgids = None |
387 | 186 | 186 | ||
388 | 187 | def __storm_invalidated__(self): | 187 | def __storm_invalidated__(self): |
389 | 188 | super(POTemplate, self).__storm_invalidated__() | ||
390 | 188 | self.clearPOFileCache() | 189 | self.clearPOFileCache() |
391 | 189 | self._uses_english_msgids = None | 190 | self._uses_english_msgids = None |
392 | 190 | 191 | ||
393 | 191 | 192 | ||
394 | === modified file 'lib/lp/translations/model/potmsgset.py' | |||
395 | --- lib/lp/translations/model/potmsgset.py 2010-08-06 06:51:37 +0000 | |||
396 | +++ lib/lp/translations/model/potmsgset.py 2010-08-17 01:31:02 +0000 | |||
397 | @@ -103,6 +103,7 @@ | |||
398 | 103 | credits_message_ids = credits_message_info.keys() | 103 | credits_message_ids = credits_message_info.keys() |
399 | 104 | 104 | ||
400 | 105 | def __storm_invalidated__(self): | 105 | def __storm_invalidated__(self): |
401 | 106 | super(POTMsgSet, self).__storm_invalidated__() | ||
402 | 106 | self._cached_singular_text = None | 107 | self._cached_singular_text = None |
403 | 107 | self._cached_uses_english_msgids = None | 108 | self._cached_uses_english_msgids = None |
404 | 108 | 109 | ||
405 | @@ -157,6 +158,7 @@ | |||
406 | 157 | @property | 158 | @property |
407 | 158 | def uses_english_msgids(self): | 159 | def uses_english_msgids(self): |
408 | 159 | """See `IPOTMsgSet`.""" | 160 | """See `IPOTMsgSet`.""" |
409 | 161 | # TODO: convert to cachedproperty, it will be simpler. | ||
410 | 160 | if self._cached_uses_english_msgids is not None: | 162 | if self._cached_uses_english_msgids is not None: |
411 | 161 | return self._cached_uses_english_msgids | 163 | return self._cached_uses_english_msgids |
412 | 162 | 164 | ||
413 | @@ -181,6 +183,7 @@ | |||
414 | 181 | @property | 183 | @property |
415 | 182 | def singular_text(self): | 184 | def singular_text(self): |
416 | 183 | """See `IPOTMsgSet`.""" | 185 | """See `IPOTMsgSet`.""" |
417 | 186 | # TODO: convert to cachedproperty, it will be simpler. | ||
418 | 184 | if self._cached_singular_text is not None: | 187 | if self._cached_singular_text is not None: |
419 | 185 | return self._cached_singular_text | 188 | return self._cached_singular_text |
420 | 186 | 189 |
Hi,
This looks good. I'm not sure what value there is in initializing _cached_properties in SQLBase.__init__ is -- seems to slightly increase coupling.
Generally, yay for safer-by-default.
Cheers,
mwh