Merge lp:~wgrant/launchpad/export-detailed-binary-download-stats into lp:launchpad/db-devel
- export-detailed-binary-download-stats
- Merge into db-devel
Proposed by
William Grant
Status: | Merged |
---|---|
Approved by: | Eleanor Berger |
Approved revision: | no longer in the source branch. |
Merged at revision: | not available |
Proposed branch: | lp:~wgrant/launchpad/export-detailed-binary-download-stats |
Merge into: | lp:launchpad/db-devel |
Diff against target: |
714 lines (+450/-35) 13 files modified
lib/canonical/launchpad/security.py (+7/-0) lib/lp/soyuz/browser/archive.py (+52/-0) lib/lp/soyuz/browser/configure.zcml (+5/-0) lib/lp/soyuz/configure.zcml (+8/-0) lib/lp/soyuz/doc/publishing.txt (+38/-3) lib/lp/soyuz/interfaces/archive.py (+15/-0) lib/lp/soyuz/interfaces/binarypackagerelease.py (+25/-11) lib/lp/soyuz/interfaces/publishing.py (+26/-1) lib/lp/soyuz/model/archive.py (+25/-0) lib/lp/soyuz/model/binarypackagerelease.py (+11/-0) lib/lp/soyuz/model/publishing.py (+48/-2) lib/lp/soyuz/stories/webservice/xx-binary-package-publishing.txt (+108/-18) lib/lp/soyuz/tests/test_archive.py (+82/-0) |
To merge this branch: | bzr merge lp:~wgrant/launchpad/export-detailed-binary-download-stats |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Eleanor Berger (community) | code | Approve | |
Review via email: mp+21828@code.launchpad.net |
Commit message
Export detailed PPA binary download counts.
Description of the change
This branch exports the most detailed PPA binary download counts that we collect, adding webservice methods to retrieve counts per (archive, binary, day, country) and (archive, binary, day).
The URL for IBPRDC is pretty foul, and by far the longest in Launchpad. But Julian advised me to do that rather than use BPRDC.id.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/canonical/launchpad/security.py' |
2 | --- lib/canonical/launchpad/security.py 2010-03-19 05:19:20 +0000 |
3 | +++ lib/canonical/launchpad/security.py 2010-03-22 07:42:21 +0000 |
4 | @@ -71,6 +71,8 @@ |
5 | from lp.soyuz.interfaces.packageset import IPackageset, IPackagesetSet |
6 | from lp.translations.interfaces.pofile import IPOFile |
7 | from lp.translations.interfaces.potemplate import IPOTemplate |
8 | +from lp.soyuz.interfaces.binarypackagerelease import ( |
9 | + IBinaryPackageReleaseDownloadCount) |
10 | from lp.soyuz.interfaces.build import IBuild |
11 | from lp.soyuz.interfaces.buildfarmbuildjob import IBuildFarmBuildJob |
12 | from lp.soyuz.interfaces.publishing import ( |
13 | @@ -2173,6 +2175,11 @@ |
14 | usedfor = IBinaryPackagePublishingHistory |
15 | |
16 | |
17 | +class ViewBinaryPackageReleaseDownloadCount(ViewSourcePackagePublishingHistory): |
18 | + """Restrict viewing of binary package download counts.""" |
19 | + usedfor = IBinaryPackageReleaseDownloadCount |
20 | + |
21 | + |
22 | class ViewSourcePackageRelease(AuthorizationBase): |
23 | """Restrict viewing of source packages. |
24 | |
25 | |
26 | === modified file 'lib/lp/soyuz/browser/archive.py' |
27 | --- lib/lp/soyuz/browser/archive.py 2010-03-10 12:50:18 +0000 |
28 | +++ lib/lp/soyuz/browser/archive.py 2010-03-22 07:42:21 +0000 |
29 | @@ -48,6 +48,7 @@ |
30 | from canonical.lazr.utils import smartquote |
31 | from lp.buildmaster.interfaces.buildbase import BuildStatus |
32 | from lp.services.browser_helpers import get_user_agent_distroseries |
33 | +from lp.services.worlddata.interfaces.country import ICountrySet |
34 | from lp.soyuz.browser.build import BuildRecordsView |
35 | from lp.soyuz.browser.sourceslist import ( |
36 | SourcesListEntries, SourcesListEntriesView) |
37 | @@ -62,6 +63,7 @@ |
38 | from lp.soyuz.interfaces.archivepermission import ( |
39 | ArchivePermissionType, IArchivePermissionSet) |
40 | from lp.soyuz.interfaces.archivesubscriber import IArchiveSubscriberSet |
41 | +from lp.soyuz.interfaces.binarypackagename import IBinaryPackageNameSet |
42 | from lp.soyuz.interfaces.build import BuildSetStatus, IBuildSet |
43 | from lp.soyuz.interfaces.buildrecords import IHasBuildRecords |
44 | from lp.soyuz.interfaces.component import IComponentSet |
45 | @@ -217,6 +219,56 @@ |
46 | |
47 | return None |
48 | |
49 | + @stepthrough('+binaryhits') |
50 | + def traverse_binaryhits(self, name_str): |
51 | + """Traverse to an `IBinaryPackageReleaseDownloadCount`. |
52 | + |
53 | + A matching path is something like this: |
54 | + |
55 | + +binaryhits/foopkg/1.0/i386/2010-03-11/AU |
56 | + |
57 | + To reach one where the country is None, use: |
58 | + |
59 | + +binaryhits/foopkg/1.0/i386/2010-03-11/unknown |
60 | + """ |
61 | + |
62 | + if len(self.request.stepstogo) < 4: |
63 | + return None |
64 | + |
65 | + version = self.request.stepstogo.consume() |
66 | + archtag = self.request.stepstogo.consume() |
67 | + date_str = self.request.stepstogo.consume() |
68 | + country_str = self.request.stepstogo.consume() |
69 | + |
70 | + try: |
71 | + name = getUtility(IBinaryPackageNameSet)[name_str] |
72 | + except NotFoundError: |
73 | + return None |
74 | + |
75 | + # This will return None if there are multiple BPRs with the same |
76 | + # name in the archive's history, but in that case downloads |
77 | + # won't be counted either. |
78 | + bpr = self.context.getBinaryPackageRelease(name, version, archtag) |
79 | + if bpr is None: |
80 | + return None |
81 | + |
82 | + try: |
83 | + date = datetime.strptime(date_str, '%Y-%m-%d').date() |
84 | + except ValueError: |
85 | + return None |
86 | + |
87 | + # 'unknown' should always be safe, since the key is the two letter |
88 | + # ISO code, and 'unknown' has more than two letters. |
89 | + if country_str == 'unknown': |
90 | + country = None |
91 | + else: |
92 | + try: |
93 | + country = getUtility(ICountrySet)[country_str] |
94 | + except NotFoundError: |
95 | + return None |
96 | + |
97 | + return self.context.getPackageDownloadCount(bpr, date, country) |
98 | + |
99 | @stepthrough('+subscriptions') |
100 | def traverse_subscription(self, person_name): |
101 | try: |
102 | |
103 | === modified file 'lib/lp/soyuz/browser/configure.zcml' |
104 | --- lib/lp/soyuz/browser/configure.zcml 2010-03-16 07:31:35 +0000 |
105 | +++ lib/lp/soyuz/browser/configure.zcml 2010-03-22 07:42:21 +0000 |
106 | @@ -798,6 +798,11 @@ |
107 | path_expression="string:+dependency/${dependency/id}" |
108 | attribute_to_parent="archive" |
109 | /> |
110 | + <browser:url |
111 | + for="lp.soyuz.interfaces.binarypackagerelease.IBinaryPackageReleaseDownloadCount" |
112 | + path_expression="string:+binaryhits/${binary_package_release/name}/${binary_package_release/version}/${binary_package_release/build/distroarchseries/architecturetag}/${day}/${country/iso3166code2|string:unknown}" |
113 | + attribute_to_parent="archive" |
114 | + /> |
115 | |
116 | <!-- XXX JeroenVermeulen 2010-03-13 bug=539395: This is buildmaster, not soyuz --> |
117 | <browser:page |
118 | |
119 | === modified file 'lib/lp/soyuz/configure.zcml' |
120 | --- lib/lp/soyuz/configure.zcml 2010-03-18 17:14:50 +0000 |
121 | +++ lib/lp/soyuz/configure.zcml 2010-03-22 07:42:21 +0000 |
122 | @@ -864,4 +864,12 @@ |
123 | factory="lp.soyuz.model.binarypackagebuildbehavior.BinaryPackageBuildBehavior" |
124 | permission="zope.Public" /> |
125 | |
126 | + <!-- BinaryPackageReleaseDownloadCount --> |
127 | + <class |
128 | + class="lp.soyuz.model.binarypackagerelease.BinaryPackageReleaseDownloadCount"> |
129 | + <require |
130 | + permission="launchpad.View" |
131 | + interface="lp.soyuz.interfaces.binarypackagerelease.IBinaryPackageReleaseDownloadCount"/> |
132 | + </class> |
133 | + |
134 | </configure> |
135 | |
136 | === modified file 'lib/lp/soyuz/doc/publishing.txt' |
137 | --- lib/lp/soyuz/doc/publishing.txt 2010-03-19 06:53:28 +0000 |
138 | +++ lib/lp/soyuz/doc/publishing.txt 2010-03-22 07:42:21 +0000 |
139 | @@ -956,11 +956,46 @@ |
140 | 0 |
141 | |
142 | >>> from datetime import date |
143 | - >>> bpph.archive.updatePackageDownloadCount( |
144 | - ... bpph.binarypackagerelease, date(2010, 2, 21), None, 10) |
145 | + >>> from lp.services.worlddata.interfaces.country import ICountrySet |
146 | + >>> australia = getUtility(ICountrySet)['AU'] |
147 | + >>> uk = getUtility(ICountrySet)['GB'] |
148 | + |
149 | + >>> bpph.archive.updatePackageDownloadCount( |
150 | + ... bpph.binarypackagerelease, date(2010, 2, 19), None, 2) |
151 | + >>> bpph.archive.updatePackageDownloadCount( |
152 | + ... bpph.binarypackagerelease, date(2010, 2, 21), australia, 10) |
153 | + >>> bpph.archive.updatePackageDownloadCount( |
154 | + ... bpph.binarypackagerelease, date(2010, 2, 21), uk, 4) |
155 | |
156 | >>> print bpph.getDownloadCount() |
157 | - 10 |
158 | + 16 |
159 | + |
160 | +We can also use getDownloadCounts to find the raw download counts per |
161 | +day and country. |
162 | + |
163 | + >>> [(b.day, b.country.name if b.country is not None else None) |
164 | + ... for b in bpph.getDownloadCounts()] |
165 | + [(datetime.date(2010, 2, 21), u'Australia'), |
166 | + (datetime.date(2010, 2, 21), u'United Kingdom'), |
167 | + (datetime.date(2010, 2, 19), None)] |
168 | + |
169 | +getDownloadCounts lets us filter by date. |
170 | + |
171 | + >>> [b.day for b in bpph.getDownloadCounts(start_date=date(2010, 2, 21))] |
172 | + [datetime.date(2010, 2, 21), datetime.date(2010, 2, 21)] |
173 | + >>> [b.day for b in bpph.getDownloadCounts(end_date=date(2010, 2, 20))] |
174 | + [datetime.date(2010, 2, 19)] |
175 | + >>> [b.day for b in bpph.getDownloadCounts( |
176 | + ... start_date=date(2010, 2, 20), end_date=date(2010, 2, 20))] |
177 | + [] |
178 | + |
179 | +We can also get a dict of totals for each day. The keys are strings to |
180 | +work around lazr.restful's dict limitations. This too has a date filter. |
181 | + |
182 | + >>> bpph.getDailyDownloadTotals() |
183 | + {'2010-02-21': 14L, '2010-02-19': 2L} |
184 | + >>> bpph.getDailyDownloadTotals(start_date=date(2010, 2, 20)) |
185 | + {'2010-02-21': 14L} |
186 | |
187 | |
188 | IPublishingSet |
189 | |
190 | === modified file 'lib/lp/soyuz/interfaces/archive.py' |
191 | --- lib/lp/soyuz/interfaces/archive.py 2010-03-19 11:29:22 +0000 |
192 | +++ lib/lp/soyuz/interfaces/archive.py 2010-03-22 07:42:21 +0000 |
193 | @@ -425,6 +425,18 @@ |
194 | :return the corresponding `ILibraryFileAlias` is the file was found. |
195 | """ |
196 | |
197 | + def getBinaryPackageRelease(name, version, archtag): |
198 | + """Find the specified `IBinaryPackageRelease` in the archive. |
199 | + |
200 | + :param name: The `IBinaryPackageName` of the package. |
201 | + :param version: The version of the package. |
202 | + :param archtag: The architecture tag of the package's build. 'all' |
203 | + will not work here -- 'i386' (the build DAS) must be used instead. |
204 | + |
205 | + :return The binary package release with the given name and version, |
206 | + or None if one does not exist or there is more than one. |
207 | + """ |
208 | + |
209 | def getBinaryPackageReleaseByFileName(filename): |
210 | """Return the corresponding `IBinaryPackageRelease` in this context. |
211 | |
212 | @@ -919,6 +931,9 @@ |
213 | :return: A list of `IArchivePermission` records. |
214 | """ |
215 | |
216 | + def getPackageDownloadCount(bpr, day, country): |
217 | + """Get the `IBinaryPackageDownloadCount` with the given key.""" |
218 | + |
219 | |
220 | class IArchiveAppend(Interface): |
221 | """Archive interface for operations restricted by append privilege.""" |
222 | |
223 | === modified file 'lib/lp/soyuz/interfaces/binarypackagerelease.py' |
224 | --- lib/lp/soyuz/interfaces/binarypackagerelease.py 2010-03-15 23:01:48 +0000 |
225 | +++ lib/lp/soyuz/interfaces/binarypackagerelease.py 2010-03-22 07:42:21 +0000 |
226 | @@ -16,13 +16,15 @@ |
227 | ] |
228 | |
229 | from lazr.enum import DBEnumeratedType, DBItem |
230 | -from lazr.restful.fields import Reference |
231 | -from zope.schema import Bool, Choice, Date, Int, Text, TextLine, Datetime |
232 | +from lazr.restful.declarations import exported, export_as_webservice_entry |
233 | +from lazr.restful.fields import Reference, ReferenceChoice |
234 | +from zope.schema import Bool, Date, Int, Text, TextLine, Datetime |
235 | from zope.interface import Interface, Attribute |
236 | |
237 | from canonical.launchpad import _ |
238 | from canonical.launchpad.validators.version import valid_debian_version |
239 | from lp.soyuz.interfaces.archive import IArchive |
240 | +from lp.services.worlddata.interfaces.country import ICountry |
241 | |
242 | |
243 | class IBinaryPackageRelease(Interface): |
244 | @@ -95,19 +97,31 @@ |
245 | |
246 | class IBinaryPackageReleaseDownloadCount(Interface): |
247 | """Daily download count of a binary package release in an archive.""" |
248 | + export_as_webservice_entry() |
249 | |
250 | - archive = Reference( |
251 | - title=_('The archive'), schema=IArchive, required=True, |
252 | - readonly=True) |
253 | + id = Int(title=_('ID'), required=True, readonly=True) |
254 | + archive = exported(Reference( |
255 | + title=_('Archive'), schema=IArchive, required=True, |
256 | + readonly=True)) |
257 | binary_package_release = Reference( |
258 | title=_('The binary package release'), schema=IBinaryPackageRelease, |
259 | required=True, readonly=True) |
260 | - day = Date( |
261 | - title=_('The day of the downloads'), required=True, readonly=True) |
262 | - count = Int( |
263 | - title=_('The number of downloads'), required=True, readonly=False) |
264 | - country = Choice( |
265 | - title=_('Country'), required=False, vocabulary='CountryName') |
266 | + binary_package_name = exported( |
267 | + TextLine( |
268 | + title=_("Binary package name"), |
269 | + required=False, readonly=True)) |
270 | + binary_package_version = exported( |
271 | + TextLine( |
272 | + title=_("Binary package version"), |
273 | + required=False, readonly=True)) |
274 | + day = exported( |
275 | + Date(title=_('Day of the downloads'), required=True, readonly=True)) |
276 | + count = exported( |
277 | + Int(title=_('Number of downloads'), required=True, readonly=True)) |
278 | + country = exported( |
279 | + ReferenceChoice( |
280 | + title=_('Country'), required=False, readonly=True, |
281 | + vocabulary='CountryName', schema=ICountry)) |
282 | |
283 | |
284 | class BinaryPackageFileType(DBEnumeratedType): |
285 | |
286 | === modified file 'lib/lp/soyuz/interfaces/publishing.py' |
287 | --- lib/lp/soyuz/interfaces/publishing.py 2010-03-11 02:26:37 +0000 |
288 | +++ lib/lp/soyuz/interfaces/publishing.py 2010-03-22 07:42:21 +0000 |
289 | @@ -29,7 +29,7 @@ |
290 | 'name_priority_map', |
291 | ] |
292 | |
293 | -from zope.schema import Choice, Datetime, Int, TextLine, Text |
294 | +from zope.schema import Choice, Date, Datetime, Int, TextLine, Text |
295 | from zope.interface import Interface, Attribute |
296 | from lazr.enum import DBEnumeratedType, DBItem |
297 | |
298 | @@ -37,6 +37,8 @@ |
299 | from lp.registry.interfaces.distroseries import IDistroSeries |
300 | from lp.registry.interfaces.person import IPerson |
301 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
302 | +from lp.soyuz.interfaces.binarypackagerelease import ( |
303 | + IBinaryPackageReleaseDownloadCount) |
304 | |
305 | from lazr.restful.fields import Reference |
306 | from lazr.restful.declarations import ( |
307 | @@ -862,6 +864,29 @@ |
308 | |
309 | This is currently only meaningful for PPAs.""" |
310 | |
311 | + @operation_parameters( |
312 | + start_date=Date(title=_("Start date"), required=False), |
313 | + end_date=Date(title=_("End date"), required=False)) |
314 | + @operation_returns_collection_of(IBinaryPackageReleaseDownloadCount) |
315 | + @export_read_operation() |
316 | + def getDownloadCounts(start_date=None, end_date=None): |
317 | + """Get detailed download counts for this binary. |
318 | + |
319 | + :param start_date: The optional first date to return. |
320 | + :param end_date: The optional last date to return. |
321 | + """ |
322 | + |
323 | + @operation_parameters( |
324 | + start_date=Date(title=_("Start date"), required=False), |
325 | + end_date=Date(title=_("End date"), required=False)) |
326 | + @export_read_operation() |
327 | + def getDailyDownloadTotals(start_date=None, end_date=None): |
328 | + """Get the daily download counts for this binary. |
329 | + |
330 | + :param start_date: The optional first date to return. |
331 | + :param end_date: The optional last date to return. |
332 | + """ |
333 | + |
334 | |
335 | class IBinaryPackagePublishingHistory(IBinaryPackagePublishingHistoryPublic, |
336 | IPublishingEdit): |
337 | |
338 | === modified file 'lib/lp/soyuz/model/archive.py' |
339 | --- lib/lp/soyuz/model/archive.py 2010-03-19 11:29:22 +0000 |
340 | +++ lib/lp/soyuz/model/archive.py 2010-03-22 07:42:21 +0000 |
341 | @@ -1133,6 +1133,25 @@ |
342 | |
343 | return archive_file |
344 | |
345 | + def getBinaryPackageRelease(self, name, version, archtag): |
346 | + """See `IArchive`.""" |
347 | + from lp.soyuz.model.distroarchseries import DistroArchSeries |
348 | + |
349 | + store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR) |
350 | + results = store.find( |
351 | + BinaryPackageRelease, |
352 | + BinaryPackageRelease.binarypackagename == name, |
353 | + BinaryPackageRelease.version == version, |
354 | + Build.id == BinaryPackageRelease.buildID, |
355 | + DistroArchSeries.id == Build.distroarchseriesID, |
356 | + DistroArchSeries.architecturetag == archtag, |
357 | + BinaryPackagePublishingHistory.archive == self, |
358 | + BinaryPackagePublishingHistory.binarypackagereleaseID == |
359 | + BinaryPackageRelease.id).config(distinct=True) |
360 | + if results.count() > 1: |
361 | + return None |
362 | + return results.one() |
363 | + |
364 | def getBinaryPackageReleaseByFileName(self, filename): |
365 | store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR) |
366 | results = store.find( |
367 | @@ -1355,6 +1374,12 @@ |
368 | ).one() |
369 | return count or 0 |
370 | |
371 | + def getPackageDownloadCount(self, bpr, day, country): |
372 | + """See `IArchive`.""" |
373 | + return Store.of(self).find( |
374 | + BinaryPackageReleaseDownloadCount, archive=self, |
375 | + binary_package_release=bpr, day=day, country=country).one() |
376 | + |
377 | def _setBuildStatuses(self, status): |
378 | """Update the pending Build Jobs' status for this archive.""" |
379 | |
380 | |
381 | === modified file 'lib/lp/soyuz/model/binarypackagerelease.py' |
382 | --- lib/lp/soyuz/model/binarypackagerelease.py 2010-03-15 23:01:48 +0000 |
383 | +++ lib/lp/soyuz/model/binarypackagerelease.py 2010-03-22 07:42:21 +0000 |
384 | @@ -230,3 +230,14 @@ |
385 | self.day = day |
386 | self.country = country |
387 | self.count = count |
388 | + |
389 | + @property |
390 | + def binary_package_name(self): |
391 | + """See `IBinaryPackageReleaseDownloadCount`.""" |
392 | + return self.binary_package_release.name |
393 | + |
394 | + @property |
395 | + def binary_package_version(self): |
396 | + """See `IBinaryPackageReleaseDownloadCount`.""" |
397 | + return self.binary_package_release.version |
398 | + |
399 | |
400 | === modified file 'lib/lp/soyuz/model/publishing.py' |
401 | --- lib/lp/soyuz/model/publishing.py 2010-03-17 09:23:40 +0000 |
402 | +++ lib/lp/soyuz/model/publishing.py 2010-03-22 07:42:21 +0000 |
403 | @@ -28,7 +28,7 @@ |
404 | |
405 | from sqlobject import ForeignKey, StringCol |
406 | |
407 | -from storm.expr import Desc, In, LeftJoin |
408 | +from storm.expr import Desc, In, LeftJoin, Sum |
409 | from storm.store import Store |
410 | |
411 | from canonical.database.sqlbase import SQLBase, sqlvalues |
412 | @@ -43,8 +43,10 @@ |
413 | from lp.buildmaster.interfaces.buildbase import BuildStatus |
414 | from lp.registry.interfaces.person import validate_public_person |
415 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
416 | +from lp.services.worlddata.model.country import Country |
417 | from lp.soyuz.model.binarypackagename import BinaryPackageName |
418 | -from lp.soyuz.model.binarypackagerelease import BinaryPackageRelease |
419 | +from lp.soyuz.model.binarypackagerelease import (BinaryPackageRelease, |
420 | + BinaryPackageReleaseDownloadCount) |
421 | from lp.soyuz.model.files import ( |
422 | BinaryPackageFile, SourcePackageReleaseFile) |
423 | from canonical.launchpad.database.librarian import ( |
424 | @@ -1010,6 +1012,50 @@ |
425 | |
426 | self.component = component |
427 | |
428 | + def _getDownloadCountClauses(self, start_date=None, end_date=None): |
429 | + clauses = [ |
430 | + BinaryPackageReleaseDownloadCount.archive == self.archive, |
431 | + BinaryPackageReleaseDownloadCount.binary_package_release == |
432 | + self.binarypackagerelease, |
433 | + ] |
434 | + |
435 | + if start_date is not None: |
436 | + clauses.append( |
437 | + BinaryPackageReleaseDownloadCount.day >= start_date) |
438 | + if end_date is not None: |
439 | + clauses.append( |
440 | + BinaryPackageReleaseDownloadCount.day <= end_date) |
441 | + |
442 | + return clauses |
443 | + |
444 | + def getDownloadCounts(self, start_date=None, end_date=None): |
445 | + """See `IBinaryPackagePublishingHistory`.""" |
446 | + clauses = self._getDownloadCountClauses(start_date, end_date) |
447 | + |
448 | + return Store.of(self).using( |
449 | + BinaryPackageReleaseDownloadCount, |
450 | + LeftJoin( |
451 | + Country, |
452 | + BinaryPackageReleaseDownloadCount.country_id == |
453 | + Country.id)).find( |
454 | + BinaryPackageReleaseDownloadCount, *clauses).order_by( |
455 | + Desc(BinaryPackageReleaseDownloadCount.day), Country.name) |
456 | + |
457 | + def getDailyDownloadTotals(self, start_date=None, end_date=None): |
458 | + """See `IBinaryPackagePublishingHistory`.""" |
459 | + clauses = self._getDownloadCountClauses(start_date, end_date) |
460 | + |
461 | + results = Store.of(self).find( |
462 | + (BinaryPackageReleaseDownloadCount.day, |
463 | + Sum(BinaryPackageReleaseDownloadCount.count)), |
464 | + *clauses).group_by( |
465 | + BinaryPackageReleaseDownloadCount.day) |
466 | + |
467 | + def date_to_string(result): |
468 | + return (result[0].strftime('%Y-%m-%d'), result[1]) |
469 | + |
470 | + return dict(date_to_string(result) for result in results) |
471 | + |
472 | |
473 | class PublishingSet: |
474 | """Utilities for manipulating publications in batches.""" |
475 | |
476 | === modified file 'lib/lp/soyuz/stories/webservice/xx-binary-package-publishing.txt' |
477 | --- lib/lp/soyuz/stories/webservice/xx-binary-package-publishing.txt 2010-03-18 22:47:27 +0000 |
478 | +++ lib/lp/soyuz/stories/webservice/xx-binary-package-publishing.txt 2010-03-22 07:42:21 +0000 |
479 | @@ -86,24 +86,6 @@ |
480 | self_link: u'http://.../~cprov/+archive/ppa/+binarypub/30' |
481 | status: u'Published' |
482 | |
483 | -We can also retrieve the total download count for the binary in this archive. |
484 | - |
485 | - >>> webservice.named_get( |
486 | - ... pubs['entries'][0]['self_link'], 'getDownloadCount').jsonBody() |
487 | - 0 |
488 | - |
489 | - >>> login("foo.bar@canonical.com") |
490 | - >>> from datetime import date |
491 | - >>> from lp.soyuz.model.publishing import BinaryPackagePublishingHistory |
492 | - >>> bpph = BinaryPackagePublishingHistory.get(30) |
493 | - >>> bpph.archive.updatePackageDownloadCount( |
494 | - ... bpph.binarypackagerelease, date(2010, 2, 21), None, 10) |
495 | - >>> logout() |
496 | - |
497 | - >>> webservice.named_get( |
498 | - ... pubs['entries'][0]['self_link'], 'getDownloadCount').jsonBody() |
499 | - 10 |
500 | - |
501 | |
502 | Security |
503 | ======== |
504 | @@ -158,3 +140,111 @@ |
505 | >>> print response |
506 | HTTP/1.1 401 Unauthorized |
507 | ... |
508 | + |
509 | + |
510 | +Download counts |
511 | +=============== |
512 | + |
513 | +We can retrieve the total download count for a binary in this archive. |
514 | + |
515 | + >>> webservice.named_get( |
516 | + ... pubs['entries'][0]['self_link'], 'getDownloadCount').jsonBody() |
517 | + 0 |
518 | + |
519 | + >>> login("foo.bar@canonical.com") |
520 | + |
521 | + >>> from datetime import date |
522 | + >>> from lp.services.worlddata.interfaces.country import ICountrySet |
523 | + >>> australia = getUtility(ICountrySet)['AU'] |
524 | + |
525 | + >>> firefox_db = cprov_db.archive.getAllPublishedBinaries( |
526 | + ... name='mozilla-firefox')[0] |
527 | + >>> firefox_db.archive.updatePackageDownloadCount( |
528 | + ... firefox_db.binarypackagerelease, date(2010, 2, 21), australia, 10) |
529 | + >>> firefox_db.archive.updatePackageDownloadCount( |
530 | + ... firefox_db.binarypackagerelease, date(2010, 2, 23), None, 8) |
531 | + |
532 | + >>> logout() |
533 | + |
534 | + >>> firefox = webservice.named_get( |
535 | + ... cprov_archive['self_link'], 'getPublishedBinaries', |
536 | + ... binary_name='mozilla-firefox').jsonBody()['entries'][0] |
537 | + >>> webservice.named_get( |
538 | + ... firefox['self_link'], 'getDownloadCount').jsonBody() |
539 | + 18 |
540 | + |
541 | +Detailed download counts are also available from the getDownloadCounts method. |
542 | + |
543 | + >>> counts = webservice.named_get( |
544 | + ... firefox['self_link'], 'getDownloadCounts').jsonBody()['entries'] |
545 | + >>> len(counts) |
546 | + 2 |
547 | + |
548 | +A detailed count object can be retrieved by its URL. |
549 | + |
550 | + >>> pprint_entry(webservice.get(counts[1]['self_link']).jsonBody()) |
551 | + archive_link: u'http://.../~cprov/+archive/ppa' |
552 | + binary_package_name: u'mozilla-firefox' |
553 | + binary_package_version: u'1.0' |
554 | + count: 10 |
555 | + country_link: u'http://.../+countries/AU' |
556 | + day: u'2010-02-21' |
557 | + resource_type_link: u'http://.../#binary_package_release_download_count' |
558 | + self_link: u'http://.../~cprov/+archive/ppa/+binaryhits/mozilla-firefox/1.0/hppa/2010-02-21/AU' |
559 | + |
560 | +We can also filter by date. |
561 | + |
562 | + >>> counts = webservice.named_get( |
563 | + ... firefox['self_link'], 'getDownloadCounts', |
564 | + ... start_date='2010-02-22').jsonBody()['entries'] |
565 | + >>> len(counts) |
566 | + 1 |
567 | + |
568 | + >>> pprint_entry(webservice.get(counts[0]['self_link']).jsonBody()) |
569 | + archive_link: u'http://.../~cprov/+archive/ppa' |
570 | + binary_package_name: u'mozilla-firefox' |
571 | + binary_package_version: u'1.0' |
572 | + count: 8 |
573 | + country_link: None |
574 | + day: u'2010-02-23' |
575 | + resource_type_link: u'http://.../#binary_package_release_download_count' |
576 | + self_link: u'http://.../~cprov/+archive/ppa/+binaryhits/mozilla-firefox/1.0/hppa/2010-02-23/unknown' |
577 | + |
578 | +But other URLs result in a 404. |
579 | + |
580 | + >>> print webservice.get( |
581 | + ... '/~cprov/+archive/ppa/+binaryhits/moz') |
582 | + HTTP/1.1 404 Not Found |
583 | + ... |
584 | + |
585 | + >>> print webservice.get( |
586 | + ... '/~cprov/+archive/ppa/+binaryhits/phoenix/1.0/hppa/2010-02-23/unknown') |
587 | + HTTP/1.1 404 Not Found |
588 | + ... |
589 | + |
590 | + >>> print webservice.get( |
591 | + ... '/~cprov/+archive/ppa/+binaryhits/mozilla-firefox/1.1/hppa/2010-02-23/unknown') |
592 | + HTTP/1.1 404 Not Found |
593 | + ... |
594 | + |
595 | + >>> print webservice.get( |
596 | + ... '/~cprov/+archive/ppa/+binaryhits/mozilla-firefox/1.0/foo/2010-02-23/unknown') |
597 | + HTTP/1.1 404 Not Found |
598 | + ... |
599 | + |
600 | + >>> print webservice.get( |
601 | + ... '/~cprov/+archive/ppa/+binaryhits/mozilla-firefox/1.0/hppa/2010-02-25/unknown') |
602 | + HTTP/1.1 404 Not Found |
603 | + ... |
604 | + |
605 | + >>> print webservice.get( |
606 | + ... '/~cprov/+archive/ppa/+binaryhits/mozilla-firefox/1.0/hppa/2010-02-23/XX') |
607 | + HTTP/1.1 404 Not Found |
608 | + ... |
609 | + |
610 | +getDailyDownloadTotals returns a dict mapping dates to total counts. |
611 | + |
612 | + >>> webservice.named_get( |
613 | + ... firefox['self_link'], 'getDailyDownloadTotals').jsonBody() |
614 | + {u'2010-02-21': 10, u'2010-02-23': 8} |
615 | + |
616 | |
617 | === modified file 'lib/lp/soyuz/tests/test_archive.py' |
618 | --- lib/lp/soyuz/tests/test_archive.py 2010-03-16 08:27:03 +0000 |
619 | +++ lib/lp/soyuz/tests/test_archive.py 2010-03-22 07:42:21 +0000 |
620 | @@ -23,6 +23,7 @@ |
621 | from lp.soyuz.interfaces.archive import ( |
622 | IArchiveSet, ArchivePurpose, CannotSwitchPrivacy) |
623 | from lp.soyuz.interfaces.archivearch import IArchiveArchSet |
624 | +from lp.soyuz.interfaces.binarypackagename import IBinaryPackageNameSet |
625 | from lp.soyuz.interfaces.binarypackagerelease import BinaryPackageFormat |
626 | from lp.soyuz.interfaces.processor import IProcessorFamilySet |
627 | from lp.soyuz.interfaces.publishing import PackagePublishingStatus |
628 | @@ -760,6 +761,87 @@ |
629 | CannotSwitchPrivacy, self.make_ppa_public, self.private_ppa) |
630 | |
631 | |
632 | +class TestGetBinaryPackageRelease(TestCaseWithFactory): |
633 | + """Ensure that getBinaryPackageRelease works as expected.""" |
634 | + |
635 | + layer = LaunchpadZopelessLayer |
636 | + |
637 | + def setUp(self): |
638 | + """Setup an archive with relevant publications.""" |
639 | + super(TestGetBinaryPackageRelease, self).setUp() |
640 | + self.publisher = SoyuzTestPublisher() |
641 | + self.publisher.prepareBreezyAutotest() |
642 | + |
643 | + self.archive = self.factory.makeArchive() |
644 | + self.archive.require_virtualized = False |
645 | + |
646 | + self.i386_pub, self.hppa_pub = self.publisher.getPubBinaries( |
647 | + version="1.2.3-4", archive=self.archive, binaryname="foo-bin", |
648 | + status=PackagePublishingStatus.PUBLISHED, |
649 | + architecturespecific=True) |
650 | + |
651 | + self.i386_indep_pub, self.hppa_indep_pub = ( |
652 | + self.publisher.getPubBinaries( |
653 | + version="1.2.3-4", archive=self.archive, binaryname="bar-bin", |
654 | + status=PackagePublishingStatus.PUBLISHED)) |
655 | + |
656 | + self.bpns = getUtility(IBinaryPackageNameSet) |
657 | + |
658 | + def test_returns_matching_binarypackagerelease(self): |
659 | + # The BPR with a file by the given name should be returned. |
660 | + self.assertEqual( |
661 | + self.i386_pub.binarypackagerelease, |
662 | + self.archive.getBinaryPackageRelease( |
663 | + self.bpns['foo-bin'], '1.2.3-4', 'i386')) |
664 | + |
665 | + def test_returns_correct_architecture(self): |
666 | + # The architecture is taken into account correctly. |
667 | + self.assertEqual( |
668 | + self.hppa_pub.binarypackagerelease, |
669 | + self.archive.getBinaryPackageRelease( |
670 | + self.bpns['foo-bin'], '1.2.3-4', 'hppa')) |
671 | + |
672 | + def test_works_with_architecture_independent_binaries(self): |
673 | + # Architecture independent binaries with multiple publishings |
674 | + # are found properly. |
675 | + # We use 'i386' as the arch tag here, since what we have in the DB |
676 | + # is the *build* arch tag, not the one in the filename ('all'). |
677 | + self.assertEqual( |
678 | + self.i386_indep_pub.binarypackagerelease, |
679 | + self.archive.getBinaryPackageRelease( |
680 | + self.bpns['bar-bin'], '1.2.3-4', 'i386')) |
681 | + |
682 | + def test_returns_none_for_nonexistent_binary(self): |
683 | + # Non-existent files return None. |
684 | + self.assertIs( |
685 | + None, |
686 | + self.archive.getBinaryPackageRelease( |
687 | + self.bpns['cdrkit'], '1.2.3-4', 'i386')) |
688 | + |
689 | + def test_returns_none_for_duplicate_file(self): |
690 | + # In the unlikely case of multiple BPRs in this archive with the same |
691 | + # name (hopefully impossible, but it still happens occasionally due |
692 | + # to bugs), None is returned. |
693 | + |
694 | + # Publish the same binaries again. Evil. |
695 | + self.publisher.getPubBinaries( |
696 | + version="1.2.3-4", archive=self.archive, binaryname="foo-bin", |
697 | + status=PackagePublishingStatus.PUBLISHED, |
698 | + architecturespecific=True) |
699 | + |
700 | + self.assertIs( |
701 | + None, |
702 | + self.archive.getBinaryPackageRelease( |
703 | + self.bpns['foo-bin'], '1.2.3-4', 'i386')) |
704 | + |
705 | + def test_returns_none_from_another_archive(self): |
706 | + # Cross-archive searches are not performed. |
707 | + self.assertIs( |
708 | + None, |
709 | + self.factory.makeArchive().getBinaryPackageRelease( |
710 | + self.bpns['foo-bin'], '1.2.3-4', 'i386')) |
711 | + |
712 | + |
713 | class TestGetBinaryPackageReleaseByFileName(TestCaseWithFactory): |
714 | """Ensure that getBinaryPackageReleaseByFileName works as expected.""" |
715 |
r=me, great work!