from __future__ import absolute_import, print_function, unicode_literals
import re
from wolframclient.language import wl
from wolframclient.utils import six
from wolframclient.utils.decorators import to_tuple
from wolframclient.utils.encoding import force_text, safe_force_text
from wolframclient.utils.functional import iterate
[docs]def serialize_traceback(exc_type, exc_value, tb, **opts):
return wl.OpenerView(
[
wl.Row([safe_force_text(exc_type.__name__), " ", safe_force_text(exc_value)]),
wl.Style(
wl.Column(_serialize_traceback(exc_type, exc_value, tb, **opts)),
FontFamily="Courier",
),
],
True,
)
def _serialize_traceback(exc_type, exc_value, tb, **opts):
frames = _get_traceback_frames(tb, exc_value, **opts)
for i, frame in enumerate(frames):
for sub in _serialize_frames(is_opened=i + 1 > len(frames) - 2, **frame):
yield sub
def _serialize_variables(variables):
hidden = variables.get("__traceback_hidden_variables__", ())
if hidden is True:
return
if not isinstance(hidden, (tuple, list)):
hidden = ()
variables = tuple(
(safe_force_text(key), safe_force_text(value))
for key, value in variables.items()
if not key in hidden
)
if variables:
yield wl.OpenerView(
[
"Local variables",
wl.Grid(
iterate((("Key", "Value"),), variables),
Background=[None, [wl.LightGray]],
Alignment=wl.Left,
Frame=wl.LightGray,
),
]
)
else:
yield "No local variables"
def _paginate(i, line):
return "%s. %s" % (force_text(i).rjust(4), line)
def _serialize_frames(
filename,
function,
pre_context,
post_context,
context_line,
variables,
lineno,
pre_context_lineno,
is_opened=False,
**opts
):
if filename:
description = wl.Row(
[
wl.Button(
wl.Style(
filename, wl.RGBColor(0.25, 0.48, 1), wl.Small, FontFamily="Courier"
),
wl.If(wl.FileExistsQ(filename), wl.SystemOpen(filename)),
Appearance="Frameless",
),
" in ",
function,
]
)
else:
description = function
yield wl.OpenerView(
[
description,
wl.Column(
iterate(
(
wl.Column(
iterate(
(
_paginate(pre_context_lineno + i, l)
for i, l in enumerate(pre_context)
),
[
wl.Item(
_paginate(lineno, context_line),
Background=wl.LightYellow,
)
],
(
_paginate(lineno + i + 1, l)
for i, l in enumerate(post_context)
),
),
Background=[[wl.GrayLevel(0.95), wl.GrayLevel(1)]],
Frame=wl.LightGray,
),
),
_serialize_variables(variables),
)
),
],
is_opened,
)
@to_tuple
def _get_traceback_frames(traceback, exc_value, context_lines=7):
def explicit_or_implicit_cause(exc_value):
explicit = getattr(exc_value, "__cause__", None)
implicit = getattr(exc_value, "__context__", None)
return explicit or implicit
# Get the exception and all its causes
exceptions = []
while exc_value:
exceptions.append(exc_value)
exc_value = explicit_or_implicit_cause(exc_value)
# No exceptions were supplied to ExceptionReporter
if exceptions:
# In case there's just one exception, take the traceback from self.tb
exc_value = exceptions.pop()
tb = traceback if not exceptions else exc_value.__traceback__
while tb is not None:
# Support for __traceback_hide__ which is used by a few libraries
# to hide internal frames.
if tb.tb_frame.f_locals.get("__traceback_hide__"):
tb = tb.tb_next
continue
filename = tb.tb_frame.f_code.co_filename
function = tb.tb_frame.f_code.co_name
lineno = tb.tb_lineno - 1
loader = tb.tb_frame.f_locals.get("__loader__") or tb.tb_frame.f_globals.get(
"__loader__"
)
module_name = tb.tb_frame.f_globals.get("__name__") or ""
pre_context_lineno, pre_context, context_line, post_context = _get_lines_from_file(
filename, lineno, context_lines, loader, module_name
)
if pre_context_lineno is None:
pre_context_lineno = lineno
pre_context = []
context_line = "<source code not available>"
post_context = []
yield {
"filename": filename and force_text(filename) or None,
"function": function and force_text(function) or None,
"lineno": lineno + 1,
"variables": tb.tb_frame.f_locals,
"pre_context": pre_context,
"context_line": context_line,
"post_context": post_context,
"pre_context_lineno": pre_context_lineno + 1,
}
# If the traceback for current exception is consumed, try the
# other exception.
if not tb.tb_next and exceptions:
exc_value = exceptions.pop()
tb = exc_value.__traceback__
else:
tb = tb.tb_next
def _get_lines_from_file(filename, lineno, context_lines, loader=None, module_name=None):
"""
Return context_lines before and after lineno from file.
Return (pre_context_lineno, pre_context, context_line, post_context).
"""
source = None
if loader is not None and hasattr(loader, "get_source"):
try:
source = loader.get_source(module_name)
except ImportError:
pass
if source is not None:
source = source.splitlines()
if source is None:
try:
with open(filename, "rb") as fp:
source = fp.read().splitlines()
except (OSError, IOError):
pass
if source is None:
return None, [], None, []
# If we just read the source from a file, or if the loader did not
# apply tokenize.detect_encoding to decode the source into a
# string, then we should do that ourselves.
if isinstance(source[0], six.binary_type):
encoding = "ascii"
for line in source[:2]:
# File coding may be specified. Match pattern from PEP-263
# (http://www.python.org/dev/peps/pep-0263/)
match = re.search(br"coding[:=]\s*([-\w.]+)", line)
if match:
encoding = match.group(1).decode("ascii")
break
source = [force_text(sline, encoding, "replace") for sline in source]
lineno = min(lineno, len(source) - 1)
lower_bound = max(0, lineno - context_lines)
upper_bound = lineno + context_lines
pre_context = source[lower_bound:lineno]
context_line = source[lineno]
post_context = source[lineno + 1 : upper_bound]
return lower_bound, pre_context, context_line, post_context