Merge lp:~oubiwann/txaws/486365-get-bucket into lp:txaws

Proposed by Duncan McGreggor
Status: Merged
Merge reported by: Duncan McGreggor
Merged at revision: not available
Proposed branch: lp:~oubiwann/txaws/486365-get-bucket
Merge into: lp:txaws
Prerequisite: lp:~oubiwann/txaws/486363-no-content-fix
Diff against target: 2773 lines (+1302/-458)
29 files modified
LICENSE (+10/-5)
README (+6/-0)
bin/txaws-create-bucket (+42/-0)
bin/txaws-delete-bucket (+42/-0)
bin/txaws-delete-object (+46/-0)
bin/txaws-get-bucket (+46/-0)
bin/txaws-get-object (+46/-0)
bin/txaws-head-object (+47/-0)
bin/txaws-list-buckets (+43/-0)
bin/txaws-put-object (+56/-0)
txaws/client/base.py (+41/-0)
txaws/client/tests/test_client.py (+41/-2)
txaws/ec2/client.py (+70/-85)
txaws/ec2/exception.py (+4/-108)
txaws/ec2/tests/test_client.py (+132/-87)
txaws/ec2/tests/test_exception.py (+2/-129)
txaws/exception.py (+113/-0)
txaws/meta.py (+10/-0)
txaws/s3/client.py (+68/-7)
txaws/s3/exception.py (+21/-0)
txaws/s3/model.py (+38/-3)
txaws/s3/tests/test_client.py (+47/-2)
txaws/s3/tests/test_exception.py (+62/-0)
txaws/script.py (+42/-0)
txaws/service.py (+12/-9)
txaws/testing/payload.py (+64/-19)
txaws/tests/test_exception.py (+114/-0)
txaws/tests/test_service.py (+7/-2)
txaws/util.py (+30/-0)
To merge this branch: bzr merge lp:~oubiwann/txaws/486365-get-bucket
Reviewer Review Type Date Requested Status
Robert Collins Approve
Review via email: mp+15343@code.launchpad.net

This proposal supersedes a proposal from 2009-11-23.

To post a comment you must log in.
Revision history for this message
Duncan McGreggor (oubiwann) wrote : Posted in a previous version of this proposal

This branch adds support for getting a list of objects in a bucket (it includes a script for this as well).

Depends on lp:~oubiwann/txaws/486363-no-content-fix

Revision history for this message
Duncan McGreggor (oubiwann) wrote : Posted in a previous version of this proposal

Landscape trunk has been tested against this branch.

Revision history for this message
Robert Collins (lifeless) wrote :

I've reviewed what I see on this web page: https://code.edge.launchpad.net/~oubiwann/txaws/486365-get-bucket/+merge/15343

I don't know if that is the whole branch or just an incremental bit; I think it might be the lot.

2024 +author = "txAWS Deelopers"

There is a bunch of duplication in the example scripts. I'd like to see that removed. Perhaps:
 - give them a if __name__ == guard
 - move the error/return etc callback support into script.py

Lastly, the default options in util/script.py include a bucket - is that relevant for all aws services? If not, lets factor that into two layers - truely global options and options for a given service.
e.g
 options = all_options()
 s3_options(options)

review: Approve
Revision history for this message
Duncan McGreggor (oubiwann) wrote :

> I've reviewed what I see on this web page:
> https://code.edge.launchpad.net/~oubiwann/txaws/486365-get-bucket/+merge/15343
>
> I don't know if that is the whole branch or just an incremental bit; I think
> it might be the lot.
>
> 2024 +author = "txAWS Deelopers"

Fixed -- thanks!

> There is a bunch of duplication in the example scripts. I'd like to see that
> removed. Perhaps:
> - give them a if __name__ == guard
> - move the error/return etc callback support into script.py

I responded to this in the scripts merge proposal:
  https://code.launchpad.net/~oubiwann/txaws/484858-s3-scripts/+merge/15340

> Lastly, the default options in util/script.py include a bucket - is that
> relevant for all aws services? If not, lets factor that into two layers -
> truely global options and options for a given service.
> e.g
> options = all_options()
> s3_options(options)

