From d1f0e0e23bf62c1a29698f9a83003007e1e784dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Mon, 8 Jul 2024 03:10:58 +0000 Subject: [PATCH 1/8] style: Fix raise-without-from-inside-except (B904) --- gui/wxpython/iscatt/plots.py | 2 +- gui/wxpython/timeline/frame.py | 2 +- gui/wxpython/tplot/frame.py | 2 +- python/grass/pygrass/modules/interface/module.py | 2 +- python/grass/pygrass/raster/category.py | 4 ++-- python/grass/pygrass/rpc/base.py | 4 +++- python/grass/pygrass/vector/table.py | 6 +++--- python/grass/script/core.py | 10 +++++----- python/grass/script/task.py | 4 ++-- python/grass/script/vector.py | 6 +++--- python/grass/semantic_label/reader.py | 6 ++++-- python/grass/utils/download.py | 14 +++++++------- scripts/r.in.wms/wms_cap_parsers.py | 10 +++++----- 13 files changed, 38 insertions(+), 34 deletions(-) diff --git a/gui/wxpython/iscatt/plots.py b/gui/wxpython/iscatt/plots.py index f9cad136314..e2e3d632a36 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/timeline/frame.py b/gui/wxpython/timeline/frame.py index 0330adbde3d..e160b77b44a 100644 --- a/gui/wxpython/timeline/frame.py +++ b/gui/wxpython/timeline/frame.py @@ -43,7 +43,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 4a1cd9f1ca6..beaa48c663c 100755 --- a/gui/wxpython/tplot/frame.py +++ b/gui/wxpython/tplot/frame.py @@ -46,7 +46,7 @@ 'The Temporal Plot Tool needs the "matplotlib" ' "(python-matplotlib) package to be installed. {0}" ).format(e) - ) + ) from e import grass.temporal as tgis diff --git a/python/grass/pygrass/modules/interface/module.py b/python/grass/pygrass/modules/interface/module.py index b9c298b4a6f..274b663e7af 100644 --- a/python/grass/pygrass/modules/interface/module.py +++ b/python/grass/pygrass/modules/interface/module.py @@ -555,7 +555,7 @@ def __init__(self, cmd, *args, **kargs): except OSError as e: print("OSError error({0}): {1}".format(e.errno, e.strerror)) str_err = "Error running: `%s --interface-description`." - raise GrassError(str_err % self.name) + raise GrassError(str_err % self.name) from e # get the xml of the module self.xml = get_cmd_xml.communicate()[0] # transform and parse the xml into an Element class: diff --git a/python/grass/pygrass/raster/category.py b/python/grass/pygrass/raster/category.py index 2cee672ed32..1480dda21e6 100644 --- a/python/grass/pygrass/raster/category.py +++ b/python/grass/pygrass/raster/category.py @@ -109,8 +109,8 @@ def _chk_index(self, index): if type(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 38cf48c1581..133cbbc2205 100644 --- a/python/grass/pygrass/rpc/base.py +++ b/python/grass/pygrass/rpc/base.py @@ -173,7 +173,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 dec3ffbba08..54ff27b4025 100644 --- a/python/grass/pygrass/vector/table.py +++ b/python/grass/pygrass/vector/table.py @@ -859,10 +859,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 else: 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. @@ -1204,7 +1204,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 exist in the DB, False otherwise diff --git a/python/grass/script/core.py b/python/grass/script/core.py index b22815bafb6..1c507ab91c8 100644 --- a/python/grass/script/core.py +++ b/python/grass/script/core.py @@ -867,8 +867,8 @@ def _parse_opts(lines): break try: var, val = line.split(b"=", 1) - except ValueError: - raise SyntaxError("invalid output from g.parser: {}".format(line)) + except ValueError as err: + raise SyntaxError("invalid output from g.parser: {}".format(line)) from err try: var = decode(var) val = decode(val) @@ -877,7 +877,7 @@ def _parse_opts(lines): "invalid output from g.parser ({error}): {line}".format( error=error, line=line ) - ) + ) from error if var.startswith("flag_"): flags[var[5:]] = bool(int(val)) elif var.startswith("opt_"): @@ -1888,7 +1888,7 @@ def _set_location_description(path, location, text): fd.write(os.linesep) fd.close() except OSError as e: - raise ScriptError(repr(e)) + raise ScriptError(repr(e)) from e def _create_location_xy(database, location): @@ -1939,7 +1939,7 @@ def _create_location_xy(database, location): os.chdir(cur_dir) 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 9ab76eedcb8..c9efe248e8e 100644 --- a/python/grass/script/task.py +++ b/python/grass/script/task.py @@ -502,7 +502,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( @@ -530,7 +530,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 4a57b5db07b..006e54c030b 100644 --- a/python/grass/script/vector.py +++ b/python/grass/script/vector.py @@ -423,7 +423,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: @@ -450,10 +450,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 1da67aff7f9..ecd152271c2 100644 --- a/python/grass/semantic_label/reader.py +++ b/python/grass/semantic_label/reader.py @@ -40,7 +40,7 @@ def _read_config(self): except json.decoder.JSONDecodeError as e: raise SemanticLabelReaderError( "Unable to parse '{}': {}".format(json_file, e) - ) + ) from e # check if configuration is valid self._check_config(config) @@ -116,7 +116,9 @@ def print_info(self, shortcut=None, band=None, semantic_label=None, extended=Fal if shortcut and re.match(shortcut, item["shortcut"]) is None: continue except re.error as e: - raise SemanticLabelReaderError("Invalid pattern: {}".format(e)) + raise SemanticLabelReaderError( + "Invalid pattern: {}".format(e) + ) 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 f6b734b7291..46e6d75527f 100644 --- a/python/grass/utils/download.py +++ b/python/grass/utils/download.py @@ -107,7 +107,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 @@ -167,9 +167,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 headers.get("content-type", "") != "application/zip": raise DownloadError( _( @@ -188,9 +188,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 5435fe29591..9467bb9c297 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: etree.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: etree.ElementTree.__init__(self, element=etree.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.")) From 7f0043dd227a0577e1409709808ea226220fbacf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Sat, 13 Jul 2024 11:00:50 -0400 Subject: [PATCH 2/8] Update frame.py --- gui/wxpython/tplot/frame.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/wxpython/tplot/frame.py b/gui/wxpython/tplot/frame.py index beaa48c663c..a3e26f08ff0 100755 --- a/gui/wxpython/tplot/frame.py +++ b/gui/wxpython/tplot/frame.py @@ -44,8 +44,8 @@ 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 From a161aac5e5d2fd9472761b73e3d8b39885c54921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Sun, 30 Mar 2025 21:43:10 +0000 Subject: [PATCH 3/8] python: Fix raise-without-from-inside-except (B904) Rule contents: Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling --- gui/wxpython/animation/data.py | 2 +- gui/wxpython/animation/nviztask.py | 4 ++-- gui/wxpython/core/settings.py | 8 ++++---- gui/wxpython/core/utils.py | 4 ++-- gui/wxpython/dbmgr/base.py | 10 +++++----- gui/wxpython/gmodeler/model.py | 2 +- gui/wxpython/gui_core/forms.py | 6 +++--- gui/wxpython/gui_core/toolbars.py | 5 ++--- gui/wxpython/mapdisp/main.py | 2 +- gui/wxpython/rdigit/controller.py | 8 ++++---- gui/wxpython/wxgui.py | 2 +- python/grass/gunittest/case.py | 2 +- python/grass/gunittest/loader.py | 2 +- python/grass/imaging/images2avi.py | 4 ++-- python/grass/pydispatch/dispatcher.py | 8 ++++---- 15 files changed, 34 insertions(+), 35 deletions(-) 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 6298b20c0cb..1c88b1f6fc6 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 80d8a631c00..388e6a2907e 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 f39cf3ec7b7..3b3dfbcaca9 100644 --- a/gui/wxpython/gmodeler/model.py +++ b/gui/wxpython/gmodeler/model.py @@ -322,7 +322,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 f8927659e02..2a8e38ac2c7 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/mapdisp/main.py b/gui/wxpython/mapdisp/main.py index d0fab79c9d5..a456d82a7ca 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/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/python/grass/gunittest/case.py b/python/grass/gunittest/case.py index 5c4e81b3c1d..bc7f1039fda 100644 --- a/python/grass/gunittest/case.py +++ b/python/grass/gunittest/case.py @@ -1334,7 +1334,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 27bdd2c15ca..7e859b89274 100644 --- a/python/grass/imaging/images2avi.py +++ b/python/grass/imaging/images2avi.py @@ -95,8 +95,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 4757acf36cf..58fb240c474 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) From 22e5e9a0fa1d7426d561eb2214e6e5929f4fb797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Sun, 30 Mar 2025 21:43:34 +0000 Subject: [PATCH 4/8] checks: Enable checking for ruff rule B904 --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 85c47f048a9..c95dd7674af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,7 +114,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 From daf9ef2f2d494af936ef4f2dbe518ce374292cf4 Mon Sep 17 00:00:00 2001 From: Vaclav Petras Date: Mon, 13 Jul 2026 10:21:42 -0400 Subject: [PATCH 5/8] grass.pygrass: Do not suppress exception context outside of an except clause The raise is in the function body, not in an except clause, so B904 does not apply to it. The 'from None' there would hide the original exception if connection() is called while another exception is being handled. --- python/grass/pygrass/vector/table.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/grass/pygrass/vector/table.py b/python/grass/pygrass/vector/table.py index 2dfda14bacc..ae6911598fc 100644 --- a/python/grass/pygrass/vector/table.py +++ b/python/grass/pygrass/vector/table.py @@ -861,7 +861,7 @@ def connection(self): raise ImportError(er) from None str_err = "Driver is not supported yet, pleas use: sqlite or pg" - raise TypeError(str_err) from None + raise TypeError(str_err) def table(self): """Return a Table object. From e9a48accc8ff7fedfe80cb989222738f01dab4b2 Mon Sep 17 00:00:00 2001 From: Vaclav Petras Date: Mon, 13 Jul 2026 10:21:42 -0400 Subject: [PATCH 6/8] grass.script: Chain AttributeError in KeyValue.__getattr__ This raise appeared on main after this branch was created and is the only remaining B904 violation, so the rule cannot be enabled without it. The KeyError is an implementation detail of the lookup, so it is suppressed. --- python/grass/script/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/grass/script/utils.py b/python/grass/script/utils.py index 86cfb8108bc..09922f26df0 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 From 1a279f6f69781aecf5644f1058b75e0344babeba Mon Sep 17 00:00:00 2001 From: Vaclav Petras Date: Mon, 13 Jul 2026 10:21:42 -0400 Subject: [PATCH 7/8] wxGUI: Remove useless try-except in __ll_parts The last fallback branch only indexes and assigns strings, so it cannot raise ValueError and the handler was dead code re-raising an empty ValueError. --- gui/wxpython/core/utils.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/gui/wxpython/core/utils.py b/gui/wxpython/core/utils.py index aecc5bd6b4a..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 as err: - raise ValueError from err + # 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 From 7f177344ea16641a8cb490eaf771e33be35c761e Mon Sep 17 00:00:00 2001 From: Vaclav Petras Date: Mon, 13 Jul 2026 10:21:42 -0400 Subject: [PATCH 8/8] grass.utils: Use one name for the caught exception in download_and_extract The two adjacent handlers used 'err' and 'e' for the same kind of error. The rest of the file and g.extension use 'error'. --- python/grass/utils/download.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/python/grass/utils/download.py b/python/grass/utils/download.py index 8e8d7ab6e00..8372a5582aa 100644 --- a/python/grass/utils/download.py +++ b/python/grass/utils/download.py @@ -167,15 +167,15 @@ def download_and_extract(source, reporthook=None): archive_name = os.path.join(tmpdir, "archive.zip") try: filename, headers = urlretrieve(source, archive_name, reporthook) - except HTTPError as err: + except HTTPError as error: raise DownloadError( http_error_message.format( url=source, - code=err, + code=error, ), - ) from err - except URLError as e: - raise DownloadError(url_error_message.format(url=source)) from e + ) from error + except URLError as error: + raise DownloadError(url_error_message.format(url=source)) from error if not re.search( response_content_type_header_pattern, headers.get("content-type", "") @@ -194,15 +194,15 @@ def download_and_extract(source, reporthook=None): archive_name = os.path.join(tmpdir, "archive" + ext) try: urlretrieve(source, archive_name, reporthook) - except HTTPError as err: + except HTTPError as error: raise DownloadError( http_error_message.format( url=source, - code=err, + code=error, ), - ) from err - except URLError as e: - raise DownloadError(url_error_message.format(url=source)) from e + ) from error + except URLError as error: + raise DownloadError(url_error_message.format(url=source)) from error extract_tar(name=archive_name, directory=directory, tmpdir=tmpdir) else: # probably programmer error