Merge lp:~gary/launchpad/lazr-dist into lp:launchpad

Proposed by Gary Poster
Status: Merged
Approved by: Edwin Grubbs
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~gary/launchpad/lazr-dist
Merge into: lp:launchpad
Diff against target: None lines
To merge this branch: bzr merge lp:~gary/launchpad/lazr-dist
Reviewer Review Type Date Requested Status
Edwin Grubbs (community) code Approve
Review via email: mp+10770@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Gary Poster (gary) wrote :

This branch converts us to using the newest versions of our own packages, via Python disributions:

launchpadlib
lazr.batchnavigator
lazr.config
lazr.delegates
lazr.enum
lazr.lifecycle
lazr.restful
lazr.restfulclient
wadllib

Many of the lines in the diff are because the underlying machinery of the lazr.restful testing helpers changed. It moved from relying on a zope functional testing framework to wsgi_intercept. This meant that I had to add some new testing helpers in the launchpad layer machinery (see lib/canonical/testing/layers.py); and the API of the webservice test helper (and its results) changed, so tests changed.

As an interesting side result, it's quite possible that we will be able to use launchpadlib within our functional tests now, or with a two line change to hook up wsgi_intercept in a different way.

An aspect of this worth particular note is that the webservice needs to have its domain set explicitly if you change domains. You can see this in the xx-service.txt diff, for instance.

I increased the timeout in lib/canonical/launchpad/testing/googletestservice.py because it helped me on my machine. I'm willing to change it back, but it seems pretty harmless to me.

In lib/canonical/launchpad/testing/pages.py, I remove "port" from calling the LaunchpadWebServiceCaller. It was ignored before, and I didn't see any reason to add it back. Perhaps the comment that explains its absence is unnecessary, though.

The MockRootFolder is added back to lib/canonical/lazr/testing/layers.py because it is no longer found in lazr.restful.

The STAGGER_RETRIES code in lib/canonical/testing/layers.py was something I removed in my earlier zbuildout branch but should be added back. We can't remove it until we move to a newer version of zope.publisher, which we can't do yet because of other issues I won't get into right now.

Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :
Download full text (47.9 KiB)

Hi Gary,

This branch looks good. I only have suggestions for minor changes and
fixing the lint items that are possible.

merge-conditional

-Edwin

{{{
= Launchpad lint =

== Pyflakes notices ==

lib/canonical/launchpad/testing/pages.py
    28: 'urlparse' imported but unused

lib/canonical/testing/layers.py
    75: 'HTTPPublication' imported but unused
    Traceback (most recent call last):
    File "/usr/bin/pyflakes", line 5, in <module>
    sys.exit(main(sys.argv[1:]))
    File "/usr/lib/python2.6/dist-packages/pyflakes/scripts/pyflakes.py", line 57, in main
    warnings += checkPath(arg)

TypeError
    unsupported operand type(s) for +=: 'int' and 'NoneType'

== Pylint notices ==

lib/lazr/__init__.py
    1: [F0001] No module named lib/lazr/__init__

lib/canonical/launchpad/testing/pages.py
    621: [C0301] Line too long (79/78)
    42: [F0401] Unable to import 'lazr.restful.testing.webservice' (No module named restful)
    55: [E1002, UnstickyCookieHTTPCaller.__init__] Use super on an old style class
    63: [E1002, UnstickyCookieHTTPCaller.__call__] Use super on an old style class
    71: [E1002, UnstickyCookieHTTPCaller.chooseRequestClass] Use super on an old style class
    28: [W0611] Unused import urlparse

lib/canonical/testing/layers.py
    75: [W0611] Unused import HTTPPublication
}}}

>=== modified file 'lib/canonical/launchpad/pagetests/webservice/root.txt'
>--- lib/canonical/launchpad/pagetests/webservice/root.txt 2009-03-18 17:20:01 +0000
>+++ lib/canonical/launchpad/pagetests/webservice/root.txt 2009-08-26 22:51:00 +0000
>@@ -5,9 +5,8 @@
> confusion when one program runs as different users, this is
> implemented as a redirect to that person's canonical URL.
>
>- >>> print webservice.get('/people/+me').getOutput()
>+ >>> print webservice.get('/people/+me')
> HTTP/1.1 303 See Other
> ...
> Location: http://.../~salgado
> ...
>-
>
>=== modified file 'lib/canonical/launchpad/pagetests/webservice/xx-hwdb.txt'
>--- lib/canonical/launchpad/pagetests/webservice/xx-hwdb.txt 2009-08-14 13:03:36 +0000
>+++ lib/canonical/launchpad/pagetests/webservice/xx-hwdb.txt 2009-08-26 22:51:00 +0000
>@@ -537,7 +537,7 @@
> Accessing the raw submission data yields a redirect to a Librarian URL.
>
> >>> print webservice.get(submission['raw_submission_link'])
>- HTTP/1.1 303 See Other
>+ HTTP/1.1 303 See Other...
> Content-Length: 0
> Content-Type: text/plain
> Location: http://localhost:58000/92/sample-submission-2.xml
>@@ -769,7 +769,7 @@
>
> A vendor ID can be accessed by via its database ID.
>
>- >>> vendor_id = webservice.get('+hwdb/+hwvendorid/1').jsonBody()
>+ >>> vendor_id = webservice.get('/+hwdb/+hwvendorid/1').jsonBody()
> >>> pprint_entry(vendor_id)
> bus: u'System'
> id: 1
>
>=== modified file 'lib/canonical/launchpad/pagetests/webservice/xx-person.txt'
>--- lib/canonical/launchpad/pagetests/webservice/xx-person.txt 2009-08-13 15:12:16 +0000
>+++ lib/canonical/launchpad/pagetests/webservice/xx-person.txt 2009-08-26 22:51:00 +0000
>@@ -387,7 +387,7 @@
> ... u'wikiname': 'MrExample'}
> >>> response = webservice.patch(
> ... wiki_name['self_link'], 'applicat...

review: Approve (code)
Revision history for this message
Gary Poster (gary) wrote :
Download full text (11.3 KiB)

On Aug 27, 2009, at 12:19 AM, Edwin Grubbs wrote:

> Review: Approve code
> Hi Gary,
>
> This branch looks good. I only have suggestions for minor changes and
> fixing the lint items that are possible.

Thank you Edwin!

I fixed all the lint issues except for pylint not findling
lazr.restful. I glanced at that; looks like somebody has already
tried to improve this, but its not quite there yet, and I could not
make it better with a quick fly-by.

I'm only responding to the questions in-line, and then putting the
incremental diff at the end.

>> def addHeadersTo(self, full_url, full_headers):
>> if (self.consumer is not None and self.access_token is not
>> None):
>> @@ -123,6 +120,8 @@
>> request.sign_request(OAuthSignatureMethod_PLAINTEXT(),
>> self.consumer, self.access_token)
>> full_headers.update(request.to_header(OAUTH_REALM))
>> + if not self.handle_errors:
>> + full_headers['X_Zope_handle_errors'] = 'False'
>
>
>
> Is this why the webservice object in tests no longer raises an
> exception?

No, that's because we were relying on jsonBody raising an exception,
and it still does, but now it is a "this isn't json!" exception rather
than a useful report of the response status and the server's traceback.

> def extract_url_parameter(url, parameter):
>> @@ -619,7 +618,7 @@
>> access_token = request_token.createAccessToken()
>> logout()
>> return LaunchpadWebServiceCaller(
>> - consumer_key, access_token.key, port=9000)
>> + consumer_key, access_token.key) # "port=9000" was ignored,
>> now not sent
>
>
> I don't think this comment is necessary. If it needs to stay, it's
> too long, needs a period, and I've been dinged for using eol comments,
> although I don't see anything in the style guide about that.

:-) Cool. Removed.

...