Yup, this is definitely planned for. However, I wanted to wait until there were some non-s3 script, before splitting out the code. I'll add a note to the ec2 scripts ticket (bug #484857).

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'LICENSE'
2--- LICENSE 2008-07-06 22:51:54 +0000
3+++ LICENSE 2009-11-28 01:15:24 +0000
4@@ -1,3 +1,8 @@
5+Copyright (C) 2008 Tristan Seligmann <mithrandi@mithrandi.net>
6+Copyright (C) 2009 Robert Collins <robertc@robertcollins.net>
7+Copyright (C) 2009 Canonical Ltd
8+Copyright (C) 2009 Duncan McGreggor <oubiwann@adytum.us>
9+
10 Permission is hereby granted, free of charge, to any person obtaining
11 a copy of this software and associated documentation files (the
12 "Software"), to deal in the Software without restriction, including
13@@ -11,8 +16,8 @@
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
25+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
26+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
28=== modified file 'README'
29--- README 2009-08-19 20:55:49 +0000
30+++ README 2009-11-28 01:15:24 +0000
31@@ -14,3 +14,9 @@
32 * The txaws python package. (No installer at the moment)
33
34 * bin/aws-status, a GUI status program for aws resources.
35+
36+License
37+-------
38+
39+txAWS is open source software, MIT License. See the LICENSE file for more
40+details.
41
42=== added file 'bin/txaws-create-bucket'
43--- bin/txaws-create-bucket 1970-01-01 00:00:00 +0000
44+++ bin/txaws-create-bucket 2009-11-28 01:15:24 +0000
45@@ -0,0 +1,42 @@
46+#!/usr/bin/env python
47+"""
48+%prog [options]
49+"""
50+
51+import sys
52+
53+from txaws.credentials import AWSCredentials
54+from txaws.script import parse_options
55+from txaws.service import AWSServiceRegion
56+from txaws.util import reactor
57+
58+
59+def printResults(results):
60+ return 0
61+
62+
63+def printError(error):
64+ print error.value
65+ return 1
66+
67+
68+def finish(return_code):
69+ reactor.stop(exitStatus=return_code)
70+
71+
72+options, args = parse_options(__doc__.strip())
73+if options.bucket is None:
74+ print "Error Message: A bucket name is required."
75+ sys.exit(1)
76+creds = AWSCredentials(options.access_key, options.secret_key)
77+region = AWSServiceRegion(
78+ creds=creds, region=options.region, s3_endpoint=options.url)
79+client = region.get_s3_client()
80+
81+d = client.create_bucket(options.bucket)
82+d.addCallback(printResults)
83+d.addErrback(printError)
84+d.addCallback(finish)
85+# We use a custom reactor so that we can return the exit status from
86+# reactor.run().
87+sys.exit(reactor.run())
88
89=== added file 'bin/txaws-delete-bucket'
90--- bin/txaws-delete-bucket 1970-01-01 00:00:00 +0000
91+++ bin/txaws-delete-bucket 2009-11-28 01:15:24 +0000
92@@ -0,0 +1,42 @@
93+#!/usr/bin/env python
94+"""
95+%prog [options]
96+"""
97+
98+import sys
99+
100+from txaws.credentials import AWSCredentials
101+from txaws.script import parse_options
102+from txaws.service import AWSServiceRegion
103+from txaws.util import reactor
104+
105+
106+def printResults(results):
107+ return 0
108+
109+
110+def printError(error):
111+ print error.value
112+ return 1
113+
114+
115+def finish(return_code):
116+ reactor.stop(exitStatus=return_code)
117+
118+
119+options, args = parse_options(__doc__.strip())
120+if options.bucket is None:
121+ print "Error Message: A bucket name is required."
122+ sys.exit(1)
123+creds = AWSCredentials(options.access_key, options.secret_key)
124+region = AWSServiceRegion(
125+ creds=creds, region=options.region, s3_endpoint=options.url)
126+client = region.get_s3_client()
127+
128+d = client.delete_bucket(options.bucket)
129+d.addCallback(printResults)
130+d.addErrback(printError)
131+d.addCallback(finish)
132+# We use a custom reactor so that we can return the exit status from
133+# reactor.run().
134+sys.exit(reactor.run())
135
136=== added file 'bin/txaws-delete-object'
137--- bin/txaws-delete-object 1970-01-01 00:00:00 +0000
138+++ bin/txaws-delete-object 2009-11-28 01:15:24 +0000
139@@ -0,0 +1,46 @@
140+#!/usr/bin/env python
141+"""
142+%prog [options]
143+"""
144+
145+import sys
146+
147+from txaws.credentials import AWSCredentials
148+from txaws.script import parse_options
149+from txaws.service import AWSServiceRegion
150+from txaws.util import reactor
151+
152+
153+def printResults(results):
154+ print results
155+ return 0
156+
157+
158+def printError(error):
159+ print error.value
160+ return 1
161+
162+
163+def finish(return_code):
164+ reactor.stop(exitStatus=return_code)
165+
166+
167+options, args = parse_options(__doc__.strip())
168+if options.bucket is None:
169+ print "Error Message: A bucket name is required."
170+ sys.exit(1)
171+if options.object_name is None:
172+ print "Error Message: An object name is required."
173+ sys.exit(1)
174+creds = AWSCredentials(options.access_key, options.secret_key)
175+region = AWSServiceRegion(
176+ creds=creds, region=options.region, s3_endpoint=options.url)
177+client = region.get_s3_client()
178+
179+d = client.delete_object(options.bucket, options.object_name)
180+d.addCallback(printResults)
181+d.addErrback(printError)
182+d.addCallback(finish)
183+# We use a custom reactor so that we can return the exit status from
184+# reactor.run().
185+sys.exit(reactor.run())
186
187=== added file 'bin/txaws-get-bucket'
188--- bin/txaws-get-bucket 1970-01-01 00:00:00 +0000
189+++ bin/txaws-get-bucket 2009-11-28 01:15:24 +0000
190@@ -0,0 +1,46 @@
191+#!/usr/bin/env python
192+"""
193+%prog [options]
194+"""
195+
196+import sys
197+
198+from txaws.credentials import AWSCredentials
199+from txaws.script import parse_options
200+from txaws.service import AWSServiceRegion
201+from txaws.util import reactor
202+
203+
204+def printResults(listing, bucket):
205+ print "Contents of '%s' bucket:" % bucket
206+ for item in listing.contents:
207+ print "\t%s (last modified on %s)" % (item.key, item.modification_date)
208+ print "Total items: %s\n" % len(listing.contents)
209+ return 0
210+
211+
212+def printError(error):
213+ print error.value
214+ return 1
215+
216+
217+def finish(return_code):
218+ reactor.stop(exitStatus=return_code)
219+
220+
221+options, args = parse_options(__doc__.strip())
222+if options.bucket is None:
223+ print "Error Message: A bucket name is required."
224+ sys.exit(1)
225+creds = AWSCredentials(options.access_key, options.secret_key)
226+region = AWSServiceRegion(
227+ creds=creds, region=options.region, s3_endpoint=options.url)
228+client = region.get_s3_client()
229+
230+d = client.get_bucket(options.bucket)
231+d.addCallback(printResults, options.bucket)
232+d.addErrback(printError)
233+d.addCallback(finish)
234+# We use a custom reactor so that we can return the exit status from
235+# reactor.run().
236+sys.exit(reactor.run())
237
238=== added file 'bin/txaws-get-object'
239--- bin/txaws-get-object 1970-01-01 00:00:00 +0000
240+++ bin/txaws-get-object 2009-11-28 01:15:24 +0000
241@@ -0,0 +1,46 @@
242+#!/usr/bin/env python
243+"""
244+%prog [options]
245+"""
246+
247+import sys
248+
249+from txaws.credentials import AWSCredentials
250+from txaws.script import parse_options
251+from txaws.service import AWSServiceRegion
252+from txaws.util import reactor
253+
254+
255+def printResults(results):
256+ print results
257+ return 0
258+
259+
260+def printError(error):
261+ print error.value
262+ return 1
263+
264+
265+def finish(return_code):
266+ reactor.stop(exitStatus=return_code)
267+
268+
269+options, args = parse_options(__doc__.strip())
270+if options.bucket is None:
271+ print "Error Message: A bucket name is required."
272+ sys.exit(1)
273+if options.object_name is None:
274+ print "Error Message: An object name is required."
275+ sys.exit(1)
276+creds = AWSCredentials(options.access_key, options.secret_key)
277+region = AWSServiceRegion(
278+ creds=creds, region=options.region, s3_endpoint=options.url)
279+client = region.get_s3_client()
280+
281+d = client.get_object(options.bucket, options.object_name)
282+d.addCallback(printResults)
283+d.addErrback(printError)
284+d.addCallback(finish)
285+# We use a custom reactor so that we can return the exit status from
286+# reactor.run().
287+sys.exit(reactor.run())
288
289=== added file 'bin/txaws-head-object'
290--- bin/txaws-head-object 1970-01-01 00:00:00 +0000
291+++ bin/txaws-head-object 2009-11-28 01:15:24 +0000
292@@ -0,0 +1,47 @@
293+#!/usr/bin/env python
294+"""
295+%prog [options]
296+"""
297+
298+import sys
299+from pprint import pprint
300+
301+from txaws.credentials import AWSCredentials
302+from txaws.script import parse_options
303+from txaws.service import AWSServiceRegion
304+from txaws.util import reactor
305+
306+
307+def printResults(results):
308+ pprint(results)
309+ return 0
310+
311+
312+def printError(error):
313+ print error.value
314+ return 1
315+
316+
317+def finish(return_code):
318+ reactor.stop(exitStatus=return_code)
319+
320+
321+options, args = parse_options(__doc__.strip())
322+if options.bucket is None:
323+ print "Error Message: A bucket name is required."
324+ sys.exit(1)
325+if options.object_name is None:
326+ print "Error Message: An object name is required."
327+ sys.exit(1)
328+creds = AWSCredentials(options.access_key, options.secret_key)
329+region = AWSServiceRegion(
330+ creds=creds, region=options.region, s3_endpoint=options.url)
331+client = region.get_s3_client()
332+
333+d = client.head_object(options.bucket, options.object_name)
334+d.addCallback(printResults)
335+d.addErrback(printError)
336+d.addCallback(finish)
337+# We use a custom reactor so that we can return the exit status from
338+# reactor.run().
339+sys.exit(reactor.run())
340
341=== added file 'bin/txaws-list-buckets'
342--- bin/txaws-list-buckets 1970-01-01 00:00:00 +0000
343+++ bin/txaws-list-buckets 2009-11-28 01:15:24 +0000
344@@ -0,0 +1,43 @@
345+#!/usr/bin/env python
346+"""
347+%prog [options]
348+"""
349+
350+import sys
351+
352+from txaws.credentials import AWSCredentials
353+from txaws.script import parse_options
354+from txaws.service import AWSServiceRegion
355+from txaws.util import reactor
356+
357+
358+def printResults(results):
359+ print "\nBuckets:"
360+ for bucket in results:
361+ print "\t%s (created on %s)" % (bucket.name, bucket.creation_date)
362+ print "Total buckets: %s\n" % len(list(results))
363+ return 0
364+
365+
366+def printError(error):
367+ print error.value
368+ return 1
369+
370+
371+def finish(return_code):
372+ reactor.stop(exitStatus=return_code)
373+
374+
375+options, args = parse_options(__doc__.strip())
376+creds = AWSCredentials(options.access_key, options.secret_key)
377+region = AWSServiceRegion(
378+ creds=creds, region=options.region, s3_endpoint=options.url)
379+client = region.get_s3_client()
380+
381+d = client.list_buckets()
382+d.addCallback(printResults)
383+d.addErrback(printError)
384+d.addCallback(finish)
385+# We use a custom reactor so that we can return the exit status from
386+# reactor.run().
387+sys.exit(reactor.run())
388
389=== added file 'bin/txaws-put-object'
390--- bin/txaws-put-object 1970-01-01 00:00:00 +0000
391+++ bin/txaws-put-object 2009-11-28 01:15:24 +0000
392@@ -0,0 +1,56 @@
393+#!/usr/bin/env python
394+"""
395+%prog [options]
396+"""
397+
398+import os
399+import sys
400+
401+from txaws.credentials import AWSCredentials
402+from txaws.script import parse_options
403+from txaws.service import AWSServiceRegion
404+from txaws.util import reactor
405+
406+
407+def printResults(results):
408+ return 0
409+
410+
411+def printError(error):
412+ print error.value
413+ return 1
414+
415+
416+def finish(return_code):
417+ reactor.stop(exitStatus=return_code)
418+
419+
420+options, args = parse_options(__doc__.strip())
421+if options.bucket is None:
422+ print "Error Message: A bucket name is required."
423+ sys.exit(1)
424+filename = options.object_filename
425+if filename:
426+ options.object_name = os.path.basename(filename)
427+ try:
428+ options.object_data = open(filename).read()
429+ except Exception, error:
430+ print error
431+ sys.exit(1)
432+elif options.object_name is None:
433+ print "Error Message: An object name is required."
434+ sys.exit(1)
435+creds = AWSCredentials(options.access_key, options.secret_key)
436+region = AWSServiceRegion(
437+ creds=creds, region=options.region, s3_endpoint=options.url)
438+client = region.get_s3_client()
439+
440+d = client.put_object(
441+ options.bucket, options.object_name, options.object_data,
442+ options.content_type)
443+d.addCallback(printResults)
444+d.addErrback(printError)
445+d.addCallback(finish)
446+# We use a custom reactor so that we can return the exit status from
447+# reactor.run().
448+sys.exit(reactor.run())
449
450=== modified file 'txaws/client/base.py'
451--- txaws/client/base.py 2009-11-28 01:15:24 +0000
452+++ txaws/client/base.py 2009-11-28 01:15:24 +0000
453@@ -1,11 +1,52 @@
454+from xml.parsers.expat import ExpatError
455+
456 from twisted.internet import reactor, ssl
457+from twisted.web import http
458 from twisted.web.client import HTTPClientFactory
459+from twisted.web.error import Error as TwistedWebError
460
461 from txaws.util import parse
462 from txaws.credentials import AWSCredentials
463+from txaws.exception import AWSResponseParseError
464 from txaws.service import AWSServiceEndpoint
465
466
467+def error_wrapper(error, errorClass):
468+ """
469+ We want to see all error messages from cloud services. Amazon's EC2 says
470+ that their errors are accompanied either by a 400-series or 500-series HTTP
471+ response code. As such, the first thing we want to do is check to see if
472+ the error is in that range. If it is, we then need to see if the error
473+ message is an EC2 one.
474+
475+ In the event that an error is not a Twisted web error nor an EC2 one, the
476+ original exception is raised.
477+ """
478+ http_status = 0
479+ if error.check(TwistedWebError):
480+ xml_payload = error.value.response
481+ if error.value.status:
482+ http_status = int(error.value.status)
483+ else:
484+ error.raiseException()
485+ if http_status >= 400:
486+ if not xml_payload:
487+ error.raiseException()
488+ try:
489+ fallback_error = errorClass(
490+ xml_payload, error.value.status, error.value.message,
491+ error.value.response)
492+ except (ExpatError, AWSResponseParseError):
493+ error_message = http.RESPONSES.get(http_status)
494+ fallback_error = TwistedWebError(
495+ http_status, error_message, error.value.response)
496+ raise fallback_error
497+ elif 200 <= http_status < 300:
498+ return str(error.value)
499+ else:
500+ error.raiseException()
501+
502+
503 class BaseClient(object):
504 """Create an AWS client.
505
506
507=== modified file 'txaws/client/tests/test_client.py'
508--- txaws/client/tests/test_client.py 2009-11-28 01:15:24 +0000
509+++ txaws/client/tests/test_client.py 2009-11-28 01:15:24 +0000
510@@ -1,16 +1,55 @@
511 import os
512
513 from twisted.internet import reactor
514+from twisted.internet.error import ConnectionRefusedError
515 from twisted.protocols.policies import WrappingFactory
516 from twisted.python import log
517 from twisted.python.filepath import FilePath
518+from twisted.python.failure import Failure
519+from twisted.web import server, static
520 from twisted.web.client import HTTPClientFactory
521-from twisted.web import server, static
522+from twisted.web.error import Error as TwistedWebError
523
524-from txaws.client.base import BaseClient, BaseQuery
525+from txaws.client.base import BaseClient, BaseQuery, error_wrapper
526 from txaws.testing.base import TXAWSTestCase
527
528
529+class ErrorWrapperTestCase(TXAWSTestCase):
530+
531+ def test_204_no_content(self):
532+ failure = Failure(TwistedWebError(204, "No content"))
533+ wrapped = error_wrapper(failure, None)
534+ self.assertEquals(wrapped, "204 No content")
535+
536+ def test_302_found(self):
537+ # XXX I'm not sure we want to raise for 300s...
538+ failure = Failure(TwistedWebError(302, "found"))
539+ error = self.assertRaises(
540+ Exception, error_wrapper, failure, None)
541+ self.assertEquals(failure.type, type(error))
542+ self.assertTrue(isinstance(error, TwistedWebError))
543+ self.assertEquals(str(error), "302 found")
544+
545+ def test_500(self):
546+ failure = Failure(TwistedWebError(500, "internal error"))
547+ error = self.assertRaises(
548+ Exception, error_wrapper, failure, None)
549+ self.assertTrue(isinstance(error, TwistedWebError))
550+ self.assertEquals(str(error), "500 internal error")
551+
552+ def test_timeout_error(self):
553+ failure = Failure(Exception("timeout"))
554+ error = self.assertRaises(Exception, error_wrapper, failure, None)
555+ self.assertTrue(isinstance(error, Exception))
556+ self.assertEquals(error.message, "timeout")
557+
558+ def test_connection_error(self):
559+ failure = Failure(ConnectionRefusedError("timeout"))
560+ error = self.assertRaises(
561+ Exception, error_wrapper, failure, ConnectionRefusedError)
562+ self.assertTrue(isinstance(error, ConnectionRefusedError))
563+
564+
565 class BaseClientTestCase(TXAWSTestCase):
566
567 def test_creation(self):
568
569=== modified file 'txaws/ec2/client.py'
570--- txaws/ec2/client.py 2009-11-28 01:15:24 +0000
571+++ txaws/ec2/client.py 2009-11-28 01:15:24 +0000
572@@ -8,16 +8,11 @@
573 from datetime import datetime
574 from urllib import quote
575 from base64 import b64encode
576-from xml.parsers.expat import ExpatError
577-
578-from twisted.web import http
579-from twisted.web.error import Error as TwistedWebError
580
581 from txaws import version
582-from txaws.client.base import BaseClient, BaseQuery
583+from txaws.client.base import BaseClient, BaseQuery, error_wrapper
584 from txaws.ec2 import model
585 from txaws.ec2.exception import EC2Error
586-from txaws.exception import AWSResponseParseError
587 from txaws.util import iso8601time, XML
588
589
590@@ -25,34 +20,7 @@
591
592
593 def ec2_error_wrapper(error):
594- """
595- We want to see all error messages from cloud services. Amazon's EC2 says
596- that their errors are accompanied either by a 400-series or 500-series HTTP
597- response code. As such, the first thing we want to do is check to see if
598- the error is in that range. If it is, we then need to see if the error
599- message is an EC2 one.
600-
601- In the event that an error is not a Twisted web error nor an EC2 one, the
602- original exception is raised.
603- """
604- http_status = 0
605- if error.check(TwistedWebError):
606- xml_payload = error.value.response
607- if error.value.status:
608- http_status = int(error.value.status)
609- else:
610- error.raiseException()
611- if http_status >= 400:
612- try:
613- fallback_error = EC2Error(xml_payload, error.value.status,
614- error.value.message, error.value.response)
615- except (ExpatError, AWSResponseParseError):
616- error_message = http.RESPONSES.get(http_status)
617- fallback_error = TwistedWebError(http_status, error_message,
618- error.value.response)
619- raise fallback_error
620- else:
621- error.raiseException()
622+ error_wrapper(error, EC2Error)
623
624
625 class EC2Client(BaseClient):
626@@ -60,16 +28,17 @@
627
628 def __init__(self, creds=None, endpoint=None, query_factory=None):
629 if query_factory is None:
630- self.query_factory = Query
631+ query_factory = Query
632 super(EC2Client, self).__init__(creds, endpoint, query_factory)
633
634 def describe_instances(self, *instance_ids):
635 """Describe current instances."""
636- instanceset = {}
637+ instances= {}
638 for pos, instance_id in enumerate(instance_ids):
639- instanceset["InstanceId.%d" % (pos + 1)] = instance_id
640- query = self.query_factory("DescribeInstances", self.creds,
641- self.endpoint, instanceset)
642+ instances["InstanceId.%d" % (pos + 1)] = instance_id
643+ query = self.query_factory(
644+ action="DescribeInstances", creds=self.creds,
645+ endpoint=self.endpoint, other_params=instances)
646 d = query.submit()
647 return d.addCallback(self._parse_describe_instances)
648
649@@ -164,7 +133,8 @@
650 if ramdisk_id is not None:
651 params["RamdiskId"] = ramdisk_id
652 query = self.query_factory(
653- "RunInstances", self.creds, self.endpoint, params)
654+ action="RunInstances", creds=self.creds, endpoint=self.endpoint,
655+ other_params=params)
656 d = query.submit()
657 return d.addCallback(self._parse_run_instances)
658
659@@ -195,11 +165,12 @@
660 @return: A deferred which on success gives an iterable of
661 (id, old-state, new-state) tuples.
662 """
663- instanceset = {}
664+ instances = {}
665 for pos, instance_id in enumerate(instance_ids):
666- instanceset["InstanceId.%d" % (pos+1)] = instance_id
667+ instances["InstanceId.%d" % (pos+1)] = instance_id
668 query = self.query_factory(
669- "TerminateInstances", self.creds, self.endpoint, instanceset)
670+ action="TerminateInstances", creds=self.creds,
671+ endpoint=self.endpoint, other_params=instances)
672 d = query.submit()
673 return d.addCallback(self._parse_terminate_instances)
674
675@@ -224,12 +195,13 @@
676 @return: A C{Deferred} that will fire with a list of L{SecurityGroup}s
677 retrieved from the cloud.
678 """
679- group_names = None
680+ group_names = {}
681 if names:
682 group_names = dict([("GroupName.%d" % (i+1), name)
683 for i, name in enumerate(names)])
684- query = self.query_factory("DescribeSecurityGroups", self.creds,
685- self.endpoint, group_names)
686+ query = self.query_factory(
687+ action="DescribeSecurityGroups", creds=self.creds,
688+ endpoint=self.endpoint, other_params=group_names)
689 d = query.submit()
690 return d.addCallback(self._parse_describe_security_groups)
691
692@@ -282,8 +254,9 @@
693 success of the operation.
694 """
695 parameters = {"GroupName": name, "GroupDescription": description}
696- query = self.query_factory("CreateSecurityGroup", self.creds,
697- self.endpoint, parameters)
698+ query = self.query_factory(
699+ action="CreateSecurityGroup", creds=self.creds,
700+ endpoint=self.endpoint, other_params=parameters)
701 d = query.submit()
702 return d.addCallback(self._parse_truth_return)
703
704@@ -298,8 +271,9 @@
705 success of the operation.
706 """
707 parameter = {"GroupName": name}
708- query = self.query_factory("DeleteSecurityGroup", self.creds,
709- self.endpoint, parameter)
710+ query = self.query_factory(
711+ action="DeleteSecurityGroup", creds=self.creds,
712+ endpoint=self.endpoint, other_params=parameter)
713 d = query.submit()
714 return d.addCallback(self._parse_truth_return)
715
716@@ -354,8 +328,9 @@
717 "all the ip parameters.")
718 raise ValueError(msg)
719 parameters["GroupName"] = group_name
720- query = self.query_factory("AuthorizeSecurityGroupIngress", self.creds,
721- self.endpoint, parameters)
722+ query = self.query_factory(
723+ action="AuthorizeSecurityGroupIngress", creds=self.creds,
724+ endpoint=self.endpoint, other_params=parameters)
725 d = query.submit()
726 return d.addCallback(self._parse_truth_return)
727
728@@ -438,8 +413,9 @@
729 "all the ip parameters.")
730 raise ValueError(msg)
731 parameters["GroupName"] = group_name
732- query = self.query_factory("RevokeSecurityGroupIngress", self.creds,
733- self.endpoint, parameters)
734+ query = self.query_factory(
735+ action="RevokeSecurityGroupIngress", creds=self.creds,
736+ endpoint=self.endpoint, other_params=parameters)
737 d = query.submit()
738 return d.addCallback(self._parse_truth_return)
739
740@@ -477,7 +453,8 @@
741 for pos, volume_id in enumerate(volume_ids):
742 volumeset["VolumeId.%d" % (pos + 1)] = volume_id
743 query = self.query_factory(
744- "DescribeVolumes", self.creds, self.endpoint, volumeset)
745+ action="DescribeVolumes", creds=self.creds, endpoint=self.endpoint,
746+ other_params=volumeset)
747 d = query.submit()
748 return d.addCallback(self._parse_describe_volumes)
749
750@@ -520,7 +497,8 @@
751 if snapshot_id is not None:
752 params["SnapshotId"] = snapshot_id
753 query = self.query_factory(
754- "CreateVolume", self.creds, self.endpoint, params)
755+ action="CreateVolume", creds=self.creds, endpoint=self.endpoint,
756+ other_params=params)
757 d = query.submit()
758 return d.addCallback(self._parse_create_volume)
759
760@@ -541,7 +519,8 @@
761
762 def delete_volume(self, volume_id):
763 query = self.query_factory(
764- "DeleteVolume", self.creds, self.endpoint, {"VolumeId": volume_id})
765+ action="DeleteVolume", creds=self.creds, endpoint=self.endpoint,
766+ other_params={"VolumeId": volume_id})
767 d = query.submit()
768 return d.addCallback(self._parse_truth_return)
769
770@@ -551,7 +530,8 @@
771 for pos, snapshot_id in enumerate(snapshot_ids):
772 snapshot_set["SnapshotId.%d" % (pos + 1)] = snapshot_id
773 query = self.query_factory(
774- "DescribeSnapshots", self.creds, self.endpoint, snapshot_set)
775+ action="DescribeSnapshots", creds=self.creds,
776+ endpoint=self.endpoint, other_params=snapshot_set)
777 d = query.submit()
778 return d.addCallback(self._parse_snapshots)
779
780@@ -575,8 +555,8 @@
781 def create_snapshot(self, volume_id):
782 """Create a new snapshot of an existing volume."""
783 query = self.query_factory(
784- "CreateSnapshot", self.creds, self.endpoint,
785- {"VolumeId": volume_id})
786+ action="CreateSnapshot", creds=self.creds, endpoint=self.endpoint,
787+ other_params={"VolumeId": volume_id})
788 d = query.submit()
789 return d.addCallback(self._parse_create_snapshot)
790
791@@ -596,17 +576,17 @@
792 def delete_snapshot(self, snapshot_id):
793 """Remove a previously created snapshot."""
794 query = self.query_factory(
795- "DeleteSnapshot", self.creds, self.endpoint,
796- {"SnapshotId": snapshot_id})
797+ action="DeleteSnapshot", creds=self.creds, endpoint=self.endpoint,
798+ other_params={"SnapshotId": snapshot_id})
799 d = query.submit()
800 return d.addCallback(self._parse_truth_return)
801
802 def attach_volume(self, volume_id, instance_id, device):
803 """Attach the given volume to the specified instance at C{device}."""
804 query = self.query_factory(
805- "AttachVolume", self.creds, self.endpoint,
806- {"VolumeId": volume_id, "InstanceId": instance_id,
807- "Device": device})
808+ action="AttachVolume", creds=self.creds, endpoint=self.endpoint,
809+ other_params={"VolumeId": volume_id, "InstanceId": instance_id,
810+ "Device": device})
811 d = query.submit()
812 return d.addCallback(self._parse_attach_volume)
813
814@@ -620,11 +600,12 @@
815
816 def describe_keypairs(self, *keypair_names):
817 """Returns information about key pairs available."""
818- keypair_set = {}
819- for pos, keypair_name in enumerate(keypair_names):
820- keypair_set["KeyPair.%d" % (pos + 1)] = keypair_name
821+ keypairs = {}
822+ for index, keypair_name in enumerate(keypair_names):
823+ keypairs["KeyPair.%d" % (index + 1)] = keypair_name
824 query = self.query_factory(
825- "DescribeKeyPairs", self.creds, self.endpoint, keypair_set)
826+ action="DescribeKeyPairs", creds=self.creds,
827+ endpoint=self.endpoint, other_params=keypairs)
828 d = query.submit()
829 return d.addCallback(self._parse_describe_keypairs)
830
831@@ -646,8 +627,8 @@
832 used to reference the created key pair when launching new instances.
833 """
834 query = self.query_factory(
835- "CreateKeyPair", self.creds, self.endpoint,
836- {"KeyName": keypair_name})
837+ action="CreateKeyPair", creds=self.creds, endpoint=self.endpoint,
838+ other_params={"KeyName": keypair_name})
839 d = query.submit()
840 return d.addCallback(self._parse_create_keypair)
841
842@@ -661,8 +642,8 @@
843 def delete_keypair(self, keypair_name):
844 """Delete a given keypair."""
845 query = self.query_factory(
846- "DeleteKeyPair", self.creds, self.endpoint,
847- {"KeyName": keypair_name})
848+ action="DeleteKeyPair", creds=self.creds, endpoint=self.endpoint,
849+ other_params={"KeyName": keypair_name})
850 d = query.submit()
851 return d.addCallback(self._parse_truth_return)
852
853@@ -673,8 +654,10 @@
854
855 @return: the IP address allocated.
856 """
857+ # XXX remove empty other_params
858 query = self.query_factory(
859- "AllocateAddress", self.creds, self.endpoint, {})
860+ action="AllocateAddress", creds=self.creds, endpoint=self.endpoint,
861+ other_params={})
862 d = query.submit()
863 return d.addCallback(self._parse_allocate_address)
864
865@@ -689,8 +672,8 @@
866 @return: C{True} if the operation succeeded.
867 """
868 query = self.query_factory(
869- "ReleaseAddress", self.creds, self.endpoint,
870- {"PublicIp": address})
871+ action="ReleaseAddress", creds=self.creds, endpoint=self.endpoint,
872+ other_params={"PublicIp": address})
873 d = query.submit()
874 return d.addCallback(self._parse_truth_return)
875
876@@ -702,8 +685,9 @@
877 @return: C{True} if the operation succeeded.
878 """
879 query = self.query_factory(
880- "AssociateAddress", self.creds, self.endpoint,
881- {"InstanceId": instance_id, "PublicIp": address})
882+ action="AssociateAddress", creds=self.creds,
883+ endpoint=self.endpoint,
884+ other_params={"InstanceId": instance_id, "PublicIp": address})
885 d = query.submit()
886 return d.addCallback(self._parse_truth_return)
887
888@@ -714,8 +698,8 @@
889 called several times without error.
890 """
891 query = self.query_factory(
892- "DisassociateAddress", self.creds, self.endpoint,
893- {"PublicIp": address})
894+ action="DisassociateAddress", creds=self.creds,
895+ endpoint=self.endpoint, other_params={"PublicIp": address})
896 d = query.submit()
897 return d.addCallback(self._parse_truth_return)
898
899@@ -732,7 +716,8 @@
900 for pos, address in enumerate(addresses):
901 address_set["PublicIp.%d" % (pos + 1)] = address
902 query = self.query_factory(
903- "DescribeAddresses", self.creds, self.endpoint, address_set)
904+ action="DescribeAddresses", creds=self.creds,
905+ endpoint=self.endpoint, other_params=address_set)
906 d = query.submit()
907 return d.addCallback(self._parse_describe_addresses)
908
909@@ -750,8 +735,9 @@
910 if names:
911 zone_names = dict([("ZoneName.%d" % (i+1), name)
912 for i, name in enumerate(names)])
913- query = self.query_factory("DescribeAvailabilityZones", self.creds,
914- self.endpoint, zone_names)
915+ query = self.query_factory(
916+ action="DescribeAvailabilityZones", creds=self.creds,
917+ endpoint=self.endpoint, other_params=zone_names)
918 d = query.submit()
919 return d.addCallback(self._parse_describe_availability_zones)
920
921@@ -830,5 +816,4 @@
922 url = "%s?%s" % (self.endpoint.get_uri(),
923 self.get_canonical_query_params())
924 d = self.get_page(url, method=self.endpoint.method)
925- d.addErrback(ec2_error_wrapper)
926- return d
927+ return d.addErrback(ec2_error_wrapper)
928
929=== modified file 'txaws/ec2/exception.py'
930--- txaws/ec2/exception.py 2009-11-28 01:15:24 +0000
931+++ txaws/ec2/exception.py 2009-11-28 01:15:24 +0000
932@@ -1,39 +1,14 @@
933 # Copyright (c) 2009 Canonical Ltd <duncan.mcgreggor@canonical.com>
934 # Licenced under the txaws licence available at /LICENSE in the txaws source.
935
936-from txaws.exception import AWSError, AWSResponseParseError
937-from txaws.util import XML
938+from txaws.exception import AWSError
939
940
941 class EC2Error(AWSError):
942 """
943 A error class providing custom methods on EC2 errors.
944 """
945- def __init__(self, xml_bytes, status=None, message=None, response=None):
946- super(AWSError, self).__init__(status, message, response)
947- if not xml_bytes:
948- raise ValueError("XML cannot be empty.")
949- self.original = xml_bytes
950- self.errors = []
951- self.request_id = ""
952- self.host_id = ""
953- self.parse()
954-
955- def __str__(self):
956- return self._get_error_message_string()
957-
958- def __repr__(self):
959- return "<%s object with %s>" % (
960- self.__class__.__name__, self._get_error_code_string())
961-
962- def _set_request_id(self, tree):
963- request_id_node = tree.find(".//RequestID")
964- if hasattr(request_id_node, "text"):
965- text = request_id_node.text
966- if text:
967- self.request_id = text
968-
969- def _set_400_errors(self, tree):
970+ def _set_400_error(self, tree):
971 errors_node = tree.find(".//Errors")
972 if errors_node:
973 for error in errors_node:
974@@ -41,84 +16,5 @@
975 if data:
976 self.errors.append(data)
977
978- def _set_host_id(self, tree):
979- host_id = tree.find(".//HostID")
980- if hasattr(host_id, "text"):
981- text = host_id.text
982- if text:
983- self.host_id = text
984-
985- def _set_500_error(self, tree):
986- self._set_request_id(tree)
987- self._set_host_id(tree)
988- data = self._node_to_dict(tree)
989- if data:
990- self.errors.append(data)
991-
992- def _get_error_code_string(self):
993- count = len(self.errors)
994- error_code = self.get_error_codes()
995- if count > 1:
996- return "Error count: %s" % error_code
997- else:
998- return "Error code: %s" % error_code
999-
1000- def _get_error_message_string(self):
1001- count = len(self.errors)
1002- error_message = self.get_error_messages()
1003- if count > 1:
1004- return "%s." % error_message
1005- else:
1006- return "Error Message: %s" % error_message
1007-
1008- def _node_to_dict(self, node):
1009- data = {}
1010- for child in node:
1011- if child.tag and child.text:
1012- data[child.tag] = child.text
1013- return data
1014-
1015- def _check_for_html(self, tree):
1016- if tree.tag == "html":
1017- message = "Could not parse HTML in the response."
1018- raise AWSResponseParseError(message)
1019-
1020- def parse(self, xml_bytes=""):
1021- if not xml_bytes:
1022- xml_bytes = self.original
1023- self.original = xml_bytes
1024- tree = XML(xml_bytes.strip())
1025- self._check_for_html(tree)
1026- self._set_request_id(tree)
1027- if self.status:
1028- status = int(self.status)
1029- else:
1030- status = 400
1031- if status >= 500:
1032- self._set_500_error(tree)
1033- else:
1034- self._set_400_errors(tree)
1035-
1036- def has_error(self, errorString):
1037- for error in self.errors:
1038- if errorString in error.values():
1039- return True
1040- return False
1041-
1042- def get_error_codes(self):
1043- count = len(self.errors)
1044- if count > 1:
1045- return count
1046- elif count == 0:
1047- return
1048- else:
1049- return self.errors[0]["Code"]
1050-
1051- def get_error_messages(self):
1052- count = len(self.errors)
1053- if count > 1:
1054- return "Multiple EC2 Errors"
1055- elif count == 0:
1056- return "Empty error list"
1057- else:
1058- return self.errors[0]["Message"]
1059+
1060+
1061
1062=== modified file 'txaws/ec2/tests/test_client.py'
1063--- txaws/ec2/tests/test_client.py 2009-11-28 01:15:24 +0000
1064+++ txaws/ec2/tests/test_client.py 2009-11-28 01:15:24 +0000
1065@@ -76,13 +76,14 @@
1066
1067 class StubQuery(object):
1068
1069- def __init__(stub, action, creds, endpoint, other_params):
1070+ def __init__(stub, action="", creds=None, endpoint=None,
1071+ other_params={}):
1072 self.assertEqual(action, "DescribeAvailabilityZones")
1073 self.assertEqual(creds.access_key, "foo")
1074 self.assertEqual(creds.secret_key, "bar")
1075 self.assertEqual(
1076- {"ZoneName.1": "us-east-1a"},
1077- other_params)
1078+ other_params,
1079+ {"ZoneName.1": "us-east-1a"})
1080
1081 def submit(self):
1082 return succeed(
1083@@ -105,7 +106,8 @@
1084
1085 class StubQuery(object):
1086
1087- def __init__(stub, action, creds, endpoint, other_params):
1088+ def __init__(stub, action="", creds=None, endpoint=None,
1089+ other_params={}):
1090 self.assertEqual(action, "DescribeAvailabilityZones")
1091 self.assertEqual(creds.access_key, "foo")
1092 self.assertEqual(creds.secret_key, "bar")
1093@@ -199,11 +201,12 @@
1094
1095 class StubQuery(object):
1096
1097- def __init__(stub, action, creds, endpoint, params):
1098+ def __init__(stub, action="", creds=None, endpoint=None,
1099+ other_params={}):
1100 self.assertEqual(action, "DescribeInstances")
1101 self.assertEqual(creds.access_key, "foo")
1102 self.assertEqual(creds.secret_key, "bar")
1103- self.assertEquals(params, {})
1104+ self.assertEquals(other_params, {})
1105
1106 def submit(self):
1107 return succeed(payload.sample_describe_instances_result)
1108@@ -218,11 +221,12 @@
1109
1110 class StubQuery(object):
1111
1112- def __init__(stub, action, creds, endpoint, params):
1113+ def __init__(stub, action="", creds=None, endpoint=None,
1114+ other_params={}):
1115 self.assertEqual(action, "DescribeInstances")
1116 self.assertEqual(creds.access_key, "foo")
1117 self.assertEqual(creds.secret_key, "bar")
1118- self.assertEquals(params, {})
1119+ self.assertEquals(other_params, {})
1120
1121 def submit(self):
1122 return succeed(
1123@@ -238,12 +242,13 @@
1124
1125 class StubQuery(object):
1126
1127- def __init__(stub, action, creds, endpoint, params):
1128+ def __init__(stub, action="", creds=None, endpoint=None,
1129+ other_params={}):
1130 self.assertEqual(action, "DescribeInstances")
1131 self.assertEqual(creds.access_key, "foo")
1132 self.assertEqual(creds.secret_key, "bar")
1133 self.assertEquals(
1134- params,
1135+ other_params,
1136 {"InstanceId.1": "i-16546401",
1137 "InstanceId.2": "i-49873415"})
1138
1139@@ -261,13 +266,14 @@
1140
1141 class StubQuery(object):
1142
1143- def __init__(stub, action, creds, endpoint, other_params):
1144+ def __init__(stub, action="", creds=None, endpoint=None,
1145+ other_params={}):
1146 self.assertEqual(action, "TerminateInstances")
1147 self.assertEqual(creds.access_key, "foo")
1148 self.assertEqual(creds.secret_key, "bar")
1149 self.assertEqual(
1150- {"InstanceId.1": "i-1234", "InstanceId.2": "i-5678"},
1151- other_params)
1152+ other_params,
1153+ {"InstanceId.1": "i-1234", "InstanceId.2": "i-5678"})
1154
1155 def submit(self):
1156 return succeed(payload.sample_terminate_instances_result)
1157@@ -313,12 +319,13 @@
1158
1159 class StubQuery(object):
1160
1161- def __init__(stub, action, creds, endpoint, params):
1162+ def __init__(stub, action="", creds=None, endpoint=None,
1163+ other_params={}):
1164 self.assertEqual(action, "RunInstances")
1165 self.assertEqual(creds.access_key, "foo")
1166 self.assertEqual(creds.secret_key, "bar")
1167 self.assertEquals(
1168- params,
1169+ other_params,
1170 {"ImageId": "ami-1234", "MaxCount": "2", "MinCount": "1",
1171 "SecurityGroup.1": u"group1", "KeyName": u"default",
1172 "UserData": "Zm9v", "InstanceType": u"m1.small",
1173@@ -348,11 +355,12 @@
1174 """
1175 class StubQuery(object):
1176
1177- def __init__(stub, action, creds, endpoint, other_params=None):
1178+ def __init__(stub, action="", creds=None, endpoint=None,
1179+ other_params={}):
1180 self.assertEqual(action, "DescribeSecurityGroups")
1181 self.assertEqual(creds.access_key, "foo")
1182 self.assertEqual(creds.secret_key, "bar")
1183- self.assertEqual(other_params, None)
1184+ self.assertEqual(other_params, {})
1185
1186 def submit(self):
1187 return succeed(payload.sample_describe_security_groups_result)
1188@@ -382,11 +390,12 @@
1189 """
1190 class StubQuery(object):
1191
1192- def __init__(stub, action, creds, endpoint, other_params=None):
1193+ def __init__(stub, action="", creds=None, endpoint=None,
1194+ other_params={}):
1195 self.assertEqual(action, "DescribeSecurityGroups")
1196 self.assertEqual(creds.access_key, "foo")
1197 self.assertEqual(creds.secret_key, "bar")
1198- self.assertEqual(other_params, None)
1199+ self.assertEqual(other_params, {})
1200
1201 def submit(self):
1202 return succeed(
1203@@ -431,7 +440,8 @@
1204 """
1205 class StubQuery(object):
1206
1207- def __init__(stub, action, creds, endpoint, other_params=None):
1208+ def __init__(stub, action="", creds=None, endpoint=None,
1209+ other_params={}):
1210 self.assertEqual(action, "DescribeSecurityGroups")
1211 self.assertEqual(creds.access_key, "foo")
1212 self.assertEqual(creds.secret_key, "bar")
1213@@ -457,7 +467,8 @@
1214 """
1215 class StubQuery(object):
1216
1217- def __init__(stub, action, creds, endpoint, other_params=None):
1218+ def __init__(stub, action="", creds=None, endpoint=None,
1219+ other_params={}):
1220 self.assertEqual(action, "CreateSecurityGroup")
1221 self.assertEqual(creds.access_key, "foo")
1222 self.assertEqual(creds.secret_key, "bar")
1223@@ -484,7 +495,8 @@
1224 """
1225 class StubQuery(object):
1226
1227- def __init__(stub, action, creds, endpoint, other_params=None):
1228+ def __init__(stub, action="", creds=None, endpoint=None,
1229+ other_params={}):
1230 self.assertEqual(action, "DeleteSecurityGroup")
1231 self.assertEqual(creds.access_key, "foo")
1232 self.assertEqual(creds.secret_key, "bar")
1233@@ -508,7 +520,8 @@
1234 """
1235 class StubQuery(object):
1236
1237- def __init__(stub, action, creds, endpoint, other_params=None):
1238+ def __init__(stub, action="", creds=None, endpoint=None,
1239+ other_params={}):
1240 self.assertEqual(action, "DeleteSecurityGroup")
1241 self.assertEqual(creds.access_key, "foo")
1242 self.assertEqual(creds.secret_key, "bar")
1243@@ -542,7 +555,8 @@
1244 """
1245 class StubQuery(object):
1246
1247- def __init__(stub, action, creds, endpoint, other_params=None):
1248+ def __init__(stub, action="", creds=None, endpoint=None,
1249+ other_params={}):
1250 self.assertEqual(action, "AuthorizeSecurityGroupIngress")
1251 self.assertEqual(creds.access_key, "foo")
1252 self.assertEqual(creds.secret_key, "bar")
1253@@ -572,7 +586,8 @@
1254 """
1255 class StubQuery(object):
1256
1257- def __init__(stub, action, creds, endpoint, other_params=None):
1258+ def __init__(stub, action="", creds=None, endpoint=None,
1259+ other_params={}):
1260 self.assertEqual(action, "AuthorizeSecurityGroupIngress")
1261 self.assertEqual(creds.access_key, "foo")
1262 self.assertEqual(creds.secret_key, "bar")
1263@@ -622,7 +637,8 @@
1264 """
1265 class StubQuery(object):
1266
1267- def __init__(stub, action, creds, endpoint, other_params=None):
1268+ def __init__(stub, action="", creds=None, endpoint=None,
1269+ other_params={}):
1270 self.assertEqual(action, "AuthorizeSecurityGroupIngress")
1271 self.assertEqual(creds.access_key, "foo")
1272 self.assertEqual(creds.secret_key, "bar")
1273@@ -650,7 +666,8 @@
1274 """
1275 class StubQuery(object):
1276
1277- def __init__(stub, action, creds, endpoint, other_params=None):
1278+ def __init__(stub, action="", creds=None, endpoint=None,
1279+ other_params={}):
1280 self.assertEqual(action, "AuthorizeSecurityGroupIngress")
1281 self.assertEqual(creds.access_key, "foo")
1282 self.assertEqual(creds.secret_key, "bar")
1283@@ -680,7 +697,8 @@
1284 """
1285 class StubQuery(object):
1286
1287- def __init__(stub, action, creds, endpoint, other_params=None):
1288+ def __init__(stub, action="", creds=None, endpoint=None,
1289+ other_params={}):
1290 self.assertEqual(action, "RevokeSecurityGroupIngress")
1291 self.assertEqual(creds.access_key, "foo")
1292 self.assertEqual(creds.secret_key, "bar")
1293@@ -710,7 +728,8 @@
1294 """
1295 class StubQuery(object):
1296
1297- def __init__(stub, action, creds, endpoint, other_params=None):
1298+ def __init__(stub, action="", creds=None, endpoint=None,
1299+ other_params={}):
1300 self.assertEqual(action, "RevokeSecurityGroupIngress")
1301 self.assertEqual(creds.access_key, "foo")
1302 self.assertEqual(creds.secret_key, "bar")
1303@@ -760,7 +779,8 @@
1304 """
1305 class StubQuery(object):
1306
1307- def __init__(stub, action, creds, endpoint, other_params=None):
1308+ def __init__(stub, action="", creds=None, endpoint=None,
1309+ other_params={}):
1310 self.assertEqual(action, "RevokeSecurityGroupIngress")
1311 self.assertEqual(creds.access_key, "foo")
1312 self.assertEqual(creds.secret_key, "bar")
1313@@ -788,7 +808,8 @@
1314 """
1315 class StubQuery(object):
1316
1317- def __init__(stub, action, creds, endpoint, other_params=None):
1318+ def __init__(stub, action="", creds=None, endpoint=None,
1319+ other_params={}):
1320 self.assertEqual(action, "RevokeSecurityGroupIngress")
1321 self.assertEqual(creds.access_key, "foo")
1322 self.assertEqual(creds.secret_key, "bar")
1323@@ -838,11 +859,12 @@
1324
1325 class StubQuery(object):
1326
1327- def __init__(stub, action, creds, endpoint, params):
1328+ def __init__(stub, action="", creds=None, endpoint=None,
1329+ other_params={}):
1330 self.assertEqual(action, "DescribeVolumes")
1331 self.assertEqual(self.creds, creds)
1332 self.assertEqual(self.endpoint, endpoint)
1333- self.assertEquals(params, {})
1334+ self.assertEquals(other_params, {})
1335
1336 def submit(self):
1337 return succeed(payload.sample_describe_volumes_result)
1338@@ -857,12 +879,13 @@
1339
1340 class StubQuery(object):
1341
1342- def __init__(stub, action, creds, endpoint, params):
1343+ def __init__(stub, action="", creds=None, endpoint=None,
1344+ other_params={}):
1345 self.assertEqual(action, "DescribeVolumes")
1346 self.assertEqual(self.creds, creds)
1347 self.assertEqual(self.endpoint, endpoint)
1348 self.assertEquals(
1349- params,
1350+ other_params,
1351 {"VolumeId.1": "vol-4282672b"})
1352
1353 def submit(self):
1354@@ -888,11 +911,12 @@
1355
1356 class StubQuery(object):
1357
1358- def __init__(stub, action, creds, endpoint, params):
1359+ def __init__(stub, action="", creds=None, endpoint=None,
1360+ other_params={}):
1361 self.assertEqual(action, "DescribeSnapshots")
1362 self.assertEqual(self.creds, creds)
1363 self.assertEqual(self.endpoint, endpoint)
1364- self.assertEquals(params, {})
1365+ self.assertEquals(other_params, {})
1366
1367 def submit(self):
1368 return succeed(payload.sample_describe_snapshots_result)
1369@@ -907,12 +931,13 @@
1370
1371 class StubQuery(object):
1372
1373- def __init__(stub, action, creds, endpoint, params):
1374+ def __init__(stub, action="", creds=None, endpoint=None,
1375+ other_params={}):
1376 self.assertEqual(action, "DescribeSnapshots")
1377 self.assertEqual(self.creds, creds)
1378 self.assertEqual(self.endpoint, endpoint)
1379 self.assertEquals(
1380- params,
1381+ other_params,
1382 {"SnapshotId.1": "snap-78a54011"})
1383
1384 def submit(self):
1385@@ -928,13 +953,14 @@
1386
1387 class StubQuery(object):
1388
1389- def __init__(stub, action, creds, endpoint, params):
1390+ def __init__(stub, action="", creds=None, endpoint=None,
1391+ other_params={}):
1392 self.assertEqual(action, "CreateVolume")
1393 self.assertEqual(self.creds, creds)
1394 self.assertEqual(self.endpoint, endpoint)
1395 self.assertEqual(
1396- {"AvailabilityZone": "us-east-1", "Size": "800"},
1397- params)
1398+ other_params,
1399+ {"AvailabilityZone": "us-east-1", "Size": "800"})
1400
1401 def submit(self):
1402 return succeed(payload.sample_create_volume_result)
1403@@ -956,14 +982,15 @@
1404
1405 class StubQuery(object):
1406
1407- def __init__(stub, action, creds, endpoint, params):
1408+ def __init__(stub, action="", creds=None, endpoint=None,
1409+ other_params={}):
1410 self.assertEqual(action, "CreateVolume")
1411 self.assertEqual(self.creds, creds)
1412 self.assertEqual(self.endpoint, endpoint)
1413 self.assertEqual(
1414+ other_params,
1415 {"AvailabilityZone": "us-east-1",
1416- "SnapshotId": "snap-12345678"},
1417- params)
1418+ "SnapshotId": "snap-12345678"})
1419
1420 def submit(self):
1421 return succeed(payload.sample_create_volume_result)
1422@@ -999,13 +1026,14 @@
1423
1424 class StubQuery(object):
1425
1426- def __init__(stub, action, creds, endpoint, params):
1427+ def __init__(stub, action="", creds=None, endpoint=None,
1428+ other_params={}):
1429 self.assertEqual(action, "DeleteVolume")
1430 self.assertEqual(self.creds, creds)
1431 self.assertEqual(self.endpoint, endpoint)
1432 self.assertEqual(
1433- {"VolumeId": "vol-4282672b"},
1434- params)
1435+ other_params,
1436+ {"VolumeId": "vol-4282672b"})
1437
1438 def submit(self):
1439 return succeed(payload.sample_delete_volume_result)
1440@@ -1020,13 +1048,14 @@
1441
1442 class StubQuery(object):
1443
1444- def __init__(stub, action, creds, endpoint, params):
1445+ def __init__(stub, action="", creds=None, endpoint=None,
1446+ other_params={}):
1447 self.assertEqual(action, "CreateSnapshot")
1448 self.assertEqual(self.creds, creds)
1449 self.assertEqual(self.endpoint, endpoint)
1450 self.assertEqual(
1451- {"VolumeId": "vol-4d826724"},
1452- params)
1453+ other_params,
1454+ {"VolumeId": "vol-4d826724"})
1455
1456 def submit(self):
1457 return succeed(payload.sample_create_snapshot_result)
1458@@ -1049,13 +1078,14 @@
1459
1460 class StubQuery(object):
1461
1462- def __init__(stub, action, creds, endpoint, params):
1463+ def __init__(stub, action="", creds=None, endpoint=None,
1464+ other_params={}):
1465 self.assertEqual(action, "DeleteSnapshot")
1466 self.assertEqual(self.creds, creds)
1467 self.assertEqual(self.endpoint, endpoint)
1468 self.assertEqual(
1469- {"SnapshotId": "snap-78a54011"},
1470- params)
1471+ other_params,
1472+ {"SnapshotId": "snap-78a54011"})
1473
1474 def submit(self):
1475 return succeed(payload.sample_delete_snapshot_result)
1476@@ -1070,14 +1100,15 @@
1477
1478 class StubQuery(object):
1479
1480- def __init__(stub, action, creds, endpoint, params):
1481+ def __init__(stub, action="", creds=None, endpoint=None,
1482+ other_params={}):
1483 self.assertEqual(action, "AttachVolume")
1484 self.assertEqual(self.creds, creds)
1485 self.assertEqual(self.endpoint, endpoint)
1486 self.assertEqual(
1487+ other_params,
1488 {"VolumeId": "vol-4d826724", "InstanceId": "i-6058a509",
1489- "Device": "/dev/sdh"},
1490- params)
1491+ "Device": "/dev/sdh"})
1492
1493 def submit(self):
1494 return succeed(payload.sample_attach_volume_result)
1495@@ -1106,10 +1137,11 @@
1496
1497 class StubQuery(object):
1498
1499- def __init__(stub, action, creds, endpoint, params):
1500+ def __init__(stub, action="", creds=None, endpoint=None,
1501+ other_params={}):
1502 self.assertEqual(action, "DescribeKeyPairs")
1503 self.assertEqual("foo", creds)
1504- self.assertEquals(params, {})
1505+ self.assertEquals(other_params, {})
1506
1507 def submit(self):
1508 return succeed(payload.sample_single_describe_keypairs_result)
1509@@ -1134,10 +1166,12 @@
1510 "1f:51:ae:28:bf:89:e9:d8:1f:25:5d:37:2d:7d:b8:ca:9f:f5:f1:70")
1511
1512 class StubQuery(object):
1513- def __init__(stub, action, creds, endpoint, params):
1514+
1515+ def __init__(stub, action="", creds=None, endpoint=None,
1516+ other_params={}):
1517 self.assertEqual(action, "DescribeKeyPairs")
1518 self.assertEqual("foo", creds)
1519- self.assertEquals(params, {})
1520+ self.assertEquals(other_params, {})
1521
1522 def submit(self):
1523 return succeed(
1524@@ -1152,11 +1186,12 @@
1525
1526 class StubQuery(object):
1527
1528- def __init__(stub, action, creds, endpoint, params):
1529+ def __init__(stub, action="", creds=None, endpoint=None,
1530+ other_params={}):
1531 self.assertEqual(action, "DescribeKeyPairs")
1532 self.assertEqual("foo", creds)
1533 self.assertEquals(
1534- params,
1535+ other_params,
1536 {"KeyPair.1": "gsg-keypair"})
1537
1538 def submit(self):
1539@@ -1182,11 +1217,12 @@
1540
1541 class StubQuery(object):
1542
1543- def __init__(stub, action, creds, endpoint, params):
1544+ def __init__(stub, action="", creds=None, endpoint=None,
1545+ other_params={}):
1546 self.assertEqual(action, "CreateKeyPair")
1547 self.assertEqual("foo", creds)
1548 self.assertEquals(
1549- params,
1550+ other_params,
1551 {"KeyName": "example-key-name"})
1552
1553 def submit(self):
1554@@ -1201,12 +1237,13 @@
1555
1556 class StubQuery(object):
1557
1558- def __init__(stub, action, creds, endpoint, params):
1559+ def __init__(stub, action="", creds=None, endpoint=None,
1560+ other_params={}):
1561 self.assertEqual(action, "DeleteKeyPair")
1562 self.assertEqual("foo", creds)
1563 self.assertEqual("http:///", endpoint.get_uri())
1564 self.assertEquals(
1565- params,
1566+ other_params,
1567 {"KeyName": "example-key-name"})
1568
1569 def submit(self):
1570@@ -1221,12 +1258,13 @@
1571
1572 class StubQuery(object):
1573
1574- def __init__(stub, action, creds, endpoint, params):
1575+ def __init__(stub, action="", creds=None, endpoint=None,
1576+ other_params={}):
1577 self.assertEqual(action, "DeleteKeyPair")
1578 self.assertEqual("foo", creds)
1579 self.assertEqual("http:///", endpoint.get_uri())
1580 self.assertEquals(
1581- params,
1582+ other_params,
1583 {"KeyName": "example-key-name"})
1584
1585 def submit(self):
1586@@ -1241,12 +1279,13 @@
1587
1588 class StubQuery(object):
1589
1590- def __init__(stub, action, creds, endpoint, params):
1591+ def __init__(stub, action="", creds=None, endpoint=None,
1592+ other_params={}):
1593 self.assertEqual(action, "DeleteKeyPair")
1594 self.assertEqual("foo", creds)
1595 self.assertEqual("http:///", endpoint.get_uri())
1596 self.assertEquals(
1597- params,
1598+ other_params,
1599 {"KeyName": "example-key-name"})
1600
1601 def submit(self):
1602@@ -1373,12 +1412,12 @@
1603 self.assertTrue("Timestamp" in query.params)
1604 del query.params["Timestamp"]
1605 self.assertEqual(
1606+ query.params,
1607 {"AWSAccessKeyId": "foo",
1608 "Action": "DescribeInstances",
1609 "SignatureMethod": "HmacSHA256",
1610 "SignatureVersion": "2",
1611- "Version": "2008-12-01"},
1612- query.params)
1613+ "Version": "2008-12-01"})
1614
1615 def test_init_other_args_are_params(self):
1616 query = client.Query(
1617@@ -1386,14 +1425,14 @@
1618 endpoint=self.endpoint, other_params={"InstanceId.0": "12345"},
1619 time_tuple=(2007,11,12,13,14,15,0,0,0))
1620 self.assertEqual(
1621+ query.params,
1622 {"AWSAccessKeyId": "foo",
1623 "Action": "DescribeInstances",
1624 "InstanceId.0": "12345",
1625 "SignatureMethod": "HmacSHA256",
1626 "SignatureVersion": "2",
1627 "Timestamp": "2007-11-12T13:14:15Z",
1628- "Version": "2008-12-01"},
1629- query.params)
1630+ "Version": "2008-12-01"})
1631
1632 def test_sorted_params(self):
1633 query = client.Query(
1634@@ -1604,11 +1643,12 @@
1635
1636 class StubQuery(object):
1637
1638- def __init__(stub, action, creds, endpoint, params):
1639+ def __init__(stub, action="", creds=None, endpoint=None,
1640+ other_params={}):
1641 self.assertEqual(action, "DescribeAddresses")
1642 self.assertEqual(self.creds, creds)
1643 self.assertEqual(self.endpoint, endpoint)
1644- self.assertEquals(params, {})
1645+ self.assertEquals(other_params, {})
1646
1647 def submit(self):
1648 return succeed(payload.sample_describe_addresses_result)
1649@@ -1625,12 +1665,13 @@
1650
1651 class StubQuery(object):
1652
1653- def __init__(stub, action, creds, endpoint, params):
1654+ def __init__(stub, action="", creds=None, endpoint=None,
1655+ other_params={}):
1656 self.assertEqual(action, "DescribeAddresses")
1657 self.assertEqual(self.creds, creds)
1658 self.assertEqual(self.endpoint, endpoint)
1659 self.assertEquals(
1660- params,
1661+ other_params,
1662 {"PublicIp.1": "67.202.55.255"})
1663
1664 def submit(self):
1665@@ -1648,12 +1689,13 @@
1666
1667 class StubQuery(object):
1668
1669- def __init__(stub, action, creds, endpoint, params):
1670+ def __init__(stub, action="", creds=None, endpoint=None,
1671+ other_params={}):
1672 self.assertEqual(action, "AssociateAddress")
1673 self.assertEqual(self.creds, creds)
1674 self.assertEqual(self.endpoint, endpoint)
1675 self.assertEquals(
1676- params,
1677+ other_params,
1678 {"InstanceId": "i-28a64341", "PublicIp": "67.202.55.255"})
1679
1680 def submit(self):
1681@@ -1669,11 +1711,12 @@
1682
1683 class StubQuery(object):
1684
1685- def __init__(stub, action, creds, endpoint, params):
1686+ def __init__(stub, action="", creds=None, endpoint=None,
1687+ other_params={}):
1688 self.assertEqual(action, "AllocateAddress")
1689 self.assertEqual(self.creds, creds)
1690 self.assertEqual(self.endpoint, endpoint)
1691- self.assertEquals(params, {})
1692+ self.assertEquals(other_params, {})
1693
1694 def submit(self):
1695 return succeed(payload.sample_allocate_address_result)
1696@@ -1688,11 +1731,12 @@
1697
1698 class StubQuery(object):
1699
1700- def __init__(stub, action, creds, endpoint, params):
1701+ def __init__(stub, action="", creds=None, endpoint=None,
1702+ other_params={}):
1703 self.assertEqual(action, "ReleaseAddress")
1704 self.assertEqual(self.creds, creds)
1705 self.assertEqual(self.endpoint, endpoint)
1706- self.assertEquals(params, {"PublicIp": "67.202.55.255"})
1707+ self.assertEquals(other_params, {"PublicIp": "67.202.55.255"})
1708
1709 def submit(self):
1710 return succeed(payload.sample_release_address_result)
1711@@ -1707,11 +1751,12 @@
1712
1713 class StubQuery(object):
1714
1715- def __init__(stub, action, creds, endpoint, params):
1716+ def __init__(stub, action="", creds=None, endpoint=None,
1717+ other_params={}):
1718 self.assertEqual(action, "DisassociateAddress")
1719 self.assertEqual(self.creds, creds)
1720 self.assertEqual(self.endpoint, endpoint)
1721- self.assertEquals(params, {"PublicIp": "67.202.55.255"})
1722+ self.assertEquals(other_params, {"PublicIp": "67.202.55.255"})
1723
1724 def submit(self):
1725 return succeed(payload.sample_disassociate_address_result)
1726
1727=== modified file 'txaws/ec2/tests/test_exception.py'
1728--- txaws/ec2/tests/test_exception.py 2009-11-28 01:15:24 +0000
1729+++ txaws/ec2/tests/test_exception.py 2009-11-28 01:15:24 +0000
1730@@ -4,7 +4,6 @@
1731 from twisted.trial.unittest import TestCase
1732
1733 from txaws.ec2.exception import EC2Error
1734-from txaws.exception import AWSResponseParseError
1735 from txaws.testing import payload
1736 from txaws.util import XML
1737
1738@@ -14,77 +13,14 @@
1739
1740 class EC2ErrorTestCase(TestCase):
1741
1742- def test_creation(self):
1743- error = EC2Error("<dummy1 />", 400, "Not Found", "<dummy2 />")
1744- self.assertEquals(error.status, 400)
1745- self.assertEquals(error.response, "<dummy2 />")
1746- self.assertEquals(error.original, "<dummy1 />")
1747- self.assertEquals(error.errors, [])
1748- self.assertEquals(error.request_id, "")
1749-
1750- def test_node_to_dict(self):
1751- xml = "<parent><child1>text1</child1><child2>text2</child2></parent>"
1752- error = EC2Error("<dummy />")
1753- data = error._node_to_dict(XML(xml))
1754- self.assertEquals(data, {"child1": "text1", "child2": "text2"})
1755-
1756- def test_set_request_id(self):
1757- xml = "<a><b /><RequestID>%s</RequestID></a>" % REQUEST_ID
1758- error = EC2Error("<dummy />")
1759- error._set_request_id(XML(xml))
1760- self.assertEquals(error.request_id, REQUEST_ID)
1761-
1762- def test_set_400_errors(self):
1763+ def test_set_400_error(self):
1764 errorsXML = "<Error><Code>1</Code><Message>2</Message></Error>"
1765 xml = "<a><Errors>%s</Errors><b /></a>" % errorsXML
1766 error = EC2Error("<dummy />")
1767- error._set_400_errors(XML(xml))
1768+ error._set_400_error(XML(xml))
1769 self.assertEquals(error.errors[0]["Code"], "1")
1770 self.assertEquals(error.errors[0]["Message"], "2")
1771
1772- def test_set_host_id(self):
1773- host_id = "ASD@#FDG$E%FG"
1774- xml = "<a><b /><HostID>%s</HostID></a>" % host_id
1775- error = EC2Error("<dummy />")
1776- error._set_host_id(XML(xml))
1777- self.assertEquals(error.host_id, host_id)
1778-
1779- def test_set_500_error(self):
1780- xml = "<Error><Code>500</Code><Message>Oops</Message></Error>"
1781- error = EC2Error("<dummy />")
1782- error._set_500_error(XML(xml))
1783- self.assertEquals(error.errors[0]["Code"], "500")
1784- self.assertEquals(error.errors[0]["Message"], "Oops")
1785-
1786- def test_set_empty_errors(self):
1787- xml = "<a><Errors /><b /></a>"
1788- error = EC2Error("<dummy />")
1789- error._set_400_errors(XML(xml))
1790- self.assertEquals(error.errors, [])
1791-
1792- def test_set_empty_error(self):
1793- xml = "<a><Errors><Error /><Error /></Errors><b /></a>"
1794- error = EC2Error("<dummy />")
1795- error._set_400_errors(XML(xml))
1796- self.assertEquals(error.errors, [])
1797-
1798- def test_parse_without_xml(self):
1799- xml = "<dummy />"
1800- error = EC2Error(xml)
1801- error.parse()
1802- self.assertEquals(error.original, xml)
1803-
1804- def test_parse_with_xml(self):
1805- xml1 = "<dummy1 />"
1806- xml2 = "<dummy2 />"
1807- error = EC2Error(xml1)
1808- error.parse(xml2)
1809- self.assertEquals(error.original, xml2)
1810-
1811- def test_parse_html(self):
1812- xml = "<html><body>a page</body></html>"
1813- self.assertRaises(AWSResponseParseError, EC2Error, xml)
1814-
1815 def test_has_error(self):
1816 errorsXML = "<Error><Code>Code1</Code><Message>2</Message></Error>"
1817 xml = "<a><Errors>%s</Errors><b /></a>" % errorsXML
1818@@ -99,69 +35,6 @@
1819 error = EC2Error(payload.sample_ec2_error_messages)
1820 self.assertEquals(len(error.errors), 2)
1821
1822- def test_empty_xml(self):
1823- self.assertRaises(ValueError, EC2Error, "")
1824-
1825- def test_no_request_id(self):
1826- errors = "<Errors><Error><Code /><Message /></Error></Errors>"
1827- xml = "<Response>%s<RequestID /></Response>" % errors
1828- error = EC2Error(xml)
1829- self.assertEquals(error.request_id, "")
1830-
1831- def test_no_request_id_node(self):
1832- errors = "<Errors><Error><Code /><Message /></Error></Errors>"
1833- xml = "<Response>%s</Response>" % errors
1834- error = EC2Error(xml)
1835- self.assertEquals(error.request_id, "")
1836-
1837- def test_no_errors_node(self):
1838- xml = "<Response><RequestID /></Response>"
1839- error = EC2Error(xml)
1840- self.assertEquals(error.errors, [])
1841-
1842- def test_no_error_node(self):
1843- xml = "<Response><Errors /><RequestID /></Response>"
1844- error = EC2Error(xml)
1845- self.assertEquals(error.errors, [])
1846-
1847- def test_no_error_code_node(self):
1848- errors = "<Errors><Error><Message /></Error></Errors>"
1849- xml = "<Response>%s<RequestID /></Response>" % errors
1850- error = EC2Error(xml)
1851- self.assertEquals(error.errors, [])
1852-
1853- def test_no_error_message_node(self):
1854- errors = "<Errors><Error><Code /></Error></Errors>"
1855- xml = "<Response>%s<RequestID /></Response>" % errors
1856- error = EC2Error(xml)
1857- self.assertEquals(error.errors, [])
1858-
1859- def test_single_get_error_codes(self):
1860- error = EC2Error(payload.sample_ec2_error_message)
1861- self.assertEquals(error.get_error_codes(), "Error.Code")
1862-
1863- def test_multiple_get_error_codes(self):
1864- error = EC2Error(payload.sample_ec2_error_messages)
1865- self.assertEquals(error.get_error_codes(), 2)
1866-
1867- def test_zero_get_error_codes(self):
1868- xml = "<Response><RequestID /></Response>"
1869- error = EC2Error(xml)
1870- self.assertEquals(error.get_error_codes(), None)
1871-
1872- def test_single_get_error_messages(self):
1873- error = EC2Error(payload.sample_ec2_error_message)
1874- self.assertEquals(error.get_error_messages(), "Message for Error.Code")
1875-
1876- def test_multiple_get_error_messages(self):
1877- error = EC2Error(payload.sample_ec2_error_messages)
1878- self.assertEquals(error.get_error_messages(), "Multiple EC2 Errors")
1879-
1880- def test_zero_get_error_messages(self):
1881- xml = "<Response><RequestID /></Response>"
1882- error = EC2Error(xml)
1883- self.assertEquals(error.get_error_messages(), "Empty error list")
1884-
1885 def test_single_error_str(self):
1886 error = EC2Error(payload.sample_ec2_error_message)
1887 self.assertEquals(str(error), "Error Message: Message for Error.Code")
1888
1889=== modified file 'txaws/exception.py'
1890--- txaws/exception.py 2009-10-28 17:43:23 +0000
1891+++ txaws/exception.py 2009-11-28 01:15:24 +0000
1892@@ -3,11 +3,124 @@
1893
1894 from twisted.web.error import Error
1895
1896+from txaws.util import XML
1897+
1898
1899 class AWSError(Error):
1900 """
1901 A base class for txAWS errors.
1902 """
1903+ def __init__(self, xml_bytes, status=None, message=None, response=None):
1904+ super(AWSError, self).__init__(status, message, response)
1905+ if not xml_bytes:
1906+ raise ValueError("XML cannot be empty.")
1907+ self.original = xml_bytes
1908+ self.errors = []
1909+ self.request_id = ""
1910+ self.host_id = ""
1911+ self.parse()
1912+
1913+ def __str__(self):
1914+ return self._get_error_message_string()
1915+
1916+ def __repr__(self):
1917+ return "<%s object with %s>" % (
1918+ self.__class__.__name__, self._get_error_code_string())
1919+
1920+ def _set_request_id(self, tree):
1921+ request_id_node = tree.find(".//RequestID")
1922+ if hasattr(request_id_node, "text"):
1923+ text = request_id_node.text
1924+ if text:
1925+ self.request_id = text
1926+
1927+ def _set_host_id(self, tree):
1928+ host_id = tree.find(".//HostID")
1929+ if hasattr(host_id, "text"):
1930+ text = host_id.text
1931+ if text:
1932+ self.host_id = text
1933+
1934+ def _get_error_code_string(self):
1935+ count = len(self.errors)
1936+ error_code = self.get_error_codes()
1937+ if count > 1:
1938+ return "Error count: %s" % error_code
1939+ else:
1940+ return "Error code: %s" % error_code
1941+
1942+ def _get_error_message_string(self):
1943+ count = len(self.errors)
1944+ error_message = self.get_error_messages()
1945+ if count > 1:
1946+ return "%s." % error_message
1947+ else:
1948+ return "Error Message: %s" % error_message
1949+
1950+ def _node_to_dict(self, node):
1951+ data = {}
1952+ for child in node:
1953+ if child.tag and child.text:
1954+ data[child.tag] = child.text
1955+ return data
1956+
1957+ def _check_for_html(self, tree):
1958+ if tree.tag == "html":
1959+ message = "Could not parse HTML in the response."
1960+ raise AWSResponseParseError(message)
1961+
1962+ def _set_400_error(self, tree):
1963+ """
1964+ This method needs to be implemented by subclasses.
1965+ """
1966+
1967+ def _set_500_error(self, tree):
1968+ self._set_request_id(tree)
1969+ self._set_host_id(tree)
1970+ data = self._node_to_dict(tree)
1971+ if data:
1972+ self.errors.append(data)
1973+
1974+ def parse(self, xml_bytes=""):
1975+ if not xml_bytes:
1976+ xml_bytes = self.original
1977+ self.original = xml_bytes
1978+ tree = XML(xml_bytes.strip())
1979+ self._check_for_html(tree)
1980+ self._set_request_id(tree)
1981+ if self.status:
1982+ status = int(self.status)
1983+ else:
1984+ status = 400
1985+ if status >= 500:
1986+ self._set_500_error(tree)
1987+ else:
1988+ self._set_400_error(tree)
1989+
1990+ def has_error(self, errorString):
1991+ for error in self.errors:
1992+ if errorString in error.values():
1993+ return True
1994+ return False
1995+
1996+ def get_error_codes(self):
1997+ count = len(self.errors)
1998+ if count > 1:
1999+ return count
2000+ elif count == 0:
2001+ return
2002+ else:
2003+ return self.errors[0]["Code"]
2004+
2005+ def get_error_messages(self):
2006+ count = len(self.errors)
2007+ if count > 1:
2008+ return "Multiple EC2 Errors"
2009+ elif count == 0:
2010+ return "Empty error list"
2011+ else:
2012+ return self.errors[0]["Message"]
2013+
2014
2015
2016 class AWSResponseParseError(Exception):
2017
2018=== added file 'txaws/meta.py'
2019--- txaws/meta.py 1970-01-01 00:00:00 +0000
2020+++ txaws/meta.py 2009-11-28 01:15:24 +0000
2021@@ -0,0 +1,10 @@
2022+display_name = "txAWS"
2023+library_name = "txaws"
2024+author = "txAWS Deelopers"
2025+author_email = "txaws-dev@lists.launchpad.net"
2026+license = "MIT"
2027+url = "http://launchpad.net/txaws"
2028+description = """
2029+Twisted-based Asynchronous Libraries for Amazon Web Services
2030+"""
2031+
2032
2033=== modified file 'txaws/s3/client.py'
2034--- txaws/s3/client.py 2009-11-28 01:15:24 +0000
2035+++ txaws/s3/client.py 2009-11-28 01:15:24 +0000
2036@@ -17,12 +17,17 @@
2037
2038 from epsilon.extime import Time
2039
2040-from txaws.client.base import BaseClient, BaseQuery
2041+from txaws.client.base import BaseClient, BaseQuery, error_wrapper
2042 from txaws.s3 import model
2043+from txaws.s3.exception import S3Error
2044 from txaws.service import AWSServiceEndpoint, S3_ENDPOINT
2045 from txaws.util import XML, calculate_md5
2046
2047
2048+def s3_error_wrapper(error):
2049+ error_wrapper(error, S3Error)
2050+
2051+
2052 class URLContext(object):
2053 """
2054 The hosts and the paths that form an S3 endpoint change depending upon the
2055@@ -129,6 +134,49 @@
2056 url_context = BucketURLContext(self.endpoint, bucket)
2057 return query.submit(url_context)
2058
2059+ def get_bucket(self, bucket):
2060+ """
2061+ Get a list of all the objects in a bucket.
2062+ """
2063+ query = self.query_factory(
2064+ action="GET", creds=self.creds, endpoint=self.endpoint,
2065+ bucket=bucket)
2066+ url_context = BucketURLContext(self.endpoint, bucket)
2067+ d = query.submit(url_context)
2068+ return d.addCallback(self._parse_get_bucket)
2069+
2070+ def _parse_get_bucket(self, xml_bytes):
2071+ root = XML(xml_bytes)
2072+ name = root.findtext("Name")
2073+ prefix = root.findtext("Prefix")
2074+ marker = root.findtext("Marker")
2075+ max_keys = root.findtext("MaxKeys")
2076+ is_truncated = root.findtext("IsTruncated")
2077+ contents = []
2078+
2079+ for content_data in root.findall("Contents"):
2080+ key = content_data.findtext("Key")
2081+ date_text = content_data.findtext("LastModified")
2082+ modification_date = Time.fromISO8601TimeAndDate(
2083+ date_text).asDatetime()
2084+ etag = content_data.findtext("ETag")
2085+ size = content_data.findtext("Size")
2086+ storage_class = content_data.findtext("StorageClass")
2087+ owner_id = content_data.findtext("Owner/ID")
2088+ owner_display_name = content_data.findtext("Owner/DisplayName")
2089+ owner = model.ItemOwner(owner_id, owner_display_name)
2090+ content_item = model.BucketItem(
2091+ key, modification_date, etag, size, storage_class, owner)
2092+ contents.append(content_item)
2093+
2094+ common_prefixes = []
2095+ for prefix_data in root.findall("CommonPrefixes"):
2096+ common_prefixes.append(prefix_data.text)
2097+
2098+ return model.BucketListing(
2099+ name, prefix, marker, max_keys, is_truncated, contents,
2100+ common_prefixes)
2101+
2102 def put_object(self, bucket, object_name, data, content_type=None,
2103 metadata={}):
2104 """
2105@@ -190,6 +238,10 @@
2106 self.endpoint.set_method(self.action)
2107
2108 def set_content_type(self):
2109+ """
2110+ Set the content type based on the file extension used in the object
2111+ name.
2112+ """
2113 if self.object_name and not self.content_type:
2114 # XXX nothing is currently done with the encoding... we may
2115 # need to in the future
2116@@ -197,6 +249,9 @@
2117 self.object_name, strict=False)
2118
2119 def get_headers(self):
2120+ """
2121+ Build the list of headers needed in order to perform S3 operations.
2122+ """
2123 headers = {"Content-Length": len(self.data),
2124 "Content-MD5": calculate_md5(self.data),
2125 "Date": self.date}
2126@@ -214,6 +269,9 @@
2127 return headers
2128
2129 def get_canonicalized_amz_headers(self, headers):
2130+ """
2131+ Get the headers defined by Amazon S3.
2132+ """
2133 headers = [
2134 (name.lower(), value) for name, value in headers.iteritems()
2135 if name.lower().startswith("x-amz-")]
2136@@ -224,6 +282,9 @@
2137 return "".join("%s:%s\n" % (name, value) for name, value in headers)
2138
2139 def get_canonicalized_resource(self):
2140+ """
2141+ Get an S3 resource path.
2142+ """
2143 resource = "/"
2144 if self.bucket:
2145 resource += self.bucket
2146@@ -232,7 +293,7 @@
2147 return resource
2148
2149 def sign(self, headers):
2150-
2151+ """Sign this query using its built in credentials."""
2152 text = (self.action + "\n" +
2153 headers.get("Content-MD5", "") + "\n" +
2154 headers.get("Content-Type", "") + "\n" +
2155@@ -242,14 +303,14 @@
2156 return self.creds.sign(text, hash_type="sha1")
2157
2158 def submit(self, url_context=None):
2159+ """Submit this query.
2160+
2161+ @return: A deferred from get_page
2162+ """
2163 if not url_context:
2164 url_context = URLContext(
2165 self.endpoint, self.bucket, self.object_name)
2166 d = self.get_page(
2167 url_context.get_url(), method=self.action, postdata=self.data,
2168 headers=self.get_headers())
2169- # XXX - we need an error wrapper like we have for ec2... but let's
2170- # wait until the new error-wrapper branch has landed, and possibly
2171- # generalize a base class for all clients.
2172- #d.addErrback(s3_error_wrapper)
2173- return d
2174+ return d.addErrback(s3_error_wrapper)
2175
2176=== added file 'txaws/s3/exception.py'
2177--- txaws/s3/exception.py 1970-01-01 00:00:00 +0000
2178+++ txaws/s3/exception.py 2009-11-28 01:15:24 +0000
2179@@ -0,0 +1,21 @@
2180+# Copyright (c) 2009 Canonical Ltd <duncan.mcgreggor@canonical.com>
2181+# Licenced under the txaws licence available at /LICENSE in the txaws source.
2182+
2183+from txaws.exception import AWSError
2184+
2185+
2186+class S3Error(AWSError):
2187+ """
2188+ A error class providing custom methods on S3 errors.
2189+ """
2190+ def _set_400_error(self, tree):
2191+ if tree.tag.lower() == "error":
2192+ data = self._node_to_dict(tree)
2193+ if data:
2194+ self.errors.append(data)
2195+
2196+ def get_error_code(self, *args, **kwargs):
2197+ return super(S3Error, self).get_error_codes(*args, **kwargs)
2198+
2199+ def get_error_message(self, *args, **kwargs):
2200+ return super(S3Error, self).get_error_messages(*args, **kwargs)
2201
2202=== modified file 'txaws/s3/model.py'
2203--- txaws/s3/model.py 2009-11-28 01:15:24 +0000
2204+++ txaws/s3/model.py 2009-11-28 01:15:24 +0000
2205@@ -1,11 +1,47 @@
2206 class Bucket(object):
2207- """An Amazon S3 storage bucket."""
2208-
2209+ """
2210+ An Amazon S3 storage bucket.
2211+ """
2212 def __init__(self, name, creation_date):
2213 self.name = name
2214 self.creation_date = creation_date
2215
2216
2217+class ItemOwner(object):
2218+ """
2219+ The owner of a content item.
2220+ """
2221+ def __init__(self, id, display_name):
2222+ self.id = id
2223+ self.display_name = display_name
2224+
2225+
2226+class BucketItem(object):
2227+ """
2228+ The contents of an Amazon S3 bucket.
2229+ """
2230+ def __init__(self, key, modification_date, etag, size, storage_class,
2231+ owner=None):
2232+ self.key = key
2233+ self.modification_date = modification_date
2234+ self.etag = etag
2235+ self.size = size
2236+ self.storage_class = storage_class
2237+ self.owner = owner
2238+
2239+
2240+class BucketListing(object):
2241+ def __init__(self, name, prefix, marker, max_keys, is_truncated,
2242+ contents=None, common_prefixes=None):
2243+ self.name = name
2244+ self.prefix = prefix
2245+ self.marker = marker
2246+ self.max_keys = max_keys
2247+ self.is_truncated = is_truncated
2248+ self.contents = contents
2249+ self.common_prefixes = common_prefixes
2250+
2251+
2252 class FileChunk(object):
2253 """
2254 An Amazon S3 file chunk.
2255@@ -13,4 +49,3 @@
2256 S3 returns file chunks, 10 MB at a time, until the entire file is returned.
2257 These chunks need to be assembled once they are all returned.
2258 """
2259-
2260
2261=== modified file 'txaws/s3/tests/test_client.py'
2262--- txaws/s3/tests/test_client.py 2009-11-28 01:15:24 +0000
2263+++ txaws/s3/tests/test_client.py 2009-11-28 01:15:24 +0000
2264@@ -129,6 +129,51 @@
2265 s3 = client.S3Client(creds, query_factory=StubQuery)
2266 return s3.create_bucket("mybucket")
2267
2268+ def test_get_bucket(self):
2269+
2270+ class StubQuery(client.Query):
2271+
2272+ def __init__(query, action, creds, endpoint, bucket=None):
2273+ super(StubQuery, query).__init__(
2274+ action=action, creds=creds, bucket=bucket)
2275+ self.assertEquals(action, "GET")
2276+ self.assertEqual(creds.access_key, "foo")
2277+ self.assertEqual(creds.secret_key, "bar")
2278+ self.assertEqual(query.bucket, "mybucket")
2279+ self.assertEqual(query.object_name, None)
2280+ self.assertEqual(query.data, "")
2281+ self.assertEqual(query.metadata, {})
2282+
2283+ def submit(query, url_context=None):
2284+ return succeed(payload.sample_get_bucket_result)
2285+
2286+ def check_results(listing):
2287+ self.assertEquals(listing.name, "mybucket")
2288+ self.assertEquals(listing.prefix, "N")
2289+ self.assertEquals(listing.marker, "Ned")
2290+ self.assertEquals(listing.max_keys, "40")
2291+ self.assertEquals(listing.is_truncated, "false")
2292+ self.assertEquals(len(listing.contents), 2)
2293+ content1 = listing.contents[0]
2294+ self.assertEquals(content1.key, "Nelson")
2295+ self.assertEquals(
2296+ content1.modification_date.timetuple(),
2297+ (2006, 1, 1, 12, 0, 0, 6, 1, 0))
2298+ self.assertEquals(
2299+ content1.etag, '"828ef3fdfa96f00ad9f27c383fc9ac7f"')
2300+ self.assertEquals(content1.size, "5")
2301+ self.assertEquals(content1.storage_class, "STANDARD")
2302+ owner = content1.owner
2303+ self.assertEquals(
2304+ owner.id,
2305+ "bcaf1ffd86f41caff1a493dc2ad8c2c281e37522a640e161ca5fb16fd081034f")
2306+ self.assertEquals(owner.display_name, "webfile")
2307+
2308+ creds = AWSCredentials("foo", "bar")
2309+ s3 = client.S3Client(creds, query_factory=StubQuery)
2310+ d = s3.get_bucket("mybucket")
2311+ return d.addCallback(check_results)
2312+
2313 def test_delete_bucket(self):
2314
2315 class StubQuery(client.Query):
2316@@ -344,7 +389,7 @@
2317 query = client.Query(action="PUT", creds=self.creds)
2318 signed = query.sign({})
2319 self.assertEquals(signed, "H6UJCNHizzXZCGPl7wM6nL6tQdo=")
2320-
2321+
2322 def test_object_query(self):
2323 """
2324 Test that a request addressing an object is created correctly.
2325@@ -421,7 +466,7 @@
2326
2327 headers = query.get_headers()
2328 self.assertEqual(
2329- headers["Authorization"],
2330+ headers["Authorization"],
2331 "AWS fookeyid:TESTINGSIG=")
2332
2333
2334
2335=== added file 'txaws/s3/tests/test_exception.py'
2336--- txaws/s3/tests/test_exception.py 1970-01-01 00:00:00 +0000
2337+++ txaws/s3/tests/test_exception.py 2009-11-28 01:15:24 +0000
2338@@ -0,0 +1,62 @@
2339+# Copyright (c) 2009 Canonical Ltd <duncan.mcgreggor@canonical.com>
2340+# Licenced under the txaws licence available at /LICENSE in the txaws source.
2341+
2342+from twisted.trial.unittest import TestCase
2343+
2344+from txaws.s3.exception import S3Error
2345+from txaws.testing import payload
2346+from txaws.util import XML
2347+
2348+
2349+REQUEST_ID = "0ef9fc37-6230-4d81-b2e6-1b36277d4247"
2350+
2351+
2352+class S3ErrorTestCase(TestCase):
2353+
2354+ def test_set_400_error(self):
2355+ xml = "<Error><Code>1</Code><Message>2</Message></Error>"
2356+ error = S3Error("<dummy />")
2357+ error._set_400_error(XML(xml))
2358+ self.assertEquals(error.errors[0]["Code"], "1")
2359+ self.assertEquals(error.errors[0]["Message"], "2")
2360+
2361+ def test_get_error_code(self):
2362+ error = S3Error(payload.sample_s3_invalid_access_key_result)
2363+ self.assertEquals(error.get_error_code(), "InvalidAccessKeyId")
2364+
2365+ def test_get_error_message(self):
2366+ error = S3Error(payload.sample_s3_invalid_access_key_result)
2367+ self.assertEquals(
2368+ error.get_error_message(),
2369+ ("The AWS Access Key Id you provided does not exist in our "
2370+ "records."))
2371+
2372+ def test_error_count(self):
2373+ error = S3Error(payload.sample_s3_invalid_access_key_result)
2374+ self.assertEquals(len(error.errors), 1)
2375+
2376+ def test_error_repr(self):
2377+ error = S3Error(payload.sample_s3_invalid_access_key_result)
2378+ self.assertEquals(
2379+ repr(error),
2380+ "<S3Error object with Error code: InvalidAccessKeyId>")
2381+
2382+ def test_signature_mismatch_result(self):
2383+ error = S3Error(payload.sample_s3_signature_mismatch)
2384+ self.assertEquals(
2385+ error.get_error_messages(),
2386+ ("The request signature we calculated does not match the "
2387+ "signature you provided. Check your key and signing method."))
2388+
2389+ def test_invalid_access_key_result(self):
2390+ error = S3Error(payload.sample_s3_invalid_access_key_result)
2391+ self.assertEquals(
2392+ error.get_error_messages(),
2393+ ("The AWS Access Key Id you provided does not exist in our "
2394+ "records."))
2395+
2396+ def test_internal_error_result(self):
2397+ error = S3Error(payload.sample_server_internal_error_result)
2398+ self.assertEquals(
2399+ error.get_error_messages(),
2400+ "We encountered an internal error. Please try again.")
2401
2402=== added file 'txaws/script.py'
2403--- txaws/script.py 1970-01-01 00:00:00 +0000
2404+++ txaws/script.py 2009-11-28 01:15:24 +0000
2405@@ -0,0 +1,42 @@
2406+from optparse import OptionParser
2407+
2408+from txaws import meta
2409+from txaws import version
2410+
2411+
2412+# XXX Once we start adding script that require conflicting options, we'll need
2413+# multiple parsers and option dispatching...
2414+def parse_options(usage):
2415+ parser = OptionParser(usage, version="%s %s" % (
2416+ meta.display_name, version.txaws))
2417+ parser.add_option(
2418+ "-a", "--access-key", dest="access_key", help="access key ID")
2419+ parser.add_option(
2420+ "-s", "--secret-key", dest="secret_key", help="access secret key")
2421+ parser.add_option(
2422+ "-r", "--region", dest="region", help="US or EU (valid for AWS only)")
2423+ parser.add_option(
2424+ "-U", "--url", dest="url", help="service URL/endpoint")
2425+ parser.add_option(
2426+ "-b", "--bucket", dest="bucket", help="name of the bucket")
2427+ parser.add_option(
2428+ "-o", "--object-name", dest="object_name", help="name of the object")
2429+ parser.add_option(
2430+ "-d", "--object-data", dest="object_data",
2431+ help="content data of the object")
2432+ parser.add_option(
2433+ "--object-file", dest="object_filename",
2434+ help=("the path to the file that will be saved as an object; if "
2435+ "provided, the --object-name and --object-data options are "
2436+ "not necessary"))
2437+ parser.add_option(
2438+ "-c", "--content-type", dest="content_type",
2439+ help="content type of the object")
2440+ options, args = parser.parse_args()
2441+ if not (options.access_key and options.secret_key):
2442+ parser.error(
2443+ "both the access key ID and the secret key must be supplied")
2444+ region = options.region
2445+ if region and region.upper() not in ["US", "EU"]:
2446+ parser.error("region must be one of 'US' or 'EU'")
2447+ return (options, args)
2448
2449=== modified file 'txaws/service.py'
2450--- txaws/service.py 2009-11-28 01:15:24 +0000
2451+++ txaws/service.py 2009-11-28 01:15:24 +0000
2452@@ -77,19 +77,22 @@
2453 """
2454 # XXX update unit test to check for both ec2 and s3 endpoints
2455 def __init__(self, creds=None, access_key="", secret_key="",
2456- region=REGION_US, ec2_endpoint="", s3_endpoint=""):
2457+ region=REGION_US, uri="", ec2_uri="", s3_uri=""):
2458 if not creds:
2459 creds = AWSCredentials(access_key, secret_key)
2460 self.creds = creds
2461- if not ec2_endpoint and region == REGION_US:
2462- ec2_endpoint = EC2_ENDPOINT_US
2463- elif not ec2_endpoint and region == REGION_EU:
2464- ec2_endpoint = EC2_ENDPOINT_EU
2465- if not s3_endpoint:
2466- s3_endpoint = S3_ENDPOINT
2467+ # Provide backwards compatibility for the "uri" parameter.
2468+ if uri and not ec2_uri:
2469+ ec2_uri = uri
2470+ if not ec2_uri and region == REGION_US:
2471+ ec2_uri = EC2_ENDPOINT_US
2472+ elif not ec2_uri and region == REGION_EU:
2473+ ec2_uri = EC2_ENDPOINT_EU
2474+ if not s3_uri:
2475+ s3_uri = S3_ENDPOINT
2476 self._clients = {}
2477- self.ec2_endpoint = AWSServiceEndpoint(uri=ec2_endpoint)
2478- self.s3_endpoint = AWSServiceEndpoint(uri=s3_endpoint)
2479+ self.ec2_endpoint = AWSServiceEndpoint(uri=ec2_uri)
2480+ self.s3_endpoint = AWSServiceEndpoint(uri=s3_uri)
2481
2482 def get_client(self, cls, purge_cache=False, *args, **kwds):
2483 """
2484
2485=== modified file 'txaws/testing/payload.py'
2486--- txaws/testing/payload.py 2009-11-28 01:15:24 +0000
2487+++ txaws/testing/payload.py 2009-11-28 01:15:24 +0000
2488@@ -656,6 +656,31 @@
2489 """
2490
2491
2492+sample_restricted_resource_result = """\
2493+<?xml version="1.0"?>
2494+<Response>
2495+ <Errors>
2496+ <Error>
2497+ <Code>AuthFailure</Code>
2498+ <Message>Unauthorized attempt to access restricted resource</Message>
2499+ </Error>
2500+ </Errors>
2501+ <RequestID>a99e832e-e6e0-416a-9a35-81798ea521b4</RequestID>
2502+</Response>
2503+"""
2504+
2505+
2506+sample_server_internal_error_result = """\
2507+<?xml version="1.0" encoding="UTF-8"?>
2508+<Error>
2509+ <Code>InternalError</Code>
2510+ <Message>We encountered an internal error. Please try again.</Message>
2511+ <RequestID>A2A7E5395E27DFBB</RequestID>
2512+ <HostID>f691zulHNsUqonsZkjhILnvWwD3ZnmOM4ObM1wXTc6xuS3GzPmjArp8QC/sGsn6K</HostID>
2513+</Error>
2514+"""
2515+
2516+
2517 sample_list_buckets_result = """\
2518 <?xml version="1.0" encoding="UTF-8"?>
2519 <ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/%s/">
2520@@ -677,6 +702,39 @@
2521 """ % (version.s3_api,)
2522
2523
2524+sample_get_bucket_result = """\
2525+<?xml version="1.0" encoding="UTF-8"?>
2526+<ListBucketResult xmlns="http://s3.amazonaws.com/doc/%s/">
2527+ <Name>mybucket</Name>
2528+ <Prefix>N</Prefix>
2529+ <Marker>Ned</Marker>
2530+ <MaxKeys>40</MaxKeys>
2531+ <IsTruncated>false</IsTruncated>
2532+ <Contents>
2533+ <Key>Nelson</Key>
2534+ <LastModified>2006-01-01T12:00:00.000Z</LastModified>
2535+ <ETag>&quot;828ef3fdfa96f00ad9f27c383fc9ac7f&quot;</ETag>
2536+ <Size>5</Size>
2537+ <StorageClass>STANDARD</StorageClass>
2538+ <Owner>
2539+ <ID>bcaf1ffd86f41caff1a493dc2ad8c2c281e37522a640e161ca5fb16fd081034f</ID>
2540+ <DisplayName>webfile</DisplayName>
2541+ </Owner>
2542+ </Contents>
2543+ <Contents>
2544+ <Key>Neo</Key>
2545+ <LastModified>2006-01-01T12:00:00.000Z</LastModified>
2546+ <ETag>&quot;828ef3fdfa96f00ad9f27c383fc9ac7f&quot;</ETag>
2547+ <Size>4</Size>
2548+ <StorageClass>STANDARD</StorageClass>
2549+ <Owner>
2550+ <ID>bcaf1ffd86f41caff1a493dc2ad8c2c281e37522a640e161ca5fb16fd081034f</ID>
2551+ <DisplayName>webfile</DisplayName>
2552+ </Owner>
2553+ </Contents>
2554+</ListBucketResult>
2555+""" % (version.s3_api,)
2556+
2557 sample_s3_signature_mismatch = """\
2558 <?xml version="1.0" encoding="UTF-8"?>
2559 <Error>
2560@@ -692,26 +750,13 @@
2561 """
2562
2563
2564-sample_server_internal_error_result = """\
2565+sample_s3_invalid_access_key_result = """\
2566 <?xml version="1.0" encoding="UTF-8"?>
2567 <Error>
2568- <Code>InternalError</Code>
2569- <Message>We encountered an internal error. Please try again.</Message>
2570- <RequestID>A2A7E5395E27DFBB</RequestID>
2571- <HostID>f691zulHNsUqonsZkjhILnvWwD3ZnmOM4ObM1wXTc6xuS3GzPmjArp8QC/sGsn6K</HostID>
2572+ <Code>InvalidAccessKeyId</Code>
2573+ <Message>The AWS Access Key Id you provided does not exist in our records.</Message>
2574+ <RequestId>0223AD81A94821CE</RequestId>
2575+ <HostId>HAw5g9P1VkN8ztgLKFTK20CY5LmCfTwXcSths1O7UQV6NuJx2P4tmFnpuOsziwOE</HostId>
2576+ <AWSAccessKeyId>SOMEKEYID</AWSAccessKeyId>
2577 </Error>
2578 """
2579-
2580-
2581-sample_restricted_resource_result = """\
2582-<?xml version="1.0"?>
2583-<Response>
2584- <Errors>
2585- <Error>
2586- <Code>AuthFailure</Code>
2587- <Message>Unauthorized attempt to access restricted resource</Message>
2588- </Error>
2589- </Errors>
2590- <RequestID>a99e832e-e6e0-416a-9a35-81798ea521b4</RequestID>
2591-</Response>
2592-"""
2593
2594=== added file 'txaws/tests/test_exception.py'
2595--- txaws/tests/test_exception.py 1970-01-01 00:00:00 +0000
2596+++ txaws/tests/test_exception.py 2009-11-28 01:15:24 +0000
2597@@ -0,0 +1,114 @@
2598+# Copyright (c) 2009 Canonical Ltd <duncan.mcgreggor@canonical.com>
2599+# Licenced under the txaws licence available at /LICENSE in the txaws source.
2600+
2601+from twisted.trial.unittest import TestCase
2602+
2603+from txaws.exception import AWSError
2604+from txaws.exception import AWSResponseParseError
2605+from txaws.util import XML
2606+
2607+
2608+REQUEST_ID = "0ef9fc37-6230-4d81-b2e6-1b36277d4247"
2609+
2610+
2611+class AWSErrorTestCase(TestCase):
2612+
2613+ def test_creation(self):
2614+ error = AWSError("<dummy1 />", 500, "Server Error", "<dummy2 />")
2615+ self.assertEquals(error.status, 500)
2616+ self.assertEquals(error.response, "<dummy2 />")
2617+ self.assertEquals(error.original, "<dummy1 />")
2618+ self.assertEquals(error.errors, [])
2619+ self.assertEquals(error.request_id, "")
2620+
2621+ def test_node_to_dict(self):
2622+ xml = "<parent><child1>text1</child1><child2>text2</child2></parent>"
2623+ error = AWSError("<dummy />")
2624+ data = error._node_to_dict(XML(xml))
2625+ self.assertEquals(data, {"child1": "text1", "child2": "text2"})
2626+
2627+ def test_set_request_id(self):
2628+ xml = "<a><b /><RequestID>%s</RequestID></a>" % REQUEST_ID
2629+ error = AWSError("<dummy />")
2630+ error._set_request_id(XML(xml))
2631+ self.assertEquals(error.request_id, REQUEST_ID)
2632+
2633+ def test_set_host_id(self):
2634+ host_id = "ASD@#FDG$E%FG"
2635+ xml = "<a><b /><HostID>%s</HostID></a>" % host_id
2636+ error = AWSError("<dummy />")
2637+ error._set_host_id(XML(xml))
2638+ self.assertEquals(error.host_id, host_id)
2639+
2640+ def test_set_empty_errors(self):
2641+ xml = "<a><Errors /><b /></a>"
2642+ error = AWSError("<dummy />")
2643+ error._set_500_error(XML(xml))
2644+ self.assertEquals(error.errors, [])
2645+
2646+ def test_set_empty_error(self):
2647+ xml = "<a><Errors><Error /><Error /></Errors><b /></a>"
2648+ error = AWSError("<dummy />")
2649+ error._set_500_error(XML(xml))
2650+ self.assertEquals(error.errors, [])
2651+
2652+ def test_parse_without_xml(self):
2653+ xml = "<dummy />"
2654+ error = AWSError(xml)
2655+ error.parse()
2656+ self.assertEquals(error.original, xml)
2657+
2658+ def test_parse_with_xml(self):
2659+ xml1 = "<dummy1 />"
2660+ xml2 = "<dummy2 />"
2661+ error = AWSError(xml1)
2662+ error.parse(xml2)
2663+ self.assertEquals(error.original, xml2)
2664+
2665+ def test_parse_html(self):
2666+ xml = "<html><body>a page</body></html>"
2667+ self.assertRaises(AWSResponseParseError, AWSError, xml)
2668+
2669+ def test_empty_xml(self):
2670+ self.assertRaises(ValueError, AWSError, "")
2671+
2672+ def test_no_request_id(self):
2673+ errors = "<Errors><Error><Code /><Message /></Error></Errors>"
2674+ xml = "<Response>%s<RequestID /></Response>" % errors
2675+ error = AWSError(xml)
2676+ self.assertEquals(error.request_id, "")
2677+
2678+ def test_no_request_id_node(self):
2679+ errors = "<Errors><Error><Code /><Message /></Error></Errors>"
2680+ xml = "<Response>%s</Response>" % errors
2681+ error = AWSError(xml)
2682+ self.assertEquals(error.request_id, "")
2683+
2684+ def test_no_errors_node(self):
2685+ xml = "<Response><RequestID /></Response>"
2686+ error = AWSError(xml)
2687+ self.assertEquals(error.errors, [])
2688+
2689+ def test_no_error_node(self):
2690+ xml = "<Response><Errors /><RequestID /></Response>"
2691+ error = AWSError(xml)
2692+ self.assertEquals(error.errors, [])
2693+
2694+ def test_no_error_code_node(self):
2695+ errors = "<Errors><Error><Message /></Error></Errors>"
2696+ xml = "<Response>%s<RequestID /></Response>" % errors
2697+ error = AWSError(xml)
2698+ self.assertEquals(error.errors, [])
2699+
2700+ def test_no_error_message_node(self):
2701+ errors = "<Errors><Error><Code /></Error></Errors>"
2702+ xml = "<Response>%s<RequestID /></Response>" % errors
2703+ error = AWSError(xml)
2704+ self.assertEquals(error.errors, [])
2705+
2706+ def test_set_500_error(self):
2707+ xml = "<Error><Code>500</Code><Message>Oops</Message></Error>"
2708+ error = AWSError("<dummy />")
2709+ error._set_500_error(XML(xml))
2710+ self.assertEquals(error.errors[0]["Code"], "500")
2711+ self.assertEquals(error.errors[0]["Message"], "Oops")
2712
2713=== modified file 'txaws/tests/test_service.py'
2714--- txaws/tests/test_service.py 2009-11-28 01:15:24 +0000
2715+++ txaws/tests/test_service.py 2009-11-28 01:15:24 +0000
2716@@ -97,12 +97,17 @@
2717
2718 def test_creation_with_uri(self):
2719 region = AWSServiceRegion(
2720- creds=self.creds, ec2_endpoint="http://foo/bar")
2721+ creds=self.creds, ec2_uri="http://foo/bar")
2722+ self.assertEquals(region.ec2_endpoint.get_uri(), "http://foo/bar")
2723+
2724+ def test_creation_with_uri_backwards_compatible(self):
2725+ region = AWSServiceRegion(
2726+ creds=self.creds, uri="http://foo/bar")
2727 self.assertEquals(region.ec2_endpoint.get_uri(), "http://foo/bar")
2728
2729 def test_creation_with_uri_and_region(self):
2730 region = AWSServiceRegion(
2731- creds=self.creds, region=REGION_EU, ec2_endpoint="http://foo/bar")
2732+ creds=self.creds, region=REGION_EU, ec2_uri="http://foo/bar")
2733 self.assertEquals(region.ec2_endpoint.get_uri(), "http://foo/bar")
2734
2735 def test_creation_with_region_override(self):
2736
2737=== modified file 'txaws/util.py'
2738--- txaws/util.py 2009-11-28 01:15:24 +0000
2739+++ txaws/util.py 2009-11-28 01:15:24 +0000
2740@@ -94,3 +94,33 @@
2741 if path == "":
2742 path = "/"
2743 return (str(scheme), str(host), port, str(path))
2744+
2745+
2746+def get_exitcode_reactor():
2747+ """
2748+ This is only neccesary until a fix like the one outlined here is
2749+ implemented for Twisted:
2750+ http://twistedmatrix.com/trac/ticket/2182
2751+ """
2752+ from twisted.internet.main import installReactor
2753+ from twisted.internet.selectreactor import SelectReactor
2754+
2755+ class ExitCodeReactor(SelectReactor):
2756+
2757+ def stop(self, exitStatus=0):
2758+ super(ExitCodeReactor, self).stop()
2759+ self.exitStatus = exitStatus
2760+
2761+ def run(self, *args, **kwargs):
2762+ super(ExitCodeReactor, self).run(*args, **kwargs)
2763+ return self.exitStatus
2764+
2765+ reactor = ExitCodeReactor()
2766+ installReactor(reactor)
2767+ return reactor
2768+
2769+
2770+try:
2771+ reactor = get_exitcode_reactor()
2772+except:
2773+ from twisted.internet import reactor

Subscribers

People subscribed via source and target branches

to all changes: