Merge lp:~jelmer/launchpad/can-upload-webcall into lp:launchpad

Proposed by Jelmer Vernooij
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 10949
Proposed branch: lp:~jelmer/launchpad/can-upload-webcall
Merge into: lp:launchpad
Diff against target: 137 lines (+72/-1)
4 files modified
lib/canonical/launchpad/interfaces/_schema_circular_imports.py (+4/-0)
lib/lp/soyuz/interfaces/archive.py (+26/-0)
lib/lp/soyuz/model/archive.py (+14/-0)
lib/lp/soyuz/stories/webservice/xx-archive.txt (+28/-1)
To merge this branch: bzr merge lp:~jelmer/launchpad/can-upload-webcall
Reviewer Review Type Date Requested Status
Michael Nelson (community) Approve
Curtis Hovey (community) code and rc Approve
Review via email: mp+26141@code.launchpad.net

Commit message

Expose IArchive.checkUpload() over the web service API.

Description of the change

This exposes the IArchive.checkUpload() call over the web service API.

To post a comment you must log in.
Revision history for this message
Michael Nelson (michael.nelson) wrote :
Download full text (3.5 KiB)

Hi Jelmer,

As discussed, my main questions were regarding the return value... we should never be raising 500's... there are a bunch of descriptive webservice_error exceptions in interfaces/archive.py that you might be able to re-user (or add new ones).

I'd suggest returning True if they can upload (as returning nothing is kinda weird), and raising the a descriptive webservice error otherwise.

Regarding the wrapping of the method for the API, I can see why you've done so (rather than changing the signature of checkUpload to use strings and updating all the call-sites), but prefixing with an '_' seems strange to me... if there are other precedents for this fine, but otherwise, I'd expect something like checkUploadAPI() or similar to be (1) a bit more descriptive, and (2) not imply that it should be treated as protected? What do you think? Let me know if you find any other examples of methods being wrapped for API arguments.

-Michael

