Use encodeutils for exception -> string function
The oslo.utils library now provides a better version of this that always returns a unicode exception message, so update our usage to use it (and remove our own local function). This guarantee of unicode also means we have to update a few other places to make sure we get back bytes or unicode as needed. Change-Id: I924380408aaf6d2aec418ceaaf623c75900268f7
This commit is contained in:
parent
27baaf46ad
commit
0d884a2fc5
@ -38,6 +38,11 @@ Miscellaneous
|
|||||||
|
|
||||||
.. automodule:: taskflow.utils.misc
|
.. automodule:: taskflow.utils.misc
|
||||||
|
|
||||||
|
Mixins
|
||||||
|
~~~~~~
|
||||||
|
|
||||||
|
.. automodule:: taskflow.utils.mixins
|
||||||
|
|
||||||
Persistence
|
Persistence
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import traceback
|
|||||||
from oslo_utils import excutils
|
from oslo_utils import excutils
|
||||||
from oslo_utils import reflection
|
from oslo_utils import reflection
|
||||||
import six
|
import six
|
||||||
|
from taskflow.utils import mixins
|
||||||
|
|
||||||
|
|
||||||
def raise_with_cause(exc_cls, message, *args, **kwargs):
|
def raise_with_cause(exc_cls, message, *args, **kwargs):
|
||||||
@ -231,7 +232,7 @@ class NotImplementedError(NotImplementedError):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class WrappedFailure(Exception):
|
class WrappedFailure(mixins.StrMixin, Exception):
|
||||||
"""Wraps one or several failure objects.
|
"""Wraps one or several failure objects.
|
||||||
|
|
||||||
When exception/s cannot be re-raised (for example, because the value and
|
When exception/s cannot be re-raised (for example, because the value and
|
||||||
@ -284,20 +285,18 @@ class WrappedFailure(Exception):
|
|||||||
return result
|
return result
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __str__(self):
|
def __bytes__(self):
|
||||||
causes = [exception_message(cause) for cause in self._causes]
|
buf = six.BytesIO()
|
||||||
return 'WrappedFailure: %s' % causes
|
buf.write(b'WrappedFailure: [')
|
||||||
|
causes_gen = (six.binary_type(cause) for cause in self._causes)
|
||||||
|
buf.write(b", ".join(causes_gen))
|
||||||
|
buf.write(b']')
|
||||||
|
return buf.getvalue()
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
def exception_message(exc):
|
buf = six.StringIO()
|
||||||
"""Return the string representation of exception.
|
buf.write(u'WrappedFailure: [')
|
||||||
|
causes_gen = (six.text_type(cause) for cause in self._causes)
|
||||||
:param exc: exception object to get a string representation of.
|
buf.write(u", ".join(causes_gen))
|
||||||
"""
|
buf.write(u']')
|
||||||
# NOTE(imelnikov): Dealing with non-ascii data in python is difficult:
|
return buf.getvalue()
|
||||||
# https://bugs.launchpad.net/taskflow/+bug/1275895
|
|
||||||
# https://bugs.launchpad.net/taskflow/+bug/1276053
|
|
||||||
try:
|
|
||||||
return six.text_type(exc)
|
|
||||||
except UnicodeError:
|
|
||||||
return str(exc)
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from oslo_utils import encodeutils
|
||||||
import six
|
import six
|
||||||
from six.moves import cPickle as pickle
|
from six.moves import cPickle as pickle
|
||||||
import testtools
|
import testtools
|
||||||
@ -301,9 +302,19 @@ class NonAsciiExceptionsTestCase(test.TestCase):
|
|||||||
|
|
||||||
def test_exception_with_non_ascii_str(self):
|
def test_exception_with_non_ascii_str(self):
|
||||||
bad_string = chr(200)
|
bad_string = chr(200)
|
||||||
fail = failure.Failure.from_exception(ValueError(bad_string))
|
excp = ValueError(bad_string)
|
||||||
self.assertEqual(fail.exception_str, bad_string)
|
fail = failure.Failure.from_exception(excp)
|
||||||
self.assertEqual(str(fail), 'Failure: ValueError: %s' % bad_string)
|
self.assertEqual(fail.exception_str,
|
||||||
|
encodeutils.exception_to_unicode(excp))
|
||||||
|
# This is slightly different on py2 vs py3... due to how
|
||||||
|
# __str__ or __unicode__ is called and what is expected from
|
||||||
|
# both...
|
||||||
|
if six.PY2:
|
||||||
|
msg = encodeutils.exception_to_unicode(excp)
|
||||||
|
expected = 'Failure: ValueError: %s' % msg.encode('utf-8')
|
||||||
|
else:
|
||||||
|
expected = u'Failure: ValueError: \xc8'
|
||||||
|
self.assertEqual(str(fail), expected)
|
||||||
|
|
||||||
def test_exception_non_ascii_unicode(self):
|
def test_exception_non_ascii_unicode(self):
|
||||||
hi_ru = u'привет'
|
hi_ru = u'привет'
|
||||||
@ -316,18 +327,11 @@ class NonAsciiExceptionsTestCase(test.TestCase):
|
|||||||
def test_wrapped_failure_non_ascii_unicode(self):
|
def test_wrapped_failure_non_ascii_unicode(self):
|
||||||
hi_cn = u'嗨'
|
hi_cn = u'嗨'
|
||||||
fail = ValueError(hi_cn)
|
fail = ValueError(hi_cn)
|
||||||
self.assertEqual(hi_cn, exceptions.exception_message(fail))
|
self.assertEqual(hi_cn, encodeutils.exception_to_unicode(fail))
|
||||||
fail = failure.Failure.from_exception(fail)
|
fail = failure.Failure.from_exception(fail)
|
||||||
wrapped_fail = exceptions.WrappedFailure([fail])
|
wrapped_fail = exceptions.WrappedFailure([fail])
|
||||||
if six.PY2:
|
|
||||||
# Python 2.x will unicode escape it, while python 3.3+ will not,
|
|
||||||
# so we sadly have to differentiate between these two...
|
|
||||||
expected_result = (u"WrappedFailure: "
|
expected_result = (u"WrappedFailure: "
|
||||||
"[u'Failure: ValueError: %s']"
|
"[Failure: ValueError: %s]" % (hi_cn))
|
||||||
% (hi_cn.encode("unicode-escape")))
|
|
||||||
else:
|
|
||||||
expected_result = (u"WrappedFailure: "
|
|
||||||
"['Failure: ValueError: %s']" % (hi_cn))
|
|
||||||
self.assertEqual(expected_result, six.text_type(wrapped_fail))
|
self.assertEqual(expected_result, six.text_type(wrapped_fail))
|
||||||
|
|
||||||
def test_failure_equality_with_non_ascii_str(self):
|
def test_failure_equality_with_non_ascii_str(self):
|
||||||
|
@ -19,12 +19,16 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
from oslo_utils import encodeutils
|
||||||
from oslo_utils import reflection
|
from oslo_utils import reflection
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from taskflow import exceptions as exc
|
from taskflow import exceptions as exc
|
||||||
|
from taskflow.utils import mixins
|
||||||
from taskflow.utils import schema_utils as su
|
from taskflow.utils import schema_utils as su
|
||||||
|
|
||||||
|
_exception_message = encodeutils.exception_to_unicode
|
||||||
|
|
||||||
|
|
||||||
def _copy_exc_info(exc_info):
|
def _copy_exc_info(exc_info):
|
||||||
if exc_info is None:
|
if exc_info is None:
|
||||||
@ -65,7 +69,7 @@ def _are_equal_exc_info_tuples(ei1, ei2):
|
|||||||
if ei1[0] is not ei2[0]:
|
if ei1[0] is not ei2[0]:
|
||||||
return False
|
return False
|
||||||
if not all((type(ei1[1]) == type(ei2[1]),
|
if not all((type(ei1[1]) == type(ei2[1]),
|
||||||
exc.exception_message(ei1[1]) == exc.exception_message(ei2[1]),
|
_exception_message(ei1[1]) == _exception_message(ei2[1]),
|
||||||
repr(ei1[1]) == repr(ei2[1]))):
|
repr(ei1[1]) == repr(ei2[1]))):
|
||||||
return False
|
return False
|
||||||
if ei1[2] == ei2[2]:
|
if ei1[2] == ei2[2]:
|
||||||
@ -75,7 +79,7 @@ def _are_equal_exc_info_tuples(ei1, ei2):
|
|||||||
return tb1 == tb2
|
return tb1 == tb2
|
||||||
|
|
||||||
|
|
||||||
class Failure(object):
|
class Failure(mixins.StrMixin):
|
||||||
"""An immutable object that represents failure.
|
"""An immutable object that represents failure.
|
||||||
|
|
||||||
Failure objects encapsulate exception information so that they can be
|
Failure objects encapsulate exception information so that they can be
|
||||||
@ -191,7 +195,7 @@ class Failure(object):
|
|||||||
if not self._exc_type_names:
|
if not self._exc_type_names:
|
||||||
raise TypeError("Invalid exception type '%s' (%s)"
|
raise TypeError("Invalid exception type '%s' (%s)"
|
||||||
% (exc_info[0], type(exc_info[0])))
|
% (exc_info[0], type(exc_info[0])))
|
||||||
self._exception_str = exc.exception_message(self._exc_info[1])
|
self._exception_str = _exception_message(self._exc_info[1])
|
||||||
self._traceback_str = ''.join(
|
self._traceback_str = ''.join(
|
||||||
traceback.format_tb(self._exc_info[2]))
|
traceback.format_tb(self._exc_info[2]))
|
||||||
self._causes = kwargs.pop('causes', None)
|
self._causes = kwargs.pop('causes', None)
|
||||||
@ -387,7 +391,7 @@ class Failure(object):
|
|||||||
self._causes = tuple(self._extract_causes_iter(self.exception))
|
self._causes = tuple(self._extract_causes_iter(self.exception))
|
||||||
return self._causes
|
return self._causes
|
||||||
|
|
||||||
def __str__(self):
|
def __unicode__(self):
|
||||||
return self.pformat()
|
return self.pformat()
|
||||||
|
|
||||||
def pformat(self, traceback=False):
|
def pformat(self, traceback=False):
|
||||||
|
35
taskflow/utils/mixins.py
Normal file
35
taskflow/utils/mixins.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (C) 2015 Yahoo! Inc. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
|
||||||
|
class StrMixin(object):
|
||||||
|
"""Mixin that helps deal with the PY2 and PY3 method differences.
|
||||||
|
|
||||||
|
http://lucumr.pocoo.org/2011/1/22/forwards-compatible-python/ explains
|
||||||
|
why this is quite useful...
|
||||||
|
"""
|
||||||
|
|
||||||
|
if six.PY2:
|
||||||
|
def __str__(self):
|
||||||
|
try:
|
||||||
|
return self.__bytes__()
|
||||||
|
except AttributeError:
|
||||||
|
return self.__unicode__().encode('utf-8')
|
||||||
|
else:
|
||||||
|
def __str__(self):
|
||||||
|
return self.__unicode__()
|
Loading…
Reference in New Issue
Block a user