Hi Bruno,

On 4/4/24 5:48 AM, Bruno Haible wrote:
> The only difference between the two outputs is the message between the two
> stack traces. The latter sounds like a program bug; therefore I would say,
> let's use the chaining — just in order to clarify that the second exception
> is intended.

Ah, that makes sense. I didn't really think about the intent that the
warning might convey. I agree with your assessment.

The first patch attached fixes all of those warnings by adding the
explicit chaining.

> Oh, indeed there's a fallacy here: {} denotes a dict, not a set!
> 
>>>> type({'x','y'})
> <class 'set'>
>>>> type({'x'})
> <class 'set'>
>>>> type({})
> <class 'dict'>

Yes, it seems the Python developers ran out of brackets to use and
decided that sets should be the odd ones out. :(

>>> type(())
> <class 'tuple'>
>>> type([])
> <class 'list'>
>>> type({})
> <class 'dict'>
>>> type(set())
> <class 'set'>

The second patch disables that warning and changes the one occurrence
of '{}' we have to 'dict()'.

> This is surprising enough that we should add to our coding style:
> 
>   - Never use the {} literal, because it denotes a dictionary,
>     as opposed to {x}, {x,y}, etc., which denote sets.

I've also added this to comment to main.py.

Collin
From 501173c3439e5607828c086d41eb1be2bd49ae8f Mon Sep 17 00:00:00 2001
From: Collin Funk <collin.fu...@gmail.com>
Date: Thu, 4 Apr 2024 14:56:12 -0700
Subject: [PATCH 1/2] gnulib-tool.py: Fix pylint 'raise-missing-from' warnings.

* pygnulib/*.py: Use explicit exception chaining so that stack trace
messages do not seem like bugs. See examples in:
<https://lists.gnu.org/archive/html/bug-gnulib/2024-04/msg00056.html>
---
 ChangeLog                |  7 +++++++
 pygnulib/GLConfig.py     | 20 ++++++++++----------
 pygnulib/GLFileSystem.py | 20 ++++++++++----------
 pygnulib/GLImport.py     |  8 ++++----
 pygnulib/GLTestDir.py    | 12 ++++++------
 5 files changed, 37 insertions(+), 30 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 536c69737e..3cceb4c60a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2024-04-04  Collin Funk  <collin.fu...@gmail.com>
+
+	gnulib-tool.py: Fix pylint 'raise-missing-from' warnings.
+	* pygnulib/*.py: Use explicit exception chaining so that stack trace
+	messages do not seem like bugs. See examples in:
+	<https://lists.gnu.org/archive/html/bug-gnulib/2024-04/msg00056.html>
+
 2024-04-04  Bruno Haible  <br...@clisp.org>
 
 	Add serial numbers to *.m4 files that did not have them.
diff --git a/pygnulib/GLConfig.py b/pygnulib/GLConfig.py
index 8136d9b08c..121edf541e 100644
--- a/pygnulib/GLConfig.py
+++ b/pygnulib/GLConfig.py
@@ -539,9 +539,9 @@ class GLConfig:
             for module in modules:
                 try:  # Try to add each module
                     self.addModule(module)
-                except TypeError:
+                except TypeError as exc:
                     self.table['modules'] = old_modules
-                    raise TypeError('each module must be a string')
+                    raise TypeError('each module must be a string') from exc
                 except GLError:
                     self.table['modules'] = old_modules
                     raise
@@ -585,9 +585,9 @@ class GLConfig:
             for module in modules:
                 try:  # Try to add each module
                     self.addAvoid(module)
-                except TypeError:
+                except TypeError as exc:
                     self.table['avoids'] = old_avoids
-                    raise TypeError('each module must be a string')
+                    raise TypeError('each module must be a string') from exc
                 except GLError:
                     self.table['avoids'] = old_avoids
                     raise
@@ -630,9 +630,9 @@ class GLConfig:
             for file in files:
                 try:  # Try to add each file
                     self.addFile(file)
-                except TypeError:
+                except TypeError as exc:
                     self.table['files'] = old_files
-                    raise TypeError('each file must be a string')
+                    raise TypeError('each file must be a string') from exc
                 except GLError:
                     self.table['files'] = old_files
                     raise
@@ -688,9 +688,9 @@ class GLConfig:
             for category in categories:
                 try:  # Try to enable each category
                     self.enableInclTestCategory(category)
-                except TypeError:
+                except TypeError as exc:
                     self.table['incl_test_categories'] = old_categories
-                    raise TypeError('each category must be one of TESTS integers')
+                    raise TypeError('each category must be one of TESTS integers') from exc
         else:  # if type of categories is not list or tuple
             raise TypeError('categories must be a list or a tuple, not %s'
                             % type(categories).__name__)
@@ -736,9 +736,9 @@ class GLConfig:
             for category in categories:
                 try:  # Try to enable each category
                     self.enableExclTestCategory(category)
-                except TypeError:
+                except TypeError as exc:
                     self.table['excl_test_categories'] = old_categories
-                    raise TypeError('each category must be one of TESTS integers')
+                    raise TypeError('each category must be one of TESTS integers') from exc
         else:  # if type of categories is not list or tuple
             raise TypeError('categories must be a list or a tuple, not %s'
                             % type(categories).__name__)
diff --git a/pygnulib/GLFileSystem.py b/pygnulib/GLFileSystem.py
index a0cddd8903..e157578e54 100644
--- a/pygnulib/GLFileSystem.py
+++ b/pygnulib/GLFileSystem.py
@@ -120,8 +120,8 @@ class GLFileSystem:
                     command = 'patch -s "%s" < "%s" >&2' % (tempFile, diff_in_localdir)
                     try:  # Try to apply patch
                         sp.check_call(command, shell=True)
-                    except sp.CalledProcessError:
-                        raise GLError(2, name)
+                    except sp.CalledProcessError as exc:
+                        raise GLError(2, name) from exc
                 result = (tempFile, True)
             else:
                 result = (lookedupFile, False)
@@ -260,8 +260,8 @@ class GLFileAssistant:
                 else:  # Move instead of linking.
                     try:  # Try to move file
                         movefile(tmpfile, joinpath(destdir, rewritten))
-                    except Exception:
-                        raise GLError(17, original)
+                    except Exception as exc:
+                        raise GLError(17, original) from exc
         else:  # if self.config['dryrun']
             print('Copy file %s' % rewritten)
 
@@ -299,8 +299,8 @@ class GLFileAssistant:
                     os.remove(backuppath)
                 try:  # Try to replace the given file
                     movefile(basepath, backuppath)
-                except Exception:
-                    raise GLError(17, original)
+                except Exception as exc:
+                    raise GLError(17, original) from exc
                 if self.filesystem.shouldLink(original, lookedup) == CopyAction.Symlink \
                         and not tmpflag and filecmp.cmp(lookedup, tmpfile):
                     link_if_changed(lookedup, basepath)
@@ -313,8 +313,8 @@ class GLFileAssistant:
                             if os.path.exists(basepath):
                                 os.remove(basepath)
                             copyfile(tmpfile, joinpath(destdir, rewritten))
-                        except Exception:
-                            raise GLError(17, original)
+                        except Exception as exc:
+                            raise GLError(17, original) from exc
             else:  # if self.config['dryrun']
                 if already_present:
                     print('Update file %s (backup in %s)' % (rewritten, backupname))
@@ -343,8 +343,8 @@ class GLFileAssistant:
         try:  # Try to copy lookedup file to tmpfile
             copyfile(lookedup, tmpfile)
             ensure_writable(tmpfile)
-        except Exception:
-            raise GLError(15, lookedup)
+        except Exception as exc:
+            raise GLError(15, lookedup) from exc
         # Don't process binary files with sed.
         if not (original.endswith(".class") or original.endswith(".mo")):
             transformer = None
diff --git a/pygnulib/GLImport.py b/pygnulib/GLImport.py
index 5cd39f66a3..c6d1a758a4 100644
--- a/pygnulib/GLImport.py
+++ b/pygnulib/GLImport.py
@@ -1018,8 +1018,8 @@ AC_DEFUN([%s_FILE_LIST], [\n''' % macro_prefix
                 if not self.config['dryrun']:
                     try:  # Try to create directory
                         os.makedirs(directory)
-                    except Exception:
-                        raise GLError(13, directory)
+                    except Exception as exc:
+                        raise GLError(13, directory) from exc
                 else:  # if self.config['dryrun']
                     print('Create directory %s' % directory)
 
@@ -1043,8 +1043,8 @@ AC_DEFUN([%s_FILE_LIST], [\n''' % macro_prefix
                         if os.path.exists(backup):
                             os.remove(backup)
                         movefile(path, '%s~' % path)
-                    except Exception:
-                        raise GLError(14, file)
+                    except Exception as exc:
+                        raise GLError(14, file) from exc
                 else:  # if self.config['dryrun']
                     print('Remove file %s (backup in %s~)' % (path, path))
                 filetable['removed'] += [file]
diff --git a/pygnulib/GLTestDir.py b/pygnulib/GLTestDir.py
index 51c6ee3193..829e26fb3f 100644
--- a/pygnulib/GLTestDir.py
+++ b/pygnulib/GLTestDir.py
@@ -75,12 +75,12 @@ def _patch_test_driver() -> None:
         command = f'patch {test_driver} < {diff}'
         try:
             result = sp.call(command, shell=True, stdout=sp.DEVNULL, stderr=sp.DEVNULL)
-        except OSError:
+        except OSError as exc:
             if isfile(f'{test_driver}.orig'):
                 os.remove(f'{test_driver}.orig')
             if isfile(f'{test_driver}.rej'):
                 os.remove(f'{test_driver}.rej')
-            raise GLError(20, None)
+            raise GLError(20, None) from exc
         if result == 0:
             patched = True
             break
@@ -112,8 +112,8 @@ class GLTestDir:
         if not os.path.exists(self.testdir):
             try:  # Try to create directory
                 os.mkdir(self.testdir)
-            except Exception:
-                raise GLError(19, self.testdir)
+            except Exception as exc:
+                raise GLError(19, self.testdir) from exc
         self.emitter = GLEmiter(self.config)
         self.filesystem = GLFileSystem(self.config)
         self.modulesystem = GLModuleSystem(self.config)
@@ -900,8 +900,8 @@ class GLMegaTestDir:
         if not os.path.exists(self.megatestdir):
             try:  # Try to create directory
                 os.mkdir(self.megatestdir)
-            except Exception:
-                raise GLError(19, self.megatestdir)
+            except Exception as exc:
+                raise GLError(19, self.megatestdir) from exc
         self.emitter = GLEmiter(self.config)
         self.filesystem = GLFileSystem(self.config)
         self.modulesystem = GLModuleSystem(self.config)
-- 
2.44.0

From 97a978c99477fc7b236d5dbc49fb6a5955193a62 Mon Sep 17 00:00:00 2001
From: Collin Funk <collin.fu...@gmail.com>
Date: Thu, 4 Apr 2024 15:29:50 -0700
Subject: [PATCH 2/2] gnulib-tool.py: Ignore 'use-dict-literal' warnings.

* pygnulib/.pylintrc: Don't emit warning messages suggesting that
'dict()' be converted to '{}'. This literal can be mistaken for sets,
see discussion here:
<https://lists.gnu.org/archive/html/bug-gnulib/2024-04/msg00054.html>
* pygnulib/main.py: Document this convention in coding style section.
* pygnulib/GLFileSystem.py (GLFileAssistant.__init__): Convert an
occurrence of '{}' to 'dict()'.
---
 ChangeLog                | 11 +++++++++++
 pygnulib/.pylintrc       |  2 +-
 pygnulib/GLFileSystem.py |  2 +-
 pygnulib/main.py         |  2 ++
 4 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 3cceb4c60a..5a84f5a2c7 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+2024-04-04  Collin Funk  <collin.fu...@gmail.com>
+
+	gnulib-tool.py: Ignore 'use-dict-literal' warnings.
+	* pygnulib/.pylintrc: Don't emit warning messages suggesting that
+	'dict()' be converted to '{}'. This literal can be mistaken for sets,
+	see discussion here:
+	<https://lists.gnu.org/archive/html/bug-gnulib/2024-04/msg00054.html>
+	* pygnulib/main.py: Document this convention in coding style section.
+	* pygnulib/GLFileSystem.py (GLFileAssistant.__init__): Convert an
+	occurrence of '{}' to 'dict()'.
+
 2024-04-04  Collin Funk  <collin.fu...@gmail.com>
 
 	gnulib-tool.py: Fix pylint 'raise-missing-from' warnings.
diff --git a/pygnulib/.pylintrc b/pygnulib/.pylintrc
index 15e2e8e800..102accb37d 100644
--- a/pygnulib/.pylintrc
+++ b/pygnulib/.pylintrc
@@ -1,7 +1,7 @@
 # .pylintrc
 
 [MESSAGES CONTROL]
-disable=C0103,C0114,C0121,C0123,C0209,C0301,C0302,R0902,R0912,R0913,R0914,R0915,R1705,R1702,R1720
+disable=C0103,C0114,C0121,C0123,C0209,C0301,C0302,R0902,R0912,R0913,R0914,R0915,R1705,R1702,R1720,R1735
 
 # Local Variables:
 # mode: conf
diff --git a/pygnulib/GLFileSystem.py b/pygnulib/GLFileSystem.py
index e157578e54..551ae177e4 100644
--- a/pygnulib/GLFileSystem.py
+++ b/pygnulib/GLFileSystem.py
@@ -154,7 +154,7 @@ class GLFileSystem:
 class GLFileAssistant:
     '''GLFileAssistant is used to help with file processing.'''
 
-    def __init__(self, config: GLConfig, transformers: dict[str, tuple[re.Pattern, str] | None] = {}) -> None:
+    def __init__(self, config: GLConfig, transformers: dict[str, tuple[re.Pattern, str] | None] = dict()) -> None:
         '''Create GLFileAssistant instance.
 
         config stores information shared between classes.
diff --git a/pygnulib/main.py b/pygnulib/main.py
index 233c79182e..4bf4da4279 100644
--- a/pygnulib/main.py
+++ b/pygnulib/main.py
@@ -52,6 +52,8 @@
 # - Prefer the standard collection type hints over those provided in the
 #   typing module. The latter are deprecated and may be removed.
 #   Cf. <https://peps.python.org/pep-0585/>.
+# - Never use the {} literal, because it denotes a dictionary,
+#   as opposed to {x}, {x,y}, etc., which denote sets.
 
 # You can use this command to check the style:
 #   $ pycodestyle *.py
-- 
2.44.0

Reply via email to