launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #30276
[Merge] ~cjwatson/launchpad:stormify-productrelease into launchpad:master
Colin Watson has proposed merging ~cjwatson/launchpad:stormify-productrelease into launchpad:master.
Commit message:
Convert ProductRelease and ProductReleaseFile to Storm
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/447211
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:stormify-productrelease into launchpad:master.
diff --git a/lib/lp/registry/model/productrelease.py b/lib/lp/registry/model/productrelease.py
index aec8786..a73d29a 100644
--- a/lib/lp/registry/model/productrelease.py
+++ b/lib/lp/registry/model/productrelease.py
@@ -9,10 +9,15 @@ __all__ = [
]
import os
+from datetime import timezone
from io import BufferedIOBase, BytesIO
+from operator import itemgetter
-from storm.expr import And, Desc
-from storm.store import EmptyResultSet
+from storm.expr import And, Desc, Join, LeftJoin
+from storm.info import ClassAlias
+from storm.properties import DateTime, Int, Unicode
+from storm.references import Reference, ReferenceSet
+from storm.store import EmptyResultSet, Store
from zope.component import getUtility
from zope.interface import implementer
@@ -30,16 +35,12 @@ from lp.registry.interfaces.productrelease import (
UpstreamFileType,
)
from lp.services.database.constants import UTC_NOW
-from lp.services.database.datetimecol import UtcDateTimeCol
+from lp.services.database.decoratedresultset import DecoratedResultSet
from lp.services.database.enumcol import DBEnum
from lp.services.database.interfaces import IStore
-from lp.services.database.sqlbase import SQLBase, sqlvalues
-from lp.services.database.sqlobject import (
- ForeignKey,
- SQLMultipleJoin,
- StringCol,
-)
+from lp.services.database.stormbase import StormBase
from lp.services.librarian.interfaces import ILibraryFileAliasSet
+from lp.services.librarian.model import LibraryFileAlias, LibraryFileContent
from lp.services.propertycache import cachedproperty
from lp.services.webapp.publisher import (
get_raw_form_value_from_current_request,
@@ -47,33 +48,51 @@ from lp.services.webapp.publisher import (
@implementer(IProductRelease)
-class ProductRelease(SQLBase):
+class ProductRelease(StormBase):
"""A release of a product."""
- _table = "ProductRelease"
- _defaultOrder = ["-datereleased"]
+ __storm_table__ = "ProductRelease"
+ __storm_order__ = ("-datereleased",)
- datereleased = UtcDateTimeCol(notNull=True)
- release_notes = StringCol(notNull=False, default=None)
- changelog = StringCol(notNull=False, default=None)
- datecreated = UtcDateTimeCol(
- dbName="datecreated", notNull=True, default=UTC_NOW
+ id = Int(primary=True)
+ datereleased = DateTime(allow_none=False, tzinfo=timezone.utc)
+ release_notes = Unicode(allow_none=True, default=None)
+ changelog = Unicode(allow_none=True, default=None)
+ datecreated = DateTime(
+ name="datecreated",
+ allow_none=False,
+ default=UTC_NOW,
+ tzinfo=timezone.utc,
)
- owner = ForeignKey(
- dbName="owner",
- foreignKey="Person",
- storm_validator=validate_person,
- notNull=True,
- )
- milestone = ForeignKey(dbName="milestone", foreignKey="Milestone")
-
- _files = SQLMultipleJoin(
- "ProductReleaseFile",
- joinColumn="productrelease",
- orderBy="-date_uploaded",
- prejoins=["productrelease"],
+ owner_id = Int(name="owner", validator=validate_person, allow_none=False)
+ owner = Reference(owner_id, "Person.id")
+ milestone_id = Int(name="milestone", allow_none=False)
+ milestone = Reference(milestone_id, "Milestone.id")
+
+ _files = ReferenceSet(
+ "id",
+ "ProductReleaseFile.productrelease_id",
+ order_by=Desc("ProductReleaseFile.date_uploaded"),
)
+ def __init__(
+ self,
+ datereleased,
+ owner,
+ milestone,
+ release_notes=None,
+ changelog=None,
+ ):
+ super().__init__()
+ self.owner = owner
+ self.milestone = milestone
+ self.datereleased = datereleased
+ self.release_notes = release_notes
+ self.changelog = changelog
+
+ # This is cached so that
+ # lp.registry.model.product.get_precached_products can populate the
+ # cache from a bulk query.
@cachedproperty
def files(self):
return self._files
@@ -119,7 +138,7 @@ class ProductRelease(SQLBase):
"You can't delete a product release which has files associated "
"with it."
)
- SQLBase.destroySelf(self)
+ Store.of(self).remove(self)
def _getFileObjectAndSize(self, file_or_data):
"""Return an object and length for file_or_data.
@@ -232,20 +251,21 @@ class ProductRelease(SQLBase):
@implementer(IProductReleaseFile)
-class ProductReleaseFile(SQLBase):
+class ProductReleaseFile(StormBase):
"""A file of a product release."""
- _table = "ProductReleaseFile"
+ __storm_table__ = "ProductReleaseFile"
- productrelease = ForeignKey(
- dbName="productrelease", foreignKey="ProductRelease", notNull=True
- )
+ id = Int(primary=True)
- libraryfile = ForeignKey(
- dbName="libraryfile", foreignKey="LibraryFileAlias", notNull=True
- )
+ productrelease_id = Int(name="productrelease", allow_none=False)
+ productrelease = Reference(productrelease_id, "ProductRelease.id")
- signature = ForeignKey(dbName="signature", foreignKey="LibraryFileAlias")
+ libraryfile_id = Int(name="libraryfile", allow_none=False)
+ libraryfile = Reference(libraryfile_id, "LibraryFileAlias.id")
+
+ signature_id = Int(name="signature", allow_none=True)
+ signature = Reference(signature_id, "LibraryFileAlias.id")
filetype = DBEnum(
name="filetype",
@@ -254,16 +274,37 @@ class ProductReleaseFile(SQLBase):
default=UpstreamFileType.CODETARBALL,
)
- description = StringCol(notNull=False, default=None)
+ description = Unicode(name="description", allow_none=True, default=None)
+
+ uploader_id = Int(
+ name="uploader", validator=validate_public_person, allow_none=False
+ )
+ uploader = Reference(uploader_id, "Person.id")
- uploader = ForeignKey(
- dbName="uploader",
- foreignKey="Person",
- storm_validator=validate_public_person,
- notNull=True,
+ date_uploaded = DateTime(
+ allow_none=False, default=UTC_NOW, tzinfo=timezone.utc
)
- date_uploaded = UtcDateTimeCol(notNull=True, default=UTC_NOW)
+ def __init__(
+ self,
+ productrelease,
+ libraryfile,
+ filetype,
+ uploader,
+ signature=None,
+ description=None,
+ ):
+ super().__init__()
+ self.productrelease = productrelease
+ self.libraryfile = libraryfile
+ self.filetype = filetype
+ self.uploader = uploader
+ self.signature = signature
+ self.description = description
+
+ def destroySelf(self):
+ """See `IProductReleaseFile`."""
+ Store.of(self).remove(self)
@implementer(IProductReleaseSet)
@@ -313,16 +354,42 @@ class ProductReleaseSet:
releases = list(releases)
if len(releases) == 0:
return EmptyResultSet()
- return ProductReleaseFile.select(
- """ProductReleaseFile.productrelease IN %s"""
- % (sqlvalues([release.id for release in releases])),
- orderBy="-date_uploaded",
- prejoins=[
- "libraryfile",
- "libraryfile.content",
- "productrelease",
- "signature",
- ],
+ SignatureAlias = ClassAlias(LibraryFileAlias)
+ return DecoratedResultSet(
+ IStore(ProductReleaseFile)
+ .using(
+ ProductReleaseFile,
+ Join(
+ LibraryFileAlias,
+ ProductReleaseFile.libraryfile_id == LibraryFileAlias.id,
+ ),
+ LeftJoin(
+ LibraryFileContent,
+ LibraryFileAlias.contentID == LibraryFileContent.id,
+ ),
+ Join(
+ ProductRelease,
+ ProductReleaseFile.productrelease_id == ProductRelease.id,
+ ),
+ LeftJoin(
+ SignatureAlias,
+ ProductReleaseFile.signature_id == SignatureAlias.id,
+ ),
+ )
+ .find(
+ (
+ ProductReleaseFile,
+ LibraryFileAlias,
+ LibraryFileContent,
+ ProductRelease,
+ SignatureAlias,
+ ),
+ ProductReleaseFile.productrelease_id.is_in(
+ [release.id for release in releases]
+ ),
+ )
+ .order_by(Desc(ProductReleaseFile.date_uploaded)),
+ result_decorator=itemgetter(0),
)
diff --git a/lib/lp/registry/model/productseries.py b/lib/lp/registry/model/productseries.py
index 10d30ea..0bd1479 100644
--- a/lib/lp/registry/model/productseries.py
+++ b/lib/lp/registry/model/productseries.py
@@ -10,6 +10,7 @@ __all__ = [
]
import datetime
+from operator import itemgetter
from lazr.delegates import delegate_to
from storm.expr import Max, Sum
@@ -222,17 +223,13 @@ class ProductSeries(
# The Milestone is cached too because most uses of a ProductRelease
# need it. The decorated resultset returns just the ProductRelease.
- def decorate(row):
- product_release, milestone = row
- return product_release
-
result = store.find(
(ProductRelease, Milestone),
Milestone.productseries == self,
ProductRelease.milestone == Milestone.id,
)
result = result.order_by(Desc("datereleased"))
- return DecoratedResultSet(result, decorate)
+ return DecoratedResultSet(result, result_decorator=itemgetter(0))
@cachedproperty
def _cached_releases(self):
diff --git a/lib/lp/registry/scripts/productreleasefinder/finder.py b/lib/lp/registry/scripts/productreleasefinder/finder.py
index 6126fb0..107a570 100644
--- a/lib/lp/registry/scripts/productreleasefinder/finder.py
+++ b/lib/lp/registry/scripts/productreleasefinder/finder.py
@@ -172,9 +172,9 @@ class ProductReleaseFinder:
Product.name == product_name,
Product.id == ProductSeries.productID,
Milestone.productseriesID == ProductSeries.id,
- ProductRelease.milestoneID == Milestone.id,
- ProductReleaseFile.productreleaseID == ProductRelease.id,
- LibraryFileAlias.id == ProductReleaseFile.libraryfileID,
+ ProductRelease.milestone_id == Milestone.id,
+ ProductReleaseFile.productrelease_id == ProductRelease.id,
+ LibraryFileAlias.id == ProductReleaseFile.libraryfile_id,
)
file_names = set(found_names)
return file_names
diff --git a/lib/lp/registry/vocabularies.py b/lib/lp/registry/vocabularies.py
index a9acabf..98333e0 100644
--- a/lib/lp/registry/vocabularies.py
+++ b/lib/lp/registry/vocabularies.py
@@ -1200,7 +1200,7 @@ class AllUserTeamsParticipationPlusSelfSimpleDisplayVocabulary(
@implementer(IHugeVocabulary)
-class ProductReleaseVocabulary(SQLObjectVocabularyBase):
+class ProductReleaseVocabulary(StormVocabularyBase):
"""All `IProductRelease` objects vocabulary."""
displayname = "Select a Product Release"
@@ -1210,8 +1210,12 @@ class ProductReleaseVocabulary(SQLObjectVocabularyBase):
# Sorting by version won't give the expected results, because it's just a
# text field. e.g. ["1.0", "2.0", "11.0"] would be sorted as ["1.0",
# "11.0", "2.0"].
- _orderBy = [Product.q.name, ProductSeries.q.name, Milestone.q.name]
- _clauseTables = ["Product", "ProductSeries"]
+ _order_by = [Product.name, ProductSeries.name, Milestone.name]
+ _clauses = [
+ ProductRelease.milestone_id == Milestone.id,
+ Milestone.productseriesID == ProductSeries.id,
+ ProductSeries.productID == Product.id,
+ ]
def toTerm(self, obj):
"""See `IVocabulary`."""
@@ -1240,14 +1244,17 @@ class ProductReleaseVocabulary(SQLObjectVocabularyBase):
except ValueError:
raise LookupError(token)
- obj = ProductRelease.selectOne(
- AND(
- ProductRelease.q.milestoneID == Milestone.q.id,
- Milestone.q.productseriesID == ProductSeries.q.id,
- ProductSeries.q.productID == Product.q.id,
- Product.q.name == productname,
- ProductSeries.q.name == productseriesname,
+ obj = (
+ IStore(ProductRelease)
+ .find(
+ ProductRelease,
+ ProductRelease.milestone_id == Milestone.id,
+ Milestone.productseriesID == ProductSeries.id,
+ ProductSeries.productID == Product.id,
+ Product.name == productname,
+ ProductSeries.name == productseriesname,
)
+ .one()
)
try:
return self.toTerm(obj)
@@ -1259,22 +1266,22 @@ class ProductReleaseVocabulary(SQLObjectVocabularyBase):
if not query:
return self.emptySelectResults()
- query = six.ensure_text(query).lower()
- objs = self._table.select(
- AND(
- Milestone.q.id == ProductRelease.q.milestoneID,
- ProductSeries.q.id == Milestone.q.productseriesID,
- Product.q.id == ProductSeries.q.productID,
- OR(
- CONTAINSSTRING(Product.q.name, query),
- CONTAINSSTRING(ProductSeries.q.name, query),
+ query = query.lower()
+ return (
+ IStore(self._table)
+ .find(
+ self._table,
+ ProductRelease.milestone_id == Milestone.id,
+ Milestone.productseriesID == ProductSeries.id,
+ ProductSeries.productID == Product.id,
+ Or(
+ Product.name.contains_string(query),
+ ProductSeries.name.contains_string(query),
),
- ),
- orderBy=self._orderBy,
+ )
+ .order_by(self._order_by)
)
- return objs
-
@implementer(IHugeVocabulary)
class ProductSeriesVocabulary(SQLObjectVocabularyBase):
diff --git a/lib/lp/translations/doc/poimport-pofile-not-exported-from-rosetta.rst b/lib/lp/translations/doc/poimport-pofile-not-exported-from-rosetta.rst
index a683b72..b0bf780 100644
--- a/lib/lp/translations/doc/poimport-pofile-not-exported-from-rosetta.rst
+++ b/lib/lp/translations/doc/poimport-pofile-not-exported-from-rosetta.rst
@@ -22,6 +22,7 @@ Here are some imports we need to get this test running.
>>> from lp.app.interfaces.launchpad import ILaunchpadCelebrities
>>> from lp.registry.interfaces.person import IPersonSet
+ >>> from lp.services.database.interfaces import IStore
>>> from lp.translations.enums import RosettaImportStatus
>>> from lp.translations.interfaces.translationimportqueue import (
... ITranslationImportQueue,
@@ -46,7 +47,7 @@ Here's the person who'll be doing the import.
Now, is time to create the new potemplate
>>> from lp.registry.model.productrelease import ProductRelease
- >>> release = ProductRelease.get(3)
+ >>> release = IStore(ProductRelease).get(ProductRelease, 3)
>>> print(release.milestone.productseries.product.name)
firefox
>>> series = release.milestone.productseries
@@ -140,10 +141,9 @@ We should also be sure that we don't block any import that is coming from
upstream. That kind of import is not blocked if they lack the
'X-Rosetta-Export-Date' header.
-We need to fetch again some SQLObjects because we did a transaction
-commit.
+We need to fetch some rows again because we committed a transaction.
- >>> release = ProductRelease.get(3)
+ >>> release = IStore(ProductRelease).get(ProductRelease, 3)
>>> series = release.milestone.productseries
>>> subset = POTemplateSubset(productseries=series)
>>> potemplate = subset.getPOTemplateByName("firefox")
diff --git a/lib/lp/translations/doc/poimport-pofile-old-po-imported.rst b/lib/lp/translations/doc/poimport-pofile-old-po-imported.rst
index 4ee040a..9b077c8 100644
--- a/lib/lp/translations/doc/poimport-pofile-old-po-imported.rst
+++ b/lib/lp/translations/doc/poimport-pofile-old-po-imported.rst
@@ -20,6 +20,7 @@ Here are some imports we need to get this test running.
>>> from lp.app.interfaces.launchpad import ILaunchpadCelebrities
>>> from lp.registry.interfaces.person import IPersonSet
+ >>> from lp.services.database.interfaces import IStore
>>> from lp.translations.interfaces.translationimportqueue import (
... ITranslationImportQueue,
... )
@@ -44,7 +45,7 @@ Here's the person who'll be doing the import.
Now it's time to create the new potemplate
>>> from lp.registry.model.productrelease import ProductRelease
- >>> release = ProductRelease.get(3)
+ >>> release = IStore(ProductRelease).get(ProductRelease, 3)
>>> print(release.milestone.productseries.product.name)
firefox
>>> series = release.milestone.productseries
diff --git a/lib/lp/translations/doc/poimport-pofile-syntax-error.rst b/lib/lp/translations/doc/poimport-pofile-syntax-error.rst
index e2f7c91..30be238 100644
--- a/lib/lp/translations/doc/poimport-pofile-syntax-error.rst
+++ b/lib/lp/translations/doc/poimport-pofile-syntax-error.rst
@@ -10,6 +10,7 @@ Here are some imports we need to get this test running.
>>> from lp.app.interfaces.launchpad import ILaunchpadCelebrities
>>> from lp.registry.interfaces.person import IPersonSet
+ >>> from lp.services.database.interfaces import IStore
>>> from lp.translations.interfaces.translationimportqueue import (
... ITranslationImportQueue,
... )
@@ -38,7 +39,7 @@ Here's the person who'll be doing the import.
Now, is time to create the new potemplate
>>> from lp.registry.model.productrelease import ProductRelease
- >>> release = ProductRelease.get(3)
+ >>> release = IStore(ProductRelease).get(ProductRelease, 3)
>>> print(release.milestone.productseries.product.name)
firefox
diff --git a/lib/lp/translations/doc/poimport-potemplate-syntax-error.rst b/lib/lp/translations/doc/poimport-potemplate-syntax-error.rst
index c14673d..a45fa79 100644
--- a/lib/lp/translations/doc/poimport-potemplate-syntax-error.rst
+++ b/lib/lp/translations/doc/poimport-potemplate-syntax-error.rst
@@ -10,6 +10,7 @@ Here are some imports we need to get this test running.
>>> from lp.app.interfaces.launchpad import ILaunchpadCelebrities
>>> from lp.registry.interfaces.person import IPersonSet
+ >>> from lp.services.database.interfaces import IStore
>>> from lp.translations.interfaces.translationimportqueue import (
... ITranslationImportQueue,
... )
@@ -37,7 +38,7 @@ Here's the person who'll be doing the import.
Now, is time to create the new potemplate
>>> from lp.registry.model.productrelease import ProductRelease
- >>> release = ProductRelease.get(3)
+ >>> release = IStore(ProductRelease).get(ProductRelease, 3)
>>> print(release.milestone.productseries.product.name)
firefox
>>> series = release.milestone.productseries