Skip to content

Commit

Permalink
fix: raise chained errors with "from" #998
Browse files Browse the repository at this point in the history
This makes exceptions report their causes correctly, as "The above exception was
the direct cause of the following exception" instead of "During handling of the
above exception, another exception occurred."
  • Loading branch information
nedbat committed Jul 20, 2021
1 parent de38a0e commit 5313297
Show file tree
Hide file tree
Showing 8 changed files with 31 additions and 33 deletions.
8 changes: 4 additions & 4 deletions coverage/collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,12 @@ def __init__(
self.threading = threading
else:
raise CoverageException(f"Don't understand concurrency={concurrency}")
except ImportError:
except ImportError as ex:
raise CoverageException(
"Couldn't trace with concurrency={}, the module isn't installed.".format(
self.concurrency,
)
)
) from ex

self.reset()

Expand Down Expand Up @@ -318,8 +318,8 @@ def start(self):
(frame, event, arg), lineno = args
try:
fn(frame, event, arg, lineno=lineno)
except TypeError:
raise Exception("fullcoverage must be run with the C trace function.")
except TypeError as ex:
raise Exception("fullcoverage must be run with the C trace function.") from ex

# Install our installation tracer in threading, to jump-start other
# threads.
Expand Down
6 changes: 3 additions & 3 deletions coverage/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def getregexlist(self, section, option):
except re.error as e:
raise CoverageException(
f"Invalid [{section}].{option} value {value!r}: {e}"
)
) from e
if value:
value_list.append(value)
return value_list
Expand Down Expand Up @@ -272,7 +272,7 @@ def from_file(self, filename, our_file):
try:
files_read = cp.read(filename)
except (configparser.Error, TomlDecodeError) as err:
raise CoverageException(f"Couldn't read config file {filename}: {err}")
raise CoverageException(f"Couldn't read config file {filename}: {err}") from err
if not files_read:
return False

Expand All @@ -285,7 +285,7 @@ def from_file(self, filename, our_file):
if was_set:
any_set = True
except ValueError as err:
raise CoverageException(f"Couldn't read config file {filename}: {err}")
raise CoverageException(f"Couldn't read config file {filename}: {err}") from err

# Check that there are no unrecognized options.
all_options = collections.defaultdict(set)
Expand Down
16 changes: 8 additions & 8 deletions coverage/execfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def find_module(modulename):
try:
spec = importlib.util.find_spec(modulename)
except ImportError as err:
raise NoSource(str(err))
raise NoSource(str(err)) from err
if not spec:
raise NoSource(f"No module named {modulename!r}")
pathname = spec.origin
Expand Down Expand Up @@ -193,7 +193,7 @@ def run(self):
raise
except Exception as exc:
msg = "Couldn't run '{filename}' as Python code: {exc.__class__.__name__}: {exc}"
raise CoverageException(msg.format(filename=self.arg0, exc=exc))
raise CoverageException(msg.format(filename=self.arg0, exc=exc)) from exc

# Execute the code object.
# Return to the original directory in case the test code exits in
Expand Down Expand Up @@ -226,7 +226,7 @@ def run(self):
sys.excepthook(typ, err, tb.tb_next)
except SystemExit: # pylint: disable=try-except-raise
raise
except Exception:
except Exception as exc:
# Getting the output right in the case of excepthook
# shenanigans is kind of involved.
sys.stderr.write("Error in sys.excepthook:\n")
Expand All @@ -236,7 +236,7 @@ def run(self):
err2.__traceback__ = err2.__traceback__.tb_next
sys.__excepthook__(typ2, err2, tb2.tb_next)
sys.stderr.write("\nOriginal exception was:\n")
raise ExceptionDuringRun(typ, err, tb.tb_next)
raise ExceptionDuringRun(typ, err, tb.tb_next) from exc
else:
sys.exit(1)
finally:
Expand Down Expand Up @@ -277,8 +277,8 @@ def make_code_from_py(filename):
# Open the source file.
try:
source = get_python_source(filename)
except (OSError, NoSource):
raise NoSource("No file to run: '%s'" % filename)
except (OSError, NoSource) as exc:
raise NoSource("No file to run: '%s'" % filename) from exc

code = compile_unicode(source, filename, "exec")
return code
Expand All @@ -288,8 +288,8 @@ def make_code_from_pyc(filename):
"""Get a code object from a .pyc file."""
try:
fpyc = open(filename, "rb")
except OSError:
raise NoCode("No file to run: '%s'" % filename)
except OSError as exc:
raise NoCode("No file to run: '%s'" % filename) from exc

