Merge lp:~abentley/launchpad/build-from-recipe-api into lp:launchpad

Proposed by Aaron Bentley
Status: Merged
Merged at revision: not available
Proposed branch: lp:~abentley/launchpad/build-from-recipe-api
Merge into: lp:launchpad
Diff against target: 786 lines (+257/-52)
21 files modified
lib/canonical/launchpad/components/apihelpers.py (+15/-0)
lib/canonical/launchpad/doc/tales.txt (+3/-3)
lib/canonical/launchpad/interfaces/_schema_circular_imports.py (+13/-2)
lib/lp/code/browser/tests/test_sourcepackagerecipe.py (+1/-1)
lib/lp/code/interfaces/sourcepackagerecipe.py (+42/-11)
lib/lp/code/interfaces/sourcepackagerecipebuild.py (+2/-0)
lib/lp/code/interfaces/webservice.py (+2/-0)
lib/lp/code/model/sourcepackagerecipe.py (+12/-0)
lib/lp/code/tests/test_sourcepackagerecipe.py (+73/-3)
lib/lp/registry/interfaces/person.py (+26/-1)
lib/lp/registry/model/person.py (+14/-0)
lib/lp/soyuz/browser/tests/archivesubscription-views.txt (+13/-13)
lib/lp/soyuz/doc/archive.txt (+1/-1)
lib/lp/soyuz/doc/archiveauthtoken.txt (+1/-1)
lib/lp/soyuz/doc/sourcepackagerelease.txt (+4/-4)
lib/lp/soyuz/model/archive.py (+1/-2)
lib/lp/soyuz/scripts/tests/test_ppa_add_missing_builds.py (+1/-0)
lib/lp/soyuz/stories/ppa/xx-ppa-packages.txt (+1/-1)
lib/lp/testing/__init__.py (+17/-1)
lib/lp/testing/_webservice.py (+3/-0)
lib/lp/testing/factory.py (+12/-8)
To merge this branch: bzr merge lp:~abentley/launchpad/build-from-recipe-api
Reviewer Review Type Date Requested Status
Tim Penhey (community) Approve
Review via email: mp+23981@code.launchpad.net

Commit message

Expose SourcePackageRecipe over the web service

Description of the change

= Summary =
Provide SourcePackageRecipe over the web service

== Proposed fix ==
See above

== Pre-implementation notes ==
Discussion with the entire code team, at various points.

== Implementation details ==
Added Person.createRecipe, since we can't expose SourcePackageRecipeSource.new.
Exposed SourcePackageRecipe, created setRecipeText, which will automatically
parse and store the recipe. Exposed SourcePackageRecipe.requestBuild.

SourcePackageRecipe.distroseries is a ReferenceSet, (not a ResultSet), and
these cannot be exposed yet. Further work is required to either change its
type or make its type exposable.

LaunchpadObjectFactory.makeArchive creates PPAs by default, which is useful,
but they are non-Ubuntu PPAs, and as such are not accessible via the web
service. This branch changes makeArchive to use Ubuntu when creating ppas
(unless overridden).

Implemented patch_list_parameter_type because it had not yet been invented.

Implemented ws_obj to make it convenient to translate a database object into a webservice object.

== Tests ==
bin/test -t TestWebservice test_sourcepackagerecipe

== Demo and Q/A ==
None

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/lp/code/interfaces/sourcepackagerecipebuild.py
  lib/lp/code/interfaces/webservice.py
  lib/canonical/launchpad/components/apihelpers.py
  lib/lp/code/tests/test_sourcepackagerecipe.py
  lib/lp/testing/factory.py
  lib/lp/testing/__init__.py
  lib/lp/testing/_webservice.py
  lib/lp/registry/interfaces/person.py
  lib/lp/code/model/sourcepackagerecipe.py
  lib/lp/code/interfaces/sourcepackagerecipe.py
  lib/lp/registry/model/person.py
  lib/canonical/launchpad/interfaces/_schema_circular_imports.py

== Pyflakes notices ==

lib/lp/code/interfaces/webservice.py
    8: 'CodeImportAlreadyRunning' imported but unused
    8: 'CodeImportNotInReviewedState' imported but unused
    8: 'BranchMergeProposalExists' imported but unused
    11: 'BranchCreatorNotOwner' imported but unused
    11: 'IBranchSet' imported but unused
    11: 'IBranch' imported but unused
    11: 'BranchExists' imported but unused
    11: 'BranchCreatorNotMemberOfOwnerTeam' imported but unused
    14: 'IBranchMergeProposal' imported but unused
    15: 'IBranchSubscription' imported but unused
    16: 'ICodeImport' imported but unused
    17: 'ICodeReviewComment' imported but unused
    18: 'ICodeReviewVoteReference' imported but unused
    19: 'IStaticDiff' imported but unused
    19: 'IDiff' imported but unused
    19: 'IPreviewDiff' imported but unused
    20: 'ISourcePackageRecipe' imported but unused
    21: 'ISourcePackageRecipeBuild' imported but unused

== Pylint notices ==

lib/lp/code/interfaces/webservice.py
    21: [C0301] Line too long (81/78)
    19: [W0611] Unused import IDiff
    8: [W0611] Unused import CodeImportAlreadyRunning
    15: [W0611] Unused import IBranchSubscription
    11: [W0611] Unused import BranchCreatorNotOwner
    8: [W0611] Unused import CodeImportNotInReviewedState
    14: [W0611] Unused import IBranchMergeProposal
    11: [W0611] Unused import BranchCreatorNotMemberOfOwnerTeam
    16: [W0611] Unused import ICodeImport
    20: [W0611] Unused import ISourcePackageRecipe
    18: [W0611] Unused import ICodeReviewVoteReference
    11: [W0611] Unused import IBranchSet
    19: [W0611] Unused import IStaticDiff
    11: [W0611] Unused import IBranch
    19: [W0611] Unused import IPreviewDiff
    8: [W0611] Unused import BranchMergeProposalExists
    11: [W0611] Unused import BranchExists
    21: [W0611] Unused import ISourcePackageRecipeBuild
    17: [W0611] Unused import ICodeReviewComment

lib/lp/code/tests/test_sourcepackagerecipe.py
    245: [E1002, TestRecipeBranchRoundTripping.setUp] Use super on an old style class

