launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #00686
[Merge] lp:~allenap/launchpad/cache-experiment into lp:launchpad/devel
Gavin Panella has proposed merging lp:~allenap/launchpad/cache-experiment into lp:launchpad/devel.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
This is a proposed replacement for the cachedproperty module. It provides a cleaner interface for inspecting and modifying the cache, and even makes cache implementations pluggable (via adaption). In use it's pretty simple and unsurprising.
See the documentation at the top of lib/lp/services/propertycache.py for a concise demonstration of how it works.
I haven't converted all uses of the existing cachedproperty yet, only those places which actually manipulate the cache (i.e. call sites for cache_property clear_property and clear_cachedproperties). I'll do that in a follow-up branch if this one is accepted.
--
https://code.launchpad.net/~allenap/launchpad/cache-experiment/+merge/33223
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~allenap/launchpad/cache-experiment into lp:launchpad/devel.
=== modified file 'lib/canonical/configure.zcml'
--- lib/canonical/configure.zcml 2010-08-17 15:05:44 +0000
+++ lib/canonical/configure.zcml 2010-08-20 14:47:44 +0000
@@ -15,16 +15,7 @@
<include package="canonical.launchpad" file="permissions.zcml" />
<include package="canonical.launchpad.webapp" file="meta.zcml" />
<include package="lazr.restful" file="meta.zcml" />
- <include package="lp.services.database" />
- <include package="lp.services.inlinehelp" file="meta.zcml" />
- <include package="lp.services.openid" />
- <include package="lp.services.job" />
- <include package="lp.services.memcache" />
- <include package="lp.services.profile" />
- <include package="lp.services.features" />
- <include package="lp.services.scripts" />
- <include package="lp.services.worlddata" />
- <include package="lp.services.salesforce" />
+ <include package="lp.services" />
<include package="lazr.uri" />
<include package="canonical.librarian" />
=== modified file 'lib/canonical/database/sqlbase.py'
--- lib/canonical/database/sqlbase.py 2010-08-16 00:40:47 +0000
+++ lib/canonical/database/sqlbase.py 2010-08-20 14:47:44 +0000
@@ -34,6 +34,8 @@
from canonical.config import config, dbconfig
from canonical.database.interfaces import ISQLBase
+from lp.services.propertycache import IPropertyCacheManager
+
__all__ = [
'alreadyInstalledMsg',
@@ -263,6 +265,7 @@
# awesomely if its broken : its entirely unclear where tests for this
# should be -- RBC 20100816.
clear_cachedproperties(self)
+ IPropertyCacheManager(self).clear()
alreadyInstalledMsg = ("A ZopelessTransactionManager with these settings is "
=== modified file 'lib/lp/registry/doc/personlocation.txt'
--- lib/lp/registry/doc/personlocation.txt 2009-11-15 01:05:49 +0000
+++ lib/lp/registry/doc/personlocation.txt 2010-08-20 14:47:44 +0000
@@ -119,10 +119,11 @@
have been pre-cached so that we don't hit the database everytime we
access a person's .location property.
- >>> from zope.security.proxy import removeSecurityProxy
+ >>> from lp.services.propertycache import IPropertyCache
>>> for mapped in guadamen.getMappedParticipants():
- ... mapped = removeSecurityProxy(mapped)
- ... if not verifyObject(IPersonLocation, mapped._location):
+ ... cache = IPropertyCache(mapped)
+ ... if ("location" not in cache or
+ ... not verifyObject(IPersonLocation, cache.location)):
... print 'No cached location on %s' % mapped.name
The mapped_participants methods takes a optional argument to limit the
=== modified file 'lib/lp/registry/doc/teammembership.txt'
--- lib/lp/registry/doc/teammembership.txt 2010-08-17 11:50:43 +0000
+++ lib/lp/registry/doc/teammembership.txt 2010-08-20 14:47:44 +0000
@@ -1008,8 +1008,8 @@
>>> from canonical.launchpad.interfaces import IMasterObject
>>> IMasterObject(bad_user.account).status = AccountStatus.SUSPENDED
>>> IMasterObject(bad_user.preferredemail).status = EmailAddressStatus.OLD
- >>> from canonical.cachedproperty import clear_property
- >>> clear_property(removeSecurityProxy(bad_user), '_preferredemail_cached')
+ >>> from lp.services.propertycache import IPropertyCache
+ >>> del IPropertyCache(removeSecurityProxy(bad_user)).preferredemail
>>> transaction.commit()
>>> [m.displayname for m in t3.allmembers]
=== modified file 'lib/lp/registry/model/distribution.py'
--- lib/lp/registry/model/distribution.py 2010-08-16 01:58:24 +0000
+++ lib/lp/registry/model/distribution.py 2010-08-20 14:47:44 +0000
@@ -19,7 +19,6 @@
from zope.interface import alsoProvides, implements
from lp.archivepublisher.debversion import Version
-from canonical.cachedproperty import cachedproperty, clear_property
from canonical.database.constants import UTC_NOW
from canonical.database.datetimecol import UtcDateTimeCol
@@ -30,7 +29,6 @@
DecoratedResultSet)
from canonical.launchpad.components.storm_operators import FTQ, Match, RANK
from canonical.launchpad.interfaces.lpstorm import IStore
-from canonical.lazr.utils import safe_hasattr
from lp.registry.model.announcement import MakesAnnouncements
from lp.soyuz.model.archive import Archive
from lp.soyuz.model.binarypackagename import BinaryPackageName
@@ -119,6 +117,7 @@
from lp.answers.interfaces.questioncollection import (
QUESTION_STATUS_DEFAULT_SEARCH)
from lp.answers.interfaces.questiontarget import IQuestionTarget
+from lp.services.propertycache import IPropertyCache, cachedproperty
class Distribution(SQLBase, BugTargetBase, MakesAnnouncements,
@@ -381,7 +380,7 @@
return (2, self.name)
return (3, self.name)
- @cachedproperty('_cached_series')
+ @cachedproperty
def series(self):
"""See `IDistribution`."""
ret = Store.of(self).find(
@@ -1619,7 +1618,8 @@
# May wish to add this to the series rather than clearing the cache --
# RBC 20100816.
- clear_property(self, '_cached_series')
+ del IPropertyCache(self).series
+
return series
@property
=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py 2010-08-18 22:02:34 +0000
+++ lib/lp/registry/model/person.py 2010-08-20 14:47:44 +0000
@@ -61,9 +61,6 @@
from canonical.database.sqlbase import (
cursor, quote, quote_like, sqlvalues, SQLBase)
-from canonical.cachedproperty import (cachedproperty, cache_property,
- clear_property)
-
from canonical.lazr.utils import get_current_browser_request
from canonical.launchpad.components.decoratedresultset import (
@@ -165,6 +162,8 @@
from canonical.launchpad.webapp.dbpolicy import MasterDatabasePolicy
from lp.registry.interfaces.person import validate_public_person
+from lp.services.propertycache import IPropertyCache, cachedproperty
+
class JoinTeamEvent:
"""See `IJoinTeamEvent`."""
@@ -380,7 +379,7 @@
personal_standing_reason = StringCol(default=None)
- @cachedproperty('_languages_cache')
+ @cachedproperty
def languages(self):
"""See `IPerson`."""
results = Store.of(self).find(
@@ -394,19 +393,19 @@
:raises AttributeError: If the cache doesn't exist.
"""
- return self._languages_cache
+ return IPropertyCache(self).languages
def setLanguagesCache(self, languages):
"""Set this person's cached languages.
Order them by name if necessary.
"""
- cache_property(self, '_languages_cache', sorted(
- languages, key=attrgetter('englishname')))
+ IPropertyCache(self).languages = sorted(
+ languages, key=attrgetter('englishname'))
def deleteLanguagesCache(self):
"""Delete this person's cached languages, if it exists."""
- clear_property(self, '_languages_cache')
+ del IPropertyCache(self).languages
def addLanguage(self, language):
"""See `IPerson`."""
@@ -473,7 +472,7 @@
Or(OAuthRequestToken.date_expires == None,
OAuthRequestToken.date_expires > UTC_NOW))
- @cachedproperty('_location')
+ @cachedproperty
def location(self):
"""See `IObjectWithLocation`."""
return PersonLocation.selectOneBy(person=self)
@@ -509,7 +508,8 @@
"""See `ISetLocation`."""
assert not self.is_team, 'Cannot edit team location.'
if self.location is None:
- self._location = PersonLocation(person=self, visible=visible)
+ IPropertyCache(self).location = PersonLocation(
+ person=self, visible=visible)
else:
self.location.visible = visible
@@ -527,7 +527,7 @@
self.location.last_modified_by = user
self.location.date_last_modified = UTC_NOW
else:
- self._location = PersonLocation(
+ IPropertyCache(self).location = PersonLocation(
person=self, time_zone=time_zone, latitude=latitude,
longitude=longitude, last_modified_by=user)
@@ -1029,7 +1029,7 @@
result = result.order_by(KarmaCategory.title)
return [karma_cache for (karma_cache, category) in result]
- @cachedproperty('_karma_cached')
+ @cachedproperty
def karma(self):
"""See `IPerson`."""
# May also be loaded from _all_members
@@ -1050,7 +1050,7 @@
return self.is_valid_person
- @cachedproperty('_is_valid_person_cached')
+ @cachedproperty
def is_valid_person(self):
"""See `IPerson`."""
if self.is_team:
@@ -1543,6 +1543,7 @@
def prepopulate_person(row):
result = row[0]
+ cache = IPropertyCache(result)
index = 1
#-- karma caching
if need_karma:
@@ -1552,27 +1553,27 @@
karma_total = 0
else:
karma_total = karma.karma_total
- cache_property(result, '_karma_cached', karma_total)
+ cache.karma = karma_total
#-- ubuntu code of conduct signer status caching.
if need_ubuntu_coc:
signed = row[index]
index += 1
- cache_property(result, '_is_ubuntu_coc_signer_cached', signed)
+ cache.is_ubuntu_coc_signer = signed
#-- location caching
if need_location:
location = row[index]
index += 1
- cache_property(result, '_location', location)
+ cache.location = location
#-- archive caching
if need_archive:
archive = row[index]
index += 1
- cache_property(result, '_archive_cached', archive)
+ cache.archive = archive
#-- preferred email caching
if need_preferred_email:
email = row[index]
index += 1
- cache_property(result, '_preferredemail_cached', email)
+ cache.preferredemail = email
#-- validity caching
if need_validity:
# valid if:
@@ -1582,7 +1583,7 @@
# -- preferred email found
and result.preferredemail is not None)
index += 1
- cache_property(result, '_is_valid_person_cached', valid)
+ cache.is_valid_person = valid
return result
return DecoratedResultSet(raw_result,
result_decorator=prepopulate_person)
@@ -1610,7 +1611,7 @@
result = self._getMembersWithPreferredEmails()
person_list = []
for person, email in result:
- cache_property(person, '_preferredemail_cached', email)
+ IPropertyCache(person).preferredemail = email
person_list.append(person)
return person_list
@@ -1742,7 +1743,7 @@
# fetches the rows when they're needed.
locations = self._getMappedParticipantsLocations(limit=limit)
for location in locations:
- location.person._location = location
+ IPropertyCache(location.person).location = location
participants = set(location.person for location in locations)
# Cache the ValidPersonCache query for all mapped participants.
if len(participants) > 0:
@@ -1884,7 +1885,7 @@
self.account_status = AccountStatus.DEACTIVATED
self.account_status_comment = comment
IMasterObject(self.preferredemail).status = EmailAddressStatus.NEW
- clear_property(self, '_preferredemail_cached')
+ del IPropertyCache(self).preferredemail
base_new_name = self.name + '-deactivatedaccount'
self.name = self._ensureNewName(base_new_name)
@@ -2218,7 +2219,7 @@
if email_address is not None:
email_address.status = EmailAddressStatus.VALIDATED
email_address.syncUpdate()
- clear_property(self, '_preferredemail_cached')
+ del IPropertyCache(self).preferredemail
def setPreferredEmail(self, email):
"""See `IPerson`."""
@@ -2255,9 +2256,9 @@
IMasterObject(email).syncUpdate()
# Now we update our cache of the preferredemail.
- cache_property(self, '_preferredemail_cached', email)
+ IPropertyCache(self).preferredemail = email
- @cachedproperty('_preferredemail_cached')
+ @cachedproperty
def preferredemail(self):
"""See `IPerson`."""
emails = self._getEmailsByStatus(EmailAddressStatus.PREFERRED)
@@ -2422,7 +2423,7 @@
distribution.main_archive, self)
return permissions.count() > 0
- @cachedproperty('_is_ubuntu_coc_signer_cached')
+ @cachedproperty
def is_ubuntu_coc_signer(self):
"""See `IPerson`."""
# Also assigned to by self._all_members.
@@ -2452,7 +2453,7 @@
sCoC_util = getUtility(ISignedCodeOfConductSet)
return sCoC_util.searchByUser(self.id, active=False)
- @cachedproperty('_archive_cached')
+ @cachedproperty
def archive(self):
"""See `IPerson`."""
return getUtility(IArchiveSet).getPPAOwnedByPerson(self)
@@ -2776,8 +2777,8 @@
# Populate the previously empty 'preferredemail' cached
# property, so the Person record is up-to-date.
if master_email.status == EmailAddressStatus.PREFERRED:
- cache_property(account_person, '_preferredemail_cached',
- master_email)
+ cache = IPropertyCache(account_person)
+ cache.preferredemail = master_email
return account_person
# There is no associated `Person` to the email `Account`.
# This is probably because the account was created externally
=== modified file 'lib/lp/registry/tests/test_distribution.py'
--- lib/lp/registry/tests/test_distribution.py 2010-08-10 19:14:56 +0000
+++ lib/lp/registry/tests/test_distribution.py 2010-08-20 14:47:44 +0000
@@ -19,6 +19,7 @@
from lp.testing import TestCaseWithFactory
from canonical.testing.layers import (
DatabaseFunctionalLayer, LaunchpadFunctionalLayer)
+from lp.services.propertycache import IPropertyCache
class TestDistribution(TestCaseWithFactory):
@@ -80,27 +81,26 @@
distribution = removeSecurityProxy(
self.factory.makeDistribution('foo'))
+ cache = IPropertyCache(distribution)
+
# Not yet cached.
- missing = object()
- cached_series = getattr(distribution, '_cached_series', missing)
- self.assertEqual(missing, cached_series)
+ self.assertNotIn("series", cache)
# Now cached.
series = distribution.series
- self.assertTrue(series is distribution._cached_series)
+ self.assertIs(series, cache.series)
# Cache cleared.
distribution.newSeries(
name='bar', displayname='Bar', title='Bar', summary='',
description='', version='1', parent_series=None,
owner=self.factory.makePerson())
- cached_series = getattr(distribution, '_cached_series', missing)
- self.assertEqual(missing, cached_series)
+ self.assertNotIn("series", cache)
# New cached value.
series = distribution.series
self.assertEqual(1, len(series))
- self.assertTrue(series is distribution._cached_series)
+ self.assertIs(series, cache.series)
class SeriesByStatusTests(TestCaseWithFactory):
=== added file 'lib/lp/services/configure.zcml'
--- lib/lp/services/configure.zcml 1970-01-01 00:00:00 +0000
+++ lib/lp/services/configure.zcml 2010-08-20 14:47:44 +0000
@@ -0,0 +1,19 @@
+<!-- Copyright 2010 Canonical Ltd. This software is licensed under the
+ GNU Affero General Public License version 3 (see the file LICENSE).
+-->
+
+<configure xmlns="http://namespaces.zope.org/zope">
+ <adapter factory=".propertycache.get_default_cache"/>
+ <adapter factory=".propertycache.PropertyCacheManager"/>
+ <adapter factory=".propertycache.DefaultPropertyCacheManager"/>
+ <include package=".database" />
+ <include package=".features" />
+ <include package=".inlinehelp" file="meta.zcml" />
+ <include package=".job" />
+ <include package=".memcache" />
+ <include package=".openid" />
+ <include package=".profile" />
+ <include package=".salesforce" />
+ <include package=".scripts" />
+ <include package=".worlddata" />
+</configure>
=== added file 'lib/lp/services/propertycache.py'
--- lib/lp/services/propertycache.py 1970-01-01 00:00:00 +0000
+++ lib/lp/services/propertycache.py 2010-08-20 14:47:44 +0000
@@ -0,0 +1,294 @@
+# 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.
+
+ >>> from itertools import count
+ >>> counter = count(1)
+
+ >>> class Foo:
+ ... @cachedproperty
+ ... def bar(self):
+ ... return next(counter)
+
+ >>> foo = Foo()
+
+The property cache can be obtained via adaption.
+
+ >>> cache = IPropertyCache(foo)
+
+Initially it is empty. Caches can be iterated over to reveal the names of the
+values cached within.
+
+ >>> list(cache)
+ []
+
+After accessing a cached property the cache is no longer empty.
+
+ >>> foo.bar
+ 1
+ >>> list(cache)
+ ['bar']
+ >>> cache.bar
+ 1
+
+Attempting to access an unknown name from the cache is an error.
+
+ >>> cache.baz
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'DefaultPropertyCache' object has no attribute 'baz'
+
+Values in the cache can be deleted.
+
+ >>> del cache.bar
+ >>> list(cache)
+ []
+
+Accessing the cached property causes its populate function to be called again.
+
+ >>> foo.bar
+ 2
+ >>> cache.bar
+ 2
+
+Values in the cache can be set and updated.
+
+ >>> cache.bar = 456
+ >>> foo.bar
+ 456
+
+Caches respond to membership tests.
+
+ >>> "bar" in cache
+ True
+
+ >>> del cache.bar
+
+ >>> "bar" in cache
+ False
+
+It is safe to delete names from the cache even if there is no value cached.
+
+ >>> del cache.bar
+ >>> del cache.bar
+
+A cache manager can be used to empty the cache.
+
+ >>> manager = IPropertyCacheManager(cache)
+
+ >>> cache.bar = 123
+ >>> cache.baz = 456
+ >>> sorted(cache)
+ ['bar', 'baz']
+
+ >>> manager.clear()
+ >>> list(cache)
+ []
+
+A cache manager can be obtained by adaption from non-cache objects too.
+
+ >>> manager = IPropertyCacheManager(foo)
+ >>> manager.cache is cache
+ True
+
+"""
+
+__metaclass__ = type
+__all__ = [
+ 'IPropertyCache',
+ 'IPropertyCacheManager',
+ 'cachedproperty',
+ ]
+
+from functools import partial
+
+from zope.component import adapter, adapts
+from zope.interface import Interface, implementer, implements
+from zope.schema import Object
+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 IPropertyCacheManager(Interface):
+
+ cache = Object(IPropertyCache)
+
+ def clear():
+ """Empty the cache."""
+
+
+class DefaultPropertyCache:
+ """A simple cache."""
+
+ implements(IPropertyCache)
+
+ 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__)
+
+
+@adapter(Interface)
+@implementer(IPropertyCache)
+def get_default_cache(target):
+ """Adapter to obtain a `DefaultPropertyCache` for any object."""
+ naked_target = removeSecurityProxy(target)
+ try:
+ return naked_target._property_cache
+ except AttributeError:
+ naked_target._property_cache = DefaultPropertyCache()
+ return naked_target._property_cache
+
+
+class PropertyCacheManager:
+ """A simple `IPropertyCacheManager`.
+
+ Should work for any `IPropertyCache` instance.
+ """
+
+ implements(IPropertyCacheManager)
+ adapts(Interface)
+
+ def __init__(self, target):
+ self.cache = IPropertyCache(target)
+
+ def clear(self):
+ """See `IPropertyCacheManager`."""
+ for name in list(self.cache):
+ delattr(self.cache, name)
+
+
+class DefaultPropertyCacheManager:
+ """A `IPropertyCacheManager` specifically for `DefaultPropertyCache`.
+
+ The implementation of `clear` is more efficient.
+ """
+
+ implements(IPropertyCacheManager)
+ adapts(DefaultPropertyCache)
+
+ def __init__(self, cache):
+ self.cache = cache
+
+ def clear(self):
+ self.cache.__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 = IPropertyCache(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.
+
+ A cached property can be declared with or without an explicit name. If not
+ provided it will be derived from the decorated object. This name is the
+ name under which values will be cached.
+
+ >>> class Foo:
+ ... @cachedproperty("a_in_cache")
+ ... def a(self):
+ ... return 1234
+ ... @cachedproperty
+ ... def b(self):
+ ... return 5678
+
+ >>> foo = Foo()
+
+ `a` was declared with an explicit name of "a_in_cache" so it is known as
+ "a_in_cache" in the cache.
+
+ >>> isinstance(Foo.a, CachedProperty)
+ True
+ >>> Foo.a.name
+ 'a_in_cache'
+ >>> Foo.a.populate
+ <function a at 0x...>
+
+ >>> foo.a
+ 1234
+ >>> IPropertyCache(foo).a_in_cache
+ 1234
+
+ `b` was defined without an explicit name so it is known as "b" in the
+ cache too.
+
+ >>> isinstance(Foo.b, CachedProperty)
+ True
+ >>> Foo.b.name
+ 'b'
+ >>> Foo.b.populate
+ <function b at 0x...>
+
+ >>> foo.b
+ 5678
+ >>> IPropertyCache(foo).b
+ 5678
+
+ """
+ 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)
=== added file 'lib/lp/services/tests/test_propertycache.py'
--- lib/lp/services/tests/test_propertycache.py 1970-01-01 00:00:00 +0000
+++ lib/lp/services/tests/test_propertycache.py 2010-08-20 14:47:44 +0000
@@ -0,0 +1,16 @@
+# Copyright 2010 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Tests for lp.services.propertycache."""
+
+__metaclass__ = type
+
+from canonical.testing import LaunchpadZopelessLayer
+from lp.services import propertycache
+
+
+def test_suite():
+ from doctest import DocTestSuite, ELLIPSIS
+ suite = DocTestSuite(propertycache, optionflags=ELLIPSIS)
+ suite.layer = LaunchpadZopelessLayer
+ return suite
=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py 2010-08-16 08:23:59 +0000
+++ lib/lp/soyuz/model/archive.py 2010-08-20 14:47:44 +0000
@@ -25,7 +25,6 @@
from lp.app.errors import NotFoundError
from lp.archivepublisher.debversion import Version
from lp.archiveuploader.utils import re_issource, re_isadeb
-from canonical.cachedproperty import clear_property
from canonical.config import config
from canonical.database.constants import UTC_NOW
from canonical.database.datetimecol import UtcDateTimeCol
@@ -103,6 +102,7 @@
from canonical.launchpad.webapp.url import urlappend
from canonical.launchpad.validators.name import valid_name
from lp.registry.interfaces.person import validate_person
+from lp.services.propertycache import IPropertyCache
class Archive(SQLBase):
@@ -1759,7 +1759,7 @@
signing_key = owner.archive.signing_key
else:
# owner.archive is a cached property and we've just cached it.
- clear_property(owner, '_archive_cached')
+ del IPropertyCache(owner).archive
new_archive = Archive(
owner=owner, distribution=distribution, name=name,