Repository: google/google-api-python-client
Pull request #412 (2017-07-08 08:55:13)

Fix details missing in googleapiclient.errors.HttpError
For instance, the **googleapiclient.errors.HttpError** returns only the message and the status code. Adding the _error details_ on that would be useful and the error becomes slightly more verbose. In the response returned from the server, if the error body contains the **details** field, just show it together with the error message for more details. For example:

`<HttpError 400 when requesting http://example.org returned "country is required". Details: "error details">`

Resolves: #382 

jonparrott: @sonlac thanks for sending this. I hate to be a bother, but could you update this to not change the indentation (or at least put the indentation change in its own commit)? It makes it extremely hard for me to review otherwise.
sonlac: @jonparrott Sorry about that. I just tried to reformat code to PEP8 style. Thanks.

Patch:
diff --git a/googleapiclient/errors.py b/googleapiclient/errors.py
index 6a741f86d..bab14189f 100644
--- a/googleapiclient/errors.py
+++ b/googleapiclient/errors.py
@@ -46,6 +46,7 @@ class HttpError(Error):
         raise TypeError("HTTP content should be bytes")
     self.content = content
     self.uri = uri
+    self.error_details = ''
 
   def _get_reason(self):
     """Calculate the reason for the error from the response content."""
@@ -54,9 +55,13 @@ class HttpError(Error):
       data = json.loads(self.content.decode('utf-8'))
       if isinstance(data, dict):
         reason = data['error']['message']
+        if 'details' in data['error']:
+            self.error_details = data['error']['details']
       elif isinstance(data, list) and len(data) > 0:
         first_error = data[0]
         reason = first_error['error']['message']
+        if 'details' in first_error['error']:
+            self.error_details = first_error['error']['details']
     except (ValueError, KeyError, TypeError):
       pass
     if reason is None:
@@ -64,7 +69,11 @@ class HttpError(Error):
     return reason
 
   def __repr__(self):
-    if self.uri:
+    reason = self._get_reason()
+    if self.error_details:
+      return '<HttpError %s when requesting %s returned "%s". Details: "%s">' % \
+             (self.resp.status, self.uri, reason.strip(), self.error_details)
+    elif self.uri:
       return '<HttpError %s when requesting %s returned "%s">' % (
           self.resp.status, self.uri, self._get_reason().strip())
     else:


Test patch:
diff --git a/tests/test_errors.py b/tests/test_errors.py
index 8a580302c..e4d2f0948 100644
--- a/tests/test_errors.py
+++ b/tests/test_errors.py
@@ -41,7 +41,8 @@ JSON_ERROR_CONTENT = b"""
    }
   ],
   "code": 400,
-  "message": "country is required"
+  "message": "country is required",
+  "details": "error details"
  }
 }
 """
@@ -61,7 +62,7 @@ class Error(unittest.TestCase):
         {'status':'400', 'content-type': 'application/json'},
         reason='Failed')
     error = HttpError(resp, content, uri='http://example.org')
-    self.assertEqual(str(error), '<HttpError 400 when requesting http://example.org returned "country is required">')
+    self.assertEqual(str(error), '<HttpError 400 when requesting http://example.org returned "country is required". Details: "error details">')
 
   def test_bad_json_body(self):
     """Test handling of bodies with invalid json."""