diff --git a/gui/wxpython/animation/data.py b/gui/wxpython/animation/data.py index 0d828a8b3c7..fb9b97c569b 100644 --- a/gui/wxpython/animation/data.py +++ b/gui/wxpython/animation/data.py @@ -301,7 +301,7 @@ def SetName(self, name): name = validateTimeseriesName(name, self._mapType) self._maps = getRegisteredMaps(name, self._mapType) except (GException, ScriptError) as e: - raise ValueError(str(e)) + raise ValueError(str(e)) from e else: self._maps = validateMapNames(name.split(","), self._mapType) self._name = name diff --git a/gui/wxpython/animation/nviztask.py b/gui/wxpython/animation/nviztask.py index b9e209d5855..1821b681689 100644 --- a/gui/wxpython/animation/nviztask.py +++ b/gui/wxpython/animation/nviztask.py @@ -34,14 +34,14 @@ def Load(self, filename): self.filename = filename try: gxwXml = ProcessWorkspaceFile(ET.parse(self.filename)) - except Exception: + except Exception as e: raise GException( _( "Reading workspace file <%s> failed.\n" "Invalid file, unable to parse XML document." ) % filename - ) + ) from e # for display in gxwXml.displays: # pprint(display) # for layer in gxwXml.layers: diff --git a/gui/wxpython/core/settings.py b/gui/wxpython/core/settings.py index 3fe6e8aec8f..060a3df92be 100644 --- a/gui/wxpython/core/settings.py +++ b/gui/wxpython/core/settings.py @@ -1002,12 +1002,12 @@ def SaveToFile(self, settings=None): with open(self.filePath, "w") as f: json.dump(settings, f, indent=2, cls=SettingsJSONEncoder) except OSError as e: - raise GException(e) + raise GException(e) from e except Exception as e: raise GException( _("Writing settings to file <%(file)s> failed.\n\nDetails: %(detail)s") % {"file": self.filePath, "detail": e} - ) + ) from e return self.filePath def _parseValue(self, value, read=False): @@ -1109,10 +1109,10 @@ def Set(self, group, value, key=None, subkey=None, settings_type="user"): settings[group][key][subkey] = value return - except KeyError: + except KeyError as e: raise GException( "%s '%s:%s:%s'" % (_("Unable to set "), group, key, subkey) - ) + ) from e def Append(self, dict, group, key, subkey, value, overwrite=True): """Set value of key/subkey diff --git a/gui/wxpython/core/utils.py b/gui/wxpython/core/utils.py index 127694280c0..6bf496ecbd9 100644 --- a/gui/wxpython/core/utils.py +++ b/gui/wxpython/core/utils.py @@ -471,14 +471,13 @@ def __ll_parts(value, reverse=False, precision=3): m = m[:-1] s = "0.0" except ValueError: - try: - d = value - hs = d[-1] - d = d[:-1] - m = "0" - s = "0.0" - except ValueError: - raise ValueError + # Value without minutes and seconds. Errors are passed to the caller + # as they are. + d = value + hs = d[-1] + d = d[:-1] + m = "0" + s = "0.0" if hs not in {"N", "S", "E", "W"}: raise ValueError diff --git a/gui/wxpython/dbmgr/base.py b/gui/wxpython/dbmgr/base.py index 1887a20f13a..51c31bcdab3 100644 --- a/gui/wxpython/dbmgr/base.py +++ b/gui/wxpython/dbmgr/base.py @@ -179,7 +179,7 @@ def LoadData(self, layer, columns=None, where=None, sql=None): keyColumn = self.mapDBInfo.layers[layer]["key"] try: self.columns = self.mapDBInfo.tables[tableName] - except KeyError: + except KeyError as err: raise GException( _( "Attribute table <%s> not found. " @@ -187,7 +187,7 @@ def LoadData(self, layer, columns=None, where=None, sql=None): "'Manage layers' tab." ) % tableName - ) + ) from err if not columns: columns = self.mapDBInfo.GetColumns(tableName) @@ -1568,7 +1568,7 @@ def OnDataItemEdit(self, event): raise ValueError( _("Value '%(value)s' needs to be entered as %(type)s.") % {"value": str(values[i]), "type": column["type"]} - ) + ) from None if column["ctype"] == str: if "'" in values[i]: # replace "'" -> "''" @@ -1686,14 +1686,14 @@ def OnDataItemAdd(self, event): "value": values[i], "type": tlist.columns[columnName[i]]["type"], } - ) + ) from None except KeyError: raise KeyError( _("Column '%(column)s' does not exist.") % { "column": columnName[i], } - ) + ) from None columnsString += "%s," % columnName[i] if tlist.columns[columnName[i]]["ctype"] == str: diff --git a/gui/wxpython/gmodeler/model.py b/gui/wxpython/gmodeler/model.py index f8e3525cac6..ca06204b0b7 100644 --- a/gui/wxpython/gmodeler/model.py +++ b/gui/wxpython/gmodeler/model.py @@ -311,7 +311,7 @@ def LoadModel(self, filename): gxmXml = ProcessModelFile(ET.parse(filename)) except Exception as e: msg = "{}".format(e) - raise GException(msg) + raise GException(msg) from e if self.canvas: win = self.canvas.parent diff --git a/gui/wxpython/gui_core/forms.py b/gui/wxpython/gui_core/forms.py index 9c45336aa3b..4c0493ad16a 100644 --- a/gui/wxpython/gui_core/forms.py +++ b/gui/wxpython/gui_core/forms.py @@ -3461,7 +3461,7 @@ def ParseCommand(self, cmd, completed=None): global _blackList self.grass_task = gtask.parse_interface(cmd[0], blackList=_blackList) except (ScriptError, ValueError) as e: - raise gcmd.GException(e.value) + raise gcmd.GException(e.value) from e # if layer parameters previously set, re-insert them into dialog if completed is not None: @@ -3491,7 +3491,7 @@ def ParseCommand(self, cmd, completed=None): # parameter try: key, value = option.split("=", 1) - except ValueError: + except ValueError as e: if self.grass_task.firstParam: if i == 0: # add key name of first parameter if not given key = self.grass_task.firstParam @@ -3499,7 +3499,7 @@ def ParseCommand(self, cmd, completed=None): else: raise gcmd.GException( _("Unable to parse command '%s'") % " ".join(cmd) - ) + ) from e else: continue diff --git a/gui/wxpython/gui_core/toolbars.py b/gui/wxpython/gui_core/toolbars.py index 939727f2c25..fb6ddb880ae 100644 --- a/gui/wxpython/gui_core/toolbars.py +++ b/gui/wxpython/gui_core/toolbars.py @@ -225,11 +225,10 @@ def Enable(self, tool, enable=True): id = getattr(self.widget, tool[0]) else: id = getattr(self.widget, tool) - except AttributeError: + except AttributeError as e: # TODO: test everything that this is not raised # this error was ignored for a long time - raise AttributeError("Toolbar does not have a tool %s." % tool) - return + raise AttributeError("Toolbar does not have a tool %s." % tool) from e self.classObject.EnableTool(self.widget, id, enable) diff --git a/gui/wxpython/mapdisp/main.py b/gui/wxpython/mapdisp/main.py index 18d654838eb..cff93ef02b0 100644 --- a/gui/wxpython/mapdisp/main.py +++ b/gui/wxpython/mapdisp/main.py @@ -365,7 +365,7 @@ def __next__(self): try: result = items[self._index] except IndexError: - raise StopIteration + raise StopIteration from None self._index += 1 return result diff --git a/gui/wxpython/rdigit/controller.py b/gui/wxpython/rdigit/controller.py index 9617e1d5178..047129713e0 100644 --- a/gui/wxpython/rdigit/controller.py +++ b/gui/wxpython/rdigit/controller.py @@ -467,8 +467,8 @@ def _createNewMap(self, mapName, backgroundMap, mapType): ).strip() if values: self.uploadMapCategories.emit(values=values.split("\n")) - except CalledModuleError: - raise ScriptError + except CalledModuleError as e: + raise ScriptError(e.msg) from e self._backupRaster(name) name = name + "@" + gcore.gisenv()["MAPSET"] @@ -494,8 +494,8 @@ def _backupRaster(self, name): backup = name + "_backupcopy_" + str(os.getpid()) try: gcore.run_command("g.copy", raster=[name, backup], quiet=True) - except CalledModuleError: - raise ScriptError + except CalledModuleError as e: + raise ScriptError(e.msg) from e self._backupRasterName = backup diff --git a/gui/wxpython/wxgui.py b/gui/wxpython/wxgui.py index 05212146fd2..d80756994e3 100644 --- a/gui/wxpython/wxgui.py +++ b/gui/wxpython/wxgui.py @@ -150,7 +150,7 @@ def main(argv=None): try: opts, args = getopt.getopt(argv[1:], "hw:", ["help", "workspace"]) except getopt.error as msg: - raise Usage(msg) + raise Usage(msg) from None except Usage as err: print(err.msg, file=sys.stderr) print(sys.stderr, "for help use --help", file=sys.stderr) diff --git a/pyproject.toml b/pyproject.toml index 7950905f39a..fdc6dcb7a31 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -174,7 +174,6 @@ ignore = [ "B026", # star-arg-unpacking-after-keyword-arg "B028", # no-explicit-stacklevel "B034", # re-sub-positional-args - "B904", # raise-without-from-inside-except "B909", # loop-iterator-mutation "BLE001", # blind-except "C414", # unnecessary-double-cast-or-process diff --git a/python/grass/gunittest/case.py b/python/grass/gunittest/case.py index 92bc38f8237..1b7a72c1c27 100644 --- a/python/grass/gunittest/case.py +++ b/python/grass/gunittest/case.py @@ -1336,7 +1336,7 @@ def runModule(cls, module, expecting_stdout=False, **kwargs): # TODO: message format, parameters raise CalledModuleError( module.name, module.get_python(), module.returncode, errors=errors - ) + ) from None # TODO: use this also in assert and apply when appropriate if expecting_stdout and (not module.outputs.stdout.strip()): if module.outputs.stderr: diff --git a/python/grass/imaging/images2avi.py b/python/grass/imaging/images2avi.py index ab5f1e7d27b..f341ab093a4 100644 --- a/python/grass/imaging/images2avi.py +++ b/python/grass/imaging/images2avi.py @@ -98,8 +98,8 @@ def writeAvi( # Get fps try: fps = float(1.0 / duration) - except Exception: - raise ValueError(_("Invalid duration parameter for writeAvi.")) + except Exception as e: + raise ValueError(_("Invalid duration parameter for writeAvi.")) from e # Determine temp dir and create images tempDir = os.path.join(os.path.expanduser("~"), ".tempIms") diff --git a/python/grass/pydispatch/dispatcher.py b/python/grass/pydispatch/dispatcher.py index 1018b5e9a6c..dd544765e85 100644 --- a/python/grass/pydispatch/dispatcher.py +++ b/python/grass/pydispatch/dispatcher.py @@ -213,18 +213,18 @@ def disconnect(receiver, signal=Any, sender=Any, weak=True): try: signals = connections[senderkey] receivers = signals[signal] - except KeyError: + except KeyError as e: raise errors.DispatcherKeyError( """No receivers found for signal %r from sender %r""" % (signal, sender) - ) + ) from e try: # also removes from receivers _removeOldBackRefs(senderkey, signal, receiver, receivers) - except ValueError: + except ValueError as e: raise errors.DispatcherKeyError( """No connection to receiver %s for signal %s from sender %s""" % (receiver, signal, sender) - ) + ) from e _cleanupConnections(senderkey, signal) diff --git a/python/grass/pygrass/raster/category.py b/python/grass/pygrass/raster/category.py index c2c0869cd4e..43554df36a0 100644 --- a/python/grass/pygrass/raster/category.py +++ b/python/grass/pygrass/raster/category.py @@ -110,8 +110,8 @@ def _chk_index(self, index): if isinstance(index, str): try: index = self.labels().index(index) - except ValueError: - raise KeyError(index) + except ValueError as error: + raise KeyError(index) from error return index def _chk_value(self, value): diff --git a/python/grass/pygrass/rpc/base.py b/python/grass/pygrass/rpc/base.py index a19f79f355e..765125ad3bd 100644 --- a/python/grass/pygrass/rpc/base.py +++ b/python/grass/pygrass/rpc/base.py @@ -186,7 +186,9 @@ def safe_receive(self, message): except (EOFError, OSError, FatalError) as e: # The pipe was closed by the checker thread because # the server process was killed - raise FatalError("Exception raised: " + str(e) + " Message: " + message) + raise FatalError( + "Exception raised: " + str(e) + " Message: " + message + ) from e def stop(self): """Stop the check thread, the libgis server and close the pipe diff --git a/python/grass/pygrass/vector/table.py b/python/grass/pygrass/vector/table.py index 637ee8b74f5..1dae4ffcab5 100644 --- a/python/grass/pygrass/vector/table.py +++ b/python/grass/pygrass/vector/table.py @@ -1211,7 +1211,7 @@ def execute(self, sql_code=None, cursor=None, many=False, values=None): "The SQL statement is not correct:\n%r,\n" "values: %r,\n" "SQL error: %s" % (sqlc, values, str(exc)) - ) + ) from exc def exist(self, cursor=None): """Return True if the table already exists in the DB, False otherwise diff --git a/python/grass/script/core.py b/python/grass/script/core.py index 98556a674a4..32a269f8077 100644 --- a/python/grass/script/core.py +++ b/python/grass/script/core.py @@ -1035,9 +1035,9 @@ def _parse_opts(lines: list) -> tuple[dict[str, str], dict[str, bool]]: break try: var, val = line.split(b"=", 1) - except ValueError: + except ValueError as err: msg = "invalid output from g.parser: {}".format(line) - raise SyntaxError(msg) + raise SyntaxError(msg) from err try: var = decode(var) val = decode(val) @@ -1045,7 +1045,7 @@ def _parse_opts(lines: list) -> tuple[dict[str, str], dict[str, bool]]: msg = "invalid output from g.parser ({error}): {line}".format( error=error, line=line ) - raise SyntaxError(msg) + raise SyntaxError(msg) from error if var.startswith("flag_"): flags[var[5:]] = bool(int(val)) elif var.startswith("opt_"): @@ -2256,7 +2256,7 @@ def _set_location_description(path, location, text): try: _set_project_description(Path(path) / location, text) except OSError as e: - raise ScriptError(repr(e)) + raise ScriptError(repr(e)) from e def _create_location_xy(database, location): @@ -2273,7 +2273,7 @@ def _create_location_xy(database, location): try: create_xy_project(Path(database) / location) except OSError as e: - raise ScriptError(repr(e)) + raise ScriptError(repr(e)) from e # interface to g.version diff --git a/python/grass/script/task.py b/python/grass/script/task.py index 2508954d9d6..bf2677807ca 100644 --- a/python/grass/script/task.py +++ b/python/grass/script/task.py @@ -489,7 +489,7 @@ def get_interface_description(cmd): "Unable to fetch interface description for command '<{cmd}>'." "\n\nDetails: <{det}>" ).format(cmd=cmd, det=e) - ) + ) from e desc = convert_xml_to_utf8(cmdout) return desc.replace( @@ -520,7 +520,7 @@ def parse_interface(name, parser=processTask, blackList=None): _("Cannot parse interface description of <{name}> module: {error}").format( name=name, error=error ) - ) + ) from error task = parser(tree, blackList=blackList).get_task() # if name from interface is different than the originally # provided name, then the provided name is likely a full path needed diff --git a/python/grass/script/utils.py b/python/grass/script/utils.py index f2538ce7df9..5383f03a53d 100644 --- a/python/grass/script/utils.py +++ b/python/grass/script/utils.py @@ -209,7 +209,7 @@ def __getattr__(self, key: str) -> VT: try: return self[key] except KeyError: - raise AttributeError(key) + raise AttributeError(key) from None def __setattr__(self, key: str, value: VT) -> None: self[key] = value diff --git a/python/grass/script/vector.py b/python/grass/script/vector.py index 431d1ffe4aa..7ab543130b0 100644 --- a/python/grass/script/vector.py +++ b/python/grass/script/vector.py @@ -437,7 +437,7 @@ def vector_what( try: ret = read_command("v.what", env=env, **cmdParams).strip() except CalledModuleError as e: - raise ScriptError(e.msg) + raise ScriptError(e.msg) from e data = [] if not ret: @@ -460,10 +460,10 @@ def vector_what( try: result = json.loads(ret, **kwargs) - except ValueError: + except ValueError as err: raise ScriptError( _("v.what output is not valid JSON format:\n {ret}").format(ret=ret) - ) + ) from err if multiple: for vmap in result["Maps"]: diff --git a/python/grass/semantic_label/reader.py b/python/grass/semantic_label/reader.py index d6538e5c940..6dc7c5eae27 100644 --- a/python/grass/semantic_label/reader.py +++ b/python/grass/semantic_label/reader.py @@ -39,7 +39,7 @@ def _read_config(self): config = json.load(fd, object_pairs_hook=OrderedDict) except json.decoder.JSONDecodeError as e: msg = "Unable to parse '{}': {}".format(json_file, e) - raise SemanticLabelReaderError(msg) + raise SemanticLabelReaderError(msg) from e # check if configuration is valid self._check_config(config) @@ -112,7 +112,7 @@ def print_info(self, shortcut=None, band=None, semantic_label=None, extended=Fal continue except re.error as e: msg = "Invalid pattern: {}".format(e) - raise SemanticLabelReaderError(msg) + raise SemanticLabelReaderError(msg) from e found = True if band and band not in item["bands"]: diff --git a/python/grass/utils/download.py b/python/grass/utils/download.py index 4819ff4cb7b..8b877b85e80 100644 --- a/python/grass/utils/download.py +++ b/python/grass/utils/download.py @@ -113,7 +113,7 @@ def extract_zip(name, directory, tmpdir): extract_dir=extract_dir, target_dir=directory, files=files ) except zipfile.BadZipfile as error: - raise DownloadError(_("ZIP file is unreadable: {0}").format(error)) + raise DownloadError(_("ZIP file is unreadable: {0}").format(error)) from error # modified copy from g.extension diff --git a/scripts/r.in.wms/wms_cap_parsers.py b/scripts/r.in.wms/wms_cap_parsers.py index d832cfce31c..c18d230382f 100644 --- a/scripts/r.in.wms/wms_cap_parsers.py +++ b/scripts/r.in.wms/wms_cap_parsers.py @@ -41,17 +41,17 @@ def __init__(self, cap_file): if is_file: try: ET.ElementTree.__init__(self, file=cap_file) - except ParseError: - raise ParseError(_("Unable to parse XML file")) + except ParseError as pe: + raise ParseError(_("Unable to parse XML file")) from pe except OSError as error: raise ParseError( _("Unable to open XML file '%s'.\n%s\n") % (cap_file, error) - ) + ) from error else: try: ET.ElementTree.__init__(self, element=ET.fromstring(cap_file)) - except ParseError: - raise ParseError(_("Unable to parse XML file")) + except ParseError as pe: + raise ParseError(_("Unable to parse XML file")) from pe if self.getroot() is None: raise ParseError(_("Root node was not found."))