launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #30348
[Merge] ~cjwatson/launchpad:stormify-worlddata into launchpad:master
Colin Watson has proposed merging ~cjwatson/launchpad:stormify-worlddata into launchpad:master.
Commit message:
Convert lp.services.worlddata to Storm
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/448428
I tried to do this back in 2020, but at the time there was too much test fallout; it worked out to be easier to start with classes that were closer to being leaves in the data model. Now it's viable.
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:stormify-worlddata into launchpad:master.
diff --git a/lib/lp/answers/model/question.py b/lib/lp/answers/model/question.py
index 2665579..a561d0f 100644
--- a/lib/lp/answers/model/question.py
+++ b/lib/lp/answers/model/question.py
@@ -899,11 +899,9 @@ class QuestionSet:
def getQuestionLanguages(self):
"""See `IQuestionSet`"""
return set(
- Language.select(
- "Language.id = Question.language",
- clauseTables=["Question"],
- distinct=True,
- )
+ IStore(Language)
+ .find(Language, Question.language == Language.id)
+ .config(distinct=True)
)
def getMostActiveProjects(self, limit=5):
diff --git a/lib/lp/registry/model/distribution.py b/lib/lp/registry/model/distribution.py
index 1692c07..6e245b7 100644
--- a/lib/lp/registry/model/distribution.py
+++ b/lib/lp/registry/model/distribution.py
@@ -1167,9 +1167,8 @@ class Distribution(
)
if not mirrors and country is not None:
- continent = country.continent
query = And(
- Country.continentID == continent.id,
+ Country.continent == country.continent,
DistributionMirror.country == Country.id,
base_query,
)
diff --git a/lib/lp/registry/model/productseries.py b/lib/lp/registry/model/productseries.py
index 9f09770..1e424b8 100644
--- a/lib/lp/registry/model/productseries.py
+++ b/lib/lp/registry/model/productseries.py
@@ -510,7 +510,7 @@ class ProductSeries(
Language.id != english.id,
)
- ordered_results = query.order_by(["Language.englishname"])
+ ordered_results = query.order_by(Language.englishname)
for language, pofile in ordered_results:
psl = ProductSeriesLanguage(self, language, pofile=pofile)
@@ -554,7 +554,7 @@ class ProductSeries(
Language.id != english.id,
).group_by(Language)
- ordered_results = query.order_by(["Language.englishname"])
+ ordered_results = query.order_by(Language.englishname)
for (
language,
diff --git a/lib/lp/registry/model/projectgroup.py b/lib/lp/registry/model/projectgroup.py
index 428c718..1e91273 100644
--- a/lib/lp/registry/model/projectgroup.py
+++ b/lib/lp/registry/model/projectgroup.py
@@ -20,7 +20,7 @@ from lp.answers.enums import QUESTION_STATUS_DEFAULT_SEARCH
from lp.answers.interfaces.faqcollection import IFAQCollection
from lp.answers.interfaces.questioncollection import ISearchableByQuestionOwner
from lp.answers.model.faq import FAQ, FAQSearch
-from lp.answers.model.question import QuestionTargetSearch
+from lp.answers.model.question import Question, QuestionTargetSearch
from lp.app.enums import ServiceUsage
from lp.app.errors import NotFoundError
from lp.blueprints.enums import SprintSpecificationStatus
@@ -63,7 +63,7 @@ from lp.services.database.constants import UTC_NOW
from lp.services.database.datetimecol import UtcDateTimeCol
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.sqlbase import SQLBase
from lp.services.database.sqlobject import (
AND,
BoolCol,
@@ -367,15 +367,14 @@ class ProjectGroup(
def getQuestionLanguages(self):
"""See `IQuestionCollection`."""
return set(
- Language.select(
- """
- Language.id = Question.language AND
- Question.product = Product.id AND
- Product.project = %s"""
- % sqlvalues(self.id),
- clauseTables=["Question", "Product"],
- distinct=True,
+ IStore(Language)
+ .find(
+ Language,
+ Question.language == Language.id,
+ Question.product == Product.id,
+ Product.projectgroup == self.id,
)
+ .config(distinct=True)
)
@property
diff --git a/lib/lp/registry/templates/person-editlanguages.pt b/lib/lp/registry/templates/person-editlanguages.pt
index 72f346b..466122e 100644
--- a/lib/lp/registry/templates/person-editlanguages.pt
+++ b/lib/lp/registry/templates/person-editlanguages.pt
@@ -48,10 +48,10 @@
</dt>
<tal:languages define="languages country/languages">
<dd class="sprite language"
- tal:condition="languages"
+ tal:condition="not: languages/is_empty"
tal:repeat="lang country/languages"
tal:content="lang/englishname"/>
- <dd tal:condition="not: languages">
+ <dd tal:condition="languages/is_empty">
No languages are currently registered as being spoken in <span
tal:replace="country/name">country</span>. If you have this
information, please see if it has already been submitted in the
diff --git a/lib/lp/services/worlddata/model/country.py b/lib/lp/services/worlddata/model/country.py
index c02d2a9..e94438b 100644
--- a/lib/lp/services/worlddata/model/country.py
+++ b/lib/lp/services/worlddata/model/country.py
@@ -3,17 +3,13 @@
__all__ = ["Country", "CountrySet", "Continent"]
+from storm.locals import Int, Reference, ReferenceSet, Unicode
from zope.interface import implementer
from lp.app.errors import NotFoundError
from lp.services.database.constants import DEFAULT
from lp.services.database.interfaces import IStore
-from lp.services.database.sqlbase import SQLBase
-from lp.services.database.sqlobject import (
- ForeignKey,
- SQLRelatedJoin,
- StringCol,
-)
+from lp.services.database.stormbase import StormBase
from lp.services.worlddata.interfaces.country import (
IContinent,
ICountry,
@@ -22,28 +18,25 @@ from lp.services.worlddata.interfaces.country import (
@implementer(ICountry)
-class Country(SQLBase):
+class Country(StormBase):
"""A country."""
- _table = "Country"
+ __storm_table__ = "Country"
# default to listing newest first
- _defaultOrder = "name"
+ __storm_order__ = "name"
# db field names
- name = StringCol(dbName="name", unique=True, notNull=True)
- iso3166code2 = StringCol(dbName="iso3166code2", unique=True, notNull=True)
- iso3166code3 = StringCol(dbName="iso3166code3", unique=True, notNull=True)
- title = StringCol(dbName="title", notNull=False, default=DEFAULT)
- description = StringCol(dbName="description")
- continent = ForeignKey(
- dbName="continent", foreignKey="Continent", default=None
- )
- languages = SQLRelatedJoin(
- "Language",
- joinColumn="country",
- otherColumn="language",
- intermediateTable="SpokenIn",
+ id = Int(primary=True)
+ name = Unicode(name="name", allow_none=False)
+ iso3166code2 = Unicode(name="iso3166code2", allow_none=False)
+ iso3166code3 = Unicode(name="iso3166code3", allow_none=False)
+ title = Unicode(name="title", allow_none=True, default=DEFAULT)
+ description = Unicode(name="description")
+ continent_id = Int(name="continent", default=None)
+ continent = Reference(continent_id, "Continent.id")
+ languages = ReferenceSet(
+ id, "SpokenIn.country_id", "SpokenIn.language_id", "Language.id"
)
@@ -52,13 +45,15 @@ class CountrySet:
"""A set of countries"""
def __getitem__(self, iso3166code2):
- country = Country.selectOneBy(iso3166code2=iso3166code2)
+ country = (
+ IStore(Country).find(Country, iso3166code2=iso3166code2).one()
+ )
if country is None:
raise NotFoundError(iso3166code2)
return country
def __iter__(self):
- yield from Country.select()
+ yield from IStore(Country).find(Country)
def getByName(self, name):
"""See `ICountrySet`."""
@@ -74,11 +69,12 @@ class CountrySet:
@implementer(IContinent)
-class Continent(SQLBase):
+class Continent(StormBase):
"""See IContinent."""
- _table = "Continent"
- _defaultOrder = ["name", "id"]
+ __storm_table__ = "Continent"
+ __storm_order__ = ["name", "id"]
- name = StringCol(unique=True, notNull=True)
- code = StringCol(unique=True, notNull=True)
+ id = Int(primary=True)
+ name = Unicode(allow_none=False)
+ code = Unicode(allow_none=False)
diff --git a/lib/lp/services/worlddata/model/language.py b/lib/lp/services/worlddata/model/language.py
index c3cd90a..a0cb647 100644
--- a/lib/lp/services/worlddata/model/language.py
+++ b/lib/lp/services/worlddata/model/language.py
@@ -7,8 +7,8 @@ __all__ = [
"LanguageSet",
]
-import six
-from storm.expr import And, Count, Desc, Join, LeftJoin, Or
+from storm.expr import And, Count, Desc, Is, Join, LeftJoin, Or
+from storm.locals import Bool, Int, Unicode
from storm.references import ReferenceSet
from zope.interface import implementer
@@ -17,14 +17,7 @@ from lp.registry.model.karma import KarmaCache, KarmaCategory
from lp.services.database.decoratedresultset import DecoratedResultSet
from lp.services.database.enumcol import DBEnum
from lp.services.database.interfaces import IStandbyStore, IStore
-from lp.services.database.sqlbase import SQLBase
-from lp.services.database.sqlobject import (
- BoolCol,
- IntCol,
- SQLObjectNotFound,
- SQLRelatedJoin,
- StringCol,
-)
+from lp.services.database.stormbase import StormBase
from lp.services.propertycache import cachedproperty, get_property_cache
from lp.services.worlddata.interfaces.language import (
ILanguage,
@@ -34,16 +27,17 @@ from lp.services.worlddata.interfaces.language import (
@implementer(ILanguage)
-class Language(SQLBase):
- _table = "Language"
-
- code = StringCol(dbName="code", notNull=True, unique=True)
- uuid = StringCol(dbName="uuid", notNull=False, default=None)
- nativename = StringCol(dbName="nativename")
- englishname = StringCol(dbName="englishname")
- pluralforms = IntCol(dbName="pluralforms")
- pluralexpression = StringCol(dbName="pluralexpression")
- visible = BoolCol(dbName="visible", notNull=True)
+class Language(StormBase):
+ __storm_table__ = "Language"
+
+ id = Int(primary=True)
+ code = Unicode(name="code", allow_none=False)
+ uuid = Unicode(name="uuid", allow_none=True, default=None)
+ nativename = Unicode(name="nativename")
+ englishname = Unicode(name="englishname", allow_none=False)
+ pluralforms = Int(name="pluralforms")
+ pluralexpression = Unicode(name="pluralexpression")
+ visible = Bool(name="visible", allow_none=False)
direction = DBEnum(
name="direction",
allow_none=False,
@@ -52,19 +46,38 @@ class Language(SQLBase):
)
translation_teams = ReferenceSet(
- "<primary key>",
- "Translator.language_id",
- "Translator.translator_id",
- "Person.id",
+ "id", "Translator.language_id", "Translator.translator_id", "Person.id"
)
- _countries = SQLRelatedJoin(
- "Country",
- joinColumn="language",
- otherColumn="country",
- intermediateTable="SpokenIn",
+ _countries = ReferenceSet(
+ "id", "SpokenIn.language_id", "SpokenIn.country_id", "Country.id"
)
+ def __init__(
+ self,
+ code,
+ englishname,
+ nativename=None,
+ pluralforms=None,
+ pluralexpression=None,
+ visible=True,
+ direction=TextDirection.LTR,
+ ):
+ super().__init__()
+ self.code = code
+ self.englishname = englishname
+ self.nativename = nativename
+ self.pluralforms = pluralforms
+ self.pluralexpression = pluralexpression
+ self.visible = visible
+ self.direction = direction
+
+ def addCountry(self, country):
+ self._countries.add(country)
+
+ def removeCountry(self, country):
+ self._countries.remove(country)
+
# Define a read/write property `countries` so it can be passed
# to language administration `LaunchpadFormView`.
def _getCountries(self):
@@ -199,7 +212,11 @@ class LanguageSet:
@property
def _visible_languages(self):
- return Language.select("visible IS TRUE", orderBy="englishname")
+ return (
+ IStore(Language)
+ .find(Language, Is(Language.visible, True))
+ .order_by(Language.englishname)
+ )
@property
def common_languages(self):
@@ -220,7 +237,7 @@ class LanguageSet:
IStore(Language)
.find(
Language,
- Language.visible == True if only_visible else True,
+ Is(Language.visible, True) if only_visible else True,
)
.order_by(Language.englishname)
)
@@ -257,7 +274,9 @@ class LanguageSet:
def __iter__(self):
"""See `ILanguageSet`."""
- return iter(Language.select(orderBy="englishname"))
+ return iter(
+ IStore(Language).find(Language).order_by(Language.englishname)
+ )
def __getitem__(self, code):
"""See `ILanguageSet`."""
@@ -270,10 +289,7 @@ class LanguageSet:
def get(self, language_id):
"""See `ILanguageSet`."""
- try:
- return Language.get(language_id)
- except SQLObjectNotFound:
- return None
+ return IStore(Language).get(Language, language_id)
def getLanguageByCode(self, code):
"""See `ILanguageSet`."""
@@ -284,7 +300,7 @@ class LanguageSet:
def keys(self):
"""See `ILanguageSet`."""
- return [language.code for language in Language.select()]
+ return list(IStore(Language).find(Language.code))
def canonicalise_language_code(self, code):
"""See `ILanguageSet`."""
@@ -307,7 +323,7 @@ class LanguageSet:
direction=TextDirection.LTR,
):
"""See `ILanguageSet`."""
- return Language(
+ language = Language(
code=code,
englishname=englishname,
nativename=nativename,
@@ -316,11 +332,12 @@ class LanguageSet:
visible=visible,
direction=direction,
)
+ return IStore(Language).add(language)
def search(self, text):
"""See `ILanguageSet`."""
if text:
- text = six.ensure_text(text).lower()
+ text = text.lower()
results = (
IStandbyStore(Language)
.find(
diff --git a/lib/lp/services/worlddata/model/spokenin.py b/lib/lp/services/worlddata/model/spokenin.py
index 61d5dc9..26c426a 100644
--- a/lib/lp/services/worlddata/model/spokenin.py
+++ b/lib/lp/services/worlddata/model/spokenin.py
@@ -3,23 +3,24 @@
__all__ = ["SpokenIn"]
+from storm.locals import Int, Reference
from zope.interface import implementer
-from lp.services.database.sqlbase import SQLBase
-from lp.services.database.sqlobject import ForeignKey
+from lp.services.database.stormbase import StormBase
from lp.services.worlddata.interfaces.spokenin import ISpokenIn
@implementer(ISpokenIn)
-class SpokenIn(SQLBase):
+class SpokenIn(StormBase):
"""A way of telling which languages are spoken in which countries.
This table maps a language which is SpokenIn a country.
"""
- _table = "SpokenIn"
+ __storm_table__ = "SpokenIn"
- country = ForeignKey(dbName="country", notNull=True, foreignKey="Country")
- language = ForeignKey(
- dbName="language", notNull=True, foreignKey="Language"
- )
+ id = Int(primary=True)
+ country_id = Int(name="country", allow_none=False)
+ country = Reference(country_id, "Country.id")
+ language_id = Int(name="language", allow_none=False)
+ language = Reference(language_id, "Language.id")
diff --git a/lib/lp/services/worlddata/vocabularies.py b/lib/lp/services/worlddata/vocabularies.py
index 72db756..c20f49b 100644
--- a/lib/lp/services/worlddata/vocabularies.py
+++ b/lib/lp/services/worlddata/vocabularies.py
@@ -11,7 +11,7 @@ from zope.component import getUtility
from zope.interface import alsoProvides
from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
-from lp.services.webapp.vocabulary import SQLObjectVocabularyBase
+from lp.services.webapp.vocabulary import StormVocabularyBase
from lp.services.worlddata.interfaces.language import ILanguage, ILanguageSet
from lp.services.worlddata.interfaces.timezone import ITimezoneNameVocabulary
from lp.services.worlddata.model.country import Country
@@ -74,31 +74,32 @@ def TimezoneNameVocabulary(context=None):
return _timezone_vocab
-# Country.name may have non-ASCII characters, so we can't use
-# NamedSQLObjectVocabulary here.
-
-
-class CountryNameVocabulary(SQLObjectVocabularyBase):
+# Country.name is the English name of the country rather than an identifier,
+# so we don't use it as the token and thus don't use NamedStormVocabulary
+# here.
+class CountryNameVocabulary(StormVocabularyBase):
"""A vocabulary for country names."""
_table = Country
- _orderBy = "name"
+ _order_by = "name"
def toTerm(self, obj):
return SimpleTerm(obj, obj.id, obj.name)
-class LanguageVocabulary(SQLObjectVocabularyBase):
+class LanguageVocabulary(StormVocabularyBase):
"""All the languages known by Launchpad."""
_table = Language
- _orderBy = "englishname"
+ _order_by = "englishname"
def __contains__(self, language):
"""See `IVocabulary`."""
- assert ILanguage.providedBy(language), (
- "'in LanguageVocabulary' requires ILanguage as left operand, "
- "got %s instead." % type(language)
+ assert ILanguage.providedBy(
+ language
+ ), "'in %s' requires ILanguage as left operand, got %s instead." % (
+ self.__class__.__name__,
+ type(language),
)
return super().__contains__(language)
diff --git a/lib/lp/translations/model/pofile.py b/lib/lp/translations/model/pofile.py
index 594eac3..14626b1 100644
--- a/lib/lp/translations/model/pofile.py
+++ b/lib/lp/translations/model/pofile.py
@@ -866,7 +866,7 @@ class POFile(StormBase, POFileMixIn):
)
params = {
"potemplate": quote(self.potemplate.id),
- "language": quote(self.language),
+ "language": quote(self.language.id),
"flag": side_traits.flag_name,
"other_flag": side_traits.other_side_traits.flag_name,
"has_msgstrs": complete_plural_clause_this_side,
@@ -946,7 +946,7 @@ class POFile(StormBase, POFileMixIn):
]
)
params = {
- "language": quote(self.language),
+ "language": quote(self.language.id),
"potemplate": quote(self.potemplate.id),
"flag": flag_name,
"suggestion_nonempty": suggestion_nonempty,
@@ -1309,7 +1309,7 @@ class POFile(StormBase, POFileMixIn):
template_id = quote(self.potemplate.id)
params = {
"flag": flag_name,
- "language": quote(self.language),
+ "language": quote(self.language.id),
"template": template_id,
}
query = (
diff --git a/lib/lp/translations/templates/language-index.pt b/lib/lp/translations/templates/language-index.pt
index 96685b1..ef31980 100644
--- a/lib/lp/translations/templates/language-index.pt
+++ b/lib/lp/translations/templates/language-index.pt
@@ -88,7 +88,7 @@
<div class="yui-u">
<div class="portlet">
<h2>Countries</h2>
- <tal:has_countries condition="context/countries">
+ <tal:has_countries condition="not:context/countries/is_empty">
<p>
<tal:language replace="view/language_name">
Español
@@ -100,7 +100,7 @@
tal:content="country/name">Spain</li>
</ul>
</tal:has_countries>
- <tal:has_not_countries condition="not:context/countries">
+ <tal:has_not_countries condition="context/countries/is_empty">
<p class="helpwanted">
<tal:language replace="view/language_name">
Español
diff --git a/lib/lp/translations/utilities/translation_import.py b/lib/lp/translations/utilities/translation_import.py
index a27c53c..2749fae 100644
--- a/lib/lp/translations/utilities/translation_import.py
+++ b/lib/lp/translations/utilities/translation_import.py
@@ -143,7 +143,7 @@ class ExistingPOFileInDatabase:
substitutions = {
"translation_columns": ", ".join(translations),
"translation_joins": "\n".join(msgstr_joins),
- "language": quote(self.pofile.language),
+ "language": quote(self.pofile.language.id),
"potemplate": quote(self.pofile.potemplate.id),
"flag": self._getFlagName(),
}
diff --git a/lib/lp/translations/vocabularies.py b/lib/lp/translations/vocabularies.py
index fcf3aec..a564444 100644
--- a/lib/lp/translations/vocabularies.py
+++ b/lib/lp/translations/vocabularies.py
@@ -23,7 +23,7 @@ from lp.services.webapp.vocabulary import (
NamedStormVocabulary,
StormVocabularyBase,
)
-from lp.services.worlddata.interfaces.language import ILanguage
+from lp.services.worlddata.model.language import Language
from lp.services.worlddata.vocabularies import LanguageVocabulary
from lp.translations.enums import LanguagePackType
from lp.translations.model.languagepack import LanguagePack
@@ -40,39 +40,7 @@ class TranslatableLanguageVocabulary(LanguageVocabulary):
excluding English and non-visible languages.
"""
- def __contains__(self, language):
- """See `IVocabulary`.
-
- This vocabulary excludes English and languages that are not visible.
- """
- assert ILanguage.providedBy(language), (
- "'in TranslatableLanguageVocabulary' requires ILanguage as "
- "left operand, got %s instead." % type(language)
- )
- if language.code == "en":
- return False
- return language.visible == True and super().__contains__(language)
-
- def __iter__(self):
- """See `IVocabulary`.
-
- Iterate languages that are visible and not English.
- """
- languages = self._table.select(
- "Language.code != 'en' AND Language.visible = True",
- orderBy=self._orderBy,
- )
- for language in languages:
- yield self.toTerm(language)
-
- def getTermByToken(self, token):
- """See `IVocabulary`."""
- if token == "en":
- raise LookupError(token)
- term = super().getTermByToken(token)
- if not term.value.visible:
- raise LookupError(token)
- return term
+ _clauses = [Language.code != "en", Is(Language.visible, True)]
class TranslationGroupVocabulary(NamedStormVocabulary):