Merge lp:~wallyworld/launchpad/recipe-webservice-api into lp:launchpad
- recipe-webservice-api
- Merge into devel
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Approved by: | Ian Booth | ||||||||
Approved revision: | no longer in the source branch. | ||||||||
Merged at revision: | 12459 | ||||||||
Proposed branch: | lp:~wallyworld/launchpad/recipe-webservice-api | ||||||||
Merge into: | lp:launchpad | ||||||||
Diff against target: |
758 lines (+201/-99) 20 files modified
lib/canonical/launchpad/interfaces/_schema_circular_imports.py (+17/-0) lib/lp/code/browser/branch.py (+1/-1) lib/lp/code/browser/sourcepackagerecipe.py (+3/-3) lib/lp/code/browser/sourcepackagerecipelisting.py (+2/-2) lib/lp/code/browser/tests/test_sourcepackagerecipe.py (+13/-11) lib/lp/code/interfaces/hasrecipes.py (+12/-3) lib/lp/code/interfaces/sourcepackagerecipe.py (+34/-9) lib/lp/code/model/branch.py (+3/-2) lib/lp/code/model/sourcepackagerecipe.py (+36/-19) lib/lp/code/model/tests/test_hasrecipes.py (+9/-15) lib/lp/code/model/tests/test_sourcepackagerecipe.py (+50/-14) lib/lp/code/scripts/tests/test_request_daily_builds.py (+5/-11) lib/lp/code/stories/webservice/xx-branch.txt (+2/-1) lib/lp/code/templates/sourcepackagerecipe-listing.pt (+1/-1) lib/lp/codehosting/scanner/bzrsync.py (+1/-1) lib/lp/registry/model/person.py (+4/-3) lib/lp/registry/model/product.py (+2/-1) lib/lp/registry/stories/webservice/xx-person.txt (+2/-0) lib/lp/registry/stories/webservice/xx-project-registry.txt (+2/-0) lib/lp/registry/tests/test_person.py (+2/-2) |
||||||||
To merge this branch: | bzr merge lp:~wallyworld/launchpad/recipe-webservice-api | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Leonard Richardson (community) | Approve | ||
Review via email: mp+50684@code.launchpad.net |
Commit message
[r=leonardr][bug=712329,712331] Add extra recipe methods to the web service.
Description of the change
Simple change to export via the webservice some recipe apis to list builds and recipes. See bugs 712329 and 712331.
== Implementation ==
Initial review discussions indicated that the current methods getBuilds() and getPendingBuilds() were poorly named for exporting. So the following methods were introduced:
getBuilds() - new, returns all builds
getPendingBuilds() - unchanged, returns builds where status == needsbuild
getCompleteBuilds() - renamed from getBuilds(), returns builds where status != needsbuild
Add required decorators to the exported methods:
IHasRecipe.
ISourcePackageR
ISourcePackageR
ISourcePackageR
ISourcePackageR
An additional small change was made to the order by statement used by the queries. The secondary order by term, BuildFarmJob.id, was changed to Desc() so that the semantics match the order imposed by the start_date, create_date ordering ie later builds go first
== Tests ==
Add/modify tests in test_sourcepack
test_getBuilds
test_getRecipes
test_requestBuild
bin/test -vvt test_sourcepack
== Lint ==
Linting changed files:
lib/canonical
lib/lp/
lib/lp/
lib/lp/
./lib/canonical
551: E501 line too long (82 characters)
551: Line exceeds 78 characters.
./lib/lp/
21: E302 expected 2 blank lines, found 1
./lib/lp/
185: E501 line too long (83 characters)
185: Line exceeds 78 characters.
Robert Collins (lifeless) wrote : | # |
Ian Booth (wallyworld) wrote : | # |
Hi Robert
I was implementing what the bug report (712331) asked for ie publish
ISourcePackageR
getBuildRecords() certainly is very different as you say, however this
method is exposed via the web service already.
On 22/02/11 16:54, Robert Collins wrote:
> Just to mention, this seems gratuitiously different to the existing api for getBuildRecords, which we could expose on your context - conceptually an IHasBuildRecords or similar. Is there a reason for this?
Leonard Richardson (leonardr) wrote : | # |
requestBuild is a classic case of a named operation, but there's no reason to publish the others as named operations.
They don't change the dataset and they don't take any arguments. Let's publish them as scoped collections and keep our public API cleaner:
recipes
builds
completed_builds
pending_builds
And a link:
last_build
To do this, define fields by these names in the interfaces, and implement them with @property method definitions that invoke the method behind the scenes.
@property
def recipes(self):
return self.getRecipes()
Or, if possible, get rid of getRecipes() altogether, and just implement .recipes for both the internal and external APIs. So 'recipes' would contain the getRecipes() implementation instead of invoking getRecipes().
Ian Booth (wallyworld) wrote : | # |
> requestBuild is a classic case of a named operation, but there's no reason to
> publish the others as named operations.
> They don't change the dataset and they don't take any arguments. Let's publish
> them as scoped collections and keep our public API cleaner:
>
> recipes
> builds
> completed_builds
> pending_builds
>
> And a link:
>
> last_build
>
Hi Leonard,
I have converted the getter methods to be properties as discussed. Tests pass and running up an instance locally works fine too. I ran some additional tests: test_person, test_hasrecipes, test_request_
Leonard Richardson (leonardr) wrote : | # |
This looks great now. My only comment is that "sorted by 'desc(date_
Robert Collins (lifeless) wrote : | # |
On Thu, Feb 24, 2011 at 2:18 AM, Leonard Richardson
<email address hidden> wrote:
> Review: Approve
> This looks great now. My only comment is that "sorted by 'desc(date_
An interesting thing about phrasing it like that, is that it makes it
obvious that the sort is inefficient: better to just sort on Desc(id)
- that is precisely the same sort because ids are allocated
sequentially, and under half the work for the DB.
Ian Booth (wallyworld) wrote : | # |
>
> An interesting thing about phrasing it like that, is that it makes it
> obvious that the sort is inefficient: better to just sort on Desc(id)
> - that is precisely the same sort because ids are allocated
> sequentially, and under half the work for the DB.
Done.
Preview Diff
1 | === modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py' | |||
2 | --- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2011-02-02 09:50:58 +0000 | |||
3 | +++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2011-02-24 23:45:43 +0000 | |||
4 | @@ -74,6 +74,7 @@ | |||
5 | 74 | IHasMergeProposals, | 74 | IHasMergeProposals, |
6 | 75 | IHasRequestedReviews, | 75 | IHasRequestedReviews, |
7 | 76 | ) | 76 | ) |
8 | 77 | from lp.code.interfaces.hasrecipes import IHasRecipes | ||
9 | 77 | from lp.code.interfaces.sourcepackagerecipe import ISourcePackageRecipe | 78 | from lp.code.interfaces.sourcepackagerecipe import ISourcePackageRecipe |
10 | 78 | from lp.code.interfaces.sourcepackagerecipebuild import ( | 79 | from lp.code.interfaces.sourcepackagerecipebuild import ( |
11 | 79 | ISourcePackageRecipeBuild, | 80 | ISourcePackageRecipeBuild, |
12 | @@ -262,6 +263,10 @@ | |||
13 | 262 | 263 | ||
14 | 263 | patch_entry_return_type(IPerson, 'getRecipe', ISourcePackageRecipe) | 264 | patch_entry_return_type(IPerson, 'getRecipe', ISourcePackageRecipe) |
15 | 264 | 265 | ||
16 | 266 | # IHasRecipe | ||
17 | 267 | patch_collection_property( | ||
18 | 268 | IHasRecipes, 'recipes', ISourcePackageRecipe) | ||
19 | 269 | |||
20 | 265 | IPerson['hardware_submissions'].value_type.schema = IHWSubmission | 270 | IPerson['hardware_submissions'].value_type.schema = IHWSubmission |
21 | 266 | 271 | ||
22 | 267 | # publishing.py | 272 | # publishing.py |
23 | @@ -440,6 +445,18 @@ | |||
24 | 440 | ISourcePackageRelease, 'source_package_recipe_build', | 445 | ISourcePackageRelease, 'source_package_recipe_build', |
25 | 441 | ISourcePackageRecipeBuild) | 446 | ISourcePackageRecipeBuild) |
26 | 442 | 447 | ||
27 | 448 | # ISourcePackageRecipeView | ||
28 | 449 | patch_entry_return_type( | ||
29 | 450 | ISourcePackageRecipe, 'requestBuild', ISourcePackageRecipeBuild) | ||
30 | 451 | patch_reference_property( | ||
31 | 452 | ISourcePackageRecipe, 'last_build', ISourcePackageRecipeBuild) | ||
32 | 453 | patch_collection_property( | ||
33 | 454 | ISourcePackageRecipe, 'builds', ISourcePackageRecipeBuild) | ||
34 | 455 | patch_collection_property( | ||
35 | 456 | ISourcePackageRecipe, 'pending_builds', ISourcePackageRecipeBuild) | ||
36 | 457 | patch_collection_property( | ||
37 | 458 | ISourcePackageRecipe, 'completed_builds', ISourcePackageRecipeBuild) | ||
38 | 459 | |||
39 | 443 | # IHasBugs | 460 | # IHasBugs |
40 | 444 | patch_plain_parameter_type( | 461 | patch_plain_parameter_type( |
41 | 445 | IHasBugs, 'searchTasks', 'assignee', IPerson) | 462 | IHasBugs, 'searchTasks', 'assignee', IPerson) |
42 | 446 | 463 | ||
43 | === modified file 'lib/lp/code/browser/branch.py' | |||
44 | --- lib/lp/code/browser/branch.py 2011-02-17 03:29:50 +0000 | |||
45 | +++ lib/lp/code/browser/branch.py 2011-02-24 23:45:43 +0000 | |||
46 | @@ -548,7 +548,7 @@ | |||
47 | 548 | 548 | ||
48 | 549 | @property | 549 | @property |
49 | 550 | def recipe_count_text(self): | 550 | def recipe_count_text(self): |
51 | 551 | count = self.context.getRecipes().count() | 551 | count = self.context.recipes.count() |
52 | 552 | if count == 0: | 552 | if count == 0: |
53 | 553 | return 'No recipes' | 553 | return 'No recipes' |
54 | 554 | elif count == 1: | 554 | elif count == 1: |
55 | 555 | 555 | ||
56 | === modified file 'lib/lp/code/browser/sourcepackagerecipe.py' | |||
57 | --- lib/lp/code/browser/sourcepackagerecipe.py 2011-02-23 02:08:58 +0000 | |||
58 | +++ lib/lp/code/browser/sourcepackagerecipe.py 2011-02-24 23:45:43 +0000 | |||
59 | @@ -299,8 +299,8 @@ | |||
60 | 299 | This allows started but unfinished builds to show up in the view but | 299 | This allows started but unfinished builds to show up in the view but |
61 | 300 | be discarded as more recent builds become available. | 300 | be discarded as more recent builds become available. |
62 | 301 | """ | 301 | """ |
65 | 302 | builds = list(recipe.getPendingBuilds()) | 302 | builds = list(recipe.pending_builds) |
66 | 303 | for build in recipe.getBuilds(): | 303 | for build in recipe.completed_builds: |
67 | 304 | builds.append(build) | 304 | builds.append(build) |
68 | 305 | if len(builds) >= 5: | 305 | if len(builds) >= 5: |
69 | 306 | break | 306 | break |
70 | @@ -328,7 +328,7 @@ | |||
71 | 328 | The distroseries function as defaults for requesting a build. | 328 | The distroseries function as defaults for requesting a build. |
72 | 329 | """ | 329 | """ |
73 | 330 | initial_values = {'distros': self.context.distroseries} | 330 | initial_values = {'distros': self.context.distroseries} |
75 | 331 | build = self.context.getLastBuild() | 331 | build = self.context.last_build |
76 | 332 | if build is not None: | 332 | if build is not None: |
77 | 333 | initial_values['archive'] = build.archive | 333 | initial_values['archive'] = build.archive |
78 | 334 | return initial_values | 334 | return initial_values |
79 | 335 | 335 | ||
80 | === modified file 'lib/lp/code/browser/sourcepackagerecipelisting.py' | |||
81 | --- lib/lp/code/browser/sourcepackagerecipelisting.py 2011-02-17 03:17:54 +0000 | |||
82 | +++ lib/lp/code/browser/sourcepackagerecipelisting.py 2011-02-24 23:45:43 +0000 | |||
83 | @@ -29,7 +29,7 @@ | |||
84 | 29 | def view_recipes(self): | 29 | def view_recipes(self): |
85 | 30 | text = 'View source package recipes' | 30 | text = 'View source package recipes' |
86 | 31 | enabled = False | 31 | enabled = False |
88 | 32 | if self.context.getRecipes().count(): | 32 | if self.context.recipes.count(): |
89 | 33 | enabled = True | 33 | enabled = True |
90 | 34 | if not getFeatureFlag(RECIPE_ENABLED_FLAG): | 34 | if not getFeatureFlag(RECIPE_ENABLED_FLAG): |
91 | 35 | enabled = False | 35 | enabled = False |
92 | @@ -51,7 +51,7 @@ | |||
93 | 51 | 51 | ||
94 | 52 | def initialize(self): | 52 | def initialize(self): |
95 | 53 | super(RecipeListingView, self).initialize() | 53 | super(RecipeListingView, self).initialize() |
97 | 54 | recipes = self.context.getRecipes() | 54 | recipes = self.context.recipes |
98 | 55 | if recipes.count() == 1: | 55 | if recipes.count() == 1: |
99 | 56 | recipe = recipes.one() | 56 | recipe = recipes.one() |
100 | 57 | self.request.response.redirect(canonical_url(recipe)) | 57 | self.request.response.redirect(canonical_url(recipe)) |
101 | 58 | 58 | ||
102 | === modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py' | |||
103 | --- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2011-02-22 23:07:17 +0000 | |||
104 | +++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2011-02-24 23:45:43 +0000 | |||
105 | @@ -1194,8 +1194,10 @@ | |||
106 | 1194 | def test_builds(self): | 1194 | def test_builds(self): |
107 | 1195 | """Ensure SourcePackageRecipeView.builds is as described.""" | 1195 | """Ensure SourcePackageRecipeView.builds is as described.""" |
108 | 1196 | recipe = self.makeRecipe() | 1196 | recipe = self.makeRecipe() |
109 | 1197 | # We create builds in time ascending order (oldest first) since we | ||
110 | 1198 | # use id as the ordering attribute and lower ids mean created earlier. | ||
111 | 1197 | date_gen = time_counter( | 1199 | date_gen = time_counter( |
113 | 1198 | datetime(2010, 03, 16, tzinfo=utc), timedelta(days=-1)) | 1200 | datetime(2010, 03, 16, tzinfo=utc), timedelta(days=1)) |
114 | 1199 | build1 = self.makeBuildJob(recipe, date_gen.next()) | 1201 | build1 = self.makeBuildJob(recipe, date_gen.next()) |
115 | 1200 | build2 = self.makeBuildJob(recipe, date_gen.next()) | 1202 | build2 = self.makeBuildJob(recipe, date_gen.next()) |
116 | 1201 | build3 = self.makeBuildJob(recipe, date_gen.next()) | 1203 | build3 = self.makeBuildJob(recipe, date_gen.next()) |
117 | @@ -1204,7 +1206,7 @@ | |||
118 | 1204 | build6 = self.makeBuildJob(recipe, date_gen.next()) | 1206 | build6 = self.makeBuildJob(recipe, date_gen.next()) |
119 | 1205 | view = SourcePackageRecipeView(recipe, None) | 1207 | view = SourcePackageRecipeView(recipe, None) |
120 | 1206 | self.assertEqual( | 1208 | self.assertEqual( |
122 | 1207 | [build1, build2, build3, build4, build5, build6], | 1209 | [build6, build5, build4, build3, build2, build1], |
123 | 1208 | view.builds) | 1210 | view.builds) |
124 | 1209 | 1211 | ||
125 | 1210 | def set_status(build, status): | 1212 | def set_status(build, status): |
126 | @@ -1214,19 +1216,19 @@ | |||
127 | 1214 | if status == BuildStatus.FULLYBUILT: | 1216 | if status == BuildStatus.FULLYBUILT: |
128 | 1215 | naked_build.date_finished = ( | 1217 | naked_build.date_finished = ( |
129 | 1216 | naked_build.date_created + timedelta(minutes=10)) | 1218 | naked_build.date_created + timedelta(minutes=10)) |
132 | 1217 | set_status(build1, BuildStatus.FULLYBUILT) | 1219 | set_status(build6, BuildStatus.FULLYBUILT) |
133 | 1218 | set_status(build2, BuildStatus.FAILEDTOBUILD) | 1220 | set_status(build5, BuildStatus.FAILEDTOBUILD) |
134 | 1219 | # When there are 4+ pending builds, only the the most | 1221 | # When there are 4+ pending builds, only the the most |
135 | 1220 | # recently-completed build is returned (i.e. build1, not build2) | 1222 | # recently-completed build is returned (i.e. build1, not build2) |
136 | 1221 | self.assertEqual( | 1223 | self.assertEqual( |
138 | 1222 | [build3, build4, build5, build6, build1], | 1224 | [build4, build3, build2, build1, build6], |
139 | 1223 | view.builds) | 1225 | view.builds) |
140 | 1226 | set_status(build4, BuildStatus.FULLYBUILT) | ||
141 | 1224 | set_status(build3, BuildStatus.FULLYBUILT) | 1227 | set_status(build3, BuildStatus.FULLYBUILT) |
145 | 1225 | set_status(build4, BuildStatus.FULLYBUILT) | 1228 | set_status(build2, BuildStatus.FULLYBUILT) |
146 | 1226 | set_status(build5, BuildStatus.FULLYBUILT) | 1229 | set_status(build1, BuildStatus.FULLYBUILT) |
144 | 1227 | set_status(build6, BuildStatus.FULLYBUILT) | ||
147 | 1228 | self.assertEqual( | 1230 | self.assertEqual( |
149 | 1229 | [build1, build2, build3, build4, build5], view.builds) | 1231 | [build6, build5, build4, build3, build2], view.builds) |
150 | 1230 | 1232 | ||
151 | 1231 | def test_request_daily_builds_button_stale(self): | 1233 | def test_request_daily_builds_button_stale(self): |
152 | 1232 | # Recipes that are stale and are built daily have a build now link | 1234 | # Recipes that are stale and are built daily have a build now link |
153 | @@ -1300,7 +1302,7 @@ | |||
154 | 1300 | browser = self.getViewBrowser(recipe) | 1302 | browser = self.getViewBrowser(recipe) |
155 | 1301 | browser.getControl('Build now').click() | 1303 | browser.getControl('Build now').click() |
156 | 1302 | login(ANONYMOUS) | 1304 | login(ANONYMOUS) |
158 | 1303 | builds = recipe.getPendingBuilds() | 1305 | builds = recipe.pending_builds |
159 | 1304 | build_distros = [ | 1306 | build_distros = [ |
160 | 1305 | build.distroseries.displayname for build in builds] | 1307 | build.distroseries.displayname for build in builds] |
161 | 1306 | build_distros.sort() | 1308 | build_distros.sort() |
162 | @@ -1346,7 +1348,7 @@ | |||
163 | 1346 | browser.getControl('Request builds').click() | 1348 | browser.getControl('Request builds').click() |
164 | 1347 | 1349 | ||
165 | 1348 | login(ANONYMOUS) | 1350 | login(ANONYMOUS) |
167 | 1349 | builds = recipe.getPendingBuilds() | 1351 | builds = recipe.pending_builds |
168 | 1350 | build_distros = [ | 1352 | build_distros = [ |
169 | 1351 | build.distroseries.displayname for build in builds] | 1353 | build.distroseries.displayname for build in builds] |
170 | 1352 | build_distros.sort() | 1354 | build_distros.sort() |
171 | 1353 | 1355 | ||
172 | === modified file 'lib/lp/code/interfaces/hasrecipes.py' | |||
173 | --- lib/lp/code/interfaces/hasrecipes.py 2010-08-20 20:31:18 +0000 | |||
174 | +++ lib/lp/code/interfaces/hasrecipes.py 2011-02-24 23:45:43 +0000 | |||
175 | @@ -10,13 +10,22 @@ | |||
176 | 10 | 10 | ||
177 | 11 | 11 | ||
178 | 12 | from zope.interface import ( | 12 | from zope.interface import ( |
179 | 13 | Attribute, | ||
180 | 14 | Interface, | 13 | Interface, |
181 | 15 | ) | 14 | ) |
182 | 16 | 15 | ||
183 | 16 | from lazr.restful.declarations import exported | ||
184 | 17 | from lazr.restful.fields import ( | ||
185 | 18 | CollectionField, | ||
186 | 19 | Reference, | ||
187 | 20 | ) | ||
188 | 21 | |||
189 | 22 | from canonical.launchpad import _ | ||
190 | 17 | 23 | ||
191 | 18 | class IHasRecipes(Interface): | 24 | class IHasRecipes(Interface): |
192 | 19 | """An object that has recipes.""" | 25 | """An object that has recipes.""" |
193 | 20 | 26 | ||
196 | 21 | def getRecipes(): | 27 | recipes = exported( |
197 | 22 | """Returns all recipes associated with the object.""" | 28 | CollectionField( |
198 | 29 | title=_("All recipes associated with the object."), | ||
199 | 30 | value_type=Reference(schema=Interface), | ||
200 | 31 | readonly=True)) | ||
201 | 23 | 32 | ||
202 | === modified file 'lib/lp/code/interfaces/sourcepackagerecipe.py' | |||
203 | --- lib/lp/code/interfaces/sourcepackagerecipe.py 2011-02-22 23:07:17 +0000 | |||
204 | +++ lib/lp/code/interfaces/sourcepackagerecipe.py 2011-02-24 23:45:43 +0000 | |||
205 | @@ -29,6 +29,7 @@ | |||
206 | 29 | mutator_for, | 29 | mutator_for, |
207 | 30 | operation_for_version, | 30 | operation_for_version, |
208 | 31 | operation_parameters, | 31 | operation_parameters, |
209 | 32 | operation_returns_entry, | ||
210 | 32 | REQUEST_USER, | 33 | REQUEST_USER, |
211 | 33 | ) | 34 | ) |
212 | 34 | from lazr.restful.fields import ( | 35 | from lazr.restful.fields import ( |
213 | @@ -104,6 +105,38 @@ | |||
214 | 104 | 105 | ||
215 | 105 | recipe_text = exported(Text(readonly=True)) | 106 | recipe_text = exported(Text(readonly=True)) |
216 | 106 | 107 | ||
217 | 108 | pending_builds = exported( | ||
218 | 109 | CollectionField( | ||
219 | 110 | title=_("The pending builds of this recipe."), | ||
220 | 111 | description=_('Pending builds of this recipe, sorted in ' | ||
221 | 112 | 'descending order of creation.'), | ||
222 | 113 | value_type=Reference(schema=Interface), | ||
223 | 114 | readonly=True)) | ||
224 | 115 | |||
225 | 116 | completed_builds = exported( | ||
226 | 117 | CollectionField( | ||
227 | 118 | title=_("The completed builds of this recipe."), | ||
228 | 119 | description=_('Completed builds of this recipe, sorted in ' | ||
229 | 120 | 'descending order of finishing (or starting if not' | ||
230 | 121 | 'completed successfully).'), | ||
231 | 122 | value_type=Reference(schema=Interface), | ||
232 | 123 | readonly=True)) | ||
233 | 124 | |||
234 | 125 | builds = exported( | ||
235 | 126 | CollectionField( | ||
236 | 127 | title=_("All builds of this recipe."), | ||
237 | 128 | description=_('All builds of this recipe, sorted in ' | ||
238 | 129 | 'descending order of finishing (or starting if not' | ||
239 | 130 | 'completed successfully).'), | ||
240 | 131 | value_type=Reference(schema=Interface), | ||
241 | 132 | readonly=True)) | ||
242 | 133 | |||
243 | 134 | last_build = exported( | ||
244 | 135 | Reference( | ||
245 | 136 | Interface, | ||
246 | 137 | title=_("The the most recent build of this recipe."), | ||
247 | 138 | readonly=True)) | ||
248 | 139 | |||
249 | 107 | def isOverQuota(requester, distroseries): | 140 | def isOverQuota(requester, distroseries): |
250 | 108 | """True if the recipe/requester/distroseries combo is >= quota. | 141 | """True if the recipe/requester/distroseries combo is >= quota. |
251 | 109 | 142 | ||
252 | @@ -111,20 +144,12 @@ | |||
253 | 111 | :param distroseries: The distroseries to build for. | 144 | :param distroseries: The distroseries to build for. |
254 | 112 | """ | 145 | """ |
255 | 113 | 146 | ||
256 | 114 | def getBuilds(): | ||
257 | 115 | """Return a ResultSet of all the non-pending builds.""" | ||
258 | 116 | |||
259 | 117 | def getPendingBuilds(): | ||
260 | 118 | """Return a ResultSet of all the pending builds.""" | ||
261 | 119 | |||
262 | 120 | def getLastBuild(): | ||
263 | 121 | """Return the the most recent build of this recipe.""" | ||
264 | 122 | |||
265 | 123 | @call_with(requester=REQUEST_USER) | 147 | @call_with(requester=REQUEST_USER) |
266 | 124 | @operation_parameters( | 148 | @operation_parameters( |
267 | 125 | archive=Reference(schema=IArchive), | 149 | archive=Reference(schema=IArchive), |
268 | 126 | distroseries=Reference(schema=IDistroSeries), | 150 | distroseries=Reference(schema=IDistroSeries), |
269 | 127 | pocket=Choice(vocabulary=PackagePublishingPocket,)) | 151 | pocket=Choice(vocabulary=PackagePublishingPocket,)) |
270 | 152 | @operation_returns_entry(Interface) | ||
271 | 128 | @export_write_operation() | 153 | @export_write_operation() |
272 | 129 | def requestBuild(archive, distroseries, requester, pocket): | 154 | def requestBuild(archive, distroseries, requester, pocket): |
273 | 130 | """Request that the recipe be built in to the specified archive. | 155 | """Request that the recipe be built in to the specified archive. |
274 | 131 | 156 | ||
275 | === modified file 'lib/lp/code/model/branch.py' | |||
276 | --- lib/lp/code/model/branch.py 2011-01-25 18:59:18 +0000 | |||
277 | +++ lib/lp/code/model/branch.py 2011-02-24 23:45:43 +0000 | |||
278 | @@ -665,7 +665,7 @@ | |||
279 | 665 | map(ClearOfficialPackageBranch, series_set.findForBranch(self))) | 665 | map(ClearOfficialPackageBranch, series_set.findForBranch(self))) |
280 | 666 | deletion_operations.extend( | 666 | deletion_operations.extend( |
281 | 667 | DeletionCallable.forSourcePackageRecipe(recipe) | 667 | DeletionCallable.forSourcePackageRecipe(recipe) |
283 | 668 | for recipe in self.getRecipes()) | 668 | for recipe in self.recipes) |
284 | 669 | return (alteration_operations, deletion_operations) | 669 | return (alteration_operations, deletion_operations) |
285 | 670 | 670 | ||
286 | 671 | def deletionRequirements(self): | 671 | def deletionRequirements(self): |
287 | @@ -1160,7 +1160,8 @@ | |||
288 | 1160 | user, checked_branches) | 1160 | user, checked_branches) |
289 | 1161 | return can_access | 1161 | return can_access |
290 | 1162 | 1162 | ||
292 | 1163 | def getRecipes(self): | 1163 | @property |
293 | 1164 | def recipes(self): | ||
294 | 1164 | """See `IHasRecipes`.""" | 1165 | """See `IHasRecipes`.""" |
295 | 1165 | from lp.code.model.sourcepackagerecipedata import ( | 1166 | from lp.code.model.sourcepackagerecipedata import ( |
296 | 1166 | SourcePackageRecipeData) | 1167 | SourcePackageRecipeData) |
297 | 1167 | 1168 | ||
298 | === modified file 'lib/lp/code/model/sourcepackagerecipe.py' | |||
299 | --- lib/lp/code/model/sourcepackagerecipe.py 2011-02-19 00:30:04 +0000 | |||
300 | +++ lib/lp/code/model/sourcepackagerecipe.py 2011-02-24 23:45:43 +0000 | |||
301 | @@ -295,34 +295,51 @@ | |||
302 | 295 | continue | 295 | continue |
303 | 296 | return builds | 296 | return builds |
304 | 297 | 297 | ||
320 | 298 | def getBuilds(self): | 298 | @property |
321 | 299 | """See `ISourcePackageRecipe`.""" | 299 | def builds(self): |
322 | 300 | where_clause = BuildFarmJob.status != BuildStatus.NEEDSBUILD | 300 | """See `ISourcePackageRecipe`.""" |
323 | 301 | order_by = Desc(Greatest( | 301 | order_by = (Desc(Greatest( |
324 | 302 | BuildFarmJob.date_started, | 302 | BuildFarmJob.date_started, |
325 | 303 | BuildFarmJob.date_finished)), BuildFarmJob.id | 303 | BuildFarmJob.date_finished)), |
326 | 304 | return self._getBuilds(where_clause, order_by) | 304 | Desc(BuildFarmJob.date_created), Desc(BuildFarmJob.id)) |
327 | 305 | 305 | return self._getBuilds(None, order_by) | |
328 | 306 | def getPendingBuilds(self): | 306 | |
329 | 307 | """See `ISourcePackageRecipe`.""" | 307 | @property |
330 | 308 | where_clause = BuildFarmJob.status == BuildStatus.NEEDSBUILD | 308 | def completed_builds(self): |
331 | 309 | order_by = Desc(BuildFarmJob.date_created), BuildFarmJob.id | 309 | """See `ISourcePackageRecipe`.""" |
332 | 310 | return self._getBuilds(where_clause, order_by) | 310 | filter_term = BuildFarmJob.status != BuildStatus.NEEDSBUILD |
333 | 311 | 311 | order_by = (Desc(Greatest( | |
334 | 312 | def _getBuilds(self, where_clause, order_by): | 312 | BuildFarmJob.date_started, |
335 | 313 | BuildFarmJob.date_finished)), | ||
336 | 314 | Desc(BuildFarmJob.id)) | ||
337 | 315 | return self._getBuilds(filter_term, order_by) | ||
338 | 316 | |||
339 | 317 | @property | ||
340 | 318 | def pending_builds(self): | ||
341 | 319 | """See `ISourcePackageRecipe`.""" | ||
342 | 320 | filter_term = BuildFarmJob.status == BuildStatus.NEEDSBUILD | ||
343 | 321 | # We want to order by date_created but this is the same as ordering | ||
344 | 322 | # by id (since id increases monotonically) and is less expensive. | ||
345 | 323 | order_by = Desc(BuildFarmJob.id) | ||
346 | 324 | return self._getBuilds(filter_term, order_by) | ||
347 | 325 | |||
348 | 326 | def _getBuilds(self, filter_term, order_by): | ||
349 | 313 | """The actual query to get the builds.""" | 327 | """The actual query to get the builds.""" |
352 | 314 | result = Store.of(self).find( | 328 | query_args = [ |
351 | 315 | SourcePackageRecipeBuild, | ||
353 | 316 | SourcePackageRecipeBuild.recipe==self, | 329 | SourcePackageRecipeBuild.recipe==self, |
354 | 317 | SourcePackageRecipeBuild.package_build_id == PackageBuild.id, | 330 | SourcePackageRecipeBuild.package_build_id == PackageBuild.id, |
355 | 318 | PackageBuild.build_farm_job_id == BuildFarmJob.id, | 331 | PackageBuild.build_farm_job_id == BuildFarmJob.id, |
356 | 319 | And(PackageBuild.archive_id == Archive.id, | 332 | And(PackageBuild.archive_id == Archive.id, |
357 | 320 | Archive._enabled == True), | 333 | Archive._enabled == True), |
359 | 321 | where_clause) | 334 | ] |
360 | 335 | if filter_term is not None: | ||
361 | 336 | query_args.append(filter_term) | ||
362 | 337 | result = Store.of(self).find(SourcePackageRecipeBuild, *query_args) | ||
363 | 322 | result.order_by(order_by) | 338 | result.order_by(order_by) |
364 | 323 | return result | 339 | return result |
365 | 324 | 340 | ||
367 | 325 | def getLastBuild(self): | 341 | @property |
368 | 342 | def last_build(self): | ||
369 | 326 | """See `ISourcePackageRecipeBuild`.""" | 343 | """See `ISourcePackageRecipeBuild`.""" |
370 | 327 | return self._getBuilds( | 344 | return self._getBuilds( |
371 | 328 | True, Desc(BuildFarmJob.date_finished)).first() | 345 | True, Desc(BuildFarmJob.date_finished)).first() |
372 | 329 | 346 | ||
373 | === modified file 'lib/lp/code/model/tests/test_hasrecipes.py' | |||
374 | --- lib/lp/code/model/tests/test_hasrecipes.py 2010-10-04 19:50:45 +0000 | |||
375 | +++ lib/lp/code/model/tests/test_hasrecipes.py 2011-02-24 23:45:43 +0000 | |||
376 | @@ -7,8 +7,6 @@ | |||
377 | 7 | 7 | ||
378 | 8 | __metaclass__ = type | 8 | __metaclass__ = type |
379 | 9 | 9 | ||
380 | 10 | import unittest | ||
381 | 11 | |||
382 | 12 | from canonical.testing.layers import DatabaseFunctionalLayer | 10 | from canonical.testing.layers import DatabaseFunctionalLayer |
383 | 13 | from lp.code.interfaces.hasrecipes import IHasRecipes | 11 | from lp.code.interfaces.hasrecipes import IHasRecipes |
384 | 14 | from lp.testing import TestCaseWithFactory | 12 | from lp.testing import TestCaseWithFactory |
385 | @@ -24,16 +22,16 @@ | |||
386 | 24 | branch = self.factory.makeBranch() | 22 | branch = self.factory.makeBranch() |
387 | 25 | self.assertProvides(branch, IHasRecipes) | 23 | self.assertProvides(branch, IHasRecipes) |
388 | 26 | 24 | ||
390 | 27 | def test_branch_getRecipes(self): | 25 | def test_branch_recipes(self): |
391 | 28 | # IBranch.recipes should provide all the SourcePackageRecipes attached | 26 | # IBranch.recipes should provide all the SourcePackageRecipes attached |
392 | 29 | # to that branch. | 27 | # to that branch. |
393 | 30 | base_branch = self.factory.makeBranch() | 28 | base_branch = self.factory.makeBranch() |
394 | 31 | recipe1 = self.factory.makeSourcePackageRecipe(branches=[base_branch]) | 29 | recipe1 = self.factory.makeSourcePackageRecipe(branches=[base_branch]) |
395 | 32 | recipe2 = self.factory.makeSourcePackageRecipe(branches=[base_branch]) | 30 | recipe2 = self.factory.makeSourcePackageRecipe(branches=[base_branch]) |
396 | 33 | recipe_ignored = self.factory.makeSourcePackageRecipe() | 31 | recipe_ignored = self.factory.makeSourcePackageRecipe() |
398 | 34 | self.assertEqual(2, base_branch.getRecipes().count()) | 32 | self.assertEqual(2, base_branch.recipes.count()) |
399 | 35 | 33 | ||
401 | 36 | def test_branch_getRecipes_nonbase(self): | 34 | def test_branch_recipes_nonbase(self): |
402 | 37 | # IBranch.recipes should provide all the SourcePackageRecipes | 35 | # IBranch.recipes should provide all the SourcePackageRecipes |
403 | 38 | # that refer to the branch, even as a non-base branch. | 36 | # that refer to the branch, even as a non-base branch. |
404 | 39 | base_branch = self.factory.makeBranch() | 37 | base_branch = self.factory.makeBranch() |
405 | @@ -41,28 +39,28 @@ | |||
406 | 41 | recipe = self.factory.makeSourcePackageRecipe( | 39 | recipe = self.factory.makeSourcePackageRecipe( |
407 | 42 | branches=[base_branch, nonbase_branch]) | 40 | branches=[base_branch, nonbase_branch]) |
408 | 43 | recipe_ignored = self.factory.makeSourcePackageRecipe() | 41 | recipe_ignored = self.factory.makeSourcePackageRecipe() |
410 | 44 | self.assertEqual(recipe, nonbase_branch.getRecipes().one()) | 42 | self.assertEqual(recipe, nonbase_branch.recipes.one()) |
411 | 45 | 43 | ||
412 | 46 | def test_person_implements_hasrecipes(self): | 44 | def test_person_implements_hasrecipes(self): |
413 | 47 | # Person should implement IHasRecipes. | 45 | # Person should implement IHasRecipes. |
414 | 48 | person = self.factory.makeBranch() | 46 | person = self.factory.makeBranch() |
415 | 49 | self.assertProvides(person, IHasRecipes) | 47 | self.assertProvides(person, IHasRecipes) |
416 | 50 | 48 | ||
419 | 51 | def test_person_getRecipes(self): | 49 | def test_person_recipes(self): |
420 | 52 | # IPerson.getRecipes should provide all the SourcePackageRecipes | 50 | # IPerson.recipes should provide all the SourcePackageRecipes |
421 | 53 | # owned by that person. | 51 | # owned by that person. |
422 | 54 | person = self.factory.makePerson() | 52 | person = self.factory.makePerson() |
423 | 55 | recipe1 = self.factory.makeSourcePackageRecipe(owner=person) | 53 | recipe1 = self.factory.makeSourcePackageRecipe(owner=person) |
424 | 56 | recipe2 = self.factory.makeSourcePackageRecipe(owner=person) | 54 | recipe2 = self.factory.makeSourcePackageRecipe(owner=person) |
425 | 57 | recipe_ignored = self.factory.makeSourcePackageRecipe() | 55 | recipe_ignored = self.factory.makeSourcePackageRecipe() |
427 | 58 | self.assertEqual(2, person.getRecipes().count()) | 56 | self.assertEqual(2, person.recipes.count()) |
428 | 59 | 57 | ||
429 | 60 | def test_product_implements_hasrecipes(self): | 58 | def test_product_implements_hasrecipes(self): |
430 | 61 | # Product should implement IHasRecipes. | 59 | # Product should implement IHasRecipes. |
431 | 62 | product = self.factory.makeProduct() | 60 | product = self.factory.makeProduct() |
432 | 63 | self.assertProvides(product, IHasRecipes) | 61 | self.assertProvides(product, IHasRecipes) |
433 | 64 | 62 | ||
435 | 65 | def test_product_getRecipes(self): | 63 | def test_product_recipes(self): |
436 | 66 | # IProduct.recipes should provide all the SourcePackageRecipes | 64 | # IProduct.recipes should provide all the SourcePackageRecipes |
437 | 67 | # attached to that product's branches. | 65 | # attached to that product's branches. |
438 | 68 | product = self.factory.makeProduct() | 66 | product = self.factory.makeProduct() |
439 | @@ -70,8 +68,4 @@ | |||
440 | 70 | recipe1 = self.factory.makeSourcePackageRecipe(branches=[branch]) | 68 | recipe1 = self.factory.makeSourcePackageRecipe(branches=[branch]) |
441 | 71 | recipe2 = self.factory.makeSourcePackageRecipe(branches=[branch]) | 69 | recipe2 = self.factory.makeSourcePackageRecipe(branches=[branch]) |
442 | 72 | recipe_ignored = self.factory.makeSourcePackageRecipe() | 70 | recipe_ignored = self.factory.makeSourcePackageRecipe() |
448 | 73 | self.assertEqual(2, product.getRecipes().count()) | 71 | self.assertEqual(2, product.recipes.count()) |
444 | 74 | |||
445 | 75 | |||
446 | 76 | def test_suite(): | ||
447 | 77 | return unittest.TestLoader().loadTestsFromName(__name__) | ||
449 | 78 | 72 | ||
450 | === modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py' | |||
451 | --- lib/lp/code/model/tests/test_sourcepackagerecipe.py 2011-02-18 04:25:00 +0000 | |||
452 | +++ lib/lp/code/model/tests/test_sourcepackagerecipe.py 2011-02-24 23:45:43 +0000 | |||
453 | @@ -10,7 +10,6 @@ | |||
454 | 10 | timedelta, | 10 | timedelta, |
455 | 11 | ) | 11 | ) |
456 | 12 | import textwrap | 12 | import textwrap |
457 | 13 | import unittest | ||
458 | 14 | 13 | ||
459 | 15 | from bzrlib.plugins.builder.recipe import ForbiddenInstructionError | 14 | from bzrlib.plugins.builder.recipe import ForbiddenInstructionError |
460 | 16 | from pytz import UTC | 15 | from pytz import UTC |
461 | @@ -582,19 +581,32 @@ | |||
462 | 582 | timedelta(minutes=11), recipe.getMedianBuildDuration()) | 581 | timedelta(minutes=11), recipe.getMedianBuildDuration()) |
463 | 583 | 582 | ||
464 | 584 | def test_getBuilds(self): | 583 | def test_getBuilds(self): |
466 | 585 | # Builds that need building are pending. | 584 | # Test the various getBuilds methods. |
467 | 586 | recipe = self.factory.makeSourcePackageRecipe() | 585 | recipe = self.factory.makeSourcePackageRecipe() |
471 | 587 | build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe) | 586 | builds = [ |
472 | 588 | self.assertEqual([], list(recipe.getBuilds())) | 587 | self.factory.makeSourcePackageRecipeBuild(recipe=recipe) |
473 | 589 | self.assertEqual([build], list(recipe.getPendingBuilds())) | 588 | for x in range(3)] |
474 | 589 | # We want the latest builds first. | ||
475 | 590 | builds.reverse() | ||
476 | 591 | |||
477 | 592 | self.assertEqual([], list(recipe.completed_builds)) | ||
478 | 593 | self.assertEqual(builds, list(recipe.pending_builds)) | ||
479 | 594 | self.assertEqual(builds, list(recipe.builds)) | ||
480 | 595 | |||
481 | 596 | # Change the status of one of the builds and retest. | ||
482 | 597 | removeSecurityProxy(builds[0]).status = BuildStatus.FULLYBUILT | ||
483 | 598 | self.assertEqual([builds[0]], list(recipe.completed_builds)) | ||
484 | 599 | self.assertEqual(builds[1:], list(recipe.pending_builds)) | ||
485 | 600 | self.assertEqual(builds, list(recipe.builds)) | ||
486 | 590 | 601 | ||
487 | 591 | def test_getBuilds_cancelled(self): | 602 | def test_getBuilds_cancelled(self): |
488 | 592 | # Cancelled builds are not considered pending. | 603 | # Cancelled builds are not considered pending. |
489 | 593 | recipe = self.factory.makeSourcePackageRecipe() | 604 | recipe = self.factory.makeSourcePackageRecipe() |
490 | 594 | build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe) | 605 | build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe) |
491 | 595 | build.cancelBuild() | 606 | build.cancelBuild() |
494 | 596 | self.assertEqual([build], list(recipe.getBuilds())) | 607 | self.assertEqual([build], list(recipe.builds)) |
495 | 597 | self.assertEqual([], list(recipe.getPendingBuilds())) | 608 | self.assertEqual([build], list(recipe.completed_builds)) |
496 | 609 | self.assertEqual([], list(recipe.pending_builds)) | ||
497 | 598 | 610 | ||
498 | 599 | def test_setRecipeText_private_base_branch(self): | 611 | def test_setRecipeText_private_base_branch(self): |
499 | 600 | source_package_recipe = self.factory.makeSourcePackageRecipe() | 612 | source_package_recipe = self.factory.makeSourcePackageRecipe() |
500 | @@ -633,8 +645,9 @@ | |||
501 | 633 | recipe=recipe, archive=archive) | 645 | recipe=recipe, archive=archive) |
502 | 634 | with person_logged_in(archive.owner): | 646 | with person_logged_in(archive.owner): |
503 | 635 | archive.disable() | 647 | archive.disable() |
506 | 636 | self.assertEqual([], list(recipe.getBuilds())) | 648 | self.assertEqual([], list(recipe.builds)) |
507 | 637 | self.assertEqual([], list(recipe.getPendingBuilds())) | 649 | self.assertEqual([], list(recipe.completed_builds)) |
508 | 650 | self.assertEqual([], list(recipe.pending_builds)) | ||
509 | 638 | 651 | ||
510 | 639 | 652 | ||
511 | 640 | class TestRecipeBranchRoundTripping(TestCaseWithFactory): | 653 | class TestRecipeBranchRoundTripping(TestCaseWithFactory): |
512 | @@ -901,8 +914,14 @@ | |||
513 | 901 | recipe, user = self.makeRecipe()[:-1] | 914 | recipe, user = self.makeRecipe()[:-1] |
514 | 902 | self.assertEqual(recipe, user.getRecipe(name=recipe.name)) | 915 | self.assertEqual(recipe, user.getRecipe(name=recipe.name)) |
515 | 903 | 916 | ||
516 | 917 | def test_recipes(self): | ||
517 | 918 | """Person.recipes works as expected.""" | ||
518 | 919 | recipe, user = self.makeRecipe()[:-1] | ||
519 | 920 | [ws_recipe] = user.recipes | ||
520 | 921 | self.assertEqual(recipe, ws_recipe) | ||
521 | 922 | |||
522 | 904 | def test_requestBuild(self): | 923 | def test_requestBuild(self): |
524 | 905 | """Build requests can be performed.""" | 924 | """Build requests can be performed and last_build works.""" |
525 | 906 | person = self.factory.makePerson() | 925 | person = self.factory.makePerson() |
526 | 907 | archive = self.factory.makeArchive(owner=person) | 926 | archive = self.factory.makeArchive(owner=person) |
527 | 908 | distroseries = self.factory.makeSourcePackageRecipeDistroseries() | 927 | distroseries = self.factory.makeSourcePackageRecipeDistroseries() |
528 | @@ -910,9 +929,10 @@ | |||
529 | 910 | recipe, user, launchpad = self.makeRecipe(person) | 929 | recipe, user, launchpad = self.makeRecipe(person) |
530 | 911 | distroseries = ws_object(launchpad, distroseries) | 930 | distroseries = ws_object(launchpad, distroseries) |
531 | 912 | archive = ws_object(launchpad, archive) | 931 | archive = ws_object(launchpad, archive) |
533 | 913 | recipe.requestBuild( | 932 | build = recipe.requestBuild( |
534 | 914 | archive=archive, distroseries=distroseries, | 933 | archive=archive, distroseries=distroseries, |
535 | 915 | pocket=PackagePublishingPocket.RELEASE.title) | 934 | pocket=PackagePublishingPocket.RELEASE.title) |
536 | 935 | self.assertEqual(build, recipe.last_build) | ||
537 | 916 | 936 | ||
538 | 917 | def test_requestBuildRejectRepeat(self): | 937 | def test_requestBuildRejectRepeat(self): |
539 | 918 | """Build requests are rejected if already pending.""" | 938 | """Build requests are rejected if already pending.""" |
540 | @@ -967,6 +987,22 @@ | |||
541 | 967 | pocket=PackagePublishingPocket.RELEASE.title) | 987 | pocket=PackagePublishingPocket.RELEASE.title) |
542 | 968 | self.assertIn('BuildNotAllowedForDistro', str(e)) | 988 | self.assertIn('BuildNotAllowedForDistro', str(e)) |
543 | 969 | 989 | ||
547 | 970 | 990 | def test_getBuilds(self): | |
548 | 971 | def test_suite(): | 991 | """SourcePackageRecipe.[pending_|completed_]builds is as expected.""" |
549 | 972 | return unittest.TestLoader().loadTestsFromName(__name__) | 992 | person = self.factory.makePerson() |
550 | 993 | archives = [self.factory.makeArchive(owner=person) for x in range(4)] | ||
551 | 994 | distroseries= self.factory.makeSourcePackageRecipeDistroseries() | ||
552 | 995 | |||
553 | 996 | recipe, user, launchpad = self.makeRecipe(person) | ||
554 | 997 | distroseries = ws_object(launchpad, distroseries) | ||
555 | 998 | |||
556 | 999 | builds = [] | ||
557 | 1000 | for archive in archives: | ||
558 | 1001 | archive = ws_object(launchpad, archive) | ||
559 | 1002 | build = recipe.requestBuild( | ||
560 | 1003 | archive=archive, distroseries=distroseries, | ||
561 | 1004 | pocket=PackagePublishingPocket.RELEASE.title) | ||
562 | 1005 | builds.insert(0, build) | ||
563 | 1006 | self.assertEqual(builds, list(recipe.pending_builds)) | ||
564 | 1007 | self.assertEqual(builds, list(recipe.builds)) | ||
565 | 1008 | self.assertEqual([], list(recipe.completed_builds)) | ||
566 | 973 | 1009 | ||
567 | === modified file 'lib/lp/code/scripts/tests/test_request_daily_builds.py' | |||
568 | --- lib/lp/code/scripts/tests/test_request_daily_builds.py 2011-01-13 03:53:53 +0000 | |||
569 | +++ lib/lp/code/scripts/tests/test_request_daily_builds.py 2011-02-24 23:45:43 +0000 | |||
570 | @@ -5,8 +5,6 @@ | |||
571 | 5 | 5 | ||
572 | 6 | """Test the request_daily_builds script""" | 6 | """Test the request_daily_builds script""" |
573 | 7 | 7 | ||
574 | 8 | import unittest | ||
575 | 9 | |||
576 | 10 | import transaction | 8 | import transaction |
577 | 11 | 9 | ||
578 | 12 | from canonical.launchpad.scripts.tests import run_script | 10 | from canonical.launchpad.scripts.tests import run_script |
579 | @@ -28,14 +26,14 @@ | |||
580 | 28 | pack_branch = self.factory.makePackageBranch() | 26 | pack_branch = self.factory.makePackageBranch() |
581 | 29 | pack_recipe = self.factory.makeSourcePackageRecipe( | 27 | pack_recipe = self.factory.makeSourcePackageRecipe( |
582 | 30 | build_daily=True, is_stale=True, branches=[pack_branch]) | 28 | build_daily=True, is_stale=True, branches=[pack_branch]) |
585 | 31 | self.assertEqual(0, prod_recipe.getPendingBuilds().count()) | 29 | self.assertEqual(0, prod_recipe.pending_builds.count()) |
586 | 32 | self.assertEqual(0, pack_recipe.getPendingBuilds().count()) | 30 | self.assertEqual(0, pack_recipe.pending_builds.count()) |
587 | 33 | transaction.commit() | 31 | transaction.commit() |
588 | 34 | retcode, stdout, stderr = run_script( | 32 | retcode, stdout, stderr = run_script( |
589 | 35 | 'cronscripts/request_daily_builds.py', []) | 33 | 'cronscripts/request_daily_builds.py', []) |
590 | 36 | self.assertIn('Requested 2 daily builds.', stderr) | 34 | self.assertIn('Requested 2 daily builds.', stderr) |
593 | 37 | self.assertEqual(1, prod_recipe.getPendingBuilds().count()) | 35 | self.assertEqual(1, prod_recipe.pending_builds.count()) |
594 | 38 | self.assertEqual(1, pack_recipe.getPendingBuilds().count()) | 36 | self.assertEqual(1, pack_recipe.pending_builds.count()) |
595 | 39 | self.assertFalse(prod_recipe.is_stale) | 37 | self.assertFalse(prod_recipe.is_stale) |
596 | 40 | self.assertFalse(pack_recipe.is_stale) | 38 | self.assertFalse(pack_recipe.is_stale) |
597 | 41 | 39 | ||
598 | @@ -47,13 +45,9 @@ | |||
599 | 47 | transaction.commit() | 45 | transaction.commit() |
600 | 48 | retcode, stdout, stderr = run_script( | 46 | retcode, stdout, stderr = run_script( |
601 | 49 | 'cronscripts/request_daily_builds.py', []) | 47 | 'cronscripts/request_daily_builds.py', []) |
603 | 50 | self.assertEqual(0, recipe.getPendingBuilds().count()) | 48 | self.assertEqual(0, recipe.pending_builds.count()) |
604 | 51 | self.assertIn('Requested 0 daily builds.', stderr) | 49 | self.assertIn('Requested 0 daily builds.', stderr) |
605 | 52 | utility = ErrorReportingUtility() | 50 | utility = ErrorReportingUtility() |
606 | 53 | utility.configure('request_daily_builds') | 51 | utility.configure('request_daily_builds') |
607 | 54 | oops = utility.getLastOopsReport() | 52 | oops = utility.getLastOopsReport() |
608 | 55 | self.assertIn('NonPPABuildRequest', oops.tb_text) | 53 | self.assertIn('NonPPABuildRequest', oops.tb_text) |
609 | 56 | |||
610 | 57 | |||
611 | 58 | def test_suite(): | ||
612 | 59 | return unittest.TestLoader().loadTestsFromName(__name__) | ||
613 | 60 | 54 | ||
614 | === modified file 'lib/lp/code/stories/webservice/xx-branch.txt' | |||
615 | --- lib/lp/code/stories/webservice/xx-branch.txt 2011-01-31 17:08:35 +0000 | |||
616 | +++ lib/lp/code/stories/webservice/xx-branch.txt 2011-02-24 23:45:43 +0000 | |||
617 | @@ -130,6 +130,7 @@ | |||
618 | 130 | owner_link: u'.../~eric' | 130 | owner_link: u'.../~eric' |
619 | 131 | private: False | 131 | private: False |
620 | 132 | project_link: u'.../fooix' | 132 | project_link: u'.../fooix' |
621 | 133 | recipes_collection_link: u'http://.../~eric/fooix/trunk/recipes' | ||
622 | 133 | registrant_link: u'.../~eric' | 134 | registrant_link: u'.../~eric' |
623 | 134 | repository_format: None | 135 | repository_format: None |
624 | 135 | resource_type_link: u'.../#branch' | 136 | resource_type_link: u'.../#branch' |
625 | @@ -282,7 +283,7 @@ | |||
626 | 282 | >>> print deletable | 283 | >>> print deletable |
627 | 283 | False | 284 | False |
628 | 284 | 285 | ||
630 | 285 | Deleting only works on branches that do not have anything else | 286 | Deleting only works on branches that do not have anything else |
631 | 286 | depending on them. | 287 | depending on them. |
632 | 287 | 288 | ||
633 | 288 | >>> response = webservice.delete('/~eric/fooix/feature-branch') | 289 | >>> response = webservice.delete('/~eric/fooix/feature-branch') |
634 | 289 | 290 | ||
635 | === modified file 'lib/lp/code/templates/sourcepackagerecipe-listing.pt' | |||
636 | --- lib/lp/code/templates/sourcepackagerecipe-listing.pt 2010-07-26 20:29:19 +0000 | |||
637 | +++ lib/lp/code/templates/sourcepackagerecipe-listing.pt 2011-02-24 23:45:43 +0000 | |||
638 | @@ -20,7 +20,7 @@ | |||
639 | 20 | </tr> | 20 | </tr> |
640 | 21 | </thead> | 21 | </thead> |
641 | 22 | <tbody> | 22 | <tbody> |
643 | 23 | <tal:recipes repeat="recipe context/getRecipes"> | 23 | <tal:recipes repeat="recipe context/recipes"> |
644 | 24 | <tr> | 24 | <tr> |
645 | 25 | <td colspan="2"> | 25 | <td colspan="2"> |
646 | 26 | <a tal:attributes="href recipe/fmt:url" | 26 | <a tal:attributes="href recipe/fmt:url" |
647 | 27 | 27 | ||
648 | === modified file 'lib/lp/codehosting/scanner/bzrsync.py' | |||
649 | --- lib/lp/codehosting/scanner/bzrsync.py 2010-10-15 18:27:58 +0000 | |||
650 | +++ lib/lp/codehosting/scanner/bzrsync.py 2011-02-24 23:45:43 +0000 | |||
651 | @@ -319,5 +319,5 @@ | |||
652 | 319 | 319 | ||
653 | 320 | 320 | ||
654 | 321 | def update_recipes(tip_changed): | 321 | def update_recipes(tip_changed): |
656 | 322 | for recipe in tip_changed.db_branch.getRecipes(): | 322 | for recipe in tip_changed.db_branch.recipes: |
657 | 323 | recipe.is_stale = True | 323 | recipe.is_stale = True |
658 | 324 | 324 | ||
659 | === modified file 'lib/lp/registry/model/person.py' | |||
660 | --- lib/lp/registry/model/person.py 2011-02-16 11:03:04 +0000 | |||
661 | +++ lib/lp/registry/model/person.py 2011-02-24 23:45:43 +0000 | |||
662 | @@ -2851,7 +2851,8 @@ | |||
663 | 2851 | from lp.hardwaredb.model.hwdb import HWSubmissionSet | 2851 | from lp.hardwaredb.model.hwdb import HWSubmissionSet |
664 | 2852 | return HWSubmissionSet().search(owner=self) | 2852 | return HWSubmissionSet().search(owner=self) |
665 | 2853 | 2853 | ||
667 | 2854 | def getRecipes(self): | 2854 | @property |
668 | 2855 | def recipes(self): | ||
669 | 2855 | """See `IHasRecipes`.""" | 2856 | """See `IHasRecipes`.""" |
670 | 2856 | from lp.code.model.sourcepackagerecipe import SourcePackageRecipe | 2857 | from lp.code.model.sourcepackagerecipe import SourcePackageRecipe |
671 | 2857 | store = Store.of(self) | 2858 | store = Store.of(self) |
672 | @@ -3454,8 +3455,8 @@ | |||
673 | 3454 | 3455 | ||
674 | 3455 | def _mergeSourcePackageRecipes(self, from_person, to_person): | 3456 | def _mergeSourcePackageRecipes(self, from_person, to_person): |
675 | 3456 | # This shouldn't use removeSecurityProxy. | 3457 | # This shouldn't use removeSecurityProxy. |
678 | 3457 | recipes = from_person.getRecipes() | 3458 | recipes = from_person.recipes |
679 | 3458 | existing_names = [r.name for r in to_person.getRecipes()] | 3459 | existing_names = [r.name for r in to_person.recipes] |
680 | 3459 | for recipe in recipes: | 3460 | for recipe in recipes: |
681 | 3460 | new_name = recipe.name | 3461 | new_name = recipe.name |
682 | 3461 | count = 1 | 3462 | count = 1 |
683 | 3462 | 3463 | ||
684 | === modified file 'lib/lp/registry/model/product.py' | |||
685 | --- lib/lp/registry/model/product.py 2011-01-24 20:53:10 +0000 | |||
686 | +++ lib/lp/registry/model/product.py 2011-02-24 23:45:43 +0000 | |||
687 | @@ -1315,7 +1315,8 @@ | |||
688 | 1315 | return DecoratedResultSet( | 1315 | return DecoratedResultSet( |
689 | 1316 | self.getVersionSortedSeries(statuses=statuses), decorate) | 1316 | self.getVersionSortedSeries(statuses=statuses), decorate) |
690 | 1317 | 1317 | ||
692 | 1318 | def getRecipes(self): | 1318 | @property |
693 | 1319 | def recipes(self): | ||
694 | 1319 | """See `IHasRecipes`.""" | 1320 | """See `IHasRecipes`.""" |
695 | 1320 | from lp.code.model.branch import Branch | 1321 | from lp.code.model.branch import Branch |
696 | 1321 | store = Store.of(self) | 1322 | store = Store.of(self) |
697 | 1322 | 1323 | ||
698 | === modified file 'lib/lp/registry/stories/webservice/xx-person.txt' | |||
699 | --- lib/lp/registry/stories/webservice/xx-person.txt 2011-02-13 22:54:48 +0000 | |||
700 | +++ lib/lp/registry/stories/webservice/xx-person.txt 2011-02-24 23:45:43 +0000 | |||
701 | @@ -50,6 +50,7 @@ | |||
702 | 50 | u'http://.../~salgado/+email/guilherme.salgado@canonical.com' | 50 | u'http://.../~salgado/+email/guilherme.salgado@canonical.com' |
703 | 51 | private: False | 51 | private: False |
704 | 52 | proposed_members_collection_link: u'http://.../~salgado/proposed_members' | 52 | proposed_members_collection_link: u'http://.../~salgado/proposed_members' |
705 | 53 | recipes_collection_link: u'http://.../~salgado/recipes' | ||
706 | 53 | resource_type_link: u'http://.../#person' | 54 | resource_type_link: u'http://.../#person' |
707 | 54 | self_link: u'http://.../~salgado' | 55 | self_link: u'http://.../~salgado' |
708 | 55 | sshkeys_collection_link: u'http://.../~salgado/sshkeys' | 56 | sshkeys_collection_link: u'http://.../~salgado/sshkeys' |
709 | @@ -110,6 +111,7 @@ | |||
710 | 110 | private: False | 111 | private: False |
711 | 111 | proposed_members_collection_link: | 112 | proposed_members_collection_link: |
712 | 112 | u'http://.../~ubuntu-team/proposed_members' | 113 | u'http://.../~ubuntu-team/proposed_members' |
713 | 114 | recipes_collection_link: u'http://.../~ubuntu-team/recipes' | ||
714 | 113 | renewal_policy: u'invite them to apply for renewal' | 115 | renewal_policy: u'invite them to apply for renewal' |
715 | 114 | resource_type_link: u'http://.../#team' | 116 | resource_type_link: u'http://.../#team' |
716 | 115 | self_link: u'http://.../~ubuntu-team' | 117 | self_link: u'http://.../~ubuntu-team' |
717 | 116 | 118 | ||
718 | === modified file 'lib/lp/registry/stories/webservice/xx-project-registry.txt' | |||
719 | --- lib/lp/registry/stories/webservice/xx-project-registry.txt 2011-02-13 22:54:48 +0000 | |||
720 | +++ lib/lp/registry/stories/webservice/xx-project-registry.txt 2011-02-24 23:45:43 +0000 | |||
721 | @@ -180,6 +180,7 @@ | |||
722 | 180 | programming_language: None | 180 | programming_language: None |
723 | 181 | project_group_link: u'http://.../mozilla' | 181 | project_group_link: u'http://.../mozilla' |
724 | 182 | qualifies_for_free_hosting: False | 182 | qualifies_for_free_hosting: False |
725 | 183 | recipes_collection_link: u'http://.../firefox/recipes' | ||
726 | 183 | registrant_link: u'http://.../~name12' | 184 | registrant_link: u'http://.../~name12' |
727 | 184 | releases_collection_link: u'http://.../firefox/releases' | 185 | releases_collection_link: u'http://.../firefox/releases' |
728 | 185 | remote_product: None | 186 | remote_product: None |
729 | @@ -244,6 +245,7 @@ | |||
730 | 244 | programming_language: None | 245 | programming_language: None |
731 | 245 | project_group_link: u'http://.../mozilla' | 246 | project_group_link: u'http://.../mozilla' |
732 | 246 | qualifies_for_free_hosting: False | 247 | qualifies_for_free_hosting: False |
733 | 248 | recipes_collection_link: u'http://.../firefox/recipes' | ||
734 | 247 | registrant_link: u'http://.../~name12' | 249 | registrant_link: u'http://.../~name12' |
735 | 248 | releases_collection_link: u'http://.../firefox/releases' | 250 | releases_collection_link: u'http://.../firefox/releases' |
736 | 249 | remote_product: None | 251 | remote_product: None |
737 | 250 | 252 | ||
738 | === modified file 'lib/lp/registry/tests/test_person.py' | |||
739 | --- lib/lp/registry/tests/test_person.py 2011-02-18 13:26:58 +0000 | |||
740 | +++ lib/lp/registry/tests/test_person.py 2011-02-24 23:45:43 +0000 | |||
741 | @@ -734,7 +734,7 @@ | |||
742 | 734 | self._do_premerge(recipe.owner, person) | 734 | self._do_premerge(recipe.owner, person) |
743 | 735 | login_person(person) | 735 | login_person(person) |
744 | 736 | self.person_set.merge(recipe.owner, person) | 736 | self.person_set.merge(recipe.owner, person) |
746 | 737 | self.assertEqual(1, person.getRecipes().count()) | 737 | self.assertEqual(1, person.recipes.count()) |
747 | 738 | 738 | ||
748 | 739 | def test_merge_with_duplicated_recipes(self): | 739 | def test_merge_with_duplicated_recipes(self): |
749 | 740 | # If both the from and to people have recipes with the same name, | 740 | # If both the from and to people have recipes with the same name, |
750 | @@ -750,7 +750,7 @@ | |||
751 | 750 | self._do_premerge(merge_from.owner, mergee) | 750 | self._do_premerge(merge_from.owner, mergee) |
752 | 751 | login_person(mergee) | 751 | login_person(mergee) |
753 | 752 | self.person_set.merge(merge_from.owner, merge_to.owner) | 752 | self.person_set.merge(merge_from.owner, merge_to.owner) |
755 | 753 | recipes = mergee.getRecipes() | 753 | recipes = mergee.recipes |
756 | 754 | self.assertEqual(2, recipes.count()) | 754 | self.assertEqual(2, recipes.count()) |
757 | 755 | descriptions = [r.description for r in recipes] | 755 | descriptions = [r.description for r in recipes] |
758 | 756 | self.assertEqual([u'TO', u'FROM'], descriptions) | 756 | self.assertEqual([u'TO', u'FROM'], descriptions) |
Just to mention, this seems gratuitiously different to the existing api for getBuildRecords, which we could expose on your context - conceptually an IHasBuildRecords or similar. Is there a reason for this?