lib/lp/registry/interfaces/person.py
    411: [E1002, PersonNameField._validate] Use super on an old style class
    851: [C0322, IPersonPublic.createRecipe] Operator not preceded by a space
    distroseries=List(value_type=Reference(schema=Interface)),
    ^
    name=TextLine(),
    recipe_text=Text(),
    sourcepackagename=TextLine(),
    )
    @export_factory_operation(Interface, [])
    def createRecipe(name, description, recipe_text, distroseries,
    sourcepackagename, registrant):
    1408: [C0322, IPersonEditRestricted.addMember] Operator not preceded by a space
    status=copy_field(ITeamMembership['status']),
    ^
    comment=Text(required=False))
    @export_write_operation()
    def addMember(person, reviewer, status=TeamMembershipStatus.APPROVED,
    comment=None, force_team_add=False,
    may_subscribe_to_list=True):
    1449: [C0322, IPersonEditRestricted.acceptInvitationToBeMemberOf] Operator not preceded by a space
    comment=Text())
    ^
    @export_write_operation()
    def acceptInvitationToBeMemberOf(team, comment):
    1461: [C0322, IPersonEditRestricted.declineInvitationToBeMemberOf] Operator not preceded by a space
    comment=Text())
    ^
    @export_write_operation()
    def declineInvitationToBeMemberOf(team, comment):
    1749: [C0322, IPersonSet.newTeam] Operator not preceded by a space
    defaultmembershipperiod='default_membership_period',
    ^
    defaultrenewalperiod='default_renewal_period')
    @operation_parameters(
    subscriptionpolicy=Choice(
    title=_('Subscription policy'), vocabulary=TeamSubscriptionPolicy,
    required=False, default=TeamSubscriptionPolicy.MODERATED))
    @export_factory_operation(
    ITeam, ['name', 'displayname', 'teamdescription',
    'defaultmembershipperiod', 'defaultrenewalperiod'])
    def newTeam(teamowner, name, displayname, teamdescription=None,
    subscriptionpolicy=TeamSubscriptionPolicy.MODERATED,
    defaultmembershipperiod=None, defaultrenewalperiod=None):
    1818: [C0322, IPersonSet.findPerson] Operator not preceded by a space
    created_after=Datetime(
    ^
    title=_("Created after"), required=False),
    created_before=Datetime(
    title=_("Created before"), required=False),
    )
    @operation_returns_collection_of(IPerson)
    @export_read_operation()
    def findPerson(text="", exclude_inactive_accounts=True,
    must_have_email=False,
    created_after=None, created_before=None):

lib/lp/code/interfaces/sourcepackagerecipe.py
    132: [C0322, ISourcePackageRecipe.requestBuild] Operator not preceded by a space
    distroseries=Reference(schema=IDistroSeries),
    ^
    )
    @export_write_operation()
    def requestBuild(archive, distroseries, requester, pocket):

lib/lp/registry/model/person.py
    301: [W1001, Person] Use of "property" on an old style class
    317: [W1001, Person] Use of "property" on an old style class
    331: [W1001, Person] Use of "property" on an old style class
    1263: [W0104, Person.addMember] Statement seems to have no effect

To post a comment you must log in.
Revision history for this message
Tim Penhey (thumper) wrote :

The exported registrant and owner in the interface need to be slightly
more constrained. Looking at the IBranch interface, it seems that
'PublicPersonChoice' is the better choice for the registrant rather than just
Reference. I recall there being a big hoo-ha over it when we initially had
some privacy issues around person.

    registrant = exported(
        PublicPersonChoice(
            title=_("The person who created this recipe."),
            required=True, readonly=True,
            vocabulary='ValidPersonOrTeam'))

