launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #00751
lp:~mwhudson/launchpad/move-SpecificationDepCandidatesVocabulary into lp:launchpad/devel
Michael Hudson has proposed merging lp:~mwhudson/launchpad/move-SpecificationDepCandidatesVocabulary into lp:launchpad/devel.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Hi there,
As a prep for some linaro work on blueprints (bug 3552), I moved the implementation and tests for SpecificationDepCandidates vocabulary into the lp.blueprints tree.
I tidied up the test a very little bit and de-moined the doctest they came from. I didn't touch the implementation at all.
Cheers,
mwh
--
https://code.launchpad.net/~mwhudson/launchpad/move-SpecificationDepCandidatesVocabulary/+merge/33611
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~mwhudson/launchpad/move-SpecificationDepCandidatesVocabulary into lp:launchpad/devel.
=== modified file 'lib/canonical/launchpad/doc/vocabularies.txt'
--- lib/canonical/launchpad/doc/vocabularies.txt 2010-02-17 11:13:06 +0000
+++ lib/canonical/launchpad/doc/vocabularies.txt 2010-08-25 05:55:56 +0000
@@ -1,6 +1,8 @@
-= Vocabularies =
+Vocabularies
+============
-== Introduction ==
+Introduction
+------------
Vocabularies are lists of terms. In Launchpad's Component Architecture
(CA), a vocabulary is a list of terms that a widget (normally a selection
@@ -18,7 +20,8 @@
>>> launchbag.clear()
-=== Values, Tokens, and Titles ===
+Values, Tokens, and Titles
+..........................
In Launchpad, we generally use "tokenized vocabularies." Each term in
a vocabulary has a value, token and title. A term is rendered in a
@@ -31,7 +34,8 @@
to the user.
-== Launchpad Vocabularies ==
+Launchpad Vocabularies
+----------------------
There are two kinds of vocabularies in Launchpad: enumerable and
non-enumerable. Enumerable vocabularies are short enough to render in a
@@ -53,10 +57,12 @@
'Select a project'
-== Enumerable Vocabularies ==
-
-
-=== DistributionUsingMaloneVocabulary ===
+Enumerable Vocabularies
+-----------------------
+
+
+DistributionUsingMaloneVocabulary
+.................................
All the distributions that use Malone as their main bug tracker.
@@ -99,7 +105,8 @@
LookupError:...
-=== BugNominatableSeriesVocabulary ===
+BugNominatableSeriesVocabulary
+..............................
All the series that can be nominated for fixing.
@@ -244,7 +251,8 @@
NoSuchDistroSeries...
-=== ProjectProductsVocabularyUsingMalone ===
+ProjectProductsVocabularyUsingMalone
+....................................
All the products in a project using Malone.
@@ -262,14 +270,16 @@
firefox: Mozilla Firefox
-== Non-Enumerable Vocabularies ==
+Non-Enumerable Vocabularies
+---------------------------
Iterating over non-enumerable vocabularies, while possible, will
probably kill the database. Instead, these vocabularies are
search-driven.
-=== BinaryAndSourcePackageNameVocabulary ===
+BinaryAndSourcePackageNameVocabulary
+....................................
The list of binary and source package names, ordered by name.
@@ -315,7 +325,8 @@
('linux-source-2.6.15', u'Source of: linux-2.6.12')]
-=== BinaryPackageNameVocabulary ===
+BinaryPackageNameVocabulary
+...........................
All the binary packages in Launchpad.
@@ -331,7 +342,8 @@
('mozilla-firefox-data', u'Mozilla Firefox Data is .....')]
-=== SourcePackageNameVocabulary ===
+SourcePackageNameVocabulary
+...........................
All the source packages in Launchpad.
@@ -352,7 +364,8 @@
[('pmount', u'pmount')]
-=== BranchVocabulary ===
+BranchVocabulary
+................
The list of bzr branches registered in Launchpad.
@@ -432,7 +445,8 @@
>>> login('foo.bar@xxxxxxxxxxxxx')
-=== BranchRestrictedOnProduct ===
+BranchRestrictedOnProduct
+.........................
The BranchRestrictedOnProduct vocabulary restricts the result set to
those of the product of the context. Currently only two types of
@@ -461,7 +475,8 @@
BranchVocabulary with respect to the tokens and privacy awareness.
-=== HostedBranchRestrictedOnOwner ===
+HostedBranchRestrictedOnOwner
+.............................
Here's a vocabulary for all hosted branches owned by the current user.
@@ -486,7 +501,8 @@
~a-branching-user/product-two/hosted
-=== Processor ===
+Processor
+.........
All processors type available in Launchpad.
@@ -498,7 +514,8 @@
['386']
-=== BugWatchVocabulary ===
+BugWatchVocabulary
+..................
All bug watches associated with a bugtask's bug.
@@ -555,111 +572,8 @@
Lionel Richtea (mailto:<email address hidden>)
-== SpecificationDepCandidatesVocabulary ==
-
-All blueprints that can be added as a dependency of the
-context blueprint.
-
-First, we set up a product with three blueprints.
-
- >>> from canonical.launchpad.interfaces import (
- ... ISpecificationSet, SpecificationDefinitionStatus)
- >>> evolution = product_set.getByName('evolution')
- >>> foobar_person = person_set.getByName('name16')
- >>> foobar_person.displayname
- u'Foo Bar'
- >>> specset = getUtility(ISpecificationSet)
- >>> spec_a = specset.new('spec-a', 'Spec A',
- ... 'http://www.example.org/SpecA', 'The first spec',
- ... SpecificationDefinitionStatus.APPROVED, foobar_person,
- ... product=evolution)
- >>> spec_b = specset.new('spec-b', 'Spec B',
- ... 'http://www.example.org/SpecB', 'The second spec',
- ... SpecificationDefinitionStatus.APPROVED, foobar_person,
- ... product=evolution)
- >>> spec_c = specset.new('spec-c', 'Spec C',
- ... 'http://www.example.org/SpecC', 'The third spec',
- ... SpecificationDefinitionStatus.APPROVED, foobar_person,
- ... product=evolution)
- >>> sorted([spec.name for spec in evolution.specifications()])
- [u'spec-a', u'spec-b', u'spec-c']
-
-The dependency candidates for spec_a are all blueprints for evolution
-except for spec_a itself.
-
- >>> vocab = vocabulary_registry.get(
- ... spec_a, "SpecificationDepCandidates")
- >>> sorted([term.value.name for term in vocab])
- [u'spec-b', u'spec-c']
-
-Dependency candidate come only from the same product of the blueprint
-they depend on.
-
- >>> unrelated_spec = specset.new('unrelated-spec', 'Unrelated Spec',
- ... 'http://example.com/SpecU', 'A spec unrelated to Evolution',
- ... SpecificationDefinitionStatus.APPROVED, foobar_person,
- ... product=firefox)
- >>> vocab = vocabulary_registry.get(
- ... spec_a, "SpecificationDepCandidates")
- >>> unrelated_spec in vocab
- False
- >>> [term.value.product for term in vocab
- ... if term.value.product != evolution]
- []
-
-We mark spec_b as a dependency of spec_a and spec_c as a dependency
-of spec_b.
-
- >>> spec_a.createDependency(spec_b)
- <SpecificationDependency at ...>
- >>> [spec.name for spec in spec_a.dependencies]
- [u'spec-b']
-
- >>> spec_b.createDependency(spec_c)
- <SpecificationDependency at ...>
- >>> [spec.name for spec in spec_b.dependencies]
- [u'spec-c']
-
-No circular dependencies - the vocabulary excludes specifications that
-are a dependency of the context spec.
-
- >>> spec_a in spec_b.all_blocked
- True
- >>> spec_b in spec_c.all_blocked
- True
- >>> vocab = vocabulary_registry.get(
- ... spec_c, "SpecificationDepCandidates")
- >>> spec_a in [term.value for term in vocab]
- False
-
-This vocabulary provides the IHugeVocabulary interface.
-
- >>> from canonical.launchpad.webapp.testing import verifyObject
- >>> from canonical.launchpad.webapp.vocabulary import IHugeVocabulary
- >>> verifyObject(IHugeVocabulary, vocab)
- True
-
-The search() method returns specifications within the vocabulary
-that matches the search string. The string is matched against the name,
-or fallbacks to a full text search.
-
- >>> vocab = get_naked_vocab(spec_a, "SpecificationDepCandidates")
- >>> list(vocab.search('spec-b')) == [spec_b]
- True
- >>> list(vocab.search('third')) == [spec_c]
- True
-
-The search method uses the SQL `LIKE` operator, with the values quoted
-appropriately. Queries conataining regual expression operators, for
-example, will simply look for the respective characters within the
-vocabulary's item (this used to be the cause of an OOPS, see
-https://bugs.edge.launchpad.net/blueprint/+bug/139385 for more details).
-
- >>> list(vocab.search('*'))
- []
-
-
-=== PPA ===
+PPA
+...
The PPA vocabulary contains all the PPAs available in a particular
collection. It provides the IHugeVocabulary interface.
=== modified file 'lib/canonical/launchpad/vocabularies/configure.zcml'
--- lib/canonical/launchpad/vocabularies/configure.zcml 2010-06-21 04:08:54 +0000
+++ lib/canonical/launchpad/vocabularies/configure.zcml 2010-08-25 05:55:56 +0000
@@ -309,20 +309,6 @@
<allow interface="canonical.launchpad.webapp.vocabulary.IHugeVocabulary"/>
</class>
-
- <securedutility
- name="SpecificationDepCandidates"
- component="canonical.launchpad.vocabularies.SpecificationDepCandidatesVocabulary"
- provides="zope.schema.interfaces.IVocabularyFactory"
- >
- <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
- </securedutility>
-
- <class class="canonical.launchpad.vocabularies.SpecificationDepCandidatesVocabulary">
- <allow interface="canonical.launchpad.webapp.vocabulary.IHugeVocabulary"/>
- </class>
-
-
<securedutility
name="Sprint"
component="canonical.launchpad.vocabularies.SprintVocabulary"
=== modified file 'lib/lp/blueprints/configure.zcml'
--- lib/lp/blueprints/configure.zcml 2010-08-19 03:06:27 +0000
+++ lib/lp/blueprints/configure.zcml 2010-08-25 05:55:56 +0000
@@ -11,6 +11,7 @@
i18n_domain="launchpad">
<include package=".browser"/>
+ <include package=".vocabularies"/>
<publisher
name="blueprints"
=== added directory 'lib/lp/blueprints/vocabularies'
=== added file 'lib/lp/blueprints/vocabularies/__init__.py'
=== added file 'lib/lp/blueprints/vocabularies/configure.zcml'
--- lib/lp/blueprints/vocabularies/configure.zcml 1970-01-01 00:00:00 +0000
+++ lib/lp/blueprints/vocabularies/configure.zcml 2010-08-25 05:55:56 +0000
@@ -0,0 +1,19 @@
+<!-- Copyright 2009 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">
+
+ <securedutility
+ name="SpecificationDepCandidates"
+ component=".specificationdependency.SpecificationDepCandidatesVocabulary"
+ provides="zope.schema.interfaces.IVocabularyFactory"
+ >
+ <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
+ </securedutility>
+
+ <class class=".specificationdependency.SpecificationDepCandidatesVocabulary">
+ <allow interface="canonical.launchpad.webapp.vocabulary.IHugeVocabulary"/>
+ </class>
+
+</configure>
=== added file 'lib/lp/blueprints/vocabularies/specificationdependency.py'
--- lib/lp/blueprints/vocabularies/specificationdependency.py 1970-01-01 00:00:00 +0000
+++ lib/lp/blueprints/vocabularies/specificationdependency.py 2010-08-25 05:55:56 +0000
@@ -0,0 +1,98 @@
+# Copyright 2010 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""The vocabularies relating to dependencies of specifications."""
+
+__metaclass__ = type
+__all__ = ['SpecificationDepCandidatesVocabulary']
+
+from zope.interface import implements
+from zope.schema.vocabulary import SimpleTerm
+
+from canonical.database.sqlbase import quote_like
+from canonical.launchpad.helpers import shortlist
+from canonical.launchpad.webapp.vocabulary import (
+ CountableIterator,
+ IHugeVocabulary,
+ SQLObjectVocabularyBase,
+ )
+
+from lp.blueprints.interfaces.specification import SpecificationFilter
+from lp.blueprints.model.specification import Specification
+
+
+class SpecificationDepCandidatesVocabulary(SQLObjectVocabularyBase):
+ """Specifications that could be dependencies of this spec.
+
+ This includes only those specs that are not blocked by this spec
+ (directly or indirectly), unless they are already dependencies.
+
+ The current spec is not included.
+ """
+
+ implements(IHugeVocabulary)
+
+ _table = Specification
+ _orderBy = 'name'
+ displayname = 'Select a blueprint'
+
+ def _filter_specs(self, specs):
+ # XXX intellectronica 2007-07-05: is 100 a reasonable count before
+ # starting to warn?
+ speclist = shortlist(specs, 100)
+ return [spec for spec in speclist
+ if (spec != self.context and
+ spec.target == self.context.target
+ and spec not in self.context.all_blocked)]
+
+ def _doSearch(self, query):
+ """Return terms where query is in the text of name
+ or title, or matches the full text index.
+ """
+
+ if not query:
+ return []
+
+ quoted_query = quote_like(query)
+ sql_query = ("""
+ (Specification.name LIKE %s OR
+ Specification.title LIKE %s OR
+ fti @@ ftq(%s))
+ """
+ % (quoted_query, quoted_query, quoted_query))
+ all_specs = Specification.select(sql_query, orderBy=self._orderBy)
+
+ return self._filter_specs(all_specs)
+
+ def toTerm(self, obj):
+ return SimpleTerm(obj, obj.name, obj.title)
+
+ def getTermByToken(self, token):
+ search_results = self._doSearch(token)
+ for search_result in search_results:
+ if search_result.name == token:
+ return self.toTerm(search_result)
+ raise LookupError(token)
+
+ def search(self, query):
+ candidate_specs = self._doSearch(query)
+ return CountableIterator(len(candidate_specs),
+ candidate_specs)
+
+ def _all_specs(self):
+ all_specs = self.context.target.specifications(
+ filter=[SpecificationFilter.ALL],
+ prejoin_people=False)
+ return self._filter_specs(all_specs)
+
+ def __iter__(self):
+ return (self.toTerm(spec) for spec in self._all_specs())
+
+ def __contains__(self, obj):
+ # We don't use self._all_specs here, since it will call
+ # self._filter_specs(all_specs) which will cause all the specs
+ # to be loaded, whereas obj in all_specs will query a single object.
+ all_specs = self.context.target.specifications(
+ filter=[SpecificationFilter.ALL],
+ prejoin_people=False)
+ return obj in all_specs and len(self._filter_specs([obj])) > 0
=== added directory 'lib/lp/blueprints/vocabularies/tests'
=== added file 'lib/lp/blueprints/vocabularies/tests/__init__.py'
=== added file 'lib/lp/blueprints/vocabularies/tests/specificationdepcandidates.txt'
--- lib/lp/blueprints/vocabularies/tests/specificationdepcandidates.txt 1970-01-01 00:00:00 +0000
+++ lib/lp/blueprints/vocabularies/tests/specificationdepcandidates.txt 2010-08-25 05:55:56 +0000
@@ -0,0 +1,99 @@
+SpecificationDepCandidatesVocabulary
+====================================
+
+All blueprints that can be added as a dependency of the context
+blueprint.
+
+ >>> from zope.schema.vocabulary import getVocabularyRegistry
+ >>> vocabulary_registry = getVocabularyRegistry()
+
+First, we set up a product with three blueprints.
+
+ >>> specced_product = factory.makeProduct()
+ >>> spec_a = factory.makeSpecification(
+ ... name='spec-a', summary='The first spec',
+ ... product=specced_product)
+ >>> spec_b = factory.makeSpecification(
+ ... name='spec-b', summary='The second spec',
+ ... product=specced_product)
+ >>> spec_c = factory.makeSpecification(
+ ... name='spec-c', summary='The third spec',
+ ... product=specced_product)
+ >>> sorted([spec.name for spec in specced_product.specifications()])
+ [u'spec-a', u'spec-b', u'spec-c']
+
+The dependency candidates for spec_a are all blueprints for
+specced_product except for spec_a itself.
+
+ >>> vocab = vocabulary_registry.get(
+ ... spec_a, "SpecificationDepCandidates")
+ >>> sorted([term.value.name for term in vocab])
+ [u'spec-b', u'spec-c']
+
+Dependency candidate come only from the same product of the blueprint
+they depend on.
+
+ >>> unrelated_spec = factory.makeSpecification(
+ ... product=factory.makeProduct())
+ >>> vocab = vocabulary_registry.get(
+ ... spec_a, "SpecificationDepCandidates")
+ >>> unrelated_spec in vocab
+ False
+ >>> [term.value.product for term in vocab
+ ... if term.value.product != specced_product]
+ []
+
+We mark spec_b as a dependency of spec_a and spec_c as a dependency of
+spec_b.
+
+ >>> spec_a.createDependency(spec_b)
+ <SpecificationDependency at ...>
+ >>> [spec.name for spec in spec_a.dependencies]
+ [u'spec-b']
+
+ >>> spec_b.createDependency(spec_c)
+ <SpecificationDependency at ...>
+ >>> [spec.name for spec in spec_b.dependencies]
+ [u'spec-c']
+
+No circular dependencies - the vocabulary excludes specifications that
+are a dependency of the context spec.
+
+ >>> spec_a in spec_b.all_blocked
+ True
+ >>> spec_b in spec_c.all_blocked
+ True
+ >>> vocab = vocabulary_registry.get(
+ ... spec_c, "SpecificationDepCandidates")
+ >>> spec_a in [term.value for term in vocab]
+ False
+
+This vocabulary provides the IHugeVocabulary interface.
+
+ >>> from canonical.launchpad.webapp.testing import verifyObject
+ >>> from canonical.launchpad.webapp.vocabulary import IHugeVocabulary
+ >>> verifyObject(IHugeVocabulary, vocab)
+ True
+
+The search() method returns specifications within the vocabulary that
+matches the search string. The string is matched against the name, or
+fallbacks to a full text search.
+
+ >>> from zope.security.proxy import removeSecurityProxy
+ >>> naked_vocab = removeSecurityProxy(
+ ... vocabulary_registry.get(
+ ... spec_a, "SpecificationDepCandidates"))
+ >>> list(naked_vocab.search('spec-b')) == [spec_b]
+ True
+ >>> list(naked_vocab.search('third')) == [spec_c]
+ True
+
+The search method uses the SQL `LIKE` operator, with the values quoted
+appropriately. Queries conataining regual expression operators, for
+example, will simply look for the respective characters within the
+vocabulary's item (this used to be the cause of an OOPS, see
+https://bugs.edge.launchpad.net/blueprint/+bug/139385 for more
+details).
+
+ >>> list(naked_vocab.search('*'))
+ []
=== added file 'lib/lp/blueprints/vocabularies/tests/test_doc.py'
--- lib/lp/blueprints/vocabularies/tests/test_doc.py 1970-01-01 00:00:00 +0000
+++ lib/lp/blueprints/vocabularies/tests/test_doc.py 2010-08-25 05:55:56 +0000
@@ -0,0 +1,17 @@
+# Copyright 2010 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""
+Run the doctests.
+"""
+
+import os
+
+from lp.services.testing import build_doctest_suite
+
+
+here = os.path.dirname(os.path.realpath(__file__))
+
+
+def test_suite():
+ return build_doctest_suite(here, '')
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-08-22 18:31:30 +0000
+++ lib/lp/testing/factory.py 2010-08-25 05:55:56 +0000
@@ -439,7 +439,6 @@
registry_team.addMember(user, registry_team.teamowner)
return user
-
def makeCopyArchiveLocation(self, distribution=None, owner=None,
name=None, enabled=True):
"""Create and return a new arbitrary location for copy packages."""
@@ -1495,7 +1494,9 @@
mail.parsed_string = mail.as_string()
return mail
- def makeSpecification(self, product=None, title=None, distribution=None):
+ def makeSpecification(self, product=None, title=None, distribution=None,
+ name=None, summary=None,
+ status=SpecificationDefinitionStatus.NEW):
"""Create and return a new, arbitrary Blueprint.
:param product: The product to make the blueprint on. If one is
@@ -1503,14 +1504,18 @@
"""
if distribution is None and product is None:
product = self.makeProduct()
+ if name is None:
+ name = self.getUniqueString('name')
+ if summary is None:
+ summary = self.getUniqueString('summary')
if title is None:
title = self.getUniqueString('title')
return getUtility(ISpecificationSet).new(
- name=self.getUniqueString('name'),
+ name=name,
title=title,
specurl=None,
- summary=self.getUniqueString('summary'),
- definition_status=SpecificationDefinitionStatus.NEW,
+ summary=summary,
+ definition_status=status,
owner=self.makePerson(),
product=product,
distribution=distribution)
Follow ups