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
=== modified file 'LICENSE'
--- LICENSE 2008-07-06 22:51:54 +0000
+++ LICENSE 2009-11-28 01:15:24 +0000
@@ -1,3 +1,8 @@
1Copyright (C) 2008 Tristan Seligmann <mithrandi@mithrandi.net>
2Copyright (C) 2009 Robert Collins <robertc@robertcollins.net>
3Copyright (C) 2009 Canonical Ltd
4Copyright (C) 2009 Duncan McGreggor <oubiwann@adytum.us>
5
1Permission is hereby granted, free of charge, to any person obtaining6Permission is hereby granted, free of charge, to any person obtaining
2a copy of this software and associated documentation files (the7a copy of this software and associated documentation files (the
3"Software"), to deal in the Software without restriction, including8"Software"), to deal in the Software without restriction, including
@@ -11,8 +16,8 @@
1116
12THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF18EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND19MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
15NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE20IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
16LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION21CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
17OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION22TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
18WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.23SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1924
=== modified file 'README'
--- README 2009-08-19 20:55:49 +0000
+++ README 2009-11-28 01:15:24 +0000
@@ -14,3 +14,9 @@
14* The txaws python package. (No installer at the moment)14* The txaws python package. (No installer at the moment)
1515
16* bin/aws-status, a GUI status program for aws resources.16* bin/aws-status, a GUI status program for aws resources.
17
18License
19-------
20
21txAWS is open source software, MIT License. See the LICENSE file for more
22details.
1723
=== added file 'bin/txaws-create-bucket'
--- bin/txaws-create-bucket 1970-01-01 00:00:00 +0000
+++ bin/txaws-create-bucket 2009-11-28 01:15:24 +0000
@@ -0,0 +1,42 @@
1#!/usr/bin/env python
2"""
3%prog [options]
4"""
5
6import sys
7
8from txaws.credentials import AWSCredentials
9from txaws.script import parse_options
10from txaws.service import AWSServiceRegion
11from txaws.util import reactor
12
13
14def printResults(results):
15 return 0
16
17
18def printError(error):
19 print error.value
20 return 1
21
22
23def finish(return_code):
24 reactor.stop(exitStatus=return_code)
25
26
27options, args = parse_options(__doc__.strip())
28if options.bucket is None:
29 print "Error Message: A bucket name is required."
30 sys.exit(1)
31creds = AWSCredentials(options.access_key, options.secret_key)
32region = AWSServiceRegion(
33 creds=creds, region=options.region, s3_endpoint=options.url)
34client = region.get_s3_client()
35
36d = client.create_bucket(options.bucket)
37d.addCallback(printResults)
38d.addErrback(printError)
39d.addCallback(finish)
40# We use a custom reactor so that we can return the exit status from
41# reactor.run().
42sys.exit(reactor.run())
043
=== added file 'bin/txaws-delete-bucket'
--- bin/txaws-delete-bucket 1970-01-01 00:00:00 +0000
+++ bin/txaws-delete-bucket 2009-11-28 01:15:24 +0000
@@ -0,0 +1,42 @@
1#!/usr/bin/env python
2"""
3%prog [options]
4"""
5
6import sys
7
8from txaws.credentials import AWSCredentials
9from txaws.script import parse_options
10from txaws.service import AWSServiceRegion
11from txaws.util import reactor
12
13
14def printResults(results):
15 return 0
16
17
18def printError(error):
19 print error.value
20 return 1
21
22
23def finish(return_code):
24 reactor.stop(exitStatus=return_code)
25
26
27options, args = parse_options(__doc__.strip())
28if options.bucket is None:
29 print "Error Message: A bucket name is required."
30 sys.exit(1)
31creds = AWSCredentials(options.access_key, options.secret_key)
32region = AWSServiceRegion(
33 creds=creds, region=options.region, s3_endpoint=options.url)
34client = region.get_s3_client()
35
36d = client.delete_bucket(options.bucket)
37d.addCallback(printResults)
38d.addErrback(printError)
39d.addCallback(finish)
40# We use a custom reactor so that we can return the exit status from
41# reactor.run().
42sys.exit(reactor.run())
043
=== added file 'bin/txaws-delete-object'
--- bin/txaws-delete-object 1970-01-01 00:00:00 +0000
+++ bin/txaws-delete-object 2009-11-28 01:15:24 +0000
@@ -0,0 +1,46 @@
1#!/usr/bin/env python
2"""
3%prog [options]
4"""
5
6import sys
7
8from txaws.credentials import AWSCredentials
9from txaws.script import parse_options
10from txaws.service import AWSServiceRegion
11from txaws.util import reactor
12
13
14def printResults(results):
15 print results
16 return 0
17
18
19def printError(error):
20 print error.value
21 return 1
22
23
24def finish(return_code):
25 reactor.stop(exitStatus=return_code)
26
27
28options, args = parse_options(__doc__.strip())
29if options.bucket is None:
30 print "Error Message: A bucket name is required."
31 sys.exit(1)
32if options.object_name is None:
33 print "Error Message: An object name is required."
34 sys.exit(1)
35creds = AWSCredentials(options.access_key, options.secret_key)
36region = AWSServiceRegion(
37 creds=creds, region=options.region, s3_endpoint=options.url)
38client = region.get_s3_client()
39
40d = client.delete_object(options.bucket, options.object_name)
41d.addCallback(printResults)
42d.addErrback(printError)
43d.addCallback(finish)
44# We use a custom reactor so that we can return the exit status from
45# reactor.run().
46sys.exit(reactor.run())
047
=== added file 'bin/txaws-get-bucket'
--- bin/txaws-get-bucket 1970-01-01 00:00:00 +0000
+++ bin/txaws-get-bucket 2009-11-28 01:15:24 +0000
@@ -0,0 +1,46 @@
1#!/usr/bin/env python
2"""
3%prog [options]
4"""
5
6import sys
7
8from txaws.credentials import AWSCredentials
9from txaws.script import parse_options
10from txaws.service import AWSServiceRegion
11from txaws.util import reactor
12
13
14def printResults(listing, bucket):
15 print "Contents of '%s' bucket:" % bucket
16 for item in listing.contents:
17 print "\t%s (last modified on %s)" % (item.key, item.modification_date)
18 print "Total items: %s\n" % len(listing.contents)
19 return 0
20
21
22def printError(error):
23 print error.value
24 return 1
25
26
27def finish(return_code):
28 reactor.stop(exitStatus=return_code)
29
30
31options, args = parse_options(__doc__.strip())
32if options.bucket is None:
33 print "Error Message: A bucket name is required."
34 sys.exit(1)
35creds = AWSCredentials(options.access_key, options.secret_key)
36region = AWSServiceRegion(
37 creds=creds, region=options.region, s3_endpoint=options.url)
38client = region.get_s3_client()
39
40d = client.get_bucket(options.bucket)
41d.addCallback(printResults, options.bucket)
42d.addErrback(printError)
43d.addCallback(finish)
44# We use a custom reactor so that we can return the exit status from
45# reactor.run().
46sys.exit(reactor.run())
047
=== added file 'bin/txaws-get-object'
--- bin/txaws-get-object 1970-01-01 00:00:00 +0000
+++ bin/txaws-get-object 2009-11-28 01:15:24 +0000
@@ -0,0 +1,46 @@
1#!/usr/bin/env python
2"""
3%prog [options]
4"""
5
6import sys
7
8from txaws.credentials import AWSCredentials
9from txaws.script import parse_options
10from txaws.service import AWSServiceRegion
11from txaws.util import reactor
12
13
14def printResults(results):
15 print results
16 return 0
17
18
19def printError(error):
20 print error.value
21 return 1
22
23
24def finish(return_code):
25 reactor.stop(exitStatus=return_code)
26
27
28options, args = parse_options(__doc__.strip())
29if options.bucket is None:
30 print "Error Message: A bucket name is required."
31 sys.exit(1)
32if options.object_name is None:
33 print "Error Message: An object name is required."
34 sys.exit(1)
35creds = AWSCredentials(options.access_key, options.secret_key)
36region = AWSServiceRegion(
37 creds=creds, region=options.region, s3_endpoint=options.url)
38client = region.get_s3_client()
39
40d = client.get_object(options.bucket, options.object_name)
41d.addCallback(printResults)
42d.addErrback(printError)
43d.addCallback(finish)
44# We use a custom reactor so that we can return the exit status from
45# reactor.run().
46sys.exit(reactor.run())
047
=== added file 'bin/txaws-head-object'
--- bin/txaws-head-object 1970-01-01 00:00:00 +0000
+++ bin/txaws-head-object 2009-11-28 01:15:24 +0000
@@ -0,0 +1,47 @@
1#!/usr/bin/env python
2"""
3%prog [options]
4"""
5
6import sys
7from pprint import pprint
8
9from txaws.credentials import AWSCredentials
10from txaws.script import parse_options
11from txaws.service import AWSServiceRegion
12from txaws.util import reactor
13
14
15def printResults(results):
16 pprint(results)
17 return 0
18
19
20def printError(error):
21 print error.value
22 return 1
23
24
25def finish(return_code):
26 reactor.stop(exitStatus=return_code)
27
28
29options, args = parse_options(__doc__.strip())
30if options.bucket is None:
31 print "Error Message: A bucket name is required."
32 sys.exit(1)
33if options.object_name is None:
34 print "Error Message: An object name is required."
35 sys.exit(1)
36creds = AWSCredentials(options.access_key, options.secret_key)
37region = AWSServiceRegion(
38 creds=creds, region=options.region, s3_endpoint=options.url)
39client = region.get_s3_client()
40
41d = client.head_object(options.bucket, options.object_name)
42d.addCallback(printResults)
43d.addErrback(printError)
44d.addCallback(finish)
45# We use a custom reactor so that we can return the exit status from
46# reactor.run().
47sys.exit(reactor.run())
048
=== added file 'bin/txaws-list-buckets'
--- bin/txaws-list-buckets 1970-01-01 00:00:00 +0000
+++ bin/txaws-list-buckets 2009-11-28 01:15:24 +0000
@@ -0,0 +1,43 @@
1#!/usr/bin/env python
2"""
3%prog [options]
4"""
5
6import sys
7
8from txaws.credentials import AWSCredentials
9from txaws.script import parse_options
10from txaws.service import AWSServiceRegion
11from txaws.util import reactor
12
13
14def printResults(results):
15 print "\nBuckets:"
16 for bucket in results:
17 print "\t%s (created on %s)" % (bucket.name, bucket.creation_date)
18 print "Total buckets: %s\n" % len(list(results))
19 return 0
20
21
22def printError(error):
23 print error.value
24 return 1
25
26
27def finish(return_code):
28 reactor.stop(exitStatus=return_code)
29
30
31options, args = parse_options(__doc__.strip())
32creds = AWSCredentials(options.access_key, options.secret_key)
33region = AWSServiceRegion(
34 creds=creds, region=options.region, s3_endpoint=options.url)
35client = region.get_s3_client()
36
37d = client.list_buckets()
38d.addCallback(printResults)
39d.addErrback(printError)
40d.addCallback(finish)
41# We use a custom reactor so that we can return the exit status from
42# reactor.run().
43sys.exit(reactor.run())
044
=== added file 'bin/txaws-put-object'
--- bin/txaws-put-object 1970-01-01 00:00:00 +0000
+++ bin/txaws-put-object 2009-11-28 01:15:24 +0000
@@ -0,0 +1,56 @@
1#!/usr/bin/env python
2"""
3%prog [options]
4"""
5
6import os
7import sys
8
9from txaws.credentials import AWSCredentials
10from txaws.script import parse_options
11from txaws.service import AWSServiceRegion
12from txaws.util import reactor
13
14
15def printResults(results):
16 return 0
17
18
19def printError(error):
20 print error.value
21 return 1
22
23
24def finish(return_code):
25 reactor.stop(exitStatus=return_code)
26
27
28options, args = parse_options(__doc__.strip())
29if options.bucket is None:
30 print "Error Message: A bucket name is required."
31 sys.exit(1)
32filename = options.object_filename
33if filename:
34 options.object_name = os.path.basename(filename)
35 try:
36 options.object_data = open(filename).read()
37 except Exception, error:
38 print error
39 sys.exit(1)
40elif options.object_name is None:
41 print "Error Message: An object name is required."
42 sys.exit(1)
43creds = AWSCredentials(options.access_key, options.secret_key)
44region = AWSServiceRegion(
45 creds=creds, region=options.region, s3_endpoint=options.url)
46client = region.get_s3_client()
47
48d = client.put_object(
49 options.bucket, options.object_name, options.object_data,
50 options.content_type)
51d.addCallback(printResults)
52d.addErrback(printError)
53d.addCallback(finish)
54# We use a custom reactor so that we can return the exit status from
55# reactor.run().
56sys.exit(reactor.run())
057
=== modified file 'txaws/client/base.py'
--- txaws/client/base.py 2009-11-28 01:15:24 +0000
+++ txaws/client/base.py 2009-11-28 01:15:24 +0000
@@ -1,11 +1,52 @@
1from xml.parsers.expat import ExpatError
2
1from twisted.internet import reactor, ssl3from twisted.internet import reactor, ssl
4from twisted.web import http
2from twisted.web.client import HTTPClientFactory5from twisted.web.client import HTTPClientFactory
6from twisted.web.error import Error as TwistedWebError
37
4from txaws.util import parse8from txaws.util import parse
5from txaws.credentials import AWSCredentials9from txaws.credentials import AWSCredentials
10from txaws.exception import AWSResponseParseError
6from txaws.service import AWSServiceEndpoint11from txaws.service import AWSServiceEndpoint
712
813
14def error_wrapper(error, errorClass):
15 """
16 We want to see all error messages from cloud services. Amazon's EC2 says
17 that their errors are accompanied either by a 400-series or 500-series HTTP
18 response code. As such, the first thing we want to do is check to see if
19 the error is in that range. If it is, we then need to see if the error
20 message is an EC2 one.
21
22 In the event that an error is not a Twisted web error nor an EC2 one, the
23 original exception is raised.
24 """
25 http_status = 0
26 if error.check(TwistedWebError):
27 xml_payload = error.value.response
28 if error.value.status:
29 http_status = int(error.value.status)
30 else:
31 error.raiseException()
32 if http_status >= 400:
33 if not xml_payload:
34 error.raiseException()
35 try:
36 fallback_error = errorClass(
37 xml_payload, error.value.status, error.value.message,
38 error.value.response)
39 except (ExpatError, AWSResponseParseError):
40 error_message = http.RESPONSES.get(http_status)
41 fallback_error = TwistedWebError(
42 http_status, error_message, error.value.response)
43 raise fallback_error
44 elif 200 <= http_status < 300:
45 return str(error.value)
46 else:
47 error.raiseException()
48
49
9class BaseClient(object):50class BaseClient(object):
10 """Create an AWS client.51 """Create an AWS client.
1152
1253
=== modified file 'txaws/client/tests/test_client.py'
--- txaws/client/tests/test_client.py 2009-11-28 01:15:24 +0000
+++ txaws/client/tests/test_client.py 2009-11-28 01:15:24 +0000
@@ -1,16 +1,55 @@
1import os1import os
22
3from twisted.internet import reactor3from twisted.internet import reactor
4from twisted.internet.error import ConnectionRefusedError
4from twisted.protocols.policies import WrappingFactory5from twisted.protocols.policies import WrappingFactory
5from twisted.python import log6from twisted.python import log
6from twisted.python.filepath import FilePath7from twisted.python.filepath import FilePath
8from twisted.python.failure import Failure
9from twisted.web import server, static
7from twisted.web.client import HTTPClientFactory10from twisted.web.client import HTTPClientFactory
8from twisted.web import server, static11from twisted.web.error import Error as TwistedWebError
912
10from txaws.client.base import BaseClient, BaseQuery13from txaws.client.base import BaseClient, BaseQuery, error_wrapper
11from txaws.testing.base import TXAWSTestCase14from txaws.testing.base import TXAWSTestCase
1215
1316
17class ErrorWrapperTestCase(TXAWSTestCase):
18
19 def test_204_no_content(self):
20 failure = Failure(TwistedWebError(204, "No content"))
21 wrapped = error_wrapper(failure, None)
22 self.assertEquals(wrapped, "204 No content")
23
24 def test_302_found(self):
25 # XXX I'm not sure we want to raise for 300s...
26 failure = Failure(TwistedWebError(302, "found"))
27 error = self.assertRaises(
28 Exception, error_wrapper, failure, None)
29 self.assertEquals(failure.type, type(error))
30 self.assertTrue(isinstance(error, TwistedWebError))
31 self.assertEquals(str(error), "302 found")
32
33 def test_500(self):
34 failure = Failure(TwistedWebError(500, "internal error"))
35 error = self.assertRaises(
36 Exception, error_wrapper, failure, None)
37 self.assertTrue(isinstance(error, TwistedWebError))
38 self.assertEquals(str(error), "500 internal error")
39
40 def test_timeout_error(self):
41 failure = Failure(Exception("timeout"))
42 error = self.assertRaises(Exception, error_wrapper, failure, None)
43 self.assertTrue(isinstance(error, Exception))
44 self.assertEquals(error.message, "timeout")
45
46 def test_connection_error(self):
47 failure = Failure(ConnectionRefusedError("timeout"))
48 error = self.assertRaises(
49 Exception, error_wrapper, failure, ConnectionRefusedError)
50 self.assertTrue(isinstance(error, ConnectionRefusedError))
51
52
14class BaseClientTestCase(TXAWSTestCase):53class BaseClientTestCase(TXAWSTestCase):
1554
16 def test_creation(self):55 def test_creation(self):
1756
=== modified file 'txaws/ec2/client.py'
--- txaws/ec2/client.py 2009-11-28 01:15:24 +0000
+++ txaws/ec2/client.py 2009-11-28 01:15:24 +0000
@@ -8,16 +8,11 @@
8from datetime import datetime8from datetime import datetime
9from urllib import quote9from urllib import quote
10from base64 import b64encode10from base64 import b64encode
11from xml.parsers.expat import ExpatError
12
13from twisted.web import http
14from twisted.web.error import Error as TwistedWebError
1511
16from txaws import version12from txaws import version
17from txaws.client.base import BaseClient, BaseQuery13from txaws.client.base import BaseClient, BaseQuery, error_wrapper
18from txaws.ec2 import model14from txaws.ec2 import model
19from txaws.ec2.exception import EC2Error15from txaws.ec2.exception import EC2Error
20from txaws.exception import AWSResponseParseError
21from txaws.util import iso8601time, XML16from txaws.util import iso8601time, XML
2217
2318
@@ -25,34 +20,7 @@
2520
2621
27def ec2_error_wrapper(error):22def ec2_error_wrapper(error):
28 """23 error_wrapper(error, EC2Error)
29 We want to see all error messages from cloud services. Amazon's EC2 says
30 that their errors are accompanied either by a 400-series or 500-series HTTP
31 response code. As such, the first thing we want to do is check to see if
32 the error is in that range. If it is, we then need to see if the error
33 message is an EC2 one.
34
35 In the event that an error is not a Twisted web error nor an EC2 one, the
36 original exception is raised.
37 """
38 http_status = 0
39 if error.check(TwistedWebError):
40 xml_payload = error.value.response
41 if error.value.status:
42 http_status = int(error.value.status)
43 else:
44 error.raiseException()
45 if http_status >= 400:
46 try:
47 fallback_error = EC2Error(xml_payload, error.value.status,
48 error.value.message, error.value.response)
49 except (ExpatError, AWSResponseParseError):
50 error_message = http.RESPONSES.get(http_status)
51 fallback_error = TwistedWebError(http_status, error_message,
52 error.value.response)
53 raise fallback_error
54 else:
55 error.raiseException()
5624
5725
58class EC2Client(BaseClient):26class EC2Client(BaseClient):
@@ -60,16 +28,17 @@
6028
61 def __init__(self, creds=None, endpoint=None, query_factory=None):29 def __init__(self, creds=None, endpoint=None, query_factory=None):
62 if query_factory is None:30 if query_factory is None:
63 self.query_factory = Query31 query_factory = Query
64 super(EC2Client, self).__init__(creds, endpoint, query_factory)32 super(EC2Client, self).__init__(creds, endpoint, query_factory)
6533
66 def describe_instances(self, *instance_ids):34 def describe_instances(self, *instance_ids):
67 """Describe current instances."""35 """Describe current instances."""
68 instanceset = {}36 instances= {}
69 for pos, instance_id in enumerate(instance_ids):37 for pos, instance_id in enumerate(instance_ids):
70 instanceset["InstanceId.%d" % (pos + 1)] = instance_id38 instances["InstanceId.%d" % (pos + 1)] = instance_id
71 query = self.query_factory("DescribeInstances", self.creds,39 query = self.query_factory(
72 self.endpoint, instanceset)40 action="DescribeInstances", creds=self.creds,
41 endpoint=self.endpoint, other_params=instances)
73 d = query.submit()42 d = query.submit()
74 return d.addCallback(self._parse_describe_instances)43 return d.addCallback(self._parse_describe_instances)
7544
@@ -164,7 +133,8 @@
164 if ramdisk_id is not None:133 if ramdisk_id is not None:
165 params["RamdiskId"] = ramdisk_id134 params["RamdiskId"] = ramdisk_id
166 query = self.query_factory(135 query = self.query_factory(
167 "RunInstances", self.creds, self.endpoint, params)136 action="RunInstances", creds=self.creds, endpoint=self.endpoint,
137 other_params=params)
168 d = query.submit()138 d = query.submit()
169 return d.addCallback(self._parse_run_instances)139 return d.addCallback(self._parse_run_instances)
170140
@@ -195,11 +165,12 @@
195 @return: A deferred which on success gives an iterable of165 @return: A deferred which on success gives an iterable of
196 (id, old-state, new-state) tuples.166 (id, old-state, new-state) tuples.
197 """167 """
198 instanceset = {}168 instances = {}
199 for pos, instance_id in enumerate(instance_ids):169 for pos, instance_id in enumerate(instance_ids):
200 instanceset["InstanceId.%d" % (pos+1)] = instance_id170 instances["InstanceId.%d" % (pos+1)] = instance_id
201 query = self.query_factory(171 query = self.query_factory(
202 "TerminateInstances", self.creds, self.endpoint, instanceset)172 action="TerminateInstances", creds=self.creds,
173 endpoint=self.endpoint, other_params=instances)
203 d = query.submit()174 d = query.submit()
204 return d.addCallback(self._parse_terminate_instances)175 return d.addCallback(self._parse_terminate_instances)
205176
@@ -224,12 +195,13 @@
224 @return: A C{Deferred} that will fire with a list of L{SecurityGroup}s195 @return: A C{Deferred} that will fire with a list of L{SecurityGroup}s
225 retrieved from the cloud.196 retrieved from the cloud.
226 """197 """
227 group_names = None198 group_names = {}
228 if names:199 if names:
229 group_names = dict([("GroupName.%d" % (i+1), name)200 group_names = dict([("GroupName.%d" % (i+1), name)
230 for i, name in enumerate(names)])201 for i, name in enumerate(names)])
231 query = self.query_factory("DescribeSecurityGroups", self.creds,202 query = self.query_factory(
232 self.endpoint, group_names)203 action="DescribeSecurityGroups", creds=self.creds,
204 endpoint=self.endpoint, other_params=group_names)
233 d = query.submit()205 d = query.submit()
234 return d.addCallback(self._parse_describe_security_groups)206 return d.addCallback(self._parse_describe_security_groups)
235207
@@ -282,8 +254,9 @@
282 success of the operation.254 success of the operation.
283 """255 """
284 parameters = {"GroupName": name, "GroupDescription": description}256 parameters = {"GroupName": name, "GroupDescription": description}
285 query = self.query_factory("CreateSecurityGroup", self.creds,257 query = self.query_factory(
286 self.endpoint, parameters)258 action="CreateSecurityGroup", creds=self.creds,
259 endpoint=self.endpoint, other_params=parameters)
287 d = query.submit()260 d = query.submit()
288 return d.addCallback(self._parse_truth_return)261 return d.addCallback(self._parse_truth_return)
289262
@@ -298,8 +271,9 @@
298 success of the operation.271 success of the operation.
299 """272 """
300 parameter = {"GroupName": name}273 parameter = {"GroupName": name}
301 query = self.query_factory("DeleteSecurityGroup", self.creds,274 query = self.query_factory(
302 self.endpoint, parameter)275 action="DeleteSecurityGroup", creds=self.creds,
276 endpoint=self.endpoint, other_params=parameter)
303 d = query.submit()277 d = query.submit()
304 return d.addCallback(self._parse_truth_return)278 return d.addCallback(self._parse_truth_return)
305279
@@ -354,8 +328,9 @@
354 "all the ip parameters.")328 "all the ip parameters.")
355 raise ValueError(msg)329 raise ValueError(msg)
356 parameters["GroupName"] = group_name330 parameters["GroupName"] = group_name
357 query = self.query_factory("AuthorizeSecurityGroupIngress", self.creds,331 query = self.query_factory(
358 self.endpoint, parameters)332 action="AuthorizeSecurityGroupIngress", creds=self.creds,
333 endpoint=self.endpoint, other_params=parameters)
359 d = query.submit()334 d = query.submit()
360 return d.addCallback(self._parse_truth_return)335 return d.addCallback(self._parse_truth_return)
361336
@@ -438,8 +413,9 @@
438 "all the ip parameters.")413 "all the ip parameters.")
439 raise ValueError(msg)414 raise ValueError(msg)
440 parameters["GroupName"] = group_name415 parameters["GroupName"] = group_name
441 query = self.query_factory("RevokeSecurityGroupIngress", self.creds,416 query = self.query_factory(
442 self.endpoint, parameters)417 action="RevokeSecurityGroupIngress", creds=self.creds,
418 endpoint=self.endpoint, other_params=parameters)
443 d = query.submit()419 d = query.submit()
444 return d.addCallback(self._parse_truth_return)420 return d.addCallback(self._parse_truth_return)
445421
@@ -477,7 +453,8 @@
477 for pos, volume_id in enumerate(volume_ids):453 for pos, volume_id in enumerate(volume_ids):
478 volumeset["VolumeId.%d" % (pos + 1)] = volume_id454 volumeset["VolumeId.%d" % (pos + 1)] = volume_id
479 query = self.query_factory(455 query = self.query_factory(
480 "DescribeVolumes", self.creds, self.endpoint, volumeset)456 action="DescribeVolumes", creds=self.creds, endpoint=self.endpoint,
457 other_params=volumeset)
481 d = query.submit()458 d = query.submit()
482 return d.addCallback(self._parse_describe_volumes)459 return d.addCallback(self._parse_describe_volumes)
483460
@@ -520,7 +497,8 @@
520 if snapshot_id is not None:497 if snapshot_id is not None:
521 params["SnapshotId"] = snapshot_id498 params["SnapshotId"] = snapshot_id
522 query = self.query_factory(499 query = self.query_factory(
523 "CreateVolume", self.creds, self.endpoint, params)500 action="CreateVolume", creds=self.creds, endpoint=self.endpoint,
501 other_params=params)
524 d = query.submit()502 d = query.submit()
525 return d.addCallback(self._parse_create_volume)503 return d.addCallback(self._parse_create_volume)
526504
@@ -541,7 +519,8 @@
541519
542 def delete_volume(self, volume_id):520 def delete_volume(self, volume_id):
543 query = self.query_factory(521 query = self.query_factory(
544 "DeleteVolume", self.creds, self.endpoint, {"VolumeId": volume_id})522 action="DeleteVolume", creds=self.creds, endpoint=self.endpoint,
523 other_params={"VolumeId": volume_id})
545 d = query.submit()524 d = query.submit()
546 return d.addCallback(self._parse_truth_return)525 return d.addCallback(self._parse_truth_return)
547526
@@ -551,7 +530,8 @@
551 for pos, snapshot_id in enumerate(snapshot_ids):530 for pos, snapshot_id in enumerate(snapshot_ids):
552 snapshot_set["SnapshotId.%d" % (pos + 1)] = snapshot_id531 snapshot_set["SnapshotId.%d" % (pos + 1)] = snapshot_id
553 query = self.query_factory(532 query = self.query_factory(
554 "DescribeSnapshots", self.creds, self.endpoint, snapshot_set)533 action="DescribeSnapshots", creds=self.creds,
534 endpoint=self.endpoint, other_params=snapshot_set)
555 d = query.submit()535 d = query.submit()
556 return d.addCallback(self._parse_snapshots)536 return d.addCallback(self._parse_snapshots)
557537
@@ -575,8 +555,8 @@
575 def create_snapshot(self, volume_id):555 def create_snapshot(self, volume_id):
576 """Create a new snapshot of an existing volume."""556 """Create a new snapshot of an existing volume."""
577 query = self.query_factory(557 query = self.query_factory(
578 "CreateSnapshot", self.creds, self.endpoint,558 action="CreateSnapshot", creds=self.creds, endpoint=self.endpoint,
579 {"VolumeId": volume_id})559 other_params={"VolumeId": volume_id})
580 d = query.submit()560 d = query.submit()
581 return d.addCallback(self._parse_create_snapshot)561 return d.addCallback(self._parse_create_snapshot)
582562
@@ -596,17 +576,17 @@
596 def delete_snapshot(self, snapshot_id):576 def delete_snapshot(self, snapshot_id):
597 """Remove a previously created snapshot."""577 """Remove a previously created snapshot."""
598 query = self.query_factory(578 query = self.query_factory(
599 "DeleteSnapshot", self.creds, self.endpoint,579 action="DeleteSnapshot", creds=self.creds, endpoint=self.endpoint,
600 {"SnapshotId": snapshot_id})580 other_params={"SnapshotId": snapshot_id})
601 d = query.submit()581 d = query.submit()
602 return d.addCallback(self._parse_truth_return)582 return d.addCallback(self._parse_truth_return)
603583
604 def attach_volume(self, volume_id, instance_id, device):584 def attach_volume(self, volume_id, instance_id, device):
605 """Attach the given volume to the specified instance at C{device}."""585 """Attach the given volume to the specified instance at C{device}."""
606 query = self.query_factory(586 query = self.query_factory(
607 "AttachVolume", self.creds, self.endpoint,587 action="AttachVolume", creds=self.creds, endpoint=self.endpoint,
608 {"VolumeId": volume_id, "InstanceId": instance_id,588 other_params={"VolumeId": volume_id, "InstanceId": instance_id,
609 "Device": device})589 "Device": device})
610 d = query.submit()590 d = query.submit()
611 return d.addCallback(self._parse_attach_volume)591 return d.addCallback(self._parse_attach_volume)
612592
@@ -620,11 +600,12 @@
620600
621 def describe_keypairs(self, *keypair_names):601 def describe_keypairs(self, *keypair_names):
622 """Returns information about key pairs available."""602 """Returns information about key pairs available."""
623 keypair_set = {}603 keypairs = {}
624 for pos, keypair_name in enumerate(keypair_names):604 for index, keypair_name in enumerate(keypair_names):
625 keypair_set["KeyPair.%d" % (pos + 1)] = keypair_name605 keypairs["KeyPair.%d" % (index + 1)] = keypair_name
626 query = self.query_factory(606 query = self.query_factory(
627 "DescribeKeyPairs", self.creds, self.endpoint, keypair_set)607 action="DescribeKeyPairs", creds=self.creds,
608 endpoint=self.endpoint, other_params=keypairs)
628 d = query.submit()609 d = query.submit()
629 return d.addCallback(self._parse_describe_keypairs)610 return d.addCallback(self._parse_describe_keypairs)
630611
@@ -646,8 +627,8 @@
646 used to reference the created key pair when launching new instances.627 used to reference the created key pair when launching new instances.
647 """628 """
648 query = self.query_factory(629 query = self.query_factory(
649 "CreateKeyPair", self.creds, self.endpoint,630 action="CreateKeyPair", creds=self.creds, endpoint=self.endpoint,
650 {"KeyName": keypair_name})631 other_params={"KeyName": keypair_name})
651 d = query.submit()632 d = query.submit()
652 return d.addCallback(self._parse_create_keypair)633 return d.addCallback(self._parse_create_keypair)
653634
@@ -661,8 +642,8 @@
661 def delete_keypair(self, keypair_name):642 def delete_keypair(self, keypair_name):
662 """Delete a given keypair."""643 """Delete a given keypair."""
663 query = self.query_factory(644 query = self.query_factory(
664 "DeleteKeyPair", self.creds, self.endpoint,645 action="DeleteKeyPair", creds=self.creds, endpoint=self.endpoint,
665 {"KeyName": keypair_name})646 other_params={"KeyName": keypair_name})
666 d = query.submit()647 d = query.submit()
667 return d.addCallback(self._parse_truth_return)648 return d.addCallback(self._parse_truth_return)
668649
@@ -673,8 +654,10 @@
673654
674 @return: the IP address allocated.655 @return: the IP address allocated.
675 """656 """
657 # XXX remove empty other_params
676 query = self.query_factory(658 query = self.query_factory(
677 "AllocateAddress", self.creds, self.endpoint, {})659 action="AllocateAddress", creds=self.creds, endpoint=self.endpoint,
660 other_params={})
678 d = query.submit()661 d = query.submit()
679 return d.addCallback(self._parse_allocate_address)662 return d.addCallback(self._parse_allocate_address)
680663
@@ -689,8 +672,8 @@
689 @return: C{True} if the operation succeeded.672 @return: C{True} if the operation succeeded.
690 """673 """
691 query = self.query_factory(674 query = self.query_factory(
692 "ReleaseAddress", self.creds, self.endpoint,675 action="ReleaseAddress", creds=self.creds, endpoint=self.endpoint,
693 {"PublicIp": address})676 other_params={"PublicIp": address})
694 d = query.submit()677 d = query.submit()
695 return d.addCallback(self._parse_truth_return)678 return d.addCallback(self._parse_truth_return)
696679
@@ -702,8 +685,9 @@
702 @return: C{True} if the operation succeeded.685 @return: C{True} if the operation succeeded.
703 """686 """
704 query = self.query_factory(687 query = self.query_factory(
705 "AssociateAddress", self.creds, self.endpoint,688 action="AssociateAddress", creds=self.creds,
706 {"InstanceId": instance_id, "PublicIp": address})689 endpoint=self.endpoint,
690 other_params={"InstanceId": instance_id, "PublicIp": address})
707 d = query.submit()691 d = query.submit()
708 return d.addCallback(self._parse_truth_return)692 return d.addCallback(self._parse_truth_return)
709693
@@ -714,8 +698,8 @@
714 called several times without error.698 called several times without error.
715 """699 """
716 query = self.query_factory(700 query = self.query_factory(
717 "DisassociateAddress", self.creds, self.endpoint,701 action="DisassociateAddress", creds=self.creds,
718 {"PublicIp": address})702 endpoint=self.endpoint, other_params={"PublicIp": address})
719 d = query.submit()703 d = query.submit()
720 return d.addCallback(self._parse_truth_return)704 return d.addCallback(self._parse_truth_return)
721705
@@ -732,7 +716,8 @@
732 for pos, address in enumerate(addresses):716 for pos, address in enumerate(addresses):
733 address_set["PublicIp.%d" % (pos + 1)] = address717 address_set["PublicIp.%d" % (pos + 1)] = address
734 query = self.query_factory(718 query = self.query_factory(
735 "DescribeAddresses", self.creds, self.endpoint, address_set)719 action="DescribeAddresses", creds=self.creds,
720 endpoint=self.endpoint, other_params=address_set)
736 d = query.submit()721 d = query.submit()
737 return d.addCallback(self._parse_describe_addresses)722 return d.addCallback(self._parse_describe_addresses)
738723
@@ -750,8 +735,9 @@
750 if names:735 if names:
751 zone_names = dict([("ZoneName.%d" % (i+1), name)736 zone_names = dict([("ZoneName.%d" % (i+1), name)
752 for i, name in enumerate(names)])737 for i, name in enumerate(names)])
753 query = self.query_factory("DescribeAvailabilityZones", self.creds,738 query = self.query_factory(
754 self.endpoint, zone_names)739 action="DescribeAvailabilityZones", creds=self.creds,
740 endpoint=self.endpoint, other_params=zone_names)
755 d = query.submit()741 d = query.submit()
756 return d.addCallback(self._parse_describe_availability_zones)742 return d.addCallback(self._parse_describe_availability_zones)
757743
@@ -830,5 +816,4 @@
830 url = "%s?%s" % (self.endpoint.get_uri(),816 url = "%s?%s" % (self.endpoint.get_uri(),
831 self.get_canonical_query_params())817 self.get_canonical_query_params())
832 d = self.get_page(url, method=self.endpoint.method)818 d = self.get_page(url, method=self.endpoint.method)
833 d.addErrback(ec2_error_wrapper)819 return d.addErrback(ec2_error_wrapper)
834 return d
835820
=== modified file 'txaws/ec2/exception.py'
--- txaws/ec2/exception.py 2009-11-28 01:15:24 +0000
+++ txaws/ec2/exception.py 2009-11-28 01:15:24 +0000
@@ -1,39 +1,14 @@
1# Copyright (c) 2009 Canonical Ltd <duncan.mcgreggor@canonical.com>1# Copyright (c) 2009 Canonical Ltd <duncan.mcgreggor@canonical.com>
2# Licenced under the txaws licence available at /LICENSE in the txaws source.2# Licenced under the txaws licence available at /LICENSE in the txaws source.
33
4from txaws.exception import AWSError, AWSResponseParseError4from txaws.exception import AWSError
5from txaws.util import XML
65
76
8class EC2Error(AWSError):7class EC2Error(AWSError):
9 """8 """
10 A error class providing custom methods on EC2 errors.9 A error class providing custom methods on EC2 errors.
11 """10 """
12 def __init__(self, xml_bytes, status=None, message=None, response=None):11 def _set_400_error(self, tree):
13 super(AWSError, self).__init__(status, message, response)
14 if not xml_bytes:
15 raise ValueError("XML cannot be empty.")
16 self.original = xml_bytes
17 self.errors = []
18 self.request_id = ""
19 self.host_id = ""
20 self.parse()
21
22 def __str__(self):
23 return self._get_error_message_string()
24
25 def __repr__(self):
26 return "<%s object with %s>" % (
27 self.__class__.__name__, self._get_error_code_string())
28
29 def _set_request_id(self, tree):
30 request_id_node = tree.find(".//RequestID")
31 if hasattr(request_id_node, "text"):
32 text = request_id_node.text
33 if text:
34 self.request_id = text
35
36 def _set_400_errors(self, tree):
37 errors_node = tree.find(".//Errors")12 errors_node = tree.find(".//Errors")
38 if errors_node:13 if errors_node:
39 for error in errors_node:14 for error in errors_node:
@@ -41,84 +16,5 @@
41 if data:16 if data:
42 self.errors.append(data)17 self.errors.append(data)
4318
44 def _set_host_id(self, tree):19
45 host_id = tree.find(".//HostID")20
46 if hasattr(host_id, "text"):
47 text = host_id.text
48 if text:
49 self.host_id = text
50
51 def _set_500_error(self, tree):
52 self._set_request_id(tree)
53 self._set_host_id(tree)
54 data = self._node_to_dict(tree)
55 if data:
56 self.errors.append(data)
57
58 def _get_error_code_string(self):
59 count = len(self.errors)
60 error_code = self.get_error_codes()
61 if count > 1:
62 return "Error count: %s" % error_code
63 else:
64 return "Error code: %s" % error_code
65
66 def _get_error_message_string(self):
67 count = len(self.errors)
68 error_message = self.get_error_messages()
69 if count > 1:
70 return "%s." % error_message
71 else:
72 return "Error Message: %s" % error_message
73
74 def _node_to_dict(self, node):
75 data = {}
76 for child in node:
77 if child.tag and child.text:
78 data[child.tag] = child.text
79 return data
80
81 def _check_for_html(self, tree):
82 if tree.tag == "html":
83 message = "Could not parse HTML in the response."
84 raise AWSResponseParseError(message)
85
86 def parse(self, xml_bytes=""):
87 if not xml_bytes:
88 xml_bytes = self.original
89 self.original = xml_bytes
90 tree = XML(xml_bytes.strip())
91 self._check_for_html(tree)
92 self._set_request_id(tree)
93 if self.status:
94 status = int(self.status)
95 else:
96 status = 400
97 if status >= 500:
98 self._set_500_error(tree)
99 else:
100 self._set_400_errors(tree)
101
102 def has_error(self, errorString):
103 for error in self.errors:
104 if errorString in error.values():
105 return True
106 return False
107
108 def get_error_codes(self):
109 count = len(self.errors)
110 if count > 1:
111 return count
112 elif count == 0:
113 return
114 else:
115 return self.errors[0]["Code"]
116
117 def get_error_messages(self):
118 count = len(self.errors)
119 if count > 1:
120 return "Multiple EC2 Errors"
121 elif count == 0:
122 return "Empty error list"
123 else:
124 return self.errors[0]["Message"]
12521
=== modified file 'txaws/ec2/tests/test_client.py'
--- txaws/ec2/tests/test_client.py 2009-11-28 01:15:24 +0000
+++ txaws/ec2/tests/test_client.py 2009-11-28 01:15:24 +0000
@@ -76,13 +76,14 @@
7676
77 class StubQuery(object):77 class StubQuery(object):
7878
79 def __init__(stub, action, creds, endpoint, other_params):79 def __init__(stub, action="", creds=None, endpoint=None,
80 other_params={}):
80 self.assertEqual(action, "DescribeAvailabilityZones")81 self.assertEqual(action, "DescribeAvailabilityZones")
81 self.assertEqual(creds.access_key, "foo")82 self.assertEqual(creds.access_key, "foo")
82 self.assertEqual(creds.secret_key, "bar")83 self.assertEqual(creds.secret_key, "bar")
83 self.assertEqual(84 self.assertEqual(
84 {"ZoneName.1": "us-east-1a"},85 other_params,
85 other_params)86 {"ZoneName.1": "us-east-1a"})
8687
87 def submit(self):88 def submit(self):
88 return succeed(89 return succeed(
@@ -105,7 +106,8 @@
105106
106 class StubQuery(object):107 class StubQuery(object):
107108
108 def __init__(stub, action, creds, endpoint, other_params):109 def __init__(stub, action="", creds=None, endpoint=None,
110 other_params={}):
109 self.assertEqual(action, "DescribeAvailabilityZones")111 self.assertEqual(action, "DescribeAvailabilityZones")
110 self.assertEqual(creds.access_key, "foo")112 self.assertEqual(creds.access_key, "foo")
111 self.assertEqual(creds.secret_key, "bar")113 self.assertEqual(creds.secret_key, "bar")
@@ -199,11 +201,12 @@
199201
200 class StubQuery(object):202 class StubQuery(object):
201203
202 def __init__(stub, action, creds, endpoint, params):204 def __init__(stub, action="", creds=None, endpoint=None,
205 other_params={}):
203 self.assertEqual(action, "DescribeInstances")206 self.assertEqual(action, "DescribeInstances")
204 self.assertEqual(creds.access_key, "foo")207 self.assertEqual(creds.access_key, "foo")
205 self.assertEqual(creds.secret_key, "bar")208 self.assertEqual(creds.secret_key, "bar")
206 self.assertEquals(params, {})209 self.assertEquals(other_params, {})
207210
208 def submit(self):211 def submit(self):
209 return succeed(payload.sample_describe_instances_result)212 return succeed(payload.sample_describe_instances_result)
@@ -218,11 +221,12 @@
218221
219 class StubQuery(object):222 class StubQuery(object):
220223
221 def __init__(stub, action, creds, endpoint, params):224 def __init__(stub, action="", creds=None, endpoint=None,
225 other_params={}):
222 self.assertEqual(action, "DescribeInstances")226 self.assertEqual(action, "DescribeInstances")
223 self.assertEqual(creds.access_key, "foo")227 self.assertEqual(creds.access_key, "foo")
224 self.assertEqual(creds.secret_key, "bar")228 self.assertEqual(creds.secret_key, "bar")
225 self.assertEquals(params, {})229 self.assertEquals(other_params, {})
226230
227 def submit(self):231 def submit(self):
228 return succeed(232 return succeed(
@@ -238,12 +242,13 @@
238242
239 class StubQuery(object):243 class StubQuery(object):
240244
241 def __init__(stub, action, creds, endpoint, params):245 def __init__(stub, action="", creds=None, endpoint=None,
246 other_params={}):
242 self.assertEqual(action, "DescribeInstances")247 self.assertEqual(action, "DescribeInstances")
243 self.assertEqual(creds.access_key, "foo")248 self.assertEqual(creds.access_key, "foo")
244 self.assertEqual(creds.secret_key, "bar")249 self.assertEqual(creds.secret_key, "bar")
245 self.assertEquals(250 self.assertEquals(
246 params,251 other_params,
247 {"InstanceId.1": "i-16546401",252 {"InstanceId.1": "i-16546401",
248 "InstanceId.2": "i-49873415"})253 "InstanceId.2": "i-49873415"})
249254
@@ -261,13 +266,14 @@
261266
262 class StubQuery(object):267 class StubQuery(object):
263268
264 def __init__(stub, action, creds, endpoint, other_params):269 def __init__(stub, action="", creds=None, endpoint=None,
270 other_params={}):
265 self.assertEqual(action, "TerminateInstances")271 self.assertEqual(action, "TerminateInstances")
266 self.assertEqual(creds.access_key, "foo")272 self.assertEqual(creds.access_key, "foo")
267 self.assertEqual(creds.secret_key, "bar")273 self.assertEqual(creds.secret_key, "bar")
268 self.assertEqual(274 self.assertEqual(
269 {"InstanceId.1": "i-1234", "InstanceId.2": "i-5678"},275 other_params,
270 other_params)276 {"InstanceId.1": "i-1234", "InstanceId.2": "i-5678"})
271277
272 def submit(self):278 def submit(self):
273 return succeed(payload.sample_terminate_instances_result)279 return succeed(payload.sample_terminate_instances_result)
@@ -313,12 +319,13 @@
313319
314 class StubQuery(object):320 class StubQuery(object):
315321
316 def __init__(stub, action, creds, endpoint, params):322 def __init__(stub, action="", creds=None, endpoint=None,
323 other_params={}):
317 self.assertEqual(action, "RunInstances")324 self.assertEqual(action, "RunInstances")
318 self.assertEqual(creds.access_key, "foo")325 self.assertEqual(creds.access_key, "foo")
319 self.assertEqual(creds.secret_key, "bar")326 self.assertEqual(creds.secret_key, "bar")
320 self.assertEquals(327 self.assertEquals(
321 params,328 other_params,
322 {"ImageId": "ami-1234", "MaxCount": "2", "MinCount": "1",329 {"ImageId": "ami-1234", "MaxCount": "2", "MinCount": "1",
323 "SecurityGroup.1": u"group1", "KeyName": u"default",330 "SecurityGroup.1": u"group1", "KeyName": u"default",
324 "UserData": "Zm9v", "InstanceType": u"m1.small",331 "UserData": "Zm9v", "InstanceType": u"m1.small",
@@ -348,11 +355,12 @@
348 """355 """
349 class StubQuery(object):356 class StubQuery(object):
350357
351 def __init__(stub, action, creds, endpoint, other_params=None):358 def __init__(stub, action="", creds=None, endpoint=None,
359 other_params={}):
352 self.assertEqual(action, "DescribeSecurityGroups")360 self.assertEqual(action, "DescribeSecurityGroups")
353 self.assertEqual(creds.access_key, "foo")361 self.assertEqual(creds.access_key, "foo")
354 self.assertEqual(creds.secret_key, "bar")362 self.assertEqual(creds.secret_key, "bar")
355 self.assertEqual(other_params, None)363 self.assertEqual(other_params, {})
356364
357 def submit(self):365 def submit(self):
358 return succeed(payload.sample_describe_security_groups_result)366 return succeed(payload.sample_describe_security_groups_result)
@@ -382,11 +390,12 @@
382 """390 """
383 class StubQuery(object):391 class StubQuery(object):
384392
385 def __init__(stub, action, creds, endpoint, other_params=None):393 def __init__(stub, action="", creds=None, endpoint=None,
394 other_params={}):
386 self.assertEqual(action, "DescribeSecurityGroups")395 self.assertEqual(action, "DescribeSecurityGroups")
387 self.assertEqual(creds.access_key, "foo")396 self.assertEqual(creds.access_key, "foo")
388 self.assertEqual(creds.secret_key, "bar")397 self.assertEqual(creds.secret_key, "bar")
389 self.assertEqual(other_params, None)398 self.assertEqual(other_params, {})
390399
391 def submit(self):400 def submit(self):
392 return succeed(401 return succeed(
@@ -431,7 +440,8 @@
431 """440 """
432 class StubQuery(object):441 class StubQuery(object):
433442
434 def __init__(stub, action, creds, endpoint, other_params=None):443 def __init__(stub, action="", creds=None, endpoint=None,
444 other_params={}):
435 self.assertEqual(action, "DescribeSecurityGroups")445 self.assertEqual(action, "DescribeSecurityGroups")
436 self.assertEqual(creds.access_key, "foo")446 self.assertEqual(creds.access_key, "foo")
437 self.assertEqual(creds.secret_key, "bar")447 self.assertEqual(creds.secret_key, "bar")
@@ -457,7 +467,8 @@
457 """467 """
458 class StubQuery(object):468 class StubQuery(object):
459469
460 def __init__(stub, action, creds, endpoint, other_params=None):470 def __init__(stub, action="", creds=None, endpoint=None,
471 other_params={}):
461 self.assertEqual(action, "CreateSecurityGroup")472 self.assertEqual(action, "CreateSecurityGroup")
462 self.assertEqual(creds.access_key, "foo")473 self.assertEqual(creds.access_key, "foo")
463 self.assertEqual(creds.secret_key, "bar")474 self.assertEqual(creds.secret_key, "bar")
@@ -484,7 +495,8 @@
484 """495 """
485 class StubQuery(object):496 class StubQuery(object):
486497
487 def __init__(stub, action, creds, endpoint, other_params=None):498 def __init__(stub, action="", creds=None, endpoint=None,
499 other_params={}):
488 self.assertEqual(action, "DeleteSecurityGroup")500 self.assertEqual(action, "DeleteSecurityGroup")
489 self.assertEqual(creds.access_key, "foo")501 self.assertEqual(creds.access_key, "foo")
490 self.assertEqual(creds.secret_key, "bar")502 self.assertEqual(creds.secret_key, "bar")
@@ -508,7 +520,8 @@
508 """520 """
509 class StubQuery(object):521 class StubQuery(object):
510522
511 def __init__(stub, action, creds, endpoint, other_params=None):523 def __init__(stub, action="", creds=None, endpoint=None,
524 other_params={}):
512 self.assertEqual(action, "DeleteSecurityGroup")525 self.assertEqual(action, "DeleteSecurityGroup")
513 self.assertEqual(creds.access_key, "foo")526 self.assertEqual(creds.access_key, "foo")
514 self.assertEqual(creds.secret_key, "bar")527 self.assertEqual(creds.secret_key, "bar")
@@ -542,7 +555,8 @@
542 """555 """
543 class StubQuery(object):556 class StubQuery(object):
544557
545 def __init__(stub, action, creds, endpoint, other_params=None):558 def __init__(stub, action="", creds=None, endpoint=None,
559 other_params={}):
546 self.assertEqual(action, "AuthorizeSecurityGroupIngress")560 self.assertEqual(action, "AuthorizeSecurityGroupIngress")
547 self.assertEqual(creds.access_key, "foo")561 self.assertEqual(creds.access_key, "foo")
548 self.assertEqual(creds.secret_key, "bar")562 self.assertEqual(creds.secret_key, "bar")
@@ -572,7 +586,8 @@
572 """586 """
573 class StubQuery(object):587 class StubQuery(object):
574588
575 def __init__(stub, action, creds, endpoint, other_params=None):589 def __init__(stub, action="", creds=None, endpoint=None,
590 other_params={}):
576 self.assertEqual(action, "AuthorizeSecurityGroupIngress")591 self.assertEqual(action, "AuthorizeSecurityGroupIngress")
577 self.assertEqual(creds.access_key, "foo")592 self.assertEqual(creds.access_key, "foo")
578 self.assertEqual(creds.secret_key, "bar")593 self.assertEqual(creds.secret_key, "bar")
@@ -622,7 +637,8 @@
622 """637 """
623 class StubQuery(object):638 class StubQuery(object):
624639
625 def __init__(stub, action, creds, endpoint, other_params=None):640 def __init__(stub, action="", creds=None, endpoint=None,
641 other_params={}):
626 self.assertEqual(action, "AuthorizeSecurityGroupIngress")642 self.assertEqual(action, "AuthorizeSecurityGroupIngress")
627 self.assertEqual(creds.access_key, "foo")643 self.assertEqual(creds.access_key, "foo")
628 self.assertEqual(creds.secret_key, "bar")644 self.assertEqual(creds.secret_key, "bar")
@@ -650,7 +666,8 @@
650 """666 """
651 class StubQuery(object):667 class StubQuery(object):
652668
653 def __init__(stub, action, creds, endpoint, other_params=None):669 def __init__(stub, action="", creds=None, endpoint=None,
670 other_params={}):
654 self.assertEqual(action, "AuthorizeSecurityGroupIngress")671 self.assertEqual(action, "AuthorizeSecurityGroupIngress")
655 self.assertEqual(creds.access_key, "foo")672 self.assertEqual(creds.access_key, "foo")
656 self.assertEqual(creds.secret_key, "bar")673 self.assertEqual(creds.secret_key, "bar")
@@ -680,7 +697,8 @@
680 """697 """
681 class StubQuery(object):698 class StubQuery(object):
682699
683 def __init__(stub, action, creds, endpoint, other_params=None):700 def __init__(stub, action="", creds=None, endpoint=None,
701 other_params={}):
684 self.assertEqual(action, "RevokeSecurityGroupIngress")702 self.assertEqual(action, "RevokeSecurityGroupIngress")
685 self.assertEqual(creds.access_key, "foo")703 self.assertEqual(creds.access_key, "foo")
686 self.assertEqual(creds.secret_key, "bar")704 self.assertEqual(creds.secret_key, "bar")
@@ -710,7 +728,8 @@
710 """728 """
711 class StubQuery(object):729 class StubQuery(object):
712730
713 def __init__(stub, action, creds, endpoint, other_params=None):731 def __init__(stub, action="", creds=None, endpoint=None,
732 other_params={}):
714 self.assertEqual(action, "RevokeSecurityGroupIngress")733 self.assertEqual(action, "RevokeSecurityGroupIngress")
715 self.assertEqual(creds.access_key, "foo")734 self.assertEqual(creds.access_key, "foo")
716 self.assertEqual(creds.secret_key, "bar")735 self.assertEqual(creds.secret_key, "bar")
@@ -760,7 +779,8 @@
760 """779 """
761 class StubQuery(object):780 class StubQuery(object):
762781
763 def __init__(stub, action, creds, endpoint, other_params=None):782 def __init__(stub, action="", creds=None, endpoint=None,
783 other_params={}):
764 self.assertEqual(action, "RevokeSecurityGroupIngress")784 self.assertEqual(action, "RevokeSecurityGroupIngress")
765 self.assertEqual(creds.access_key, "foo")785 self.assertEqual(creds.access_key, "foo")
766 self.assertEqual(creds.secret_key, "bar")786 self.assertEqual(creds.secret_key, "bar")
@@ -788,7 +808,8 @@
788 """808 """
789 class StubQuery(object):809 class StubQuery(object):
790810
791 def __init__(stub, action, creds, endpoint, other_params=None):811 def __init__(stub, action="", creds=None, endpoint=None,
812 other_params={}):
792 self.assertEqual(action, "RevokeSecurityGroupIngress")813 self.assertEqual(action, "RevokeSecurityGroupIngress")
793 self.assertEqual(creds.access_key, "foo")814 self.assertEqual(creds.access_key, "foo")
794 self.assertEqual(creds.secret_key, "bar")815 self.assertEqual(creds.secret_key, "bar")
@@ -838,11 +859,12 @@
838859
839 class StubQuery(object):860 class StubQuery(object):
840861
841 def __init__(stub, action, creds, endpoint, params):862 def __init__(stub, action="", creds=None, endpoint=None,
863 other_params={}):
842 self.assertEqual(action, "DescribeVolumes")864 self.assertEqual(action, "DescribeVolumes")
843 self.assertEqual(self.creds, creds)865 self.assertEqual(self.creds, creds)
844 self.assertEqual(self.endpoint, endpoint)866 self.assertEqual(self.endpoint, endpoint)
845 self.assertEquals(params, {})867 self.assertEquals(other_params, {})
846868
847 def submit(self):869 def submit(self):
848 return succeed(payload.sample_describe_volumes_result)870 return succeed(payload.sample_describe_volumes_result)
@@ -857,12 +879,13 @@
857879
858 class StubQuery(object):880 class StubQuery(object):
859881
860 def __init__(stub, action, creds, endpoint, params):882 def __init__(stub, action="", creds=None, endpoint=None,
883 other_params={}):
861 self.assertEqual(action, "DescribeVolumes")884 self.assertEqual(action, "DescribeVolumes")
862 self.assertEqual(self.creds, creds)885 self.assertEqual(self.creds, creds)
863 self.assertEqual(self.endpoint, endpoint)886 self.assertEqual(self.endpoint, endpoint)
864 self.assertEquals(887 self.assertEquals(
865 params,888 other_params,
866 {"VolumeId.1": "vol-4282672b"})889 {"VolumeId.1": "vol-4282672b"})
867890
868 def submit(self):891 def submit(self):
@@ -888,11 +911,12 @@
888911
889 class StubQuery(object):912 class StubQuery(object):
890913
891 def __init__(stub, action, creds, endpoint, params):914 def __init__(stub, action="", creds=None, endpoint=None,
915 other_params={}):
892 self.assertEqual(action, "DescribeSnapshots")916 self.assertEqual(action, "DescribeSnapshots")
893 self.assertEqual(self.creds, creds)917 self.assertEqual(self.creds, creds)
894 self.assertEqual(self.endpoint, endpoint)918 self.assertEqual(self.endpoint, endpoint)
895 self.assertEquals(params, {})919 self.assertEquals(other_params, {})
896920
897 def submit(self):921 def submit(self):
898 return succeed(payload.sample_describe_snapshots_result)922 return succeed(payload.sample_describe_snapshots_result)
@@ -907,12 +931,13 @@
907931
908 class StubQuery(object):932 class StubQuery(object):
909933
910 def __init__(stub, action, creds, endpoint, params):934 def __init__(stub, action="", creds=None, endpoint=None,
935 other_params={}):
911 self.assertEqual(action, "DescribeSnapshots")936 self.assertEqual(action, "DescribeSnapshots")
912 self.assertEqual(self.creds, creds)937 self.assertEqual(self.creds, creds)
913 self.assertEqual(self.endpoint, endpoint)938 self.assertEqual(self.endpoint, endpoint)
914 self.assertEquals(939 self.assertEquals(
915 params,940 other_params,
916 {"SnapshotId.1": "snap-78a54011"})941 {"SnapshotId.1": "snap-78a54011"})
917942
918 def submit(self):943 def submit(self):
@@ -928,13 +953,14 @@
928953
929 class StubQuery(object):954 class StubQuery(object):
930955
931 def __init__(stub, action, creds, endpoint, params):956 def __init__(stub, action="", creds=None, endpoint=None,
957 other_params={}):
932 self.assertEqual(action, "CreateVolume")958 self.assertEqual(action, "CreateVolume")
933 self.assertEqual(self.creds, creds)959 self.assertEqual(self.creds, creds)
934 self.assertEqual(self.endpoint, endpoint)960 self.assertEqual(self.endpoint, endpoint)
935 self.assertEqual(961 self.assertEqual(
936 {"AvailabilityZone": "us-east-1", "Size": "800"},962 other_params,
937 params)963 {"AvailabilityZone": "us-east-1", "Size": "800"})
938964
939 def submit(self):965 def submit(self):
940 return succeed(payload.sample_create_volume_result)966 return succeed(payload.sample_create_volume_result)
@@ -956,14 +982,15 @@
956982
957 class StubQuery(object):983 class StubQuery(object):
958984
959 def __init__(stub, action, creds, endpoint, params):985 def __init__(stub, action="", creds=None, endpoint=None,
986 other_params={}):
960 self.assertEqual(action, "CreateVolume")987 self.assertEqual(action, "CreateVolume")
961 self.assertEqual(self.creds, creds)988 self.assertEqual(self.creds, creds)
962 self.assertEqual(self.endpoint, endpoint)989 self.assertEqual(self.endpoint, endpoint)
963 self.assertEqual(990 self.assertEqual(
991 other_params,
964 {"AvailabilityZone": "us-east-1",992 {"AvailabilityZone": "us-east-1",
965 "SnapshotId": "snap-12345678"},993 "SnapshotId": "snap-12345678"})
966 params)
967994
968 def submit(self):995 def submit(self):
969 return succeed(payload.sample_create_volume_result)996 return succeed(payload.sample_create_volume_result)
@@ -999,13 +1026,14 @@
9991026
1000 class StubQuery(object):1027 class StubQuery(object):
10011028
1002 def __init__(stub, action, creds, endpoint, params):1029 def __init__(stub, action="", creds=None, endpoint=None,
1030 other_params={}):
1003 self.assertEqual(action, "DeleteVolume")1031 self.assertEqual(action, "DeleteVolume")
1004 self.assertEqual(self.creds, creds)1032 self.assertEqual(self.creds, creds)
1005 self.assertEqual(self.endpoint, endpoint)1033 self.assertEqual(self.endpoint, endpoint)
1006 self.assertEqual(1034 self.assertEqual(
1007 {"VolumeId": "vol-4282672b"},1035 other_params,
1008 params)1036 {"VolumeId": "vol-4282672b"})
10091037
1010 def submit(self):1038 def submit(self):
1011 return succeed(payload.sample_delete_volume_result)1039 return succeed(payload.sample_delete_volume_result)
@@ -1020,13 +1048,14 @@
10201048
1021 class StubQuery(object):1049 class StubQuery(object):
10221050
1023 def __init__(stub, action, creds, endpoint, params):1051 def __init__(stub, action="", creds=None, endpoint=None,
1052 other_params={}):
1024 self.assertEqual(action, "CreateSnapshot")1053 self.assertEqual(action, "CreateSnapshot")
1025 self.assertEqual(self.creds, creds)1054 self.assertEqual(self.creds, creds)
1026 self.assertEqual(self.endpoint, endpoint)1055 self.assertEqual(self.endpoint, endpoint)
1027 self.assertEqual(1056 self.assertEqual(
1028 {"VolumeId": "vol-4d826724"},1057 other_params,
1029 params)1058 {"VolumeId": "vol-4d826724"})
10301059
1031 def submit(self):1060 def submit(self):
1032 return succeed(payload.sample_create_snapshot_result)1061 return succeed(payload.sample_create_snapshot_result)
@@ -1049,13 +1078,14 @@
10491078
1050 class StubQuery(object):1079 class StubQuery(object):
10511080
1052 def __init__(stub, action, creds, endpoint, params):1081 def __init__(stub, action="", creds=None, endpoint=None,
1082 other_params={}):
1053 self.assertEqual(action, "DeleteSnapshot")1083 self.assertEqual(action, "DeleteSnapshot")
1054 self.assertEqual(self.creds, creds)1084 self.assertEqual(self.creds, creds)
1055 self.assertEqual(self.endpoint, endpoint)1085 self.assertEqual(self.endpoint, endpoint)
1056 self.assertEqual(1086 self.assertEqual(
1057 {"SnapshotId": "snap-78a54011"},1087 other_params,
1058 params)1088 {"SnapshotId": "snap-78a54011"})
10591089
1060 def submit(self):1090 def submit(self):
1061 return succeed(payload.sample_delete_snapshot_result)1091 return succeed(payload.sample_delete_snapshot_result)
@@ -1070,14 +1100,15 @@
10701100
1071 class StubQuery(object):1101 class StubQuery(object):
10721102
1073 def __init__(stub, action, creds, endpoint, params):1103 def __init__(stub, action="", creds=None, endpoint=None,
1104 other_params={}):
1074 self.assertEqual(action, "AttachVolume")1105 self.assertEqual(action, "AttachVolume")
1075 self.assertEqual(self.creds, creds)1106 self.assertEqual(self.creds, creds)
1076 self.assertEqual(self.endpoint, endpoint)1107 self.assertEqual(self.endpoint, endpoint)
1077 self.assertEqual(1108 self.assertEqual(
1109 other_params,
1078 {"VolumeId": "vol-4d826724", "InstanceId": "i-6058a509",1110 {"VolumeId": "vol-4d826724", "InstanceId": "i-6058a509",
1079 "Device": "/dev/sdh"},1111 "Device": "/dev/sdh"})
1080 params)
10811112
1082 def submit(self):1113 def submit(self):
1083 return succeed(payload.sample_attach_volume_result)1114 return succeed(payload.sample_attach_volume_result)
@@ -1106,10 +1137,11 @@
11061137
1107 class StubQuery(object):1138 class StubQuery(object):
11081139
1109 def __init__(stub, action, creds, endpoint, params):1140 def __init__(stub, action="", creds=None, endpoint=None,
1141 other_params={}):
1110 self.assertEqual(action, "DescribeKeyPairs")1142 self.assertEqual(action, "DescribeKeyPairs")
1111 self.assertEqual("foo", creds)1143 self.assertEqual("foo", creds)
1112 self.assertEquals(params, {})1144 self.assertEquals(other_params, {})
11131145
1114 def submit(self):1146 def submit(self):
1115 return succeed(payload.sample_single_describe_keypairs_result)1147 return succeed(payload.sample_single_describe_keypairs_result)
@@ -1134,10 +1166,12 @@
1134 "1f:51:ae:28:bf:89:e9:d8:1f:25:5d:37:2d:7d:b8:ca:9f:f5:f1:70")1166 "1f:51:ae:28:bf:89:e9:d8:1f:25:5d:37:2d:7d:b8:ca:9f:f5:f1:70")
11351167
1136 class StubQuery(object):1168 class StubQuery(object):
1137 def __init__(stub, action, creds, endpoint, params):1169
1170 def __init__(stub, action="", creds=None, endpoint=None,
1171 other_params={}):
1138 self.assertEqual(action, "DescribeKeyPairs")1172 self.assertEqual(action, "DescribeKeyPairs")
1139 self.assertEqual("foo", creds)1173 self.assertEqual("foo", creds)
1140 self.assertEquals(params, {})1174 self.assertEquals(other_params, {})
11411175
1142 def submit(self):1176 def submit(self):
1143 return succeed(1177 return succeed(
@@ -1152,11 +1186,12 @@
11521186
1153 class StubQuery(object):1187 class StubQuery(object):
11541188
1155 def __init__(stub, action, creds, endpoint, params):1189 def __init__(stub, action="", creds=None, endpoint=None,
1190 other_params={}):
1156 self.assertEqual(action, "DescribeKeyPairs")1191 self.assertEqual(action, "DescribeKeyPairs")
1157 self.assertEqual("foo", creds)1192 self.assertEqual("foo", creds)
1158 self.assertEquals(1193 self.assertEquals(
1159 params,1194 other_params,
1160 {"KeyPair.1": "gsg-keypair"})1195 {"KeyPair.1": "gsg-keypair"})
11611196
1162 def submit(self):1197 def submit(self):
@@ -1182,11 +1217,12 @@
11821217
1183 class StubQuery(object):1218 class StubQuery(object):
11841219
1185 def __init__(stub, action, creds, endpoint, params):1220 def __init__(stub, action="", creds=None, endpoint=None,
1221 other_params={}):
1186 self.assertEqual(action, "CreateKeyPair")1222 self.assertEqual(action, "CreateKeyPair")
1187 self.assertEqual("foo", creds)1223 self.assertEqual("foo", creds)
1188 self.assertEquals(1224 self.assertEquals(
1189 params,1225 other_params,
1190 {"KeyName": "example-key-name"})1226 {"KeyName": "example-key-name"})
11911227
1192 def submit(self):1228 def submit(self):
@@ -1201,12 +1237,13 @@
12011237
1202 class StubQuery(object):1238 class StubQuery(object):
12031239
1204 def __init__(stub, action, creds, endpoint, params):1240 def __init__(stub, action="", creds=None, endpoint=None,
1241 other_params={}):
1205 self.assertEqual(action, "DeleteKeyPair")1242 self.assertEqual(action, "DeleteKeyPair")
1206 self.assertEqual("foo", creds)1243 self.assertEqual("foo", creds)
1207 self.assertEqual("http:///", endpoint.get_uri())1244 self.assertEqual("http:///", endpoint.get_uri())
1208 self.assertEquals(1245 self.assertEquals(
1209 params,1246 other_params,
1210 {"KeyName": "example-key-name"})1247 {"KeyName": "example-key-name"})
12111248
1212 def submit(self):1249 def submit(self):
@@ -1221,12 +1258,13 @@
12211258
1222 class StubQuery(object):1259 class StubQuery(object):
12231260
1224 def __init__(stub, action, creds, endpoint, params):1261 def __init__(stub, action="", creds=None, endpoint=None,
1262 other_params={}):
1225 self.assertEqual(action, "DeleteKeyPair")1263 self.assertEqual(action, "DeleteKeyPair")
1226 self.assertEqual("foo", creds)1264 self.assertEqual("foo", creds)
1227 self.assertEqual("http:///", endpoint.get_uri())1265 self.assertEqual("http:///", endpoint.get_uri())
1228 self.assertEquals(1266 self.assertEquals(
1229 params,1267 other_params,
1230 {"KeyName": "example-key-name"})1268 {"KeyName": "example-key-name"})
12311269
1232 def submit(self):1270 def submit(self):
@@ -1241,12 +1279,13 @@
12411279
1242 class StubQuery(object):1280 class StubQuery(object):
12431281
1244 def __init__(stub, action, creds, endpoint, params):1282 def __init__(stub, action="", creds=None, endpoint=None,
1283 other_params={}):
1245 self.assertEqual(action, "DeleteKeyPair")1284 self.assertEqual(action, "DeleteKeyPair")
1246 self.assertEqual("foo", creds)1285 self.assertEqual("foo", creds)
1247 self.assertEqual("http:///", endpoint.get_uri())1286 self.assertEqual("http:///", endpoint.get_uri())
1248 self.assertEquals(1287 self.assertEquals(
1249 params,1288 other_params,
1250 {"KeyName": "example-key-name"})1289 {"KeyName": "example-key-name"})
12511290
1252 def submit(self):1291 def submit(self):
@@ -1373,12 +1412,12 @@
1373 self.assertTrue("Timestamp" in query.params)1412 self.assertTrue("Timestamp" in query.params)
1374 del query.params["Timestamp"]1413 del query.params["Timestamp"]
1375 self.assertEqual(1414 self.assertEqual(
1415 query.params,
1376 {"AWSAccessKeyId": "foo",1416 {"AWSAccessKeyId": "foo",
1377 "Action": "DescribeInstances",1417 "Action": "DescribeInstances",
1378 "SignatureMethod": "HmacSHA256",1418 "SignatureMethod": "HmacSHA256",
1379 "SignatureVersion": "2",1419 "SignatureVersion": "2",
1380 "Version": "2008-12-01"},1420 "Version": "2008-12-01"})
1381 query.params)
13821421
1383 def test_init_other_args_are_params(self):1422 def test_init_other_args_are_params(self):
1384 query = client.Query(1423 query = client.Query(
@@ -1386,14 +1425,14 @@
1386 endpoint=self.endpoint, other_params={"InstanceId.0": "12345"},1425 endpoint=self.endpoint, other_params={"InstanceId.0": "12345"},
1387 time_tuple=(2007,11,12,13,14,15,0,0,0))1426 time_tuple=(2007,11,12,13,14,15,0,0,0))
1388 self.assertEqual(1427 self.assertEqual(
1428 query.params,
1389 {"AWSAccessKeyId": "foo",1429 {"AWSAccessKeyId": "foo",
1390 "Action": "DescribeInstances",1430 "Action": "DescribeInstances",
1391 "InstanceId.0": "12345",1431 "InstanceId.0": "12345",
1392 "SignatureMethod": "HmacSHA256",1432 "SignatureMethod": "HmacSHA256",
1393 "SignatureVersion": "2",1433 "SignatureVersion": "2",
1394 "Timestamp": "2007-11-12T13:14:15Z",1434 "Timestamp": "2007-11-12T13:14:15Z",
1395 "Version": "2008-12-01"},1435 "Version": "2008-12-01"})
1396 query.params)
13971436
1398 def test_sorted_params(self):1437 def test_sorted_params(self):
1399 query = client.Query(1438 query = client.Query(
@@ -1604,11 +1643,12 @@
16041643
1605 class StubQuery(object):1644 class StubQuery(object):
16061645
1607 def __init__(stub, action, creds, endpoint, params):1646 def __init__(stub, action="", creds=None, endpoint=None,
1647 other_params={}):
1608 self.assertEqual(action, "DescribeAddresses")1648 self.assertEqual(action, "DescribeAddresses")
1609 self.assertEqual(self.creds, creds)1649 self.assertEqual(self.creds, creds)
1610 self.assertEqual(self.endpoint, endpoint)1650 self.assertEqual(self.endpoint, endpoint)
1611 self.assertEquals(params, {})1651 self.assertEquals(other_params, {})
16121652
1613 def submit(self):1653 def submit(self):
1614 return succeed(payload.sample_describe_addresses_result)1654 return succeed(payload.sample_describe_addresses_result)
@@ -1625,12 +1665,13 @@
16251665
1626 class StubQuery(object):1666 class StubQuery(object):
16271667
1628 def __init__(stub, action, creds, endpoint, params):1668 def __init__(stub, action="", creds=None, endpoint=None,
1669 other_params={}):
1629 self.assertEqual(action, "DescribeAddresses")1670 self.assertEqual(action, "DescribeAddresses")
1630 self.assertEqual(self.creds, creds)1671 self.assertEqual(self.creds, creds)
1631 self.assertEqual(self.endpoint, endpoint)1672 self.assertEqual(self.endpoint, endpoint)
1632 self.assertEquals(1673 self.assertEquals(
1633 params,1674 other_params,
1634 {"PublicIp.1": "67.202.55.255"})1675 {"PublicIp.1": "67.202.55.255"})
16351676
1636 def submit(self):1677 def submit(self):
@@ -1648,12 +1689,13 @@
16481689
1649 class StubQuery(object):1690 class StubQuery(object):
16501691
1651 def __init__(stub, action, creds, endpoint, params):1692 def __init__(stub, action="", creds=None, endpoint=None,
1693 other_params={}):
1652 self.assertEqual(action, "AssociateAddress")1694 self.assertEqual(action, "AssociateAddress")
1653 self.assertEqual(self.creds, creds)1695 self.assertEqual(self.creds, creds)
1654 self.assertEqual(self.endpoint, endpoint)1696 self.assertEqual(self.endpoint, endpoint)
1655 self.assertEquals(1697 self.assertEquals(
1656 params,1698 other_params,
1657 {"InstanceId": "i-28a64341", "PublicIp": "67.202.55.255"})1699 {"InstanceId": "i-28a64341", "PublicIp": "67.202.55.255"})
16581700
1659 def submit(self):1701 def submit(self):
@@ -1669,11 +1711,12 @@
16691711
1670 class StubQuery(object):1712 class StubQuery(object):
16711713
1672 def __init__(stub, action, creds, endpoint, params):1714 def __init__(stub, action="", creds=None, endpoint=None,
1715 other_params={}):
1673 self.assertEqual(action, "AllocateAddress")1716 self.assertEqual(action, "AllocateAddress")
1674 self.assertEqual(self.creds, creds)1717 self.assertEqual(self.creds, creds)
1675 self.assertEqual(self.endpoint, endpoint)1718 self.assertEqual(self.endpoint, endpoint)
1676 self.assertEquals(params, {})1719 self.assertEquals(other_params, {})
16771720
1678 def submit(self):1721 def submit(self):
1679 return succeed(payload.sample_allocate_address_result)1722 return succeed(payload.sample_allocate_address_result)
@@ -1688,11 +1731,12 @@
16881731
1689 class StubQuery(object):1732 class StubQuery(object):
16901733
1691 def __init__(stub, action, creds, endpoint, params):1734 def __init__(stub, action="", creds=None, endpoint=None,
1735 other_params={}):
1692 self.assertEqual(action, "ReleaseAddress")1736 self.assertEqual(action, "ReleaseAddress")
1693 self.assertEqual(self.creds, creds)1737 self.assertEqual(self.creds, creds)
1694 self.assertEqual(self.endpoint, endpoint)1738 self.assertEqual(self.endpoint, endpoint)
1695 self.assertEquals(params, {"PublicIp": "67.202.55.255"})1739 self.assertEquals(other_params, {"PublicIp": "67.202.55.255"})
16961740
1697 def submit(self):1741 def submit(self):
1698 return succeed(payload.sample_release_address_result)1742 return succeed(payload.sample_release_address_result)
@@ -1707,11 +1751,12 @@
17071751
1708 class StubQuery(object):1752 class StubQuery(object):
17091753
1710 def __init__(stub, action, creds, endpoint, params):1754 def __init__(stub, action="", creds=None, endpoint=None,
1755 other_params={}):
1711 self.assertEqual(action, "DisassociateAddress")1756 self.assertEqual(action, "DisassociateAddress")
1712 self.assertEqual(self.creds, creds)1757 self.assertEqual(self.creds, creds)
1713 self.assertEqual(self.endpoint, endpoint)1758 self.assertEqual(self.endpoint, endpoint)
1714 self.assertEquals(params, {"PublicIp": "67.202.55.255"})1759 self.assertEquals(other_params, {"PublicIp": "67.202.55.255"})
17151760
1716 def submit(self):1761 def submit(self):
1717 return succeed(payload.sample_disassociate_address_result)1762 return succeed(payload.sample_disassociate_address_result)
17181763
=== modified file 'txaws/ec2/tests/test_exception.py'
--- txaws/ec2/tests/test_exception.py 2009-11-28 01:15:24 +0000
+++ txaws/ec2/tests/test_exception.py 2009-11-28 01:15:24 +0000
@@ -4,7 +4,6 @@
4from twisted.trial.unittest import TestCase4from twisted.trial.unittest import TestCase
55
6from txaws.ec2.exception import EC2Error6from txaws.ec2.exception import EC2Error
7from txaws.exception import AWSResponseParseError
8from txaws.testing import payload7from txaws.testing import payload
9from txaws.util import XML8from txaws.util import XML
109
@@ -14,77 +13,14 @@
1413
15class EC2ErrorTestCase(TestCase):14class EC2ErrorTestCase(TestCase):
1615
17 def test_creation(self):16 def test_set_400_error(self):
18 error = EC2Error("<dummy1 />", 400, "Not Found", "<dummy2 />")
19 self.assertEquals(error.status, 400)
20 self.assertEquals(error.response, "<dummy2 />")
21 self.assertEquals(error.original, "<dummy1 />")
22 self.assertEquals(error.errors, [])
23 self.assertEquals(error.request_id, "")
24
25 def test_node_to_dict(self):
26 xml = "<parent><child1>text1</child1><child2>text2</child2></parent>"
27 error = EC2Error("<dummy />")
28 data = error._node_to_dict(XML(xml))
29 self.assertEquals(data, {"child1": "text1", "child2": "text2"})
30
31 def test_set_request_id(self):
32 xml = "<a><b /><RequestID>%s</RequestID></a>" % REQUEST_ID
33 error = EC2Error("<dummy />")
34 error._set_request_id(XML(xml))
35 self.assertEquals(error.request_id, REQUEST_ID)
36
37 def test_set_400_errors(self):
38 errorsXML = "<Error><Code>1</Code><Message>2</Message></Error>"17 errorsXML = "<Error><Code>1</Code><Message>2</Message></Error>"
39 xml = "<a><Errors>%s</Errors><b /></a>" % errorsXML18 xml = "<a><Errors>%s</Errors><b /></a>" % errorsXML
40 error = EC2Error("<dummy />")19 error = EC2Error("<dummy />")
41 error._set_400_errors(XML(xml))20 error._set_400_error(XML(xml))
42 self.assertEquals(error.errors[0]["Code"], "1")21 self.assertEquals(error.errors[0]["Code"], "1")
43 self.assertEquals(error.errors[0]["Message"], "2")22 self.assertEquals(error.errors[0]["Message"], "2")
4423
45 def test_set_host_id(self):
46 host_id = "ASD@#FDG$E%FG"
47 xml = "<a><b /><HostID>%s</HostID></a>" % host_id
48 error = EC2Error("<dummy />")
49 error._set_host_id(XML(xml))
50 self.assertEquals(error.host_id, host_id)
51
52 def test_set_500_error(self):
53 xml = "<Error><Code>500</Code><Message>Oops</Message></Error>"
54 error = EC2Error("<dummy />")
55 error._set_500_error(XML(xml))
56 self.assertEquals(error.errors[0]["Code"], "500")
57 self.assertEquals(error.errors[0]["Message"], "Oops")
58
59 def test_set_empty_errors(self):
60 xml = "<a><Errors /><b /></a>"
61 error = EC2Error("<dummy />")
62 error._set_400_errors(XML(xml))
63 self.assertEquals(error.errors, [])
64
65 def test_set_empty_error(self):
66 xml = "<a><Errors><Error /><Error /></Errors><b /></a>"
67 error = EC2Error("<dummy />")
68 error._set_400_errors(XML(xml))
69 self.assertEquals(error.errors, [])
70
71 def test_parse_without_xml(self):
72 xml = "<dummy />"
73 error = EC2Error(xml)
74 error.parse()
75 self.assertEquals(error.original, xml)
76
77 def test_parse_with_xml(self):
78 xml1 = "<dummy1 />"
79 xml2 = "<dummy2 />"
80 error = EC2Error(xml1)
81 error.parse(xml2)
82 self.assertEquals(error.original, xml2)
83
84 def test_parse_html(self):
85 xml = "<html><body>a page</body></html>"
86 self.assertRaises(AWSResponseParseError, EC2Error, xml)
87
88 def test_has_error(self):24 def test_has_error(self):
89 errorsXML = "<Error><Code>Code1</Code><Message>2</Message></Error>"25 errorsXML = "<Error><Code>Code1</Code><Message>2</Message></Error>"
90 xml = "<a><Errors>%s</Errors><b /></a>" % errorsXML26 xml = "<a><Errors>%s</Errors><b /></a>" % errorsXML
@@ -99,69 +35,6 @@
99 error = EC2Error(payload.sample_ec2_error_messages)35 error = EC2Error(payload.sample_ec2_error_messages)
100 self.assertEquals(len(error.errors), 2)36 self.assertEquals(len(error.errors), 2)
10137
102 def test_empty_xml(self):
103 self.assertRaises(ValueError, EC2Error, "")
104
105 def test_no_request_id(self):
106 errors = "<Errors><Error><Code /><Message /></Error></Errors>"
107 xml = "<Response>%s<RequestID /></Response>" % errors
108 error = EC2Error(xml)
109 self.assertEquals(error.request_id, "")
110
111 def test_no_request_id_node(self):
112 errors = "<Errors><Error><Code /><Message /></Error></Errors>"
113 xml = "<Response>%s</Response>" % errors
114 error = EC2Error(xml)
115 self.assertEquals(error.request_id, "")
116
117 def test_no_errors_node(self):
118 xml = "<Response><RequestID /></Response>"
119 error = EC2Error(xml)
120 self.assertEquals(error.errors, [])
121
122 def test_no_error_node(self):
123 xml = "<Response><Errors /><RequestID /></Response>"
124 error = EC2Error(xml)
125 self.assertEquals(error.errors, [])
126
127 def test_no_error_code_node(self):
128 errors = "<Errors><Error><Message /></Error></Errors>"
129 xml = "<Response>%s<RequestID /></Response>" % errors
130 error = EC2Error(xml)
131 self.assertEquals(error.errors, [])
132
133 def test_no_error_message_node(self):
134 errors = "<Errors><Error><Code /></Error></Errors>"
135 xml = "<Response>%s<RequestID /></Response>" % errors
136 error = EC2Error(xml)
137 self.assertEquals(error.errors, [])
138
139 def test_single_get_error_codes(self):
140 error = EC2Error(payload.sample_ec2_error_message)
141 self.assertEquals(error.get_error_codes(), "Error.Code")
142
143 def test_multiple_get_error_codes(self):
144 error = EC2Error(payload.sample_ec2_error_messages)
145 self.assertEquals(error.get_error_codes(), 2)
146
147 def test_zero_get_error_codes(self):
148 xml = "<Response><RequestID /></Response>"
149 error = EC2Error(xml)
150 self.assertEquals(error.get_error_codes(), None)
151
152 def test_single_get_error_messages(self):
153 error = EC2Error(payload.sample_ec2_error_message)
154 self.assertEquals(error.get_error_messages(), "Message for Error.Code")
155
156 def test_multiple_get_error_messages(self):
157 error = EC2Error(payload.sample_ec2_error_messages)
158 self.assertEquals(error.get_error_messages(), "Multiple EC2 Errors")
159
160 def test_zero_get_error_messages(self):
161 xml = "<Response><RequestID /></Response>"
162 error = EC2Error(xml)
163 self.assertEquals(error.get_error_messages(), "Empty error list")
164
165 def test_single_error_str(self):38 def test_single_error_str(self):
166 error = EC2Error(payload.sample_ec2_error_message)39 error = EC2Error(payload.sample_ec2_error_message)
167 self.assertEquals(str(error), "Error Message: Message for Error.Code")40 self.assertEquals(str(error), "Error Message: Message for Error.Code")
16841
=== modified file 'txaws/exception.py'
--- txaws/exception.py 2009-10-28 17:43:23 +0000
+++ txaws/exception.py 2009-11-28 01:15:24 +0000
@@ -3,11 +3,124 @@
33
4from twisted.web.error import Error4from twisted.web.error import Error
55
6from txaws.util import XML
7
68
7class AWSError(Error):9class AWSError(Error):
8 """10 """
9 A base class for txAWS errors.11 A base class for txAWS errors.
10 """12 """
13 def __init__(self, xml_bytes, status=None, message=None, response=None):
14 super(AWSError, self).__init__(status, message, response)
15 if not xml_bytes:
16 raise ValueError("XML cannot be empty.")
17 self.original = xml_bytes
18 self.errors = []
19 self.request_id = ""
20 self.host_id = ""
21 self.parse()
22
23 def __str__(self):
24 return self._get_error_message_string()
25
26 def __repr__(self):
27 return "<%s object with %s>" % (
28 self.__class__.__name__, self._get_error_code_string())
29
30 def _set_request_id(self, tree):
31 request_id_node = tree.find(".//RequestID")
32 if hasattr(request_id_node, "text"):
33 text = request_id_node.text
34 if text:
35 self.request_id = text
36
37 def _set_host_id(self, tree):
38 host_id = tree.find(".//HostID")
39 if hasattr(host_id, "text"):
40 text = host_id.text
41 if text:
42 self.host_id = text
43
44 def _get_error_code_string(self):
45 count = len(self.errors)
46 error_code = self.get_error_codes()
47 if count > 1:
48 return "Error count: %s" % error_code
49 else:
50 return "Error code: %s" % error_code
51
52 def _get_error_message_string(self):
53 count = len(self.errors)
54 error_message = self.get_error_messages()
55 if count > 1:
56 return "%s." % error_message
57 else:
58 return "Error Message: %s" % error_message
59
60 def _node_to_dict(self, node):
61 data = {}
62 for child in node:
63 if child.tag and child.text:
64 data[child.tag] = child.text
65 return data
66
67 def _check_for_html(self, tree):
68 if tree.tag == "html":
69 message = "Could not parse HTML in the response."
70 raise AWSResponseParseError(message)
71
72 def _set_400_error(self, tree):
73 """
74 This method needs to be implemented by subclasses.
75 """
76
77 def _set_500_error(self, tree):
78 self._set_request_id(tree)
79 self._set_host_id(tree)
80 data = self._node_to_dict(tree)
81 if data:
82 self.errors.append(data)
83
84 def parse(self, xml_bytes=""):
85 if not xml_bytes:
86 xml_bytes = self.original
87 self.original = xml_bytes
88 tree = XML(xml_bytes.strip())
89 self._check_for_html(tree)
90 self._set_request_id(tree)
91 if self.status:
92 status = int(self.status)
93 else:
94 status = 400
95 if status >= 500:
96 self._set_500_error(tree)
97 else:
98 self._set_400_error(tree)
99
100 def has_error(self, errorString):
101 for error in self.errors:
102 if errorString in error.values():
103 return True
104 return False
105
106 def get_error_codes(self):
107 count = len(self.errors)
108 if count > 1:
109 return count
110 elif count == 0:
111 return
112 else:
113 return self.errors[0]["Code"]
114
115 def get_error_messages(self):
116 count = len(self.errors)
117 if count > 1:
118 return "Multiple EC2 Errors"
119 elif count == 0:
120 return "Empty error list"
121 else:
122 return self.errors[0]["Message"]
123
11124
12125
13class AWSResponseParseError(Exception):126class AWSResponseParseError(Exception):
14127
=== added file 'txaws/meta.py'
--- txaws/meta.py 1970-01-01 00:00:00 +0000
+++ txaws/meta.py 2009-11-28 01:15:24 +0000
@@ -0,0 +1,10 @@
1display_name = "txAWS"
2library_name = "txaws"
3author = "txAWS Deelopers"
4author_email = "txaws-dev@lists.launchpad.net"
5license = "MIT"
6url = "http://launchpad.net/txaws"
7description = """
8Twisted-based Asynchronous Libraries for Amazon Web Services
9"""
10
011
=== modified file 'txaws/s3/client.py'
--- txaws/s3/client.py 2009-11-28 01:15:24 +0000
+++ txaws/s3/client.py 2009-11-28 01:15:24 +0000
@@ -17,12 +17,17 @@
1717
18from epsilon.extime import Time18from epsilon.extime import Time
1919
20from txaws.client.base import BaseClient, BaseQuery20from txaws.client.base import BaseClient, BaseQuery, error_wrapper
21from txaws.s3 import model21from txaws.s3 import model
22from txaws.s3.exception import S3Error
22from txaws.service import AWSServiceEndpoint, S3_ENDPOINT23from txaws.service import AWSServiceEndpoint, S3_ENDPOINT
23from txaws.util import XML, calculate_md524from txaws.util import XML, calculate_md5
2425
2526
27def s3_error_wrapper(error):
28 error_wrapper(error, S3Error)
29
30
26class URLContext(object):31class URLContext(object):
27 """32 """
28 The hosts and the paths that form an S3 endpoint change depending upon the33 The hosts and the paths that form an S3 endpoint change depending upon the
@@ -129,6 +134,49 @@
129 url_context = BucketURLContext(self.endpoint, bucket)134 url_context = BucketURLContext(self.endpoint, bucket)
130 return query.submit(url_context)135 return query.submit(url_context)
131136
137 def get_bucket(self, bucket):
138 """
139 Get a list of all the objects in a bucket.
140 """
141 query = self.query_factory(
142 action="GET", creds=self.creds, endpoint=self.endpoint,
143 bucket=bucket)
144 url_context = BucketURLContext(self.endpoint, bucket)
145 d = query.submit(url_context)
146 return d.addCallback(self._parse_get_bucket)
147
148 def _parse_get_bucket(self, xml_bytes):
149 root = XML(xml_bytes)
150 name = root.findtext("Name")
151 prefix = root.findtext("Prefix")
152 marker = root.findtext("Marker")
153 max_keys = root.findtext("MaxKeys")
154 is_truncated = root.findtext("IsTruncated")
155 contents = []
156
157 for content_data in root.findall("Contents"):
158 key = content_data.findtext("Key")
159 date_text = content_data.findtext("LastModified")
160 modification_date = Time.fromISO8601TimeAndDate(
161 date_text).asDatetime()
162 etag = content_data.findtext("ETag")
163 size = content_data.findtext("Size")
164 storage_class = content_data.findtext("StorageClass")
165 owner_id = content_data.findtext("Owner/ID")
166 owner_display_name = content_data.findtext("Owner/DisplayName")
167 owner = model.ItemOwner(owner_id, owner_display_name)
168 content_item = model.BucketItem(
169 key, modification_date, etag, size, storage_class, owner)
170 contents.append(content_item)
171
172 common_prefixes = []
173 for prefix_data in root.findall("CommonPrefixes"):
174 common_prefixes.append(prefix_data.text)
175
176 return model.BucketListing(
177 name, prefix, marker, max_keys, is_truncated, contents,
178 common_prefixes)
179
132 def put_object(self, bucket, object_name, data, content_type=None,180 def put_object(self, bucket, object_name, data, content_type=None,
133 metadata={}):181 metadata={}):
134 """182 """
@@ -190,6 +238,10 @@
190 self.endpoint.set_method(self.action)238 self.endpoint.set_method(self.action)
191239
192 def set_content_type(self):240 def set_content_type(self):
241 """
242 Set the content type based on the file extension used in the object
243 name.
244 """
193 if self.object_name and not self.content_type:245 if self.object_name and not self.content_type:
194 # XXX nothing is currently done with the encoding... we may246 # XXX nothing is currently done with the encoding... we may
195 # need to in the future247 # need to in the future
@@ -197,6 +249,9 @@
197 self.object_name, strict=False)249 self.object_name, strict=False)
198250
199 def get_headers(self):251 def get_headers(self):
252 """
253 Build the list of headers needed in order to perform S3 operations.
254 """
200 headers = {"Content-Length": len(self.data),255 headers = {"Content-Length": len(self.data),
201 "Content-MD5": calculate_md5(self.data),256 "Content-MD5": calculate_md5(self.data),
202 "Date": self.date}257 "Date": self.date}
@@ -214,6 +269,9 @@
214 return headers269 return headers
215270
216 def get_canonicalized_amz_headers(self, headers):271 def get_canonicalized_amz_headers(self, headers):
272 """
273 Get the headers defined by Amazon S3.
274 """
217 headers = [275 headers = [
218 (name.lower(), value) for name, value in headers.iteritems()276 (name.lower(), value) for name, value in headers.iteritems()
219 if name.lower().startswith("x-amz-")]277 if name.lower().startswith("x-amz-")]
@@ -224,6 +282,9 @@
224 return "".join("%s:%s\n" % (name, value) for name, value in headers)282 return "".join("%s:%s\n" % (name, value) for name, value in headers)
225283
226 def get_canonicalized_resource(self):284 def get_canonicalized_resource(self):
285 """
286 Get an S3 resource path.
287 """
227 resource = "/"288 resource = "/"
228 if self.bucket:289 if self.bucket:
229 resource += self.bucket290 resource += self.bucket
@@ -232,7 +293,7 @@
232 return resource293 return resource
233294
234 def sign(self, headers):295 def sign(self, headers):
235296 """Sign this query using its built in credentials."""
236 text = (self.action + "\n" +297 text = (self.action + "\n" +
237 headers.get("Content-MD5", "") + "\n" +298 headers.get("Content-MD5", "") + "\n" +
238 headers.get("Content-Type", "") + "\n" +299 headers.get("Content-Type", "") + "\n" +
@@ -242,14 +303,14 @@
242 return self.creds.sign(text, hash_type="sha1")303 return self.creds.sign(text, hash_type="sha1")
243304
244 def submit(self, url_context=None):305 def submit(self, url_context=None):
306 """Submit this query.
307
308 @return: A deferred from get_page
309 """
245 if not url_context:310 if not url_context:
246 url_context = URLContext(311 url_context = URLContext(
247 self.endpoint, self.bucket, self.object_name)312 self.endpoint, self.bucket, self.object_name)
248 d = self.get_page(313 d = self.get_page(
249 url_context.get_url(), method=self.action, postdata=self.data,314 url_context.get_url(), method=self.action, postdata=self.data,
250 headers=self.get_headers())315 headers=self.get_headers())
251 # XXX - we need an error wrapper like we have for ec2... but let's316 return d.addErrback(s3_error_wrapper)
252 # wait until the new error-wrapper branch has landed, and possibly
253 # generalize a base class for all clients.
254 #d.addErrback(s3_error_wrapper)
255 return d
256317
=== added file 'txaws/s3/exception.py'
--- txaws/s3/exception.py 1970-01-01 00:00:00 +0000
+++ txaws/s3/exception.py 2009-11-28 01:15:24 +0000
@@ -0,0 +1,21 @@
1# Copyright (c) 2009 Canonical Ltd <duncan.mcgreggor@canonical.com>
2# Licenced under the txaws licence available at /LICENSE in the txaws source.
3
4from txaws.exception import AWSError
5
6
7class S3Error(AWSError):
8 """
9 A error class providing custom methods on S3 errors.
10 """
11 def _set_400_error(self, tree):
12 if tree.tag.lower() == "error":
13 data = self._node_to_dict(tree)
14 if data:
15 self.errors.append(data)
16
17 def get_error_code(self, *args, **kwargs):
18 return super(S3Error, self).get_error_codes(*args, **kwargs)
19
20 def get_error_message(self, *args, **kwargs):
21 return super(S3Error, self).get_error_messages(*args, **kwargs)
022
=== modified file 'txaws/s3/model.py'
--- txaws/s3/model.py 2009-11-28 01:15:24 +0000
+++ txaws/s3/model.py 2009-11-28 01:15:24 +0000
@@ -1,11 +1,47 @@
1class Bucket(object):1class Bucket(object):
2 """An Amazon S3 storage bucket."""2 """
33 An Amazon S3 storage bucket.
4 """
4 def __init__(self, name, creation_date):5 def __init__(self, name, creation_date):
5 self.name = name6 self.name = name
6 self.creation_date = creation_date7 self.creation_date = creation_date
78
89
10class ItemOwner(object):
11 """
12 The owner of a content item.
13 """
14 def __init__(self, id, display_name):
15 self.id = id
16 self.display_name = display_name
17
18
19class BucketItem(object):
20 """
21 The contents of an Amazon S3 bucket.
22 """
23 def __init__(self, key, modification_date, etag, size, storage_class,
24 owner=None):
25 self.key = key
26 self.modification_date = modification_date
27 self.etag = etag
28 self.size = size
29 self.storage_class = storage_class
30 self.owner = owner
31
32
33class BucketListing(object):
34 def __init__(self, name, prefix, marker, max_keys, is_truncated,
35 contents=None, common_prefixes=None):
36 self.name = name
37 self.prefix = prefix
38 self.marker = marker
39 self.max_keys = max_keys
40 self.is_truncated = is_truncated
41 self.contents = contents
42 self.common_prefixes = common_prefixes
43
44
9class FileChunk(object):45class FileChunk(object):
10 """46 """
11 An Amazon S3 file chunk.47 An Amazon S3 file chunk.
@@ -13,4 +49,3 @@
13 S3 returns file chunks, 10 MB at a time, until the entire file is returned.49 S3 returns file chunks, 10 MB at a time, until the entire file is returned.
14 These chunks need to be assembled once they are all returned.50 These chunks need to be assembled once they are all returned.
15 """51 """
16
1752
=== modified file 'txaws/s3/tests/test_client.py'
--- txaws/s3/tests/test_client.py 2009-11-28 01:15:24 +0000
+++ txaws/s3/tests/test_client.py 2009-11-28 01:15:24 +0000
@@ -129,6 +129,51 @@
129 s3 = client.S3Client(creds, query_factory=StubQuery)129 s3 = client.S3Client(creds, query_factory=StubQuery)
130 return s3.create_bucket("mybucket")130 return s3.create_bucket("mybucket")
131131
132 def test_get_bucket(self):
133
134 class StubQuery(client.Query):
135
136 def __init__(query, action, creds, endpoint, bucket=None):
137 super(StubQuery, query).__init__(
138 action=action, creds=creds, bucket=bucket)
139 self.assertEquals(action, "GET")
140 self.assertEqual(creds.access_key, "foo")
141 self.assertEqual(creds.secret_key, "bar")
142 self.assertEqual(query.bucket, "mybucket")
143 self.assertEqual(query.object_name, None)
144 self.assertEqual(query.data, "")
145 self.assertEqual(query.metadata, {})
146
147 def submit(query, url_context=None):
148 return succeed(payload.sample_get_bucket_result)
149
150 def check_results(listing):
151 self.assertEquals(listing.name, "mybucket")
152 self.assertEquals(listing.prefix, "N")
153 self.assertEquals(listing.marker, "Ned")
154 self.assertEquals(listing.max_keys, "40")
155 self.assertEquals(listing.is_truncated, "false")
156 self.assertEquals(len(listing.contents), 2)
157 content1 = listing.contents[0]
158 self.assertEquals(content1.key, "Nelson")
159 self.assertEquals(
160 content1.modification_date.timetuple(),
161 (2006, 1, 1, 12, 0, 0, 6, 1, 0))
162 self.assertEquals(
163 content1.etag, '"828ef3fdfa96f00ad9f27c383fc9ac7f"')
164 self.assertEquals(content1.size, "5")
165 self.assertEquals(content1.storage_class, "STANDARD")
166 owner = content1.owner
167 self.assertEquals(
168 owner.id,
169 "bcaf1ffd86f41caff1a493dc2ad8c2c281e37522a640e161ca5fb16fd081034f")
170 self.assertEquals(owner.display_name, "webfile")
171
172 creds = AWSCredentials("foo", "bar")
173 s3 = client.S3Client(creds, query_factory=StubQuery)
174 d = s3.get_bucket("mybucket")
175 return d.addCallback(check_results)
176
132 def test_delete_bucket(self):177 def test_delete_bucket(self):
133178
134 class StubQuery(client.Query):179 class StubQuery(client.Query):
@@ -344,7 +389,7 @@
344 query = client.Query(action="PUT", creds=self.creds)389 query = client.Query(action="PUT", creds=self.creds)
345 signed = query.sign({})390 signed = query.sign({})
346 self.assertEquals(signed, "H6UJCNHizzXZCGPl7wM6nL6tQdo=")391 self.assertEquals(signed, "H6UJCNHizzXZCGPl7wM6nL6tQdo=")
347 392
348 def test_object_query(self):393 def test_object_query(self):
349 """394 """
350 Test that a request addressing an object is created correctly.395 Test that a request addressing an object is created correctly.
@@ -421,7 +466,7 @@
421466
422 headers = query.get_headers()467 headers = query.get_headers()
423 self.assertEqual(468 self.assertEqual(
424 headers["Authorization"], 469 headers["Authorization"],
425 "AWS fookeyid:TESTINGSIG=")470 "AWS fookeyid:TESTINGSIG=")
426471
427472
428473
=== added file 'txaws/s3/tests/test_exception.py'
--- txaws/s3/tests/test_exception.py 1970-01-01 00:00:00 +0000
+++ txaws/s3/tests/test_exception.py 2009-11-28 01:15:24 +0000
@@ -0,0 +1,62 @@
1# Copyright (c) 2009 Canonical Ltd <duncan.mcgreggor@canonical.com>
2# Licenced under the txaws licence available at /LICENSE in the txaws source.
3
4from twisted.trial.unittest import TestCase
5
6from txaws.s3.exception import S3Error
7from txaws.testing import payload
8from txaws.util import XML
9
10
11REQUEST_ID = "0ef9fc37-6230-4d81-b2e6-1b36277d4247"
12
13
14class S3ErrorTestCase(TestCase):
15
16 def test_set_400_error(self):
17 xml = "<Error><Code>1</Code><Message>2</Message></Error>"
18 error = S3Error("<dummy />")
19 error._set_400_error(XML(xml))
20 self.assertEquals(error.errors[0]["Code"], "1")
21 self.assertEquals(error.errors[0]["Message"], "2")
22
23 def test_get_error_code(self):
24 error = S3Error(payload.sample_s3_invalid_access_key_result)
25 self.assertEquals(error.get_error_code(), "InvalidAccessKeyId")
26
27 def test_get_error_message(self):
28 error = S3Error(payload.sample_s3_invalid_access_key_result)
29 self.assertEquals(
30 error.get_error_message(),
31 ("The AWS Access Key Id you provided does not exist in our "
32 "records."))
33
34 def test_error_count(self):
35 error = S3Error(payload.sample_s3_invalid_access_key_result)
36 self.assertEquals(len(error.errors), 1)
37
38 def test_error_repr(self):
39 error = S3Error(payload.sample_s3_invalid_access_key_result)
40 self.assertEquals(
41 repr(error),
42 "<S3Error object with Error code: InvalidAccessKeyId>")
43
44 def test_signature_mismatch_result(self):
45 error = S3Error(payload.sample_s3_signature_mismatch)
46 self.assertEquals(
47 error.get_error_messages(),
48 ("The request signature we calculated does not match the "
49 "signature you provided. Check your key and signing method."))
50
51 def test_invalid_access_key_result(self):
52 error = S3Error(payload.sample_s3_invalid_access_key_result)
53 self.assertEquals(
54 error.get_error_messages(),
55 ("The AWS Access Key Id you provided does not exist in our "
56 "records."))
57
58 def test_internal_error_result(self):
59 error = S3Error(payload.sample_server_internal_error_result)
60 self.assertEquals(
61 error.get_error_messages(),
62 "We encountered an internal error. Please try again.")
063
=== added file 'txaws/script.py'
--- txaws/script.py 1970-01-01 00:00:00 +0000
+++ txaws/script.py 2009-11-28 01:15:24 +0000
@@ -0,0 +1,42 @@
1from optparse import OptionParser
2
3from txaws import meta
4from txaws import version
5
6
7# XXX Once we start adding script that require conflicting options, we'll need
8# multiple parsers and option dispatching...
9def parse_options(usage):
10 parser = OptionParser(usage, version="%s %s" % (
11 meta.display_name, version.txaws))
12 parser.add_option(
13 "-a", "--access-key", dest="access_key", help="access key ID")
14 parser.add_option(
15 "-s", "--secret-key", dest="secret_key", help="access secret key")
16 parser.add_option(
17 "-r", "--region", dest="region", help="US or EU (valid for AWS only)")
18 parser.add_option(
19 "-U", "--url", dest="url", help="service URL/endpoint")
20 parser.add_option(
21 "-b", "--bucket", dest="bucket", help="name of the bucket")
22 parser.add_option(
23 "-o", "--object-name", dest="object_name", help="name of the object")
24 parser.add_option(
25 "-d", "--object-data", dest="object_data",
26 help="content data of the object")
27 parser.add_option(
28 "--object-file", dest="object_filename",
29 help=("the path to the file that will be saved as an object; if "
30 "provided, the --object-name and --object-data options are "
31 "not necessary"))
32 parser.add_option(
33 "-c", "--content-type", dest="content_type",
34 help="content type of the object")
35 options, args = parser.parse_args()
36 if not (options.access_key and options.secret_key):
37 parser.error(
38 "both the access key ID and the secret key must be supplied")
39 region = options.region
40 if region and region.upper() not in ["US", "EU"]:
41 parser.error("region must be one of 'US' or 'EU'")
42 return (options, args)
043
=== modified file 'txaws/service.py'
--- txaws/service.py 2009-11-28 01:15:24 +0000
+++ txaws/service.py 2009-11-28 01:15:24 +0000
@@ -77,19 +77,22 @@
77 """77 """
78 # XXX update unit test to check for both ec2 and s3 endpoints78 # XXX update unit test to check for both ec2 and s3 endpoints
79 def __init__(self, creds=None, access_key="", secret_key="",79 def __init__(self, creds=None, access_key="", secret_key="",
80 region=REGION_US, ec2_endpoint="", s3_endpoint=""):80 region=REGION_US, uri="", ec2_uri="", s3_uri=""):
81 if not creds:81 if not creds:
82 creds = AWSCredentials(access_key, secret_key)82 creds = AWSCredentials(access_key, secret_key)
83 self.creds = creds83 self.creds = creds
84 if not ec2_endpoint and region == REGION_US:84 # Provide backwards compatibility for the "uri" parameter.
85 ec2_endpoint = EC2_ENDPOINT_US85 if uri and not ec2_uri:
86 elif not ec2_endpoint and region == REGION_EU:86 ec2_uri = uri
87 ec2_endpoint = EC2_ENDPOINT_EU87 if not ec2_uri and region == REGION_US:
88 if not s3_endpoint:88 ec2_uri = EC2_ENDPOINT_US
89 s3_endpoint = S3_ENDPOINT89 elif not ec2_uri and region == REGION_EU:
90 ec2_uri = EC2_ENDPOINT_EU
91 if not s3_uri:
92 s3_uri = S3_ENDPOINT
90 self._clients = {}93 self._clients = {}
91 self.ec2_endpoint = AWSServiceEndpoint(uri=ec2_endpoint)94 self.ec2_endpoint = AWSServiceEndpoint(uri=ec2_uri)
92 self.s3_endpoint = AWSServiceEndpoint(uri=s3_endpoint)95 self.s3_endpoint = AWSServiceEndpoint(uri=s3_uri)
9396
94 def get_client(self, cls, purge_cache=False, *args, **kwds):97 def get_client(self, cls, purge_cache=False, *args, **kwds):
95 """98 """
9699
=== modified file 'txaws/testing/payload.py'
--- txaws/testing/payload.py 2009-11-28 01:15:24 +0000
+++ txaws/testing/payload.py 2009-11-28 01:15:24 +0000
@@ -656,6 +656,31 @@
656"""656"""
657657
658658
659sample_restricted_resource_result = """\
660<?xml version="1.0"?>
661<Response>
662 <Errors>
663 <Error>
664 <Code>AuthFailure</Code>
665 <Message>Unauthorized attempt to access restricted resource</Message>
666 </Error>
667 </Errors>
668 <RequestID>a99e832e-e6e0-416a-9a35-81798ea521b4</RequestID>
669</Response>
670"""
671
672
673sample_server_internal_error_result = """\
674<?xml version="1.0" encoding="UTF-8"?>
675<Error>
676 <Code>InternalError</Code>
677 <Message>We encountered an internal error. Please try again.</Message>
678 <RequestID>A2A7E5395E27DFBB</RequestID>
679 <HostID>f691zulHNsUqonsZkjhILnvWwD3ZnmOM4ObM1wXTc6xuS3GzPmjArp8QC/sGsn6K</HostID>
680</Error>
681"""
682
683
659sample_list_buckets_result = """\684sample_list_buckets_result = """\
660<?xml version="1.0" encoding="UTF-8"?>685<?xml version="1.0" encoding="UTF-8"?>
661<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/%s/">686<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/%s/">
@@ -677,6 +702,39 @@
677""" % (version.s3_api,)702""" % (version.s3_api,)
678703
679704
705sample_get_bucket_result = """\
706<?xml version="1.0" encoding="UTF-8"?>
707<ListBucketResult xmlns="http://s3.amazonaws.com/doc/%s/">
708 <Name>mybucket</Name>
709 <Prefix>N</Prefix>
710 <Marker>Ned</Marker>
711 <MaxKeys>40</MaxKeys>
712 <IsTruncated>false</IsTruncated>
713 <Contents>
714 <Key>Nelson</Key>
715 <LastModified>2006-01-01T12:00:00.000Z</LastModified>
716 <ETag>&quot;828ef3fdfa96f00ad9f27c383fc9ac7f&quot;</ETag>
717 <Size>5</Size>
718 <StorageClass>STANDARD</StorageClass>
719 <Owner>
720 <ID>bcaf1ffd86f41caff1a493dc2ad8c2c281e37522a640e161ca5fb16fd081034f</ID>
721 <DisplayName>webfile</DisplayName>
722 </Owner>
723 </Contents>
724 <Contents>
725 <Key>Neo</Key>
726 <LastModified>2006-01-01T12:00:00.000Z</LastModified>
727 <ETag>&quot;828ef3fdfa96f00ad9f27c383fc9ac7f&quot;</ETag>
728 <Size>4</Size>
729 <StorageClass>STANDARD</StorageClass>
730 <Owner>
731 <ID>bcaf1ffd86f41caff1a493dc2ad8c2c281e37522a640e161ca5fb16fd081034f</ID>
732 <DisplayName>webfile</DisplayName>
733 </Owner>
734 </Contents>
735</ListBucketResult>
736""" % (version.s3_api,)
737
680sample_s3_signature_mismatch = """\738sample_s3_signature_mismatch = """\
681<?xml version="1.0" encoding="UTF-8"?>739<?xml version="1.0" encoding="UTF-8"?>
682<Error>740<Error>
@@ -692,26 +750,13 @@
692"""750"""
693751
694752
695sample_server_internal_error_result = """\753sample_s3_invalid_access_key_result = """\
696<?xml version="1.0" encoding="UTF-8"?>754<?xml version="1.0" encoding="UTF-8"?>
697<Error>755<Error>
698 <Code>InternalError</Code>756 <Code>InvalidAccessKeyId</Code>
699 <Message>We encountered an internal error. Please try again.</Message>757 <Message>The AWS Access Key Id you provided does not exist in our records.</Message>
700 <RequestID>A2A7E5395E27DFBB</RequestID>758 <RequestId>0223AD81A94821CE</RequestId>
701 <HostID>f691zulHNsUqonsZkjhILnvWwD3ZnmOM4ObM1wXTc6xuS3GzPmjArp8QC/sGsn6K</HostID>759 <HostId>HAw5g9P1VkN8ztgLKFTK20CY5LmCfTwXcSths1O7UQV6NuJx2P4tmFnpuOsziwOE</HostId>
760 <AWSAccessKeyId>SOMEKEYID</AWSAccessKeyId>
702</Error>761</Error>
703"""762"""
704
705
706sample_restricted_resource_result = """\
707<?xml version="1.0"?>
708<Response>
709 <Errors>
710 <Error>
711 <Code>AuthFailure</Code>
712 <Message>Unauthorized attempt to access restricted resource</Message>
713 </Error>
714 </Errors>
715 <RequestID>a99e832e-e6e0-416a-9a35-81798ea521b4</RequestID>
716</Response>
717"""
718763
=== added file 'txaws/tests/test_exception.py'
--- txaws/tests/test_exception.py 1970-01-01 00:00:00 +0000
+++ txaws/tests/test_exception.py 2009-11-28 01:15:24 +0000
@@ -0,0 +1,114 @@
1# Copyright (c) 2009 Canonical Ltd <duncan.mcgreggor@canonical.com>
2# Licenced under the txaws licence available at /LICENSE in the txaws source.
3
4from twisted.trial.unittest import TestCase
5
6from txaws.exception import AWSError
7from txaws.exception import AWSResponseParseError
8from txaws.util import XML
9
10
11REQUEST_ID = "0ef9fc37-6230-4d81-b2e6-1b36277d4247"
12
13
14class AWSErrorTestCase(TestCase):
15
16 def test_creation(self):
17 error = AWSError("<dummy1 />", 500, "Server Error", "<dummy2 />")
18 self.assertEquals(error.status, 500)
19 self.assertEquals(error.response, "<dummy2 />")
20 self.assertEquals(error.original, "<dummy1 />")
21 self.assertEquals(error.errors, [])
22 self.assertEquals(error.request_id, "")
23
24 def test_node_to_dict(self):
25 xml = "<parent><child1>text1</child1><child2>text2</child2></parent>"
26 error = AWSError("<dummy />")
27 data = error._node_to_dict(XML(xml))
28 self.assertEquals(data, {"child1": "text1", "child2": "text2"})
29
30 def test_set_request_id(self):
31 xml = "<a><b /><RequestID>%s</RequestID></a>" % REQUEST_ID
32 error = AWSError("<dummy />")
33 error._set_request_id(XML(xml))
34 self.assertEquals(error.request_id, REQUEST_ID)
35
36 def test_set_host_id(self):
37 host_id = "ASD@#FDG$E%FG"
38 xml = "<a><b /><HostID>%s</HostID></a>" % host_id
39 error = AWSError("<dummy />")
40 error._set_host_id(XML(xml))
41 self.assertEquals(error.host_id, host_id)
42
43 def test_set_empty_errors(self):
44 xml = "<a><Errors /><b /></a>"
45 error = AWSError("<dummy />")
46 error._set_500_error(XML(xml))
47 self.assertEquals(error.errors, [])
48
49 def test_set_empty_error(self):
50 xml = "<a><Errors><Error /><Error /></Errors><b /></a>"
51 error = AWSError("<dummy />")
52 error._set_500_error(XML(xml))
53 self.assertEquals(error.errors, [])
54
55 def test_parse_without_xml(self):
56 xml = "<dummy />"
57 error = AWSError(xml)
58 error.parse()
59 self.assertEquals(error.original, xml)
60
61 def test_parse_with_xml(self):
62 xml1 = "<dummy1 />"
63 xml2 = "<dummy2 />"
64 error = AWSError(xml1)
65 error.parse(xml2)
66 self.assertEquals(error.original, xml2)
67
68 def test_parse_html(self):
69 xml = "<html><body>a page</body></html>"
70 self.assertRaises(AWSResponseParseError, AWSError, xml)
71
72 def test_empty_xml(self):
73 self.assertRaises(ValueError, AWSError, "")
74
75 def test_no_request_id(self):
76 errors = "<Errors><Error><Code /><Message /></Error></Errors>"
77 xml = "<Response>%s<RequestID /></Response>" % errors
78 error = AWSError(xml)
79 self.assertEquals(error.request_id, "")
80
81 def test_no_request_id_node(self):
82 errors = "<Errors><Error><Code /><Message /></Error></Errors>"
83 xml = "<Response>%s</Response>" % errors
84 error = AWSError(xml)
85 self.assertEquals(error.request_id, "")
86
87 def test_no_errors_node(self):
88 xml = "<Response><RequestID /></Response>"
89 error = AWSError(xml)
90 self.assertEquals(error.errors, [])
91
92 def test_no_error_node(self):
93 xml = "<Response><Errors /><RequestID /></Response>"
94 error = AWSError(xml)
95 self.assertEquals(error.errors, [])
96
97 def test_no_error_code_node(self):
98 errors = "<Errors><Error><Message /></Error></Errors>"
99 xml = "<Response>%s<RequestID /></Response>" % errors
100 error = AWSError(xml)
101 self.assertEquals(error.errors, [])
102
103 def test_no_error_message_node(self):
104 errors = "<Errors><Error><Code /></Error></Errors>"
105 xml = "<Response>%s<RequestID /></Response>" % errors
106 error = AWSError(xml)
107 self.assertEquals(error.errors, [])
108
109 def test_set_500_error(self):
110 xml = "<Error><Code>500</Code><Message>Oops</Message></Error>"
111 error = AWSError("<dummy />")
112 error._set_500_error(XML(xml))
113 self.assertEquals(error.errors[0]["Code"], "500")
114 self.assertEquals(error.errors[0]["Message"], "Oops")
0115
=== modified file 'txaws/tests/test_service.py'
--- txaws/tests/test_service.py 2009-11-28 01:15:24 +0000
+++ txaws/tests/test_service.py 2009-11-28 01:15:24 +0000
@@ -97,12 +97,17 @@
9797
98 def test_creation_with_uri(self):98 def test_creation_with_uri(self):
99 region = AWSServiceRegion(99 region = AWSServiceRegion(
100 creds=self.creds, ec2_endpoint="http://foo/bar")100 creds=self.creds, ec2_uri="http://foo/bar")
101 self.assertEquals(region.ec2_endpoint.get_uri(), "http://foo/bar")
102
103 def test_creation_with_uri_backwards_compatible(self):
104 region = AWSServiceRegion(
105 creds=self.creds, uri="http://foo/bar")
101 self.assertEquals(region.ec2_endpoint.get_uri(), "http://foo/bar")106 self.assertEquals(region.ec2_endpoint.get_uri(), "http://foo/bar")
102107
103 def test_creation_with_uri_and_region(self):108 def test_creation_with_uri_and_region(self):
104 region = AWSServiceRegion(109 region = AWSServiceRegion(
105 creds=self.creds, region=REGION_EU, ec2_endpoint="http://foo/bar")110 creds=self.creds, region=REGION_EU, ec2_uri="http://foo/bar")
106 self.assertEquals(region.ec2_endpoint.get_uri(), "http://foo/bar")111 self.assertEquals(region.ec2_endpoint.get_uri(), "http://foo/bar")
107112
108 def test_creation_with_region_override(self):113 def test_creation_with_region_override(self):
109114
=== modified file 'txaws/util.py'
--- txaws/util.py 2009-11-28 01:15:24 +0000
+++ txaws/util.py 2009-11-28 01:15:24 +0000
@@ -94,3 +94,33 @@
94 if path == "":94 if path == "":
95 path = "/"95 path = "/"
96 return (str(scheme), str(host), port, str(path))96 return (str(scheme), str(host), port, str(path))
97
98
99def get_exitcode_reactor():
100 """
101 This is only neccesary until a fix like the one outlined here is
102 implemented for Twisted:
103 http://twistedmatrix.com/trac/ticket/2182
104 """
105 from twisted.internet.main import installReactor
106 from twisted.internet.selectreactor import SelectReactor
107
108 class ExitCodeReactor(SelectReactor):
109
110 def stop(self, exitStatus=0):
111 super(ExitCodeReactor, self).stop()
112 self.exitStatus = exitStatus
113
114 def run(self, *args, **kwargs):
115 super(ExitCodeReactor, self).run(*args, **kwargs)
116 return self.exitStatus
117
118 reactor = ExitCodeReactor()
119 installReactor(reactor)
120 return reactor
121
122
123try:
124 reactor = get_exitcode_reactor()
125except:
126 from twisted.internet import reactor

Subscribers

People subscribed via source and target branches

to all changes: