Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-pylint for openSUSE:Factory checked in at 2026-01-26 12:34:33 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pylint (Old) and /work/SRC/openSUSE:Factory/.python-pylint.new.1928 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pylint" Mon Jan 26 12:34:33 2026 rev:52 rq:1329152 version:4.0.4 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pylint/python-pylint.changes 2025-11-18 15:35:43.725039478 +0100 +++ /work/SRC/openSUSE:Factory/.python-pylint.new.1928/python-pylint.changes 2026-01-26 12:34:39.530019815 +0100 @@ -1,0 +2,12 @@ +Mon Jan 26 08:08:45 UTC 2026 - Dirk Müller <[email protected]> + +- update to 4.0.4: + * Fixed false positive for ``invalid-name`` where module-level + constants were incorrectly classified as variables when a + class-level attribute with the same name exists. + * Closes #10719 + * Fix a false positive for ``invalid-name`` on an UPPER_CASED + name inside an ``if`` branch that assigns an object. + * Closes #10745 + +------------------------------------------------------------------- Old: ---- pylint-4.0.3-gh.tar.gz New: ---- pylint-4.0.4-gh.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pylint.spec ++++++ --- /var/tmp/diff_new_pack.KF4g5q/_old 2026-01-26 12:34:40.382055568 +0100 +++ /var/tmp/diff_new_pack.KF4g5q/_new 2026-01-26 12:34:40.382055568 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-pylint # -# Copyright (c) 2025 SUSE LLC and contributors +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -24,7 +24,7 @@ %endif %{?sle15_python_module_pythons} Name: python-pylint -Version: 4.0.3 +Version: 4.0.4 Release: 0 Summary: Syntax and style checker for Python code License: GPL-2.0-or-later ++++++ pylint-4.0.3-gh.tar.gz -> pylint-4.0.4-gh.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylint-4.0.3/custom_dict.txt new/pylint-4.0.4/custom_dict.txt --- old/pylint-4.0.3/custom_dict.txt 2025-11-13 15:56:42.000000000 +0100 +++ new/pylint-4.0.4/custom_dict.txt 2025-11-30 14:24:33.000000000 +0100 @@ -24,6 +24,7 @@ attr attrib attrname +attrs backport BaseChecker basename @@ -144,6 +145,7 @@ hashable hmac html +iattrs idgeneratormixin ifexpr igetattr diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylint-4.0.3/doc/whatsnew/4/4.0/index.rst new/pylint-4.0.4/doc/whatsnew/4/4.0/index.rst --- old/pylint-4.0.3/doc/whatsnew/4/4.0/index.rst 2025-11-13 15:56:42.000000000 +0100 +++ new/pylint-4.0.4/doc/whatsnew/4/4.0/index.rst 2025-11-30 14:24:33.000000000 +0100 @@ -74,6 +74,24 @@ .. towncrier release notes start +What's new in Pylint 4.0.4? +-------------------------------- +Release date: 2025-11-30 + + +False Positives Fixed +--------------------- + +- Fixed false positive for ``invalid-name`` where module-level constants were incorrectly classified as variables when a class-level attribute with the same name exists. + + Closes #10719 (`#10719 <https://github.com/pylint-dev/pylint/issues/10719>`_) + +- Fix a false positive for ``invalid-name`` on an UPPER_CASED name inside an ``if`` branch that assigns an object. + + Closes #10745 (`#10745 <https://github.com/pylint-dev/pylint/issues/10745>`_) + + + What's new in Pylint 4.0.3? --------------------------- Release date: 2025-11-13 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylint-4.0.3/pylint/__pkginfo__.py new/pylint-4.0.4/pylint/__pkginfo__.py --- old/pylint-4.0.3/pylint/__pkginfo__.py 2025-11-13 15:56:42.000000000 +0100 +++ new/pylint-4.0.4/pylint/__pkginfo__.py 2025-11-30 14:24:33.000000000 +0100 @@ -9,7 +9,7 @@ from __future__ import annotations -__version__ = "4.0.3" +__version__ = "4.0.4" def get_numversion_from_version(v: str) -> tuple[int, int, int]: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylint-4.0.3/pylint/checkers/base/name_checker/checker.py new/pylint-4.0.4/pylint/checkers/base/name_checker/checker.py --- old/pylint-4.0.3/pylint/checkers/base/name_checker/checker.py 2025-11-13 15:56:42.000000000 +0100 +++ new/pylint-4.0.4/pylint/checkers/base/name_checker/checker.py 2025-11-30 14:24:33.000000000 +0100 @@ -518,13 +518,12 @@ and self._name_regexps["const"].match(node.name) is not None ): return - if ( - util.Uninferable not in iattrs - and len(iattrs) > 1 - and all( - astroid.are_exclusive(*combo) - for combo in itertools.combinations(iattrs, 2) - ) + # Do the exclusive assignment analysis on attrs, not iattrs. + # iattrs locations could be anywhere (inference result). + attrs = tuple(node.frame().getattr(node.name)) + if len(attrs) > 1 and all( + astroid.are_exclusive(*combo) + for combo in itertools.combinations(attrs, 2) ): node_type = "const" if not self._meets_exception_for_non_consts( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylint-4.0.3/pylint/checkers/utils.py new/pylint-4.0.4/pylint/checkers/utils.py --- old/pylint-4.0.3/pylint/checkers/utils.py 2025-11-13 15:56:42.000000000 +0100 +++ new/pylint-4.0.4/pylint/checkers/utils.py 2025-11-30 14:24:33.000000000 +0100 @@ -1843,28 +1843,50 @@ return False +def _is_node_in_same_scope( + candidate: nodes.NodeNG, node_scope: nodes.LocalsDictNodeNG +) -> bool: + if isinstance(candidate, (nodes.ClassDef, nodes.FunctionDef)): + return candidate.parent is not None and candidate.parent.scope() is node_scope + return candidate.scope() is node_scope + + +def _is_reassigned_relative_to_current( + node: nodes.NodeNG, varname: str, before: bool +) -> bool: + """Check if the given variable name is reassigned in the same scope relative to + the current node. + """ + node_scope = node.scope() + node_lineno = node.lineno + if node_lineno is None: + return False + for a in node_scope.nodes_of_class( + (nodes.AssignName, nodes.ClassDef, nodes.FunctionDef) + ): + if a.name == varname and a.lineno is not None: + if before: + if a.lineno < node_lineno: + if _is_node_in_same_scope(a, node_scope): + return True + elif a.lineno > node_lineno: + if _is_node_in_same_scope(a, node_scope): + return True + return False + + def is_reassigned_before_current(node: nodes.NodeNG, varname: str) -> bool: """Check if the given variable name is reassigned in the same scope before the current node. """ - return any( - a.name == varname and a.lineno < node.lineno - for a in node.scope().nodes_of_class( - (nodes.AssignName, nodes.ClassDef, nodes.FunctionDef) - ) - ) + return _is_reassigned_relative_to_current(node, varname, before=True) def is_reassigned_after_current(node: nodes.NodeNG, varname: str) -> bool: """Check if the given variable name is reassigned in the same scope after the current node. """ - return any( - a.name == varname and a.lineno > node.lineno - for a in node.scope().nodes_of_class( - (nodes.AssignName, nodes.ClassDef, nodes.FunctionDef) - ) - ) + return _is_reassigned_relative_to_current(node, varname, before=False) def is_deleted_after_current(node: nodes.NodeNG, varname: str) -> bool: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylint-4.0.3/tbump.toml new/pylint-4.0.4/tbump.toml --- old/pylint-4.0.3/tbump.toml 2025-11-13 15:56:42.000000000 +0100 +++ new/pylint-4.0.4/tbump.toml 2025-11-30 14:24:33.000000000 +0100 @@ -1,7 +1,7 @@ github_url = "https://github.com/pylint-dev/pylint" [version] -current = "4.0.3" +current = "4.0.4" regex = ''' ^(?P<major>0|[1-9]\d*) \. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylint-4.0.3/tests/checkers/unittest_utils.py new/pylint-4.0.4/tests/checkers/unittest_utils.py --- old/pylint-4.0.3/tests/checkers/unittest_utils.py 2025-11-13 15:56:42.000000000 +0100 +++ new/pylint-4.0.4/tests/checkers/unittest_utils.py 2025-11-30 14:24:33.000000000 +0100 @@ -520,3 +520,105 @@ ) assert not utils.is_typing_member(code[0], ("Literal",)) assert not utils.is_typing_member(code[1], ("Literal",)) + + +def test_is_reassigned_after_current_requires_isinstance_check() -> None: + tree = astroid.parse( + """ + CONSTANT = 1 + + def global_function_assign(): + global CONSTANT + def CONSTANT(): + pass + CONSTANT() + """ + ) + func = tree.body[1] + global_stmt = func.body[0] + nested_func = func.body[1] + + assert isinstance(global_stmt, nodes.Global) + assert isinstance(nested_func, nodes.FunctionDef) + + node_scope = global_stmt.scope() + + assert nested_func.scope() == nested_func + assert nested_func.scope() != node_scope + + assert nested_func.parent.scope() == node_scope + + assert utils.is_reassigned_after_current(global_stmt, "CONSTANT") is True + + +def test_is_reassigned_before_current() -> None: + tree = astroid.parse( + """ + x = 1 + x = 2 + x = 3 + """ + ) + first_assign = tree.body[0] + second_assign = tree.body[1] + third_assign = tree.body[2] + + assert isinstance(first_assign, nodes.Assign) + assert isinstance(second_assign, nodes.Assign) + assert isinstance(third_assign, nodes.Assign) + + third_assign_name = third_assign.targets[0] + first_assign_name = first_assign.targets[0] + + assert isinstance(third_assign_name, nodes.AssignName) + assert isinstance(first_assign_name, nodes.AssignName) + + assert utils.is_reassigned_before_current(third_assign_name, "x") is True + assert utils.is_reassigned_before_current(first_assign_name, "x") is False + + +def test_is_reassigned_after_current_with_assignname() -> None: + tree = astroid.parse( + """ + x = 1 + x = 2 + x = 3 + """ + ) + first_assign = tree.body[0] + second_assign = tree.body[1] + third_assign = tree.body[2] + + assert isinstance(first_assign, nodes.Assign) + assert isinstance(second_assign, nodes.Assign) + assert isinstance(third_assign, nodes.Assign) + + first_assign_name = first_assign.targets[0] + third_assign_name = third_assign.targets[0] + + assert isinstance(first_assign_name, nodes.AssignName) + assert isinstance(third_assign_name, nodes.AssignName) + + assert utils.is_reassigned_after_current(first_assign_name, "x") is True + assert utils.is_reassigned_after_current(third_assign_name, "x") is False + + +def test_is_reassigned_with_node_no_lineno() -> None: + tree = astroid.parse( + """ + x = 1 + x = 2 + """ + ) + first_assign = tree.body[0] + first_assign_name = first_assign.targets[0] + + assert isinstance(first_assign_name, nodes.AssignName) + original_lineno = first_assign_name.lineno + first_assign_name.lineno = None + + try: + assert utils.is_reassigned_after_current(first_assign_name, "x") is False + assert utils.is_reassigned_before_current(first_assign_name, "x") is False + finally: + first_assign_name.lineno = original_lineno diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylint-4.0.3/tests/functional/i/invalid/invalid_name/invalid_name_module_level.py new/pylint-4.0.4/tests/functional/i/invalid/invalid_name/invalid_name_module_level.py --- old/pylint-4.0.3/tests/functional/i/invalid/invalid_name/invalid_name_module_level.py 2025-11-13 15:56:42.000000000 +0100 +++ new/pylint-4.0.4/tests/functional/i/invalid/invalid_name/invalid_name_module_level.py 2025-11-30 14:24:33.000000000 +0100 @@ -44,6 +44,12 @@ other_const = [3] +if CONST: + ANOTHER_CONST = A() +else: + ANOTHER_CONST = 5 + + from importlib.metadata import PackageNotFoundError from importlib.metadata import version @@ -55,3 +61,15 @@ from typing import Annotated IntWithAnnotation = Annotated[int, "anything"] + + +# Regression test for #10719: module-level constants should not be incorrectly +# classified as variables when a class-level attribute with the same name exists. +class Theme: + INPUT = ">>> " + + +INPUT = Theme() +input = Theme() # pylint: disable=redefined-builtin +OUTPUT = Theme() +output = Theme() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylint-4.0.3/tests/functional/n/no/no_dummy_redefined.py new/pylint-4.0.4/tests/functional/n/no/no_dummy_redefined.py --- old/pylint-4.0.3/tests/functional/n/no/no_dummy_redefined.py 2025-11-13 15:56:42.000000000 +0100 +++ new/pylint-4.0.4/tests/functional/n/no/no_dummy_redefined.py 2025-11-30 14:24:33.000000000 +0100 @@ -1,4 +1,5 @@ """Make sure warnings about redefinitions do not trigger for dummy variables.""" +# pylint: disable=invalid-name _, INTERESTING = 'a=b'.split('=') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylint-4.0.3/tests/functional/n/no/no_dummy_redefined.txt new/pylint-4.0.4/tests/functional/n/no/no_dummy_redefined.txt --- old/pylint-4.0.3/tests/functional/n/no/no_dummy_redefined.txt 2025-11-13 15:56:42.000000000 +0100 +++ new/pylint-4.0.4/tests/functional/n/no/no_dummy_redefined.txt 2025-11-30 14:24:33.000000000 +0100 @@ -1 +1 @@ -redefined-outer-name:11:4:11:9:clobbering:Redefining name 'value' from outer scope (line 6):UNDEFINED +redefined-outer-name:12:4:12:9:clobbering:Redefining name 'value' from outer scope (line 7):UNDEFINED