with fpyc:
# First four bytes are a version-specific magic number. It has to
Expand Down
8 changes: 3 additions & 5 deletions coverage/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ def __init__(self, text=None, filename=None, exclude=None):
try:
self.text = get_python_source(self.filename)
except OSError as err:
raise NoSource(
f"No source for code: '{self.filename}': {err}"
)
raise NoSource(f"No source for code: '{self.filename}': {err}") from err

self.exclude = exclude

Expand Down Expand Up @@ -243,7 +241,7 @@ def parse_source(self):
"Couldn't parse '%s' as Python source: '%s' at line %d" % (
self.filename, err.args[0], lineno
)
)
) from err

self.excluded = self.first_lines(self.raw_excluded)

Expand Down Expand Up @@ -363,7 +361,7 @@ def __init__(self, text, code=None, filename=None):
"Couldn't parse '%s' as Python source: '%s' at line %d" % (
filename, synerr.msg, synerr.lineno
)
)
) from synerr

# Alternative Python implementations don't always provide all the
# attributes on code objects that we need to do the analysis.
Expand Down
4 changes: 2 additions & 2 deletions coverage/sqldata.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ def _read_db(self):
"Data file {!r} doesn't seem to be a coverage data file: {}".format(
self._filename, exc
)
)
) from exc
else:
if schema_version != SCHEMA_VERSION:
raise CoverageException(
Expand Down Expand Up @@ -1095,7 +1095,7 @@ def execute(self, sql, parameters=()):
pass
if self.debug:
self.debug.write(f"EXCEPTION from execute: {msg}")
raise CoverageException(f"Couldn't use data file {self.filename!r}: {msg}")
raise CoverageException(f"Couldn't use data file {self.filename!r}: {msg}") from exc

def execute_one(self, sql, parameters=()):
"""Execute a statement and return the one row that results.
Expand Down
4 changes: 2 additions & 2 deletions coverage/templite.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,10 +288,10 @@ def _do_dots(self, value, *dots):
except AttributeError:
try:
value = value[dot]
except (TypeError, KeyError):
except (TypeError, KeyError) as exc:
raise TempliteValueError(
f"Couldn't evaluate {value!r}.{dot}"
)
) from exc
if callable(value):
value = value()
return value
8 changes: 4 additions & 4 deletions coverage/tomlconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def read(self, filenames):
try:
self.data = tomli.loads(toml_text)
except tomli.TOMLDecodeError as err:
raise TomlDecodeError(str(err))
raise TomlDecodeError(str(err)) from err
return [filename]
else:
has_toml = re.search(r"^\[tool\.coverage\.", toml_text, flags=re.MULTILINE)
Expand Down Expand Up @@ -95,8 +95,8 @@ def _get(self, section, option):
raise configparser.NoSectionError(section)
try:
return name, data[option]
except KeyError:
raise configparser.NoOptionError(option, name)
except KeyError as exc:
raise configparser.NoOptionError(option, name) from exc

def has_option(self, section, option):
_, data = self._get_section(section)
Expand Down Expand Up @@ -149,7 +149,7 @@ def getregexlist(self, section, option):
except re.error as e:
raise CoverageException(
f"Invalid [{name}].{option} value {value!r}: {e}"
)
) from e
return values

def getint(self, section, option):
Expand Down
10 changes: 5 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,21 +161,21 @@ def run(self):
"""Wrap `run` with `BuildFailed`."""
try:
build_ext.run(self)
except errors.DistutilsPlatformError:
raise BuildFailed()
except errors.DistutilsPlatformError as exc:
raise BuildFailed() from exc

def build_extension(self, ext):
"""Wrap `build_extension` with `BuildFailed`."""
try:
# Uncomment to test compile failure handling:
# raise errors.CCompilerError("OOPS")
build_ext.build_extension(self, ext)
except ext_errors:
raise BuildFailed()
except ext_errors as exc:
raise BuildFailed() from exc
except ValueError as err:
# this can happen on Windows 64 bit, see Python issue 7511
if "'path'" in str(err): # works with both py 2/3
raise BuildFailed()
raise BuildFailed() from err
raise

# There are a few reasons we might not be able to compile the C extension.
Expand Down

0 comments on commit 5313297

Please sign in to comment.