diff --git a/gui/wxpython/animation/data.py b/gui/wxpython/animation/data.py index a627ce00865..6ae0cf1f37c 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 cd3f63ce86c..adf67a4aaf0 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 e0758a482d6..71c5a9b2f1e 100644 --- a/gui/wxpython/core/settings.py +++ b/gui/wxpython/core/settings.py @@ -992,12 +992,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): @@ -1099,10 +1099,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 3ec6f45541f..32948d8d00d 100644 --- a/gui/wxpython/core/utils.py +++ b/gui/wxpython/core/utils.py @@ -476,8 +476,8 @@ def __ll_parts(value, reverse=False, precision=3): d = d[:-1] m = "0" s = "0.0" - except ValueError: - raise ValueError + except ValueError as err: + raise ValueError from err 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 d743a74e3c7..2d1f5e1c457 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) @@ -1559,7 +1559,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 "'" -> "''" @@ -1677,14 +1677,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 1d75f3869df..bef687f70e8 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 4e0b3b93857..ee5c46c96db 100644 --- a/gui/wxpython/gui_core/forms.py +++ b/gui/wxpython/gui_core/forms.py @@ -3080,7 +3080,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: @@ -3110,7 +3110,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 @@ -3118,7 +3118,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 753736f60db..40edcd0a82e 100644 --- a/gui/wxpython/gui_core/toolbars.py +++ b/gui/wxpython/gui_core/toolbars.py @@ -224,11 +224,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/iscatt/plots.py b/gui/wxpython/iscatt/plots.py index 2f9354b3202..c7bc4388677 100644 --- a/gui/wxpython/iscatt/plots.py +++ b/gui/wxpython/iscatt/plots.py @@ -45,7 +45,7 @@ 'The Scatterplot Tool needs the "matplotlib" ' "(python-matplotlib) package to be installed. {0}" ).format(e) - ) + ) from e import grass.script as gs from grass.pydispatch.signal import Signal diff --git a/gui/wxpython/mapdisp/main.py b/gui/wxpython/mapdisp/main.py index 5554338e8e7..2dad0eab733 100644 --- a/gui/wxpython/mapdisp/main.py +++ b/gui/wxpython/mapdisp/main.py @@ -364,7 +364,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/timeline/frame.py b/gui/wxpython/timeline/frame.py index e1004ce35d4..9dc36c38e8a 100644 --- a/gui/wxpython/timeline/frame.py +++ b/gui/wxpython/timeline/frame.py @@ -44,7 +44,7 @@ "(python-matplotlib and on some systems also python-matplotlib-wx) " "package(s) to be installed. {}" ).format(e) - ) + ) from e import grass.script as gs diff --git a/gui/wxpython/tplot/frame.py b/gui/wxpython/tplot/frame.py index adaed601ebc..422720646e1 100755 --- a/gui/wxpython/tplot/frame.py +++ b/gui/wxpython/tplot/frame.py @@ -46,9 +46,9 @@ raise ImportError( _( 'The Temporal Plot Tool needs the "matplotlib" ' - "(python-matplotlib) package to be installed. {0}" - ).format(e) - ) + "(python-matplotlib) package to be installed." + ) + ) from e import grass.temporal as tgis diff --git a/gui/wxpython/wxgui.py b/gui/wxpython/wxgui.py index 83fed965ecd..fd2b71bc246 100644 --- a/gui/wxpython/wxgui.py +++ b/gui/wxpython/wxgui.py @@ -151,7 +151,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 6697e4c9051..aac8bb4a7a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -118,7 +118,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 5a383072478..bcaee9443c7 100644 --- a/python/grass/gunittest/case.py +++ b/python/grass/gunittest/case.py @@ -1341,7 +1341,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/gunittest/loader.py b/python/grass/gunittest/loader.py index b59e607a341..6c413a97220 100644 --- a/python/grass/gunittest/loader.py +++ b/python/grass/gunittest/loader.py @@ -180,7 +180,7 @@ def discover_modules( raise ImportError( "Cannot import module named %s in %s (%s)" % (name, full, e.message) - ) + ) from e # alternative is to create TestClass which will raise # see unittest.loader if add: diff --git a/python/grass/imaging/images2avi.py b/python/grass/imaging/images2avi.py index 0e98bddbc18..7b50f0c4fb1 100644 --- a/python/grass/imaging/images2avi.py +++ b/python/grass/imaging/images2avi.py @@ -96,8 +96,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 773007141a6..02bef6939c0 100644 --- a/python/grass/pygrass/vector/table.py +++ b/python/grass/pygrass/vector/table.py @@ -858,10 +858,10 @@ def connection(self): return psycopg2.connect(db) except ImportError: er = "You need to install psycopg2 to connect with this table." - raise ImportError(er) + raise ImportError(er) from None str_err = "Driver is not supported yet, pleas use: sqlite or pg" - raise TypeError(str_err) + raise TypeError(str_err) from None def table(self): """Return a Table object. @@ -1209,7 +1209,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 ebcd5e9febf..e41274e803d 100644 --- a/python/grass/script/core.py +++ b/python/grass/script/core.py @@ -1034,9 +1034,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) @@ -1044,7 +1044,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_"): @@ -2111,7 +2111,7 @@ def _set_location_description(path, location, text): else: fd.write(os.linesep) except OSError as e: - raise ScriptError(repr(e)) + raise ScriptError(repr(e)) from e def _create_location_xy(database, location): @@ -2155,7 +2155,7 @@ def _create_location_xy(database, location): default_wind_path.write_text("\n".join(regioninfo)) shutil.copy(default_wind_path, wind_path) 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 5483a858fbd..d8ad363e06a 100644 --- a/python/grass/script/task.py +++ b/python/grass/script/task.py @@ -487,7 +487,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( @@ -518,7 +518,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/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 ade1ac9411c..eb6b1c51ae6 100644 --- a/python/grass/utils/download.py +++ b/python/grass/utils/download.py @@ -112,7 +112,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 @@ -172,9 +172,9 @@ def download_and_extract(source, reporthook=None): url=source, code=err, ), - ) - except URLError: - raise DownloadError(url_error_message.format(url=source)) + ) from err + except URLError as e: + raise DownloadError(url_error_message.format(url=source)) from e if not re.search( reponse_content_type_header_pattern, headers.get("content-type", "") @@ -199,9 +199,9 @@ def download_and_extract(source, reporthook=None): url=source, code=err, ), - ) - except URLError: - raise DownloadError(url_error_message.format(url=source)) + ) from err + except URLError as e: + raise DownloadError(url_error_message.format(url=source)) from e extract_tar(name=archive_name, directory=directory, tmpdir=tmpdir) else: # probably programmer error diff --git a/scripts/r.in.wms/wms_cap_parsers.py b/scripts/r.in.wms/wms_cap_parsers.py index c54ede9f49e..fc774f1077f 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."))