Code review comment for lp:~allenap/launchpad/cache-experiment-roll-out
- cache-experiment-roll-out
- Merge into devel
Revision history for this message
Gavin Panella (allenap) wrote : | # |
1 | === removed file 'lib/canonical/cachedproperty.py' |
2 | --- lib/canonical/cachedproperty.py 2010-08-17 07:03:25 +0000 |
3 | +++ lib/canonical/cachedproperty.py 1970-01-01 00:00:00 +0000 |
4 | @@ -1,229 +0,0 @@ |
5 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
6 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
7 | - |
8 | -"""Cached properties for situations where a property is computed once and |
9 | -then returned each time it is asked for. |
10 | - |
11 | -The clear_cachedproperties function can be used to wipe the cache of properties |
12 | -from an instance. |
13 | -""" |
14 | - |
15 | -__metaclass__ = type |
16 | - |
17 | -__all__ = [ |
18 | - 'cache_property', |
19 | - 'cachedproperty', |
20 | - 'clear_cachedproperties', |
21 | - 'clear_property', |
22 | - ] |
23 | - |
24 | -from zope.security.proxy import removeSecurityProxy |
25 | - |
26 | -from canonical.lazr.utils import safe_hasattr |
27 | - |
28 | -# XXX: JonathanLange 2010-01-11 bug=505731: Move this to lp.services. |
29 | - |
30 | -def cachedproperty(attrname_or_fn): |
31 | - """A decorator for methods that makes them properties with their return |
32 | - value cached. |
33 | - |
34 | - The value is cached on the instance, using the attribute name provided. |
35 | - |
36 | - If you don't provide a name, the mangled name of the property is used. |
37 | - |
38 | - cachedproperty is not threadsafe - it should not be used on objects which |
39 | - are shared across threads / external locking should be used on those |
40 | - objects. |
41 | - |
42 | - >>> class CachedPropertyTest(object): |
43 | - ... |
44 | - ... @cachedproperty('_foo_cache') |
45 | - ... def foo(self): |
46 | - ... print 'foo computed' |
47 | - ... return 23 |
48 | - ... |
49 | - ... @cachedproperty |
50 | - ... def bar(self): |
51 | - ... print 'bar computed' |
52 | - ... return 69 |
53 | - |
54 | - >>> cpt = CachedPropertyTest() |
55 | - >>> getattr(cpt, '_foo_cache', None) is None |
56 | - True |
57 | - >>> cpt.foo |
58 | - foo computed |
59 | - 23 |
60 | - >>> cpt.foo |
61 | - 23 |
62 | - >>> cpt._foo_cache |
63 | - 23 |
64 | - >>> cpt.bar |
65 | - bar computed |
66 | - 69 |
67 | - >>> cpt._bar_cached_value |
68 | - 69 |
69 | - |
70 | - Cached properties are listed on instances. |
71 | - >>> sorted(cpt._cached_properties) |
72 | - ['_bar_cached_value', '_foo_cache'] |
73 | - |
74 | - """ |
75 | - if isinstance(attrname_or_fn, basestring): |
76 | - attrname = attrname_or_fn |
77 | - return CachedPropertyForAttr(attrname) |
78 | - else: |
79 | - fn = attrname_or_fn |
80 | - attrname = '_%s_cached_value' % fn.__name__ |
81 | - return CachedProperty(attrname, fn) |
82 | - |
83 | -def cache_property(instance, attrname, value): |
84 | - """Cache value on instance as attrname. |
85 | - |
86 | - instance._cached_properties is updated with attrname. |
87 | - |
88 | - >>> class CachedPropertyTest(object): |
89 | - ... |
90 | - ... @cachedproperty('_foo_cache') |
91 | - ... def foo(self): |
92 | - ... return 23 |
93 | - ... |
94 | - >>> instance = CachedPropertyTest() |
95 | - >>> cache_property(instance, '_foo_cache', 42) |
96 | - >>> instance.foo |
97 | - 42 |
98 | - >>> instance._cached_properties |
99 | - ['_foo_cache'] |
100 | - |
101 | - Caching a new value does not duplicate the cache keys. |
102 | - |
103 | - >>> cache_property(instance, '_foo_cache', 84) |
104 | - >>> instance._cached_properties |
105 | - ['_foo_cache'] |
106 | - |
107 | - And does update the cached value. |
108 | - |
109 | - >>> instance.foo |
110 | - 84 |
111 | - """ |
112 | - naked_instance = removeSecurityProxy(instance) |
113 | - clear_property(naked_instance, attrname) |
114 | - setattr(naked_instance, attrname, value) |
115 | - cached_properties = getattr(naked_instance, '_cached_properties', []) |
116 | - cached_properties.append(attrname) |
117 | - naked_instance._cached_properties = cached_properties |
118 | - |
119 | - |
120 | -def clear_property(instance, attrname): |
121 | - """Remove a cached attribute from instance. |
122 | - |
123 | - The attribute name is removed from instance._cached_properties. |
124 | - |
125 | - If the property is not cached, nothing happens. |
126 | - |
127 | - :seealso clear_cachedproperties: For clearing all cached items at once. |
128 | - |
129 | - >>> class CachedPropertyTest(object): |
130 | - ... |
131 | - ... @cachedproperty('_foo_cache') |
132 | - ... def foo(self): |
133 | - ... return 23 |
134 | - ... |
135 | - >>> instance = CachedPropertyTest() |
136 | - >>> instance.foo |
137 | - 23 |
138 | - >>> clear_property(instance, '_foo_cache') |
139 | - >>> instance._cached_properties |
140 | - [] |
141 | - >>> is_cached(instance, '_foo_cache') |
142 | - False |
143 | - >>> clear_property(instance, '_foo_cache') |
144 | - """ |
145 | - naked_instance = removeSecurityProxy(instance) |
146 | - if not is_cached(naked_instance, attrname): |
147 | - return |
148 | - delattr(naked_instance, attrname) |
149 | - naked_instance._cached_properties.remove(attrname) |
150 | - |
151 | - |
152 | -def clear_cachedproperties(instance): |
153 | - """Clear cached properties from an object. |
154 | - |
155 | - >>> class CachedPropertyTest(object): |
156 | - ... |
157 | - ... @cachedproperty('_foo_cache') |
158 | - ... def foo(self): |
159 | - ... return 23 |
160 | - ... |
161 | - >>> instance = CachedPropertyTest() |
162 | - >>> instance.foo |
163 | - 23 |
164 | - >>> instance._cached_properties |
165 | - ['_foo_cache'] |
166 | - >>> clear_cachedproperties(instance) |
167 | - >>> instance._cached_properties |
168 | - [] |
169 | - >>> hasattr(instance, '_foo_cache') |
170 | - False |
171 | - """ |
172 | - naked_instance = removeSecurityProxy(instance) |
173 | - cached_properties = getattr(naked_instance, '_cached_properties', []) |
174 | - for property_name in cached_properties: |
175 | - delattr(naked_instance, property_name) |
176 | - naked_instance._cached_properties = [] |
177 | - |
178 | - |
179 | -def is_cached(instance, attrname): |
180 | - """Return True if attrname is cached on instance. |
181 | - |
182 | - >>> class CachedPropertyTest(object): |
183 | - ... |
184 | - ... @cachedproperty('_foo_cache') |
185 | - ... def foo(self): |
186 | - ... return 23 |
187 | - ... |
188 | - >>> instance = CachedPropertyTest() |
189 | - >>> instance.foo |
190 | - 23 |
191 | - >>> is_cached(instance, '_foo_cache') |
192 | - True |
193 | - >>> is_cached(instance, '_var_cache') |
194 | - False |
195 | - """ |
196 | - naked_instance = removeSecurityProxy(instance) |
197 | - return safe_hasattr(naked_instance, attrname) |
198 | - |
199 | - |
200 | -class CachedPropertyForAttr: |
201 | - """Curry a decorator to provide arguments to the CachedProperty.""" |
202 | - |
203 | - def __init__(self, attrname): |
204 | - self.attrname = attrname |
205 | - |
206 | - def __call__(self, fn): |
207 | - return CachedProperty(self.attrname, fn) |
208 | - |
209 | - |
210 | -class CachedProperty: |
211 | - |
212 | - # Used to detect not-yet-cached properties. |
213 | - sentinel = object() |
214 | - |
215 | - def __init__(self, attrname, fn): |
216 | - self.fn = fn |
217 | - self.attrname = attrname |
218 | - |
219 | - def __get__(self, inst, cls=None): |
220 | - if inst is None: |
221 | - return self |
222 | - cachedresult = getattr(inst, self.attrname, CachedProperty.sentinel) |
223 | - if cachedresult is CachedProperty.sentinel: |
224 | - result = self.fn(inst) |
225 | - cache_property(inst, self.attrname, result) |
226 | - return result |
227 | - else: |
228 | - return cachedresult |
229 | - |
230 | - |
231 | -if __name__ == '__main__': |
232 | - import doctest |
233 | - doctest.testmod() |
234 | |
235 | === modified file 'lib/canonical/database/sqlbase.py' |
236 | --- lib/canonical/database/sqlbase.py 2010-08-23 10:57:10 +0000 |
237 | +++ lib/canonical/database/sqlbase.py 2010-08-24 10:29:11 +0000 |
238 | @@ -65,7 +65,6 @@ |
239 | from zope.interface import implements |
240 | from zope.security.proxy import removeSecurityProxy |
241 | |
242 | -from canonical.cachedproperty import clear_cachedproperties |
243 | from canonical.config import ( |
244 | config, |
245 | dbconfig, |
246 | @@ -271,10 +270,6 @@ |
247 | # XXX: RobertCollins 2010-08-16 bug=622648: Note this is not directly |
248 | # tested, but the entire test suite blows up awesomely if it's broken. |
249 | # It's entirely unclear where tests for this should be. |
250 | - |
251 | - # While canonical.cachedproperty and lp.services.propertycache are |
252 | - # both in use, we must clear the caches for both. |
253 | - clear_cachedproperties(self) |
254 | IPropertyCacheManager(self).clear() |
255 | |
256 | |
257 | |
258 | === removed file 'lib/canonical/tests/test_cachedproperty.py' |
259 | --- lib/canonical/tests/test_cachedproperty.py 2010-07-14 14:11:15 +0000 |
260 | +++ lib/canonical/tests/test_cachedproperty.py 1970-01-01 00:00:00 +0000 |
261 | @@ -1,15 +0,0 @@ |
262 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
263 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
264 | - |
265 | -from doctest import DocTestSuite, ELLIPSIS |
266 | -import unittest |
267 | - |
268 | -import canonical.cachedproperty |
269 | - |
270 | -def test_suite(): |
271 | - suite = DocTestSuite(canonical.cachedproperty, optionflags=ELLIPSIS) |
272 | - return suite |
273 | - |
274 | - |
275 | -if __name__ == '__main__': |
276 | - unittest.main(defaultTest='test_suite') |
277 | |
278 | === modified file 'lib/lp/bugs/model/bug.py' |
279 | --- lib/lp/bugs/model/bug.py 2010-08-22 18:31:30 +0000 |
280 | +++ lib/lp/bugs/model/bug.py 2010-08-24 10:12:39 +0000 |
281 | @@ -70,9 +70,9 @@ |
282 | providedBy, |
283 | ) |
284 | |
285 | -from canonical.cachedproperty import ( |
286 | +from lp.services.propertycache import ( |
287 | cachedproperty, |
288 | - clear_property, |
289 | + IPropertyCache, |
290 | ) |
291 | from canonical.config import config |
292 | from canonical.database.constants import UTC_NOW |
293 | @@ -633,7 +633,7 @@ |
294 | # disabled see the change. |
295 | store.flush() |
296 | self.updateHeat() |
297 | - clear_property(self, '_cached_viewers') |
298 | + del IPropertyCache(self)._known_viewers |
299 | return |
300 | |
301 | def unsubscribeFromDupes(self, person, unsubscribed_by): |
302 | @@ -1626,7 +1626,7 @@ |
303 | self, self.messages[comment_number]) |
304 | bug_message.visible = visible |
305 | |
306 | - @cachedproperty('_cached_viewers') |
307 | + @cachedproperty |
308 | def _known_viewers(self): |
309 | """A dict of of known persons able to view this bug.""" |
310 | return set() |
311 | |
312 | === modified file 'lib/lp/bugs/model/bugtask.py' |
313 | --- lib/lp/bugs/model/bugtask.py 2010-08-22 18:31:30 +0000 |
314 | +++ lib/lp/bugs/model/bugtask.py 2010-08-24 10:32:39 +0000 |
315 | @@ -59,7 +59,6 @@ |
316 | removeSecurityProxy, |
317 | ) |
318 | |
319 | -from canonical.cachedproperty import cache_property |
320 | from canonical.config import config |
321 | from canonical.database.constants import UTC_NOW |
322 | from canonical.database.datetimecol import UtcDateTimeCol |
323 | @@ -156,6 +155,9 @@ |
324 | from lp.soyuz.interfaces.publishing import PackagePublishingStatus |
325 | from lp.soyuz.model.publishing import SourcePackagePublishingHistory |
326 | from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease |
327 | +from lp.services.propertycache import ( |
328 | + IPropertyCache, |
329 | + ) |
330 | |
331 | |
332 | debbugsseveritymap = {None: BugTaskImportance.UNDECIDED, |
333 | @@ -1324,7 +1326,7 @@ |
334 | """ |
335 | userid = user.id |
336 | def cache_user_can_view_bug(bugtask): |
337 | - cache_property(bugtask.bug, '_cached_viewers', set([userid])) |
338 | + IPropertyCache(bugtask.bug)._known_viewers = set([userid]) |
339 | return bugtask |
340 | return cache_user_can_view_bug |
341 | |
342 | |
343 | === modified file 'lib/lp/registry/browser/person.py' |
344 | --- lib/lp/registry/browser/person.py 2010-08-23 03:25:20 +0000 |
345 | +++ lib/lp/registry/browser/person.py 2010-08-24 10:19:26 +0000 |
346 | @@ -141,7 +141,10 @@ |
347 | from zope.security.interfaces import Unauthorized |
348 | from zope.security.proxy import removeSecurityProxy |
349 | |
350 | -from canonical.cachedproperty import cachedproperty |
351 | +from lp.services.propertycache import ( |
352 | + cachedproperty, |
353 | + IPropertyCache, |
354 | + ) |
355 | from canonical.config import config |
356 | from canonical.database.sqlbase import flush_database_updates |
357 | from canonical.launchpad import ( |
358 | @@ -5693,10 +5696,7 @@ |
359 | def _reset_state(self): |
360 | """Reset the cache because the recipients changed.""" |
361 | self._count_recipients = None |
362 | - if safe_hasattr(self, '_all_recipients_cached'): |
363 | - # The clear the cache of _all_recipients. The caching will fail |
364 | - # if this method creates the attribute before _all_recipients. |
365 | - del self._all_recipients_cached |
366 | + del IPropertyCache(self)._all_recipients |
367 | |
368 | def _getPrimaryReason(self, person_or_team): |
369 | """Return the primary reason enumeration. |
370 | @@ -5804,7 +5804,7 @@ |
371 | 'You are contacting %s of the %s (%s) team directly.' |
372 | % (text, person_or_team.displayname, person_or_team.name)) |
373 | |
374 | - @cachedproperty('_all_recipients_cached') |
375 | + @cachedproperty |
376 | def _all_recipients(self): |
377 | """Set the cache of all recipients.""" |
378 | all_recipients = {} |
379 | |
380 | === modified file 'lib/lp/registry/model/distroseries.py' |
381 | --- lib/lp/registry/model/distroseries.py 2010-08-22 18:31:30 +0000 |
382 | +++ lib/lp/registry/model/distroseries.py 2010-08-24 10:20:53 +0000 |
383 | @@ -547,7 +547,7 @@ |
384 | orderBy=["Language.englishname"]) |
385 | return result |
386 | |
387 | - @cachedproperty('_previous_series_cached') |
388 | + @cachedproperty |
389 | def previous_series(self): |
390 | """See `IDistroSeries`.""" |
391 | # This property is cached because it is used intensely inside |
392 | |
393 | === modified file 'lib/lp/registry/model/product.py' |
394 | --- lib/lp/registry/model/product.py 2010-08-22 19:26:46 +0000 |
395 | +++ lib/lp/registry/model/product.py 2010-08-24 10:23:04 +0000 |
396 | @@ -38,7 +38,10 @@ |
397 | from zope.interface import implements |
398 | from zope.security.proxy import removeSecurityProxy |
399 | |
400 | -from canonical.cachedproperty import cachedproperty |
401 | +from lp.services.propertycache import ( |
402 | + cachedproperty, |
403 | + IPropertyCache, |
404 | + ) |
405 | from canonical.database.constants import UTC_NOW |
406 | from canonical.database.datetimecol import UtcDateTimeCol |
407 | from canonical.database.enumcol import EnumCol |
408 | @@ -428,7 +431,7 @@ |
409 | notNull=True, default=False, |
410 | storm_validator=_validate_license_approved) |
411 | |
412 | - @cachedproperty('_commercial_subscription_cached') |
413 | + @cachedproperty |
414 | def commercial_subscription(self): |
415 | return CommercialSubscription.selectOneBy(product=self) |
416 | |
417 | @@ -475,7 +478,7 @@ |
418 | purchaser=purchaser, |
419 | sales_system_id=voucher, |
420 | whiteboard=whiteboard) |
421 | - self._commercial_subscription_cached = subscription |
422 | + IPropertyCache(self).commercial_subscription = subscription |
423 | else: |
424 | if current_datetime <= self.commercial_subscription.date_expires: |
425 | # Extend current subscription. |
426 | |
427 | === modified file 'lib/lp/soyuz/model/distroseriesbinarypackage.py' |
428 | --- lib/lp/soyuz/model/distroseriesbinarypackage.py 2010-08-20 20:31:18 +0000 |
429 | +++ lib/lp/soyuz/model/distroseriesbinarypackage.py 2010-08-24 10:25:36 +0000 |
430 | @@ -12,7 +12,10 @@ |
431 | from storm.store import Store |
432 | from zope.interface import implements |
433 | |
434 | -from canonical.cachedproperty import cachedproperty |
435 | +from lp.services.propertycache import ( |
436 | + cachedproperty, |
437 | + IPropertyCache, |
438 | + ) |
439 | from canonical.database.sqlbase import sqlvalues |
440 | from lp.soyuz.interfaces.distroseriesbinarypackage import ( |
441 | IDistroSeriesBinaryPackage, |
442 | @@ -40,7 +43,7 @@ |
443 | self.distroseries = distroseries |
444 | self.binarypackagename = binarypackagename |
445 | if cache is not None: |
446 | - self._cache = cache |
447 | + IPropertyCache(self).cache = cache |
448 | |
449 | @property |
450 | def name(self): |
451 | @@ -58,7 +61,7 @@ |
452 | """See IDistroSeriesBinaryPackage.""" |
453 | return self.distroseries.distribution |
454 | |
455 | - @cachedproperty('_cache') |
456 | + @cachedproperty |
457 | def cache(self): |
458 | """See IDistroSeriesBinaryPackage.""" |
459 | store = Store.of(self.distroseries) |
460 | |
461 | === modified file 'lib/lp/testopenid/browser/server.py' |
462 | --- lib/lp/testopenid/browser/server.py 2010-08-20 20:31:18 +0000 |
463 | +++ lib/lp/testopenid/browser/server.py 2010-08-24 10:27:59 +0000 |
464 | @@ -32,7 +32,10 @@ |
465 | from zope.security.proxy import isinstance as zisinstance |
466 | from zope.session.interfaces import ISession |
467 | |
468 | -from canonical.cachedproperty import cachedproperty |
469 | +from lp.services.propertycache import ( |
470 | + cachedproperty, |
471 | + IPropertyCache, |
472 | + ) |
473 | from canonical.launchpad import _ |
474 | from canonical.launchpad.interfaces.account import ( |
475 | AccountStatus, |
476 | @@ -142,7 +145,7 @@ |
477 | return (self.openid_request.idSelect() or |
478 | self.openid_request.identity == self.user_identity_url) |
479 | |
480 | - @cachedproperty('_openid_parameters') |
481 | + @cachedproperty |
482 | def openid_parameters(self): |
483 | """A dictionary of OpenID query parameters from request.""" |
484 | query = {} |
485 | @@ -165,8 +168,9 @@ |
486 | def restoreRequestFromSession(self): |
487 | """Get the OpenIDRequest from our session.""" |
488 | session = self.getSession() |
489 | + cache = IPropertyCache(self) |
490 | try: |
491 | - self._openid_parameters = session[OPENID_REQUEST_SESSION_KEY] |
492 | + cache.openid_parameters = session[OPENID_REQUEST_SESSION_KEY] |
493 | except KeyError: |
494 | raise UnexpectedFormData("No OpenID request in session") |
495 | |
496 | |
497 | === modified file 'lib/lp/shipit.py' |
498 | --- lib/lp/shipit.py 2010-08-20 20:31:18 +0000 |
499 | +++ lib/lp/shipit.py 2010-08-24 15:27:46 +0000 |
500 | @@ -106,6 +106,10 @@ |
501 | from lp.registry.model.person import Person |
502 | from lp.services.mail import stub |
503 | from lp.services.mail.sendmail import simple_sendmail |
504 | +from lp.services.propertycache import ( |
505 | + cachedproperty, |
506 | + IPropertyCache, |
507 | + ) |
508 | from lp.services.scripts.base import ( |
509 | LaunchpadCronScript, |
510 | LaunchpadScript, |
While this branch appears very long, the majority of it is mechanical
changes. I've attached a diff of the non-mechanical parts.