summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2021-07-20 06:54:22 -0400
committerNed Batchelder <ned@nedbatchelder.com>2021-07-20 05:32:28 -0700
commit5313297fe84c596f9222a4890dd45a53a6d4d632 (patch)
treeceacf73477e129786a8566fff146c18cd57c0edf
parentde38a0e74a8683a3d3381038aeee4d226cc5b714 (diff)
downloadpython-coveragepy-git-5313297fe84c596f9222a4890dd45a53a6d4d632.tar.gz
fix: raise chained errors with "from" #998
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."
-rw-r--r--coverage/collector.py8
-rw-r--r--coverage/config.py6
-rw-r--r--coverage/execfile.py16
-rw-r--r--coverage/parser.py8
-rw-r--r--coverage/sqldata.py4
-rw-r--r--coverage/templite.py4
-rw-r--r--coverage/tomlconfig.py8
-rw-r--r--setup.py10
8 files changed, 31 insertions, 33 deletions
diff --git a/coverage/collector.py b/coverage/collector.py
index f9e9d14f..73babf44 100644
--- a/coverage/collector.py
+++ b/coverage/collector.py
@@ -137,12 +137,12 @@ class Collector:
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()
@@ -318,8 +318,8 @@ class Collector:
(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.
diff --git a/coverage/config.py b/coverage/config.py
index 44bae957..7287e963 100644
--- a/coverage/config.py
+++ b/coverage/config.py
@@ -125,7 +125,7 @@ class HandyConfigParser(configparser.RawConfigParser):
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
@@ -272,7 +272,7 @@ class CoverageConfig:
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
@@ -285,7 +285,7 @@ class CoverageConfig:
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)
diff --git a/coverage/execfile.py b/coverage/execfile.py
index 2a3776bf..66020019 100644
--- a/coverage/execfile.py
+++ b/coverage/execfile.py
@@ -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
@@ -193,7 +193,7 @@ class PyRunner:
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
@@ -226,7 +226,7 @@ class PyRunner:
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")
@@ -236,7 +236,7 @@ class PyRunner:
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:
@@ -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
@@ -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
diff --git a/coverage/parser.py b/coverage/parser.py
index abaa2e50..ed049685 100644
--- a/coverage/parser.py
+++ b/coverage/parser.py
@@ -41,9 +41,7 @@ class PythonParser:
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
@@ -243,7 +241,7 @@ class PythonParser:
"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)
@@ -363,7 +361,7 @@ class ByteParser:
"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.
diff --git a/coverage/sqldata.py b/coverage/sqldata.py
index 41542969..b2133026 100644
--- a/coverage/sqldata.py
+++ b/coverage/sqldata.py
@@ -291,7 +291,7 @@ class CoverageData(SimpleReprMixin):
"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(
@@ -1095,7 +1095,7 @@ class SqliteDb(SimpleReprMixin):
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.
diff --git a/coverage/templite.py b/coverage/templite.py
index 2ceeb6e2..ab3cf1cf 100644
--- a/coverage/templite.py
+++ b/coverage/templite.py
@@ -288,10 +288,10 @@ class Templite:
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
diff --git a/coverage/tomlconfig.py b/coverage/tomlconfig.py
index aa11a8a9..8212cfe6 100644
--- a/coverage/tomlconfig.py
+++ b/coverage/tomlconfig.py
@@ -49,7 +49,7 @@ class TomlConfigParser:
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)
@@ -95,8 +95,8 @@ class TomlConfigParser:
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)
@@ -149,7 +149,7 @@ class TomlConfigParser:
except re.error as e:
raise CoverageException(
f"Invalid [{name}].{option} value {value!r}: {e}"
- )
+ ) from e
return values
def getint(self, section, option):
diff --git a/setup.py b/setup.py
index f6fb7b4e..1c540764 100644
--- a/setup.py
+++ b/setup.py
@@ -161,8 +161,8 @@ class ve_build_ext(build_ext):
"""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`."""
@@ -170,12 +170,12 @@ class ve_build_ext(build_ext):
# 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.