Merge lp:~jml/launchpad/more-services into lp:launchpad
- more-services
- Merge into devel
Proposed by
Jonathan Lange
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~jml/launchpad/more-services |
Merge into: | lp:launchpad |
Diff against target: |
800 lines (+208/-237) 15 files modified
lib/canonical/launchpad/scripts/__init__.py (+0/-3) lib/canonical/launchpad/xmlrpc/faults.py (+1/-35) lib/canonical/launchpad/xmlrpc/tests/test_authserver.py (+2/-8) lib/canonical/launchpad/xmlrpc/tests/test_faults.py (+0/-65) lib/lp/code/xmlrpc/tests/test_branch.py (+2/-1) lib/lp/code/xmlrpc/tests/test_codehosting.py (+20/-34) lib/lp/codehosting/sshserver/accesslog.py (+1/-15) lib/lp/codehosting/sshserver/auth.py (+1/-1) lib/lp/codehosting/vfs/branchfs.py (+3/-2) lib/lp/codehosting/vfs/branchfsclient.py (+0/-18) lib/lp/codehosting/vfs/tests/test_branchfsclient.py (+1/-54) lib/lp/services/twistedsupport/tests/test_xmlrpc.py (+90/-0) lib/lp/services/twistedsupport/xmlrpc.py (+27/-0) lib/lp/services/utils.py (+20/-1) lib/lp/services/xmlrpc.py (+40/-0) |
To merge this branch: | bzr merge lp:~jml/launchpad/more-services |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Abel Deuring (community) | code | Approve | |
Review via email: mp+23107@code.launchpad.net |
Commit message
Move Twisted fault support and the base LaunchpadFault to lp.services. Move 'synchronize' helper to lp.services.
Description of the change
This branch extracts some drive-by-cleanups from my uber-branch lp:~jml/launchpad/ssh-key-auth.
Specifically:
1. The higher-order function "synchronize" is moved to lp.services
2. The "trap_fault" helper for XMLRPC Twisted code is moved to lp.services.
3. The "LaunchpadFault" base class for all of our XMLRPC faults is moved to lp.services.xmlrpc
4. A XXX comment from 2005 is deleted
I know it's a bit of a grab-bag of changes, but they are all conceptually quite simple, so I hope it's an easy enough branch to review.
To post a comment you must log in.
Revision history for this message
Abel Deuring (adeuring) : | # |
review:
Approve
(code)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/canonical/launchpad/scripts/__init__.py' |
2 | --- lib/canonical/launchpad/scripts/__init__.py 2010-03-18 21:30:51 +0000 |
3 | +++ lib/canonical/launchpad/scripts/__init__.py 2010-04-10 13:58:26 +0000 |
4 | @@ -46,9 +46,6 @@ |
5 | # We should probably split out all the stuff in this directory that |
6 | # doesn't rely on Zope and migrate it to canonical/scripts. |
7 | |
8 | -# XXX SteveAlexander 2005-04-11: |
9 | -# This is a total mess. I need to work out what this all means. |
10 | - |
11 | class NullItem: |
12 | def __init__(self, context, handler, info, *argdata): |
13 | newcontext = GroupingContextDecorator(context) |
14 | |
15 | === modified file 'lib/canonical/launchpad/xmlrpc/faults.py' |
16 | --- lib/canonical/launchpad/xmlrpc/faults.py 2010-03-14 20:15:34 +0000 |
17 | +++ lib/canonical/launchpad/xmlrpc/faults.py 2010-04-10 13:58:26 +0000 |
18 | @@ -15,7 +15,6 @@ |
19 | 'BranchNameInUse', |
20 | 'BranchUniqueNameConflict', |
21 | 'CannotHaveLinkedBranch', |
22 | - 'check_fault', |
23 | 'FileBugGotProductAndDistro', |
24 | 'FileBugMissingProductOrDistribution', |
25 | 'InvalidBranchIdentifier', |
26 | @@ -42,41 +41,8 @@ |
27 | ] |
28 | |
29 | |
30 | -import xmlrpclib |
31 | - |
32 | - |
33 | from lp.registry.interfaces.projectgroup import IProjectGroup |
34 | - |
35 | - |
36 | -def check_fault(fault, *fault_classes): |
37 | - """Check if 'fault's faultCode matches any of 'fault_classes'. |
38 | - |
39 | - :param fault: An instance of `xmlrpclib.Fault`. |
40 | - :param fault_classes: Any number of `LaunchpadFault` subclasses. |
41 | - """ |
42 | - for cls in fault_classes: |
43 | - if fault.faultCode == cls.error_code: |
44 | - return True |
45 | - return False |
46 | - |
47 | - |
48 | -class LaunchpadFault(xmlrpclib.Fault): |
49 | - """Base class for a Launchpad XMLRPC fault. |
50 | - |
51 | - Subclasses should define a unique error_code and a msg_template, |
52 | - which will be interpolated with the given keyword arguments. |
53 | - """ |
54 | - |
55 | - error_code = None |
56 | - msg_template = None |
57 | - |
58 | - def __init__(self, **kw): |
59 | - assert self.error_code is not None, ( |
60 | - "Subclasses must define error_code.") |
61 | - assert self.msg_template is not None, ( |
62 | - "Subclasses must define msg_template.") |
63 | - msg = self.msg_template % kw |
64 | - xmlrpclib.Fault.__init__(self, self.error_code, msg) |
65 | +from lp.services.xmlrpc import LaunchpadFault |
66 | |
67 | |
68 | class NoSuchProduct(LaunchpadFault): |
69 | |
70 | === modified file 'lib/canonical/launchpad/xmlrpc/tests/test_authserver.py' |
71 | --- lib/canonical/launchpad/xmlrpc/tests/test_authserver.py 2009-06-25 05:30:52 +0000 |
72 | +++ lib/canonical/launchpad/xmlrpc/tests/test_authserver.py 2010-04-10 13:58:26 +0000 |
73 | @@ -16,6 +16,7 @@ |
74 | from canonical.launchpad.xmlrpc.authserver import AuthServerAPIView |
75 | from canonical.testing.layers import DatabaseFunctionalLayer |
76 | |
77 | + |
78 | class GetUserAndSSHKeysTests(TestCaseWithFactory): |
79 | """Tests for the implementation of `IAuthServer.getUserAndSSHKeys`. |
80 | """ |
81 | @@ -28,17 +29,10 @@ |
82 | self.authserver = AuthServerAPIView( |
83 | private_root.authserver, TestRequest()) |
84 | |
85 | - def assertFaultEqual(self, expected_fault, observed_fault): |
86 | - """Assert that `expected_fault` equals `observed_fault`.""" |
87 | - self.assertIsInstance(observed_fault, faults.LaunchpadFault) |
88 | - self.assertEqual(expected_fault.faultCode, observed_fault.faultCode) |
89 | - self.assertEqual( |
90 | - expected_fault.faultString, observed_fault.faultString) |
91 | - |
92 | def test_user_not_found(self): |
93 | # getUserAndSSHKeys returns the NoSuchPersonWithName fault if there is |
94 | # no Person of the given name. |
95 | - self.assertFaultEqual( |
96 | + self.assertEqual( |
97 | faults.NoSuchPersonWithName('no-one'), |
98 | self.authserver.getUserAndSSHKeys('no-one')) |
99 | |
100 | |
101 | === removed file 'lib/canonical/launchpad/xmlrpc/tests/test_faults.py' |
102 | --- lib/canonical/launchpad/xmlrpc/tests/test_faults.py 2009-06-25 05:30:52 +0000 |
103 | +++ lib/canonical/launchpad/xmlrpc/tests/test_faults.py 1970-01-01 00:00:00 +0000 |
104 | @@ -1,65 +0,0 @@ |
105 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
106 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
107 | - |
108 | -"""Tests for `canonical.launchpad.xmlrpc.faults`.""" |
109 | - |
110 | -__metaclass__ = type |
111 | - |
112 | -import unittest |
113 | - |
114 | -from lp.testing import TestCase |
115 | -from canonical.launchpad.xmlrpc import faults |
116 | - |
117 | - |
118 | -class TestFaultOne(faults.LaunchpadFault): |
119 | - """An arbitrary subclass of `LaunchpadFault`. |
120 | - |
121 | - This class and `TestFaultTwo` are a pair of distinct `LaunchpadFault` |
122 | - subclasses to use in tests. |
123 | - """ |
124 | - |
125 | - error_code = 1001 |
126 | - msg_template = "Fault one." |
127 | - |
128 | - |
129 | -class TestFaultTwo(faults.LaunchpadFault): |
130 | - """Another arbitrary subclass of `LaunchpadFault`. |
131 | - |
132 | - This class and `TestFaultOne` are a pair of distinct `LaunchpadFault` |
133 | - subclasses to use in tests. |
134 | - """ |
135 | - |
136 | - error_code = 1002 |
137 | - msg_template = "Fault two." |
138 | - |
139 | - |
140 | -class TestTrapFault(TestCase): |
141 | - """Tests for `check_fault`.""" |
142 | - |
143 | - def test_wrong_fault(self): |
144 | - # check_fault returns False if the passed fault does not have the code |
145 | - # of one of the passed classes. |
146 | - self.assertFalse( |
147 | - faults.check_fault(TestFaultOne(), TestFaultTwo)) |
148 | - |
149 | - def test_no_fault_classes(self): |
150 | - # check_fault returns False if there are no passed classes. |
151 | - self.assertFalse( |
152 | - faults.check_fault(TestFaultOne())) |
153 | - |
154 | - def test_matches(self): |
155 | - # check_fault returns True if the passed fault has the code of the |
156 | - # passed class. |
157 | - self.assertTrue( |
158 | - faults.check_fault(TestFaultOne(), TestFaultOne)) |
159 | - |
160 | - def test_matches_one_of_set(self): |
161 | - # check_fault returns True if the passed fault has the code of one of |
162 | - # the passed classes. |
163 | - self.assertTrue(faults.check_fault( |
164 | - TestFaultOne(), TestFaultOne, TestFaultTwo)) |
165 | - |
166 | - |
167 | -def test_suite(): |
168 | - return unittest.TestLoader().loadTestsFromName(__name__) |
169 | - |
170 | |
171 | === modified file 'lib/lp/code/xmlrpc/tests/test_branch.py' |
172 | --- lib/lp/code/xmlrpc/tests/test_branch.py 2010-02-22 06:16:47 +0000 |
173 | +++ lib/lp/code/xmlrpc/tests/test_branch.py 2010-04-10 13:58:26 +0000 |
174 | @@ -21,6 +21,7 @@ |
175 | from lp.testing import TestCaseWithFactory |
176 | from lazr.uri import URI |
177 | from lp.code.xmlrpc.branch import PublicCodehostingAPI |
178 | +from lp.services.xmlrpc import LaunchpadFault |
179 | from canonical.launchpad.xmlrpc import faults |
180 | from canonical.testing import DatabaseFunctionalLayer |
181 | |
182 | @@ -62,7 +63,7 @@ |
183 | """Assert that `lp_url_path` path expands to `unique_name`.""" |
184 | results = self.api.resolve_lp_path(lp_url_path) |
185 | # This improves the error message if results happens to be a fault. |
186 | - if isinstance(results, faults.LaunchpadFault): |
187 | + if isinstance(results, LaunchpadFault): |
188 | raise results |
189 | for url in results['urls']: |
190 | self.assertEqual('/' + unique_name, URI(url).path) |
191 | |
192 | === modified file 'lib/lp/code/xmlrpc/tests/test_codehosting.py' |
193 | --- lib/lp/code/xmlrpc/tests/test_codehosting.py 2010-02-24 19:01:25 +0000 |
194 | +++ lib/lp/code/xmlrpc/tests/test_codehosting.py 2010-04-10 13:58:26 +0000 |
195 | @@ -137,13 +137,6 @@ |
196 | self.branch_lookup = frontend.getBranchLookup() |
197 | self.getLastActivity = frontend.getLastActivity |
198 | |
199 | - def assertFaultEqual(self, expected_fault, observed_fault): |
200 | - """Assert that `expected_fault` equals `observed_fault`.""" |
201 | - self.assertIsInstance(observed_fault, faults.LaunchpadFault) |
202 | - self.assertEqual(expected_fault.faultCode, observed_fault.faultCode) |
203 | - self.assertEqual( |
204 | - expected_fault.faultString, observed_fault.faultString) |
205 | - |
206 | def assertMirrorFailed(self, branch, failure_message, num_failures=1): |
207 | """Assert that `branch` failed to mirror. |
208 | |
209 | @@ -204,7 +197,7 @@ |
210 | # exist. |
211 | invalid_id = self.getUnusedBranchID() |
212 | fault = self.storage.startMirroring(invalid_id) |
213 | - self.assertFaultEqual(faults.NoBranchWithID(invalid_id), fault) |
214 | + self.assertEqual(faults.NoBranchWithID(invalid_id), fault) |
215 | |
216 | def test_mirrorFailed(self): |
217 | branch = self.factory.makeAnyBranch() |
218 | @@ -220,7 +213,7 @@ |
219 | branch_id = self.getUnusedBranchID() |
220 | failure_message = self.factory.getUniqueString() |
221 | fault = self.storage.mirrorFailed(branch_id, failure_message) |
222 | - self.assertFaultEqual(faults.NoBranchWithID(branch_id), fault) |
223 | + self.assertEqual(faults.NoBranchWithID(branch_id), fault) |
224 | |
225 | def test_mirrorComplete(self): |
226 | # mirrorComplete marks the branch as having been successfully |
227 | @@ -240,7 +233,7 @@ |
228 | branch_id = self.getUnusedBranchID() |
229 | fault = self.storage.mirrorComplete( |
230 | branch_id, self.factory.getUniqueString()) |
231 | - self.assertFaultEqual(faults.NoBranchWithID(branch_id), fault) |
232 | + self.assertEqual(faults.NoBranchWithID(branch_id), fault) |
233 | |
234 | def test_mirrorComplete_resets_failure_count(self): |
235 | # mirrorComplete marks the branch as successfully mirrored and removes |
236 | @@ -388,7 +381,7 @@ |
237 | stacked_branch = self.factory.makeAnyBranch() |
238 | url = self.factory.getUniqueURL() |
239 | fault = self.storage.setStackedOn(stacked_branch.id, url) |
240 | - self.assertFaultEqual(faults.NoSuchBranch(url), fault) |
241 | + self.assertEqual(faults.NoSuchBranch(url), fault) |
242 | |
243 | def test_setStackedOnNoBranchWithID(self): |
244 | # If setStackedOn is called for a branch that doesn't exist, it will |
245 | @@ -397,7 +390,7 @@ |
246 | branch_type=BranchType.MIRRORED) |
247 | branch_id = self.getUnusedBranchID() |
248 | fault = self.storage.setStackedOn(branch_id, stacked_on_branch.url) |
249 | - self.assertFaultEqual(faults.NoBranchWithID(branch_id), fault) |
250 | + self.assertEqual(faults.NoBranchWithID(branch_id), fault) |
251 | |
252 | |
253 | class AcquireBranchToPullTestsViaEndpoint(TestCaseWithFactory, |
254 | @@ -499,13 +492,6 @@ |
255 | self.factory = frontend.getLaunchpadObjectFactory() |
256 | self.branch_lookup = frontend.getBranchLookup() |
257 | |
258 | - def assertFaultEqual(self, expected_fault, observed_fault): |
259 | - """Assert that `expected_fault` equals `observed_fault`.""" |
260 | - self.assertIsInstance(observed_fault, faults.LaunchpadFault) |
261 | - self.assertEqual(expected_fault.faultCode, observed_fault.faultCode) |
262 | - self.assertEqual( |
263 | - expected_fault.faultString, observed_fault.faultString) |
264 | - |
265 | def test_createBranch(self): |
266 | # createBranch creates a branch with the supplied details and the |
267 | # caller as registrant. |
268 | @@ -527,7 +513,7 @@ |
269 | path = escape(u'invalid') |
270 | fault = self.branchfs.createBranch(requester.id, path) |
271 | login(ANONYMOUS) |
272 | - self.assertFaultEqual(faults.InvalidPath(path), fault) |
273 | + self.assertEqual(faults.InvalidPath(path), fault) |
274 | |
275 | def test_createBranch_junk(self): |
276 | # createBranch can create +junk branches. |
277 | @@ -565,7 +551,7 @@ |
278 | message = "Project 'no-such-product' does not exist." |
279 | fault = self.branchfs.createBranch( |
280 | owner.id, escape('/~%s/no-such-product/%s' % (owner.name, name))) |
281 | - self.assertFaultEqual(faults.NotFound(message), fault) |
282 | + self.assertEqual(faults.NotFound(message), fault) |
283 | |
284 | def test_createBranch_other_user(self): |
285 | # Creating a branch under another user's directory fails. |
286 | @@ -578,7 +564,7 @@ |
287 | fault = self.branchfs.createBranch( |
288 | creator.id, |
289 | escape('/~%s/%s/%s' % (other_person.name, product.name, name))) |
290 | - self.assertFaultEqual(faults.PermissionDenied(message), fault) |
291 | + self.assertEqual(faults.PermissionDenied(message), fault) |
292 | |
293 | def test_createBranch_bad_name(self): |
294 | # Creating a branch with an invalid name fails. |
295 | @@ -590,7 +576,7 @@ |
296 | fault = self.branchfs.createBranch( |
297 | owner.id, escape( |
298 | '/~%s/%s/%s' % (owner.name, product.name, invalid_name))) |
299 | - self.assertFaultEqual(faults.PermissionDenied(message), fault) |
300 | + self.assertEqual(faults.PermissionDenied(message), fault) |
301 | |
302 | def test_createBranch_unicode_name(self): |
303 | # Creating a branch with an invalid name fails. |
304 | @@ -603,7 +589,7 @@ |
305 | fault = self.branchfs.createBranch( |
306 | owner.id, escape( |
307 | '/~%s/%s/%s' % (owner.name, product.name, invalid_name))) |
308 | - self.assertFaultEqual( |
309 | + self.assertEqual( |
310 | faults.PermissionDenied(message), fault) |
311 | |
312 | def test_createBranch_bad_user(self): |
313 | @@ -614,7 +600,7 @@ |
314 | message = "User/team 'no-one' does not exist." |
315 | fault = self.branchfs.createBranch( |
316 | owner.id, escape('/~no-one/%s/%s' % (product.name, name))) |
317 | - self.assertFaultEqual(faults.NotFound(message), fault) |
318 | + self.assertEqual(faults.NotFound(message), fault) |
319 | |
320 | def test_createBranch_bad_user_bad_product(self): |
321 | # If both the user and the product are not found, then the missing |
322 | @@ -625,7 +611,7 @@ |
323 | message = "User/team 'no-one' does not exist." |
324 | fault = self.branchfs.createBranch( |
325 | owner.id, escape('/~no-one/no-product/%s' % (name,))) |
326 | - self.assertFaultEqual(faults.NotFound(message), fault) |
327 | + self.assertEqual(faults.NotFound(message), fault) |
328 | |
329 | def test_createBranch_not_branch(self): |
330 | # Trying to create a branch at a path that's not valid for branches |
331 | @@ -634,7 +620,7 @@ |
332 | path = escape('/~%s' % owner.name) |
333 | fault = self.branchfs.createBranch(owner.id, path) |
334 | message = "Cannot create branch at '%s'" % path |
335 | - self.assertFaultEqual(faults.PermissionDenied(message), fault) |
336 | + self.assertEqual(faults.PermissionDenied(message), fault) |
337 | |
338 | def test_createBranch_source_package(self): |
339 | # createBranch can take the path to a source package branch and create |
340 | @@ -671,7 +657,7 @@ |
341 | branch_name) |
342 | fault = self.branchfs.createBranch(owner.id, escape(unique_name)) |
343 | message = "No such distribution: 'ningnangnong'." |
344 | - self.assertFaultEqual(faults.NotFound(message), fault) |
345 | + self.assertEqual(faults.NotFound(message), fault) |
346 | |
347 | def test_createBranch_invalid_distroseries(self): |
348 | # If createBranch is called with the path to a non-existent |
349 | @@ -685,7 +671,7 @@ |
350 | branch_name) |
351 | fault = self.branchfs.createBranch(owner.id, escape(unique_name)) |
352 | message = "No such distribution series: 'ningnangnong'." |
353 | - self.assertFaultEqual(faults.NotFound(message), fault) |
354 | + self.assertEqual(faults.NotFound(message), fault) |
355 | |
356 | def test_createBranch_invalid_sourcepackagename(self): |
357 | # If createBranch is called with the path to an invalid source |
358 | @@ -698,7 +684,7 @@ |
359 | branch_name) |
360 | fault = self.branchfs.createBranch(owner.id, escape(unique_name)) |
361 | message = "No such source package: 'ningnangnong'." |
362 | - self.assertFaultEqual(faults.NotFound(message), fault) |
363 | + self.assertEqual(faults.NotFound(message), fault) |
364 | |
365 | def test_initialMirrorRequest(self): |
366 | # The default 'next_mirror_time' for a newly created hosted branch |
367 | @@ -727,21 +713,21 @@ |
368 | def assertCannotTranslate(self, requester, path): |
369 | """Assert that we cannot translate 'path'.""" |
370 | fault = self.branchfs.translatePath(requester.id, path) |
371 | - self.assertFaultEqual(faults.PathTranslationError(path), fault) |
372 | + self.assertEqual(faults.PathTranslationError(path), fault) |
373 | |
374 | def assertNotFound(self, requester, path): |
375 | """Assert that the given path cannot be found.""" |
376 | if requester not in [LAUNCHPAD_ANONYMOUS, LAUNCHPAD_SERVICES]: |
377 | requester = requester.id |
378 | fault = self.branchfs.translatePath(requester, path) |
379 | - self.assertFaultEqual(faults.PathTranslationError(path), fault) |
380 | + self.assertEqual(faults.PathTranslationError(path), fault) |
381 | |
382 | def assertPermissionDenied(self, requester, path): |
383 | """Assert that looking at the given path gives permission denied.""" |
384 | if requester not in [LAUNCHPAD_ANONYMOUS, LAUNCHPAD_SERVICES]: |
385 | requester = requester.id |
386 | fault = self.branchfs.translatePath(requester, path) |
387 | - self.assertFaultEqual(faults.PermissionDenied(), fault) |
388 | + self.assertEqual(faults.PermissionDenied(), fault) |
389 | |
390 | def _makeProductWithDevFocus(self, private=False): |
391 | """Make a stacking-enabled product with a development focus. |
392 | @@ -769,7 +755,7 @@ |
393 | requester = self.factory.makePerson() |
394 | path = escape(u'invalid') |
395 | fault = self.branchfs.translatePath(requester.id, path) |
396 | - self.assertFaultEqual(faults.InvalidPath(path), fault) |
397 | + self.assertEqual(faults.InvalidPath(path), fault) |
398 | |
399 | def test_translatePath_branch(self): |
400 | requester = self.factory.makePerson() |
401 | |
402 | === modified file 'lib/lp/codehosting/sshserver/accesslog.py' |
403 | --- lib/lp/codehosting/sshserver/accesslog.py 2010-03-19 10:43:51 +0000 |
404 | +++ lib/lp/codehosting/sshserver/accesslog.py 2010-04-10 13:58:26 +0000 |
405 | @@ -29,24 +29,10 @@ |
406 | |
407 | from canonical.config import config |
408 | from canonical.launchpad.scripts import WatchedFileHandler |
409 | +from lp.services.utils import synchronize |
410 | from lp.services.twistedsupport.loggingsupport import set_up_oops_reporting |
411 | |
412 | |
413 | -def synchronize(source, target, add, remove): |
414 | - """Update 'source' to match 'target' using 'add' and 'remove'. |
415 | - |
416 | - Changes the container 'source' so that it equals 'target', calling 'add' |
417 | - with any object in 'target' not in 'source' and 'remove' with any object |
418 | - not in 'target' but in 'source'. |
419 | - """ |
420 | - need_to_add = [obj for obj in target if obj not in source] |
421 | - need_to_remove = [obj for obj in source if obj not in target] |
422 | - for obj in need_to_add: |
423 | - add(obj) |
424 | - for obj in need_to_remove: |
425 | - remove(obj) |
426 | - |
427 | - |
428 | class LoggingManager: |
429 | """Class for managing codehosting logging.""" |
430 | |
431 | |
432 | === modified file 'lib/lp/codehosting/sshserver/auth.py' |
433 | --- lib/lp/codehosting/sshserver/auth.py 2009-06-25 04:06:00 +0000 |
434 | +++ lib/lp/codehosting/sshserver/auth.py 2010-04-10 13:58:26 +0000 |
435 | @@ -42,7 +42,7 @@ |
436 | from lp.codehosting.sshserver import accesslog |
437 | from lp.codehosting.sshserver.session import ( |
438 | launch_smart_server, PatchedSSHSession) |
439 | -from lp.codehosting.vfs.branchfsclient import trap_fault |
440 | +from lp.services.twistedsupport.xmlrpc import trap_fault |
441 | from canonical.config import config |
442 | from canonical.launchpad.xmlrpc import faults |
443 | |
444 | |
445 | === modified file 'lib/lp/codehosting/vfs/branchfs.py' |
446 | --- lib/lp/codehosting/vfs/branchfs.py 2010-03-23 03:35:13 +0000 |
447 | +++ lib/lp/codehosting/vfs/branchfs.py 2010-04-10 13:58:26 +0000 |
448 | @@ -83,16 +83,17 @@ |
449 | from zope.interface import implements, Interface |
450 | |
451 | from lp.codehosting.vfs.branchfsclient import ( |
452 | - BlockingProxy, BranchFileSystemClient, trap_fault) |
453 | + BlockingProxy, BranchFileSystemClient) |
454 | from lp.codehosting.vfs.transport import ( |
455 | AsyncVirtualServer, AsyncVirtualTransport, _MultiServer, |
456 | get_chrooted_transport, get_readonly_transport, TranslationError) |
457 | from canonical.config import config |
458 | +from canonical.launchpad.xmlrpc import faults |
459 | from lp.code.enums import BranchType |
460 | from lp.code.interfaces.branchlookup import IBranchLookup |
461 | from lp.code.interfaces.codehosting import ( |
462 | BRANCH_TRANSPORT, CONTROL_TRANSPORT, LAUNCHPAD_SERVICES) |
463 | -from canonical.launchpad.xmlrpc import faults |
464 | +from lp.services.twistedsupport.xmlrpc import trap_fault |
465 | |
466 | |
467 | class BadUrl(Exception): |
468 | |
469 | === modified file 'lib/lp/codehosting/vfs/branchfsclient.py' |
470 | --- lib/lp/codehosting/vfs/branchfsclient.py 2010-03-23 02:34:35 +0000 |
471 | +++ lib/lp/codehosting/vfs/branchfsclient.py 2010-04-10 13:58:26 +0000 |
472 | @@ -11,13 +11,11 @@ |
473 | 'BlockingProxy', |
474 | 'BranchFileSystemClient', |
475 | 'NotInCache', |
476 | - 'trap_fault', |
477 | ] |
478 | |
479 | import time |
480 | |
481 | from twisted.internet import defer |
482 | -from twisted.web.xmlrpc import Fault |
483 | |
484 | from lp.code.interfaces.codehosting import BRANCH_TRANSPORT |
485 | |
486 | @@ -136,19 +134,3 @@ |
487 | 'translatePath', self._user_id, path) |
488 | deferred.addCallback(self._addToCache, path) |
489 | return deferred |
490 | - |
491 | - |
492 | -def trap_fault(failure, *fault_classes): |
493 | - """Trap a fault, based on fault code. |
494 | - |
495 | - :param failure: A Twisted L{Failure}. |
496 | - :param *fault_codes: `LaunchpadFault` subclasses. |
497 | - :raise Failure: if 'failure' is not a Fault failure, or if the fault code |
498 | - does not match the given codes. |
499 | - :return: The Fault if it matches one of the codes. |
500 | - """ |
501 | - failure.trap(Fault) |
502 | - fault = failure.value |
503 | - if fault.faultCode in [cls.error_code for cls in fault_classes]: |
504 | - return fault |
505 | - raise failure |
506 | |
507 | === modified file 'lib/lp/codehosting/vfs/tests/test_branchfsclient.py' |
508 | --- lib/lp/codehosting/vfs/tests/test_branchfsclient.py 2010-03-23 02:34:35 +0000 |
509 | +++ lib/lp/codehosting/vfs/tests/test_branchfsclient.py 2010-04-10 13:58:26 +0000 |
510 | @@ -9,16 +9,13 @@ |
511 | |
512 | import unittest |
513 | |
514 | -from twisted.python.failure import Failure |
515 | from twisted.trial.unittest import TestCase |
516 | |
517 | from lp.codehosting.vfs.branchfsclient import ( |
518 | - BranchFileSystemClient, NotInCache, trap_fault) |
519 | + BranchFileSystemClient, NotInCache) |
520 | from lp.codehosting.inmemory import InMemoryFrontend, XMLRPCWrapper |
521 | from lp.code.interfaces.codehosting import BRANCH_TRANSPORT |
522 | from lp.testing import FakeTime |
523 | -from canonical.launchpad.xmlrpc.tests.test_faults import ( |
524 | - TestFaultOne, TestFaultTwo) |
525 | |
526 | |
527 | class TestBranchFileSystemClient(TestCase): |
528 | @@ -250,56 +247,6 @@ |
529 | [branch1.unique_name, branch2.unique_name], seen_branches) |
530 | |
531 | |
532 | -class TestTrapFault(TestCase): |
533 | - """Tests for `trap_fault`.""" |
534 | - |
535 | - def makeFailure(self, exception_factory, *args, **kwargs): |
536 | - """Make a `Failure` from the given exception factory.""" |
537 | - try: |
538 | - raise exception_factory(*args, **kwargs) |
539 | - except: |
540 | - return Failure() |
541 | - |
542 | - def assertRaisesFailure(self, failure, function, *args, **kwargs): |
543 | - try: |
544 | - function(*args, **kwargs) |
545 | - except Failure, raised_failure: |
546 | - self.assertEqual(failure, raised_failure) |
547 | - |
548 | - def test_raises_non_faults(self): |
549 | - # trap_fault re-raises any failures it gets that aren't faults. |
550 | - failure = self.makeFailure(RuntimeError, 'example failure') |
551 | - self.assertRaisesFailure(failure, trap_fault, failure, TestFaultOne) |
552 | - |
553 | - def test_raises_faults_with_wrong_code(self): |
554 | - # trap_fault re-raises any failures it gets that are faults but have |
555 | - # the wrong fault code. |
556 | - failure = self.makeFailure(TestFaultOne) |
557 | - self.assertRaisesFailure(failure, trap_fault, failure, TestFaultTwo) |
558 | - |
559 | - def test_raises_faults_if_no_codes_given(self): |
560 | - # If trap_fault is not given any fault codes, it re-raises the fault |
561 | - # failure. |
562 | - failure = self.makeFailure(TestFaultOne) |
563 | - self.assertRaisesFailure(failure, trap_fault, failure) |
564 | - |
565 | - def test_returns_fault_if_code_matches(self): |
566 | - # trap_fault returns the Fault inside the Failure if the fault code |
567 | - # matches what's given. |
568 | - failure = self.makeFailure(TestFaultOne) |
569 | - fault = trap_fault(failure, TestFaultOne) |
570 | - self.assertEqual(TestFaultOne.error_code, fault.faultCode) |
571 | - self.assertEqual(TestFaultOne.msg_template, fault.faultString) |
572 | - |
573 | - def test_returns_fault_if_code_matches_one_of_set(self): |
574 | - # trap_fault returns the Fault inside the Failure if the fault code |
575 | - # matches even one of the given fault codes. |
576 | - failure = self.makeFailure(TestFaultOne) |
577 | - fault = trap_fault(failure, TestFaultOne, TestFaultTwo) |
578 | - self.assertEqual(TestFaultOne.error_code, fault.faultCode) |
579 | - self.assertEqual(TestFaultOne.msg_template, fault.faultString) |
580 | - |
581 | - |
582 | def test_suite(): |
583 | return unittest.TestLoader().loadTestsFromName(__name__) |
584 | |
585 | |
586 | === added file 'lib/lp/services/twistedsupport/tests/test_xmlrpc.py' |
587 | --- lib/lp/services/twistedsupport/tests/test_xmlrpc.py 1970-01-01 00:00:00 +0000 |
588 | +++ lib/lp/services/twistedsupport/tests/test_xmlrpc.py 2010-04-10 13:58:26 +0000 |
589 | @@ -0,0 +1,90 @@ |
590 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
591 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
592 | + |
593 | +"""Tests for Twisted XML-RPC support.""" |
594 | + |
595 | +__metaclass__ = type |
596 | + |
597 | +import unittest |
598 | + |
599 | +from twisted.python.failure import Failure |
600 | +from twisted.trial.unittest import TestCase |
601 | + |
602 | +from lp.services.xmlrpc import LaunchpadFault |
603 | +from lp.services.twistedsupport.xmlrpc import trap_fault |
604 | + |
605 | + |
606 | +class TestFaultOne(LaunchpadFault): |
607 | + """An arbitrary subclass of `LaunchpadFault`. |
608 | + |
609 | + This class and `TestFaultTwo` are a pair of distinct `LaunchpadFault` |
610 | + subclasses to use in tests. |
611 | + """ |
612 | + |
613 | + error_code = 1001 |
614 | + msg_template = "Fault one." |
615 | + |
616 | + |
617 | +class TestFaultTwo(LaunchpadFault): |
618 | + """Another arbitrary subclass of `LaunchpadFault`. |
619 | + |
620 | + This class and `TestFaultOne` are a pair of distinct `LaunchpadFault` |
621 | + subclasses to use in tests. |
622 | + """ |
623 | + |
624 | + error_code = 1002 |
625 | + msg_template = "Fault two." |
626 | + |
627 | + |
628 | +class TestTrapFault(TestCase): |
629 | + """Tests for `trap_fault`.""" |
630 | + |
631 | + def makeFailure(self, exception_factory, *args, **kwargs): |
632 | + """Make a `Failure` from the given exception factory.""" |
633 | + try: |
634 | + raise exception_factory(*args, **kwargs) |
635 | + except: |
636 | + return Failure() |
637 | + |
638 | + def assertRaisesFailure(self, failure, function, *args, **kwargs): |
639 | + try: |
640 | + function(*args, **kwargs) |
641 | + except Failure, raised_failure: |
642 | + self.assertEqual(failure, raised_failure) |
643 | + |
644 | + def test_raises_non_faults(self): |
645 | + # trap_fault re-raises any failures it gets that aren't faults. |
646 | + failure = self.makeFailure(RuntimeError, 'example failure') |
647 | + self.assertRaisesFailure(failure, trap_fault, failure, TestFaultOne) |
648 | + |
649 | + def test_raises_faults_with_wrong_code(self): |
650 | + # trap_fault re-raises any failures it gets that are faults but have |
651 | + # the wrong fault code. |
652 | + failure = self.makeFailure(TestFaultOne) |
653 | + self.assertRaisesFailure(failure, trap_fault, failure, TestFaultTwo) |
654 | + |
655 | + def test_raises_faults_if_no_codes_given(self): |
656 | + # If trap_fault is not given any fault codes, it re-raises the fault |
657 | + # failure. |
658 | + failure = self.makeFailure(TestFaultOne) |
659 | + self.assertRaisesFailure(failure, trap_fault, failure) |
660 | + |
661 | + def test_returns_fault_if_code_matches(self): |
662 | + # trap_fault returns the Fault inside the Failure if the fault code |
663 | + # matches what's given. |
664 | + failure = self.makeFailure(TestFaultOne) |
665 | + fault = trap_fault(failure, TestFaultOne) |
666 | + self.assertEqual(TestFaultOne.error_code, fault.faultCode) |
667 | + self.assertEqual(TestFaultOne.msg_template, fault.faultString) |
668 | + |
669 | + def test_returns_fault_if_code_matches_one_of_set(self): |
670 | + # trap_fault returns the Fault inside the Failure if the fault code |
671 | + # matches even one of the given fault codes. |
672 | + failure = self.makeFailure(TestFaultOne) |
673 | + fault = trap_fault(failure, TestFaultOne, TestFaultTwo) |
674 | + self.assertEqual(TestFaultOne.error_code, fault.faultCode) |
675 | + self.assertEqual(TestFaultOne.msg_template, fault.faultString) |
676 | + |
677 | + |
678 | +def test_suite(): |
679 | + return unittest.TestLoader().loadTestsFromName(__name__) |
680 | |
681 | === added file 'lib/lp/services/twistedsupport/xmlrpc.py' |
682 | --- lib/lp/services/twistedsupport/xmlrpc.py 1970-01-01 00:00:00 +0000 |
683 | +++ lib/lp/services/twistedsupport/xmlrpc.py 2010-04-10 13:58:26 +0000 |
684 | @@ -0,0 +1,27 @@ |
685 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
686 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
687 | + |
688 | +"""Support for XML-RPC stuff with Twisted.""" |
689 | + |
690 | +__metaclass__ = type |
691 | +__all__ = [ |
692 | + 'trap_fault', |
693 | + ] |
694 | + |
695 | +from twisted.web.xmlrpc import Fault |
696 | + |
697 | + |
698 | +def trap_fault(failure, *fault_classes): |
699 | + """Trap a fault, based on fault code. |
700 | + |
701 | + :param failure: A Twisted L{Failure}. |
702 | + :param *fault_codes: `LaunchpadFault` subclasses. |
703 | + :raise Failure: if 'failure' is not a Fault failure, or if the fault code |
704 | + does not match the given codes. |
705 | + :return: The Fault if it matches one of the codes. |
706 | + """ |
707 | + failure.trap(Fault) |
708 | + fault = failure.value |
709 | + if fault.faultCode in [cls.error_code for cls in fault_classes]: |
710 | + return fault |
711 | + raise failure |
712 | |
713 | === modified file 'lib/lp/services/utils.py' |
714 | --- lib/lp/services/utils.py 2010-03-18 22:53:50 +0000 |
715 | +++ lib/lp/services/utils.py 2010-04-10 13:58:26 +0000 |
716 | @@ -1,12 +1,16 @@ |
717 | # Copyright 2009 Canonical Ltd. This software is licensed under the |
718 | # GNU Affero General Public License version 3 (see the file LICENSE). |
719 | |
720 | +"""Generic Python utilities. |
721 | |
722 | -"""Implementations of the XML-RPC APIs for codehosting.""" |
723 | +Functions, lists and so forth. Nothing here that does system calls or network |
724 | +stuff. |
725 | +""" |
726 | |
727 | __metaclass__ = type |
728 | __all__ = [ |
729 | 'iter_split', |
730 | + 'synchronize', |
731 | 'text_delta', |
732 | 'value_string', |
733 | ] |
734 | @@ -35,6 +39,21 @@ |
735 | yield splitter.join(tokens[:i]), splitter.join(tokens[i:]) |
736 | |
737 | |
738 | +def synchronize(source, target, add, remove): |
739 | + """Update 'source' to match 'target' using 'add' and 'remove'. |
740 | + |
741 | + Changes the container 'source' so that it equals 'target', calling 'add' |
742 | + with any object in 'target' not in 'source' and 'remove' with any object |
743 | + not in 'target' but in 'source'. |
744 | + """ |
745 | + need_to_add = [obj for obj in target if obj not in source] |
746 | + need_to_remove = [obj for obj in source if obj not in target] |
747 | + for obj in need_to_add: |
748 | + add(obj) |
749 | + for obj in need_to_remove: |
750 | + remove(obj) |
751 | + |
752 | + |
753 | def value_string(item): |
754 | """Return a unicode string representing value. |
755 | |
756 | |
757 | === added file 'lib/lp/services/xmlrpc.py' |
758 | --- lib/lp/services/xmlrpc.py 1970-01-01 00:00:00 +0000 |
759 | +++ lib/lp/services/xmlrpc.py 2010-04-10 13:58:26 +0000 |
760 | @@ -0,0 +1,40 @@ |
761 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
762 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
763 | + |
764 | +"""Generic code for XML-RPC in Launchpad.""" |
765 | + |
766 | +__metaclass__ = type |
767 | +__all__ = [ |
768 | + 'LaunchpadFault', |
769 | + ] |
770 | + |
771 | +import xmlrpclib |
772 | + |
773 | + |
774 | +class LaunchpadFault(xmlrpclib.Fault): |
775 | + """Base class for a Launchpad XMLRPC fault. |
776 | + |
777 | + Subclasses should define a unique error_code and a msg_template, |
778 | + which will be interpolated with the given keyword arguments. |
779 | + """ |
780 | + |
781 | + error_code = None |
782 | + msg_template = None |
783 | + |
784 | + def __init__(self, **kw): |
785 | + assert self.error_code is not None, ( |
786 | + "Subclasses must define error_code.") |
787 | + assert self.msg_template is not None, ( |
788 | + "Subclasses must define msg_template.") |
789 | + msg = self.msg_template % kw |
790 | + xmlrpclib.Fault.__init__(self, self.error_code, msg) |
791 | + |
792 | + def __eq__(self, other): |
793 | + if not isinstance(other, LaunchpadFault): |
794 | + return False |
795 | + return ( |
796 | + self.faultCode == other.faultCode |
797 | + and self.faultString == other.faultString) |
798 | + |
799 | + def __ne__(self, other): |
800 | + return not (self == other) |