Handle objects with broken __unicode__

Because Python 2 objects may have __unicode__ broken, and we need
unicode output to avoid implicit decodes, explicitly handle
__unicode__.
This commit is contained in:
Robert Collins 2015-03-09 13:31:29 +13:00
parent 7ec70e3de3
commit 652c95d267
3 changed files with 31 additions and 8 deletions

View File

@ -6,7 +6,12 @@ Profit.
Things to be aware of!
In Python 2.x, unlike traceback, traceback2 creates unicode output.
In Python 2.x, unlike traceback, traceback2 creates unicode output (because it
depends on the linecache2 module).
Exception frame clearing silently does nothing if the interpreter in use does
not support it.
traceback2._some_str, which while not an official API is so old its likely in
use behaves similarly to the Python3 version - objects where unicode(obj) fails
but str(object) works will be shown as b'thestrvaluerepr'.

View File

@ -4,7 +4,7 @@ import sys
import operator
import linecache2 as linecache
from six import u
from six import u, PY2
__all__ = ['extract_stack', 'extract_tb', 'format_exception',
'format_exception_only', 'format_list', 'format_stack',
@ -141,14 +141,24 @@ def format_exception_only(etype, value):
def _format_final_exc_line(etype, value):
valuestr = _some_str(value)
if value == 'None' or value is None or not valuestr:
line = "%s\n" % etype
line = u("%s\n") % etype
else:
line = "%s: %s\n" % (etype, valuestr)
line = u("%s: %s\n") % (etype, valuestr)
return line
def _some_str(value):
try:
return str(value)
if PY2:
# If there is a working __unicode__, great.
# Otherwise see if we can get a bytestring...
# Otherwise we fallback to unprintable.
try:
return unicode(value)
except:
return "b%s" % repr(str(value))
else:
# For Python3, bytestrings don't implicit decode, so its trivial.
return str(value)
except:
return '<unprintable %s object>' % type(value).__name__
@ -508,7 +518,7 @@ class TracebackException:
stype = getattr(self.exc_type, '__qualname__', self.exc_type.__name__)
smod = u(self.exc_type.__module__)
if smod not in ("__main__", "builtins"):
if smod not in ("__main__", "builtins", "exceptions"):
stype = smod + u('.') + stype
if not issubclass(self.exc_type, SyntaxError):

View File

@ -12,7 +12,7 @@ import contextlib2 as contextlib
import fixtures
import linecache2 as linecache
import six
from six import text_type, u
from six import b, text_type, u
try:
from six import raise_from
except ImportError:
@ -153,6 +153,14 @@ class SyntaxTracebackCases(testtools.TestCase):
str_name = '.'.join([X.__module__, qualname(X)])
self.assertEqual(err[0], "%s: %s\n" % (str_name, str_value))
def test_format_exception_only_undecodable__str__(self):
# This won't decode via the ascii codec.
X = Exception(u('\u5341').encode('shift-jis'))
err = traceback.format_exception_only(type(X), X)
self.assertEqual(len(err), 1)
str_value = "b'\\x8f\\\\'"
self.assertEqual(err[0], "Exception: %s\n" % str_value)
def test_without_exception(self):
err = traceback.format_exception_only(None, None)
self.assertEqual(err, ['None\n'])
@ -619,7 +627,7 @@ class TestStack(unittest.TestCase):
traceback.walk_stack(None), capture_locals=True, limit=1)
s = some_inner(3, 4)
self.assertEqual(
[' File "' + FNAME + '", line 619, '
[' File "' + FNAME + '", line 627, '
'in some_inner\n'
' traceback.walk_stack(None), capture_locals=True, limit=1)\n'
' a = 1\n'