Repository: googleapis/google-cloud-python

Issue:
raised UnicodeEncodeError when printing GCloudError  exception
When exception  was received from google storage  and UnicodeEncodeError exception was raised

``` python
try:
    client = storage.Client()
    bucket = client.get_bucket(bucket_name)
except exceptions.NotFound as e:
    print e
```

raised 

```
unexpected: UnicodeEncodeError('ascii', u'502 <!DOCTYPE html>\n<html lang=en>\n  <meta charset=utf-8>\n  <meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width">\n  <title>Error 502 (Server Error)!!1</title>\n  <style>\n    *{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}* > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}#logo{background:url(//www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png) no-repeat;margin-left:-5px}@media only screen and (min-resolution:192dpi){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat 0% 0%/100% 100%;-moz-border-image:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) 0}}@media only screen and (-webkit-min-device-pixel-ratio:2){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat;-webkit-background-size:100% 100%}}#logo{display:inline-block;height:54px;width:150px}\n  </style>\n  <a href=//www.google.com/><span id=logo aria-label=Google></span></a>\n  <p><b>502.</b> <ins>That\u2019s an error.</ins>\n  <p>The server encountered a temporary error and could not complete your request.<p>Please try again in 30 seconds.  <ins>That\u2019s all we know.</ins>\n (GET https://www.googleapis.com/storage/v1/b/anura-recordings-ar?projection=noAcl)', 1445, 1446, 'ordinal not in range(128)')
```

This is the unicode string that caused the problem -> `That\u2019s all we know.`

The problem seems to be the `__str__` function in your base exception class. 

``` python
class GCloudError(Exception):
    ....
    def __str__(self):
        return '%d %s' % (self.code, self.message)
```

HOW TO REPRODUCE

``` python
try:
    a = u'That\u2019s all we know'
    print a
    raise exceptions.NotFound(a)
except exceptions.GCloudError as e:
    print e
```

which gives

```
$ python test_gs2.py
That’s all we know
Traceback (most recent call last):
  File "test_gs2.py", line 11, in <module>
    print e
UnicodeEncodeError: 'ascii' codec can't encode character u'\u2019' in position 8: ordinal not in range(128)
```



Discussion:
@cesterlizi thanks for reporting!

@dhermes would making [this](https://github.com/GoogleCloudPlatform/google-cloud-python/blob/master/google/cloud/exceptions.py#L44) a unicode string solve this properly for py2 and py3?

``` python
 def __str__(self):
        return u'%d %s' % (self.code, self.message)
```

@daspecster I'm not 100% on that. We probably want to implement `__unicode__` as well. I always consult http://www.rafekettler.com/magicmethods.html in these situations


Resolving patch:
diff --git a/google/cloud/exceptions.py b/google/cloud/exceptions.py
--- a/google/cloud/exceptions.py
+++ b/google/cloud/exceptions.py
@@ -21,6 +21,8 @@
 import json
 import six
 
+from google.cloud._helpers import _to_bytes
+
 _HTTP_CODE_TO_EXCEPTION = {}  # populated at end of module
 
 
@@ -41,7 +43,10 @@ def __init__(self, message, errors=()):
         self._errors = errors
 
     def __str__(self):
-        return '%d %s' % (self.code, self.message)
+        result = u'%d %s' % (self.code, self.message)
+        if six.PY2:
+            result = _to_bytes(result, 'utf-8')
+        return result
 
     @property
     def errors(self):


Test patch:
diff --git a/unit_tests/test_exceptions.py b/unit_tests/test_exceptions.py
--- a/unit_tests/test_exceptions.py
+++ b/unit_tests/test_exceptions.py
@@ -62,6 +62,44 @@ def test_hit_w_content_as_str(self):
         self.assertEqual(exception.message, 'Not Found')
         self.assertEqual(list(exception.errors), [])
 
+    def test_hit_w_content_as_unicode(self):
+        import six
+        from google.cloud._helpers import _to_bytes
+        from google.cloud.exceptions import NotFound
+        error_message = u'That\u2019s not found.'
+        expected = u'404 %s' % (error_message,)
+
+        response = _Response(404)
+        content = u'{"error": {"message": "%s" }}' % (error_message,)
+
+        exception = self._callFUT(response, content)
+        if six.PY2:
+            self.assertEqual(str(exception),
+                             _to_bytes(expected, encoding='utf-8'))
+        else:  # pragma: NO COVER
+            self.assertEqual(str(exception), expected)
+
+        self.assertIsInstance(exception, NotFound)
+        self.assertEqual(exception.message, error_message)
+        self.assertEqual(list(exception.errors), [])
+
+    def test_hit_w_content_as_unicode_as_py3(self):
+        import six
+        from unit_tests._testing import _Monkey
+        from google.cloud.exceptions import NotFound
+        error_message = u'That is not found.'
+        expected = u'404 %s' % (error_message,)
+
+        with _Monkey(six, PY2=False):
+            response = _Response(404)
+            content = u'{"error": {"message": "%s" }}' % (error_message,)
+            exception = self._callFUT(response, content)
+
+            self.assertIsInstance(exception, NotFound)
+            self.assertEqual(exception.message, error_message)
+            self.assertEqual(list(exception.errors), [])
+            self.assertEqual(str(exception), expected)
+
     def test_miss_w_content_as_dict(self):
         from google.cloud.exceptions import GoogleCloudError
         ERROR = {