From 53c1dff91c0b7100e1ce1b51acbf0fffbc10cf9c Mon Sep 17 00:00:00 2001 From: Ram Rachum Date: Sun, 14 Jun 2020 00:46:19 +0300 Subject: [PATCH] Fix exception causes all over the codebase --- sphinx/builders/gettext.py | 2 +- sphinx/builders/html/__init__.py | 4 +-- sphinx/builders/latex/theming.py | 8 +++--- sphinx/config.py | 14 +++++----- sphinx/domains/c.py | 13 ++++----- sphinx/domains/cpp.py | 27 ++++++++++--------- sphinx/domains/std.py | 4 +-- sphinx/environment/__init__.py | 7 ++--- sphinx/ext/autodoc/importer.py | 4 +-- sphinx/ext/autosummary/__init__.py | 2 +- sphinx/ext/graphviz.py | 8 +++--- sphinx/ext/imgconverter.py | 2 +- sphinx/ext/imgmath.py | 16 +++++------ sphinx/ext/intersphinx.py | 2 +- sphinx/pycode/__init__.py | 10 +++---- sphinx/registry.py | 21 ++++++++------- sphinx/search/ja.py | 4 +-- sphinx/setup_command.py | 2 +- sphinx/theming.py | 16 +++++------ sphinx/transforms/post_transforms/__init__.py | 4 +-- sphinx/util/__init__.py | 14 +++++----- sphinx/util/i18n.py | 2 +- sphinx/util/inspect.py | 8 +++--- sphinx/util/osutil.py | 4 +-- sphinx/util/pycompat.py | 7 ++--- sphinx/writers/texinfo.py | 4 +-- tests/test_build_latex.py | 6 ++--- tests/test_build_texinfo.py | 4 +-- 28 files changed, 114 insertions(+), 105 deletions(-) diff --git a/sphinx/builders/gettext.py b/sphinx/builders/gettext.py index 65f112510..8e30762e9 100644 --- a/sphinx/builders/gettext.py +++ b/sphinx/builders/gettext.py @@ -278,7 +278,7 @@ class MessageCatalogBuilder(I18nBuilder): origin = MsgOrigin(template, line) self.catalogs['sphinx'].add(msg, origin) except Exception as exc: - raise ThemeError('%s: %r' % (template, exc)) + raise ThemeError('%s: %r' % (template, exc)) from exc def build(self, docnames: Iterable[str], summary: str = None, method: str = 'update') -> None: # NOQA self._extract_from_template() diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index 5340c93f9..99d7aa13b 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -140,7 +140,7 @@ class BuildInfo: build_info.tags_hash = lines[3].split()[1].strip() return build_info except Exception as exc: - raise ValueError(__('build info file is broken: %r') % exc) + raise ValueError(__('build info file is broken: %r') % exc) from exc def __init__(self, config: Config = None, tags: Tags = None, config_categories: List[str] = []) -> None: # NOQA self.config_hash = '' @@ -1015,7 +1015,7 @@ class StandaloneHTMLBuilder(Builder): return except Exception as exc: raise ThemeError(__("An error happened in rendering the page %s.\nReason: %r") % - (pagename, exc)) + (pagename, exc)) from exc if not outfilename: outfilename = self.get_outfilename(pagename) diff --git a/sphinx/builders/latex/theming.py b/sphinx/builders/latex/theming.py index da6a7fa04..130bded4a 100644 --- a/sphinx/builders/latex/theming.py +++ b/sphinx/builders/latex/theming.py @@ -87,10 +87,12 @@ class UserTheme(Theme): try: value = self.config.get('theme', key) setattr(self, key, value) - except configparser.NoSectionError: - raise ThemeError(__('%r doesn\'t have "theme" setting') % filename) + except configparser.NoSectionError as exc: + raise ThemeError(__('%r doesn\'t have "theme" setting') % + filename) from exc except configparser.NoOptionError as exc: - raise ThemeError(__('%r doesn\'t have "%s" setting') % (filename, exc.args[0])) + raise ThemeError(__('%r doesn\'t have "%s" setting') % + (filename, exc.args[0])) from exc for key in self.OPTIONAL_CONFIG_KEYS: try: diff --git a/sphinx/config.py b/sphinx/config.py index 92c203dfd..353268e38 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -196,9 +196,9 @@ class Config: elif isinstance(defvalue, int): try: return int(value) - except ValueError: + except ValueError as exc: raise ValueError(__('invalid number %r for config value %r, ignoring') % - (value, name)) + (value, name)) from exc elif hasattr(defvalue, '__call__'): return value elif defvalue is not None and not isinstance(defvalue, str): @@ -319,17 +319,17 @@ def eval_config_file(filename: str, tags: Tags) -> Dict[str, Any]: execfile_(filename, namespace) except SyntaxError as err: msg = __("There is a syntax error in your configuration file: %s\n") - raise ConfigError(msg % err) - except SystemExit: + raise ConfigError(msg % err) from err + except SystemExit as exc: msg = __("The configuration file (or one of the modules it imports) " "called sys.exit()") - raise ConfigError(msg) + raise ConfigError(msg) from exc except ConfigError: # pass through ConfigError from conf.py as is. It will be shown in console. raise - except Exception: + except Exception as exc: msg = __("There is a programmable error in your configuration file:\n\n%s") - raise ConfigError(msg % traceback.format_exc()) + raise ConfigError(msg % traceback.format_exc()) from exc return namespace diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 36a8f1f65..b4cf32a99 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -2300,7 +2300,8 @@ class DefinitionParser(BaseParser): errs = [] errs.append((exCast, "If type cast expression")) errs.append((exUnary, "If unary expression")) - raise self._make_multi_error(errs, "Error in cast expression.") + raise self._make_multi_error(errs, + "Error in cast expression.") from exUnary else: return self._parse_unary_expression() @@ -2767,7 +2768,7 @@ class DefinitionParser(BaseParser): msg += " (e.g., 'void (*f(int arg))(double)')" prevErrors.append((exNoPtrParen, msg)) header = "Error in declarator" - raise self._make_multi_error(prevErrors, header) + raise self._make_multi_error(prevErrors, header) from exNoPtrParen pos = self.pos try: return self._parse_declarator_name_suffix(named, paramMode, typed) @@ -2775,7 +2776,7 @@ class DefinitionParser(BaseParser): self.pos = pos prevErrors.append((e, "If declarator-id")) header = "Error in declarator or parameters" - raise self._make_multi_error(prevErrors, header) + raise self._make_multi_error(prevErrors, header) from e def _parse_initializer(self, outer: str = None, allowFallback: bool = True ) -> ASTInitializer: @@ -2843,7 +2844,7 @@ class DefinitionParser(BaseParser): if True: header = "Type must be either just a name or a " header += "typedef-like declaration." - raise self._make_multi_error(prevErrors, header) + raise self._make_multi_error(prevErrors, header) from exTyped else: # For testing purposes. # do it again to get the proper traceback (how do you @@ -2994,7 +2995,7 @@ class DefinitionParser(BaseParser): errs = [] errs.append((exExpr, "If expression")) errs.append((exType, "If type")) - raise self._make_multi_error(errs, header) + raise self._make_multi_error(errs, header) from exType return res @@ -3132,7 +3133,7 @@ class CObject(ObjectDescription): name = _make_phony_error_name() symbol = parentSymbol.add_name(name) self.env.temp_data['c:last_symbol'] = symbol - raise ValueError + raise ValueError from e try: symbol = parentSymbol.add_declaration(ast, docname=self.env.docname) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 1783db491..c202786e8 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -4882,7 +4882,7 @@ class DefinitionParser(BaseParser): raise self._make_multi_error([ (eFold, "If fold expression"), (eExpr, "If parenthesized expression") - ], "Error in fold expression or parenthesized expression.") + ], "Error in fold expression or parenthesized expression.") from eExpr return ASTParenExpr(res) # now it definitely is a fold expression if self.skip_string(')'): @@ -5066,7 +5066,7 @@ class DefinitionParser(BaseParser): errors = [] errors.append((eType, "If type")) errors.append((eExpr, "If expression")) - raise self._make_multi_error(errors, header) + raise self._make_multi_error(errors, header) from eExpr else: # a primary expression or a type pos = self.pos try: @@ -5093,7 +5093,7 @@ class DefinitionParser(BaseParser): errors = [] errors.append((eOuter, "If primary expression")) errors.append((eInner, "If type")) - raise self._make_multi_error(errors, header) + raise self._make_multi_error(errors, header) from eInner # and now parse postfixes postFixes = [] # type: List[ASTPostfixOp] @@ -5253,7 +5253,8 @@ class DefinitionParser(BaseParser): errs = [] errs.append((exCast, "If type cast expression")) errs.append((exUnary, "If unary expression")) - raise self._make_multi_error(errs, "Error in cast expression.") + raise self._make_multi_error(errs, + "Error in cast expression.") from exUnary else: return self._parse_unary_expression() @@ -5504,7 +5505,7 @@ class DefinitionParser(BaseParser): self.pos = pos prevErrors.append((e, "If non-type argument")) header = "Error in parsing template argument list." - raise self._make_multi_error(prevErrors, header) + raise self._make_multi_error(prevErrors, header) from e if parsedEnd: assert not parsedComma break @@ -5949,7 +5950,7 @@ class DefinitionParser(BaseParser): self.pos = pos prevErrors.append((exNoPtrParen, "If parenthesis in noptr-declarator")) header = "Error in declarator" - raise self._make_multi_error(prevErrors, header) + raise self._make_multi_error(prevErrors, header) from exNoPtrParen if typed: # pointer to member pos = self.pos try: @@ -5988,7 +5989,7 @@ class DefinitionParser(BaseParser): self.pos = pos prevErrors.append((e, "If declarator-id")) header = "Error in declarator or parameters-and-qualifiers" - raise self._make_multi_error(prevErrors, header) + raise self._make_multi_error(prevErrors, header) from e def _parse_initializer(self, outer: str = None, allowFallback: bool = True ) -> ASTInitializer: @@ -6096,7 +6097,7 @@ class DefinitionParser(BaseParser): header = "Error when parsing function declaration." else: assert False - raise self._make_multi_error(prevErrors, header) + raise self._make_multi_error(prevErrors, header) from exTyped else: # For testing purposes. # do it again to get the proper traceback (how do you @@ -6163,7 +6164,7 @@ class DefinitionParser(BaseParser): errs.append((eType, "If default template argument is a type")) msg = "Error in non-type template parameter" msg += " or constrained template parameter." - raise self._make_multi_error(errs, msg) + raise self._make_multi_error(errs, msg) from eType def _parse_type_using(self) -> ASTTypeUsing: name = self._parse_nested_name() @@ -6510,7 +6511,7 @@ class DefinitionParser(BaseParser): self.pos = pos prevErrors.append((e, "If type alias or template alias")) header = "Error in type declaration." - raise self._make_multi_error(prevErrors, header) + raise self._make_multi_error(prevErrors, header) from e elif objectType == 'concept': declaration = self._parse_concept() elif objectType == 'member': @@ -6576,7 +6577,7 @@ class DefinitionParser(BaseParser): errs.append((e1, "If shorthand ref")) errs.append((e2, "If full function ref")) msg = "Error in cross-reference." - raise self._make_multi_error(errs, msg) + raise self._make_multi_error(errs, msg) from e2 def parse_expression(self) -> Union[ASTExpression, ASTType]: pos = self.pos @@ -6597,7 +6598,7 @@ class DefinitionParser(BaseParser): errs = [] errs.append((exExpr, "If expression")) errs.append((exType, "If type")) - raise self._make_multi_error(errs, header) + raise self._make_multi_error(errs, header) from exType def _make_phony_error_name() -> ASTNestedName: @@ -6790,7 +6791,7 @@ class CPPObject(ObjectDescription): name = _make_phony_error_name() symbol = parentSymbol.add_name(name) self.env.temp_data['cpp:last_symbol'] = symbol - raise ValueError + raise ValueError from e try: symbol = parentSymbol.add_declaration(ast, docname=self.env.docname) diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index fbbed3a6b..016f84ebc 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -1061,10 +1061,10 @@ class StandardDomain(Domain): try: figure_id = target_node['ids'][0] return env.toc_fignumbers[docname][figtype][figure_id] - except (KeyError, IndexError): + except (KeyError, IndexError) as exc: # target_node is found, but fignumber is not assigned. # Maybe it is defined in orphaned document. - raise ValueError + raise ValueError from exc def get_full_qualified_name(self, node: Element) -> str: if node.get('reftype') == 'option': diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index 6584ac6d8..1e58542bb 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -393,7 +393,8 @@ class BuildEnvironment: if catalog.domain == domain: self.dependencies[docname].add(catalog.mo_path) except OSError as exc: - raise DocumentError(__('Failed to scan documents in %s: %r') % (self.srcdir, exc)) + raise DocumentError(__('Failed to scan documents in %s: %r') % + (self.srcdir, exc)) from exc def get_outdated_files(self, config_changed: bool) -> Tuple[Set[str], Set[str], Set[str]]: """Return (added, changed, removed) sets.""" @@ -511,8 +512,8 @@ class BuildEnvironment: """ try: return self.domains[domainname] - except KeyError: - raise ExtensionError(__('Domain %r is not registered') % domainname) + except KeyError as exc: + raise ExtensionError(__('Domain %r is not registered') % domainname) from exc # --------- RESOLVING REFERENCES AND TOCTREES ------------------------------ diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py index cdccf710d..e5e84eda3 100644 --- a/sphinx/ext/autodoc/importer.py +++ b/sphinx/ext/autodoc/importer.py @@ -33,7 +33,7 @@ def import_module(modname: str, warningiserror: bool = False) -> Any: except BaseException as exc: # Importing modules may cause any side effects, including # SystemExit, so we need to catch all errors. - raise ImportError(exc, traceback.format_exc()) + raise ImportError(exc, traceback.format_exc()) from exc def import_object(modname: str, objpath: List[str], objtype: str = '', @@ -98,7 +98,7 @@ def import_object(modname: str, objpath: List[str], objtype: str = '', errmsg += '; the following exception was raised:\n%s' % traceback.format_exc() logger.debug(errmsg) - raise ImportError(errmsg) + raise ImportError(errmsg) from exc def get_module_members(module: Any) -> List[Tuple[str, Any]]: diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 162b6868c..58e192bda 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -647,7 +647,7 @@ def _import_by_name(name: str) -> Tuple[Any, Any, str]: else: return sys.modules[modname], None, modname except (ValueError, ImportError, AttributeError, KeyError) as e: - raise ImportError(*e.args) + raise ImportError(*e.args) from e # -- :autolink: (smart default role) ------------------------------------------- diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py index c21868a6f..4a8dd0a4d 100644 --- a/sphinx/ext/graphviz.py +++ b/sphinx/ext/graphviz.py @@ -256,7 +256,7 @@ def render_dot(self: SphinxTranslator, code: str, options: Dict, return None, None except CalledProcessError as exc: raise GraphvizError(__('dot exited with error:\n[stderr]\n%r\n' - '[stdout]\n%r') % (exc.stderr, exc.stdout)) + '[stdout]\n%r') % (exc.stderr, exc.stdout)) from exc def render_dot_html(self: HTMLTranslator, node: graphviz, code: str, options: Dict, @@ -270,7 +270,7 @@ def render_dot_html(self: HTMLTranslator, node: graphviz, code: str, options: Di fname, outfn = render_dot(self, code, options, format, prefix) except GraphvizError as exc: logger.warning(__('dot code %r: %s'), code, exc) - raise nodes.SkipNode + raise nodes.SkipNode from exc classes = [imgcls, 'graphviz'] + node.get('classes', []) imgcls = ' '.join(filter(None, classes)) @@ -321,7 +321,7 @@ def render_dot_latex(self: LaTeXTranslator, node: graphviz, code: str, fname, outfn = render_dot(self, code, options, 'pdf', prefix) except GraphvizError as exc: logger.warning(__('dot code %r: %s'), code, exc) - raise nodes.SkipNode + raise nodes.SkipNode from exc is_inline = self.is_inline(node) @@ -358,7 +358,7 @@ def render_dot_texinfo(self: TexinfoTranslator, node: graphviz, code: str, fname, outfn = render_dot(self, code, options, 'png', prefix) except GraphvizError as exc: logger.warning(__('dot code %r: %s'), code, exc) - raise nodes.SkipNode + raise nodes.SkipNode from exc if fname is not None: self.body.append('@image{%s,,,[graphviz],png}\n' % fname[:-4]) raise nodes.SkipNode diff --git a/sphinx/ext/imgconverter.py b/sphinx/ext/imgconverter.py index bf4b9b9d1..dd13a9879 100644 --- a/sphinx/ext/imgconverter.py +++ b/sphinx/ext/imgconverter.py @@ -70,7 +70,7 @@ class ImagemagickConverter(ImageConverter): except CalledProcessError as exc: raise ExtensionError(__('convert exited with error:\n' '[stderr]\n%r\n[stdout]\n%r') % - (exc.stderr, exc.stdout)) + (exc.stderr, exc.stdout)) from exc def setup(app: Sphinx) -> Dict[str, Any]: diff --git a/sphinx/ext/imgmath.py b/sphinx/ext/imgmath.py index 2050e470e..d11c5d7c5 100644 --- a/sphinx/ext/imgmath.py +++ b/sphinx/ext/imgmath.py @@ -165,13 +165,13 @@ def compile_math(latex: str, builder: Builder) -> str: try: subprocess.run(command, stdout=PIPE, stderr=PIPE, cwd=tempdir, check=True) return path.join(tempdir, 'math.dvi') - except OSError: + except OSError as exc: logger.warning(__('LaTeX command %r cannot be run (needed for math ' 'display), check the imgmath_latex setting'), builder.config.imgmath_latex) - raise InvokeError + raise InvokeError from exc except CalledProcessError as exc: - raise MathExtError('latex exited with error', exc.stderr, exc.stdout) + raise MathExtError('latex exited with error', exc.stderr, exc.stdout) from exc def convert_dvi_to_image(command: List[str], name: str) -> Tuple[bytes, bytes]: @@ -179,13 +179,13 @@ def convert_dvi_to_image(command: List[str], name: str) -> Tuple[bytes, bytes]: try: ret = subprocess.run(command, stdout=PIPE, stderr=PIPE, check=True) return ret.stdout, ret.stderr - except OSError: + except OSError as exc: logger.warning(__('%s command %r cannot be run (needed for math ' 'display), check the imgmath_%s setting'), name, command[0], name) - raise InvokeError + raise InvokeError from exc except CalledProcessError as exc: - raise MathExtError('%s exited with error' % name, exc.stderr, exc.stdout) + raise MathExtError('%s exited with error' % name, exc.stderr, exc.stdout) from exc def convert_dvi_to_png(dvipath: str, builder: Builder) -> Tuple[str, int]: @@ -326,7 +326,7 @@ def html_visit_math(self: HTMLTranslator, node: nodes.math) -> None: backrefs=[], source=node.astext()) sm.walkabout(self) logger.warning(__('display latex %r: %s'), node.astext(), msg) - raise nodes.SkipNode + raise nodes.SkipNode from exc if fname is None: # something failed -- use text-only as a bad substitute self.body.append('%s' % @@ -352,7 +352,7 @@ def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None backrefs=[], source=node.astext()) sm.walkabout(self) logger.warning(__('inline latex %r: %s'), node.astext(), msg) - raise nodes.SkipNode + raise nodes.SkipNode from exc self.body.append(self.starttag(node, 'div', CLASS='math')) self.body.append('

