Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-pyflakes for openSUSE:Factory checked in at 2025-05-20 09:30:53 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pyflakes (Old) and /work/SRC/openSUSE:Factory/.python-pyflakes.new.30101 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pyflakes" Tue May 20 09:30:53 2025 rev:40 rq:1269465 version:3.3.2 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pyflakes/python-pyflakes.changes 2024-08-06 09:08:00.312903383 +0200 +++ /work/SRC/openSUSE:Factory/.python-pyflakes.new.30101/python-pyflakes.changes 2025-05-20 09:30:55.079272571 +0200 @@ -1,0 +2,13 @@ +Tue Apr 15 07:53:16 UTC 2025 - Dirk Müller <dmuel...@suse.com> + +- update to 3.3.2: + * Fix crash with global / nonlocal in class bodies (regressed + in 3.3.0) + * Allow assignment expressions to redefine annotations + (regressed in 3.3.0) + * Add __debuggerskip__ as a special local + * Allow assignment expressions to redefine outer names + * Drop support for EOL python 3.8 + * Add new error for unused global / nonlocal names + +------------------------------------------------------------------- Old: ---- pyflakes-3.2.0.tar.gz New: ---- pyflakes-3.3.2.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pyflakes.spec ++++++ --- /var/tmp/diff_new_pack.OR85pE/_old 2025-05-20 09:30:55.747300406 +0200 +++ /var/tmp/diff_new_pack.OR85pE/_new 2025-05-20 09:30:55.747300406 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-pyflakes # -# Copyright (c) 2024 SUSE LLC +# Copyright (c) 2025 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-pyflakes -Version: 3.2.0 +Version: 3.3.2 Release: 0 Summary: Passive checker of Python programs License: MIT ++++++ pyflakes-3.2.0.tar.gz -> pyflakes-3.3.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-3.2.0/NEWS.rst new/pyflakes-3.3.2/NEWS.rst --- old/pyflakes-3.2.0/NEWS.rst 2024-01-05 01:26:21.000000000 +0100 +++ new/pyflakes-3.3.2/NEWS.rst 2025-03-31 15:19:39.000000000 +0200 @@ -1,3 +1,18 @@ +3.3.2 (2025-03-31) + +- Fix crash with ``global`` / ``nonlocal`` in class bodies (regressed in 3.3.0) + +3.3.1 (2025-03-30) + +- Allow assignment expressions to redefine annotations (regressed in 3.3.0) + +3.3.0 (2025-03-29) + +- Add ``__debuggerskip__`` as a special local +- Allow assignment expressions to redefine outer names +- Drop support for EOL python 3.8 +- Add new error for unused ``global`` / ``nonlocal`` names + 3.2.0 (2024-01-04) - Add support for ``*T`` (TypeVarTuple) and ``**P`` (ParamSpec) in PEP 695 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-3.2.0/PKG-INFO new/pyflakes-3.3.2/PKG-INFO --- old/pyflakes-3.2.0/PKG-INFO 2024-01-05 01:28:32.802719800 +0100 +++ new/pyflakes-3.3.2/PKG-INFO 2025-03-31 15:20:31.019775600 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: pyflakes -Version: 3.2.0 +Version: 3.3.2 Summary: passive checker of Python programs Home-page: https://github.com/PyCQA/pyflakes Author: A lot of people @@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development Classifier: Topic :: Utilities -Requires-Python: >=3.8 +Requires-Python: >=3.9 License-File: LICENSE ======== @@ -31,7 +31,7 @@ modules with side effects. It's also much faster. It is `available on PyPI <https://pypi.org/project/pyflakes/>`_ -and it supports all active versions of Python: 3.6+. +and it supports all active versions of Python: 3.9+. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-3.2.0/README.rst new/pyflakes-3.3.2/README.rst --- old/pyflakes-3.2.0/README.rst 2023-01-31 19:28:24.000000000 +0100 +++ new/pyflakes-3.3.2/README.rst 2025-03-31 15:04:03.000000000 +0200 @@ -9,7 +9,7 @@ modules with side effects. It's also much faster. It is `available on PyPI <https://pypi.org/project/pyflakes/>`_ -and it supports all active versions of Python: 3.6+. +and it supports all active versions of Python: 3.9+. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-3.2.0/pyflakes/__init__.py new/pyflakes-3.3.2/pyflakes/__init__.py --- old/pyflakes-3.2.0/pyflakes/__init__.py 2024-01-05 01:25:40.000000000 +0100 +++ new/pyflakes-3.3.2/pyflakes/__init__.py 2025-03-31 15:19:44.000000000 +0200 @@ -1 +1 @@ -__version__ = '3.2.0' +__version__ = '3.3.2' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-3.2.0/pyflakes/checker.py new/pyflakes-3.3.2/pyflakes/checker.py --- old/pyflakes-3.2.0/pyflakes/checker.py 2024-01-05 01:24:56.000000000 +0100 +++ new/pyflakes-3.3.2/pyflakes/checker.py 2025-03-31 15:19:04.000000000 +0200 @@ -165,17 +165,6 @@ return fields -def counter(items): - """ - Simplest required implementation of collections.Counter. Required as 2.6 - does not have Counter in collections. - """ - results = {} - for item in items: - results[item] = results.get(item, 0) + 1 - return results - - def iter_child_nodes(node, omit=None, _fields_order=_FieldsOrder()): """ Yield all direct child nodes of *node*, that is, all fields that @@ -537,7 +526,10 @@ class ClassScope(Scope): - pass + def __init__(self): + super().__init__() + # {name: node} + self.indirect_assignments = {} class FunctionScope(Scope): @@ -548,13 +540,14 @@ """ usesLocals = False alwaysUsed = {'__tracebackhide__', '__traceback_info__', - '__traceback_supplement__'} + '__traceback_supplement__', '__debuggerskip__'} def __init__(self): super().__init__() # Simplify: manage the special locals as globals self.globals = self.alwaysUsed.copy() - self.returnValue = None # First non-empty return + # {name: node} + self.indirect_assignments = {} def unused_assignments(self): """ @@ -842,6 +835,10 @@ which were imported but unused. """ for scope in self.deadScopes: + if isinstance(scope, (ClassScope, FunctionScope)): + for name, node in scope.indirect_assignments.items(): + self.report(messages.UnusedIndirectAssignment, node, name) + # imports in classes are public members if isinstance(scope, ClassScope): continue @@ -993,6 +990,9 @@ self.report(messages.RedefinedWhileUnused, node, value.name, existing.source) + if isinstance(scope, (ClassScope, FunctionScope)): + scope.indirect_assignments.pop(value.name, None) + elif isinstance(existing, Importation) and value.redefines(existing): existing.redefined.append(node) @@ -1003,14 +1003,21 @@ # don't treat annotations as assignments if there is an existing value # in scope if value.name not in self.scope or not isinstance(value, Annotation): - cur_scope_pos = -1 - # As per PEP 572, use scope in which outermost generator is defined - while ( - isinstance(value, NamedExprAssignment) and - isinstance(self.scopeStack[cur_scope_pos], GeneratorScope) - ): - cur_scope_pos -= 1 - self.scopeStack[cur_scope_pos][value.name] = value + if isinstance(value, NamedExprAssignment): + # PEP 572: use scope in which outermost generator is defined + scope = next( + scope + for scope in reversed(self.scopeStack) + if not isinstance(scope, GeneratorScope) + ) + if value.name in scope and isinstance(scope[value.name], Annotation): + # re-assignment to name that was previously only an annotation + scope[value.name] = value + else: + # it may be a re-assignment to an already existing name + scope.setdefault(value.name, value) + else: + self.scope[value.name] = value def _unknown_handler(self, node): # this environment variable configures whether to error on unknown @@ -1186,6 +1193,9 @@ # be executed. return + if isinstance(self.scope, (ClassScope, FunctionScope)): + self.scope.indirect_assignments.pop(name, None) + if isinstance(self.scope, FunctionScope) and name in self.scope.globals: self.scope.globals.remove(name) else: @@ -1774,7 +1784,7 @@ convert_to_value(key) for key in node.keys ] - key_counts = counter(keys) + key_counts = collections.Counter(keys) duplicate_keys = [ key for key, count in key_counts.items() if count > 1 @@ -1783,7 +1793,7 @@ for key in duplicate_keys: key_indices = [i for i, i_key in enumerate(keys) if i_key == key] - values = counter( + values = collections.Counter( convert_to_value(node.values[index]) for index in key_indices ) @@ -1844,6 +1854,8 @@ for scope in self.scopeStack[global_scope_index + 1:]: scope[node_name] = node_value + self.scope.indirect_assignments[node_name] = node + NONLOCAL = GLOBAL def GENERATOREXP(self, node): @@ -1896,12 +1908,6 @@ self.report(messages.ReturnOutsideFunction, node) return - if ( - node.value and - hasattr(self.scope, 'returnValue') and - not self.scope.returnValue - ): - self.scope.returnValue = node.value self.handleNode(node.value, node) def YIELD(self, node): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-3.2.0/pyflakes/messages.py new/pyflakes-3.3.2/pyflakes/messages.py --- old/pyflakes-3.2.0/pyflakes/messages.py 2023-01-31 19:28:24.000000000 +0100 +++ new/pyflakes-3.3.2/pyflakes/messages.py 2025-03-31 15:04:03.000000000 +0200 @@ -168,6 +168,15 @@ self.message_args = (names,) +class UnusedIndirectAssignment(Message): + """A `global` or `nonlocal` statement where the name is never reassigned""" + message = '`%s %s` is unused: name is never assigned in scope' + + def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc) + self.message_args = (type(loc).__name__.lower(), name) + + class ReturnOutsideFunction(Message): """ Indicates a return statement outside of a function/method. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-3.2.0/pyflakes/test/test_api.py new/pyflakes-3.3.2/pyflakes/test/test_api.py --- old/pyflakes-3.2.0/pyflakes/test/test_api.py 2023-06-13 03:50:05.000000000 +0200 +++ new/pyflakes-3.3.2/pyflakes/test/test_api.py 2025-03-31 15:04:03.000000000 +0200 @@ -479,16 +479,12 @@ else: msg = 'non-default argument follows default argument' - if PYPY and sys.version_info >= (3, 9): + if PYPY: column = 18 - elif PYPY: - column = 8 elif sys.version_info >= (3, 10): column = 18 - elif sys.version_info >= (3, 9): - column = 21 else: - column = 9 + column = 21 last_line = ' ' * (column - 1) + '^\n' self.assertHasErrors( sourcePath, @@ -508,23 +504,13 @@ foo(bar=baz, bax) """ with self.makeTempFile(source) as sourcePath: - if sys.version_info >= (3, 9): - column = 17 - elif not PYPY: - column = 14 - else: - column = 13 - last_line = ' ' * (column - 1) + '^\n' - columnstr = '%d:' % column - - message = 'positional argument follows keyword argument' - + last_line = ' ' * 16 + '^\n' self.assertHasErrors( sourcePath, - ["""\ -{}:1:{} {} + [f"""\ +{sourcePath}:1:17: positional argument follows keyword argument foo(bar=baz, bax) -{}""".format(sourcePath, columnstr, message, last_line)]) +{last_line}"""]) def test_invalidEscape(self): """ @@ -533,11 +519,9 @@ # ValueError: invalid \x escape with self.makeTempFile(r"foo = '\xyz'") as sourcePath: position_end = 1 - if PYPY and sys.version_info >= (3, 9): + if PYPY: column = 7 - elif PYPY: - column = 6 - elif (3, 9) <= sys.version_info < (3, 12): + elif sys.version_info < (3, 12): column = 13 else: column = 7 @@ -669,23 +653,11 @@ self.assertEqual(count, 1) errlines = err.getvalue().split("\n")[:-1] - if sys.version_info >= (3, 9): - expected_error = [ - "<stdin>:1:5: Generator expression must be parenthesized", - "max(1 for i in range(10), key=lambda x: x+1)", - " ^", - ] - elif PYPY: - expected_error = [ - "<stdin>:1:4: Generator expression must be parenthesized if not sole argument", # noqa: E501 - "max(1 for i in range(10), key=lambda x: x+1)", - " ^", - ] - else: - expected_error = [ - "<stdin>:1:5: Generator expression must be parenthesized", - ] - + expected_error = [ + "<stdin>:1:5: Generator expression must be parenthesized", + "max(1 for i in range(10), key=lambda x: x+1)", + " ^", + ] self.assertEqual(errlines, expected_error) @@ -774,8 +746,14 @@ with open(self.tempfilepath, 'wb') as fd: fd.write(b"import") d = self.runPyflakes([self.tempfilepath]) - error_msg = '{0}:1:7: invalid syntax{1}import{1} ^{1}'.format( - self.tempfilepath, os.linesep) + + if sys.version_info >= (3, 13): + message = "Expected one or more names after 'import'" + else: + message = 'invalid syntax' + + error_msg = '{0}:1:7: {1}{2}import{2} ^{2}'.format( + self.tempfilepath, message, os.linesep) self.assertEqual(d, ('', error_msg, 1)) def test_readFromStdin(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-3.2.0/pyflakes/test/test_imports.py new/pyflakes-3.3.2/pyflakes/test/test_imports.py --- old/pyflakes-3.2.0/pyflakes/test/test_imports.py 2022-11-23 19:37:38.000000000 +0100 +++ new/pyflakes-3.3.2/pyflakes/test/test_imports.py 2025-03-31 15:04:03.000000000 +0200 @@ -654,7 +654,7 @@ self.flakes(''' import fu def f(): global fu - ''', m.UnusedImport) + ''', m.UnusedImport, m.UnusedIndirectAssignment) def test_usedAndGlobal(self): """ @@ -665,7 +665,7 @@ import foo def f(): global foo def g(): foo.is_used() - ''') + ''', m.UnusedIndirectAssignment) def test_assignedToGlobal(self): """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-3.2.0/pyflakes/test/test_other.py new/pyflakes-3.3.2/pyflakes/test/test_other.py --- old/pyflakes-3.2.0/pyflakes/test/test_other.py 2023-01-31 19:28:24.000000000 +0100 +++ new/pyflakes-3.3.2/pyflakes/test/test_other.py 2025-03-31 15:19:04.000000000 +0200 @@ -1085,8 +1085,72 @@ self.flakes(''' def f(): global foo def g(): foo = 'anything'; foo.is_used() + ''', m.UnusedIndirectAssignment) + + def test_unused_global_statement(self): + self.flakes(''' + g = 0 + def f1(): + global g + g = 1 + def f2(): + global g # this is unused! + return g + ''', m.UnusedIndirectAssignment) + + def test_unused_nonlocal_statement(self): + self.flakes(''' + def f(): + x = 1 + def set_x(): + nonlocal x + x = 2 + def get_x(): + nonlocal x + return x + set_x() + return get_x() + ''', m.UnusedIndirectAssignment) + + def test_unused_global_statement_not_marked_as_used_by_nested_scope(self): + self.flakes(''' + g = 0 + def f(): + global g + def f2(): + g = 2 + ''', m.UnusedIndirectAssignment, m.UnusedVariable) + + def test_global_nonlocal_in_class_bodies(self): + self.flakes(''' + g = 0 + class C: + global g + g = 1 + def f(): + nl = 0 + class C: + nonlocal nl + nl = 1 ''') + def test_unused_global_in_class(self): + self.flakes(''' + g = 0 + class C: + global g + u = g + ''', m.UnusedIndirectAssignment) + + def test_unused_nonlocal_in_clas(self): + self.flakes(''' + def f(): + nl = 1 + class C: + nonlocal nl + u = nl + ''', m.UnusedIndirectAssignment) + def test_function_arguments(self): """ Test to traverse ARG and ARGUMENT handler @@ -1349,6 +1413,16 @@ __tracebackhide__ = True """) + def test_debuggerskipSpecialVariable(self): + """ + Do not warn about unused local variable __debuggerskip__, which is + a special variable for IPython. + """ + self.flakes(""" + def helper(): + __debuggerskip__ = True + """) + def test_ifexp(self): """ Test C{foo if bar else baz} statements. @@ -1700,6 +1774,13 @@ print(x) ''') + def test_assign_expr_after_annotation(self): + self.flakes(""" + a: int + print(a := 3) + print(a) + """) + def test_assign_expr_generator_scope(self): """Test assignment expressions in generator expressions.""" self.flakes(''' @@ -1707,6 +1788,13 @@ print(y) ''') + def test_assign_expr_generator_scope_reassigns_parameter(self): + self.flakes(''' + def foo(x): + fns = [lambda x: x + 1, lambda x: x + 2, lambda x: x + 3] + return [(x := fn(x)) for fn in fns] + ''') + def test_assign_expr_nested(self): """Test assignment expressions in nested expressions.""" self.flakes(''' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-3.2.0/pyflakes/test/test_type_annotations.py new/pyflakes-3.3.2/pyflakes/test/test_type_annotations.py --- old/pyflakes-3.2.0/pyflakes/test/test_type_annotations.py 2024-01-05 01:24:56.000000000 +0100 +++ new/pyflakes-3.3.2/pyflakes/test/test_type_annotations.py 2025-03-31 15:04:03.000000000 +0200 @@ -797,3 +797,10 @@ return f(*args, **kwargs) return g """) + + @skipIf(version_info < (3, 13), 'new in Python 3.13') + def test_type_parameter_defaults(self): + self.flakes(""" + def f[T = int](u: T) -> T: + return u + """) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-3.2.0/pyflakes/test/test_undefined_names.py new/pyflakes-3.3.2/pyflakes/test/test_undefined_names.py --- old/pyflakes-3.2.0/pyflakes/test/test_undefined_names.py 2022-11-23 19:37:38.000000000 +0100 +++ new/pyflakes-3.3.2/pyflakes/test/test_undefined_names.py 2025-03-31 15:04:03.000000000 +0200 @@ -327,7 +327,7 @@ def f2(): global m - ''', m.UndefinedName) + ''', m.UndefinedName, m.UnusedIndirectAssignment) @skip("todo") def test_unused_global(self): @@ -462,7 +462,7 @@ a a = 2 return a - ''', m.UndefinedLocal) + ''', m.UndefinedLocal, m.UnusedIndirectAssignment) def test_intermediateClassScopeIgnored(self): """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-3.2.0/pyflakes.egg-info/PKG-INFO new/pyflakes-3.3.2/pyflakes.egg-info/PKG-INFO --- old/pyflakes-3.2.0/pyflakes.egg-info/PKG-INFO 2024-01-05 01:28:32.000000000 +0100 +++ new/pyflakes-3.3.2/pyflakes.egg-info/PKG-INFO 2025-03-31 15:20:30.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: pyflakes -Version: 3.2.0 +Version: 3.3.2 Summary: passive checker of Python programs Home-page: https://github.com/PyCQA/pyflakes Author: A lot of people @@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development Classifier: Topic :: Utilities -Requires-Python: >=3.8 +Requires-Python: >=3.9 License-File: LICENSE ======== @@ -31,7 +31,7 @@ modules with side effects. It's also much faster. It is `available on PyPI <https://pypi.org/project/pyflakes/>`_ -and it supports all active versions of Python: 3.6+. +and it supports all active versions of Python: 3.9+. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-3.2.0/setup.py new/pyflakes-3.3.2/setup.py --- old/pyflakes-3.2.0/setup.py 2023-01-31 19:28:24.000000000 +0100 +++ new/pyflakes-3.3.2/setup.py 2025-03-31 15:04:03.000000000 +0200 @@ -42,7 +42,7 @@ author_email="code-qual...@python.org", url="https://github.com/PyCQA/pyflakes", packages=["pyflakes", "pyflakes.scripts", "pyflakes.test"], - python_requires='>=3.8', + python_requires='>=3.9', classifiers=[ "Development Status :: 6 - Mature", "Environment :: Console",