Merge ~andrey-fedoseev/launchpad:bug-task-channel into launchpad:master
- Git
- lp:~andrey-fedoseev/launchpad
- bug-task-channel
- Merge into master
Proposed by
Andrey Fedoseev
Status: | Needs review |
---|---|
Proposed branch: | ~andrey-fedoseev/launchpad:bug-task-channel |
Merge into: | launchpad:master |
Diff against target: |
1067 lines (+328/-71) 20 files modified
lib/lp/bugs/configure.zcml (+2/-0) lib/lp/bugs/interfaces/bugsummary.py (+3/-1) lib/lp/bugs/interfaces/bugtask.py (+1/-0) lib/lp/bugs/model/bugsummary.py (+12/-1) lib/lp/bugs/model/bugtask.py (+27/-1) lib/lp/bugs/model/bugtaskflat.py (+2/-1) lib/lp/bugs/model/tests/test_bugtask.py (+24/-0) lib/lp/bugs/scripts/bugsummaryrebuild.py (+36/-20) lib/lp/bugs/scripts/bugtasktargetnamecaches.py (+2/-2) lib/lp/registry/interfaces/distroseries.py (+1/-1) lib/lp/registry/interfaces/sourcepackage.py (+11/-1) lib/lp/registry/model/distroseries.py (+3/-2) lib/lp/registry/model/sourcepackage.py (+64/-16) lib/lp/registry/stories/webservice/xx-source-package.rst (+1/-0) lib/lp/registry/tests/test_distributionsourcepackage.py (+1/-1) lib/lp/registry/tests/test_distroseries.py (+6/-0) lib/lp/registry/tests/test_sourcepackage.py (+60/-0) lib/lp/soyuz/tests/test_binarypackagebuild.py (+9/-5) lib/lp/soyuz/tests/test_hasbuildrecords.py (+50/-17) lib/lp/testing/factory.py (+13/-2) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Launchpad code reviewers | Pending | ||
Review via email: mp+434693@code.launchpad.net |
Commit message
WIP: Add `channel` field to `BugTask` and `SourcePackage`
This should also make a `SourcePackage` with a channel a valid target for a `BugTask`, and some work has been done in that direction, but it may not be 100% complete.
Description of the change
Currently, it is confirmed to pass all tests in `lp.{bugs,
To post a comment you must log in.
Unmerged commits
- c6e1a93... by Andrey Fedoseev
-
WIP: Add `channel` field to `BugTask` and `SourcePackage`
This should also make a `SourcePackage` with a channel a valid target for a `BugTask`, and some work has been done in that direction, but it may not be 100% complete.
-
docs:0 (build) lint:0 (build) mypy:0 (build) 1 → 3 of 3 results First • Previous • Next • Last
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/lib/lp/bugs/configure.zcml b/lib/lp/bugs/configure.zcml |
2 | index 6eaa58c..12bfdd7 100644 |
3 | --- a/lib/lp/bugs/configure.zcml |
4 | +++ b/lib/lp/bugs/configure.zcml |
5 | @@ -211,6 +211,8 @@ |
6 | distribution |
7 | distroseries |
8 | milestone |
9 | + _channel |
10 | + channel |
11 | _status |
12 | status |
13 | status_explanation |
14 | diff --git a/lib/lp/bugs/interfaces/bugsummary.py b/lib/lp/bugs/interfaces/bugsummary.py |
15 | index 7cb55ef..be83775 100644 |
16 | --- a/lib/lp/bugs/interfaces/bugsummary.py |
17 | +++ b/lib/lp/bugs/interfaces/bugsummary.py |
18 | @@ -9,7 +9,7 @@ __all__ = [ |
19 | ] |
20 | |
21 | from zope.interface import Interface |
22 | -from zope.schema import Bool, Choice, Int, Object, Text |
23 | +from zope.schema import Bool, Choice, Int, Object, Text, TextLine |
24 | |
25 | from lp import _ |
26 | from lp.bugs.interfaces.bugtask import BugTaskImportance, BugTaskStatusSearch |
27 | @@ -51,6 +51,8 @@ class IBugSummary(Interface): |
28 | ociproject_id = Int(readonly=True) |
29 | ociproject = Object(IOCIProject, readonly=True) |
30 | |
31 | + channel = TextLine(readonly=True) |
32 | + |
33 | milestone_id = Int(readonly=True) |
34 | milestone = Object(IMilestone, readonly=True) |
35 | |
36 | diff --git a/lib/lp/bugs/interfaces/bugtask.py b/lib/lp/bugs/interfaces/bugtask.py |
37 | index a8c4e45..d2586f7 100644 |
38 | --- a/lib/lp/bugs/interfaces/bugtask.py |
39 | +++ b/lib/lp/bugs/interfaces/bugtask.py |
40 | @@ -475,6 +475,7 @@ class IBugTask(IHasBug, IBugTaskDelete): |
41 | title=_("Series"), required=False, vocabulary="DistroSeries" |
42 | ) |
43 | distroseries_id = Attribute("The distroseries ID") |
44 | + channel = TextLine(title=_("Channel"), required=False) |
45 | milestone = exported( |
46 | ReferenceChoice( |
47 | title=_("Milestone"), |
48 | diff --git a/lib/lp/bugs/model/bugsummary.py b/lib/lp/bugs/model/bugsummary.py |
49 | index 9084e0e..2caabf6 100644 |
50 | --- a/lib/lp/bugs/model/bugsummary.py |
51 | +++ b/lib/lp/bugs/model/bugsummary.py |
52 | @@ -9,6 +9,8 @@ __all__ = [ |
53 | "get_bugsummary_filter_for_user", |
54 | ] |
55 | |
56 | +from typing import Optional |
57 | + |
58 | from storm.base import Storm |
59 | from storm.expr import SQL, And, Or, Select |
60 | from storm.properties import Bool, Int, Unicode |
61 | @@ -32,9 +34,10 @@ from lp.registry.model.product import Product |
62 | from lp.registry.model.productseries import ProductSeries |
63 | from lp.registry.model.sourcepackagename import SourcePackageName |
64 | from lp.registry.model.teammembership import TeamParticipation |
65 | +from lp.services.channels import channel_list_to_string |
66 | from lp.services.database.enumcol import DBEnum |
67 | from lp.services.database.interfaces import IStore |
68 | -from lp.services.database.stormexpr import WithMaterialized |
69 | +from lp.services.database.stormexpr import ImmutablePgJSON, WithMaterialized |
70 | |
71 | |
72 | @implementer(IBugSummary) |
73 | @@ -64,6 +67,8 @@ class BugSummary(Storm): |
74 | ociproject_id = Int(name="ociproject") |
75 | ociproject = Reference(ociproject_id, "OCIProject.id") |
76 | |
77 | + _channel = ImmutablePgJSON(name="channel") |
78 | + |
79 | milestone_id = Int(name="milestone") |
80 | milestone = Reference(milestone_id, Milestone.id) |
81 | |
82 | @@ -80,6 +85,12 @@ class BugSummary(Storm): |
83 | |
84 | has_patch = Bool() |
85 | |
86 | + @property |
87 | + def channel(self) -> Optional[str]: |
88 | + if self._channel is None: |
89 | + return None |
90 | + return channel_list_to_string(*self._channel) |
91 | + |
92 | |
93 | @implementer(IBugSummaryDimension) |
94 | class CombineBugSummaryConstraint: |
95 | diff --git a/lib/lp/bugs/model/bugtask.py b/lib/lp/bugs/model/bugtask.py |
96 | index c006618..8955720 100644 |
97 | --- a/lib/lp/bugs/model/bugtask.py |
98 | +++ b/lib/lp/bugs/model/bugtask.py |
99 | @@ -21,6 +21,7 @@ import re |
100 | from collections import defaultdict |
101 | from itertools import chain, repeat |
102 | from operator import attrgetter, itemgetter |
103 | +from typing import Optional |
104 | |
105 | import pytz |
106 | from lazr.lifecycle.event import ObjectDeletedEvent |
107 | @@ -98,6 +99,7 @@ from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet |
108 | from lp.registry.model.pillar import pillar_sort_key |
109 | from lp.registry.model.sourcepackagename import SourcePackageName |
110 | from lp.services import features |
111 | +from lp.services.channels import channel_list_to_string, channel_string_to_list |
112 | from lp.services.database.bulk import create, load, load_related |
113 | from lp.services.database.constants import UTC_NOW |
114 | from lp.services.database.decoratedresultset import DecoratedResultSet |
115 | @@ -111,6 +113,7 @@ from lp.services.database.sqlbase import ( |
116 | sqlvalues, |
117 | ) |
118 | from lp.services.database.stormbase import StormBase |
119 | +from lp.services.database.stormexpr import ImmutablePgJSON |
120 | from lp.services.helpers import shortlist |
121 | from lp.services.propertycache import get_property_cache |
122 | from lp.services.searchbuilder import any |
123 | @@ -171,6 +174,7 @@ def bug_target_from_key( |
124 | distroseries, |
125 | sourcepackagename, |
126 | ociproject, |
127 | + channel, |
128 | ): |
129 | """Returns the IBugTarget defined by the given DB column values.""" |
130 | if ociproject: |
131 | @@ -189,7 +193,7 @@ def bug_target_from_key( |
132 | return distribution |
133 | elif distroseries: |
134 | if sourcepackagename: |
135 | - return distroseries.getSourcePackage(sourcepackagename) |
136 | + return distroseries.getSourcePackage(sourcepackagename, channel) |
137 | else: |
138 | return distroseries |
139 | else: |
140 | @@ -205,6 +209,7 @@ def bug_target_to_key(target): |
141 | distroseries=None, |
142 | sourcepackagename=None, |
143 | ociproject=None, |
144 | + channel=None, |
145 | ) |
146 | if IProduct.providedBy(target): |
147 | values["product"] = target |
148 | @@ -220,6 +225,7 @@ def bug_target_to_key(target): |
149 | elif ISourcePackage.providedBy(target): |
150 | values["distroseries"] = target.distroseries |
151 | values["sourcepackagename"] = target.sourcepackagename |
152 | + values["channel"] = target.channel |
153 | elif IOCIProject.providedBy(target): |
154 | # De-normalize the ociproject, including also the ociproject's |
155 | # pillar (distribution or product). |
156 | @@ -499,6 +505,8 @@ class BugTask(StormBase): |
157 | distroseries_id = Int(name="distroseries", allow_none=True) |
158 | distroseries = Reference(distroseries_id, "DistroSeries.id") |
159 | |
160 | + _channel = ImmutablePgJSON(name="channel", allow_none=True) |
161 | + |
162 | milestone_id = Int( |
163 | name="milestone", |
164 | allow_none=True, |
165 | @@ -613,6 +621,19 @@ class BugTask(StormBase): |
166 | ) |
167 | |
168 | @property |
169 | + def channel(self) -> Optional[str]: |
170 | + if self._channel is None: |
171 | + return None |
172 | + return channel_list_to_string(*self._channel) |
173 | + |
174 | + @channel.setter |
175 | + def channel(self, value: str) -> None: |
176 | + if value is None: |
177 | + self._channel = None |
178 | + else: |
179 | + self._channel = channel_string_to_list(value) |
180 | + |
181 | + @property |
182 | def status(self): |
183 | if self._status in DB_INCOMPLETE_BUGTASK_STATUSES: |
184 | return BugTaskStatus.INCOMPLETE |
185 | @@ -652,6 +673,7 @@ class BugTask(StormBase): |
186 | self.distroseries, |
187 | self.sourcepackagename, |
188 | self.ociproject, |
189 | + self.channel, |
190 | ) |
191 | |
192 | @property |
193 | @@ -1863,6 +1885,9 @@ class BugTaskSet: |
194 | key["distroseries"], |
195 | key["sourcepackagename"], |
196 | key["ociproject"], |
197 | + channel_string_to_list(key["channel"]) |
198 | + if key["channel"] |
199 | + else None, |
200 | status, |
201 | importance, |
202 | assignee, |
203 | @@ -1880,6 +1905,7 @@ class BugTaskSet: |
204 | BugTask.distroseries, |
205 | BugTask.sourcepackagename, |
206 | BugTask.ociproject, |
207 | + BugTask._channel, |
208 | BugTask._status, |
209 | BugTask.importance, |
210 | BugTask.assignee, |
211 | diff --git a/lib/lp/bugs/model/bugtaskflat.py b/lib/lp/bugs/model/bugtaskflat.py |
212 | index b25a440..b636ecf 100644 |
213 | --- a/lib/lp/bugs/model/bugtaskflat.py |
214 | +++ b/lib/lp/bugs/model/bugtaskflat.py |
215 | @@ -1,6 +1,5 @@ |
216 | # Copyright 2012-2020 Canonical Ltd. This software is licensed under the |
217 | # GNU Affero General Public License version 3 (see the file LICENSE). |
218 | - |
219 | from storm.locals import Bool, DateTime, Int, List, Reference, Storm |
220 | |
221 | from lp.app.enums import InformationType |
222 | @@ -10,6 +9,7 @@ from lp.bugs.interfaces.bugtask import ( |
223 | BugTaskStatusSearch, |
224 | ) |
225 | from lp.services.database.enumcol import DBEnum |
226 | +from lp.services.database.stormexpr import ImmutablePgJSON |
227 | |
228 | |
229 | class BugTaskFlat(Storm): |
230 | @@ -42,6 +42,7 @@ class BugTaskFlat(Storm): |
231 | sourcepackagename = Reference(sourcepackagename_id, "SourcePackageName.id") |
232 | ociproject_id = Int(name="ociproject") |
233 | ociproject = Reference(ociproject_id, "OCIProject.id") |
234 | + channel = ImmutablePgJSON() |
235 | status = DBEnum(enum=(BugTaskStatus, BugTaskStatusSearch)) |
236 | importance = DBEnum(enum=BugTaskImportance) |
237 | assignee_id = Int(name="assignee") |
238 | diff --git a/lib/lp/bugs/model/tests/test_bugtask.py b/lib/lp/bugs/model/tests/test_bugtask.py |
239 | index c3eb248..ecb258f 100644 |
240 | --- a/lib/lp/bugs/model/tests/test_bugtask.py |
241 | +++ b/lib/lp/bugs/model/tests/test_bugtask.py |
242 | @@ -3145,6 +3145,7 @@ class TestBugTargetKeys(TestCaseWithFactory): |
243 | distroseries=None, |
244 | sourcepackagename=None, |
245 | ociproject=None, |
246 | + channel=None, |
247 | ), |
248 | ) |
249 | |
250 | @@ -3159,6 +3160,7 @@ class TestBugTargetKeys(TestCaseWithFactory): |
251 | distroseries=None, |
252 | sourcepackagename=None, |
253 | ociproject=None, |
254 | + channel=None, |
255 | ), |
256 | ) |
257 | |
258 | @@ -3173,6 +3175,7 @@ class TestBugTargetKeys(TestCaseWithFactory): |
259 | distroseries=None, |
260 | sourcepackagename=None, |
261 | ociproject=None, |
262 | + channel=None, |
263 | ), |
264 | ) |
265 | |
266 | @@ -3187,6 +3190,7 @@ class TestBugTargetKeys(TestCaseWithFactory): |
267 | distroseries=distroseries, |
268 | sourcepackagename=None, |
269 | ociproject=None, |
270 | + channel=None, |
271 | ), |
272 | ) |
273 | |
274 | @@ -3201,6 +3205,7 @@ class TestBugTargetKeys(TestCaseWithFactory): |
275 | distroseries=None, |
276 | sourcepackagename=dsp.sourcepackagename, |
277 | ociproject=None, |
278 | + channel=None, |
279 | ), |
280 | ) |
281 | |
282 | @@ -3215,6 +3220,22 @@ class TestBugTargetKeys(TestCaseWithFactory): |
283 | distroseries=sp.distroseries, |
284 | sourcepackagename=sp.sourcepackagename, |
285 | ociproject=None, |
286 | + channel=None, |
287 | + ), |
288 | + ) |
289 | + |
290 | + def test_sourcepackage_with_channel(self): |
291 | + sp = self.factory.makeSourcePackage(channel="stable") |
292 | + self.assertTargetKeyWorks( |
293 | + sp, |
294 | + dict( |
295 | + product=None, |
296 | + productseries=None, |
297 | + distribution=None, |
298 | + distroseries=sp.distroseries, |
299 | + sourcepackagename=sp.sourcepackagename, |
300 | + ociproject=None, |
301 | + channel="stable", |
302 | ), |
303 | ) |
304 | |
305 | @@ -3230,6 +3251,7 @@ class TestBugTargetKeys(TestCaseWithFactory): |
306 | distroseries=None, |
307 | sourcepackagename=None, |
308 | ociproject=ociproject, |
309 | + channel=None, |
310 | ), |
311 | ) |
312 | |
313 | @@ -3245,6 +3267,7 @@ class TestBugTargetKeys(TestCaseWithFactory): |
314 | distroseries=None, |
315 | sourcepackagename=None, |
316 | ociproject=ociproject, |
317 | + channel=None, |
318 | ), |
319 | ) |
320 | |
321 | @@ -3263,6 +3286,7 @@ class TestBugTargetKeys(TestCaseWithFactory): |
322 | None, |
323 | None, |
324 | None, |
325 | + None, |
326 | ) |
327 | |
328 | |
329 | diff --git a/lib/lp/bugs/scripts/bugsummaryrebuild.py b/lib/lp/bugs/scripts/bugsummaryrebuild.py |
330 | index 21003b4..cf9ae8a 100644 |
331 | --- a/lib/lp/bugs/scripts/bugsummaryrebuild.py |
332 | +++ b/lib/lp/bugs/scripts/bugsummaryrebuild.py |
333 | @@ -24,6 +24,7 @@ from lp.registry.model.ociproject import OCIProject |
334 | from lp.registry.model.product import Product |
335 | from lp.registry.model.productseries import ProductSeries |
336 | from lp.registry.model.sourcepackagename import SourcePackageName |
337 | +from lp.services.channels import channel_string_to_list |
338 | from lp.services.database.bulk import create |
339 | from lp.services.database.interfaces import IStore |
340 | from lp.services.database.stormexpr import Unnest |
341 | @@ -109,7 +110,7 @@ def load_target(pid, psid, did, dsid, spnid, ociproject_id): |
342 | (pid, psid, did, dsid, spnid, ociproject_id), |
343 | ), |
344 | ) |
345 | - return bug_target_from_key(p, ps, d, ds, spn, ociproject) |
346 | + return bug_target_from_key(p, ps, d, ds, spn, ociproject, None) |
347 | |
348 | |
349 | def format_target(target): |
350 | @@ -130,7 +131,15 @@ def format_target(target): |
351 | def _get_bugsummary_constraint_bits(target): |
352 | raw_key = bug_target_to_key(target) |
353 | # Map to ID columns to work around Storm bug #682989. |
354 | - return {"%s_id" % k: v.id if v else None for (k, v) in raw_key.items()} |
355 | + constraint_bits = {} |
356 | + for name, value in raw_key.items(): |
357 | + if name == "channel": |
358 | + constraint_bits["_channel"] = ( |
359 | + channel_string_to_list(value) if value else None |
360 | + ) |
361 | + else: |
362 | + constraint_bits["{}_id".format(name)] = value.id if value else None |
363 | + return constraint_bits |
364 | |
365 | |
366 | def get_bugsummary_constraint(target, cls=RawBugSummary): |
367 | @@ -154,10 +163,19 @@ def get_bugtaskflat_constraint(target): |
368 | if IProduct.providedBy(target): |
369 | del raw_key["ociproject"] |
370 | # Map to ID columns to work around Storm bug #682989. |
371 | - return [ |
372 | - getattr(BugTaskFlat, "%s_id" % k) == (v.id if v else None) |
373 | - for (k, v) in raw_key.items() |
374 | - ] |
375 | + constraint_bits = [] |
376 | + for name, value in raw_key.items(): |
377 | + if name == "channel": |
378 | + constraint_bits.append( |
379 | + getattr(BugTaskFlat, name) |
380 | + == (channel_string_to_list(value) if value else None) |
381 | + ) |
382 | + else: |
383 | + constraint_bits.append( |
384 | + getattr(BugTaskFlat, "{}_id".format(name)) |
385 | + == (value.id if value else None) |
386 | + ) |
387 | + return constraint_bits |
388 | |
389 | |
390 | def get_bugsummary_rows(target): |
391 | @@ -167,6 +185,7 @@ def get_bugsummary_rows(target): |
392 | with BugSummary which is actually combinedbugsummary, a view over |
393 | bugsummary and bugsummaryjournal. |
394 | """ |
395 | + constraint = get_bugsummary_constraint(target) |
396 | return IStore(RawBugSummary).find( |
397 | ( |
398 | RawBugSummary.status, |
399 | @@ -178,7 +197,7 @@ def get_bugsummary_rows(target): |
400 | RawBugSummary.access_policy_id, |
401 | RawBugSummary.count, |
402 | ), |
403 | - *get_bugsummary_constraint(target), |
404 | + *constraint, |
405 | ) |
406 | |
407 | |
408 | @@ -191,7 +210,7 @@ def get_bugsummaryjournal_rows(target): |
409 | |
410 | |
411 | def calculate_bugsummary_changes(old, new): |
412 | - """Calculate the changes between between the new and old dicts. |
413 | + """Calculate the changes between the new and old dicts. |
414 | |
415 | Takes {key: int} dicts, returns items from the new dict that differ |
416 | from the old one. |
417 | @@ -219,18 +238,14 @@ def calculate_bugsummary_changes(old, new): |
418 | def apply_bugsummary_changes(target, added, updated, removed): |
419 | """Apply a set of BugSummary changes to the DB.""" |
420 | bits = _get_bugsummary_constraint_bits(target) |
421 | - target_key = tuple( |
422 | - map( |
423 | - bits.get, |
424 | - ( |
425 | - "product_id", |
426 | - "productseries_id", |
427 | - "distribution_id", |
428 | - "distroseries_id", |
429 | - "sourcepackagename_id", |
430 | - "ociproject_id", |
431 | - ), |
432 | - ) |
433 | + target_key = ( |
434 | + bits["product_id"], |
435 | + bits["productseries_id"], |
436 | + bits["distribution_id"], |
437 | + bits["distroseries_id"], |
438 | + bits["sourcepackagename_id"], |
439 | + bits["ociproject_id"], |
440 | + bits["_channel"], |
441 | ) |
442 | target_cols = ( |
443 | RawBugSummary.product_id, |
444 | @@ -239,6 +254,7 @@ def apply_bugsummary_changes(target, added, updated, removed): |
445 | RawBugSummary.distroseries_id, |
446 | RawBugSummary.sourcepackagename_id, |
447 | RawBugSummary.ociproject_id, |
448 | + RawBugSummary._channel, |
449 | ) |
450 | key_cols = ( |
451 | RawBugSummary.status, |
452 | diff --git a/lib/lp/bugs/scripts/bugtasktargetnamecaches.py b/lib/lp/bugs/scripts/bugtasktargetnamecaches.py |
453 | index 70a82d4..dcbf424 100644 |
454 | --- a/lib/lp/bugs/scripts/bugtasktargetnamecaches.py |
455 | +++ b/lib/lp/bugs/scripts/bugtasktargetnamecaches.py |
456 | @@ -103,10 +103,10 @@ class BugTaskTargetNameCachesTunableLoop: |
457 | self.offset += 1 |
458 | # Resolve the IDs to objects, and get the actual IBugTarget. |
459 | # If the ID is None, don't even try to get an object. |
460 | - target_objects = ( |
461 | + target_objects = [ |
462 | (store.get(cls, id) if id is not None else None) |
463 | for cls, id in zip(target_classes, target_bits) |
464 | - ) |
465 | + ] + [None] |
466 | target = bug_target_from_key(*target_objects) |
467 | new_name = target.bugtargetdisplayname |
468 | cached_names.discard(new_name) |
469 | diff --git a/lib/lp/registry/interfaces/distroseries.py b/lib/lp/registry/interfaces/distroseries.py |
470 | index 03e6d68..5a718c2 100644 |
471 | --- a/lib/lp/registry/interfaces/distroseries.py |
472 | +++ b/lib/lp/registry/interfaces/distroseries.py |
473 | @@ -667,7 +667,7 @@ class IDistroSeriesPublic( |
474 | @operation_returns_entry(ISourcePackage) |
475 | @export_read_operation() |
476 | @operation_for_version("beta") |
477 | - def getSourcePackage(name): |
478 | + def getSourcePackage(name, channel=None): |
479 | """Return a source package in this distro series by name. |
480 | |
481 | The name given may be a string or an ISourcePackageName-providing |
482 | diff --git a/lib/lp/registry/interfaces/sourcepackage.py b/lib/lp/registry/interfaces/sourcepackage.py |
483 | index d8f776e..a4505c0 100644 |
484 | --- a/lib/lp/registry/interfaces/sourcepackage.py |
485 | +++ b/lib/lp/registry/interfaces/sourcepackage.py |
486 | @@ -130,6 +130,15 @@ class ISourcePackagePublic( |
487 | |
488 | sourcepackagename = Attribute("SourcePackageName") |
489 | |
490 | + channel = exported( |
491 | + TextLine( |
492 | + title=_("Channel"), |
493 | + required=False, |
494 | + readonly=True, |
495 | + description=_("The channel for this source package."), |
496 | + ), |
497 | + ) |
498 | + |
499 | # This is really a reference to an IProductSeries. |
500 | productseries = exported( |
501 | ReferenceChoice( |
502 | @@ -362,11 +371,12 @@ class ISourcePackage(ISourcePackagePublic, ISourcePackageEdit): |
503 | class ISourcePackageFactory(Interface): |
504 | """A creator of source packages.""" |
505 | |
506 | - def new(sourcepackagename, distroseries): |
507 | + def new(sourcepackagename, distroseries, channel=None): |
508 | """Create a new `ISourcePackage`. |
509 | |
510 | :param sourcepackagename: An `ISourcePackageName`. |
511 | :param distroseries: An `IDistroSeries`. |
512 | + :param channel: A channel name or None. |
513 | :return: `ISourcePackage`. |
514 | """ |
515 | |
516 | diff --git a/lib/lp/registry/model/distroseries.py b/lib/lp/registry/model/distroseries.py |
517 | index 7844249..a128ed5 100644 |
518 | --- a/lib/lp/registry/model/distroseries.py |
519 | +++ b/lib/lp/registry/model/distroseries.py |
520 | @@ -13,6 +13,7 @@ __all__ = [ |
521 | import collections |
522 | from io import BytesIO |
523 | from operator import itemgetter |
524 | +from typing import Optional |
525 | |
526 | import apt_pkg |
527 | from lazr.delegates import delegate_to |
528 | @@ -1036,7 +1037,7 @@ class DistroSeries( |
529 | self.messagecount = messagecount |
530 | ztm.commit() |
531 | |
532 | - def getSourcePackage(self, name): |
533 | + def getSourcePackage(self, name, channel: Optional[str] = None): |
534 | """See `IDistroSeries`.""" |
535 | if not ISourcePackageName.providedBy(name): |
536 | try: |
537 | @@ -1044,7 +1045,7 @@ class DistroSeries( |
538 | except SQLObjectNotFound: |
539 | return None |
540 | return getUtility(ISourcePackageFactory).new( |
541 | - sourcepackagename=name, distroseries=self |
542 | + sourcepackagename=name, distroseries=self, channel=channel |
543 | ) |
544 | |
545 | def getBinaryPackage(self, name): |
546 | diff --git a/lib/lp/registry/model/sourcepackage.py b/lib/lp/registry/model/sourcepackage.py |
547 | index 8a0280b..5aff937 100644 |
548 | --- a/lib/lp/registry/model/sourcepackage.py |
549 | +++ b/lib/lp/registry/model/sourcepackage.py |
550 | @@ -8,7 +8,9 @@ __all__ = [ |
551 | "SourcePackageQuestionTargetMixin", |
552 | ] |
553 | |
554 | +import json |
555 | from operator import attrgetter, itemgetter |
556 | +from typing import Optional |
557 | |
558 | from storm.locals import And, Desc, Join, Store |
559 | from zope.component import getUtility |
560 | @@ -42,6 +44,7 @@ from lp.registry.interfaces.sourcepackage import ( |
561 | from lp.registry.model.hasdrivers import HasDriversMixin |
562 | from lp.registry.model.packaging import Packaging |
563 | from lp.registry.model.suitesourcepackage import SuiteSourcePackage |
564 | +from lp.services.channels import channel_string_to_list |
565 | from lp.services.database.decoratedresultset import DecoratedResultSet |
566 | from lp.services.database.interfaces import IStore |
567 | from lp.services.database.sqlbase import flush_database_updates, sqlvalues |
568 | @@ -204,7 +207,9 @@ class SourcePackage( |
569 | to the relevant database objects. |
570 | """ |
571 | |
572 | - def __init__(self, sourcepackagename, distroseries): |
573 | + def __init__( |
574 | + self, sourcepackagename, distroseries, channel: Optional[str] = None |
575 | + ): |
576 | # We store the ID of the sourcepackagename and distroseries |
577 | # simply because Storm can break when accessing them |
578 | # with implicit flush is blocked (like in a permission check when |
579 | @@ -213,11 +218,14 @@ class SourcePackage( |
580 | self.sourcepackagename = sourcepackagename |
581 | self.distroseries = distroseries |
582 | self.distroseriesID = distroseries.id |
583 | + self.channel = channel |
584 | |
585 | @classmethod |
586 | - def new(cls, sourcepackagename, distroseries): |
587 | + def new( |
588 | + cls, sourcepackagename, distroseries, channel: Optional[str] = None |
589 | + ): |
590 | """See `ISourcePackageFactory`.""" |
591 | - return cls(sourcepackagename, distroseries) |
592 | + return cls(sourcepackagename, distroseries, channel=channel) |
593 | |
594 | def __repr__(self): |
595 | return "<%s %r %r %r>" % ( |
596 | @@ -242,6 +250,12 @@ class SourcePackage( |
597 | == self.sourcepackagename, |
598 | SourcePackagePublishingHistory.distroseries |
599 | == self.distroseries, |
600 | + SourcePackagePublishingHistory._channel |
601 | + == ( |
602 | + None |
603 | + if self.channel is None |
604 | + else channel_string_to_list(self.channel) |
605 | + ), |
606 | SourcePackagePublishingHistory.archiveID.is_in( |
607 | self.distribution.all_distro_archive_ids |
608 | ), |
609 | @@ -296,24 +310,27 @@ class SourcePackage( |
610 | ) |
611 | |
612 | @property |
613 | + def series_name(self): |
614 | + series_name = self.distroseries.fullseriesname |
615 | + if self.channel is not None: |
616 | + series_name = "%s, %s" % (series_name, self.channel) |
617 | + return series_name |
618 | + |
619 | + @property |
620 | def display_name(self): |
621 | - return "%s in %s %s" % ( |
622 | - self.sourcepackagename.name, |
623 | - self.distribution.displayname, |
624 | - self.distroseries.displayname, |
625 | - ) |
626 | + return "%s in %s" % (self.sourcepackagename.name, self.series_name) |
627 | |
628 | displayname = display_name |
629 | |
630 | @property |
631 | def bugtargetdisplayname(self): |
632 | """See IBugTarget.""" |
633 | - return "%s (%s)" % (self.name, self.distroseries.fullseriesname) |
634 | + return "%s (%s)" % (self.name, self.series_name) |
635 | |
636 | @property |
637 | def bugtargetname(self): |
638 | """See `IBugTarget`.""" |
639 | - return "%s (%s)" % (self.name, self.distroseries.fullseriesname) |
640 | + return "%s (%s)" % (self.name, self.series_name) |
641 | |
642 | @property |
643 | def bugtarget_parent(self): |
644 | @@ -551,6 +568,12 @@ class SourcePackage( |
645 | return And( |
646 | BugSummary.distroseries == self.distroseries, |
647 | BugSummary.sourcepackagename == self.sourcepackagename, |
648 | + BugSummary._channel |
649 | + == ( |
650 | + None |
651 | + if self.channel is None |
652 | + else channel_string_to_list(self.channel) |
653 | + ), |
654 | ) |
655 | |
656 | def setPackaging(self, productseries, owner): |
657 | @@ -616,7 +639,11 @@ class SourcePackage( |
658 | |
659 | def __hash__(self): |
660 | """See `ISourcePackage`.""" |
661 | - return hash(self.distroseriesID) ^ hash(self.sourcepackagenameID) |
662 | + return ( |
663 | + hash(self.distroseriesID) |
664 | + ^ hash(self.sourcepackagenameID) |
665 | + ^ hash(self.channel) |
666 | + ) |
667 | |
668 | def __eq__(self, other): |
669 | """See `ISourcePackage`.""" |
670 | @@ -624,6 +651,7 @@ class SourcePackage( |
671 | (ISourcePackage.providedBy(other)) |
672 | and (self.distroseries.id == other.distroseries.id) |
673 | and (self.sourcepackagename.id == other.sourcepackagename.id) |
674 | + and (self.channel == other.channel) |
675 | ) |
676 | |
677 | def __ne__(self, other): |
678 | @@ -672,6 +700,16 @@ class SourcePackage( |
679 | ) |
680 | ] |
681 | |
682 | + if self.channel is None: |
683 | + condition_clauses.append( |
684 | + "SourcePackagePublishingHistory.channel IS NULL" |
685 | + ) |
686 | + else: |
687 | + condition_clauses.append( |
688 | + "SourcePackagePublishingHistory.channel = '%s'::jsonb" |
689 | + % json.dumps(channel_string_to_list(self.channel)) |
690 | + ) |
691 | + |
692 | # We re-use the optional-parameter handling provided by BuildSet |
693 | # here, but pass None for the name argument as we've already |
694 | # matched on exact source package name. |
695 | @@ -882,16 +920,26 @@ class SourcePackage( |
696 | |
697 | def weight_function(bugtask): |
698 | if bugtask.sourcepackagename_id == sourcepackagenameID: |
699 | - if bugtask.distroseries_id == seriesID: |
700 | + if ( |
701 | + bugtask.distroseries_id == seriesID |
702 | + and bugtask.channel == self.channel |
703 | + ): |
704 | return OrderedBugTask(1, bugtask.id, bugtask) |
705 | - elif bugtask.distribution_id == distributionID: |
706 | + elif bugtask.distroseries_id == seriesID: |
707 | return OrderedBugTask(2, bugtask.id, bugtask) |
708 | + elif bugtask.distribution_id == distributionID: |
709 | + return OrderedBugTask(3, bugtask.id, bugtask) |
710 | + elif ( |
711 | + bugtask.distroseries_id == seriesID |
712 | + and bugtask.channel == self.channel |
713 | + ): |
714 | + return OrderedBugTask(4, bugtask.id, bugtask) |
715 | elif bugtask.distroseries_id == seriesID: |
716 | - return OrderedBugTask(3, bugtask.id, bugtask) |
717 | + return OrderedBugTask(5, bugtask.id, bugtask) |
718 | elif bugtask.distribution_id == distributionID: |
719 | - return OrderedBugTask(4, bugtask.id, bugtask) |
720 | + return OrderedBugTask(6, bugtask.id, bugtask) |
721 | # Catch the default case, and where there is a task for the same |
722 | # sourcepackage on a different distro. |
723 | - return OrderedBugTask(5, bugtask.id, bugtask) |
724 | + return OrderedBugTask(7, bugtask.id, bugtask) |
725 | |
726 | return weight_function |
727 | diff --git a/lib/lp/registry/stories/webservice/xx-source-package.rst b/lib/lp/registry/stories/webservice/xx-source-package.rst |
728 | index d09785d..b5c17bc 100644 |
729 | --- a/lib/lp/registry/stories/webservice/xx-source-package.rst |
730 | +++ b/lib/lp/registry/stories/webservice/xx-source-package.rst |
731 | @@ -32,6 +32,7 @@ distribution series. |
732 | >>> pprint_entry(evolution) |
733 | bug_reported_acknowledgement: None |
734 | bug_reporting_guidelines: None |
735 | + channel: None |
736 | displayname: 'evolution in My-distro My-series' |
737 | distribution_link: 'http://.../my-distro' |
738 | distroseries_link: 'http://.../my-distro/my-series' |
739 | diff --git a/lib/lp/registry/tests/test_distributionsourcepackage.py b/lib/lp/registry/tests/test_distributionsourcepackage.py |
740 | index d8dffd9..6262cb4 100644 |
741 | --- a/lib/lp/registry/tests/test_distributionsourcepackage.py |
742 | +++ b/lib/lp/registry/tests/test_distributionsourcepackage.py |
743 | @@ -49,7 +49,7 @@ class TestDistributionSourcePackage(TestCaseWithFactory): |
744 | registrant=self.factory.makePerson(), |
745 | ) |
746 | naked_distribution = removeSecurityProxy(distribution) |
747 | - self.factory.makeSourcePackage(distroseries=distribution) |
748 | + self.factory.makeDistributionSourcePackage(distribution=distribution) |
749 | dsp = naked_distribution.getSourcePackage(name="pmount") |
750 | self.assertEqual(None, dsp.summary) |
751 | |
752 | diff --git a/lib/lp/registry/tests/test_distroseries.py b/lib/lp/registry/tests/test_distroseries.py |
753 | index ab3c6bf..168386f 100644 |
754 | --- a/lib/lp/registry/tests/test_distroseries.py |
755 | +++ b/lib/lp/registry/tests/test_distroseries.py |
756 | @@ -449,6 +449,12 @@ class TestDistroSeries(TestCaseWithFactory): |
757 | ] |
758 | ) |
759 | |
760 | + def test_getSourcePackage_channel(self): |
761 | + distroseries = self.factory.makeDistroSeries() |
762 | + spn = self.factory.makeSourcePackageName() |
763 | + source_package = distroseries.getSourcePackage(spn, channel="stable") |
764 | + self.assertEqual("stable", source_package.channel) |
765 | + |
766 | |
767 | class TestDistroSeriesPackaging(TestCaseWithFactory): |
768 | |
769 | diff --git a/lib/lp/registry/tests/test_sourcepackage.py b/lib/lp/registry/tests/test_sourcepackage.py |
770 | index b839a18..a74f23a 100644 |
771 | --- a/lib/lp/registry/tests/test_sourcepackage.py |
772 | +++ b/lib/lp/registry/tests/test_sourcepackage.py |
773 | @@ -506,6 +506,66 @@ class TestSourcePackage(TestCaseWithFactory): |
774 | sourcepackage.personHasDriverRights(distroseries.owner) |
775 | ) |
776 | |
777 | + def test_channel(self): |
778 | + source_package = self.factory.makeSourcePackage(channel="stable") |
779 | + self.assertEqual("stable", source_package.channel) |
780 | + |
781 | + def test_hash(self): |
782 | + spn = self.factory.makeSourcePackageName() |
783 | + distroseries = self.factory.makeDistroSeries() |
784 | + self.assertEqual( |
785 | + hash( |
786 | + self.factory.makeSourcePackage( |
787 | + sourcepackagename=spn, distroseries=distroseries |
788 | + ) |
789 | + ), |
790 | + hash( |
791 | + self.factory.makeSourcePackage( |
792 | + sourcepackagename=spn, distroseries=distroseries |
793 | + ) |
794 | + ), |
795 | + ) |
796 | + self.assertNotEqual( |
797 | + hash( |
798 | + self.factory.makeSourcePackage( |
799 | + sourcepackagename=spn, |
800 | + distroseries=distroseries, |
801 | + channel="stable", |
802 | + ) |
803 | + ), |
804 | + hash( |
805 | + self.factory.makeSourcePackage( |
806 | + sourcepackagename=spn, |
807 | + distroseries=distroseries, |
808 | + channel="beta", |
809 | + ) |
810 | + ), |
811 | + ) |
812 | + |
813 | + def test_eq(self): |
814 | + spn = self.factory.makeSourcePackageName() |
815 | + distroseries = self.factory.makeDistroSeries() |
816 | + self.assertEqual( |
817 | + self.factory.makeSourcePackage( |
818 | + sourcepackagename=spn, distroseries=distroseries |
819 | + ), |
820 | + self.factory.makeSourcePackage( |
821 | + sourcepackagename=spn, distroseries=distroseries |
822 | + ), |
823 | + ) |
824 | + self.assertNotEqual( |
825 | + self.factory.makeSourcePackage( |
826 | + sourcepackagename=spn, |
827 | + distroseries=distroseries, |
828 | + channel="stable", |
829 | + ), |
830 | + self.factory.makeSourcePackage( |
831 | + sourcepackagename=spn, |
832 | + distroseries=distroseries, |
833 | + channel="beta", |
834 | + ), |
835 | + ) |
836 | + |
837 | |
838 | class TestSourcePackageWebService(WebServiceTestCase): |
839 | def test_setPackaging(self): |
840 | diff --git a/lib/lp/soyuz/tests/test_binarypackagebuild.py b/lib/lp/soyuz/tests/test_binarypackagebuild.py |
841 | index ef513d3..939e6c1 100644 |
842 | --- a/lib/lp/soyuz/tests/test_binarypackagebuild.py |
843 | +++ b/lib/lp/soyuz/tests/test_binarypackagebuild.py |
844 | @@ -433,13 +433,19 @@ class BaseTestCaseWithThreeBuilds(TestCaseWithFactory): |
845 | """Publish some builds for the test archive.""" |
846 | super().setUp() |
847 | self.ds = self.factory.makeDistroSeries() |
848 | + self.builds = self.makeBuilds() |
849 | + self.sources = [ |
850 | + build.current_source_publication for build in self.builds |
851 | + ] |
852 | + |
853 | + def makeBuilds(self): |
854 | i386_das = self.factory.makeDistroArchSeries( |
855 | distroseries=self.ds, architecturetag="i386" |
856 | ) |
857 | hppa_das = self.factory.makeDistroArchSeries( |
858 | distroseries=self.ds, architecturetag="hppa" |
859 | ) |
860 | - self.builds = [ |
861 | + return [ |
862 | self.factory.makeBinaryPackageBuild( |
863 | archive=self.ds.main_archive, distroarchseries=i386_das |
864 | ), |
865 | @@ -449,12 +455,10 @@ class BaseTestCaseWithThreeBuilds(TestCaseWithFactory): |
866 | pocket=PackagePublishingPocket.PROPOSED, |
867 | ), |
868 | self.factory.makeBinaryPackageBuild( |
869 | - archive=self.ds.main_archive, distroarchseries=hppa_das |
870 | + archive=self.ds.main_archive, |
871 | + distroarchseries=hppa_das, |
872 | ), |
873 | ] |
874 | - self.sources = [ |
875 | - build.current_source_publication for build in self.builds |
876 | - ] |
877 | |
878 | |
879 | class TestBuildSet(TestCaseWithFactory): |
880 | diff --git a/lib/lp/soyuz/tests/test_hasbuildrecords.py b/lib/lp/soyuz/tests/test_hasbuildrecords.py |
881 | index b0b8e8c..07c5c26 100644 |
882 | --- a/lib/lp/soyuz/tests/test_hasbuildrecords.py |
883 | +++ b/lib/lp/soyuz/tests/test_hasbuildrecords.py |
884 | @@ -2,7 +2,7 @@ |
885 | # GNU Affero General Public License version 3 (see the file LICENSE). |
886 | |
887 | """Test implementations of the IHasBuildRecords interface.""" |
888 | - |
889 | +from testscenarios import WithScenarios |
890 | from zope.component import getUtility |
891 | from zope.security.proxy import removeSecurityProxy |
892 | |
893 | @@ -14,12 +14,11 @@ from lp.buildmaster.interfaces.buildfarmjob import ( |
894 | ) |
895 | from lp.registry.interfaces.person import IPersonSet |
896 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
897 | +from lp.registry.interfaces.sourcepackage import SourcePackageType |
898 | from lp.registry.model.sourcepackage import SourcePackage |
899 | -from lp.services.database.interfaces import IStore |
900 | from lp.soyuz.enums import ArchivePurpose |
901 | from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuild |
902 | from lp.soyuz.interfaces.buildrecords import IHasBuildRecords |
903 | -from lp.soyuz.model.publishing import SourcePackagePublishingHistory |
904 | from lp.soyuz.tests.test_binarypackagebuild import BaseTestCaseWithThreeBuilds |
905 | from lp.soyuz.tests.test_publishing import SoyuzTestPublisher |
906 | from lp.testing import TestCaseWithFactory, person_logged_in |
907 | @@ -256,30 +255,64 @@ class TestBuilderHasBuildRecords(TestHasBuildRecordsInterface): |
908 | ) |
909 | |
910 | |
911 | -class TestSourcePackageHasBuildRecords(TestHasBuildRecordsInterface): |
912 | +class TestSourcePackageHasBuildRecords( |
913 | + WithScenarios, TestHasBuildRecordsInterface |
914 | +): |
915 | """Test the SourcePackage implementation of IHasBuildRecords.""" |
916 | |
917 | + scenarios = [ |
918 | + ( |
919 | + "channel", |
920 | + {"channel": "stable", "format": SourcePackageType.CI_BUILD}, |
921 | + ), |
922 | + ("no_channel", {"channel": None, "format": None}), |
923 | + ] |
924 | + |
925 | def setUp(self): |
926 | super().setUp() |
927 | gedit_name = self.builds[0].source_package_release.sourcepackagename |
928 | self.context = SourcePackage( |
929 | - gedit_name, self.builds[0].distro_arch_series.distroseries |
930 | + gedit_name, |
931 | + self.builds[0].distro_arch_series.distroseries, |
932 | + channel=self.channel, |
933 | ) |
934 | |
935 | - # Convert the other two builds to be builds of |
936 | - # gedit as well so that the one source package (gedit) will have |
937 | - # three builds. |
938 | - for build in self.builds[1:3]: |
939 | - spr = build.source_package_release |
940 | - removeSecurityProxy(spr).sourcepackagename = gedit_name |
941 | - IStore(SourcePackagePublishingHistory).find( |
942 | - SourcePackagePublishingHistory, sourcepackagerelease=spr |
943 | - ).set(sourcepackagenameID=gedit_name.id) |
944 | - |
945 | + def makeBuilds(self): |
946 | + i386_das = self.factory.makeDistroArchSeries( |
947 | + distroseries=self.ds, architecturetag="i386" |
948 | + ) |
949 | + hppa_das = self.factory.makeDistroArchSeries( |
950 | + distroseries=self.ds, architecturetag="hppa" |
951 | + ) |
952 | + spn = self.factory.makeSourcePackageName() |
953 | + builds = [ |
954 | + self.factory.makeBinaryPackageBuild( |
955 | + sourcepackagename=spn, |
956 | + archive=self.ds.main_archive, |
957 | + distroarchseries=i386_das, |
958 | + channel=self.channel, |
959 | + format=self.format, |
960 | + ), |
961 | + self.factory.makeBinaryPackageBuild( |
962 | + sourcepackagename=spn, |
963 | + archive=self.ds.main_archive, |
964 | + distroarchseries=i386_das, |
965 | + channel=self.channel, |
966 | + format=self.format, |
967 | + ), |
968 | + self.factory.makeBinaryPackageBuild( |
969 | + sourcepackagename=spn, |
970 | + archive=self.ds.main_archive, |
971 | + distroarchseries=hppa_das, |
972 | + channel=self.channel, |
973 | + format=self.format, |
974 | + ), |
975 | + ] |
976 | # Set them as successfully built |
977 | - for build in self.builds: |
978 | + for build in builds: |
979 | build.updateStatus(BuildStatus.BUILDING) |
980 | build.updateStatus(BuildStatus.FULLYBUILT) |
981 | + return builds |
982 | |
983 | def test_get_build_records(self): |
984 | # We can fetch builds records from a SourcePackage. |
985 | @@ -290,7 +323,7 @@ class TestSourcePackageHasBuildRecords(TestHasBuildRecordsInterface): |
986 | builds = self.context.getBuildRecords( |
987 | pocket=PackagePublishingPocket.RELEASE |
988 | ).count() |
989 | - self.assertEqual(2, builds) |
990 | + self.assertEqual(3, builds) |
991 | builds = self.context.getBuildRecords( |
992 | pocket=PackagePublishingPocket.UPDATES |
993 | ).count() |
994 | diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py |
995 | index 40a959c..b83a946 100644 |
996 | --- a/lib/lp/testing/factory.py |
997 | +++ b/lib/lp/testing/factory.py |
998 | @@ -31,6 +31,7 @@ from functools import wraps |
999 | from io import BytesIO |
1000 | from itertools import count |
1001 | from textwrap import dedent |
1002 | +from typing import Optional |
1003 | |
1004 | import pytz |
1005 | import six |
1006 | @@ -2385,6 +2386,7 @@ class LaunchpadObjectFactory(ObjectFactory): |
1007 | self.makeSourcePackagePublishingHistory( |
1008 | distroseries=target.distroseries, |
1009 | sourcepackagename=target.sourcepackagename, |
1010 | + channel=target.channel, |
1011 | ) |
1012 | if IDistributionSourcePackage.providedBy(target): |
1013 | if publish: |
1014 | @@ -4575,6 +4577,7 @@ class LaunchpadObjectFactory(ObjectFactory): |
1015 | distroseries=None, |
1016 | publish=False, |
1017 | owner=None, |
1018 | + channel: Optional[str] = None, |
1019 | ): |
1020 | """Make an `ISourcePackage`. |
1021 | |
1022 | @@ -4590,7 +4593,9 @@ class LaunchpadObjectFactory(ObjectFactory): |
1023 | distroseries = self.makeDistroSeries(owner=owner) |
1024 | if publish: |
1025 | self.makeSourcePackagePublishingHistory( |
1026 | - distroseries=distroseries, sourcepackagename=sourcepackagename |
1027 | + distroseries=distroseries, |
1028 | + sourcepackagename=sourcepackagename, |
1029 | + channel=channel, |
1030 | ) |
1031 | with dbuser("statistician"): |
1032 | DistributionSourcePackageCache( |
1033 | @@ -4599,7 +4604,9 @@ class LaunchpadObjectFactory(ObjectFactory): |
1034 | archive=distroseries.main_archive, |
1035 | name=sourcepackagename.name, |
1036 | ) |
1037 | - return distroseries.getSourcePackage(sourcepackagename) |
1038 | + return distroseries.getSourcePackage( |
1039 | + sourcepackagename, channel=channel |
1040 | + ) |
1041 | |
1042 | def getAnySourcePackageUrgency(self): |
1043 | return SourcePackageUrgency.MEDIUM |
1044 | @@ -4892,6 +4899,8 @@ class LaunchpadObjectFactory(ObjectFactory): |
1045 | processor=None, |
1046 | sourcepackagename=None, |
1047 | arch_indep=None, |
1048 | + channel=None, |
1049 | + format=None, |
1050 | ): |
1051 | """Create a BinaryPackageBuild. |
1052 | |
1053 | @@ -4950,12 +4959,14 @@ class LaunchpadObjectFactory(ObjectFactory): |
1054 | component=multiverse, |
1055 | distroseries=distroarchseries.distroseries, |
1056 | sourcepackagename=sourcepackagename, |
1057 | + format=format, |
1058 | ) |
1059 | self.makeSourcePackagePublishingHistory( |
1060 | distroseries=distroarchseries.distroseries, |
1061 | archive=archive, |
1062 | sourcepackagerelease=source_package_release, |
1063 | pocket=pocket, |
1064 | + channel=channel, |
1065 | ) |
1066 | if status is None: |
1067 | status = BuildStatus.NEEDSBUILD |