>>
>> === modified file 'lib/canonical/testing/layers.py'
>> --- lib/canonical/testing/layers.py 2009-08-21 19:38:21 +0000
>> +++ lib/canonical/testing/layers.py 2009-08-26 22:51:00 +0000
>> @@ -69,7 +69,10 @@
>> import psycopg2
>> from storm.zope.interfaces import IZStorm
>> import transaction
>> +import wsgi_intercept
>>
>> +from zope.app.publication.httpfactory import chooseClasses
>> +from zope.app.publication.http import HTTPPublication
>> import zope.app.testing.functional
>> from zope.app.testing.functional import FunctionalTestSetup,
>> ZopePublication
>> from zope.component import getUtility, provideUtility
>> @@ -752,6 +755,47 @@
>> "DELETE FROM SessionData")
>>
>>
>> +def wsgi_application(environ, start_response):
>> + """This is a wsgi application for Zope functional testing.
>> +
>> + We use it with wsgi_intercept, which is itself mostly
>> interesting
>> + for our webservice (lazr.restful) tests.
>> + """
>> + # Committing work done up to now is a convenience that the Zope
>> + # zope.app.testing.functional.HTTPCaller does. We're
>> replacing that bit,
>> + # so it is easiest to follow that lead, even if it feels a
>> little loose.
>> + transaction.commit()
>> + # Let's support post-mortem debugging.
>> + if environ.get('HTTP_X_ZOPE_...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/pagetests/webservice/root.txt'
2--- lib/canonical/launchpad/pagetests/webservice/root.txt 2009-03-18 17:20:01 +0000
3+++ lib/canonical/launchpad/pagetests/webservice/root.txt 2009-08-20 04:46:48 +0000
4@@ -5,9 +5,8 @@
5 confusion when one program runs as different users, this is
6 implemented as a redirect to that person's canonical URL.
7
8- >>> print webservice.get('/people/+me').getOutput()
9+ >>> print webservice.get('/people/+me')
10 HTTP/1.1 303 See Other
11 ...
12 Location: http://.../~salgado
13 ...
14-
15
16=== modified file 'lib/canonical/launchpad/pagetests/webservice/xx-hwdb.txt'
17--- lib/canonical/launchpad/pagetests/webservice/xx-hwdb.txt 2009-08-14 13:03:36 +0000
18+++ lib/canonical/launchpad/pagetests/webservice/xx-hwdb.txt 2009-08-21 19:49:18 +0000
19@@ -537,7 +537,7 @@
20 Accessing the raw submission data yields a redirect to a Librarian URL.
21
22 >>> print webservice.get(submission['raw_submission_link'])
23- HTTP/1.1 303 See Other
24+ HTTP/1.1 303 See Other...
25 Content-Length: 0
26 Content-Type: text/plain
27 Location: http://localhost:58000/92/sample-submission-2.xml
28@@ -769,7 +769,7 @@
29
30 A vendor ID can be accessed by via its database ID.
31
32- >>> vendor_id = webservice.get('+hwdb/+hwvendorid/1').jsonBody()
33+ >>> vendor_id = webservice.get('/+hwdb/+hwvendorid/1').jsonBody()
34 >>> pprint_entry(vendor_id)
35 bus: u'System'
36 id: 1
37
38=== modified file 'lib/canonical/launchpad/pagetests/webservice/xx-person.txt'
39--- lib/canonical/launchpad/pagetests/webservice/xx-person.txt 2009-08-13 15:12:16 +0000
40+++ lib/canonical/launchpad/pagetests/webservice/xx-person.txt 2009-08-21 19:49:18 +0000
41@@ -387,7 +387,7 @@
42 ... u'wikiname': 'MrExample'}
43 >>> response = webservice.patch(
44 ... wiki_name['self_link'], 'application/json', dumps(patch))
45- >>> print response.getOutput()
46+ >>> print response
47 HTTP/1.1 400 Bad Request
48 ...
49 wiki: The URI scheme "javascript" is not allowed.
50
51=== modified file 'lib/canonical/launchpad/pagetests/webservice/xx-private-membership.txt'
52--- lib/canonical/launchpad/pagetests/webservice/xx-private-membership.txt 2009-07-15 09:42:45 +0000
53+++ lib/canonical/launchpad/pagetests/webservice/xx-private-membership.txt 2009-08-20 04:46:48 +0000
54@@ -101,7 +101,7 @@
55 ... simplejson.dumps(representation),
56 ... headers)
57
58- >>> print modify_team('~my-new-team', {'visibility' : 'Private Membership'},
59+ >>> print modify_team('/~my-new-team', {'visibility' : 'Private Membership'},
60 ... 'PATCH', comm_webservice)
61 HTTP/1.1 209 Content Returned
62 ...
63@@ -116,7 +116,7 @@
64
65 As an admin, Salgado can also change the team's visibility.
66
67- >>> print modify_team('~my-new-team', {'visibility' : 'Public'},
68+ >>> print modify_team('/~my-new-team', {'visibility' : 'Public'},
69 ... 'PATCH', webservice)
70 HTTP/1.1 209 Content Returned
71 ...
72@@ -131,7 +131,7 @@
73
74 An unprivileged user is not able to change the visibility.
75
76- >>> print modify_team('~my-new-team', {'visibility' : 'Private Membership'},
77+ >>> print modify_team('/~my-new-team', {'visibility' : 'Private Membership'},
78 ... 'PATCH', user_webservice)
79 HTTP/1.1 401 Unauthorized
80 ...
81
82=== modified file 'lib/canonical/launchpad/pagetests/webservice/xx-project-registry.txt'
83--- lib/canonical/launchpad/pagetests/webservice/xx-project-registry.txt 2009-08-13 15:12:16 +0000
84+++ lib/canonical/launchpad/pagetests/webservice/xx-project-registry.txt 2009-08-21 19:49:18 +0000
85@@ -173,10 +173,8 @@
86 name. As a convenience, requests for projects using the wrong case
87 are redirected to the correct location.
88
89- >>> firefox = webservice.get('/FireFox').jsonBody()
90- Traceback (most recent call last):
91- ...
92- ValueError: HTTP/1.1 301 Moved Permanently
93+ >>> print webservice.get('/FireFox')
94+ HTTP/1.1 301 Moved Permanently
95 ...
96 Location: http://api.launchpad.dev/beta/firefox
97 ...
98@@ -578,15 +576,17 @@
99 Mega Money Maker
100
101 The use of "licensing_search" is restricted to commercial admins.
102-Attempting to access it as a normal users results in an exception.
103+Attempting to access it as a normal users is unauthorized.
104
105- >>> proprietary = user_webservice.named_get(
106+ >>> print user_webservice.named_get(
107 ... "/projects", "licensing_search",
108- ... licenses=["Other/Proprietary"]).jsonBody()
109+ ... licenses=["Other/Proprietary"])
110+ HTTP/1.1 401 Unauthorized
111+ ...
112 Traceback (most recent call last):
113 ...
114- ValueError: HTTP/1.1 401 Unauthorized
115- ...
116+ Unauthorized: (...)
117+ <BLANKLINE>
118
119 The project collection has a method for creating a new project.
120
121@@ -996,14 +996,14 @@
122 >>> url = '/firefox/trunk/0.9.2/+file/firefox-0.9.2.orig.tar.gz/file'
123 >>> response = webservice.put(url, 'application/x-tar-gz', 'fakefiledata')
124 >>> print response
125- HTTP/1.1 405 Method Not Allowed
126+ HTTP/1.1 405 Method Not Allowed...
127 Allow: GET
128 ...
129
130 >>> url = '/firefox/trunk/0.9.2/+file/firefox-0.9.2.orig.tar.gz/signature'
131 >>> response = webservice.put(url, 'pgpapplication/data', 'signaturedata')
132 >>> print response
133- HTTP/1.1 405 Method Not Allowed
134+ HTTP/1.1 405 Method Not Allowed...
135 Allow: GET
136 ...
137
138
139=== modified file 'lib/canonical/launchpad/pagetests/webservice/xx-service.txt'
140--- lib/canonical/launchpad/pagetests/webservice/xx-service.txt 2009-07-16 22:15:11 +0000
141+++ lib/canonical/launchpad/pagetests/webservice/xx-service.txt 2009-08-20 04:46:48 +0000
142@@ -77,6 +77,7 @@
143 request to http://api.launchpad.net/beta/, but with the links pointing
144 to a different host.
145
146+ >>> webservice.domain = 'bugs.launchpad.dev'
147 >>> root = webservice.get(
148 ... 'http://bugs.launchpad.dev/api/beta/').jsonBody()
149 >>> print root['people_collection_link']
150@@ -87,7 +88,8 @@
151
152 >>> from canonical.launchpad.testing.pages import (
153 ... LaunchpadWebServiceCaller)
154- >>> noauth_webservice = LaunchpadWebServiceCaller()
155+ >>> noauth_webservice = LaunchpadWebServiceCaller(
156+ ... domain='bugs.launchpad.dev')
157 >>> sample_auth = 'Basic %s' % 'test@canonical.com:test'.encode('base64')
158 >>> print noauth_webservice.get(
159 ... 'http://bugs.launchpad.dev/api/beta/people/+me',
160@@ -100,9 +102,9 @@
161 But the regular authentication still doesn't work on the normal API
162 virtual host:
163
164+ >>> noauth_webservice.domain = 'api.launchpad.dev'
165 >>> print noauth_webservice.get(
166 ... 'http://api.launchpad.dev/beta/people/+me',
167 ... headers={'Authorization': sample_auth})
168 HTTP/1.1 401 Unauthorized
169 ...
170-
171
172=== modified file 'lib/canonical/launchpad/pagetests/webservice/xx-wadl.txt'
173--- lib/canonical/launchpad/pagetests/webservice/xx-wadl.txt 2009-06-12 16:36:02 +0000
174+++ lib/canonical/launchpad/pagetests/webservice/xx-wadl.txt 2009-08-20 04:46:48 +0000
175@@ -10,7 +10,7 @@
176 canonical/launchpad/apidoc/wadl-development.xml.
177
178 >>> test_wadl = webservice.get(
179- ... '/', 'application/vd.sun.wadl+xml').getBody()
180+ ... '/', 'application/vd.sun.wadl+xml').body
181 >>> print test_wadl
182 <?xml version="1.0"?>
183 <wadl:application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
184@@ -32,7 +32,7 @@
185 >>> WebServiceApplication.cached_wadl = None
186
187 >>> wadl = webservice.get(
188- ... '/', 'application/vd.sun.wadl+xml').getBody()
189+ ... '/', 'application/vd.sun.wadl+xml').body
190 >>> wadl = wadl.decode('UTF-8')
191
192 The real file is much bigger than the test file.
193
194=== modified file 'lib/canonical/launchpad/testing/googletestservice.py'
195--- lib/canonical/launchpad/testing/googletestservice.py 2009-06-25 05:30:52 +0000
196+++ lib/canonical/launchpad/testing/googletestservice.py 2009-08-25 21:39:10 +0000
197@@ -108,7 +108,7 @@
198 sock.close() # Clean up.
199
200
201-def wait_for_service(timeout=10.0):
202+def wait_for_service(timeout=15.0):
203 """Poll the service and BLOCK until we can connect to it.
204
205 :param timeout: The socket should timeout after this many seconds.
206
207=== modified file 'lib/canonical/launchpad/testing/pages.py'
208--- lib/canonical/launchpad/testing/pages.py 2009-08-20 06:53:22 +0000
209+++ lib/canonical/launchpad/testing/pages.py 2009-08-21 19:49:18 +0000
210@@ -25,7 +25,7 @@
211 BeautifulSoup, Comment, Declaration, NavigableString, PageElement,
212 ProcessingInstruction, SoupStrainer, Tag)
213 from contrib.oauth import OAuthRequest, OAuthSignatureMethod_PLAINTEXT
214-from urlparse import urljoin
215+from urlparse import urljoin, urlparse
216
217 from zope.app.testing.functional import HTTPCaller, SimpleCookie
218 from zope.component import getUtility
219@@ -87,10 +87,9 @@
220 class LaunchpadWebServiceCaller(WebServiceCaller):
221 """A class for making calls to Launchpad web services."""
222
223- base_url = 'http://api.launchpad.dev'
224-
225 def __init__(self, oauth_consumer_key=None, oauth_access_key=None,
226- handle_errors=True, *args, **kwargs):
227+ handle_errors=True, domain='api.launchpad.dev',
228+ protocol='http'):
229 """Create a LaunchpadWebServiceCaller.
230 :param oauth_consumer_key: The OAuth consumer key to use.
231 :param oauth_access_key: The OAuth access key to use for the request.
232@@ -111,9 +110,7 @@
233 self.access_token = None
234
235 self.handle_errors = handle_errors
236-
237- # Set up a delegate to make the actual HTTP calls.
238- self.http_caller = UnstickyCookieHTTPCaller(*args, **kwargs)
239+ WebServiceCaller.__init__(self, handle_errors, domain, protocol)
240
241 def addHeadersTo(self, full_url, full_headers):
242 if (self.consumer is not None and self.access_token is not None):
243@@ -123,6 +120,8 @@
244 request.sign_request(OAuthSignatureMethod_PLAINTEXT(),
245 self.consumer, self.access_token)
246 full_headers.update(request.to_header(OAUTH_REALM))
247+ if not self.handle_errors:
248+ full_headers['X_Zope_handle_errors'] = 'False'
249
250
251 def extract_url_parameter(url, parameter):
252@@ -619,7 +618,7 @@
253 access_token = request_token.createAccessToken()
254 logout()
255 return LaunchpadWebServiceCaller(
256- consumer_key, access_token.key, port=9000)
257+ consumer_key, access_token.key) # "port=9000" was ignored, now not sent
258
259
260 def stop():
261
262=== modified file 'lib/canonical/lazr/testing/layers.py'
263--- lib/canonical/lazr/testing/layers.py 2009-06-25 05:30:52 +0000
264+++ lib/canonical/lazr/testing/layers.py 2009-08-17 19:16:23 +0000
265@@ -1,9 +1,20 @@
266 # Copyright 2009 Canonical Ltd. This software is licensed under the
267 # GNU Affero General Public License version 3 (see the file LICENSE).
268-
269-# Re-import code from lazr.restful until it can be refactored into a
270-# utility module.
271-__all__ = []
272-import lazr.restful.testing.layers
273-__all__.extend(lazr.restful.testing.layers.__all__)
274-from lazr.restful.testing.layers import *
275+"""Miscellaneous testing helpers."""
276+
277+__metaclass__ = type
278+__all__ = [
279+ 'MockRootFolder',
280+]
281+
282+class MockRootFolder:
283+ """Implement the minimum functionality required by Z3 ZODB dependencies
284+
285+ Installed as part of FunctionalLayer.testSetUp() to allow the http()
286+ method (zope.app.testing.functional.HTTPCaller) to work.
287+ """
288+ @property
289+ def _p_jar(self):
290+ return self
291+ def sync(self):
292+ pass
293
294=== modified file 'lib/canonical/testing/layers.py'
295--- lib/canonical/testing/layers.py 2009-08-21 19:38:21 +0000
296+++ lib/canonical/testing/layers.py 2009-08-25 19:09:14 +0000
297@@ -69,7 +69,10 @@
298 import psycopg2
299 from storm.zope.interfaces import IZStorm
300 import transaction
301+import wsgi_intercept
302
303+from zope.app.publication.httpfactory import chooseClasses
304+from zope.app.publication.http import HTTPPublication
305 import zope.app.testing.functional
306 from zope.app.testing.functional import FunctionalTestSetup, ZopePublication
307 from zope.component import getUtility, provideUtility
308@@ -752,6 +755,47 @@
309 "DELETE FROM SessionData")
310
311
312+def wsgi_application(environ, start_response):
313+ """This is a wsgi application for Zope functional testing.
314+
315+ We use it with wsgi_intercept, which is itself mostly interesting
316+ for our webservice (lazr.restful) tests.
317+ """
318+ # Committing work done up to now is a convenience that the Zope
319+ # zope.app.testing.functional.HTTPCaller does. We're replacing that bit,
320+ # so it is easiest to follow that lead, even if it feels a little loose.
321+ transaction.commit()
322+ # Let's support post-mortem debugging.
323+ if environ.get('HTTP_X_ZOPE_HANDLE_ERRORS') == 'False':
324+ environ['wsgi.handleErrors'] = False
325+ if 'HTTP_X_ZOPE_HANDLE_ERRORS' in environ:
326+ del environ['HTTP_X_ZOPE_HANDLE_ERRORS']
327+ handle_errors = environ.get('wsgi.handleErrors', True)
328+ # Now we do the proper dance to get the desired request. This is an
329+ # almalgam of code from zope.app.testing.functional.HTTPCaller and
330+ # zope.publisher.paste.Application.
331+ request_cls, publication_cls = chooseClasses(
332+ environ['REQUEST_METHOD'], environ)
333+ publication = publication_cls(FunctionalTestSetup().db)
334+ request = request_cls(environ['wsgi.input'], environ)
335+ request.setPublication(publication)
336+ # The rest of this function is an amalgam of
337+ # zope.publisher.paste.Application.__call__ and van.testing.layers.
338+ request = zope.publisher.publish.publish(
339+ request, handle_errors=handle_errors)
340+ response = request.response
341+ # We sort these, and then put the status first, because
342+ # zope.testbrowser.testing does--and because it makes it easier to write
343+ # reliable tests.
344+ headers = sorted(response.getHeaders())
345+ status = response.getStatusString()
346+ headers.insert(0, ('Status', status))
347+ # Start the WSGI server response.
348+ start_response(status, headers)
349+ # Return the result body iterable.
350+ return response.consumeBodyIter()
351+
352+
353 class FunctionalLayer(BaseLayer):
354 """Loads the Zope3 component architecture in appserver mode."""
355
356@@ -773,11 +817,14 @@
357 # they're defined by Python code, we need to call that code
358 # here.
359 register_launchpad_request_publication_factories()
360+ wsgi_intercept.add_wsgi_intercept(
361+ 'localhost', 80, lambda: wsgi_application)
362
363 @classmethod
364 @profiled
365 def tearDown(cls):
366 FunctionalLayer.isSetUp = False
367+ wsgi_intercept.remove_wsgi_intercept('localhost', 80)
368 # Signal Layer cannot be torn down fully
369 raise NotImplementedError
370
371@@ -1279,6 +1326,11 @@
372 access_logger.log(MockHTTPTask(response._response, first_line))
373 return response
374
375+ # Setting STAGGER_RETRIES makes tests like notfound-traversals.txt go
376+ # much, much faster by avoiding calls to time.sleep()
377+ cls._original_stagger_retries = zope.publisher.http.STAGGER_RETRIES
378+ zope.publisher.http.STAGGER_RETRIES = False
379+
380 PageTestLayer.orig__call__ = (
381 zope.app.testing.functional.HTTPCaller.__call__)
382 zope.app.testing.functional.HTTPCaller.__call__ = my__call__
383@@ -1290,6 +1342,7 @@
384 PageTestLayer.resetBetweenTests(True)
385 zope.app.testing.functional.HTTPCaller.__call__ = (
386 PageTestLayer.orig__call__)
387+ zope.publisher.http.STAGGER_RETRIES = cls._original_stagger_retries
388 if PageTestLayer.profiler:
389 PageTestLayer.profiler.dump_stats(
390 os.environ.get('PROFILE_PAGETESTS_REQUESTS'))
391
392=== removed directory 'lib/lazr'
393=== removed file 'lib/lazr/__init__.py'
394--- lib/lazr/__init__.py 2009-06-02 08:20:49 +0000
395+++ lib/lazr/__init__.py 1970-01-01 00:00:00 +0000
396@@ -1,10 +0,0 @@
397-# This is a namespace package for lazr.* packages. We should absolutely get rid
398-# of this once we are using eggs.
399-import warnings
400-warnings.filterwarnings(
401- 'ignore',
402- 'Module .+ was already imported from .+, but .+ is being added.*',
403- UserWarning)
404-
405-import pkg_resources
406-pkg_resources.declare_namespace('lazr')
407
408=== removed symlink 'lib/lazr/batchnavigator'
409=== target was u'../../sourcecode/lazr.batchnavigator/src/lazr/batchnavigator'
410=== removed symlink 'lib/lazr/config'
411=== target was u'../../sourcecode/lazr.config/src/lazr/config'
412=== removed symlink 'lib/lazr/delegates'
413=== target was u'../../sourcecode/lazr.delegates/src/lazr/delegates'
414=== removed symlink 'lib/lazr/enum'
415=== target was u'../../sourcecode/lazr.enum/src/lazr/enum'
416=== removed symlink 'lib/lazr/lifecycle'
417=== target was u'../../sourcecode/lazr.lifecycle/src/lazr/lifecycle'
418=== removed symlink 'lib/lazr/restful'
419=== target was u'../../sourcecode/lazr.restful/src/lazr/restful/'
420=== modified file 'lib/lp/bugs/stories/webservice/xx-bug-target.txt'
421--- lib/lp/bugs/stories/webservice/xx-bug-target.txt 2009-06-12 16:36:02 +0000
422+++ lib/lp/bugs/stories/webservice/xx-bug-target.txt 2009-08-20 04:46:48 +0000
423@@ -30,8 +30,8 @@
424 ... u'Include your credit-card details, mwuh'}
425 >>> response = user_webservice.patch(
426 ... product['self_link'], 'application/json', dumps(patch))
427- >>> print response.getOutput()
428- HTTP/1.1 401 Unauthorized
429+ >>> print response
430+ HTTP/1.1 401 Unauthorized...
431 Content-Length: ...
432 Content-Type: text/plain
433 X-Lazr-Oopsid: OOPS-...
434@@ -117,4 +117,3 @@
435 ... '/testix', 'application/json',
436 ... dumps({'official_bug_tags': [u'foo', u'bar']}))
437 HTTP/1.1 209 Content Returned...
438-
439
440=== modified file 'lib/lp/bugs/stories/webservice/xx-bug.txt'
441--- lib/lp/bugs/stories/webservice/xx-bug.txt 2009-08-22 16:01:03 +0000
442+++ lib/lp/bugs/stories/webservice/xx-bug.txt 2009-08-25 19:09:14 +0000
443@@ -245,7 +245,7 @@
444 There is no top-level collection of messages; they only exist in
445 relation to some bug.
446
447- >>> webservice.get("/messages").getStatus()
448+ >>> webservice.get("/messages").status
449 404
450
451 We can add a new message to a bug by calling the newMessage method.
452@@ -254,7 +254,7 @@
453 ... "/bugs/5", 'newMessage',
454 ... subject='A new message',
455 ... content='This is a new message added through the webservice API.')
456- HTTP/1.1 201 Created
457+ HTTP/1.1 201 Created...
458 Content-Length: 0
459 Content-Type: text/plain
460 Location: http://api.launchpad.dev/beta/firefox/+bug/5/comments/1
461@@ -273,7 +273,7 @@
462 >>> print webservice.named_post(
463 ... "/bugs/5", 'newMessage',
464 ... content='This is a new message with no subject.')
465- HTTP/1.1 201 Created
466+ HTTP/1.1 201 Created...
467 Content-Length: 0
468 Content-Type: text/plain
469 Location: http://api.launchpad.dev/beta/firefox/+bug/5/comments/2
470@@ -334,7 +334,7 @@
471
472 The collection of bug tasks is not exposed as a resource:
473
474- >>> webservice.get("/bug_tasks").getStatus()
475+ >>> webservice.get("/bug_tasks").status
476 404
477
478 It's possible to change the task's assignee.
479@@ -823,7 +823,7 @@
480
481 The collection of bug watches is not exposed as a resource:
482
483- >>> webservice.get("/bug_watches").getStatus()
484+ >>> webservice.get("/bug_watches").status
485 404
486
487 We can modify the remote bug.
488@@ -844,8 +844,8 @@
489 >>> patch = {u'url': u'http://www.example.com/'}
490 >>> response = webservice.patch(
491 ... bug_watch_2000['self_link'], 'application/json', dumps(patch))
492- >>> print response.getOutput()
493- HTTP/1.1 400 Bad Request
494+ >>> print response
495+ HTTP/1.1 400 Bad Request...
496 Content-Length: 47
497 Content-Type: text/plain
498 ...
499@@ -860,8 +860,8 @@
500 ... bug_tracker=webservice.getAbsoluteUrl(
501 ... '/bugs/bugtrackers/mozilla.org'),
502 ... remote_bug='9876')
503- >>> print response.getOutput()
504- HTTP/1.1 201 Created
505+ >>> print response
506+ HTTP/1.1 201 Created...
507 Content-Length: 0
508 Content-Type: text/plain
509 Location: http://.../bugs/1/+watch/13
510@@ -922,8 +922,8 @@
511 ... }
512 >>> response = webservice.patch(
513 ... bug_tracker['self_link'], 'application/json', dumps(patch))
514- >>> print response.getOutput()
515- HTTP/1.1 301 Moved Permanently
516+ >>> print response
517+ HTTP/1.1 301 Moved Permanently...
518 Content-Length: 0
519 Content-Type: text/plain
520 Location: http://.../bugs/bugtrackers/bob
521@@ -932,8 +932,8 @@
522 Note the 301 response. We changed the name, so the API URL at which
523 the bug tracker can be found has changed.
524
525- >>> print webservice.get(bug_tracker['self_link']).getOutput()
526- HTTP/1.1 404 Not Found
527+ >>> print webservice.get(bug_tracker['self_link'])
528+ HTTP/1.1 404 Not Found...
529 Content-Length: ...
530 Content-Type: text/plain
531 X-Lazr-Oopsid: OOPS-...
532@@ -969,10 +969,13 @@
533
534 >>> print public_webservice.patch(
535 ... bug_tracker_path, 'application/json',
536- ... dumps(dict(active=False))).jsonBody()
537+ ... dumps(dict(active=False)))
538+ HTTP/1.1 401 Unauthorized
539+ ...
540 Traceback (most recent call last):
541- ...
542- ValueError: HTTP/1.1 401 Unauthorized...
543+ ...
544+ Unauthorized: (<BugTracker at ...>, 'active', 'launchpad.Admin')
545+ <BLANKLINE>
546
547 Admins can, however.
548
549@@ -1002,8 +1005,8 @@
550 ... bug_one['self_link'], 'addAttachment',
551 ... data="12345", filename="numbers.txt", content_type='foo/bar',
552 ... comment="The numbers you asked for.")
553- >>> print response.getOutput()
554- HTTP/1.1 201 Created
555+ >>> print response
556+ HTTP/1.1 201 Created...
557 Content-Length: 0
558 Content-Type: text/plain
559 Location: http://.../bugs/1/+attachment/1
560@@ -1043,8 +1046,8 @@
561 we must follow to download the data.
562
563 >>> data_response = webservice.get(attachment['data_link'])
564- >>> print data_response.getOutput()
565- HTTP/1.1 303 See Other
566+ >>> print data_response
567+ HTTP/1.1 303 See Other...
568 Content-Length: 0
569 Content-Type: text/plain
570 Location: http://localhost:58000/.../numbers.txt
571@@ -1098,7 +1101,7 @@
572
573 >>> response = webservice.put(
574 ... attachment['data_link'], 'text/text', 'abcdefg')
575- >>> print response.getOutput()
576+ >>> print response
577 HTTP/1.1 405 Method Not Allowed
578 ...
579
580@@ -1116,7 +1119,7 @@
581
582 >>> response = webservice.named_post(
583 ... attachment['self_link'], 'removeFromBug')
584- >>> print response.getOutput()
585+ >>> print response
586 HTTP/1.1 200 Ok
587 ...
588
589
590=== modified file 'lib/lp/bugs/tests/test_bugs_webservice.py'
591--- lib/lp/bugs/tests/test_bugs_webservice.py 2009-07-24 12:32:28 +0000
592+++ lib/lp/bugs/tests/test_bugs_webservice.py 2009-08-20 04:46:48 +0000
593@@ -40,7 +40,7 @@
594
595 def findBugDescription(self, response):
596 """Find the bug description field in an XHTML document fragment."""
597- soup = BeautifulSoup(response.consumeBody())
598+ soup = BeautifulSoup(response.body)
599 dt = soup.find('dt', text="description").parent
600 dd = dt.findNextSibling('dd')
601 return str(dd.contents.pop())
602@@ -49,8 +49,7 @@
603 response = self.webservice.get(
604 '/bugs/' + str(self.bug_two.id),
605 'application/xhtml+xml')
606-
607- self.assertEqual(response.getStatus(), 200)
608+ self.assertEqual(response.status, 200)
609
610 self.assertEqual(
611 self.findBugDescription(response),
612@@ -70,7 +69,7 @@
613 dumps(dict(description=new_description)),
614 headers=dict(accept='application/xhtml+xml'))
615
616- self.assertEqual(response.getStatus(), 209)
617+ self.assertEqual(response.status, 209)
618
619 self.assertEqual(
620 self.findBugDescription(response),
621@@ -118,9 +117,9 @@
622 response = self.webservice.get(
623 self.message_path, 'application/xhtml+xml')
624
625- self.assertEqual(response.getStatus(), 200)
626+ self.assertEqual(response.status, 200)
627
628- rendered_comment = response.consumeBody()
629+ rendered_comment = response.body
630 # XXX Bjorn Tillenius 2009-05-15 bug=377003
631 # The current request is a web service request when rendering
632 # the HTML, causing canonical_url to produce links pointing to the
633@@ -137,4 +136,3 @@
634
635 def test_suite():
636 return unittest.TestLoader().loadTestsFromName(__name__)
637-
638
639=== modified file 'lib/lp/code/stories/webservice/xx-branchmergeproposal.txt'
640--- lib/lp/code/stories/webservice/xx-branchmergeproposal.txt 2009-08-07 02:48:00 +0000
641+++ lib/lp/code/stories/webservice/xx-branchmergeproposal.txt 2009-08-20 04:46:48 +0000
642@@ -208,9 +208,9 @@
643 ... diff_content=diff_content,
644 ... diff_stat='M fooix.txt', source_revision_id='rev-a',
645 ... target_revision_id='rev-b', conflicts='oh, no conflicts')
646- >>> print response.getOutput()
647+ >>> print response
648 HTTP/1.1 200 Ok
649- Content-Length: ...
650+ ...
651 Content-Type: application/json
652 Vary: ...
653 ...
654@@ -245,9 +245,9 @@
655 ... diff_content='',
656 ... diff_stat='', source_revision_id='rev-c',
657 ... target_revision_id='rev-d', conflicts=None)
658- >>> print response.getOutput()
659+ >>> print response
660 HTTP/1.1 200 Ok
661- Content-Length: ...
662+ ...
663 Content-Type: application/json
664 Vary: ...
665 ...
666
667=== modified file 'lib/lp/soyuz/stories/webservice/xx-archive.txt'
668--- lib/lp/soyuz/stories/webservice/xx-archive.txt 2009-08-13 15:12:16 +0000
669+++ lib/lp/soyuz/stories/webservice/xx-archive.txt 2009-08-21 19:49:18 +0000
670@@ -1,4 +1,5 @@
671-= Archives =
672+Archives
673+========
674
675 Representations for IArchive can be fetch via the API for PPAs and
676 distribution archives.
677@@ -101,12 +102,14 @@
678
679 Attempting to grab a non-existent archive will result in a 404 error:
680
681- >>> bogus_archive = "ubuntutest/+archive/bogus"
682- >>> webservice.get(bogus_archive).jsonBody()
683+ >>> bogus_archive = "http://api.launchpad.dev/beta/ubuntutest/+archive/bogus"
684+ >>> print webservice.get(bogus_archive)
685+ HTTP/1.1 404 Not Found
686+ ...
687 Traceback (most recent call last):
688 ...
689- ValueError: HTTP/1.1 404 Not Found
690- ...
691+ NotFound: Object: ..., name: u'bogus'
692+ <BLANKLINE>
693
694
695 = Archive Permissions =
696@@ -224,7 +227,7 @@
697
698 >>> print webservice.named_get(
699 ... ubuntu['main_archive_link'], 'getUploadersForPackage',
700- ... source_package_name="badpackage").getOutput()
701+ ... source_package_name="badpackage")
702 HTTP/1.1 400 Bad Request
703 ...
704
705@@ -236,7 +239,7 @@
706 ... ubuntu['main_archive_link'], 'newPackageUploader', {},
707 ... person=name12['self_link'],
708 ... source_package_name='mozilla-firefox')
709- >>> print response.getOutput()
710+ >>> print response
711 HTTP/1.1 201 Created
712 ...
713
714@@ -279,7 +282,7 @@
715
716 >>> print webservice.named_get(
717 ... ubuntu['main_archive_link'], 'getUploadersForComponent',
718- ... component_name="badcomponent").getOutput()
719+ ... component_name="badcomponent")
720 HTTP/1.1 400 Bad Request
721 ...
722
723@@ -297,7 +300,7 @@
724 ... ubuntu['main_archive_link'], 'newComponentUploader', {},
725 ... person=name12['self_link'],
726 ... component_name='restricted')
727- >>> print response.getOutput()
728+ >>> print response
729 HTTP/1.1 201 Created
730 ...
731
732@@ -318,7 +321,7 @@
733 >>> response = webservice.named_post(
734 ... cprov_archive['self_link'], 'newComponentUploader', {},
735 ... person=name12['self_link'], component_name='restricted')
736- >>> print response.getOutput()
737+ >>> print response
738 HTTP/1.1 400 Bad Request
739 ...
740 InvalidComponent: Component for PPAs should be 'main'
741@@ -327,7 +330,7 @@
742 >>> response = webservice.named_post(
743 ... cprov_archive['self_link'], 'newComponentUploader', {},
744 ... person=name12['self_link'], component_name='main')
745- >>> print response.getOutput()
746+ >>> print response
747 HTTP/1.1 201 Created
748 ...
749
750@@ -382,7 +385,7 @@
751 ... ubuntu['main_archive_link'], 'newQueueAdmin', {},
752 ... person=name12['self_link'],
753 ... component_name='partner')
754- >>> print response.getOutput()
755+ >>> print response
756 HTTP/1.1 201 Created
757 ...
758
759@@ -425,7 +428,7 @@
760
761 >>> missing_type_url = '/ubuntu/+archive/primary/+upload/name12?item=firefox'
762 >>> this_will_fail = webservice.get(missing_type_url)
763- >>> print this_will_fail.getOutput()
764+ >>> print this_will_fail
765 HTTP/1.1 404 Not Found
766 ...
767
768@@ -434,7 +437,7 @@
769
770 >>> wrong_type_url = '/ubuntu/+archive/primary/+upload/name12?type=packageset&item=firefox&type=Integer'
771 >>> this_will_fail = webservice.get(missing_type_url)
772- >>> print this_will_fail.getOutput()
773+ >>> print this_will_fail
774 HTTP/1.1 404 Not Found
775 ...
776
777@@ -442,7 +445,7 @@
778
779 >>> missing_item_url = '/ubuntu/+archive/primary/+upload/name12?type=packageset'
780 >>> this_will_fail = webservice.get(missing_type_url)
781- >>> print this_will_fail.getOutput()
782+ >>> print this_will_fail
783 HTTP/1.1 404 Not Found
784 ...
785
786@@ -451,7 +454,7 @@
787
788 >>> wrong_type_url = '/ubuntu/+archive/primary/+upload/name12?type=packageset&item=firefox&item=vapourware'
789 >>> this_will_fail = webservice.get(missing_type_url)
790- >>> print this_will_fail.getOutput()
791+ >>> print this_will_fail
792 HTTP/1.1 404 Not Found
793 ...
794
795@@ -626,7 +629,7 @@
796 syncSources is invoked directly by the client, and any problems are
797 the client's fault. Therefore, there's no need to record an OOPS.
798
799- >>> 'X-Lazr-Oopsid' in already_copied.getOutput()
800+ >>> 'X-Lazr-Oopsid' in str(already_copied)
801 False
802
803 'syncSources' behaves trasactionally, i.e. it will only synchronise
804@@ -749,7 +752,7 @@
805 >>> response = mark_webservice.named_post(
806 ... mark_archive['self_link'], 'newSubscription',
807 ... subscriber=cprov_archive['owner_link'])
808- >>> print response.getOutput()
809+ >>> print response
810 HTTP/1.1 400 Bad Request
811 ...
812 ArchiveNotPrivate: Only private archives can have subscriptions.
813@@ -763,7 +766,7 @@
814 ... cprov_archive['self_link'], 'newSubscription',
815 ... subscriber=mark['self_link'])
816
817- >>> print response.getOutput()
818+ >>> print response
819 HTTP/1.1 201 Created
820 ...
821
822@@ -789,7 +792,7 @@
823
824 >>> response = user_webservice.get(
825 ... response.getHeader('Location'))
826- >>> print response.getOutput()
827+ >>> print response
828 HTTP/1.1 401 Unauthorized
829 ...
830
831@@ -800,7 +803,7 @@
832 >>> response = user_webservice.named_post(
833 ... cprov_archive['self_link'], 'newSubscription',
834 ... subscriber=cprov_archive['owner_link'])
835- >>> print response.getOutput()
836+ >>> print response
837 HTTP/1.1 401 Unauthorized
838 ...
839
840@@ -810,7 +813,7 @@
841 >>> response = cprov_webservice.named_post(
842 ... cprov_archive['self_link'], 'newSubscription',
843 ... subscriber=mark['self_link'])
844- >>> print response.getOutput()
845+ >>> print response
846 HTTP/1.1 400 Bad Request
847 ...
848 AlreadySubscribed: Mark Shuttleworth already has a current subscription
849
850=== modified file 'lib/lp/soyuz/stories/webservice/xx-binary-package-publishing.txt'
851--- lib/lp/soyuz/stories/webservice/xx-binary-package-publishing.txt 2009-04-28 12:59:43 +0000
852+++ lib/lp/soyuz/stories/webservice/xx-binary-package-publishing.txt 2009-08-20 04:46:48 +0000
853@@ -110,7 +110,7 @@
854
855 >>> cprov_bins_response = webservice.named_get(
856 ... cprov_archive['self_link'], 'getPublishedBinaries')
857- >>> print cprov_bins_response.getOutput()
858+ >>> print cprov_bins_response
859 HTTP/1.1 200 Ok
860 ...
861
862@@ -118,7 +118,7 @@
863
864 >>> response = user_webservice.named_get(
865 ... cprov_archive['self_link'], 'getPublishedBinaries')
866- >>> print response.getOutput()
867+ >>> print response
868 HTTP/1.1 401 Unauthorized
869 ...
870
871@@ -127,7 +127,6 @@
872
873 >>> private_publication_url = pubs['entries'][0]['self_link']
874 >>> response = user_webservice.get(private_publication_url)
875- >>> print response.getOutput()
876+ >>> print response
877 HTTP/1.1 401 Unauthorized
878 ...
879-
880
881=== modified file 'lib/lp/soyuz/stories/webservice/xx-packageset.txt'
882--- lib/lp/soyuz/stories/webservice/xx-packageset.txt 2009-07-25 16:33:39 +0000
883+++ lib/lp/soyuz/stories/webservice/xx-packageset.txt 2009-08-20 04:46:48 +0000
884@@ -12,7 +12,7 @@
885
886 The actual package set *functionality* is tested in much greater detail
887 here:
888-
889+
890 lib/lp/soyuz/doc/packageset.txt
891
892 Please refer to the tests contained in the file above if you are really
893@@ -29,7 +29,7 @@
894 ... '/package-sets', 'new', {},
895 ... name=u'umbrella', description=u'Contains all source packages',
896 ... owner=name12['self_link'])
897- >>> print response.getOutput()
898+ >>> print response
899 HTTP/1.1 201 Created
900 ...
901
902@@ -67,7 +67,7 @@
903
904 >>> response = webservice.named_get(
905 ... '/package-sets', 'getByName', {}, name=u'not-found')
906- >>> print response.getOutput()
907+ >>> print response
908 HTTP/1.1 400 Bad Request
909 ...
910 NoSuchPackageSet: No such packageset: 'not-found'.
911@@ -83,7 +83,7 @@
912 >>> response = webservice.named_post(
913 ... '/package-sets/umbrella', 'addSources', {},
914 ... names=[spn.name for spn in all_spns])
915- >>> print response.getOutput()
916+ >>> print response
917 HTTP/1.1 200 Ok
918 ...
919
920@@ -93,7 +93,7 @@
921 >>> response = webservice.named_post(
922 ... '/package-sets/umbrella', 'addSources', {},
923 ... names=[u'does-not-exist'])
924- >>> print response.getOutput()
925+ >>> print response
926 HTTP/1.1 200 Ok
927 ...
928 null
929@@ -101,7 +101,7 @@
930 >>> response = webservice.named_post(
931 ... '/package-sets/umbrella', 'removeSources', {},
932 ... names=[u'does-not-exist'])
933- >>> print response.getOutput()
934+ >>> print response
935 HTTP/1.1 200 Ok
936 ...
937 null
938@@ -110,7 +110,7 @@
939
940 >>> response = webservice.named_get(
941 ... '/package-sets/umbrella', 'getSourcesIncluded', {})
942- >>> print response.getOutput()
943+ >>> print response
944 HTTP/1.1 200 Ok
945 ...
946 ["a52dec",
947@@ -138,7 +138,7 @@
948 >>> response = webservice.named_post(
949 ... '/package-sets/umbrella', 'removeSources', {},
950 ... names=["foobar", "iceweasel"])
951- >>> print response.getOutput()
952+ >>> print response
953 HTTP/1.1 200 Ok
954 ...
955
956@@ -147,7 +147,7 @@
957
958 >>> response = webservice.named_get(
959 ... '/package-sets/umbrella', 'getSourcesIncluded', {})
960- >>> print response.getOutput()
961+ >>> print response
962 HTTP/1.1 200 Ok
963 ...
964 ["a52dec",
965@@ -183,7 +183,7 @@
966
967 >>> response = webservice.named_get(
968 ... '/package-sets/umbrella', 'setsIncluded', {})
969- >>> print response.getOutput()
970+ >>> print response
971 HTTP/1.1 200 Ok
972 ...
973 {"total_size": 0, "start": null, "entries": []}
974@@ -194,7 +194,7 @@
975 ... '/package-sets', 'new', {},
976 ... name=u'gnome', description=u'Contains all gnome packages',
977 ... owner=name12['self_link'])
978- >>> print response.getOutput()
979+ >>> print response
980 HTTP/1.1 201 Created
981 ...
982
983@@ -202,7 +202,7 @@
984 ... '/package-sets', 'new', {},
985 ... name=u'mozilla', description=u'Contains all mozilla packages',
986 ... owner=name12['self_link'])
987- >>> print response.getOutput()
988+ >>> print response
989 HTTP/1.1 201 Created
990 ...
991
992@@ -210,7 +210,7 @@
993 ... '/package-sets', 'new', {},
994 ... name=u'firefox', description=u'Contains all firefox packages',
995 ... owner=name12['self_link'])
996- >>> print response.getOutput()
997+ >>> print response
998 HTTP/1.1 201 Created
999 ...
1000
1001@@ -219,7 +219,7 @@
1002 ... name=u'thunderbird',
1003 ... description=u'Contains all thunderbird packages',
1004 ... owner=name12['self_link'])
1005- >>> print response.getOutput()
1006+ >>> print response
1007 HTTP/1.1 201 Created
1008 ...
1009
1010@@ -228,7 +228,7 @@
1011 ... name=u'languagepack',
1012 ... description=u'Contains all languagepack packages',
1013 ... owner=name12['self_link'])
1014- >>> print response.getOutput()
1015+ >>> print response
1016 HTTP/1.1 201 Created
1017 ...
1018
1019@@ -247,27 +247,27 @@
1020 >>> response = webservice.named_post(
1021 ... '/package-sets/umbrella', 'addSubsets', {},
1022 ... names=[u'gnome', u'mozilla'])
1023- >>> print response.getOutput()
1024+ >>> print response
1025 HTTP/1.1 200 Ok
1026 ...
1027
1028 >>> response = webservice.named_post(
1029 ... '/package-sets/gnome', 'addSubsets', {}, names=[u'languagepack'])
1030- >>> print response.getOutput()
1031+ >>> print response
1032 HTTP/1.1 200 Ok
1033 ...
1034
1035 >>> response = webservice.named_post(
1036 ... '/package-sets/thunderbird', 'addSubsets', {},
1037 ... names=[u'languagepack'])
1038- >>> print response.getOutput()
1039+ >>> print response
1040 HTTP/1.1 200 Ok
1041 ...
1042
1043 >>> response = webservice.named_post(
1044 ... '/package-sets/mozilla', 'addSubsets', {},
1045 ... names=[u'firefox', u'thunderbird'])
1046- >>> print response.getOutput()
1047+ >>> print response
1048 HTTP/1.1 200 Ok
1049 ...
1050
1051@@ -277,7 +277,7 @@
1052 >>> response = webservice.named_post(
1053 ... '/package-sets/thunderbird', 'addSubsets', {},
1054 ... names=[u'does-not-exist'])
1055- >>> print response.getOutput()
1056+ >>> print response
1057 HTTP/1.1 200 Ok
1058 ...
1059 null
1060@@ -285,7 +285,7 @@
1061 >>> response = webservice.named_post(
1062 ... '/package-sets/thunderbird', 'removeSubsets', {},
1063 ... names=[u'does-not-exist'])
1064- >>> print response.getOutput()
1065+ >>> print response
1066 HTTP/1.1 200 Ok
1067 ...
1068 null
1069@@ -337,7 +337,7 @@
1070 >>> response = webservice.named_post(
1071 ... '/package-sets/thunderbird', 'removeSubsets', {},
1072 ... names=[u'languagepack'])
1073- >>> print response.getOutput()
1074+ >>> print response
1075 HTTP/1.1 200 Ok
1076 ...
1077
1078@@ -355,13 +355,13 @@
1079 >>> response = webservice.named_post(
1080 ... '/package-sets/firefox', 'addSources', {},
1081 ... names=['at', 'mozilla-firefox', 'language-pack-de'])
1082- >>> print response.getOutput()
1083+ >>> print response
1084 HTTP/1.1 200 Ok
1085 ...
1086
1087 >>> response = webservice.named_get(
1088 ... '/package-sets/firefox', 'getSourcesIncluded', {})
1089- >>> print response.getOutput()
1090+ >>> print response
1091 HTTP/1.1 200 Ok
1092 ...
1093 ["at", "language-pack-de", "mozilla-firefox"]
1094@@ -369,13 +369,13 @@
1095 >>> response = webservice.named_post(
1096 ... '/package-sets/thunderbird', 'addSources', {},
1097 ... names=['at', 'cnews', 'thunderbird', 'language-pack-de'])
1098- >>> print response.getOutput()
1099+ >>> print response
1100 HTTP/1.1 200 Ok
1101 ...
1102
1103 >>> response = webservice.named_get(
1104 ... '/package-sets/thunderbird', 'getSourcesIncluded', {})
1105- >>> print response.getOutput()
1106+ >>> print response
1107 HTTP/1.1 200 Ok
1108 ...
1109 ["at", "cnews", "language-pack-de", "thunderbird"]
1110@@ -405,7 +405,7 @@
1111 >>> response = webservice.named_get(
1112 ... '/package-sets/', 'setsIncludingSource', {},
1113 ... sourcepackagename=u'does-not-exist')
1114- >>> print response.getOutput()
1115+ >>> print response
1116 HTTP/1.1 400 Bad Request
1117 ...
1118 No such source package: 'does-not-exist'.
1119@@ -418,7 +418,7 @@
1120 >>> response = webservice.named_get(
1121 ... '/package-sets/firefox', 'getSourcesSharedBy', {},
1122 ... other_package_set=thunderbird['self_link'])
1123- >>> print response.getOutput()
1124+ >>> print response
1125 HTTP/1.1 200 Ok
1126 ...
1127 ["at", "language-pack-de"]
1128@@ -428,7 +428,7 @@
1129 >>> response = webservice.named_get(
1130 ... '/package-sets/firefox', 'getSourcesNotSharedBy', {},
1131 ... other_package_set=thunderbird['self_link'])
1132- >>> print response.getOutput()
1133+ >>> print response
1134 HTTP/1.1 200 Ok
1135 ...
1136 ["mozilla-firefox"]
1137@@ -437,7 +437,7 @@
1138 >>> response = webservice.named_get(
1139 ... '/package-sets/thunderbird', 'getSourcesNotSharedBy', {},
1140 ... other_package_set=firefox['self_link'])
1141- >>> print response.getOutput()
1142+ >>> print response
1143 HTTP/1.1 200 Ok
1144 ...
1145 ["cnews", "thunderbird"]
1146@@ -462,7 +462,7 @@
1147 ... ubuntu['main_archive_link'], 'newPackagesetUploader', {},
1148 ... person=name12['self_link'],
1149 ... packageset='firefox')
1150- >>> print response.getOutput()
1151+ >>> print response
1152 HTTP/1.1 201 Created
1153 ...
1154
1155@@ -490,7 +490,7 @@
1156 ... ubuntu['main_archive_link'], 'newPackagesetUploader', {},
1157 ... person=name12['self_link'],
1158 ... packageset='mozilla')
1159- >>> print response.getOutput()
1160+ >>> print response
1161 HTTP/1.1 201 Created
1162 ...
1163
1164@@ -519,7 +519,7 @@
1165 ... ubuntu['main_archive_link'], 'deletePackagesetUploader', {},
1166 ... person=name12['self_link'],
1167 ... packageset='mozilla')
1168- >>> print response.getOutput()
1169+ >>> print response
1170 HTTP/1.1 200 Ok
1171 ...
1172
1173@@ -539,7 +539,7 @@
1174 ... ubuntu['main_archive_link'], 'newPackagesetUploader', {},
1175 ... person=cprov['self_link'],
1176 ... packageset='mozilla')
1177- >>> print response.getOutput()
1178+ >>> print response
1179 HTTP/1.1 201 Created
1180 ...
1181
1182@@ -547,7 +547,7 @@
1183 ... ubuntu['main_archive_link'], 'newPackagesetUploader', {},
1184 ... person=cprov['self_link'],
1185 ... packageset='thunderbird')
1186- >>> print response.getOutput()
1187+ >>> print response
1188 HTTP/1.1 201 Created
1189 ...
1190
1191
1192=== modified file 'lib/lp/soyuz/stories/webservice/xx-source-package-publishing.txt'
1193--- lib/lp/soyuz/stories/webservice/xx-source-package-publishing.txt 2009-07-10 09:14:41 +0000
1194+++ lib/lp/soyuz/stories/webservice/xx-source-package-publishing.txt 2009-08-20 04:46:48 +0000
1195@@ -179,7 +179,7 @@
1196
1197 >>> cprov_srcs_response = webservice.named_get(
1198 ... cprov_archive['self_link'], 'getPublishedSources')
1199- >>> print cprov_srcs_response.getOutput()
1200+ >>> print cprov_srcs_response
1201 HTTP/1.1 200 Ok
1202 ...
1203
1204@@ -187,7 +187,7 @@
1205
1206 >>> response = user_webservice.named_get(
1207 ... cprov_archive['self_link'], 'getPublishedSources')
1208- >>> print response.getOutput()
1209+ >>> print response
1210 HTTP/1.1 401 Unauthorized
1211 ...
1212
1213@@ -196,7 +196,7 @@
1214
1215 >>> private_publication_url = pubs['entries'][0]['self_link']
1216 >>> response = user_webservice.get(private_publication_url)
1217- >>> print response.getOutput()
1218+ >>> print response
1219 HTTP/1.1 401 Unauthorized
1220 ...
1221
1222@@ -252,7 +252,7 @@
1223 >>> cprov_srcs = cprov_srcs_response.jsonBody()
1224 >>> src_link = cprov_srcs['entries'][0]['self_link']
1225
1226-The src_link will be of the form:
1227+The src_link will be of the form:
1228 u'http://api.launchpad.dev/beta/~cprov/+archive/ppa/+sourcepub/27'
1229 so:
1230
1231@@ -283,4 +283,3 @@
1232 >>> print_build_summaries(build_summaries)
1233 Source ID 27: FAILEDTOBUILD ([u'i386'])
1234 Source ID 28: FULLYBUILT_PENDING ([u'i386'])
1235-
1236
1237=== modified file 'setup.py'
1238--- setup.py 2009-08-05 18:52:52 +0000
1239+++ setup.py 2009-08-17 19:16:23 +0000
1240@@ -30,6 +30,12 @@
1241 'feedvalidator',
1242 'funkload',
1243 'launchpadlib',
1244+ 'lazr.batchnavigator',
1245+ 'lazr.config',
1246+ 'lazr.delegates',
1247+ 'lazr.enum',
1248+ 'lazr.lifecycle',
1249+ 'lazr.restful',
1250 'lazr.smtptest',
1251 'lazr.uri',
1252 'mechanize',
1253
1254=== modified file 'utilities/sourcedeps.conf'
1255--- utilities/sourcedeps.conf 2009-08-14 17:26:21 +0000
1256+++ utilities/sourcedeps.conf 2009-08-17 19:16:23 +0000
1257@@ -3,13 +3,7 @@
1258 bzr-svn=lp:~launchpad-pqm/bzr-svn/devel/
1259 dulwich=lp:~launchpad-pqm/dulwich/devel/
1260 launchpad-loggerhead=lp:~launchpad-pqm/launchpad-loggerhead/devel/
1261-lazr.batchnavigator=lp:~launchpad-pqm/lazr.batchnavigator/trunk/
1262-lazr.config=lp:~launchpad-pqm/lazr.config/devel/
1263-lazr.delegates=lp:~launchpad-pqm/lazr.delegates/devel/
1264-lazr.enum=lp:~launchpad-pqm/lazr.enum/trunk/
1265 lazr-js=lp:~launchpad-pqm/lazr-js/toolchain/
1266-lazr.lifecycle=lp:~launchpad-pqm/lazr.lifecycle/trunk/
1267-lazr.restful=lp:~launchpad-pqm/lazr.restful/trunk/
1268 loggerhead=lp:~launchpad-pqm/loggerhead/devel/
1269 mailman=lp:~launchpad-pqm/mailman/2.1/
1270 pygpgme=lp:~launchpad-pqm/pygpgme/devel/
1271
1272=== modified file 'versions.cfg'
1273--- versions.cfg 2009-08-24 21:21:35 +0000
1274+++ versions.cfg 2009-08-25 21:39:10 +0000
1275@@ -11,14 +11,20 @@
1276 ctypes = 1.0.2
1277 docutils = 0.5
1278 elementtree = 1.2.6-20050316
1279+epydoc = 3.0.1
1280 feedvalidator = 0.0.0DEV-r1049
1281 functest = 0.8.7
1282 funkload = 1.10.0
1283 httplib2 = 0.4.0
1284 ipython = 0.9.1
1285-jquery.javascript = 1.0.0
1286-jquery.layer = 1.0.0
1287-launchpadlib = 1.0.3
1288+launchpadlib = 1.5.1
1289+lazr.batchnavigator = 1.0
1290+lazr.config = 1.1.3
1291+lazr.delegates = 1.0.1
1292+lazr.enum = 1.1.1
1293+lazr.lifecycle = 0.1
1294+lazr.restful = 0.9.5
1295+lazr.restfulclient = 0.9.4
1296 lazr.smtptest = 1.1
1297 lazr.uri = 1.0.1
1298 mechanize = 0.1.7b
1299@@ -30,6 +36,7 @@
1300 python-openid = 2.2.1
1301 pytz = 2009j
1302 RestrictedPython = 3.4.2
1303+roman = 1.4.0
1304 setuptools = 0.6c9
1305 simplejson = 2.0.9
1306 simplesettings = 0.4
1307@@ -38,10 +45,12 @@
1308 storm = 0.15
1309 transaction = 1.0a1
1310 uuid = 1.30
1311-wadllib = 0.1
1312+van.testing = 2.0.1
1313+wadllib = 1.1.3
1314 webunit = 1.3.8
1315 windmill = 1.2
1316 wsgi-fileserver = 0.2.7
1317+wsgi-intercept = 0.4
1318 wsgi-jsonrpc = 0.2.8
1319 wsgi-xmlrpc = 0.2.7
1320 z3c.coverage = 1.1.2
1321@@ -153,7 +162,6 @@
1322 zope.pagetemplate = 3.4.0
1323 zope.proxy = 3.5.0
1324 zope.publisher = 3.5.6
1325-zope.rdb = 3.4.0
1326 zope.schema = 3.5.4
1327 zope.security = 3.7.1
1328 zope.securitypolicy = 3.4.1
1329@@ -167,6 +175,5 @@
1330 zope.testbrowser = 3.4.2
1331 zope.testing = 3.8.1
1332 zope.thread = 3.4
1333-# zope.traversing = 3.5.2
1334 zope.traversing = 3.4.1
1335 zope.viewlet = 3.4.2