And similarly, the owner should be constrained to the person or a team the
person is a member of, otherwise someone would be able to pass off the recipe
to anyone else (which we don't support elsewhere).

    owner = exported(
        ParticipatingPersonChoice(
            title=_('Owner'),
            required=True, readonly=False,
            vocabulary='UserTeamsParticipationPlusSelf',
            description=_("The person or team who can edit this recipe.")))

In the operation_parameters for requestBuild you have pocket one space
dedented from the others.

lib/lp/registry/interfaces/person.py
 - You need a blank line between the end of createRecipe and getRecipe.

review: Needs Fixing
Revision history for this message
Aaron Bentley (abentley) wrote :

Updated per your review.

Revision history for this message
Tim Penhey (thumper) wrote :

Apart from the merge conflicts, this looks good now.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/components/apihelpers.py'
--- lib/canonical/launchpad/components/apihelpers.py 2009-06-25 05:30:52 +0000
+++ lib/canonical/launchpad/components/apihelpers.py 2010-04-29 19:44:31 +0000
@@ -19,6 +19,7 @@
19 'patch_collection_property',19 'patch_collection_property',
20 'patch_collection_return_type',20 'patch_collection_return_type',
21 'patch_plain_parameter_type',21 'patch_plain_parameter_type',
22 'patch_list_parameter_type',
22 'patch_reference_property',23 'patch_reference_property',
23 ]24 ]
2425
@@ -48,6 +49,20 @@
48 collection['return_type'].value_type.schema = return_type49 collection['return_type'].value_type.schema = return_type
4950
5051
52def patch_list_parameter_type(exported_class, method_name, param_name,
53 param_type):
54 """Update a list parameter type for a webservice method.
55
56 :param exported_class: The class containing the method.
57 :param method_name: The method name that you need to patch.
58 :param param_name: The name of the parameter that you need to patch.
59 :param param_type: The new type for the parameter.
60 """
61 method = exported_class[method_name]
62 params = method.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED)['params']
63 params[param_name].value_type = param_type
64
65
51def patch_plain_parameter_type(exported_class, method_name, param_name,66def patch_plain_parameter_type(exported_class, method_name, param_name,
52 param_type):67 param_type):
53 """Update a plain parameter type for a webservice method.68 """Update a plain parameter type for a webservice method.
5469
=== modified file 'lib/canonical/launchpad/doc/tales.txt'
--- lib/canonical/launchpad/doc/tales.txt 2010-04-16 15:06:55 +0000
+++ lib/canonical/launchpad/doc/tales.txt 2010-04-29 19:44:31 +0000
@@ -157,10 +157,10 @@
157157
158 >>> login('admin@canonical.com')158 >>> login('admin@canonical.com')
159 >>> private_ppa = factory.makeArchive(159 >>> private_ppa = factory.makeArchive(
160 ... name='ppa', private=True, owner=owner)160 ... name='pppa', private=True, owner=owner)
161161
162 >>> print test_tales("ppa/fmt:link", ppa=private_ppa)162 >>> print test_tales("ppa/fmt:link", ppa=private_ppa)
163 <a href="/~joe/+archive/ppa" class="sprite ppa-icon">PPA163 <a href="/~joe/+archive/pppa" class="sprite ppa-icon">PPA named pppa
164 for Joe Smith</a>164 for Joe Smith</a>
165165
166 >>> login(ANONYMOUS)166 >>> login(ANONYMOUS)
@@ -178,7 +178,7 @@
178 >>> ignore = private_ppa.newSubscription(ppa_user, owner)178 >>> ignore = private_ppa.newSubscription(ppa_user, owner)
179 >>> login_person(ppa_user)179 >>> login_person(ppa_user)
180 >>> print test_tales("ppa/fmt:reference", ppa=private_ppa)180 >>> print test_tales("ppa/fmt:reference", ppa=private_ppa)
181 ppa:joe/ppa181 ppa:joe/pppa
182182
183We also have icons for builds which may have different dimensions.183We also have icons for builds which may have different dimensions.
184184
185185
=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-04-13 15:12:18 +0000
+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-04-29 19:44:31 +0000
@@ -16,11 +16,13 @@
1616
1717
18from lazr.restful.declarations import LAZR_WEBSERVICE_EXPORTED18from lazr.restful.declarations import LAZR_WEBSERVICE_EXPORTED
19from lazr.restful.fields import Reference
1920
20from canonical.launchpad.components.apihelpers import (21from canonical.launchpad.components.apihelpers import (
21 patch_entry_return_type, patch_collection_property,22 patch_entry_return_type, patch_collection_property,
22 patch_collection_return_type, patch_plain_parameter_type,23 patch_collection_return_type, patch_list_parameter_type,
23 patch_choice_parameter_type, patch_reference_property)24 patch_plain_parameter_type, patch_choice_parameter_type,
25 patch_reference_property)
2426
25from lp.registry.interfaces.structuralsubscription import (27from lp.registry.interfaces.structuralsubscription import (
26 IStructuralSubscription, IStructuralSubscriptionTarget)28 IStructuralSubscription, IStructuralSubscriptionTarget)
@@ -46,6 +48,8 @@
46from lp.code.interfaces.diff import IPreviewDiff48from lp.code.interfaces.diff import IPreviewDiff
47from lp.code.interfaces.hasbranches import (49from lp.code.interfaces.hasbranches import (
48 IHasBranches, IHasCodeImports, IHasMergeProposals, IHasRequestedReviews)50 IHasBranches, IHasCodeImports, IHasMergeProposals, IHasRequestedReviews)
51from lp.code.interfaces.sourcepackagerecipe import (
52 ISourcePackageRecipe)
49from lp.code.interfaces.sourcepackagerecipebuild import (53from lp.code.interfaces.sourcepackagerecipebuild import (
50 ISourcePackageRecipeBuild)54 ISourcePackageRecipeBuild)
51from lp.registry.interfaces.distribution import IDistribution55from lp.registry.interfaces.distribution import IDistribution
@@ -185,6 +189,13 @@
185 LAZR_WEBSERVICE_EXPORTED)['params']['branch'].schema = IBranch189 LAZR_WEBSERVICE_EXPORTED)['params']['branch'].schema = IBranch
186patch_reference_property(ISourcePackage, 'distribution', IDistribution)190patch_reference_property(ISourcePackage, 'distribution', IDistribution)
187191
192# IPerson
193patch_entry_return_type(IPerson, 'createRecipe', ISourcePackageRecipe)
194patch_list_parameter_type(IPerson, 'createRecipe', 'distroseries',
195 Reference(schema=IDistroSeries))
196
197patch_entry_return_type(IPerson, 'getRecipe', ISourcePackageRecipe)
198
188IPerson['hardware_submissions'].value_type.schema = IHWSubmission199IPerson['hardware_submissions'].value_type.schema = IHWSubmission
189200
190# publishing.py201# publishing.py
191202
=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-04-28 16:01:10 +0000
+++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-04-29 19:44:31 +0000
@@ -35,7 +35,7 @@
35 self.chef = self.factory.makePerson(35 self.chef = self.factory.makePerson(
36 displayname='Master Chef', name='chef', password='test')36 displayname='Master Chef', name='chef', password='test')
37 self.ppa = self.factory.makeArchive(37 self.ppa = self.factory.makeArchive(
38 displayname='Secret PPA', owner=self.chef)38 displayname='Secret PPA', owner=self.chef, name='ppa')
39 self.squirrel = self.factory.makeDistroSeries(39 self.squirrel = self.factory.makeDistroSeries(
40 displayname='Secret Squirrel', name='secret')40 displayname='Secret Squirrel', name='secret')
4141
4242
=== modified file 'lib/lp/code/interfaces/sourcepackagerecipe.py'
--- lib/lp/code/interfaces/sourcepackagerecipe.py 2010-04-21 01:59:57 +0000
+++ lib/lp/code/interfaces/sourcepackagerecipe.py 2010-04-29 19:44:31 +0000
@@ -21,19 +21,25 @@
2121
22from textwrap import dedent22from textwrap import dedent
2323
24from lazr.restful.declarations import (
25 call_with, export_as_webservice_entry, export_write_operation, exported,
26 operation_parameters, REQUEST_USER)
24from lazr.restful.fields import CollectionField, Reference27from lazr.restful.fields import CollectionField, Reference
25from zope.interface import Attribute, Interface28from zope.interface import Attribute, Interface
26from zope.schema import Bool, Datetime, Object, Text, TextLine29from zope.schema import Bool, Choice, Datetime, Object, Text, TextLine
2730
28from canonical.launchpad import _31from canonical.launchpad import _
29from canonical.launchpad.fields import ParticipatingPersonChoice32from canonical.launchpad.fields import (
33 ParticipatingPersonChoice, PublicPersonChoice
34)
30from canonical.launchpad.validators.name import name_validator35from canonical.launchpad.validators.name import name_validator
3136
32from lp.code.interfaces.branch import IBranch37from lp.code.interfaces.branch import IBranch
33from lp.registry.interfaces.person import IPerson38from lp.registry.interfaces.pocket import PackagePublishingPocket
34from lp.registry.interfaces.role import IHasOwner39from lp.registry.interfaces.role import IHasOwner
35from lp.registry.interfaces.distroseries import IDistroSeries40from lp.registry.interfaces.distroseries import IDistroSeries
36from lp.registry.interfaces.sourcepackagename import ISourcePackageName41from lp.registry.interfaces.sourcepackagename import ISourcePackageName
42from lp.soyuz.interfaces.archive import IArchive
3743
3844
39MINIMAL_RECIPE_TEXT = dedent(u'''\45MINIMAL_RECIPE_TEXT = dedent(u'''\
@@ -80,16 +86,24 @@
80 More precisely, it describes how to combine a number of branches into a86 More precisely, it describes how to combine a number of branches into a
81 debianized source tree.87 debianized source tree.
82 """88 """
89 export_as_webservice_entry()
8390
84 date_created = Datetime(required=True, readonly=True)91 date_created = Datetime(required=True, readonly=True)
85 date_last_modified = Datetime(required=True, readonly=True)92 date_last_modified = Datetime(required=True, readonly=True)
8693
87 registrant = Reference(94 registrant = exported(
88 IPerson, title=_("The person who created this recipe"), readonly=True)95 PublicPersonChoice(
89 owner = ParticipatingPersonChoice(96 title=_("The person who created this recipe."),
90 title=_('Owner'), required=True, readonly=False,97 required=True, readonly=True,
91 vocabulary='UserTeamsParticipationPlusSelf',98 vocabulary='ValidPersonOrTeam'))
92 description=_("The person or team who can edit this recipe."))99
100 owner = exported(
101 ParticipatingPersonChoice(
102 title=_('Owner'),
103 required=True, readonly=False,
104 vocabulary='UserTeamsParticipationPlusSelf',
105 description=_("The person or team who can edit this recipe.")))
106
93 distroseries = CollectionField(107 distroseries = CollectionField(
94 Reference(IDistroSeries), title=_("The distroseries this recipe will"108 Reference(IDistroSeries), title=_("The distroseries this recipe will"
95 " build a source package for"),109 " build a source package for"),
@@ -101,10 +115,13 @@
101 "recipe will build a source package"),115 "recipe will build a source package"),
102 readonly=True)116 readonly=True)
103117
104 name = TextLine(118 _sourcepackagename_text = exported(
119 TextLine(), exported_as='sourcepackagename')
120
121 name = exported(TextLine(
105 title=_("Name"), required=True,122 title=_("Name"), required=True,
106 constraint=name_validator,123 constraint=name_validator,
107 description=_("The name of this recipe."))124 description=_("The name of this recipe.")))
108125
109 description = Text(126 description = Text(
110 title=_('Description'), required=True,127 title=_('Description'), required=True,
@@ -117,6 +134,20 @@
117 IBranch, title=_("The base branch used by this recipe."),134 IBranch, title=_("The base branch used by this recipe."),
118 required=True, readonly=True)135 required=True, readonly=True)
119136
137 @operation_parameters(recipe_text=Text())
138 @export_write_operation()
139 def setRecipeText(recipe_text):
140 """Set the text of the recipe."""
141
142 recipe_text = exported(Text())
143
144 @call_with(requester=REQUEST_USER)
145 @operation_parameters(
146 archive=Reference(schema=IArchive),
147 distroseries=Reference(schema=IDistroSeries),
148 pocket=Choice(vocabulary=PackagePublishingPocket,)
149 )
150 @export_write_operation()
120 def requestBuild(archive, distroseries, requester, pocket):151 def requestBuild(archive, distroseries, requester, pocket):
121 """Request that the recipe be built in to the specified archive.152 """Request that the recipe be built in to the specified archive.
122153
123154
=== modified file 'lib/lp/code/interfaces/sourcepackagerecipebuild.py'
--- lib/lp/code/interfaces/sourcepackagerecipebuild.py 2010-03-16 05:08:47 +0000
+++ lib/lp/code/interfaces/sourcepackagerecipebuild.py 2010-04-29 19:44:31 +0000
@@ -14,6 +14,7 @@
14 ]14 ]
1515
16from lazr.restful.fields import Reference16from lazr.restful.fields import Reference
17from lazr.restful.declarations import export_as_webservice_entry
1718
18from zope.interface import Interface19from zope.interface import Interface
19from zope.schema import Int, Object20from zope.schema import Int, Object
@@ -32,6 +33,7 @@
3233
33class ISourcePackageRecipeBuild(IBuildBase):34class ISourcePackageRecipeBuild(IBuildBase):
34 """A build of a source package."""35 """A build of a source package."""
36 export_as_webservice_entry()
3537
36 id = Int(title=_("Identifier for this build."))38 id = Int(title=_("Identifier for this build."))
3739
3840
=== modified file 'lib/lp/code/interfaces/webservice.py'
--- lib/lp/code/interfaces/webservice.py 2010-04-02 20:31:43 +0000
+++ lib/lp/code/interfaces/webservice.py 2010-04-29 19:44:31 +0000
@@ -17,3 +17,5 @@
17from lp.code.interfaces.codereviewcomment import ICodeReviewComment17from lp.code.interfaces.codereviewcomment import ICodeReviewComment
18from lp.code.interfaces.codereviewvote import ICodeReviewVoteReference18from lp.code.interfaces.codereviewvote import ICodeReviewVoteReference
19from lp.code.interfaces.diff import IDiff, IPreviewDiff, IStaticDiff19from lp.code.interfaces.diff import IDiff, IPreviewDiff, IStaticDiff
20from lp.code.interfaces.sourcepackagerecipe import ISourcePackageRecipe
21from lp.code.interfaces.sourcepackagerecipebuild import ISourcePackageRecipeBuild
2022
=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
--- lib/lp/code/model/sourcepackagerecipe.py 2010-04-19 05:04:00 +0000
+++ lib/lp/code/model/sourcepackagerecipe.py 2010-04-29 19:44:31 +0000
@@ -10,6 +10,7 @@
10 'SourcePackageRecipe',10 'SourcePackageRecipe',
11 ]11 ]
1212
13from bzrlib.plugins.builder import RecipeParser
13from lazr.delegates import delegates14from lazr.delegates import delegates
1415
15from storm.locals import (16from storm.locals import (
@@ -80,6 +81,10 @@
80 sourcepackagename = Reference(81 sourcepackagename = Reference(
81 sourcepackagename_id, 'SourcePackageName.id')82 sourcepackagename_id, 'SourcePackageName.id')
8283
84 @property
85 def _sourcepackagename_text(self):
86 return self.sourcepackagename.name
87
83 name = Unicode(allow_none=True)88 name = Unicode(allow_none=True)
84 description = Unicode(allow_none=False)89 description = Unicode(allow_none=False)
8590
@@ -103,6 +108,13 @@
103 def base_branch(self):108 def base_branch(self):
104 return self._recipe_data.base_branch109 return self._recipe_data.base_branch
105110
111 def setRecipeText(self, recipe_text):
112 self.builder_recipe = RecipeParser(recipe_text).parse()
113
114 @property
115 def recipe_text(self):
116 return str(self.builder_recipe)
117
106 @staticmethod118 @staticmethod
107 def new(registrant, owner, distroseries, sourcepackagename, name,119 def new(registrant, owner, distroseries, sourcepackagename, name,
108 builder_recipe, description):120 builder_recipe, description):
109121
=== modified file 'lib/lp/code/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/tests/test_sourcepackagerecipe.py 2010-03-26 19:29:56 +0000
+++ lib/lp/code/tests/test_sourcepackagerecipe.py 2010-04-29 19:44:31 +0000
@@ -12,11 +12,12 @@
1212
13from storm.locals import Store13from storm.locals import Store
1414
15import transaction
15from zope.component import getUtility16from zope.component import getUtility
16from zope.security.interfaces import Unauthorized17from zope.security.interfaces import Unauthorized
17from zope.security.proxy import removeSecurityProxy18from zope.security.proxy import removeSecurityProxy
1819
19from canonical.testing.layers import DatabaseFunctionalLayer20from canonical.testing.layers import DatabaseFunctionalLayer, AppServerLayer
2021
21from lp.archiveuploader.permission import (22from lp.archiveuploader.permission import (
22 ArchiveDisabled, CannotUploadToArchive, InvalidPocketForPPA)23 ArchiveDisabled, CannotUploadToArchive, InvalidPocketForPPA)
@@ -24,7 +25,7 @@
24from lp.buildmaster.model.buildqueue import BuildQueue25from lp.buildmaster.model.buildqueue import BuildQueue
25from lp.code.interfaces.sourcepackagerecipe import (26from lp.code.interfaces.sourcepackagerecipe import (
26 ForbiddenInstruction, ISourcePackageRecipe, ISourcePackageRecipeSource,27 ForbiddenInstruction, ISourcePackageRecipe, ISourcePackageRecipeSource,
27 TooNewRecipeFormat)28 TooNewRecipeFormat, MINIMAL_RECIPE_TEXT)
28from lp.code.interfaces.sourcepackagerecipebuild import (29from lp.code.interfaces.sourcepackagerecipebuild import (
29 ISourcePackageRecipeBuild, ISourcePackageRecipeBuildJob)30 ISourcePackageRecipeBuild, ISourcePackageRecipeBuildJob)
30from lp.code.model.sourcepackagerecipebuild import (31from lp.code.model.sourcepackagerecipebuild import (
@@ -35,7 +36,9 @@
35from lp.services.job.interfaces.job import (36from lp.services.job.interfaces.job import (
36 IJob, JobStatus)37 IJob, JobStatus)
37from lp.soyuz.interfaces.archive import ArchivePurpose38from lp.soyuz.interfaces.archive import ArchivePurpose
38from lp.testing import login_person, TestCaseWithFactory39from lp.testing import (
40 ANONYMOUS, launchpadlib_for, login, login_person, TestCaseWithFactory,
41 ws_object)
3942
4043
41class TestSourcePackageRecipe(TestCaseWithFactory):44class TestSourcePackageRecipe(TestCaseWithFactory):
@@ -425,5 +428,72 @@
425 child_branch, "zam", self.merged_branch.bzr_identity, revspec="2")428 child_branch, "zam", self.merged_branch.bzr_identity, revspec="2")
426429
427430
431class TestWebservice(TestCaseWithFactory):
432
433 layer = AppServerLayer
434
435 def makeRecipeText(self):
436 branch = self.factory.makeBranch()
437 return MINIMAL_RECIPE_TEXT % branch.bzr_identity
438
439 def makeRecipe(self, user=None, owner=None, recipe_text=None):
440 if user is None:
441 user = self.factory.makePerson()
442 if owner is None:
443 owner = user
444 db_distroseries = self.factory.makeDistroSeries()
445 if recipe_text is None:
446 recipe_text = self.makeRecipeText()
447 launchpad = launchpadlib_for('test', user,
448 service_root="http://api.launchpad.dev:8085")
449 login(ANONYMOUS)
450 distroseries = ws_object(launchpad, db_distroseries)
451 ws_owner = ws_object(launchpad, owner)
452 recipe = ws_owner.createRecipe(
453 name='toaster-1', sourcepackagename='toaster',
454 description='a recipe', distroseries=[distroseries.self_link],
455 recipe_text=recipe_text)
456 # at the moment, distroseries is not exposed in the API.
457 transaction.commit()
458 db_recipe = owner.getRecipe(name=u'toaster-1')
459 self.assertEqual(set([db_distroseries]), set(db_recipe.distroseries))
460 return recipe, ws_owner, launchpad
461
462 def test_createRecipe(self):
463 """Ensure recipe creation works."""
464 team = self.factory.makeTeam()
465 recipe_text = self.makeRecipeText()
466 recipe, user = self.makeRecipe(user=team.teamowner, owner=team,
467 recipe_text=recipe_text)[:2]
468 self.assertEqual(team.name, recipe.owner.name)
469 self.assertEqual(team.teamowner.name, recipe.registrant.name)
470 self.assertEqual('toaster-1', recipe.name)
471 self.assertEqual(recipe_text, recipe.recipe_text)
472 self.assertEqual('toaster', recipe.sourcepackagename)
473
474 def test_recipe_text(self):
475 recipe_text2 = self.makeRecipeText()
476 recipe = self.makeRecipe()[0]
477 recipe.setRecipeText(recipe_text=recipe_text2)
478 self.assertEqual(recipe_text2, recipe.recipe_text)
479
480 def test_getRecipe(self):
481 """Person.getRecipe returns the named recipe."""
482 recipe, user = self.makeRecipe()[:-1]
483 self.assertEqual(recipe, user.getRecipe(name=recipe.name))
484
485 def test_requestBuild(self):
486 """Build requests can be performed."""
487 person = self.factory.makePerson()
488 archive = self.factory.makeArchive(owner=person)
489 distroseries = self.factory.makeDistroSeries()
490 recipe, user, launchpad = self.makeRecipe(person)
491 distroseries = ws_object(launchpad, distroseries)
492 archive = ws_object(launchpad, archive)
493 recipe.requestBuild(
494 archive=archive, distroseries=distroseries,
495 pocket=PackagePublishingPocket.RELEASE.title)
496
497
428def test_suite():498def test_suite():
429 return unittest.TestLoader().loadTestsFromName(__name__)499 return unittest.TestLoader().loadTestsFromName(__name__)
430500
=== modified file 'lib/lp/registry/interfaces/person.py'
--- lib/lp/registry/interfaces/person.py 2010-04-27 11:48:16 +0000
+++ lib/lp/registry/interfaces/person.py 2010-04-29 19:44:31 +0000
@@ -41,7 +41,8 @@
4141
4242
43from zope.formlib.form import NoInputData43from zope.formlib.form import NoInputData
44from zope.schema import Bool, Choice, Datetime, Int, Object, Text, TextLine44from zope.schema import (
45 Bool, Choice, Datetime, Int, List, Object, Text, TextLine)
45from zope.interface import Attribute, Interface46from zope.interface import Attribute, Interface
46from zope.interface.exceptions import Invalid47from zope.interface.exceptions import Invalid
47from zope.interface.interface import invariant48from zope.interface.interface import invariant
@@ -844,6 +845,30 @@
844 not teams can be converted into teams.845 not teams can be converted into teams.
845 """846 """
846847
848 @call_with(registrant=REQUEST_USER)
849 @operation_parameters(
850 description=Text(),
851 distroseries=List(value_type=Reference(schema=Interface)),
852 name=TextLine(),
853 recipe_text=Text(),
854 sourcepackagename=TextLine(),
855 )
856 @export_factory_operation(Interface, [])
857 def createRecipe(name, description, recipe_text, distroseries,
858 sourcepackagename, registrant):
859 """Create a SourcePackageRecipe owned by this person.
860
861 :param name: the name to use for referring to the recipe.
862 :param description: A description of the recipe.
863 :param recipe_text: The text of the recipe.
864 :param distroseries: The distroseries to use.
865 :param sourcepackagename: The name of the sourcepackage for the recipe.
866 :return: a SourcePackageRecipe.
867 """
868
869 @operation_parameters(name=TextLine(required=True))
870 @operation_returns_entry(Interface) # Really ISourcePackageRecipe.
871 @export_read_operation()
847 def getRecipe(name):872 def getRecipe(name):
848 """Return the person's recipe with the given name."""873 """Return the person's recipe with the given name."""
849874
850875
=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py 2010-04-26 15:59:41 +0000
+++ lib/lp/registry/model/person.py 2010-04-29 19:44:31 +0000
@@ -32,6 +32,7 @@
32import re32import re
33import weakref33import weakref
3434
35from bzrlib.plugins.builder import RecipeParser
35from zope.lifecycleevent import ObjectCreatedEvent36from zope.lifecycleevent import ObjectCreatedEvent
36from zope.interface import alsoProvides, implementer, implements37from zope.interface import alsoProvides, implementer, implements
37from zope.component import adapter, getUtility38from zope.component import adapter, getUtility
@@ -120,6 +121,8 @@
120 SpecificationDefinitionStatus, SpecificationFilter,121 SpecificationDefinitionStatus, SpecificationFilter,
121 SpecificationImplementationStatus, SpecificationSort)122 SpecificationImplementationStatus, SpecificationSort)
122from canonical.launchpad.interfaces.lpstorm import IStore123from canonical.launchpad.interfaces.lpstorm import IStore
124from lp.registry.interfaces.sourcepackagename import (
125 ISourcePackageNameSet)
123from lp.registry.interfaces.ssh import ISSHKey, ISSHKeySet, SSHKeyType126from lp.registry.interfaces.ssh import ISSHKey, ISSHKeySet, SSHKeyType
124from lp.registry.interfaces.teammembership import (127from lp.registry.interfaces.teammembership import (
125 TeamMembershipStatus)128 TeamMembershipStatus)
@@ -2265,6 +2268,17 @@
22652268
2266 return rset2269 return rset
22672270
2271 def createRecipe(self, name, description, recipe_text, distroseries,
2272 sourcepackagename, registrant):
2273 """See `IPerson`."""
2274 from lp.code.model.sourcepackagerecipe import SourcePackageRecipe
2275 builder_recipe = RecipeParser(recipe_text).parse()
2276 spnset = getUtility(ISourcePackageNameSet)
2277 sourcepackagename = spnset.getOrCreateByName(sourcepackagename)
2278 return SourcePackageRecipe.new(
2279 registrant, self, distroseries, sourcepackagename, name,
2280 builder_recipe, description)
2281
2268 def getRecipe(self, name):2282 def getRecipe(self, name):
2269 from lp.code.model.sourcepackagerecipe import SourcePackageRecipe2283 from lp.code.model.sourcepackagerecipe import SourcePackageRecipe
2270 return Store.of(self).find(2284 return Store.of(self).find(
22712285
=== modified file 'lib/lp/soyuz/browser/tests/archivesubscription-views.txt'
--- lib/lp/soyuz/browser/tests/archivesubscription-views.txt 2010-02-23 15:10:46 +0000
+++ lib/lp/soyuz/browser/tests/archivesubscription-views.txt 2010-04-29 19:44:31 +0000
@@ -23,10 +23,10 @@
23 >>> from lp.registry.interfaces.person import IPersonSet23 >>> from lp.registry.interfaces.person import IPersonSet
24 >>> cprov = getUtility(IPersonSet).getByName("cprov")24 >>> cprov = getUtility(IPersonSet).getByName("cprov")
25 >>> cprov_private_ppa = factory.makeArchive(25 >>> cprov_private_ppa = factory.makeArchive(
26 ... owner=cprov, private=True)26 ... owner=cprov, private=True, name='pppa')
27 >>> mark = getUtility(IPersonSet).getByName("mark")27 >>> mark = getUtility(IPersonSet).getByName("mark")
28 >>> mark_private_ppa = factory.makeArchive(28 >>> mark_private_ppa = factory.makeArchive(
29 ... owner=mark, private=True)29 ... owner=mark, private=True, name='pppa')
30 >>> transaction.commit()30 >>> transaction.commit()
31 >>> logout()31 >>> logout()
3232
@@ -36,7 +36,7 @@
36 >>> view = create_initialized_view(36 >>> view = create_initialized_view(
37 ... cprov_private_ppa, name="+subscriptions")37 ... cprov_private_ppa, name="+subscriptions")
38 >>> print view.label38 >>> print view.label
39 Manage access to PPA for Celso Providelo39 Manage access to PPA named pppa for Celso Providelo
4040
41Initially the view does not display any subscribers, as can be seen41Initially the view does not display any subscribers, as can be seen
42using the has_subscriptions property:42using the has_subscriptions property:
@@ -86,7 +86,7 @@
86 >>> for notification in view.request.notifications:86 >>> for notification in view.request.notifications:
87 ... print notification.message87 ... print notification.message
88 You have granted access for Andrew Bennetts to install software88 You have granted access for Andrew Bennetts to install software
89 from PPA for Celso Providelo.89 from PPA named pppa for Celso Providelo.
90 Andrew Bennetts will be notified of the access via email.90 Andrew Bennetts will be notified of the access via email.
9191
92The view includes a subscribers property that returns all the current92The view includes a subscribers property that returns all the current
@@ -182,7 +182,7 @@
182 ... cprov_private_ppa).one()182 ... cprov_private_ppa).one()
183 >>> view = create_initialized_view(spiv_subscription, name="+edit")183 >>> view = create_initialized_view(spiv_subscription, name="+edit")
184 >>> print view.label184 >>> print view.label
185 Edit Andrew Bennetts's access to PPA for Celso Providelo185 Edit Andrew Bennetts's access to PPA named pppa for Celso Providelo
186186
187The ArchiveSubscriptionEditView presents the expiry and description ready187The ArchiveSubscriptionEditView presents the expiry and description ready
188for editing, together with Update and Cancel actions:188for editing, together with Update and Cancel actions:
@@ -197,7 +197,7 @@
197The ArchiveSubscriptionEditView has a next_url helper property.197The ArchiveSubscriptionEditView has a next_url helper property.
198198
199 >>> print view.next_url199 >>> print view.next_url
200 http://launchpad.dev/~cprov/+archive/ppa/+subscriptions200 http://launchpad.dev/~cprov/+archive/pppa/+subscriptions
201201
202The ArchiveSubscriptionEditView can be used to update the description field:202The ArchiveSubscriptionEditView can be used to update the description field:
203203
@@ -253,7 +253,7 @@
253253
254 >>> for notification in view.request.notifications:254 >>> for notification in view.request.notifications:
255 ... print notification.message255 ... print notification.message
256 You have revoked Andrew Bennetts's access to PPA for256 You have revoked Andrew Bennetts's access to PPA named pppa for
257 Celso Providelo.257 Celso Providelo.
258258
259Just uncancel the subscription before continuing on.259Just uncancel the subscription before continuing on.
@@ -295,8 +295,8 @@
295 ... print token_text295 ... print token_text
296296
297 >>> print_subscriptions_with_tokens()297 >>> print_subscriptions_with_tokens()
298 PPA for Mark Shuttleworth None298 PPA named pppa for Mark Shuttleworth None
299 PPA for Celso Providelo None299 PPA named pppa for Celso Providelo None
300300
301After activating a subscription, the token will be included in the301After activating a subscription, the token will be included in the
302subscriptions_with_tokens property:302subscriptions_with_tokens property:
@@ -305,8 +305,8 @@
305 >>> new_token = cprov_private_ppa.newAuthToken(spiv)305 >>> new_token = cprov_private_ppa.newAuthToken(spiv)
306 >>> view = create_initialized_view(spiv, name="+archivesubscriptions")306 >>> view = create_initialized_view(spiv, name="+archivesubscriptions")
307 >>> print_subscriptions_with_tokens()307 >>> print_subscriptions_with_tokens()
308 PPA for Mark Shuttleworth None308 PPA named pppa for Mark Shuttleworth None
309 PPA for Celso Providelo Token309 PPA named pppa for Celso Providelo Token
310310
311Just deactivate the new token again for the remaining tests.311Just deactivate the new token again for the remaining tests.
312312
@@ -326,7 +326,7 @@
326 ... spiv_subscription.subscriber, spiv_subscription.archive)326 ... spiv_subscription.subscriber, spiv_subscription.archive)
327 >>> view = create_initialized_view(spiv_subscription, name="+index")327 >>> view = create_initialized_view(spiv_subscription, name="+index")
328 >>> print view.label328 >>> print view.label
329 Access to PPA for Celso Providelo329 Access to PPA named pppa for Celso Providelo
330330
331Initially the subscription does not have an active token:331Initially the subscription does not have an active token:
332332
@@ -353,7 +353,7 @@
353 Andrew Bennetts353 Andrew Bennetts
354354
355 >>> print view.sources_list_entries.context.archive_url355 >>> print view.sources_list_entries.context.archive_url
356 http://spiv:...@private-ppa.launchpad.dev/cprov/ppa/...356 http://spiv:...@private-ppa.launchpad.dev/cprov/pppa/...
357357
358The view can also be used to regenerate the source.list entries.358The view can also be used to regenerate the source.list entries.
359359
360360
=== modified file 'lib/lp/soyuz/doc/archive.txt'
--- lib/lp/soyuz/doc/archive.txt 2010-04-28 14:07:13 +0000
+++ lib/lp/soyuz/doc/archive.txt 2010-04-29 19:44:31 +0000
@@ -1531,7 +1531,7 @@
15311531
1532 >>> archive_purposes = [archive.purpose.name for archive in archive_set]1532 >>> archive_purposes = [archive.purpose.name for archive in archive_set]
1533 >>> len(archive_purposes)1533 >>> len(archive_purposes)
1534 201534 19
15351535
1536 >>> print sorted(set(archive_purposes))1536 >>> print sorted(set(archive_purposes))
1537 ['COPY', 'DEBUG', 'PARTNER', 'PPA', 'PRIMARY']1537 ['COPY', 'DEBUG', 'PARTNER', 'PPA', 'PRIMARY']
15381538
=== modified file 'lib/lp/soyuz/doc/archiveauthtoken.txt'
--- lib/lp/soyuz/doc/archiveauthtoken.txt 2010-03-03 09:05:56 +0000
+++ lib/lp/soyuz/doc/archiveauthtoken.txt 2010-04-29 19:44:31 +0000
@@ -12,7 +12,7 @@
12 >>> login("admin@canonical.com")12 >>> login("admin@canonical.com")
13 >>> joe = factory.makePerson(name="joe", displayname="Joe Smith")13 >>> joe = factory.makePerson(name="joe", displayname="Joe Smith")
14 >>> joe_private_ppa = factory.makeArchive(14 >>> joe_private_ppa = factory.makeArchive(
15 ... owner=joe, private=True)15 ... owner=joe, private=True, name='ppa')
16 >>> logout()16 >>> logout()
1717
18== Creating new tokens ==18== Creating new tokens ==
1919
=== modified file 'lib/lp/soyuz/doc/sourcepackagerelease.txt'
--- lib/lp/soyuz/doc/sourcepackagerelease.txt 2010-04-12 08:29:02 +0000
+++ lib/lp/soyuz/doc/sourcepackagerelease.txt 2010-04-29 19:44:31 +0000
@@ -271,7 +271,7 @@
271271
272 >>> login('foo.bar@canonical.com')272 >>> login('foo.bar@canonical.com')
273 >>> cprov_private_ppa = factory.makeArchive(273 >>> cprov_private_ppa = factory.makeArchive(
274 ... owner=cprov, private=True)274 ... owner=cprov, private=True, name='pppa')
275275
276 >>> private_publication = test_publisher.getPubSource(276 >>> private_publication = test_publisher.getPubSource(
277 ... archive=cprov_private_ppa)277 ... archive=cprov_private_ppa)
@@ -283,7 +283,7 @@
283 >>> published_archives = test_sourcepackagerelease.published_archives283 >>> published_archives = test_sourcepackagerelease.published_archives
284 >>> for archive in published_archives:284 >>> for archive in published_archives:
285 ... print archive.displayname285 ... print archive.displayname
286 PPA for Celso Providelo286 PPA named pppa for Celso Providelo
287287
288'foo - 666' sourcepackagerelease is only published in Celso's Private288'foo - 666' sourcepackagerelease is only published in Celso's Private
289PPA. So, Only Celso and administrators can get 'launchpad.View' on it.289PPA. So, Only Celso and administrators can get 'launchpad.View' on it.
@@ -320,7 +320,7 @@
320 >>> for archive in published_archives:320 >>> for archive in published_archives:
321 ... print archive.displayname321 ... print archive.displayname
322 Primary Archive for Ubuntu Linux322 Primary Archive for Ubuntu Linux
323 PPA for Celso Providelo323 PPA named pppa for Celso Providelo
324324
325And we can see it's publicly available now, as expected.325And we can see it's publicly available now, as expected.
326326
@@ -366,5 +366,5 @@
366 >>> for archive in published_archives:366 >>> for archive in published_archives:
367 ... print archive.displayname367 ... print archive.displayname
368 Primary Archive for Ubuntu Linux368 Primary Archive for Ubuntu Linux
369 PPA for Celso Providelo369 PPA named pppa for Celso Providelo
370370
371371
=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py 2010-04-28 14:07:13 +0000
+++ lib/lp/soyuz/model/archive.py 2010-04-29 19:44:31 +0000
@@ -1560,8 +1560,7 @@
1560 (name, distribution.name))1560 (name, distribution.name))
1561 else:1561 else:
1562 archive = Archive.selectOneBy(1562 archive = Archive.selectOneBy(
1563 owner=owner, distribution=distribution, name=name,1563 owner=owner, name=name, purpose=ArchivePurpose.PPA)
1564 purpose=ArchivePurpose.PPA)
1565 if archive is not None:1564 if archive is not None:
1566 raise AssertionError(1565 raise AssertionError(
1567 "Person '%s' already has a PPA named '%s'." %1566 "Person '%s' already has a PPA named '%s'." %
15681567
=== modified file 'lib/lp/soyuz/scripts/tests/test_ppa_add_missing_builds.py'
--- lib/lp/soyuz/scripts/tests/test_ppa_add_missing_builds.py 2010-01-27 17:45:03 +0000
+++ lib/lp/soyuz/scripts/tests/test_ppa_add_missing_builds.py 2010-04-29 19:44:31 +0000
@@ -119,6 +119,7 @@
119 "-a", "i386",119 "-a", "i386",
120 "-a", "hppa",120 "-a", "hppa",
121 "--owner", "%s" % self.ppa.owner.name,121 "--owner", "%s" % self.ppa.owner.name,
122 "--ppa", self.ppa.name,
122 ]123 ]
123 code, stdout, stderr = self.runScript(args)124 code, stdout, stderr = self.runScript(args)
124 self.assertEqual(125 self.assertEqual(
125126
=== modified file 'lib/lp/soyuz/stories/ppa/xx-ppa-packages.txt'
--- lib/lp/soyuz/stories/ppa/xx-ppa-packages.txt 2010-03-08 09:27:36 +0000
+++ lib/lp/soyuz/stories/ppa/xx-ppa-packages.txt 2010-04-29 19:44:31 +0000
@@ -192,7 +192,7 @@
192 ... distribution=ubuntu)192 ... distribution=ubuntu)
193 >>> joe = factory.makePerson(name="joe")193 >>> joe = factory.makePerson(name="joe")
194 >>> public_ppa = factory.makeArchive(194 >>> public_ppa = factory.makeArchive(
195 ... owner=joe, distribution=ubuntu)195 ... owner=joe, distribution=ubuntu, name='ppa')
196 >>> from lp.soyuz.tests.test_publishing import SoyuzTestPublisher196 >>> from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
197 >>> test_publisher = SoyuzTestPublisher()197 >>> test_publisher = SoyuzTestPublisher()
198 >>> test_publisher.prepareBreezyAutotest()198 >>> test_publisher.prepareBreezyAutotest()
199199
=== modified file 'lib/lp/testing/__init__.py'
--- lib/lp/testing/__init__.py 2010-04-24 02:41:03 +0000
+++ lib/lp/testing/__init__.py 2010-04-29 19:44:31 +0000
@@ -38,6 +38,7 @@
38 'validate_mock_class',38 'validate_mock_class',
39 'WindmillTestCase',39 'WindmillTestCase',
40 'with_anonymous_login',40 'with_anonymous_login',
41 'ws_object'
41 'YUIUnitTestCase',42 'YUIUnitTestCase',
42 'ZopeTestInSubProcess',43 'ZopeTestInSubProcess',
43 ]44 ]
@@ -79,7 +80,8 @@
79 isinstance as zope_isinstance, removeSecurityProxy)80 isinstance as zope_isinstance, removeSecurityProxy)
80from zope.testing.testrunner.runner import TestResult as ZopeTestResult81from zope.testing.testrunner.runner import TestResult as ZopeTestResult
8182
82from canonical.launchpad.webapp import errorlog83from canonical.launchpad.webapp import canonical_url, errorlog
84from canonical.launchpad.webapp.servers import WebServiceTestRequest
83from canonical.config import config85from canonical.config import config
84from canonical.launchpad.webapp.interaction import ANONYMOUS86from canonical.launchpad.webapp.interaction import ANONYMOUS
85from canonical.launchpad.webapp.interfaces import ILaunchBag87from canonical.launchpad.webapp.interfaces import ILaunchBag
@@ -952,6 +954,20 @@
952 else:954 else:
953 break955 break
954956
957
958def ws_object(launchpad, obj):
959 """Convert an object into its webservice version.
960
961 :param launchpad: The Launchpad instance to convert from.
962 :param obj: The object to convert.
963 :return: A launchpadlib Entry object.
964 """
965 api_request = WebServiceTestRequest()
966 obj_url = canonical_url(obj, request=api_request)
967 return launchpad.load(
968 obj_url.replace('http://api.launchpad.dev/',
969 str(launchpad._root_uri)))
970
955def unlink_source_packages(product):971def unlink_source_packages(product):
956 """Remove all links between the product and source packages.972 """Remove all links between the product and source packages.
957973
958974
=== modified file 'lib/lp/testing/_webservice.py'
--- lib/lp/testing/_webservice.py 2010-04-23 17:31:49 +0000
+++ lib/lp/testing/_webservice.py 2010-04-29 19:44:31 +0000
@@ -11,6 +11,8 @@
11 'oauth_access_token_for',11 'oauth_access_token_for',
12 ]12 ]
1313
14
15import transaction
14from zope.component import getUtility16from zope.component import getUtility
15from launchpadlib.credentials import AccessToken, Credentials17from launchpadlib.credentials import AccessToken, Credentials
16from launchpadlib.launchpad import Launchpad18from launchpadlib.launchpad import Launchpad
@@ -117,5 +119,6 @@
117 """119 """
118 credentials = launchpadlib_credentials_for(120 credentials = launchpadlib_credentials_for(
119 consumer_name, person, permission, context)121 consumer_name, person, permission, context)
122 transaction.commit()
120 version = version or Launchpad.DEFAULT_VERSION123 version = version or Launchpad.DEFAULT_VERSION
121 return Launchpad(credentials, service_root, version=version)124 return Launchpad(credentials, service_root, version=version)
122125
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-04-26 15:02:03 +0000
+++ lib/lp/testing/factory.py 2010-04-29 19:44:31 +0000
@@ -1637,7 +1637,7 @@
1637 """Create and return a new arbitrary archive.1637 """Create and return a new arbitrary archive.
16381638
1639 :param distribution: Supply IDistribution, defaults to a new one1639 :param distribution: Supply IDistribution, defaults to a new one
1640 made with makeDistribution().1640 made with makeDistribution() for non-PPAs and ubuntu for PPAs.
1641 :param owner: Supper IPerson, defaults to a new one made with1641 :param owner: Supper IPerson, defaults to a new one made with
1642 makePerson().1642 makePerson().
1643 :param name: Name of the archive, defaults to a random string.1643 :param name: Name of the archive, defaults to a random string.
@@ -1647,16 +1647,20 @@
1647 :param virtualized: Whether the archive is virtualized.1647 :param virtualized: Whether the archive is virtualized.
1648 :param description: A description of the archive.1648 :param description: A description of the archive.
1649 """1649 """
1650 if distribution is None:
1651 distribution = self.makeDistribution()
1652 if owner is None:
1653 owner = self.makePerson()
1654 if purpose is None:1650 if purpose is None:
1655 purpose = ArchivePurpose.PPA1651 purpose = ArchivePurpose.PPA
1652 if distribution is None:
1653 # See bug #568769
1654 if purpose == ArchivePurpose.PPA:
1655 distribution = getUtility(ILaunchpadCelebrities).ubuntu
1656 else:
1657 distribution = self.makeDistribution()
1658 if owner is None:
1659 owner = self.makePerson()
1656 if name is None:1660 if name is None:
1657 try:1661 if purpose != ArchivePurpose.PPA:
1658 name = default_name_by_purpose[purpose]1662 name = default_name_by_purpose.get(purpose)
1659 except KeyError:1663 if name is None:
1660 name = self.getUniqueString()1664 name = self.getUniqueString()
16611665
1662 # Making a distribution makes an archive, and there can be only one1666 # Making a distribution makes an archive, and there can be only one