|
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +import linecache |
| 4 | +import textwrap |
| 5 | +import traceback |
| 6 | +import typing as t |
| 7 | + |
| 8 | +import click |
| 9 | + |
| 10 | + |
| 11 | +def format_error_message(err: BaseException) -> str: |
| 12 | + return f"{type(err).__name__}: {err}" |
| 13 | + |
| 14 | + |
| 15 | +def format_minimal_error(err: BaseException, *, indent: int = 0) -> str: |
| 16 | + lines = [textwrap.indent(str(err), indent * " ")] |
| 17 | + if err.__cause__ is not None: |
| 18 | + lines.append( |
| 19 | + textwrap.indent(format_error_message(err.__cause__), (indent + 2) * " ") |
| 20 | + ) |
| 21 | + |
| 22 | + return "\n".join(lines) |
| 23 | + |
| 24 | + |
| 25 | +def format_shortened_error(err: BaseException, *, indent: int = 0) -> str: |
| 26 | + lines = [] |
| 27 | + lines.append(textwrap.indent(format_error_message(err), indent * " ")) |
| 28 | + if err.__traceback__ is not None: |
| 29 | + lineno = err.__traceback__.tb_lineno |
| 30 | + tb_frame = err.__traceback__.tb_frame |
| 31 | + filename = tb_frame.f_code.co_filename |
| 32 | + line = linecache.getline(filename, lineno) |
| 33 | + lines.append((indent + 2) * " " + f'in "{filename}", line {lineno}') |
| 34 | + lines.append((indent + 2) * " " + ">>> " + line.strip()) |
| 35 | + return "\n".join(lines) |
| 36 | + |
| 37 | + |
| 38 | +def format_shortened_trace(caught_err: BaseException) -> str: |
| 39 | + err_stack: list[BaseException] = [caught_err] |
| 40 | + while err_stack[-1].__context__ is not None: |
| 41 | + err_stack.append(err_stack[-1].__context__) # type: ignore[arg-type] |
| 42 | + |
| 43 | + parts = [format_shortened_error(caught_err)] |
| 44 | + indent = 0 |
| 45 | + for err in err_stack[1:]: |
| 46 | + indent += 2 |
| 47 | + parts.append("\n" + indent * " " + "caused by\n") |
| 48 | + parts.append(format_shortened_error(err, indent=indent)) |
| 49 | + return "\n".join(parts) |
| 50 | + |
| 51 | + |
| 52 | +def format_error( |
| 53 | + err: Exception, mode: t.Literal["minimal", "short", "full"] = "short" |
| 54 | +) -> str: |
| 55 | + if mode == "minimal": |
| 56 | + return format_minimal_error(err) |
| 57 | + elif mode == "short": |
| 58 | + return format_shortened_trace(err) |
| 59 | + else: |
| 60 | + return "".join(traceback.format_exception(type(err), err, err.__traceback__)) |
| 61 | + |
| 62 | + |
| 63 | +def print_error( |
| 64 | + err: Exception, mode: t.Literal["minimal", "short", "full"] = "short" |
| 65 | +) -> None: |
| 66 | + click.echo(format_error(err, mode=mode), err=True) |
0 commit comments