Repository: boto/botocore
Pull request #425 (2014-12-30 21:12:11)

Parse empty xml nodes that are strings are an empty string
This allows `get_*` operations to better round trip with the
corresponding `put_*` operations.

We can get the code reviewed, but I'm not sure this is change we want.
It has the potential to break users so we'll need to evaluate if we can take
this change.

cc @kyleknap @danielgtaylor 

Fixes https://github.com/aws/aws-cli/issues/1076


Patch:
diff --git a/botocore/parsers.py b/botocore/parsers.py
index 59cc911c2..a27ae1701 100644
--- a/botocore/parsers.py
+++ b/botocore/parsers.py
@@ -141,6 +141,11 @@ def _text_content(func):
     def _get_text_content(self, shape, node_or_string):
         if hasattr(node_or_string, 'text'):
             text = node_or_string.text
+            if text is None:
+                # If an XML node is empty <foo></foo>,
+                # we want to parse that as an empty string,
+                # not as a null/None value.
+                text = ''
         else:
             text = node_or_string
         return func(self, shape, text)


Test patch:
diff --git a/tests/unit/protocols/output/rest-xml.json b/tests/unit/protocols/output/rest-xml.json
index 4723c3a3f..ba1f09ffa 100644
--- a/tests/unit/protocols/output/rest-xml.json
+++ b/tests/unit/protocols/output/rest-xml.json
@@ -107,6 +107,35 @@
           },
           "body": "<OperationNameResponse><Str>myname</Str><FooNum>123</FooNum><FalseBool>false</FalseBool><TrueBool>true</TrueBool><Float>1.2</Float><Double>1.3</Double><Long>200</Long><Char>a</Char><Timestamp>2015-01-25T08:00:00Z</Timestamp></OperationNameResponse>"
         }
+      },
+      {
+        "given": {
+          "output": {
+            "shape": "OutputShape"
+          },
+          "name": "OperationName"
+        },
+        "result": {
+          "ImaHeader": "test",
+          "ImaHeaderLocation": "abc",
+          "Str": "",
+          "Num": 123,
+          "FalseBool": false,
+          "TrueBool": true,
+          "Float": 1.2,
+          "Double": 1.3,
+          "Long": 200,
+          "Char": "a",
+          "Timestamp": 1422172800
+        },
+        "response": {
+          "status_code": 200,
+          "headers": {
+            "ImaHeader": "test",
+            "X-Foo": "abc"
+          },
+          "body": "<OperationNameResponse><Str></Str><FooNum>123</FooNum><FalseBool>false</FalseBool><TrueBool>true</TrueBool><Float>1.2</Float><Double>1.3</Double><Long>200</Long><Char>a</Char><Timestamp>2015-01-25T08:00:00Z</Timestamp></OperationNameResponse>"
+        }
       }
     ]
   },
diff --git a/tests/unit/response_parsing/xml/responses/s3-list-multipart-uploads#2.json b/tests/unit/response_parsing/xml/responses/s3-list-multipart-uploads#2.json
index a2d2e0b05..df89574b3 100644
--- a/tests/unit/response_parsing/xml/responses/s3-list-multipart-uploads#2.json
+++ b/tests/unit/response_parsing/xml/responses/s3-list-multipart-uploads#2.json
@@ -1,5 +1,5 @@
 {
-    "UploadIdMarker": null, 
+    "UploadIdMarker": "", 
     "ResponseMetadata": {}, 
     "NextKeyMarker": "my-movie.m2ts", 
     "Bucket": "bucket", 
@@ -48,7 +48,7 @@
             }
         }
     ], 
-    "KeyMarker": null, 
+    "KeyMarker": "", 
     "MaxUploads": 3, 
     "IsTruncated": true
 }
diff --git a/tests/unit/response_parsing/xml/responses/s3-list-multipart-uploads.json b/tests/unit/response_parsing/xml/responses/s3-list-multipart-uploads.json
index 6c527b034..429e5bbd4 100644
--- a/tests/unit/response_parsing/xml/responses/s3-list-multipart-uploads.json
+++ b/tests/unit/response_parsing/xml/responses/s3-list-multipart-uploads.json
@@ -1,16 +1,16 @@
 {
-    "UploadIdMarker": null, 
+    "UploadIdMarker": "", 
     "CommonPrefixes": [
 	{"Prefix": "foo/"},
 	{"Prefix": "foobar/"}
     ], 
     "ResponseMetadata": {}, 
-    "NextKeyMarker": null, 
+    "NextKeyMarker": "", 
     "Bucket": "botocoretest1374528673-218", 
     "Prefix": "foo",
     "Delimiter": "/",
-    "NextUploadIdMarker": null, 
-    "KeyMarker": null, 
+    "NextUploadIdMarker": "", 
+    "KeyMarker": "", 
     "MaxUploads": 1000, 
     "IsTruncated": false
 }
diff --git a/tests/unit/response_parsing/xml/responses/s3-list-object-versions.json b/tests/unit/response_parsing/xml/responses/s3-list-object-versions.json
index c7146b5c1..4567a2193 100644
--- a/tests/unit/response_parsing/xml/responses/s3-list-object-versions.json
+++ b/tests/unit/response_parsing/xml/responses/s3-list-object-versions.json
@@ -44,7 +44,7 @@
     ], 
     "MaxKeys": 5, 
     "Prefix": "my", 
-    "KeyMarker": null, 
+    "KeyMarker": "", 
     "DeleteMarkers": [
         {
             "Owner": {
@@ -68,5 +68,5 @@
         }
     ], 
     "IsTruncated": false, 
-    "VersionIdMarker": null
+    "VersionIdMarker": ""
 }
diff --git a/tests/unit/response_parsing/xml/responses/s3-list-objects.json b/tests/unit/response_parsing/xml/responses/s3-list-objects.json
index 0e0066057..ed2671e8d 100644
--- a/tests/unit/response_parsing/xml/responses/s3-list-objects.json
+++ b/tests/unit/response_parsing/xml/responses/s3-list-objects.json
@@ -2,8 +2,8 @@
     "Name": "test-1357854246", 
     "ResponseMetadata": {}, 
     "MaxKeys": 1000, 
-    "Prefix": null, 
-    "Marker": null, 
+    "Prefix": "", 
+    "Marker": "", 
     "IsTruncated": false, 
     "Contents": [
         {
diff --git a/tests/unit/test_response.py b/tests/unit/test_response.py
index 683a169cc..56bdb990c 100644
--- a/tests/unit/test_response.py
+++ b/tests/unit/test_response.py
@@ -169,14 +169,13 @@ class TestGetResponse(unittest.TestCase):
                             u'Size': 6702,
                             u'StorageClass': 'STANDARD'}],
              u'IsTruncated': False,
-             u'Marker': None,
+             u'Marker': "",
              u'MaxKeys': 1000,
              u'Name': 'mybucket',
-             u'Prefix': None,
+             u'Prefix': "",
              'ResponseMetadata': {
                  'RequestId': 'XXXXXXXXXXXXXXXX',
                  'HostId': 'AAAAAAAAAAAAAAAAAAA',
                  'HTTPStatusCode': 200,
              }}
         )
-