') if node['number']: diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py index 02f605bac..a6c4ef694 100644 --- a/sphinx/ext/intersphinx.py +++ b/sphinx/ext/intersphinx.py @@ -179,7 +179,7 @@ def fetch_inventory(app: Sphinx, uri: str, inv: Any) -> Any: join = path.join if localuri else posixpath.join invdata = InventoryFile.load(f, uri, join) except ValueError as exc: - raise ValueError('unknown or unsupported inventory version: %r' % exc) + raise ValueError('unknown or unsupported inventory version: %r' % exc) from exc except Exception as err: err.args = ('intersphinx inventory %r not readable due to %s: %s', inv, err.__class__.__name__, str(err)) diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py index 963680a54..29e53178f 100644 --- a/sphinx/pycode/__init__.py +++ b/sphinx/pycode/__init__.py @@ -37,7 +37,7 @@ class ModuleAnalyzer: try: mod = import_module(modname) except Exception as err: - raise PycodeError('error importing %r' % modname, err) + raise PycodeError('error importing %r' % modname, err) from err loader = getattr(mod, '__loader__', None) filename = getattr(mod, '__file__', None) if loader and getattr(loader, 'get_source', None): @@ -54,7 +54,7 @@ class ModuleAnalyzer: try: filename = loader.get_filename(modname) except ImportError as err: - raise PycodeError('error getting filename for %r' % modname, err) + raise PycodeError('error getting filename for %r' % modname, err) from err if filename is None: # all methods for getting filename failed, so raise... raise PycodeError('no source found for module %r' % modname) @@ -92,7 +92,7 @@ class ModuleAnalyzer: if '.egg' + path.sep in filename: obj = cls.cache['file', filename] = cls.for_egg(filename, modname) else: - raise PycodeError('error opening %r' % filename, err) + raise PycodeError('error opening %r' % filename, err) from err return obj @classmethod @@ -104,7 +104,7 @@ class ModuleAnalyzer: code = egg.read(relpath).decode() return cls.for_string(code, modname, filename) except Exception as exc: - raise PycodeError('error opening %r' % filename, exc) + raise PycodeError('error opening %r' % filename, exc) from exc @classmethod def for_module(cls, modname: str) -> "ModuleAnalyzer": @@ -169,7 +169,7 @@ class ModuleAnalyzer: self.tags = parser.definitions self.tagorder = parser.deforders except Exception as exc: - raise PycodeError('parsing %r failed: %r' % (self.srcname, exc)) + raise PycodeError('parsing %r failed: %r' % (self.srcname, exc)) from exc def find_attr_docs(self) -> Dict[Tuple[str, str], List[str]]: """Find class and module-level attributes and their documentation.""" diff --git a/sphinx/registry.py b/sphinx/registry.py index cad74559c..0aec0a9fd 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -139,9 +139,9 @@ class SphinxComponentRegistry: entry_points = iter_entry_points('sphinx.builders', name) try: entry_point = next(entry_points) - except StopIteration: + except StopIteration as exc: raise SphinxError(__('Builder name %s not registered or available' - ' through entry point') % name) + ' through entry point') % name) from exc self.load_extension(app, entry_point.module_name) @@ -273,8 +273,8 @@ class SphinxComponentRegistry: def get_source_parser(self, filetype: str) -> "Type[Parser]": try: return self.source_parsers[filetype] - except KeyError: - raise SphinxError(__('Source parser for %s not registered') % filetype) + except KeyError as exc: + raise SphinxError(__('Source parser for %s not registered') % filetype) from exc def get_source_parsers(self) -> Dict[str, "Type[Parser]"]: return self.source_parsers @@ -311,9 +311,11 @@ class SphinxComponentRegistry: try: visit, depart = handlers # unpack once for assertion translation_handlers[node.__name__] = (visit, depart) - except ValueError: - raise ExtensionError(__('kwargs for add_node() must be a (visit, depart) ' - 'function tuple: %r=%r') % (builder_name, handlers)) + except ValueError as exc: + raise ExtensionError( + __('kwargs for add_node() must be a (visit, depart) ' + 'function tuple: %r=%r') % (builder_name, handlers) + ) from exc def get_translator_class(self, builder: Builder) -> "Type[nodes.NodeVisitor]": return self.translators.get(builder.name, @@ -407,7 +409,8 @@ class SphinxComponentRegistry: mod = import_module(extname) except ImportError as err: logger.verbose(__('Original exception:\n') + traceback.format_exc()) - raise ExtensionError(__('Could not import extension %s') % extname, err) + raise ExtensionError(__('Could not import extension %s') % extname, + err) from err setup = getattr(mod, 'setup', None) if setup is None: @@ -423,7 +426,7 @@ class SphinxComponentRegistry: __('The %s extension used by this project needs at least ' 'Sphinx v%s; it therefore cannot be built with this ' 'version.') % (extname, err) - ) + ) from err if metadata is None: metadata = {} diff --git a/sphinx/search/ja.py b/sphinx/search/ja.py index d1f444be1..c1e72b8f8 100644 --- a/sphinx/search/ja.py +++ b/sphinx/search/ja.py @@ -528,9 +528,9 @@ class SearchJapanese(SearchLanguage): dotted_path = options.get('type', 'sphinx.search.ja.DefaultSplitter') try: self.splitter = import_object(dotted_path)(options) - except ExtensionError: + except ExtensionError as exc: raise ExtensionError("Splitter module %r can't be imported" % - dotted_path) + dotted_path) from exc def split(self, input: str) -> List[str]: return self.splitter.split(input) diff --git a/sphinx/setup_command.py b/sphinx/setup_command.py index 2c0076304..24beab856 100644 --- a/sphinx/setup_command.py +++ b/sphinx/setup_command.py @@ -198,7 +198,7 @@ class BuildDoc(Command): except Exception as exc: handle_exception(app, self, exc, sys.stderr) if not self.pdb: - raise SystemExit(1) + raise SystemExit(1) from exc if not self.link_index: continue diff --git a/sphinx/theming.py b/sphinx/theming.py index 13a895393..087ee7f24 100644 --- a/sphinx/theming.py +++ b/sphinx/theming.py @@ -74,17 +74,17 @@ class Theme: try: inherit = self.config.get('theme', 'inherit') - except configparser.NoSectionError: - raise ThemeError(__('theme %r doesn\'t have "theme" setting') % name) - except configparser.NoOptionError: - raise ThemeError(__('theme %r doesn\'t have "inherit" setting') % name) + except configparser.NoSectionError as exc: + raise ThemeError(__('theme %r doesn\'t have "theme" setting') % name) from exc + except configparser.NoOptionError as exc: + raise ThemeError(__('theme %r doesn\'t have "inherit" setting') % name) from exc if inherit != 'none': try: self.base = factory.create(inherit) - except ThemeError: + except ThemeError as exc: raise ThemeError(__('no theme named %r found, inherited by %r') % - (inherit, name)) + (inherit, name)) from exc def get_theme_dirs(self) -> List[str]: """Return a list of theme directories, beginning with this theme's, @@ -101,13 +101,13 @@ class Theme: """ try: return self.config.get(section, name) - except (configparser.NoOptionError, configparser.NoSectionError): + except (configparser.NoOptionError, configparser.NoSectionError) as exc: if self.base: return self.base.get_config(section, name, default) if default is NODEFAULT: raise ThemeError(__('setting %s.%s occurs in none of the ' - 'searched theme configs') % (section, name)) + 'searched theme configs') % (section, name)) from exc else: return default diff --git a/sphinx/transforms/post_transforms/__init__.py b/sphinx/transforms/post_transforms/__init__.py index 4499e3376..7dc14af52 100644 --- a/sphinx/transforms/post_transforms/__init__.py +++ b/sphinx/transforms/post_transforms/__init__.py @@ -82,8 +82,8 @@ class ReferencesResolver(SphinxPostTransform): # let the domain try to resolve the reference try: domain = self.env.domains[node['refdomain']] - except KeyError: - raise NoUri(target, typ) + except KeyError as exc: + raise NoUri(target, typ) from exc newnode = domain.resolve_xref(self.env, refdoc, self.app.builder, typ, target, node, contnode) # really hardwired reference types diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index ca9bb028d..3bf049f98 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -285,21 +285,21 @@ def get_module_source(modname: str) -> Tuple[str, str]: try: mod = import_module(modname) except Exception as err: - raise PycodeError('error importing %r' % modname, err) + raise PycodeError('error importing %r' % modname, err) from err filename = getattr(mod, '__file__', None) loader = getattr(mod, '__loader__', None) if loader and getattr(loader, 'get_filename', None): try: filename = loader.get_filename(modname) except Exception as err: - raise PycodeError('error getting filename for %r' % filename, err) + raise PycodeError('error getting filename for %r' % filename, err) from err if filename is None and loader: try: filename = loader.get_source(modname) if filename: return 'string', filename except Exception as err: - raise PycodeError('error getting source for %r' % modname, err) + raise PycodeError('error getting source for %r' % modname, err) from err if filename is None: raise PycodeError('no source found for module %r' % modname) filename = path.normpath(path.abspath(filename)) @@ -456,8 +456,8 @@ def parselinenos(spec: str, total: int) -> List[int]: items.extend(range(start - 1, end)) else: raise ValueError - except Exception: - raise ValueError('invalid line number spec: %r' % spec) + except Exception as exc: + raise ValueError('invalid line number spec: %r' % spec) from exc return items @@ -596,9 +596,9 @@ def import_object(objname: str, source: str = None) -> Any: except (AttributeError, ImportError) as exc: if source: raise ExtensionError('Could not import %s (needed for %s)' % - (objname, source), exc) + (objname, source), exc) from exc else: - raise ExtensionError('Could not import %s' % objname, exc) + raise ExtensionError('Could not import %s' % objname, exc) from exc def split_full_qualified_name(name: str) -> Tuple[str, str]: diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index 1cb75637c..499f2316f 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -312,7 +312,7 @@ def get_image_filename_for_language(filename: str, env: "BuildEnvironment") -> s try: return filename_format.format(**d) except KeyError as exc: - raise SphinxError('Invalid figure_language_filename: %r' % exc) + raise SphinxError('Invalid figure_language_filename: %r' % exc) from exc def search_image_for_language(filename: str, env: "BuildEnvironment") -> str: diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index d4928c847..3077f9eb2 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -321,7 +321,7 @@ def safe_getattr(obj: Any, name: str, *defargs: Any) -> Any: """A getattr() that turns all exceptions into AttributeErrors.""" try: return getattr(obj, name, *defargs) - except Exception: + except Exception as exc: # sometimes accessing a property raises an exception (e.g. # NotImplementedError), so let's try to read the attribute directly try: @@ -336,7 +336,7 @@ def safe_getattr(obj: Any, name: str, *defargs: Any) -> Any: if defargs: return defargs[0] - raise AttributeError(name) + raise AttributeError(name) from exc def safe_getmembers(object: Any, predicate: Callable[[str], bool] = None, @@ -385,8 +385,8 @@ def object_description(object: Any) -> str: for x in sorted_values) try: s = repr(object) - except Exception: - raise ValueError + except Exception as exc: + raise ValueError from exc # Strip non-deterministic memory addresses such as # ``<__main__.A at 0x7f68cb685710>`` s = memory_address_re.sub('', s) diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index 23f5b0137..0390b038d 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -168,10 +168,10 @@ def abspath(pathdir: str) -> str: if isinstance(pathdir, bytes): try: pathdir = pathdir.decode(fs_encoding) - except UnicodeDecodeError: + except UnicodeDecodeError as exc: raise UnicodeDecodeError('multibyte filename not supported on ' 'this filesystem encoding ' - '(%r)' % fs_encoding) + '(%r)' % fs_encoding) from exc return pathdir diff --git a/sphinx/util/pycompat.py b/sphinx/util/pycompat.py index 664387cac..2173fce14 100644 --- a/sphinx/util/pycompat.py +++ b/sphinx/util/pycompat.py @@ -34,11 +34,11 @@ def convert_with_2to3(filepath: str) -> str: try: from lib2to3.refactor import RefactoringTool, get_fixers_from_package from lib2to3.pgen2.parse import ParseError - except ImportError: + except ImportError as exc: # python 3.9.0a6+ emits PendingDeprecationWarning for lib2to3. # Additionally, removal of the module is still discussed at PEP-594. # To support future python, this catches ImportError for lib2to3. - raise SyntaxError + raise SyntaxError from exc fixers = get_fixers_from_package('lib2to3.fixes') refactoring_tool = RefactoringTool(fixers) @@ -49,7 +49,8 @@ def convert_with_2to3(filepath: str) -> str: # do not propagate lib2to3 exceptions lineno, offset = err.context[1] # try to match ParseError details with SyntaxError details - raise SyntaxError(err.msg, (filepath, lineno, offset, err.value)) + + raise SyntaxError(err.msg, (filepath, lineno, offset, err.value)) from err return str(tree) diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py index 9c30244e9..b77bf4352 100644 --- a/sphinx/writers/texinfo.py +++ b/sphinx/writers/texinfo.py @@ -853,8 +853,8 @@ class TexinfoTranslator(SphinxTranslator): num = node.astext().strip() try: footnode, used = self.footnotestack[-1][num] - except (KeyError, IndexError): - raise nodes.SkipNode + except (KeyError, IndexError) as exc: + raise nodes.SkipNode from exc # footnotes are repeated for each reference footnode.walkabout(self) # type: ignore raise nodes.SkipChildren diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index d3c491461..3bf6edd66 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -64,8 +64,8 @@ def compile_latex_document(app, filename='python.tex'): '-output-directory=%s' % app.config.latex_engine, filename] subprocess.run(args, stdout=PIPE, stderr=PIPE, check=True) - except OSError: # most likely the latex executable was not found - raise pytest.skip.Exception + except OSError as exc: # most likely the latex executable was not found + raise pytest.skip.Exception from exc except CalledProcessError as exc: print(exc.stdout) print(exc.stderr) @@ -1545,7 +1545,7 @@ def test_texescape_for_unicode_supported_engine(app, status, warning): assert 'superscript: ⁰, ¹' in result assert 'subscript: ₀, ₁' in result - + @pytest.mark.sphinx('latex', testroot='basic', confoverrides={'latex_elements': {'extrapackages': r'\usepackage{foo}'}}) def test_latex_elements_extrapackages(app, status, warning): diff --git a/tests/test_build_texinfo.py b/tests/test_build_texinfo.py index 378eaa192..9833218d7 100644 --- a/tests/test_build_texinfo.py +++ b/tests/test_build_texinfo.py @@ -58,8 +58,8 @@ def test_texinfo(app, status, warning): try: args = ['makeinfo', '--no-split', 'sphinxtests.texi'] subprocess.run(args, stdout=PIPE, stderr=PIPE, cwd=app.outdir, check=True) - except OSError: - raise pytest.skip.Exception # most likely makeinfo was not found + except OSError as exc: + raise pytest.skip.Exception from exc # most likely makeinfo was not found except CalledProcessError as exc: print(exc.stdout) print(exc.stderr)