{{{
16:15 < noodles775> jelmer_: are there other examples of wrapping a method for api exposure that prefix with an _?
16:15 < noodles775> (in ref. to your checkUpload branch).
16:16 < sinzui> noodles775, I think that means you want to rename the method to be public now
16:16 < sinzui> noodles775, I will refrain from suggesting a uses the rename hack since exposing a method explicitly makes it public
16:17 -!- NCommander [~mcasadeva@ubuntu/member/ncommander] has joined #launchpad-reviews
16:17 < jelmer_> noodles775: Maybe, I haven't actually checked...
16:17 < jelmer_> noodles775: I'll have a look now.
16:18 < noodles775> sinzui: in this case it seems it's just to use string args instead of the normal IFaces... it seems strange to prefix with _ imo. (sinzui: https://code.edge.launchpad.net/~jelmer/launchpad/can-upload-webcall/+merge/26141 for reference :) ).
16:22 < noodles775> jelmer_: also, why are you not returning True/False? It also seems strange to not return *anything* to mean yes, and raise an exception otherwise?
16:24 < jelmer_> noodles775: Originally I had it return the exception string and None when uploading was allowed.
16:25 < jelmer_> noodles775: I guess it really depends on whether we would want to expose the reason why the upload would not be possible to the user
16:25 < jelmer_> noodles775: there are different exception classes so in theory you could have different behaviour based on that.
16:27 < noodles775> jelmer_: we should never be expecting a 500, afaik (if you do raise an exception, it needs to use webservice_error(xxx) as in the other examples in interfaces/archive.py)
16:29 < jelmer_> noodles775: ok
16:29 < noodles775> jelmer_: in fact, there's already an InsufficientUploadRights exception defined there - it might be what you're after.
16:30 < noodles775> jelmer_: but I'm still unsure why you're not returning True/False.
16:30 * noodles775 checks for other examples.
16:30 < jelmer_> noodles775: With True/False the user doesn't get an idea why the upload wouldn't be allowed (user not in packageset, pocket is release and distroseries is closed, etc)
16:31 < jelmer_> noodles775: The feature request doesn't require that though, so I'm happy to change it to a boolean.
16:32 < noodles775...

Read more...

review: Needs Fixing (code)
Revision history for this message
Jelmer Vernooij (jelmer) wrote :

The only two other examples I can find are Bug.linkCVE (API call is named Bug.linkCVEAndReturnNothing) and Branch._createMergeProposal() (which is the web API for Branch.createMergeProposal and consistent with Archive.checkUpload).

Revision history for this message
Curtis Hovey (sinzui) wrote :

Thanks for asking me to review this before the deadline. Thanks to for explaining the naming situation for me. I think this branch is fine to land.

review: Approve (code and rc)
Revision history for this message
Michael Nelson (michael.nelson) wrote :

Thanks for finding the precedent Jelmer, and for updating the exception to use webservice_error.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-05-27 17:20:07 +0000
+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-06-04 15:03:25 +0000
@@ -267,6 +267,10 @@
267patch_plain_parameter_type(267patch_plain_parameter_type(
268 IArchive, 'isSourceUploadAllowed', 'distroseries', IDistroSeries)268 IArchive, 'isSourceUploadAllowed', 'distroseries', IDistroSeries)
269patch_plain_parameter_type(269patch_plain_parameter_type(
270 IArchive, '_checkUpload', 'distroseries', IDistroSeries)
271patch_choice_parameter_type(
272 IArchive, '_checkUpload', 'pocket', PackagePublishingPocket)
273patch_plain_parameter_type(
270 IArchive, 'newPackagesetUploader', 'packageset', IPackageset)274 IArchive, 'newPackagesetUploader', 'packageset', IPackageset)
271patch_plain_parameter_type(275patch_plain_parameter_type(
272 IArchive, 'getUploadersForPackageset', 'packageset', IPackageset)276 IArchive, 'getUploadersForPackageset', 'packageset', IPackageset)
273277
=== modified file 'lib/lp/soyuz/interfaces/archive.py'
--- lib/lp/soyuz/interfaces/archive.py 2010-05-19 10:28:25 +0000
+++ lib/lp/soyuz/interfaces/archive.py 2010-06-04 15:03:25 +0000
@@ -143,6 +143,7 @@
143143
144class CannotUploadToArchive(Exception):144class CannotUploadToArchive(Exception):
145 """A reason for not being able to upload to an archive."""145 """A reason for not being able to upload to an archive."""
146 webservice_error(403) # Forbidden.
146147
147 _fmt = '%(person)s has no upload rights to %(archive)s.'148 _fmt = '%(person)s has no upload rights to %(archive)s.'
148149
@@ -492,6 +493,31 @@
492 :return: Reason why uploading is not possible or None493 :return: Reason why uploading is not possible or None
493 """494 """
494495
496 @operation_parameters(
497 person=Reference(schema=IPerson),
498 distroseries=Reference(
499 # Really IDistroSeries, avoiding a circular import here.
500 Interface,
501 title=_("The distro series"), required=True),
502 sourcepackagename=TextLine(
503 title=_("Source package name"), required=True),
504 component=TextLine(
505 title=_("Component"), required=True),
506 pocket=Choice(
507 title=_("Pocket"),
508 description=_("The pocket into which this entry is published"),
509 # Really PackagePublishingPocket, circular import fixed below.
510 vocabulary=DBEnumeratedType,
511 required=True),
512 strict_component=Bool(
513 title=_("Strict component"), required=False)
514 )
515 @export_operation_as("checkUpload")
516 @export_read_operation()
517 def _checkUpload(person, distroseries, sourcepackagename, component,
518 pocket, strict_component=True):
519 """Wrapper around checkUpload for the web service API."""
520
495 def checkUpload(person, distroseries, sourcepackagename, component, 521 def checkUpload(person, distroseries, sourcepackagename, component,
496 pocket, strict_component=True):522 pocket, strict_component=True):
497 """Check if 'person' upload 'suitesourcepackage' to 'archive'.523 """Check if 'person' upload 'suitesourcepackage' to 'archive'.
498524
=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py 2010-05-26 08:54:27 +0000
+++ lib/lp/soyuz/model/archive.py 2010-06-04 15:03:25 +0000
@@ -1019,6 +1019,20 @@
1019 if not distroseries.canUploadToPocket(pocket):1019 if not distroseries.canUploadToPocket(pocket):
1020 return CannotUploadToPocket(distroseries, pocket)1020 return CannotUploadToPocket(distroseries, pocket)
10211021
1022 def _checkUpload(self, person, distroseries, sourcepackagename, component,
1023 pocket, strict_component=True):
1024 """See `IArchive`."""
1025 if isinstance(component, basestring):
1026 component = getUtility(IComponentSet)[component]
1027 if isinstance(sourcepackagename, basestring):
1028 sourcepackagename = getUtility(
1029 ISourcePackageNameSet)[sourcepackagename]
1030 reason = self.checkUpload(person, distroseries, sourcepackagename,
1031 component, pocket, strict_component)
1032 if reason is not None:
1033 raise reason
1034 return True
1035
1022 def checkUpload(self, person, distroseries, sourcepackagename, component,1036 def checkUpload(self, person, distroseries, sourcepackagename, component,
1023 pocket, strict_component=True):1037 pocket, strict_component=True):
1024 """See `IArchive`."""1038 """See `IArchive`."""
10251039
=== modified file 'lib/lp/soyuz/stories/webservice/xx-archive.txt'
--- lib/lp/soyuz/stories/webservice/xx-archive.txt 2010-03-17 15:57:17 +0000
+++ lib/lp/soyuz/stories/webservice/xx-archive.txt 2010-06-04 15:03:25 +0000
@@ -335,6 +335,19 @@
335 Archive Upload Rights ...~ubuntu-team restricted None335 Archive Upload Rights ...~ubuntu-team restricted None
336 Archive Upload Rights ...~ubuntu-team universe None336 Archive Upload Rights ...~ubuntu-team universe None
337337
338We can use ``checkUpload`` to verify that a person can upload a
339sourcepackage.
340
341 >>> grumpy = user_webservice.get("/ubuntu/grumpy").jsonBody()
342 >>> response = user_webservice.named_get(
343 ... ubuntu['main_archive_link'], 'checkUpload',
344 ... distroseries=grumpy['self_link'],
345 ... sourcepackagename='mozilla-firefox', pocket='Release',
346 ... component='restricted', person=name12['self_link'])
347 >>> print(response)
348 HTTP/1.1 200 Ok
349 ...
350
338deleteComponentUploader() removes that permission:351deleteComponentUploader() removes that permission:
339352
340 >>> print cjwatson_webservice.named_post(353 >>> print cjwatson_webservice.named_post(
@@ -351,6 +364,21 @@
351 Archive Upload Rights ...~ubuntu-team restricted None364 Archive Upload Rights ...~ubuntu-team restricted None
352 Archive Upload Rights ...~ubuntu-team universe None365 Archive Upload Rights ...~ubuntu-team universe None
353366
367And ``checkUpload`` now also no longer passes:
368
369 >>> grumpy = user_webservice.get("/ubuntu/grumpy").jsonBody()
370 >>> response = user_webservice.named_get(
371 ... ubuntu['main_archive_link'], 'checkUpload',
372 ... distroseries=grumpy['self_link'],
373 ... sourcepackagename='mozilla-firefox', pocket='Release',
374 ... component='main', person=name12['self_link'])
375 >>> print(response)
376 HTTP/1.1 403 Forbidden
377 ...
378 InsufficientUploadRights: The signer of this package is lacking the upload rights for the source package, component or package set in question.
379
380
381
354Only the archive owners can add or remove component-uploaders.382Only the archive owners can add or remove component-uploaders.
355383
356 >>> no_priv = webservice.get("/~no-priv").jsonBody()384 >>> no_priv = webservice.get("/~no-priv").jsonBody()
@@ -457,7 +485,6 @@
457 Queue Administration Rights ...~name12 restricted None485 Queue Administration Rights ...~name12 restricted None
458 Queue Administration Rights ...~name12 universe None486 Queue Administration Rights ...~name12 universe None
459487
460
461== Malformed archive permission URLs ==488== Malformed archive permission URLs ==
462489
463Malformed URLs are handled reasonably well.490Malformed URLs are handled reasonably well.