1
# Copyright (c) 2008-2011 testtools developers. See LICENSE for details.
3
"""Compatibility support for python 2 and 3."""
13
'unicode_output_stream',
24
from testtools.helpers import try_imports
26
StringIO = try_imports(['StringIO.StringIO', 'io.StringIO'])
28
BytesIO = try_imports(['io.BytesIO', 'BytesIO'])
31
__u_doc = """A function version of the 'u' prefix.
33
This is needed becayse the u prefix is not usable in Python 3 but is required
34
in Python 2 to get a unicode object.
36
To migrate code that was written as u'\u1234' in Python 2 to 2+3 change
37
it to be _u('\u1234'). The Python 3 interpreter will decode it
38
appropriately and the no-op _u for Python 3 lets it through, in Python
39
2 we then call unicode-escape in the _u function.
42
if sys.version_info > (3, 0):
48
return s.encode("latin-1")
49
advance_iterator = next
51
return isinstance(x, str)
57
# The double replace mangling going on prepares the string for
58
# unicode-escape - \foo is preserved, \u and \U are decoded.
59
return (s.replace("\\", "\\\\").replace("\\\\u", "\\u")
60
.replace("\\\\U", "\\U").decode("unicode-escape"))
64
advance_iterator = lambda it: it.next()
66
return isinstance(x, basestring)
69
return (type, types.ClassType)
70
str_is_unicode = sys.platform == "cli"
75
if sys.version_info > (2, 5):
77
_error_repr = BaseException.__repr__
78
def isbaseexception(exception):
79
"""Return whether exception inherits from BaseException only"""
80
return (isinstance(exception, BaseException)
81
and not isinstance(exception, Exception))
84
"""If contents of iterable all evaluate as boolean True"""
89
def _error_repr(exception):
90
"""Format an exception instance as Python 2.5 and later do"""
91
return exception.__class__.__name__ + repr(exception.args)
92
def isbaseexception(exception):
93
"""Return whether exception would inherit from BaseException only
95
This approximates the hierarchy in Python 2.5 and later, compare the
96
difference between the diagrams at the bottom of the pages:
97
<http://docs.python.org/release/2.4.4/lib/module-exceptions.html>
98
<http://docs.python.org/release/2.5.4/lib/module-exceptions.html>
100
return isinstance(exception, (KeyboardInterrupt, SystemExit))
103
def unicode_output_stream(stream):
104
"""Get wrapper for given stream that writes any unicode without exception
106
Characters that can't be coerced to the encoding of the stream, or 'ascii'
107
if valid encoding is not found, will be replaced. The original stream may
108
be returned in situations where a wrapper is determined unneeded.
110
The wrapper only allows unicode to be written, not non-ascii bytestrings,
111
which is a good thing to ensure sanity and sanitation.
113
if sys.platform == "cli":
114
# Best to never encode before writing in IronPython
117
writer = codecs.getwriter(stream.encoding or "")
118
except (AttributeError, LookupError):
119
# GZ 2010-06-16: Python 3 StringIO ends up here, but probably needs
120
# different handling as it doesn't want bytestrings
121
return codecs.getwriter("ascii")(stream, "replace")
122
if writer.__module__.rsplit(".", 1)[1].startswith("utf"):
123
# The current stream has a unicode encoding so no error handler is needed
125
if sys.version_info > (3, 0):
126
# Python 3 doesn't seem to make this easy, handle a common case
128
return stream.__class__(stream.buffer, stream.encoding, "replace",
129
stream.newlines, stream.line_buffering)
130
except AttributeError:
132
return writer(stream, "replace")
135
# The default source encoding is actually "iso-8859-1" until Python 2.5 but
136
# using non-ascii causes a deprecation warning in 2.4 and it's cleaner to
137
# treat all versions the same way
138
_default_source_encoding = "ascii"
140
# Pattern specified in <http://www.python.org/dev/peps/pep-0263/>
141
_cookie_search=re.compile("coding[:=]\s*([-\w.]+)").search
143
def _detect_encoding(lines):
144
"""Get the encoding of a Python source file from a list of lines as bytes
146
This function does less than tokenize.detect_encoding added in Python 3 as
147
it does not attempt to raise a SyntaxError when the interpreter would, it
148
just wants the encoding of a source file Python has already compiled and
152
return _default_source_encoding
153
if lines[0].startswith("\xef\xbb\xbf"):
154
# Source starting with UTF-8 BOM is either UTF-8 or a SyntaxError
156
# Only the first two lines of the source file are examined
157
magic = _cookie_search("".join(lines[:2]))
159
return _default_source_encoding
160
encoding = magic.group(1)
162
codecs.lookup(encoding)
164
# Some codecs raise something other than LookupError if they don't
165
# support the given error handler, but not the text ones that could
166
# actually be used for Python source code
167
return _default_source_encoding
171
class _EncodingTuple(tuple):
172
"""A tuple type that can have an encoding attribute smuggled on"""
175
def _get_source_encoding(filename):
176
"""Detect, cache and return the encoding of Python source at filename"""
178
return linecache.cache[filename].encoding
179
except (AttributeError, KeyError):
180
encoding = _detect_encoding(linecache.getlines(filename))
181
if filename in linecache.cache:
182
newtuple = _EncodingTuple(linecache.cache[filename])
183
newtuple.encoding = encoding
184
linecache.cache[filename] = newtuple
188
def _get_exception_encoding():
189
"""Return the encoding we expect messages from the OS to be encoded in"""
191
# GZ 2010-05-24: Really want the codepage number instead, the error
192
# handling of standard codecs is more deterministic
194
# GZ 2010-05-23: We need this call to be after initialisation, but there's
195
# no benefit in asking more than once as it's a global
196
# setting that can change after the message is formatted.
197
return locale.getlocale(locale.LC_MESSAGES)[1] or "ascii"
200
def _exception_to_text(evalue):
201
"""Try hard to get a sensible text value out of an exception instance"""
203
return unicode(evalue)
204
except KeyboardInterrupt:
207
# Apparently this is what traceback._some_str does. Sigh - RBC 20100623
210
return str(evalue).decode(_get_exception_encoding(), "replace")
211
except KeyboardInterrupt:
214
# Apparently this is what traceback._some_str does. Sigh - RBC 20100623
216
# Okay, out of ideas, let higher level handle it
220
# GZ 2010-05-23: This function is huge and horrible and I welcome suggestions
221
# on the best way to break it up
222
_TB_HEADER = _u('Traceback (most recent call last):\n')
223
def _format_exc_info(eclass, evalue, tb, limit=None):
224
"""Format a stack trace and the exception information as unicode
226
Compatibility function for Python 2 which ensures each component of a
227
traceback is correctly decoded according to its origins.
229
Based on traceback.format_exception and related functions.
231
fs_enc = sys.getfilesystemencoding()
235
for filename, lineno, name, line in traceback.extract_tb(tb, limit):
236
extracted_list.append((
237
filename.decode(fs_enc, "replace"),
239
name.decode("ascii", "replace"),
240
line and line.decode(
241
_get_source_encoding(filename), "replace")))
242
list.extend(traceback.format_list(extracted_list))
246
# Is a (deprecated) string exception
247
list.append((eclass + "\n").decode("ascii", "replace"))
249
if isinstance(evalue, SyntaxError):
250
# Avoid duplicating the special formatting for SyntaxError here,
251
# instead create a new instance with unicode filename and line
252
# Potentially gives duff spacing, but that's a pre-existing issue
254
msg, (filename, lineno, offset, line) = evalue
255
except (TypeError, ValueError):
256
pass # Strange exception instance, fall through to generic code
258
# Errors during parsing give the line from buffer encoded as
259
# latin-1 or utf-8 or the encoding of the file depending on the
260
# coding and whether the patch for issue #1031213 is applied, so
261
# give up on trying to decode it and just read the file again
263
bytestr = linecache.getline(filename, lineno)
265
if lineno == 1 and bytestr.startswith("\xef\xbb\xbf"):
266
bytestr = bytestr[3:]
267
line = bytestr.decode(
268
_get_source_encoding(filename), "replace")
269
del linecache.cache[filename]
271
line = line.decode("ascii", "replace")
273
filename = filename.decode(fs_enc, "replace")
274
evalue = eclass(msg, (filename, lineno, offset, line))
275
list.extend(traceback.format_exception_only(eclass, evalue))
277
sclass = eclass.__name__
278
svalue = _exception_to_text(evalue)
280
list.append("%s: %s\n" % (sclass, svalue))
282
# GZ 2010-05-24: Not a great fallback message, but keep for the moment
283
list.append("%s: <unprintable %s object>\n" % (sclass, sclass))
285
list.append("%s\n" % sclass)