launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #03817
[Merge] lp:~abentley/launchpad/ppa-api into lp:launchpad
Aaron Bentley has proposed merging lp:~abentley/launchpad/ppa-api into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #776444 in Launchpad itself: "Add external dependencies for PPA via API"
https://bugs.launchpad.net/launchpad/+bug/776444
Bug #776449 in Launchpad itself: "Set Ubuntu dependencies for PPA via API"
https://bugs.launchpad.net/launchpad/+bug/776449
For more details, see:
https://code.launchpad.net/~abentley/launchpad/ppa-api/+merge/63170
Summary
=======
Fix bug #776444 and #776449 about missing APIs
Proposed change
===============
Export APIs
Implementation Details
======================
Implemented _addArchiveDependency as a wrapper, because we do not want to export IComponent.
Also fixed the permissions of addArchiveDependency and removeArchiveDependency.
= Launchpad lint =
Checking for conflicts and issues in changed files.
Linting changed files:
lib/canonical/launchpad/interfaces/_schema_circular_imports.py
lib/lp/soyuz/browser/archive.py
lib/lp/soyuz/browser/tests/test_archive_webservice.py
lib/lp/soyuz/interfaces/archive.py
lib/lp/soyuz/model/archive.py
lib/lp/testing/factory.py
--
https://code.launchpad.net/~abentley/launchpad/ppa-api/+merge/63170
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~abentley/launchpad/ppa-api into lp:launchpad.
=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2011-05-17 14:27:34 +0000
+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2011-06-02 14:48:33 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Update the interface schema values due to circular imports.
@@ -226,9 +226,9 @@
IBranch['landing_candidates'].value_type.schema = IBranchMergeProposal
IBranch['landing_targets'].value_type.schema = IBranchMergeProposal
IBranch['linkBug'].queryTaggedValue(
- LAZR_WEBSERVICE_EXPORTED)['params']['bug'].schema= IBug
+ LAZR_WEBSERVICE_EXPORTED)['params']['bug'].schema = IBug
IBranch['linkSpecification'].queryTaggedValue(
- LAZR_WEBSERVICE_EXPORTED)['params']['spec'].schema= ISpecification
+ LAZR_WEBSERVICE_EXPORTED)['params']['spec'].schema = ISpecification
IBranch['product'].schema = IProduct
patch_plain_parameter_type(
@@ -243,9 +243,9 @@
LAZR_WEBSERVICE_EXPORTED)['return_type'].schema = IBranchSubscription
IBranch['subscriptions'].value_type.schema = IBranchSubscription
IBranch['unlinkBug'].queryTaggedValue(
- LAZR_WEBSERVICE_EXPORTED)['params']['bug'].schema= IBug
+ LAZR_WEBSERVICE_EXPORTED)['params']['bug'].schema = IBug
IBranch['unlinkSpecification'].queryTaggedValue(
- LAZR_WEBSERVICE_EXPORTED)['params']['spec'].schema= ISpecification
+ LAZR_WEBSERVICE_EXPORTED)['params']['spec'].schema = ISpecification
patch_entry_return_type(IBranch, '_createMergeProposal', IBranchMergeProposal)
patch_plain_parameter_type(
@@ -426,6 +426,14 @@
IArchive, 'getUploadersForPackageset', 'packageset', IPackageset)
patch_plain_parameter_type(
IArchive, 'deletePackagesetUploader', 'packageset', IPackageset)
+patch_plain_parameter_type(
+ IArchive, 'removeArchiveDependency', 'dependency', IArchive)
+patch_plain_parameter_type(
+ IArchive, '_addArchiveDependency', 'dependency', IArchive)
+patch_choice_parameter_type(
+ IArchive, '_addArchiveDependency', 'pocket', PackagePublishingPocket)
+patch_entry_return_type(
+ IArchive, '_addArchiveDependency', IArchiveDependency)
# IBuildFarmJob
=== modified file 'lib/lp/soyuz/browser/archive.py'
--- lib/lp/soyuz/browser/archive.py 2011-05-27 21:12:25 +0000
+++ lib/lp/soyuz/browser/archive.py 2011-06-02 14:48:33 +0000
@@ -33,7 +33,6 @@
datetime,
timedelta,
)
-from urlparse import urlparse
import pytz
from sqlobject import SQLObjectNotFound
@@ -142,6 +141,7 @@
IArchiveEditDependenciesForm,
IArchiveSet,
NoSuchPPA,
+ validate_external_dependencies,
)
from lp.soyuz.interfaces.archivepermission import IArchivePermissionSet
from lp.soyuz.interfaces.archivesubscriber import IArchiveSubscriberSet
@@ -2146,7 +2146,7 @@
# Check the external_dependencies field.
ext_deps = data.get('external_dependencies')
if ext_deps is not None:
- errors = self.validate_external_dependencies(ext_deps)
+ errors = validate_external_dependencies(ext_deps)
if len(errors) != 0:
error_text = "\n".join(errors)
self.setFieldError('external_dependencies', error_text)
@@ -2156,31 +2156,6 @@
'commercial',
'Can only set commericial for private archives.')
- def validate_external_dependencies(self, ext_deps):
- """Validate the external_dependencies field.
-
- :param ext_deps: The dependencies form field to check.
- """
- errors = []
- # The field can consist of multiple entries separated by
- # newlines, so process each in turn.
- for dep in ext_deps.splitlines():
- try:
- deb, url, suite, components = dep.split(" ", 3)
- except ValueError:
- errors.append(
- "'%s' is not a complete and valid sources.list entry"
- % dep)
- continue
-
- if deb != "deb":
- errors.append("%s: Must start with 'deb'" % dep)
- url_components = urlparse(url)
- if not url_components[0] or not url_components[1]:
- errors.append("%s: Invalid URL" % dep)
-
- return errors
-
@property
def owner_is_private_team(self):
"""Is the owner a private team?
=== modified file 'lib/lp/soyuz/browser/tests/test_archive_webservice.py'
--- lib/lp/soyuz/browser/tests/test_archive_webservice.py 2010-10-26 15:47:24 +0000
+++ lib/lp/soyuz/browser/tests/test_archive_webservice.py 2011-06-02 14:48:33 +0000
@@ -1,19 +1,30 @@
-# Copyright 2010 Canonical Ltd. This software is licensed under the
+# Copyright 2010-2011 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
import unittest
-from lazr.restfulclient.errors import HTTPError
+from lazr.restfulclient.errors import (
+ BadRequest,
+ HTTPError,
+ Unauthorized as LRUnauthorized,
+)
+from testtools import ExpectedException
+import transaction
+from zope.component import getUtility
from canonical.launchpad.testing.pages import LaunchpadWebServiceCaller
from canonical.testing.layers import DatabaseFunctionalLayer
+from lp.app.interfaces.launchpad import ILaunchpadCelebrities
+from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.soyuz.enums import ArchivePurpose
from lp.testing import (
celebrity_logged_in,
launchpadlib_for,
+ person_logged_in,
TestCaseWithFactory,
+ WebServiceTestCase,
)
@@ -67,5 +78,111 @@
"in the 'DEVELOPMENT' state.", e.content)
+class TestExternalDependencies(WebServiceTestCase):
+
+ def test_external_dependencies_random_user(self):
+ """Normal users can look but not touch."""
+ archive = self.factory.makeArchive()
+ transaction.commit()
+ ws_archive = self.wsObject(archive)
+ self.assertIs(None, ws_archive.external_dependencies)
+ ws_archive.external_dependencies = "random"
+ with ExpectedException(LRUnauthorized, '.*'):
+ ws_archive.lp_save()
+
+ def test_external_dependencies_owner(self):
+ """Normal archive owners can look but not touch."""
+ archive = self.factory.makeArchive()
+ transaction.commit()
+ ws_archive = self.wsObject(archive, archive.owner)
+ self.assertIs(None, ws_archive.external_dependencies)
+ ws_archive.external_dependencies = "random"
+ with ExpectedException(LRUnauthorized, '.*'):
+ ws_archive.lp_save()
+
+ def test_external_dependencies_commercial_owner_invalid(self):
+ """Commercial admins can look and touch."""
+ commercial = getUtility(ILaunchpadCelebrities).commercial_admin
+ owner = self.factory.makePerson(member_of=[commercial])
+ archive = self.factory.makeArchive(owner=owner)
+ transaction.commit()
+ ws_archive = self.wsObject(archive, archive.owner)
+ self.assertIs(None, ws_archive.external_dependencies)
+ ws_archive.external_dependencies = "random"
+ regex = '(\n|.)*Invalid external dependencies(\n|.)*'
+ with ExpectedException(BadRequest, regex):
+ ws_archive.lp_save()
+
+ def test_external_dependencies_commercial_owner_valid(self):
+ """Commercial admins can look and touch."""
+ commercial = getUtility(ILaunchpadCelebrities).commercial_admin
+ owner = self.factory.makePerson(member_of=[commercial])
+ archive = self.factory.makeArchive(owner=owner)
+ transaction.commit()
+ ws_archive = self.wsObject(archive, archive.owner)
+ self.assertIs(None, ws_archive.external_dependencies)
+ ws_archive.external_dependencies = (
+ "deb http://example.org suite components")
+ ws_archive.lp_save()
+
+
+class TestArchiveDependencies(WebServiceTestCase):
+
+ def test_addArchiveDependency_random_user(self):
+ """Normal users cannot add archive dependencies."""
+ archive = self.factory.makeArchive()
+ dependency = self.factory.makeArchive()
+ transaction.commit()
+ ws_archive = self.wsObject(archive)
+ ws_dependency = self.wsObject(dependency)
+ self.assertContentEqual([], ws_archive.dependencies)
+ failure_regex = '(.|\n)*addArchiveDependency.*launchpad.Edit(.|\n)*'
+ with ExpectedException(LRUnauthorized, failure_regex):
+ dependency = ws_archive.addArchiveDependency(
+ dependency=ws_dependency, pocket='Release', component='main')
+
+ def test_addArchiveDependency_owner(self):
+ """Normal users cannot add archive dependencies."""
+ archive = self.factory.makeArchive()
+ dependency = self.factory.makeArchive()
+ transaction.commit()
+ ws_archive = self.wsObject(archive, archive.owner)
+ ws_dependency = self.wsObject(dependency)
+ self.assertContentEqual([], ws_archive.dependencies)
+ with ExpectedException(BadRequest, '(.|\n)*asdf(.|\n)*'):
+ ws_archive.addArchiveDependency(
+ dependency=ws_dependency, pocket='Release', component='asdf')
+ dependency = ws_archive.addArchiveDependency(
+ dependency=ws_dependency, pocket='Release', component='main')
+ self.assertContentEqual([dependency], ws_archive.dependencies)
+
+ def test_removeArchiveDependency_random_user(self):
+ """Normal users can remove archive dependencies."""
+ archive = self.factory.makeArchive()
+ dependency = self.factory.makeArchive()
+ with person_logged_in(archive.owner):
+ archive.addArchiveDependency(
+ dependency, PackagePublishingPocket.RELEASE)
+ transaction.commit()
+ ws_archive = self.wsObject(archive)
+ ws_dependency = self.wsObject(dependency)
+ failure_regex = '(.|\n)*remove.*Dependency.*launchpad.Edit(.|\n)*'
+ with ExpectedException(LRUnauthorized, failure_regex):
+ ws_archive.removeArchiveDependency(dependency=ws_dependency)
+
+ def test_removeArchiveDependency_owner(self):
+ """Normal users can remove archive dependencies."""
+ archive = self.factory.makeArchive()
+ dependency = self.factory.makeArchive()
+ with person_logged_in(archive.owner):
+ archive.addArchiveDependency(
+ dependency, PackagePublishingPocket.RELEASE)
+ transaction.commit()
+ ws_archive = self.wsObject(archive, archive.owner)
+ ws_dependency = self.wsObject(dependency)
+ ws_archive.removeArchiveDependency(dependency=ws_dependency)
+ self.assertContentEqual([], ws_archive.dependencies)
+
+
def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__)
=== modified file 'lib/lp/soyuz/interfaces/archive.py'
--- lib/lp/soyuz/interfaces/archive.py 2011-05-19 04:50:33 +0000
+++ lib/lp/soyuz/interfaces/archive.py 2011-06-02 14:48:33 +0000
@@ -32,6 +32,7 @@
'IDistributionArchive',
'InsufficientUploadRights',
'InvalidComponent',
+ 'InvalidExternalDependencies',
'InvalidPocketForPartnerArchive',
'InvalidPocketForPPA',
'IPPA',
@@ -43,8 +44,12 @@
'PocketNotFound',
'VersionRequiresName',
'default_name_by_purpose',
+ 'validate_external_dependencies',
]
+
+from urlparse import urlparse
+
from lazr.enum import DBEnumeratedType
from lazr.restful.declarations import (
call_with,
@@ -54,6 +59,7 @@
export_read_operation,
export_write_operation,
exported,
+ operation_for_version,
operation_parameters,
operation_returns_collection_of,
operation_returns_entry,
@@ -114,59 +120,59 @@
class CannotCopy(Exception):
"""Exception raised when a copy cannot be performed."""
- webservice_error(400) #Bad request.
+ webservice_error(400) # Bad request.
class CannotSwitchPrivacy(Exception):
"""Raised when switching the privacy of an archive that has
publishing records."""
- webservice_error(400) # Bad request.
+ webservice_error(400) # Bad request.
class PocketNotFound(Exception):
"""Invalid pocket."""
- webservice_error(400) #Bad request.
+ webservice_error(400) # Bad request.
class DistroSeriesNotFound(Exception):
"""Invalid distroseries."""
- webservice_error(400) #Bad request.
+ webservice_error(400) # Bad request.
class AlreadySubscribed(Exception):
"""Raised when creating a subscription for a subscribed person."""
- webservice_error(400) # Bad request.
+ webservice_error(400) # Bad request.
class ArchiveNotPrivate(Exception):
"""Raised when creating an archive subscription for a public archive."""
- webservice_error(400) # Bad request.
+ webservice_error(400) # Bad request.
class NoTokensForTeams(Exception):
"""Raised when creating a token for a team, rather than a person."""
- webservice_error(400) # Bad request.
+ webservice_error(400) # Bad request.
class ComponentNotFound(Exception):
"""Invalid source name."""
- webservice_error(400) #Bad request.
+ webservice_error(400) # Bad request.
class InvalidComponent(Exception):
"""Invalid component name."""
- webservice_error(400) #Bad request.
+ webservice_error(400) # Bad request.
class NoSuchPPA(NameLookupFailed):
"""Raised when we try to look up an PPA that doesn't exist."""
- webservice_error(400) #Bad request.
+ webservice_error(400) # Bad request.
_message_prefix = "No such ppa"
class VersionRequiresName(Exception):
"""Raised on some queries when version is specified but name is not."""
- webservice_error(400) # Bad request.
+ webservice_error(400) # Bad request.
class CannotRestrictArchitectures(Exception):
@@ -175,7 +181,7 @@
class CannotUploadToArchive(Exception):
"""A reason for not being able to upload to an archive."""
- webservice_error(403) # Forbidden.
+ webservice_error(403) # Forbidden.
_fmt = '%(person)s has no upload rights to %(archive)s.'
@@ -192,7 +198,7 @@
class CannotUploadToPocket(Exception):
"""Returned when a pocket is closed for uploads."""
- webservice_error(403) # Forbidden.
+ webservice_error(403) # Forbidden.
def __init__(self, distroseries, pocket):
Exception.__init__(self,
@@ -248,6 +254,17 @@
CannotUploadToArchive.__init__(self, archive_name=archive_name)
+class InvalidExternalDependencies(Exception):
+ """Tried to set external dependencies to an invalid value."""
+
+ webservice_error(400) # Bad request.
+
+ def __init__(self, errors):
+ error_msg = 'Invalid external dependencies:\n%s\n' % '\n'.join(errors)
+ Exception.__init__(self, error_msg)
+ self.errors = errors
+
+
class IArchivePublic(IHasOwner, IPrivacy):
"""An Archive interface for publicly available operations."""
id = Attribute("The archive ID.")
@@ -332,7 +349,7 @@
distribution = exported(
Reference(
- Interface, # Redefined to IDistribution later.
+ Interface, # Redefined to IDistribution later.
title=_("The distribution that uses or is used by this "
"archive.")))
@@ -412,9 +429,9 @@
"A delta to apply to all build scores for the archive. Builds "
"with a higher score will build sooner."))
- external_dependencies = Text(
- title=_("External dependencies"), required=False, readonly=False,
- description=_(
+ external_dependencies = exported(
+ Text(title=_("External dependencies"), required=False,
+ readonly=False, description=_(
"Newline-separated list of repositories to be used to retrieve "
"any external build dependencies when building packages in the "
"archive, in the format:\n"
@@ -422,7 +439,7 @@
"[components]\n"
"The series variable is replaced with the series name of the "
"context build.\n"
- "NOTE: This is for migration of OEM PPAs only!"))
+ "NOTE: This is for migration of OEM PPAs only!")))
enabled_restricted_families = CollectionField(
title=_("Enabled restricted families"),
@@ -520,27 +537,6 @@
records.
"""
- def removeArchiveDependency(dependency):
- """Remove the `IArchiveDependency` record for the given dependency.
-
- :param dependency: is an `IArchive` object.
- """
-
- def addArchiveDependency(dependency, pocket, component=None):
- """Record an archive dependency record for the context archive.
-
- :param dependency: is an `IArchive` object.
- :param pocket: is an `PackagePublishingPocket` enum.
- :param component: is an optional `IComponent` object, if not given
- the archive dependency will be tied to the component used
- for a corresponding source in primary archive.
-
- :raise: `ArchiveDependencyError` if given 'dependency' does not fit
- the context archive.
- :return: a `IArchiveDependency` object targeted to the context
- `IArchive` requiring 'dependency' `IArchive`.
- """
-
def getPermissions(person, item, perm_type):
"""Get the `IArchivePermission` record with the supplied details.
@@ -892,7 +888,8 @@
:return: True if the person is allowed to upload the source package.
"""
- num_pkgs_building = Attribute("Tuple of packages building and waiting to build")
+ num_pkgs_building = Attribute(
+ "Tuple of packages building and waiting to build")
def getSourcePackageReleases(build_status=None):
"""Return the releases for this archive.
@@ -944,7 +941,8 @@
dependencies = exported(
CollectionField(
title=_("Archive dependencies recorded for this archive."),
- value_type=Reference(schema=Interface), #Really IArchiveDependency
+ value_type=Reference(schema=Interface),
+ # Really IArchiveDependency
readonly=True))
description = exported(
@@ -1111,8 +1109,8 @@
"""
@operation_parameters(
- dependency=Reference(schema=Interface)) #Really IArchive. See below.
- @operation_returns_entry(schema=Interface) #Really IArchiveDependency.
+ dependency=Reference(schema=Interface)) # Really IArchive. See below.
+ @operation_returns_entry(schema=Interface) # Really IArchiveDependency.
@export_read_operation()
def getArchiveDependency(dependency):
"""Return the `IArchiveDependency` object for the given dependency.
@@ -1233,7 +1231,8 @@
source_names=List(
title=_("Source package names"),
value_type=TextLine()),
- from_archive=Reference(schema=Interface), #Really IArchive, see below
+ from_archive=Reference(schema=Interface),
+ #Really IArchive, see below
to_pocket=TextLine(title=_("Pocket name")),
to_series=TextLine(title=_("Distroseries name"), required=False),
include_binaries=Bool(
@@ -1275,7 +1274,8 @@
@operation_parameters(
source_name=TextLine(title=_("Source package name")),
version=TextLine(title=_("Version")),
- from_archive=Reference(schema=Interface), #Really IArchive, see below
+ from_archive=Reference(schema=Interface),
+ # Really IArchive, see below
to_pocket=TextLine(title=_("Pocket name")),
to_series=TextLine(title=_("Distroseries name"), required=False),
include_binaries=Bool(
@@ -1314,7 +1314,7 @@
@call_with(registrant=REQUEST_USER)
@operation_parameters(
- subscriber = PublicPersonChoice(
+ subscriber=PublicPersonChoice(
title=_("Subscriber"),
required=True,
vocabulary='ValidPersonOrTeam',
@@ -1458,6 +1458,61 @@
processed.
"""
+ def addArchiveDependency(dependency, pocket, component=None):
+ """Record an archive dependency record for the context archive.
+
+ :param dependency: is an `IArchive` object.
+ :param pocket: is an `PackagePublishingPocket` enum.
+ :param component: is an optional `IComponent` object, if not given
+ the archive dependency will be tied to the component used
+ for a corresponding source in primary archive.
+
+ :raise: `ArchiveDependencyError` if given 'dependency' does not fit
+ the context archive.
+ :return: a `IArchiveDependency` object targeted to the context
+ `IArchive` requiring 'dependency' `IArchive`.
+ """
+
+ @operation_parameters(
+ dependency=Reference(schema=Interface, required=True),
+ # Really IArchive
+ pocket=Choice(
+ title=_("Pocket"),
+ description=_("The pocket into which this entry is published"),
+ # Really PackagePublishingPocket.
+ vocabulary=DBEnumeratedType,
+ required=True),
+ component=TextLine(title=_("Component"), required=False),
+ )
+ @export_operation_as('addArchiveDependency')
+ @export_factory_operation(Interface, []) # Really IArchiveDependency
+ @operation_for_version('devel')
+ def _addArchiveDependency(dependency, pocket, component=None):
+ """Record an archive dependency record for the context archive.
+
+ :param dependency: is an `IArchive` object.
+ :param pocket: is an `PackagePublishingPocket` enum.
+ :param component: is the name of a component. If not given,
+ the archive dependency will be tied to the component used
+ for a corresponding source in primary archive.
+
+ :raise: `ArchiveDependencyError` if given 'dependency' does not fit
+ the context archive.
+ :return: a `IArchiveDependency` object targeted to the context
+ `IArchive` requiring 'dependency' `IArchive`.
+ """
+ @operation_parameters(
+ dependency=Reference(schema=Interface, required=True),
+ # Really IArchive
+ )
+ @export_write_operation()
+ @operation_for_version('devel')
+ def removeArchiveDependency(dependency):
+ """Remove the `IArchiveDependency` record for the given dependency.
+
+ :param dependency: is an `IArchive` object.
+ """
+
class IArchive(IArchivePublic, IArchiveAppend, IArchiveEdit, IArchiveView):
"""Main Archive interface."""
@@ -1690,3 +1745,29 @@
)
# Circular dependency issues fixed in _schema_circular_imports.py
+
+
+def validate_external_dependencies(ext_deps):
+ """Validate the external_dependencies field.
+
+ :param ext_deps: The dependencies form field to check.
+ """
+ errors = []
+ # The field can consist of multiple entries separated by
+ # newlines, so process each in turn.
+ for dep in ext_deps.splitlines():
+ try:
+ deb, url, suite, components = dep.split(" ", 3)
+ except ValueError:
+ errors.append(
+ "'%s' is not a complete and valid sources.list entry"
+ % dep)
+ continue
+
+ if deb != "deb":
+ errors.append("%s: Must start with 'deb'" % dep)
+ url_components = urlparse(url)
+ if not url_components[0] or not url_components[1]:
+ errors.append("%s: Invalid URL" % dep)
+
+ return errors
=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py 2011-05-27 21:12:25 +0000
+++ lib/lp/soyuz/model/archive.py 2011-06-02 14:48:33 +0000
@@ -125,6 +125,7 @@
CannotSwitchPrivacy,
CannotUploadToPocket,
CannotUploadToPPA,
+ ComponentNotFound,
default_name_by_purpose,
DistroSeriesNotFound,
FULL_COMPONENT_SUPPORT,
@@ -133,6 +134,7 @@
IDistributionArchive,
InsufficientUploadRights,
InvalidComponent,
+ InvalidExternalDependencies,
InvalidPocketForPartnerArchive,
InvalidPocketForPPA,
IPPA,
@@ -143,6 +145,7 @@
NoTokensForTeams,
PocketNotFound,
VersionRequiresName,
+ validate_external_dependencies,
)
from lp.soyuz.interfaces.archivearch import IArchiveArchSet
from lp.soyuz.interfaces.archiveauthtoken import IArchiveAuthTokenSet
@@ -197,6 +200,14 @@
from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
+def storm_validate_external_dependencies(archive, attr, value):
+ assert attr == 'external_dependencies'
+ errors = validate_external_dependencies(value)
+ if len(errors) > 0:
+ raise InvalidExternalDependencies(errors)
+ return value
+
+
class Archive(SQLBase):
implements(IArchive, IHasOwner, IHasBuildRecords)
_table = 'Archive'
@@ -306,7 +317,8 @@
# Launchpad and should be re-examined in October 2010 to see if it
# is still relevant.
external_dependencies = StringCol(
- dbName='external_dependencies', notNull=False, default=None)
+ dbName='external_dependencies', notNull=False, default=None,
+ storm_validator=storm_validate_external_dependencies)
commercial = BoolCol(
dbName='commercial', notNull=True, default=False)
@@ -483,7 +495,7 @@
if name is not None:
if exact_match:
- storm_clauses.append(SourcePackageName.name==name)
+ storm_clauses.append(SourcePackageName.name == name)
else:
clauses.append(
"SourcePackageName.name LIKE '%%%%' || %s || '%%%%'"
@@ -494,7 +506,7 @@
raise VersionRequiresName(
"The 'version' parameter can be used only together with"
" the 'name' parameter.")
- storm_clauses.append(SourcePackageRelease.version==version)
+ storm_clauses.append(SourcePackageRelease.version == version)
else:
orderBy.insert(1, Desc(SourcePackageRelease.version))
@@ -514,7 +526,7 @@
if pocket is not None:
storm_clauses.append(
- SourcePackagePublishingHistory.pocket==pocket)
+ SourcePackagePublishingHistory.pocket == pocket)
if created_since_date is not None:
clauses.append(
@@ -529,6 +541,7 @@
*orderBy)
if not eager_load:
return resultset
+
# Its not clear that this eager load is necessary or sufficient, it
# replaces a prejoin that had pathological query plans.
def eager_load(rows):
@@ -609,7 +622,7 @@
clauseTables = ['SourcePackageRelease', 'SourcePackageName']
order_const = "SourcePackageRelease.version"
- desc_version_order = SQLConstant(order_const+" DESC")
+ desc_version_order = SQLConstant(order_const + " DESC")
orderBy = ['SourcePackageName.name', desc_version_order,
'-SourcePackagePublishingHistory.id']
@@ -970,6 +983,15 @@
archive=self, dependency=dependency, pocket=pocket,
component=component)
+ def _addArchiveDependency(self, dependency, pocket, component=None):
+ """See `IArchive`."""
+ if isinstance(component, basestring):
+ try:
+ component = getUtility(IComponentSet)[component]
+ except NotFoundError as e:
+ raise ComponentNotFound(e)
+ return self.addArchiveDependency(dependency, pocket, component)
+
def getPermissions(self, user, item, perm_type):
"""See `IArchive`."""
permission_set = getUtility(IArchivePermissionSet)
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2011-05-28 04:09:11 +0000
+++ lib/lp/testing/factory.py 2011-06-02 14:48:33 +0000
@@ -593,7 +593,7 @@
self, email=None, name=None, password=None,
email_address_status=None, hide_email_addresses=False,
displayname=None, time_zone=None, latitude=None, longitude=None,
- selfgenerated_bugnotifications=False):
+ selfgenerated_bugnotifications=False, member_of=()):
"""Create and return a new, arbitrary Person.
:param email: The email address for the new person.
@@ -658,6 +658,10 @@
self.makeOpenIdIdentifier(person.account)
+ for team in member_of:
+ with person_logged_in(team.teamowner):
+ team.addMember(person, team.teamowner)
+
# Ensure updated ValidPersonCache
flush_database_updates()
return person