← Back to team overview

launchpad-reviewers team mailing list archive

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