diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2021-07-20 06:54:22 -0400 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2021-07-20 05:32:28 -0700 |
commit | 5313297fe84c596f9222a4890dd45a53a6d4d632 (patch) | |
tree | ceacf73477e129786a8566fff146c18cd57c0edf | |
parent | de38a0e74a8683a3d3381038aeee4d226cc5b714 (diff) | |
download | python-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.py | 8 | ||||
-rw-r--r-- | coverage/config.py | 6 | ||||
-rw-r--r-- | coverage/execfile.py | 16 | ||||
-rw-r--r-- | coverage/parser.py | 8 | ||||
-rw-r--r-- | coverage/sqldata.py | 4 | ||||
-rw-r--r-- | coverage/templite.py | 4 | ||||
-rw-r--r-- | coverage/tomlconfig.py | 8 | ||||
-rw-r--r-- | setup.py | 10 |
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): @@ -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. |