Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-pyupgrade for openSUSE:Factory checked in at 2021-10-26 20:14:17 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pyupgrade (Old) and /work/SRC/openSUSE:Factory/.python-pyupgrade.new.1890 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pyupgrade" Tue Oct 26 20:14:17 2021 rev:18 rq:927577 version:2.28.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pyupgrade/python-pyupgrade.changes 2021-09-25 22:51:29.903353222 +0200 +++ /work/SRC/openSUSE:Factory/.python-pyupgrade.new.1890/python-pyupgrade.changes 2021-10-26 20:15:05.378054049 +0200 @@ -1,0 +2,13 @@ +Sun Sep 26 07:22:08 UTC 2021 - Sebastian Wagner <sebix+novell....@sebix.at> + +- update to version 2.28.0: + - don't rewrite old super calls for __new__ + - fix super replacement of multiple lines + - Fix bug with calling different superclass method + - Revert "Revert "Merge pull request #317 from asottile/old_super"" + This reverts commit 2719335fa7bdb582b35ac90547a0f763d4225036. + - fix raise_from with multi lines / trailing commas + - fix rewrite causing syntax error when the first arg has newlines + - fix invalid dedent with comment after block + +------------------------------------------------------------------- Old: ---- python-pyupgrade-2.27.0.tar.gz New: ---- python-pyupgrade-2.28.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pyupgrade.spec ++++++ --- /var/tmp/diff_new_pack.MxzJoi/_old 2021-10-26 20:15:05.862054304 +0200 +++ /var/tmp/diff_new_pack.MxzJoi/_new 2021-10-26 20:15:05.866054306 +0200 @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %define skip_python2 1 Name: python-pyupgrade -Version: 2.27.0 +Version: 2.28.0 Release: 0 Summary: A tool to automatically upgrade syntax for newer versions License: MIT ++++++ python-pyupgrade-2.27.0.tar.gz -> python-pyupgrade-2.28.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyupgrade-2.27.0/.pre-commit-config.yaml new/pyupgrade-2.28.0/.pre-commit-config.yaml --- old/pyupgrade-2.27.0/.pre-commit-config.yaml 2021-09-23 04:23:06.000000000 +0200 +++ new/pyupgrade-2.28.0/.pre-commit-config.yaml 2021-09-25 23:35:49.000000000 +0200 @@ -34,7 +34,7 @@ - id: add-trailing-comma args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.27.0 + rev: v2.28.0 hooks: - id: pyupgrade args: [--py36-plus] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyupgrade-2.27.0/README.md new/pyupgrade-2.28.0/README.md --- old/pyupgrade-2.27.0/README.md 2021-09-23 04:23:06.000000000 +0200 +++ new/pyupgrade-2.28.0/README.md 2021-09-25 23:35:49.000000000 +0200 @@ -20,7 +20,7 @@ ```yaml - repo: https://github.com/asottile/pyupgrade - rev: v2.27.0 + rev: v2.28.0 hooks: - id: pyupgrade ``` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyupgrade-2.27.0/pyupgrade/_ast_helpers.py new/pyupgrade-2.28.0/pyupgrade/_ast_helpers.py --- old/pyupgrade-2.27.0/pyupgrade/_ast_helpers.py 2021-09-23 04:23:06.000000000 +0200 +++ new/pyupgrade-2.28.0/pyupgrade/_ast_helpers.py 2021-09-25 23:35:49.000000000 +0200 @@ -1,8 +1,12 @@ import ast import warnings +from typing import Any from typing import Container from typing import Dict +from typing import Iterable from typing import Set +from typing import Tuple +from typing import Type from typing import Union from tokenize_rt import Offset @@ -57,3 +61,43 @@ any(gen.is_async for gen in node.generators) or contains_await(node) ) + + +def _all_isinstance( + vals: Iterable[Any], + tp: Union[Type[Any], Tuple[Type[Any], ...]], +) -> bool: + return all(isinstance(v, tp) for v in vals) + + +def _fields_same(n1: ast.AST, n2: ast.AST) -> bool: + for (a1, v1), (a2, v2) in zip(ast.iter_fields(n1), ast.iter_fields(n2)): + # ignore ast attributes, they'll be covered by walk + if a1 != a2: + return False + elif _all_isinstance((v1, v2), ast.AST): + continue + elif _all_isinstance((v1, v2), (list, tuple)): + if len(v1) != len(v2): + return False + # ignore sequences which are all-ast, they'll be covered by walk + elif _all_isinstance(v1, ast.AST) and _all_isinstance(v2, ast.AST): + continue + elif v1 != v2: + return False + elif v1 != v2: + return False + return True + + +def targets_same(node1: ast.AST, node2: ast.AST) -> bool: + for t1, t2 in zip(ast.walk(node1), ast.walk(node2)): + # ignore `ast.Load` / `ast.Store` + if _all_isinstance((t1, t2), ast.expr_context): + continue + elif type(t1) != type(t2): + return False + elif not _fields_same(t1, t2): + return False + else: + return True diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyupgrade-2.27.0/pyupgrade/_plugins/legacy.py new/pyupgrade-2.28.0/pyupgrade/_plugins/legacy.py --- old/pyupgrade-2.27.0/pyupgrade/_plugins/legacy.py 2021-09-23 04:23:06.000000000 +0200 +++ new/pyupgrade-2.28.0/pyupgrade/_plugins/legacy.py 2021-09-25 23:35:49.000000000 +0200 @@ -2,21 +2,19 @@ import collections import contextlib import functools -from typing import Any from typing import Dict from typing import Generator from typing import Iterable from typing import List from typing import Set from typing import Tuple -from typing import Type -from typing import Union from tokenize_rt import Offset from tokenize_rt import Token from tokenize_rt import tokens_to_src from pyupgrade._ast_helpers import ast_to_offset +from pyupgrade._ast_helpers import targets_same from pyupgrade._data import register from pyupgrade._data import State from pyupgrade._data import TokenFunc @@ -26,6 +24,7 @@ from pyupgrade._token_helpers import find_token FUNC_TYPES = (ast.Lambda, ast.FunctionDef, ast.AsyncFunctionDef) +NON_LAMBDA_FUNC_TYPES = (ast.FunctionDef, ast.AsyncFunctionDef) def _fix_yield(i: int, tokens: List[Token]) -> None: @@ -36,44 +35,13 @@ tokens[i:block.end] = [Token('CODE', f'yield from {container}\n')] -def _all_isinstance( - vals: Iterable[Any], - tp: Union[Type[Any], Tuple[Type[Any], ...]], -) -> bool: - return all(isinstance(v, tp) for v in vals) - - -def _fields_same(n1: ast.AST, n2: ast.AST) -> bool: - for (a1, v1), (a2, v2) in zip(ast.iter_fields(n1), ast.iter_fields(n2)): - # ignore ast attributes, they'll be covered by walk - if a1 != a2: - return False - elif _all_isinstance((v1, v2), ast.AST): - continue - elif _all_isinstance((v1, v2), (list, tuple)): - if len(v1) != len(v2): - return False - # ignore sequences which are all-ast, they'll be covered by walk - elif _all_isinstance(v1, ast.AST) and _all_isinstance(v2, ast.AST): - continue - elif v1 != v2: - return False - elif v1 != v2: - return False - return True - - -def _targets_same(target: ast.AST, yield_value: ast.AST) -> bool: - for t1, t2 in zip(ast.walk(target), ast.walk(yield_value)): - # ignore `ast.Load` / `ast.Store` - if _all_isinstance((t1, t2), ast.expr_context): - continue - elif type(t1) != type(t2): - return False - elif not _fields_same(t1, t2): - return False - else: - return True +def _is_simple_base(base: ast.AST) -> bool: + return ( + isinstance(base, ast.Name) or ( + isinstance(base, ast.Attribute) and + _is_simple_base(base.value) + ) + ) class Scope: @@ -92,6 +60,7 @@ def __init__(self) -> None: self._scopes: List[Scope] = [] self.super_offsets: Set[Offset] = set() + self.old_super_offsets: Set[Tuple[Offset, str]] = set() self.yield_offsets: Set[Offset] = set() @contextlib.contextmanager @@ -137,7 +106,6 @@ len(node.args) == 2 and isinstance(node.args[0], ast.Name) and isinstance(node.args[1], ast.Name) and - # there are at least two scopes len(self._scopes) >= 2 and # the second to last scope is the class in arg1 isinstance(self._scopes[-2].node, ast.ClassDef) and @@ -148,6 +116,29 @@ node.args[1].id == self._scopes[-1].node.args.args[0].arg ): self.super_offsets.add(ast_to_offset(node)) + elif ( + # base.funcname(funcarg1, ...) + isinstance(node.func, ast.Attribute) and + len(node.args) >= 1 and + isinstance(node.args[0], ast.Name) and + len(self._scopes) >= 2 and + # last stack is a function whose first argument is the first + # argument of this function + isinstance(self._scopes[-1].node, NON_LAMBDA_FUNC_TYPES) and + node.func.attr == self._scopes[-1].node.name and + node.func.attr != '__new__' and + len(self._scopes[-1].node.args.args) >= 1 and + node.args[0].id == self._scopes[-1].node.args.args[0].arg and + # the function is an attribute of the contained class name + isinstance(self._scopes[-2].node, ast.ClassDef) and + len(self._scopes[-2].node.bases) == 1 and + _is_simple_base(self._scopes[-2].node.bases[0]) and + targets_same( + self._scopes[-2].node.bases[0], + node.func.value, + ) + ): + self.old_super_offsets.add((ast_to_offset(node), node.func.attr)) self.generic_visit(node) @@ -159,7 +150,7 @@ isinstance(node.body[0], ast.Expr) and isinstance(node.body[0].value, ast.Yield) and node.body[0].value.value is not None and - _targets_same(node.target, node.body[0].value.value) and + targets_same(node.target, node.body[0].value.value) and not node.orelse ): offset = ast_to_offset(node) @@ -198,5 +189,10 @@ for offset in visitor.super_offsets: yield offset, super_func + for offset, func_name in visitor.old_super_offsets: + template = f'super().{func_name}({{rest}})' + callback = functools.partial(find_and_replace_call, template=template) + yield offset, callback + for offset in visitor.yield_offsets: yield offset, _fix_yield diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyupgrade-2.27.0/pyupgrade/_plugins/six_calls.py new/pyupgrade-2.28.0/pyupgrade/_plugins/six_calls.py --- old/pyupgrade-2.27.0/pyupgrade/_plugins/six_calls.py 2021-09-23 04:23:06.000000000 +0200 +++ new/pyupgrade-2.28.0/pyupgrade/_plugins/six_calls.py 2021-09-25 23:35:49.000000000 +0200 @@ -51,7 +51,7 @@ 'assertRegex': '{args[0]}.assertRegex({rest})', } SIX_INT2BYTE_TMPL = 'bytes(({args[0]},))' -RAISE_FROM_TMPL = 'raise {args[0]} from {rest}' +RAISE_FROM_TMPL = 'raise {args[0]} from {args[1]}' RERAISE_TMPL = 'raise' RERAISE_2_TMPL = 'raise {args[1]}.with_traceback(None)' RERAISE_3_TMPL = 'raise {args[1]}.with_traceback({args[2]})' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyupgrade-2.27.0/pyupgrade/_token_helpers.py new/pyupgrade-2.28.0/pyupgrade/_token_helpers.py --- old/pyupgrade-2.27.0/pyupgrade/_token_helpers.py 2021-09-23 04:23:06.000000000 +0200 +++ new/pyupgrade-2.28.0/pyupgrade/_token_helpers.py 2021-09-25 23:35:49.000000000 +0200 @@ -195,7 +195,9 @@ for i in range(self.block, self.end): if ( tokens[i - 1].name in ('NL', 'NEWLINE') and - tokens[i].name in ('INDENT', UNIMPORTANT_WS) + tokens[i].name in ('INDENT', UNIMPORTANT_WS) and + # comments can have arbitrary indentation so ignore them + tokens[i + 1].name != 'COMMENT' ): token_indent = len(tokens[i].src) if block_indent is None: @@ -209,13 +211,17 @@ def dedent(self, tokens: List[Token]) -> None: if self.line: return - diff = self._minimum_indent(tokens) - self._initial_indent(tokens) + initial_indent = self._initial_indent(tokens) + diff = self._minimum_indent(tokens) - initial_indent for i in range(self.block, self.end): if ( tokens[i - 1].name in ('DEDENT', 'NL', 'NEWLINE') and tokens[i].name in ('INDENT', UNIMPORTANT_WS) ): - tokens[i] = tokens[i]._replace(src=tokens[i].src[diff:]) + # make sure we preserve *at least* the initial indent + s = tokens[i].src + s = s[:initial_indent] + s[initial_indent + diff:] + tokens[i] = tokens[i]._replace(src=s) def replace_condition(self, tokens: List[Token], new: List[Token]) -> None: start = self.start @@ -390,6 +396,16 @@ return tokens_to_src(tokens[start:end]).strip() +def _arg_contains_newline(tokens: List[Token], start: int, end: int) -> bool: + while tokens[start].name in {'NL', 'NEWLINE', UNIMPORTANT_WS}: + start += 1 + for i in range(start, end): + if tokens[i].name in {'NL', 'NEWLINE'}: + return True + else: + return False + + def replace_call( tokens: List[Token], start: int, @@ -403,6 +419,14 @@ for paren in parens: arg_strs[paren] = f'({arg_strs[paren]})' + # there are a few edge cases which cause syntax errors when the first + # argument contains newlines (especially when moved outside of a natural + # contiunuation context) + if _arg_contains_newline(tokens, *args[0]) and 0 not in parens: + # this attempts to preserve more of the whitespace by using the + # original non-stripped argument string + arg_strs[0] = f'({tokens_to_src(tokens[slice(*args[0])])})' + start_rest = args[0][1] + 1 while ( start_rest < end and diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyupgrade-2.27.0/setup.cfg new/pyupgrade-2.28.0/setup.cfg --- old/pyupgrade-2.27.0/setup.cfg 2021-09-23 04:23:06.000000000 +0200 +++ new/pyupgrade-2.28.0/setup.cfg 2021-09-25 23:35:49.000000000 +0200 @@ -1,6 +1,6 @@ [metadata] name = pyupgrade -version = 2.27.0 +version = 2.28.0 description = A tool to automatically upgrade syntax for newer versions. long_description = file: README.md long_description_content_type = text/markdown diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyupgrade-2.27.0/tests/ast_helpers_test.py new/pyupgrade-2.28.0/tests/ast_helpers_test.py --- old/pyupgrade-2.27.0/tests/ast_helpers_test.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pyupgrade-2.28.0/tests/ast_helpers_test.py 2021-09-25 23:35:49.000000000 +0200 @@ -0,0 +1,19 @@ +import ast + +from pyupgrade._ast_helpers import _fields_same +from pyupgrade._ast_helpers import targets_same + + +def test_targets_same(): + assert targets_same(ast.parse('global a, b'), ast.parse('global a, b')) + assert not targets_same(ast.parse('global a'), ast.parse('global b')) + + +def _get_body(expr): + body = ast.parse(expr).body[0] + assert isinstance(body, ast.Expr) + return body.value + + +def test_fields_same(): + assert not _fields_same(_get_body('x'), _get_body('1')) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyupgrade-2.27.0/tests/features/six_test.py new/pyupgrade-2.28.0/tests/features/six_test.py --- old/pyupgrade-2.27.0/tests/features/six_test.py 2021-09-23 04:23:06.000000000 +0200 +++ new/pyupgrade-2.28.0/tests/features/six_test.py 2021-09-25 23:35:49.000000000 +0200 @@ -126,6 +126,16 @@ 'six.raise_from(exc, exc_from)\n', 'raise exc from exc_from\n', ), + pytest.param( + 'six.raise_from(\n' + ' e,\n' + ' f,\n' + ')', + + 'raise e from f', + + id='six raise_from across multiple lines', + ), ( 'six.reraise(tp, exc, tb)\n', 'raise exc.with_traceback(tb)\n', @@ -347,6 +357,28 @@ '(x < y).values()', id='needs parentehsizing for Compare', ), + pytest.param( + 'x = six.itervalues(\n' + ' # comment\n' + ' x\n' + ')', + 'x = (\n' + ' # comment\n' + ' x\n' + ').values()', + id='multiline first argument with comment', + ), + pytest.param( + 'x = six.itervalues(\n' + ' # comment\n' + ' x,\n' + ')', + # TODO: ideally this would preserve whitespace better + 'x = (\n' + ' # comment\n' + ' x).values()', + id='multiline first argument with comment, trailing comma', + ), ), ) def test_fix_six(s, expected): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyupgrade-2.27.0/tests/features/super_test.py new/pyupgrade-2.28.0/tests/features/super_test.py --- old/pyupgrade-2.27.0/tests/features/super_test.py 2021-09-23 04:23:06.000000000 +0200 +++ new/pyupgrade-2.28.0/tests/features/super_test.py 2021-09-25 23:35:49.000000000 +0200 @@ -122,3 +122,107 @@ ) def test_fix_super(s, expected): assert _fix_plugins(s, settings=Settings(min_version=(3,))) == expected + + +@pytest.mark.parametrize( + 's', + ( + pytest.param( + 'class C(B):\n' + ' def f(self):\n' + ' B.f(notself)\n', + id='old style super, first argument is not first function arg', + ), + pytest.param( + 'class C(B1, B2):\n' + ' def f(self):\n' + ' B1.f(self)\n', + # TODO: is this safe to rewrite? I don't think so + id='old-style super, multiple inheritance first class', + ), + pytest.param( + 'class C(B1, B2):\n' + ' def f(self):\n' + ' B2.f(self)\n', + # TODO: is this safe to rewrite? I don't think so + id='old-style super, multiple inheritance not-first class', + ), + pytest.param( + 'class C(Base):\n' + ' def f(self):\n' + ' return [Base.f(self) for _ in ()]\n', + id='super in comprehension', + ), + pytest.param( + 'class C(Base):\n' + ' def f(self):\n' + ' def g():\n' + ' Base.f(self)\n' + ' g()\n', + id='super in nested functions', + ), + pytest.param( + 'class C(not_simple()):\n' + ' def f(self):\n' + ' not_simple().f(self)\n', + id='not a simple base', + ), + pytest.param( + 'class C(a().b):\n' + ' def f(self):\n' + ' a().b.f(self)\n', + id='non simple attribute base', + ), + pytest.param( + 'class C:\n' + ' @classmethod\n' + ' def make(cls, instance):\n' + ' ...\n' + 'class D(C):\n' + ' def find(self):\n' + ' return C.make(self)\n', + ), + pytest.param( + 'class C(tuple):\n' + ' def __new__(cls, arg):\n' + ' return tuple.__new__(cls, (arg,))\n', + id='super() does not work properly for __new__', + ), + ), +) +def test_old_style_class_super_noop(s): + assert _fix_plugins(s, settings=Settings(min_version=(3,))) == s + + +@pytest.mark.parametrize( + ('s', 'expected'), + ( + ( + 'class C(B):\n' + ' def f(self):\n' + ' B.f(self)\n' + ' B.f(self, arg, arg)\n', + 'class C(B):\n' + ' def f(self):\n' + ' super().f()\n' + ' super().f(arg, arg)\n', + ), + pytest.param( + 'class C(B):\n' + ' def f(self, a):\n' + ' B.f(\n' + ' self,\n' + ' a,\n' + ' )\n', + + 'class C(B):\n' + ' def f(self, a):\n' + ' super().f(\n' + ' a,\n' + ' )\n', + id='multi-line super call', + ), + ), +) +def test_old_style_class_super(s, expected): + assert _fix_plugins(s, settings=Settings(min_version=(3,))) == expected diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyupgrade-2.27.0/tests/features/versioned_branches_test.py new/pyupgrade-2.28.0/tests/features/versioned_branches_test.py --- old/pyupgrade-2.27.0/tests/features/versioned_branches_test.py 2021-09-23 04:23:06.000000000 +0200 +++ new/pyupgrade-2.28.0/tests/features/versioned_branches_test.py 2021-09-25 23:35:49.000000000 +0200 @@ -430,6 +430,20 @@ id='elif six.PY3 no else, indented', ), + pytest.param( + 'if True:\n' + ' if sys.version_info > (3,):\n' + ' print(3)\n' + ' # comment\n' + ' print(2+3)\n', + + 'if True:\n' + ' print(3)\n' + ' # comment\n' + ' print(2+3)\n', + + id='comment after dedented block', + ), ), ) def test_fix_py2_blocks(s, expected): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyupgrade-2.27.0/tests/features/yield_from_test.py new/pyupgrade-2.28.0/tests/features/yield_from_test.py --- old/pyupgrade-2.27.0/tests/features/yield_from_test.py 2021-09-23 04:23:06.000000000 +0200 +++ new/pyupgrade-2.28.0/tests/features/yield_from_test.py 2021-09-25 23:35:49.000000000 +0200 @@ -1,11 +1,7 @@ -import ast - import pytest from pyupgrade._data import Settings from pyupgrade._main import _fix_plugins -from pyupgrade._plugins.legacy import _fields_same -from pyupgrade._plugins.legacy import _targets_same @pytest.mark.parametrize( @@ -215,18 +211,3 @@ ) def test_fix_yield_from_noop(s): assert _fix_plugins(s, settings=Settings(min_version=(3,))) == s - - -def test_targets_same(): - assert _targets_same(ast.parse('global a, b'), ast.parse('global a, b')) - assert not _targets_same(ast.parse('global a'), ast.parse('global b')) - - -def _get_body(expr): - body = ast.parse(expr).body[0] - assert isinstance(body, ast.Expr) - return body.value - - -def test_fields_same(): - assert not _fields_same(_get_body('x'), _get_body('1'))