def sweeten_errors(): try: yield except Exception as exc: SPACES = 2 w = term.white prefix = w("║" + " " * (SPACES - 1)) suffix = w(" " * (SPACES - 1) + "║") pre_re = re.compile("([^`]*)`([^`]*)`([^`]*)") def format_arg(arg): length = len(pre_re.sub("\\1\\2\\3", arg)) arg = pre_re.sub(w("\\1") + term.bold("\\2") + w("\\3"), arg) arg = re.sub(r"^ \$ (.*)", term.lightblack(" $ ") + term.reset("\\1"), arg) return (arg, length) def f(*args): return "".join(args) term_width, term_height = term.get_size() line_length = min(80, term_width) for arg in exc.args: line_length = max(min(line_length, len(arg) + 2 * SPACES), 120) print(f(w("╔" + "═" * (line_length - 2) + "╗"))) for i, arg in enumerate(exc.args): if i == 1: print(f(prefix, " " * (line_length - 2 * SPACES), suffix)) arg_formatted, arg_length = format_arg(arg) if not i: # first line print( f( prefix, term.red_bg(term.bold(" " + type(exc).__name__ + " ")), " ", w(arg_formatted), " " * (line_length - (arg_length + 3 + len(type(exc).__name__) + 2 * SPACES)), suffix, ) ) else: # other lines print(f(prefix, arg_formatted + " " * (line_length - arg_length - 2 * SPACES), suffix)) print(f(w("╚" + "═" * (line_length - 2) + "╝"))) logging.getLogger().debug("This error was caused by the following exception chain.", exc_info=exc_info())
def humanized(exc, *, fg=term.red, bg=lambda *args: term.red_bg(term.bold(*args)), help_url=None): SPACES = 2 prefix, suffix = fg(VERT + " " * (SPACES - 1)), fg(" " * (SPACES - 1) + VERT) result = [] def format_arg(arg): length = len(preformatted_pattern.sub("\\1\\2\\3", arg)) arg = preformatted_pattern.sub("\\1" + term.bold("\\2") + "\\3", arg) arg = re.sub("^ \$ (.*)", term.lightblack(" $ ") + term.reset("\\1"), arg) return (arg, length) def joined(*args): return "".join(args) term_width, term_height = term.get_size() line_length = min(80, term_width) for arg in exc.args: line_length = max(min(line_length, len(arg) + 2 * SPACES), 120) result.append(joined(fg(TOP_LEFT + HORIZ * (line_length - 2) + TOP_RIGHT))) args = list(exc.args) for i, arg in enumerate(args): if i == 1: result.append( joined(prefix, " " * (line_length - 2 * SPACES), suffix)) arg_formatted, arg_length = format_arg(arg) if not i: # first line result.append( joined( prefix, bg(" " + type(exc).__name__ + " "), " ", term.white(arg_formatted), " " * (line_length - (arg_length + 3 + len(type(exc).__name__) + 2 * SPACES)), suffix, )) else: # other lines result.append( joined( prefix, arg_formatted + " " * (line_length - arg_length - 2 * SPACES), suffix)) if help_url: help_prefix = "Read more: " arg_length = len(help_url) + len(help_prefix) arg_formatted = help_prefix + term.underline(term.lightblue(help_url)) result.append(joined(prefix, " " * (line_length - 2 * SPACES), suffix)) result.append( joined( prefix, arg_formatted + " " * (line_length - arg_length - 2 * SPACES), suffix)) more = settings.DEBUG exc_lines = format_exception(exc_info(), fg=fg, bg=bg, summary=False).splitlines() if not len(exc_lines): more = False result.append( joined( fg((VERT_LEFT if more else BOTTOM_LEFT) + HORIZ * (line_length - 2) + BOTTOM_RIGHT))) if more: for _line in exc_lines: result.append(_line) result.append(joined(fg("╵"))) elif len(exc_lines): result.append( term.lightblack( "(add DEBUG=1 to system environment for stack trace)".rjust( line_length))) return "\n".join(result)
class PrettyPrinter(Configurable): max_width = Option( int, default=term.get_size()[0], required=False, __doc__=""" If set, truncates the output values longer than this to this width. """, ) filter = Method( default=(lambda self, index, key, value: (value is not None) and (not isinstance(key, str) or not key.startswith("_"))), __doc__=""" A filter that determine what to print. Default is to ignore any key starting with an underscore and none values. """, ) @ContextProcessor def context(self, context): context.setdefault("_jupyter_html", None) yield context if context._jupyter_html is not None: from IPython.display import display, HTML display( HTML("\n".join(["<table>"] + context._jupyter_html + ["</table>"]))) def __call__(self, context, *args, **kwargs): if not settings.QUIET: if term.isjupyter: self.print_jupyter(context, *args, **kwargs) return NOT_MODIFIED if term.istty: self.print_console(context, *args, **kwargs) return NOT_MODIFIED self.print_quiet(context, *args, **kwargs) return NOT_MODIFIED def print_quiet(self, context, *args, **kwargs): for index, (key, value) in enumerate( itertools.chain(enumerate(args), kwargs.items())): if self.filter(index, key, value): print( self.format_quiet(index, key, value, fields=context.get_input_fields())) def format_quiet(self, index, key, value, *, fields=None): # XXX should we implement argnames here ? return " ".join( ((" " if index else "-"), str(key), ":", str(value).strip())) def print_console(self, context, *args, **kwargs): print("\u250c") for index, (key, value) in enumerate( itertools.chain(enumerate(args), kwargs.items())): if self.filter(index, key, value): print( self.format_console(index, key, value, fields=context.get_input_fields())) print("\u2514") def format_console(self, index, key, value, *, fields=None): fields = fields or [] if not isinstance(key, str): if len(fields) > key and str(key) != str(fields[key]): key = "{}{}".format(fields[key], term.lightblack("[{}]".format(key))) else: key = str(index) prefix = "\u2502 {} = ".format(key) prefix_length = len(prefix) def indent(text, prefix): for i, line in enumerate(text.splitlines()): yield (prefix if i else "") + line + CLEAR_EOL + "\n" repr_of_value = "".join( indent(pprint.pformat(value, width=self.max_width - prefix_length), "\u2502" + " " * (len(prefix) - 1))).strip() return "{}{}{}".format(prefix, repr_of_value.replace("\n", CLEAR_EOL + "\n"), CLEAR_EOL) def print_jupyter(self, context, *args): if not context._jupyter_html: context._jupyter_html = [ "<thead><tr>", *map( "<th>{}</th>".format, map( html.escape, map(str, context.get_input_fields() or range(len(args))))), "</tr></thead>", ] context._jupyter_html += [ "<tr>", *map("<td>{}</td>".format, map(html.escape, map(repr, args))), "</tr>" ]