Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Commit

Permalink
Merge pull request #2373 from felixonmars/cloudformation-py3
Browse files Browse the repository at this point in the history
cloudformation module: add backward-compatible support for Python 3.3+. Fixes #2373.
  • Loading branch information
danielgtaylor committed Jul 8, 2014
2 parents 5e81fca + a8484bb commit 9872f27
Show file tree
Hide file tree
Showing 6 changed files with 33 additions and 38 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ At the moment, boto supports:
* Deployment and Management

* AWS Elastic Beanstalk
* AWS CloudFormation
* AWS CloudFormation (Python 3)
* AWS Data Pipeline
* AWS Opsworks
* AWS CloudTrail (Python 3)
Expand Down
2 changes: 1 addition & 1 deletion boto/cloudformation/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ def _do_request(self, call, params, path, method):
:return: Parsed JSON response data
"""
response = self.make_request(call, params, path, method)
body = response.read()
body = response.read().decode('utf-8')
if response.status == 200:
body = json.loads(body)
return body
Expand Down
2 changes: 1 addition & 1 deletion docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Currently Supported Services

* **Deployment and Management**

* CloudFormation -- (:doc:`API Reference <ref/cloudformation>`)
* CloudFormation -- (:doc:`API Reference <ref/cloudformation>`) (Python 3)
* Elastic Beanstalk -- (:doc:`API Reference <ref/beanstalk>`)
* Data Pipeline -- (:doc:`API Reference <ref/datapipeline>`)
* Opsworks -- (:doc:`API Reference <ref/opsworks>`)
Expand Down
1 change: 1 addition & 0 deletions tests/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
PY3_WHITELIST = (
'tests/unit/auth',
'tests/unit/beanstalk',
'tests/unit/cloudformation',
'tests/unit/cloudtrail',
'tests/unit/directconnect',
'tests/unit/elasticache',
Expand Down
46 changes: 20 additions & 26 deletions tests/unit/cloudformation/test_connection.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
#!/usr/bin/env python
import unittest
import httplib
from datetime import datetime
try:
import json
except ImportError:
import simplejson as json

from mock import Mock

from tests.unit import AWSMockServiceTestCase
from boto.cloudformation.connection import CloudFormationConnection
from boto.exception import BotoServerError

from boto.compat import json

SAMPLE_TEMPLATE = r"""
{
Expand Down Expand Up @@ -55,7 +49,7 @@ def default_body(self):
return json.dumps(
{u'CreateStackResponse':
{u'CreateStackResult': {u'StackId': self.stack_id},
u'ResponseMetadata': {u'RequestId': u'1'}}})
u'ResponseMetadata': {u'RequestId': u'1'}}}).encode('utf-8')

def test_create_stack_has_correct_request_params(self):
self.set_http_response(status_code=200)
Expand Down Expand Up @@ -109,7 +103,7 @@ def test_create_stack_with_minimum_args(self):

def test_create_stack_fails(self):
self.set_http_response(status_code=400, reason='Bad Request',
body='{"Error": {"Code": 1, "Message": "Invalid arg."}}')
body=b'{"Error": {"Code": 1, "Message": "Invalid arg."}}')
with self.assertRaisesRegexp(self.service_connection.ResponseError,
'Invalid arg.'):
api_response = self.service_connection.create_stack(
Expand All @@ -118,7 +112,7 @@ def test_create_stack_fails(self):

def test_create_stack_fail_error(self):
self.set_http_response(status_code=400, reason='Bad Request',
body='{"RequestId": "abc", "Error": {"Code": 1, "Message": "Invalid arg."}}')
body=b'{"RequestId": "abc", "Error": {"Code": 1, "Message": "Invalid arg."}}')
try:
api_response = self.service_connection.create_stack(
'stack_name', template_body=SAMPLE_TEMPLATE,
Expand All @@ -133,7 +127,7 @@ def default_body(self):
return json.dumps(
{u'UpdateStackResponse':
{u'UpdateStackResult': {u'StackId': self.stack_id},
u'ResponseMetadata': {u'RequestId': u'1'}}})
u'ResponseMetadata': {u'RequestId': u'1'}}}).encode('utf-8')

