Repository: boto/botocore

Issue:
Missing retry logic for Elastic Transcoder
We are not retrying throttling errors for Elastic Transcoder.  We _should_ be able to just update our retry config.

The throttling looks like:

```
014-02-19 14:13:15,100 - botocore.vendored.requests.packages.urllib3.connectionpool - DEBUG - "DELETE /2012-09-25/pipelines/1392424160324-e4hnuc HTTP/1.1" 400 120
2014-02-19 14:13:15,101 - botocore.response - DEBUG - Response Headers:
x-amzn-requestid: 1234
content-length: 120
date: Wed, 19 Feb 2014 22:13:13 GMT
nncoection: close
content-type: application/json
x-amzn-errortype: ThrottlingException:
2014-02-19 14:13:15,101 - botocore.response - DEBUG - Response Body:
{"message":"Your application is submitting requests to Amazon Elastic Transcoder faster than the maximum request rate."}
```



Discussion:

Resolving patch:
diff --git a/botocore/response.py b/botocore/response.py
--- a/botocore/response.py
+++ b/botocore/response.py
@@ -338,11 +338,10 @@ def parse(self, s, encoding):
         try:
             decoded = s.decode(encoding)
             self.value = json.loads(decoded)
-            self.get_response_errors()
         except Exception as err:
             logger.debug('Error loading JSON response body, %r', err)
 
-    def get_response_errors(self):
+    def merge_header_values(self, headers):
         # Most JSON services return a __type in error response bodies.
         # Unfortunately, ElasticTranscoder does not.  It simply returns
         # a JSON body with a single key, "message".
@@ -357,7 +356,12 @@ def get_response_errors(self):
             code = self._parse_code_from_type(error_type)
             error['Code'] = code
         elif 'message' in self.value and len(self.value.keys()) == 1:
-            error = {'Type': 'Unspecified', 'Code': 'Unspecified',
+            error_type = 'Unspecified'
+            if headers and 'x-amzn-errortype' in headers:
+                # ElasticTranscoder suffixes errors with `:`, so we strip
+                # them off when possible.
+                error_type = headers.get('x-amzn-errortype').strip(':')
+            error = {'Type': error_type, 'Code': error_type,
                      'Message': self.value['message']}
             del self.value['message']
         if error:


Test patch:
diff --git a/tests/unit/response_parsing/json/inputs/elastictranscoder-delete-pipeline.json b/tests/unit/response_parsing/json/inputs/elastictranscoder-delete-pipeline.json
--- a/tests/unit/response_parsing/json/inputs/elastictranscoder-delete-pipeline.json
+++ b/tests/unit/response_parsing/json/inputs/elastictranscoder-delete-pipeline.json
@@ -1 +1,11 @@
-{"message":"1 validation error detected: Value 'foobar' at 'id' failed to satisfy constraint: Member must satisfy regular expression pattern: ^\\d{13}-\\w{6}$"}
+{
+  "message": "1 validation error detected: Value 'foobar' at 'id' failed to satisfy constraint: Member must satisfy regular expression pattern: ^\\d{13}-\\w{6}$",
+  "__headers": {
+    "x-amzn-requestid": "1234",
+    "content-length": "120",
+    "date": "Wed, 19 Feb 2014 22:13:13 GMT",
+    "nncoection": "close",
+    "content-type": "application/json",
+    "x-amzn-errortype": "ValidationException:"
+  }
+}
diff --git a/tests/unit/response_parsing/json/outputs/elastictranscoder-delete-pipeline.json b/tests/unit/response_parsing/json/outputs/elastictranscoder-delete-pipeline.json
--- a/tests/unit/response_parsing/json/outputs/elastictranscoder-delete-pipeline.json
+++ b/tests/unit/response_parsing/json/outputs/elastictranscoder-delete-pipeline.json
@@ -1,9 +1,9 @@
 {
     "Errors": [
         {
-            "Message": "1 validation error detected: Value 'foobar' at 'id' failed to satisfy constraint: Member must satisfy regular expression pattern: ^\\d{13}-\\w{6}$", 
-            "Type": "Unspecified",
-            "Code": "Unspecified"
+            "Message": "1 validation error detected: Value 'foobar' at 'id' failed to satisfy constraint: Member must satisfy regular expression pattern: ^\\d{13}-\\w{6}$",
+            "Type": "ValidationException",
+            "Code": "ValidationException"
         }
     ]
 }
diff --git a/tests/unit/response_parsing/test_response_parsing.py b/tests/unit/response_parsing/test_response_parsing.py
--- a/tests/unit/response_parsing/test_response_parsing.py
+++ b/tests/unit/response_parsing/test_response_parsing.py
@@ -115,9 +115,21 @@ def test_json_parsing():
             sn, opname = basename.split('-', 1)
             operation = service.get_operation(opname)
             r = JSONResponse(session, operation)
-            with open(inputfile, 'rb') as fp:
+            headers = {}
+            with open(inputfile, 'r') as fp:
                 jsondoc = fp.read()
-            r.parse(jsondoc, 'utf-8')
+                # Try to get any headers using a special key
+                try:
+                    parsed = json.loads(jsondoc)
+                except ValueError:
+                    # This will error later, let it go on
+                    parsed = {}
+                if '__headers' in parsed:
+                    headers = parsed['__headers']
+                    del parsed['__headers']
+                    jsondoc = json.dumps(parsed)
+            r.parse(jsondoc.encode('utf-8'), 'utf-8')
+            r.merge_header_values(headers)
             save_jsonfile(outputfile, r)
             fp = open(outputfile)
             data = json.load(fp)