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:
Joshua Harlow 2015-06-16 15:57:11 -07:00
parent 27baaf46ad
commit 0d884a2fc5
5 changed files with 81 additions and 34 deletions

View File

@ -38,6 +38,11 @@ Miscellaneous
.. automodule:: taskflow.utils.misc
Mixins
~~~~~~
.. automodule:: taskflow.utils.mixins
Persistence
~~~~~~~~~~~

View File

@ -20,6 +20,7 @@ import traceback
from oslo_utils import excutils
from oslo_utils import reflection
import six
from taskflow.utils import mixins
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.
When exception/s cannot be re-raised (for example, because the value and
@ -284,20 +285,18 @@ class WrappedFailure(Exception):
return result
return None
def __str__(self):
causes = [exception_message(cause) for cause in self._causes]
return 'WrappedFailure: %s' % causes
def __bytes__(self):
buf = six.BytesIO()
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 exception_message(exc):
"""Return the string representation of exception.
:param exc: exception object to get a string representation of.
"""
# NOTE(imelnikov): Dealing with non-ascii data in python is difficult:
# 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)
def __unicode__(self):
buf = six.StringIO()
buf.write(u'WrappedFailure: [')
causes_gen = (six.text_type(cause) for cause in self._causes)
buf.write(u", ".join(causes_gen))
buf.write(u']')
return buf.getvalue()

View File

@ -16,6 +16,7 @@
import sys
from oslo_utils import encodeutils
import six
from six.moves import cPickle as pickle
import testtools
@ -301,9 +302,19 @@ class NonAsciiExceptionsTestCase(test.TestCase):
def test_exception_with_non_ascii_str(self):
bad_string = chr(200)
fail = failure.Failure.from_exception(ValueError(bad_string))
self.assertEqual(fail.exception_str, bad_string)
self.assertEqual(str(fail), 'Failure: ValueError: %s' % bad_string)
excp = ValueError(bad_string)
fail = failure.Failure.from_exception(excp)
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):
hi_ru = u'привет'
@ -316,18 +327,11 @@ class NonAsciiExceptionsTestCase(test.TestCase):
def test_wrapped_failure_non_ascii_unicode(self):
hi_cn = u''
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)
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: "
"[u'Failure: ValueError: %s']"
% (hi_cn.encode("unicode-escape")))
else:
expected_result = (u"WrappedFailure: "
"['Failure: ValueError: %s']" % (hi_cn))
expected_result = (u"WrappedFailure: "
"[Failure: ValueError: %s]" % (hi_cn))
self.assertEqual(expected_result, six.text_type(wrapped_fail))
def test_failure_equality_with_non_ascii_str(self):

View File

@ -19,12 +19,16 @@ import os
import sys
import traceback
from oslo_utils import encodeutils
from oslo_utils import reflection
import six
from taskflow import exceptions as exc
from taskflow.utils import mixins
from taskflow.utils import schema_utils as su
_exception_message = encodeutils.exception_to_unicode
def _copy_exc_info(exc_info):
if exc_info is None:
@ -65,7 +69,7 @@ def _are_equal_exc_info_tuples(ei1, ei2):
if ei1[0] is not ei2[0]:
return False
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]))):
return False
if ei1[2] == ei2[2]:
@ -75,7 +79,7 @@ def _are_equal_exc_info_tuples(ei1, ei2):
return tb1 == tb2
class Failure(object):
class Failure(mixins.StrMixin):
"""An immutable object that represents failure.
Failure objects encapsulate exception information so that they can be
@ -191,7 +195,7 @@ class Failure(object):
if not self._exc_type_names:
raise TypeError("Invalid exception type '%s' (%s)"
% (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(
traceback.format_tb(self._exc_info[2]))
self._causes = kwargs.pop('causes', None)
@ -387,7 +391,7 @@ class Failure(object):
self._causes = tuple(self._extract_causes_iter(self.exception))
return self._causes
def __str__(self):
def __unicode__(self):
return self.pformat()
def pformat(self, traceback=False):

35
taskflow/utils/mixins.py Normal file
View 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__()