from __future__ import annotations import sys import traceback from tempfile import NamedTemporaryFile from typing import TYPE_CHECKING from sphinx.errors import SphinxParallelError from sphinx.util.console import strip_colors if TYPE_CHECKING: from sphinx.application import Sphinx def save_traceback(app: Sphinx | None, exc: BaseException) -> str: """Save the given exception's traceback in a temporary file.""" import platform import docutils import jinja2 import pygments import sphinx if isinstance(exc, SphinxParallelError): exc_format = '(Error in parallel process)\n' + exc.traceback else: exc_format = traceback.format_exc() if app is None: last_msgs = exts_list = '' else: extensions = app.extensions.values() last_msgs = '\n'.join(f'# {strip_colors(s).strip()}' for s in app.messagelog) exts_list = '\n'.join(f'# {ext.name} ({ext.version})' for ext in extensions if ext.version != 'builtin') with NamedTemporaryFile('w', suffix='.log', prefix='sphinx-err-', delete=False) as f: f.write(f"""\ # Platform: {sys.platform}; ({platform.platform()}) # Sphinx version: {sphinx.__display_version__} # Python version: {platform.python_version()} ({platform.python_implementation()}) # Docutils version: {docutils.__version__} # Jinja2 version: {jinja2.__version__} # Pygments version: {pygments.__version__} # Last messages: {last_msgs} # Loaded extensions: {exts_list} # Traceback: {exc_format} """) return f.name def format_exception_cut_frames(x: int = 1) -> str: """Format an exception with traceback, but only the last x frames.""" typ, val, tb = sys.exc_info() # res = ['Traceback (most recent call last):\n'] res: list[str] = [] tbres = traceback.format_tb(tb) res += tbres[-x:] res += traceback.format_exception_only(typ, val) return ''.join(res)