-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix unicode message in exceptions #2348
Fix unicode message in exceptions #2348
Conversation
@daspecster Ping me when Travis passing? (I'm working on release notes right now so a bit scatterbrained.) |
I'm not in favor of the |
@tseaver ok, can do. |
@daspecster Agree with @tseaver. That won't help detect |
Here's a cleaner way to handle it, however when running the follow test with python 2.7, you get an exception thrown by the exception_test.py from google.cloud import exceptions
try:
a = u'That\u2019s all we know'
print(a)
raise exceptions.NotFound(a)
except exceptions.GoogleCloudError as e:
print(e) Output: ╰─$ python test.py
That’s all we know
Traceback (most recent call last):
File "test.py", line 7, in <module>
print(e)
UnicodeEncodeError: 'ascii' codec can't encode character u'\u2019' in position 8: ordinal not in range(128) If that is not something we're concerned with then we are good to go. |
exception = self._callFUT(response, content) | ||
self.assertTrue(isinstance(exception, NotFound)) | ||
|
||
self.assertEqual(exception.message, u'That\u2019s all we know.') |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
@daspecster You shouldn't have to branch for from __future__ import print_function
class NotFound(Exception):
def __init__(self, message):
super(NotFound, self).__init__(message)
self.message = message
self._errors = ()
def __str__(self):
return '%d %s' % (404, self.message)
VAL = u'it\u2019s all good'
print(VAL)
print('=' * 40)
EXC = NotFound(VAL)
print(EXC) we see the failure
NOTE This min-repro was too min. Putting the following in from __future__ import print_function
print(u'it\u2019s all good') it works as expected:
|
OK, here is the problem: In Python 2, the result of |
I was thinking about Should we used
From the bottom of that link you posted before. |
No. The issue is |
response = _Response(404) | ||
content = u'{"error": {"message": "That\u2019s all we know."}}' | ||
exception = self._callFUT(response, content) | ||
self.assertTrue(isinstance(exception, NotFound)) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
I'm apparently missing something here. I have the code you demonstrated above, but I'm still getting the error. from __future__ import print_function
class NotFound(Exception):
def __init__(self, message):
super(NotFound, self).__init__(message)
self.message = message
self._errors = ()
def __str__(self):
return '%d %s' % (404, self.message)
VAL = u'it\u2019s all good'
print(VAL)
print('=' * 40)
EXC = NotFound(VAL)
print(EXC) ╰─$ python2.7 test.py 1 ↵
it’s all good
========================================
Traceback (most recent call last):
File "test.py", line 20, in <module>
print(EXC)
UnicodeEncodeError: 'ascii' codec can't encode character u'\u2019' in position 6: ordinal not in range(128) |
@daspecster The example I posted doesn't fix it, I just posted it because my original snippet was busted. The issue is that Python 2.7.12 (default, Jul 17 2016, 01:21:14)
[GCC 4.8.4] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> class A(object):
... def __str__(self):
... return u'\u2019'
...
>>> a = A()
>>> a.__str__()
u'\u2019'
>>> str(a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\u2019' in position 0: ordinal not in range(128)
>>> a.__str__().encode()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\u2019' in position 0: ordinal not in range(128)
>>> unicode(a)
u'\u2019' |
@@ -41,7 +41,7 @@ def __init__(self, message, errors=()): | |||
self._errors = errors | |||
|
|||
def __str__(self): | |||
return '%d %s' % (self.code, self.message) | |||
return u'%d %s' % (self.code, self.message) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
def test_hit_w_content_as_unicode(self): | ||
from google.cloud.exceptions import NotFound | ||
response = _Response(404) | ||
content = u'{"error": {"message": "%s" }}' % self._EXPECTED_MESSAGE |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
@@ -48,6 +48,8 @@ def test_ctor_explicit(self): | |||
|
|||
class Test_make_exception(unittest.TestCase): | |||
|
|||
_EXPECTED_MESSAGE = u'That\u2019s not found.' |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
@@ -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: # pragma: NO COVER Python2 |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
return '%d %s' % (self.code, self.message) | ||
result = u'%d %s' % (self.code, self.message) | ||
if six.PY2: | ||
result = _to_bytes(result) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
@dhermes PTAL when you have a chance. |
return '%d %s' % (self.code, self.message) | ||
result = u'%d %s' % (self.code, self.message) | ||
if six.PY2: | ||
result = _to_bytes(result) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
return '%d %s' % (self.code, self.message) | ||
result = u'%d %s' % (self.code, self.message) | ||
if six.PY2: | ||
result = result.encode('ascii', 'ignore') |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
@@ -21,6 +21,7 @@ | |||
import json | |||
import six | |||
|
|||
|
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
if six.PY3: # pragma: NO COVER | ||
_ERROR_MESSAGE = u'That\u2019s not found.' | ||
elif six.PY2: # pragma: NO COVER | ||
_ERROR_MESSAGE = 'Thats not found.' |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
def test_unicode_non_ascii(self):
value = u'\u2013' # Long hyphen
encoded_value = b'\xe2\x80\x93'
self.assertRaises(UnicodeEncodeError, self._callFUT, value)
self.assertEqual(self._callFUT(value, encoding='utf-8'),
encoded_value) Also, to get tests for py27 and py35 to pass and cover, I had to check for two message strings. |
Huh? Why should that be necessary? |
@@ -455,7 +455,7 @@ def _datetime_to_rfc3339(value, ignore_zone=True): | |||
return value.strftime(_RFC3339_MICROS) | |||
|
|||
|
|||
def _to_bytes(value, encoding='ascii'): | |||
def _to_bytes(value, encoding='utf-8'): |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
@@ -736,7 +736,6 @@ def test_with_unicode(self): | |||
def test_unicode_non_ascii(self): | |||
value = u'\u2013' # Long hyphen | |||
encoded_value = b'\xe2\x80\x93' | |||
self.assertRaises(UnicodeEncodeError, self._callFUT, value) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
_ERROR_MESSAGE = u'That\'s not found.' | ||
|
||
with _Monkey(six, PY2=False): | ||
with _Monkey(six, PY3=True): |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
from google.cloud.exceptions import NotFound | ||
_ERROR_MESSAGE = u'That\u2019s not found.' | ||
_EXPECTED = ('404 That\xe2\x80\x99s not found.', | ||
'404 %s' % (_ERROR_MESSAGE,)) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
72b29d6
to
8767a4c
Compare
import six | ||
from unit_tests._testing import _Monkey | ||
from google.cloud.exceptions import NotFound | ||
_ERROR_MESSAGE = u'That\'s not found.' |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
content = u'{"error": {"message": "%s" }}' % (_ERROR_MESSAGE,) | ||
|
||
exception = self._callFUT(response, content) | ||
self.assertIn(str(exception), _EXPECTED) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
@daspecster Travis build cancelled. Is there another push coming? |
8767a4c
to
9240117
Compare
@tseaver yup! Sorry, I was trying to get my commits GPG verified. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
See #2346
@dhermes looking at the magic methods and playing with
__bytes__
and__unicode__
, the solutions I had felt really hacky. i.e. checking forsix.PY3
in__str__
and then just callingself.__bytes__()
instead.I'm not sure if it's good form to use
__future__
here but it seems to be a valid way to solve the issue.