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