def test_update_stack_all_args(self):
self.set_http_response(status_code=200)
Expand Down Expand Up @@ -177,7 +171,7 @@ def test_update_stack_with_minimum_args(self):

def test_update_stack_fails(self):
self.set_http_response(status_code=400, reason='Bad Request',
body='Invalid arg.')
body=b'Invalid arg.')
with self.assertRaises(self.service_connection.ResponseError):
api_response = self.service_connection.update_stack(
'stack_name', template_body=SAMPLE_TEMPLATE,
Expand All @@ -188,12 +182,12 @@ class TestCloudFormationDeleteStack(CloudFormationConnectionBase):
def default_body(self):
return json.dumps(
{u'DeleteStackResponse':
{u'ResponseMetadata': {u'RequestId': u'1'}}})
{u'ResponseMetadata': {u'RequestId': u'1'}}}).encode('utf-8')

def test_delete_stack(self):
self.set_http_response(status_code=200)
api_response = self.service_connection.delete_stack('stack_name')
self.assertEqual(api_response, json.loads(self.default_body()))
self.assertEqual(api_response, json.loads(self.default_body().decode('utf-8')))
self.assert_request_parameters({
'Action': 'DeleteStack',
'ContentType': 'JSON',
Expand All @@ -209,7 +203,7 @@ def test_delete_stack_fails(self):

class TestCloudFormationDescribeStackResource(CloudFormationConnectionBase):
def default_body(self):
return json.dumps('fake server response')
return json.dumps('fake server response').encode('utf-8')

def test_describe_stack_resource(self):
self.set_http_response(status_code=200)
Expand All @@ -233,7 +227,7 @@ def test_describe_stack_resource_fails(self):

class TestCloudFormationGetTemplate(CloudFormationConnectionBase):
def default_body(self):
return json.dumps('fake server response')
return json.dumps('fake server response').encode('utf-8')

def test_get_template(self):
self.set_http_response(status_code=200)
Expand All @@ -255,7 +249,7 @@ def test_get_template_fails(self):

class TestCloudFormationGetStackevents(CloudFormationConnectionBase):
def default_body(self):
return """
return b"""
<DescribeStackEventsResult>
<StackEvents>
<member>
Expand Down Expand Up @@ -318,7 +312,7 @@ def test_describe_stack_events(self):

class TestCloudFormationDescribeStackResources(CloudFormationConnectionBase):
def default_body(self):
return """
return b"""
<DescribeStackResourcesResult>
<StackResources>
<member>
Expand Down Expand Up @@ -378,7 +372,7 @@ def test_describe_stack_resources(self):

class TestCloudFormationDescribeStacks(CloudFormationConnectionBase):
def default_body(self):
return """
return b"""
<DescribeStacksResponse>
<DescribeStacksResult>
<Stacks>
Expand Down Expand Up @@ -468,7 +462,7 @@ def test_describe_stacks(self):

class TestCloudFormationListStackResources(CloudFormationConnectionBase):
def default_body(self):
return """
return b"""
<ListStackResourcesResponse>
<ListStackResourcesResult>
<StackResourceSummaries>
Expand Down Expand Up @@ -525,7 +519,7 @@ def test_list_stack_resources(self):

class TestCloudFormationListStacks(CloudFormationConnectionBase):
def default_body(self):
return """
return b"""
<ListStacksResponse>
<ListStacksResult>
<StackSummaries>
Expand Down Expand Up @@ -565,7 +559,7 @@ def test_list_stacks(self):

class TestCloudFormationValidateTemplate(CloudFormationConnectionBase):
def default_body(self):
return """
return b"""
<ValidateTemplateResponse xmlns="http://cloudformation.amazonaws.com/doc/2010-05-15/">
<ValidateTemplateResult>
<Description>My Description.</Description>
Expand Down Expand Up @@ -625,7 +619,7 @@ def test_validate_template(self):

class TestCloudFormationCancelUpdateStack(CloudFormationConnectionBase):
def default_body(self):
return """<CancelUpdateStackResult/>"""
return b"""<CancelUpdateStackResult/>"""

def test_cancel_update_stack(self):
self.set_http_response(status_code=200)
Expand All @@ -640,7 +634,7 @@ def test_cancel_update_stack(self):

class TestCloudFormationEstimateTemplateCost(CloudFormationConnectionBase):
def default_body(self):
return """
return b"""
{
"EstimateTemplateCostResponse": {
"EstimateTemplateCostResult": {
Expand All @@ -666,7 +660,7 @@ def test_estimate_template_cost(self):

class TestCloudFormationGetStackPolicy(CloudFormationConnectionBase):
def default_body(self):
return """
return b"""
{
"GetStackPolicyResponse": {
"GetStackPolicyResult": {
Expand All @@ -690,7 +684,7 @@ def test_get_stack_policy(self):

class TestCloudFormationSetStackPolicy(CloudFormationConnectionBase):
def default_body(self):
return """
return b"""
{
"SetStackPolicyResponse": {
"SetStackPolicyResult": {
Expand Down
18 changes: 9 additions & 9 deletions tests/unit/cloudformation/test_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import boto.resultset
import boto.cloudformation

SAMPLE_XML = r"""
SAMPLE_XML = b"""
<DescribeStacksResponse xmlns="http://cloudformation.amazonaws.com/doc/2010-05-15/">
<DescribeStacksResult>
<Stacks>
Expand Down Expand Up @@ -52,7 +52,7 @@
</DescribeStacksResponse>
"""

DESCRIBE_STACK_RESOURCE_XML = r"""
DESCRIBE_STACK_RESOURCE_XML = b"""
<DescribeStackResourcesResult>
<StackResources>
<member>
Expand All @@ -77,7 +77,7 @@
</DescribeStackResourcesResult>
"""

LIST_STACKS_XML = r"""
LIST_STACKS_XML = b"""
<ListStacksResponse>
<ListStacksResult>
<StackSummaries>
Expand Down Expand Up @@ -109,7 +109,7 @@
</ListStacksResponse>
"""

LIST_STACK_RESOURCES_XML = r"""
LIST_STACK_RESOURCES_XML = b"""
<ListStackResourcesResponse>
<ListStackResourcesResult>
<StackResourceSummaries>
Expand Down Expand Up @@ -147,8 +147,8 @@ def test_parse_tags(self):

def test_event_creation_time_with_millis(self):
millis_xml = SAMPLE_XML.replace(
"<CreationTime>2013-01-10T05:04:56Z</CreationTime>",
"<CreationTime>2013-01-10T05:04:56.102342Z</CreationTime>"
b"<CreationTime>2013-01-10T05:04:56Z</CreationTime>",
b"<CreationTime>2013-01-10T05:04:56.102342Z</CreationTime>"
)

rs = boto.resultset.ResultSet([
Expand Down Expand Up @@ -230,23 +230,23 @@ def test_disable_rollback_false_upper(self):
# Should also handle "False"
rs = boto.resultset.ResultSet([('member', boto.cloudformation.stack.Stack)])
h = boto.handler.XmlHandler(rs, None)
sample_xml_upper = SAMPLE_XML.replace('false', 'False')
sample_xml_upper = SAMPLE_XML.replace(b'false', b'False')
xml.sax.parseString(sample_xml_upper, h)
disable_rollback = rs[0].disable_rollback
self.assertFalse(disable_rollback)

def test_disable_rollback_true(self):
rs = boto.resultset.ResultSet([('member', boto.cloudformation.stack.Stack)])
h = boto.handler.XmlHandler(rs, None)
sample_xml_upper = SAMPLE_XML.replace('false', 'true')
sample_xml_upper = SAMPLE_XML.replace(b'false', b'true')
xml.sax.parseString(sample_xml_upper, h)
disable_rollback = rs[0].disable_rollback
self.assertTrue(disable_rollback)

def test_disable_rollback_true_upper(self):
rs = boto.resultset.ResultSet([('member', boto.cloudformation.stack.Stack)])
h = boto.handler.XmlHandler(rs, None)
sample_xml_upper = SAMPLE_XML.replace('false', 'True')
sample_xml_upper = SAMPLE_XML.replace(b'false', b'True')
xml.sax.parseString(sample_xml_upper, h)
disable_rollback = rs[0].disable_rollback
self.assertTrue(disable_rollback)
Expand Down

0 comments on commit 9872f27

Please sign in to comment.