mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Move exception formatting utilities to `sphinx.util.exceptions`
This commit is contained in:
@@ -22,6 +22,16 @@ The following is a list of deprecated interfaces.
|
||||
- Removed
|
||||
- Alternatives
|
||||
|
||||
* - ``sphinx.util.save_traceback``
|
||||
- 6.1
|
||||
- 8.0
|
||||
- ``sphinx.util.exceptions.save_traceback``
|
||||
|
||||
* - ``sphinx.util.format_exception_cut_frames``
|
||||
- 6.1
|
||||
- 8.0
|
||||
- ``sphinx.util.exceptions.format_exception_cut_frames``
|
||||
|
||||
* - ``sphinx.util.epoch_to_rfc1123``
|
||||
- 6.1
|
||||
- 8.0
|
||||
|
||||
@@ -20,9 +20,10 @@ from sphinx import __display_version__, package_dir
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.errors import SphinxError
|
||||
from sphinx.locale import __
|
||||
from sphinx.util import Tee, format_exception_cut_frames, save_traceback
|
||||
from sphinx.util import Tee
|
||||
from sphinx.util.console import color_terminal, nocolor, red, terminal_safe # type: ignore
|
||||
from sphinx.util.docutils import docutils_namespace, patch_docutils
|
||||
from sphinx.util.exceptions import format_exception_cut_frames, save_traceback
|
||||
from sphinx.util.osutil import abspath, ensuredir
|
||||
|
||||
|
||||
@@ -53,7 +54,7 @@ def handle_exception(
|
||||
elif isinstance(exception, UnicodeError):
|
||||
print(red(__('Encoding error:')), file=stderr)
|
||||
print(terminal_safe(str(exception)), file=stderr)
|
||||
tbpath = save_traceback(app)
|
||||
tbpath = save_traceback(app, exception)
|
||||
print(red(__('The full traceback has been saved in %s, if you want '
|
||||
'to report the issue to the developers.') % tbpath),
|
||||
file=stderr)
|
||||
@@ -68,7 +69,7 @@ def handle_exception(
|
||||
else:
|
||||
print(red(__('Exception occurred:')), file=stderr)
|
||||
print(format_exception_cut_frames().rstrip(), file=stderr)
|
||||
tbpath = save_traceback(app)
|
||||
tbpath = save_traceback(app, exception)
|
||||
print(red(__('The full traceback has been saved in %s, if you '
|
||||
'want to report the issue to the developers.') % tbpath),
|
||||
file=stderr)
|
||||
|
||||
@@ -6,23 +6,21 @@ import hashlib
|
||||
import os
|
||||
import posixpath
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
import traceback
|
||||
import warnings
|
||||
from importlib import import_module
|
||||
from os import path
|
||||
from typing import IO, TYPE_CHECKING, Any, Iterable
|
||||
from typing import IO, Any, Iterable
|
||||
from urllib.parse import parse_qsl, quote_plus, urlencode, urlsplit, urlunsplit
|
||||
|
||||
from sphinx.deprecation import RemovedInSphinx70Warning, deprecated_alias
|
||||
from sphinx.errors import ExtensionError, FiletypeNotFoundError, SphinxParallelError
|
||||
from sphinx.errors import ExtensionError, FiletypeNotFoundError
|
||||
from sphinx.locale import __
|
||||
from sphinx.util import display as _display
|
||||
from sphinx.util import exceptions as _exceptions
|
||||
from sphinx.util import http_date as _http_date
|
||||
from sphinx.util import logging
|
||||
from sphinx.util import osutil as _osutil
|
||||
from sphinx.util.console import strip_colors
|
||||
from sphinx.util.console import strip_colors # NoQA: F401
|
||||
from sphinx.util.matching import patfilter # noqa: F401
|
||||
from sphinx.util.nodes import (caption_ref_re, explicit_title_re, # noqa: F401
|
||||
nested_parse_with_titles, split_explicit_title)
|
||||
@@ -32,10 +30,6 @@ from sphinx.util.osutil import (SEP, copyfile, copytimes, ensuredir, # noqa: F4
|
||||
make_filename, mtimes_of_files, os_path, relative_uri)
|
||||
from sphinx.util.typing import PathMatcher
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sphinx.application import Sphinx
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Generally useful regular expressions.
|
||||
@@ -193,54 +187,6 @@ class DownloadFiles(dict):
|
||||
self.add_file(docname, filename)
|
||||
|
||||
|
||||
_DEBUG_HEADER = '''\
|
||||
# Sphinx version: %s
|
||||
# Python version: %s (%s)
|
||||
# Docutils version: %s %s
|
||||
# Jinja2 version: %s
|
||||
# Last messages:
|
||||
%s
|
||||
# Loaded extensions:
|
||||
'''
|
||||
|
||||
|
||||
def save_traceback(app: Sphinx | None) -> str:
|
||||
"""Save the current exception's traceback in a temporary file."""
|
||||
import platform
|
||||
|
||||
import docutils
|
||||
import jinja2
|
||||
|
||||
import sphinx
|
||||
exc = sys.exc_info()[1]
|
||||
if isinstance(exc, SphinxParallelError):
|
||||
exc_format = '(Error in parallel process)\n' + exc.traceback
|
||||
else:
|
||||
exc_format = traceback.format_exc()
|
||||
fd, path = tempfile.mkstemp('.log', 'sphinx-err-')
|
||||
last_msgs = ''
|
||||
if app is not None:
|
||||
last_msgs = '\n'.join(
|
||||
'# %s' % strip_colors(s).strip()
|
||||
for s in app.messagelog)
|
||||
os.write(fd, (_DEBUG_HEADER %
|
||||
(sphinx.__display_version__,
|
||||
platform.python_version(),
|
||||
platform.python_implementation(),
|
||||
docutils.__version__, docutils.__version_details__,
|
||||
jinja2.__version__,
|
||||
last_msgs)).encode())
|
||||
if app is not None:
|
||||
for ext in app.extensions.values():
|
||||
modfile = getattr(ext.module, '__file__', 'unknown')
|
||||
if ext.version != 'builtin':
|
||||
os.write(fd, ('# %s (%s) from %s\n' %
|
||||
(ext.name, ext.version, modfile)).encode())
|
||||
os.write(fd, exc_format.encode())
|
||||
os.close(fd)
|
||||
return path
|
||||
|
||||
|
||||
def get_full_modname(modname: str, attribute: str) -> str | None:
|
||||
if modname is None:
|
||||
# Prevents a TypeError: if the last getattr() call will return None
|
||||
@@ -359,17 +305,6 @@ def split_index_msg(type: str, value: str) -> list[str]:
|
||||
return result
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def import_object(objname: str, source: str | None = None) -> Any:
|
||||
"""Import python object by qualname."""
|
||||
try:
|
||||
@@ -473,6 +408,8 @@ deprecated_alias('sphinx.util',
|
||||
'progress_message': _display.progress_message,
|
||||
'epoch_to_rfc1123': _http_date.epoch_to_rfc1123,
|
||||
'rfc1123_to_epoch': _http_date.rfc1123_to_epoch,
|
||||
'save_traceback': _exceptions.save_traceback,
|
||||
'format_exception_cut_frames': _exceptions.format_exception_cut_frames,
|
||||
},
|
||||
RemovedInSphinx70Warning,
|
||||
{
|
||||
@@ -483,4 +420,6 @@ deprecated_alias('sphinx.util',
|
||||
'progress_message': 'sphinx.util.display.progress_message',
|
||||
'epoch_to_rfc1123': 'sphinx.http_date.epoch_to_rfc1123',
|
||||
'rfc1123_to_epoch': 'sphinx.http_date.rfc1123_to_epoch',
|
||||
'save_traceback': 'sphinx.exceptions.save_traceback',
|
||||
'format_exception_cut_frames': 'sphinx.exceptions.format_exception_cut_frames', # NoQA: E501
|
||||
})
|
||||
|
||||
67
sphinx/util/exceptions.py
Normal file
67
sphinx/util/exceptions.py
Normal file
@@ -0,0 +1,67 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user