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-04-01 14:17:16
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pyupgrade (Old)
 and      /work/SRC/openSUSE:Factory/.python-pyupgrade.new.2401 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pyupgrade"

Thu Apr  1 14:17:16 2021 rev:7 rq:882038 version:2.11.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pyupgrade/python-pyupgrade.changes        
2021-02-21 22:14:16.494097016 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-pyupgrade.new.2401/python-pyupgrade.changes  
    2021-04-01 14:18:32.080094240 +0200
@@ -1,0 +2,9 @@
+Mon Mar 22 21:04:52 UTC 2021 - Sebastian Wagner <[email protected]>
+
+- Update to version 2.10.1
+ - fix nested union replaces
+- Update to version 2.11.0
+ - dequote annotations: quoted annotations + `from __future__ import 
annotations`
+ - rewrite open even with mode=: Rewrite open_mode even if mode is passed by 
name
+
+-------------------------------------------------------------------

Old:
----
  python-pyupgrade-2.10.0.tar.gz

New:
----
  python-pyupgrade-2.11.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pyupgrade.spec ++++++
--- /var/tmp/diff_new_pack.GcbXOx/_old  2021-04-01 14:18:32.660095142 +0200
+++ /var/tmp/diff_new_pack.GcbXOx/_new  2021-04-01 14:18:32.664095149 +0200
@@ -19,7 +19,7 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %define skip_python2 1
 Name:           python-pyupgrade
-Version:        2.10.0
+Version:        2.11.0
 Release:        0
 Summary:        A tool to automatically upgrade syntax for newer versions
 License:        MIT
@@ -37,7 +37,7 @@
 BuildRequires:  fdupes
 Requires:       python-tokenize-rt >= 3.2.0
 Requires(post):   update-alternatives
-Requires(postun):  update-alternatives
+Requires(postun):update-alternatives
 BuildArch:      noarch
 
 %python_subpackages

++++++ python-pyupgrade-2.10.0.tar.gz -> python-pyupgrade-2.11.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyupgrade-2.10.0/.pre-commit-config.yaml 
new/pyupgrade-2.11.0/.pre-commit-config.yaml
--- old/pyupgrade-2.10.0/.pre-commit-config.yaml        2021-02-08 
01:10:19.000000000 +0100
+++ new/pyupgrade-2.11.0/.pre-commit-config.yaml        2021-03-20 
22:13:59.000000000 +0100
@@ -11,20 +11,20 @@
     -   id: requirements-txt-fixer
     -   id: trailing-whitespace
 -   repo: https://github.com/asottile/setup-cfg-fmt
-    rev: v1.16.0
+    rev: v1.17.0
     hooks:
     -   id: setup-cfg-fmt
 -   repo: https://gitlab.com/pycqa/flake8
-    rev: 3.8.4
+    rev: 3.9.0
     hooks:
     -   id: flake8
         additional_dependencies: [flake8-typing-imports==1.7.0]
 -   repo: https://github.com/pre-commit/mirrors-autopep8
-    rev: v1.5.4
+    rev: v1.5.5
     hooks:
     -   id: autopep8
 -   repo: https://github.com/asottile/reorder_python_imports
-    rev: v2.3.6
+    rev: v2.4.0
     hooks:
     -   id: reorder-python-imports
         args: [--py3-plus]
@@ -34,11 +34,11 @@
     -   id: add-trailing-comma
         args: [--py36-plus]
 -   repo: https://github.com/asottile/pyupgrade
-    rev: v2.10.0
+    rev: v2.11.0
     hooks:
     -   id: pyupgrade
         args: [--py36-plus]
 -   repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v0.800
+    rev: v0.812
     hooks:
     -   id: mypy
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyupgrade-2.10.0/README.md 
new/pyupgrade-2.11.0/README.md
--- old/pyupgrade-2.10.0/README.md      2021-02-08 01:10:19.000000000 +0100
+++ new/pyupgrade-2.11.0/README.md      2021-03-20 22:13:59.000000000 +0100
@@ -20,7 +20,7 @@
 
 ```yaml
 -   repo: https://github.com/asottile/pyupgrade
-    rev: v2.10.0
+    rev: v2.11.0
     hooks:
     -   id: pyupgrade
 ```
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyupgrade-2.10.0/pyupgrade/_plugins/open_mode.py 
new/pyupgrade-2.11.0/pyupgrade/_plugins/open_mode.py
--- old/pyupgrade-2.10.0/pyupgrade/_plugins/open_mode.py        2021-02-08 
01:10:19.000000000 +0100
+++ new/pyupgrade-2.11.0/pyupgrade/_plugins/open_mode.py        2021-03-20 
22:13:59.000000000 +0100
@@ -1,6 +1,8 @@
 import ast
+import functools
 from typing import Iterable
 from typing import List
+from typing import NamedTuple
 from typing import Tuple
 
 from tokenize_rt import Offset
@@ -21,19 +23,28 @@
 U_MODE_REPLACE = U_MODE_REPLACE_R | U_MODE_REMOVE_U
 
 
-def _fix_open_mode(i: int, tokens: List[Token]) -> None:
+class FunctionArg(NamedTuple):
+    arg_idx: int
+    value: ast.expr
+
+
+def _fix_open_mode(i: int, tokens: List[Token], *, arg_idx: int) -> None:
     j = find_open_paren(tokens, i)
     func_args, end = parse_call_args(tokens, j)
-    mode = tokens_to_src(tokens[slice(*func_args[1])])
-    mode_stripped = mode.strip().strip('"\'')
+    mode = tokens_to_src(tokens[slice(*func_args[arg_idx])])
+    mode_stripped = mode.split('=')[-1]
+    mode_stripped = mode_stripped.strip().strip('"\'')
     if mode_stripped in U_MODE_REMOVE:
-        del tokens[func_args[0][1]:func_args[1][1]]
+        if arg_idx == 0:
+            del tokens[func_args[arg_idx][0]: func_args[arg_idx + 1][0]]
+        else:
+            del tokens[func_args[arg_idx - 1][1]:func_args[arg_idx][1]]
     elif mode_stripped in U_MODE_REPLACE_R:
         new_mode = mode.replace('U', 'r')
-        tokens[slice(*func_args[1])] = [Token('SRC', new_mode)]
+        tokens[slice(*func_args[arg_idx])] = [Token('SRC', new_mode)]
     elif mode_stripped in U_MODE_REMOVE_U:
         new_mode = mode.replace('U', '')
-        tokens[slice(*func_args[1])] = [Token('SRC', new_mode)]
+        tokens[slice(*func_args[arg_idx])] = [Token('SRC', new_mode)]
     else:
         raise AssertionError(f'unreachable: {mode!r}')
 
@@ -48,11 +59,37 @@
             state.settings.min_version >= (3,) and
             isinstance(node.func, ast.Name) and
             node.func.id == 'open' and
-            not has_starargs(node) and
-            len(node.args) >= 2 and
-            isinstance(node.args[1], ast.Str) and (
+            not has_starargs(node)
+    ):
+        if len(node.args) >= 2 and isinstance(node.args[1], ast.Str):
+            if (
                 node.args[1].s in U_MODE_REPLACE or
                 (len(node.args) == 2 and node.args[1].s in U_MODE_REMOVE)
+            ):
+                func = functools.partial(
+                    _fix_open_mode,
+                    arg_idx=1,
+                )
+                yield ast_to_offset(node), func
+        elif node.keywords and (len(node.keywords) + len(node.args) > 1):
+            mode = next(
+                (
+                    FunctionArg(n, keyword.value)
+                    for n, keyword in enumerate(node.keywords)
+                    if keyword.arg == 'mode'
+                ),
+                None,
             )
-    ):
-        yield ast_to_offset(node), _fix_open_mode
+            if (
+                mode is not None and
+                isinstance(mode.value, ast.Str) and
+                (
+                    mode.value.s in U_MODE_REMOVE or
+                    mode.value.s in U_MODE_REPLACE
+                )
+            ):
+                func = functools.partial(
+                    _fix_open_mode,
+                    arg_idx=len(node.args) + mode.arg_idx,
+                )
+                yield ast_to_offset(node), func
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyupgrade-2.10.0/pyupgrade/_plugins/typing_pep563.py 
new/pyupgrade-2.11.0/pyupgrade/_plugins/typing_pep563.py
--- old/pyupgrade-2.10.0/pyupgrade/_plugins/typing_pep563.py    1970-01-01 
01:00:00.000000000 +0100
+++ new/pyupgrade-2.11.0/pyupgrade/_plugins/typing_pep563.py    2021-03-20 
22:13:59.000000000 +0100
@@ -0,0 +1,168 @@
+import ast
+import functools
+import sys
+from typing import Iterable
+from typing import List
+from typing import Optional
+from typing import Sequence
+from typing import Tuple
+
+from tokenize_rt import Offset
+
+from pyupgrade._ast_helpers import ast_to_offset
+from pyupgrade._data import register
+from pyupgrade._data import State
+from pyupgrade._data import Token
+from pyupgrade._data import TokenFunc
+
+
+def _supported_version(state: State) -> bool:
+    return (
+        state.settings.min_version >= (3, 10) or
+        'annotations' in state.from_imports['__future__']
+    )
+
+
+def _dequote(i: int, tokens: List[Token], *, new: str) -> None:
+    tokens[i] = tokens[i]._replace(src=new)
+
+
+def _get_name(node: ast.expr) -> str:
+    if isinstance(node, ast.Name):
+        return node.id
+    elif isinstance(node, ast.Attribute):
+        return node.attr
+    else:
+        raise AssertionError(f'expected Name or Attribute: {ast.dump(node)}')
+
+
+def _get_keyword_value(
+        keywords: List[ast.keyword],
+        keyword: str,
+) -> Optional[ast.expr]:
+    for kw in keywords:
+        if kw.arg == keyword:
+            return kw.value
+    else:
+        return None
+
+
+def _process_call(node: ast.Call) -> Iterable[ast.AST]:
+    name = _get_name(node.func)
+    args = node.args
+    keywords = node.keywords
+    if name == 'TypedDict':
+        if keywords:
+            for keyword in keywords:
+                yield keyword.value
+        elif len(args) != 2:  # garbage
+            pass
+        elif isinstance(args[1], ast.Dict):
+            yield from args[1].values
+        else:
+            raise AssertionError(f'expected ast.Dict: {ast.dump(args[1])}')
+    elif name == 'NamedTuple':
+        if len(args) == 2:
+            fields: Optional[ast.expr] = args[1]
+        elif keywords:
+            fields = _get_keyword_value(keywords, 'fields')
+        else:  # garbage
+            fields = None
+
+        if isinstance(fields, ast.List):
+            for elt in fields.elts:
+                if isinstance(elt, ast.Tuple) and len(elt.elts) == 2:
+                    yield elt.elts[1]
+        elif fields is not None:
+            raise AssertionError(f'expected ast.List: {ast.dump(fields)}')
+    elif name in {
+        'Arg',
+        'DefaultArg',
+        'NamedArg',
+        'DefaultNamedArg',
+        'VarArg',
+        'KwArg',
+    }:
+        if args:
+            yield args[0]
+        else:
+            keyword_value = _get_keyword_value(keywords, 'type')
+            if keyword_value is not None:
+                yield keyword_value
+
+
+def _process_subscript(node: ast.Subscript) -> Iterable[ast.AST]:
+    name = _get_name(node.value)
+    if name == 'Annotated':
+        if sys.version_info >= (3, 9):  # pragma: no cover (py39+)
+            node_slice: ast.expr = node.slice
+        elif isinstance(node.slice, ast.Index):  # pragma: no cover (<py39)
+            node_slice = node.slice.value
+        else:  # pragma: no cover (<py39)
+            pass  # unexpected slice type
+
+        if isinstance(node_slice, ast.Tuple):
+            if node_slice.elts:
+                yield node_slice.elts[0]
+        else:
+            raise AssertionError(f'expected ast.Tuple: {ast.dump(node_slice)}')
+    elif name != 'Literal':
+        yield node.slice
+
+
+def _replace_string_literal(
+        annotation: ast.expr,
+) -> Iterable[Tuple[Offset, TokenFunc]]:
+    nodes: List[ast.AST] = [annotation]
+    while nodes:
+        node = nodes.pop()
+        if isinstance(node, ast.Call):
+            nodes.extend(_process_call(node))
+        elif isinstance(node, ast.Subscript):
+            nodes.extend(_process_subscript(node))
+        elif isinstance(node, ast.Str):
+            func = functools.partial(_dequote, new=node.s)
+            yield ast_to_offset(node), func
+        else:
+            for name in node._fields:
+                value = getattr(node, name)
+                if isinstance(value, ast.AST):
+                    nodes.append(value)
+                elif isinstance(value, list):
+                    nodes.extend(value)
+
+
+def _process_args(
+        args: Sequence[Optional[ast.arg]],
+) -> Iterable[Tuple[Offset, TokenFunc]]:
+    for arg in args:
+        if arg is not None and arg.annotation is not None:
+            yield from _replace_string_literal(arg.annotation)
+
+
+@register(ast.FunctionDef)
+def visit_FunctionDef(
+        state: State,
+        node: ast.FunctionDef,
+        parent: ast.AST,
+) -> Iterable[Tuple[Offset, TokenFunc]]:
+    if not _supported_version(state):
+        return
+
+    yield from _process_args([node.args.vararg, node.args.kwarg])
+    yield from _process_args(node.args.args)
+    yield from _process_args(node.args.kwonlyargs)
+    yield from _process_args(getattr(node.args, 'posonlyargs', []))
+    if node.returns is not None:
+        yield from _replace_string_literal(node.returns)
+
+
+@register(ast.AnnAssign)
+def visit_AnnAssign(
+        state: State,
+        node: ast.AnnAssign,
+        parent: ast.AST,
+) -> Iterable[Tuple[Offset, TokenFunc]]:
+    if not _supported_version(state):
+        return
+    yield from _replace_string_literal(node.annotation)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyupgrade-2.10.0/pyupgrade/_plugins/typing_pep604.py 
new/pyupgrade-2.11.0/pyupgrade/_plugins/typing_pep604.py
--- old/pyupgrade-2.10.0/pyupgrade/_plugins/typing_pep604.py    2021-02-08 
01:10:19.000000000 +0100
+++ new/pyupgrade-2.11.0/pyupgrade/_plugins/typing_pep604.py    2021-03-20 
22:13:59.000000000 +0100
@@ -5,6 +5,7 @@
 from typing import List
 from typing import Tuple
 
+from tokenize_rt import NON_CODING_TOKENS
 from tokenize_rt import Offset
 from tokenize_rt import Token
 
@@ -35,40 +36,70 @@
         i: int,
         tokens: List[Token],
         *,
-        arg: ast.expr,
         arg_count: int,
 ) -> None:
-    arg_offset = ast_to_offset(arg)
-    j = find_token(tokens, i, '[')
-    to_delete = []
-    commas: List[int] = []
-
-    arg_depth = -1
     depth = 1
+    parens_done = []
+    open_parens = []
+    commas = []
+    coding_depth = None
+
+    j = find_token(tokens, i, '[')
     k = j + 1
     while depth:
+        # it's possible our first coding token is a close paren
+        # so make sure this is separate from the if chain below
+        if (
+                tokens[k].name not in NON_CODING_TOKENS and
+                tokens[k].src != '(' and
+                coding_depth is None
+        ):
+            if tokens[k].src == ')':  # the coding token was an empty tuple
+                coding_depth = depth - 1
+            else:
+                coding_depth = depth
+
         if tokens[k].src in OPENING:
-            if arg_depth == -1:
-                to_delete.append(k)
+            if tokens[k].src == '(':
+                open_parens.append((depth, k))
+
             depth += 1
         elif tokens[k].src in CLOSING:
+            if tokens[k].src == ')':
+                paren_depth, open_paren = open_parens.pop()
+                parens_done.append((paren_depth, (open_paren, k)))
+
             depth -= 1
-            if 0 < depth < arg_depth:
-                to_delete.append(k)
-        elif tokens[k].offset == arg_offset:
-            arg_depth = depth
-        elif depth == arg_depth and tokens[k].src == ',':
-            if len(commas) >= arg_count - 1:
-                to_delete.append(k)
-            else:
-                commas.append(k)
+        elif tokens[k].src == ',':
+            commas.append((depth, k))
 
         k += 1
     k -= 1
 
+    assert coding_depth is not None
+    assert not open_parens, open_parens
+    comma_depth = min((depth for depth, _ in commas), default=sys.maxsize)
+    min_depth = min(comma_depth, coding_depth)
+
+    to_delete = [
+        paren
+        for depth, positions in parens_done
+        if depth < min_depth
+        for paren in positions
+    ]
+
+    if comma_depth <= coding_depth:
+        comma_positions = [k for depth, k in commas if depth == comma_depth]
+        if len(comma_positions) == arg_count:
+            to_delete.append(comma_positions.pop())
+    else:
+        comma_positions = []
+
+    to_delete.sort()
+
     if tokens[j].line == tokens[k].line:
         del tokens[k]
-        for comma in commas:
+        for comma in comma_positions:
             tokens[comma] = Token('CODE', ' |')
         for paren in reversed(to_delete):
             del tokens[paren]
@@ -77,7 +108,7 @@
         tokens[j] = tokens[j]._replace(src='(')
         tokens[k] = tokens[k]._replace(src=')')
 
-        for comma in commas:
+        for comma in comma_positions:
             tokens[comma] = Token('CODE', ' |')
         for paren in reversed(to_delete):
             del tokens[paren]
@@ -116,13 +147,11 @@
 
         if isinstance(node_slice, ast.Tuple):
             if node_slice.elts:
-                arg = node_slice.elts[0]
                 arg_count = len(node_slice.elts)
             else:
                 return  # empty Union
         else:
-            arg = node_slice
             arg_count = 1
 
-        func = functools.partial(_fix_union, arg=arg, arg_count=arg_count)
+        func = functools.partial(_fix_union, arg_count=arg_count)
         yield ast_to_offset(node), func
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyupgrade-2.10.0/setup.cfg 
new/pyupgrade-2.11.0/setup.cfg
--- old/pyupgrade-2.10.0/setup.cfg      2021-02-08 01:10:19.000000000 +0100
+++ new/pyupgrade-2.11.0/setup.cfg      2021-03-20 22:13:59.000000000 +0100
@@ -1,6 +1,6 @@
 [metadata]
 name = pyupgrade
-version = 2.10.0
+version = 2.11.0
 description = A tool to automatically upgrade syntax for newer versions.
 long_description = file: README.md
 long_description_content_type = text/markdown
@@ -26,15 +26,15 @@
     tokenize-rt>=3.2.0
 python_requires = >=3.6.1
 
-[options.entry_points]
-console_scripts =
-    pyupgrade = pyupgrade._main:main
-
 [options.packages.find]
 exclude =
     tests*
     testing*
 
+[options.entry_points]
+console_scripts =
+    pyupgrade = pyupgrade._main:main
+
 [bdist_wheel]
 universal = True
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pyupgrade-2.10.0/tests/_plugins/typing_pep604_test.py 
new/pyupgrade-2.11.0/tests/_plugins/typing_pep604_test.py
--- old/pyupgrade-2.10.0/tests/_plugins/typing_pep604_test.py   1970-01-01 
01:00:00.000000000 +0100
+++ new/pyupgrade-2.11.0/tests/_plugins/typing_pep604_test.py   2021-03-20 
22:13:59.000000000 +0100
@@ -0,0 +1,28 @@
+import pytest
+from tokenize_rt import src_to_tokens
+from tokenize_rt import tokens_to_src
+
+from pyupgrade._plugins.typing_pep604 import _fix_union
+
+
[email protected](
+    ('s', 'arg_count', 'expected'),
+    (
+        ('Union[a, b]', 2, 'a | b'),
+        ('Union[(a, b)]', 2, 'a | b'),
+        ('Union[(a,)]', 1, 'a'),
+        ('Union[(((a, b)))]', 2, 'a | b'),
+        pytest.param('Union[((a), b)]', 2, '(a) | b', id='wat'),
+        ('Union[(((a,), b))]', 2, '(a,) | b'),
+        ('Union[((a,), (a, b))]', 2, '(a,) | (a, b)'),
+        ('Union[((a))]', 1, 'a'),
+        ('Union[a()]', 1, 'a()'),
+        ('Union[a(b, c)]', 1, 'a(b, c)'),
+        ('Union[(a())]', 1, 'a()'),
+        ('Union[(())]', 1, '()'),
+    ),
+)
+def test_fix_union_edge_cases(s, arg_count, expected):
+    tokens = src_to_tokens(s)
+    _fix_union(0, tokens, arg_count=arg_count)
+    assert tokens_to_src(tokens) == expected
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyupgrade-2.10.0/tests/features/open_mode_test.py 
new/pyupgrade-2.11.0/tests/features/open_mode_test.py
--- old/pyupgrade-2.10.0/tests/features/open_mode_test.py       2021-02-08 
01:10:19.000000000 +0100
+++ new/pyupgrade-2.11.0/tests/features/open_mode_test.py       2021-03-20 
22:13:59.000000000 +0100
@@ -9,9 +9,13 @@
     (
         # already a reduced mode
         'open("foo", "w")',
+        'open("foo", mode="w")',
         'open("foo", "rb")',
         # nonsense mode
         'open("foo", "Uw")',
+        'open("foo", qux="r")',
+        'open("foo", 3)',
+        'open(mode="r")',
         # TODO: could maybe be rewritten to remove t?
         'open("foo", "wt")',
         # don't remove this, they meant to use `encoding=`
@@ -26,12 +30,38 @@
     ('s', 'expected'),
     (
         ('open("foo", "U")', 'open("foo")'),
+        ('open("foo", mode="U")', 'open("foo")'),
         ('open("foo", "Ur")', 'open("foo")'),
+        ('open("foo", mode="Ur")', 'open("foo")'),
         ('open("foo", "Ub")', 'open("foo", "rb")'),
+        ('open("foo", mode="Ub")', 'open("foo", mode="rb")'),
         ('open("foo", "rUb")', 'open("foo", "rb")'),
+        ('open("foo", mode="rUb")', 'open("foo", mode="rb")'),
         ('open("foo", "r")', 'open("foo")'),
+        ('open("foo", mode="r")', 'open("foo")'),
         ('open("foo", "rt")', 'open("foo")'),
+        ('open("foo", mode="rt")', 'open("foo")'),
         ('open("f", "r", encoding="UTF-8")', 'open("f", encoding="UTF-8")'),
+        (
+            'open("f", mode="r", encoding="UTF-8")',
+            'open("f", encoding="UTF-8")',
+        ),
+        (
+            'open(file="f", mode="r", encoding="UTF-8")',
+            'open(file="f", encoding="UTF-8")',
+        ),
+        (
+            'open("f", encoding="UTF-8", mode="r")',
+            'open("f", encoding="UTF-8")',
+        ),
+        (
+            'open(file="f", encoding="UTF-8", mode="r")',
+            'open(file="f", encoding="UTF-8")',
+        ),
+        (
+            'open(mode="r", encoding="UTF-8", file="t.py")',
+            'open( encoding="UTF-8", file="t.py")',
+        ),
     ),
 )
 def test_fix_open_mode(s, expected):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pyupgrade-2.10.0/tests/features/typing_pep563_test.py 
new/pyupgrade-2.11.0/tests/features/typing_pep563_test.py
--- old/pyupgrade-2.10.0/tests/features/typing_pep563_test.py   1970-01-01 
01:00:00.000000000 +0100
+++ new/pyupgrade-2.11.0/tests/features/typing_pep563_test.py   2021-03-20 
22:13:59.000000000 +0100
@@ -0,0 +1,365 @@
+import sys
+
+import pytest
+
+from pyupgrade._data import Settings
+from pyupgrade._main import _fix_plugins
+
+
[email protected](
+    ('s', 'version'),
+    (
+        pytest.param(
+            'from typing import Literal\n'
+            'x: "str"\n',
+            (2, 7),
+            id='not python 3.10+',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x: Literal["foo", "bar"]\n',
+            (3,),
+            id='Literal',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x = TypeVar("x", "str")\n',
+            (3,),
+            id='TypeVar',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x = cast(x, "str")\n',
+            (3,),
+            id='cast',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'X = List["MyClass"]\n',
+            (3,),
+            id='Alias',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'X: MyCallable("X")\n',
+            (3,),
+            id='Custom callable',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'def foo(x, *args, **kwargs): ...\n',
+            (3,),
+            id='Untyped',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'def foo(*, inplace): ...\n',
+            (3,),
+            id='Kwonly, untyped',
+        ),
+    ),
+)
+def test_fix_typing_pep563_noop(s, version):
+    assert _fix_plugins(s, settings=Settings(min_version=version)) == s
+
+
[email protected](
+    ('s', 'expected'),
+    (
+        pytest.param(
+            'from __future__ import annotations\n'
+            'def foo(var: "MyClass") -> "MyClass":\n'
+            '   x: "MyClass"\n',
+
+            'from __future__ import annotations\n'
+            'def foo(var: MyClass) -> MyClass:\n'
+            '   x: MyClass\n',
+
+            id='Simple annotation',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'def foo(*, inplace: "bool"): ...\n',
+
+            'from __future__ import annotations\n'
+            'def foo(*, inplace: bool): ...\n',
+
+            id='Kwonly, typed',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'def foo(*args: "str", **kwargs: "int"): ...\n',
+
+            'from __future__ import annotations\n'
+            'def foo(*args: str, **kwargs: int): ...\n',
+
+            id='Vararg and kwarg typed',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x: Tuple["MyClass"]\n',
+
+            'from __future__ import annotations\n'
+            'x: Tuple[MyClass]\n',
+
+            id='Tuple',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x: Callable[["MyClass"], None]\n',
+
+            'from __future__ import annotations\n'
+            'x: Callable[[MyClass], None]\n',
+
+            id='List within Callable',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'class Foo(NamedTuple):\n'
+            '    x: "MyClass"\n',
+
+            'from __future__ import annotations\n'
+            'class Foo(NamedTuple):\n'
+            '    x: MyClass\n',
+
+            id='Inherit from NamedTuple',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'class D(TypedDict):\n'
+            '    E: TypedDict("E", foo="int", total=False)\n',
+
+            'from __future__ import annotations\n'
+            'class D(TypedDict):\n'
+            '    E: TypedDict("E", foo=int, total=False)\n',
+
+            id='TypedDict keyword syntax',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'class D(TypedDict):\n'
+            '    E: TypedDict("E", {"foo": "int"})\n',
+
+            'from __future__ import annotations\n'
+            'class D(TypedDict):\n'
+            '    E: TypedDict("E", {"foo": int})\n',
+
+            id='TypedDict dict syntax',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'class D(typing.TypedDict):\n'
+            '    E: typing.TypedDict("E", {"foo": "int"})\n',
+
+            'from __future__ import annotations\n'
+            'class D(typing.TypedDict):\n'
+            '    E: typing.TypedDict("E", {"foo": int})\n',
+
+            id='typing.TypedDict',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'class D(TypedDict):\n'
+            '    E: TypedDict("E")\n',
+
+            'from __future__ import annotations\n'
+            'class D(TypedDict):\n'
+            '    E: TypedDict("E")\n',
+
+            id='TypedDict no type (invalid syntax)',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x: Annotated["str", "metadata"]\n',
+
+            'from __future__ import annotations\n'
+            'x: Annotated[str, "metadata"]\n',
+
+            id='Annotated',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x: typing.Annotated["str", "metadata"]\n',
+
+            'from __future__ import annotations\n'
+            'x: typing.Annotated[str, "metadata"]\n',
+
+            id='typing.Annotated',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x: Annotated[()]\n',
+
+            'from __future__ import annotations\n'
+            'x: Annotated[()]\n',
+
+            id='Empty Annotated (garbage)',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x: Arg("str", "name")\n',
+
+            'from __future__ import annotations\n'
+            'x: Arg(str, "name")\n',
+
+            id='Arg',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x: DefaultArg("str", "name")\n',
+
+            'from __future__ import annotations\n'
+            'x: DefaultArg(str, "name")\n',
+
+            id='DefaultArg',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x: NamedArg("str", "name")\n',
+
+            'from __future__ import annotations\n'
+            'x: NamedArg(str, "name")\n',
+
+            id='NamedArg',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x: DefaultNamedArg("str", "name")\n',
+
+            'from __future__ import annotations\n'
+            'x: DefaultNamedArg(str, "name")\n',
+
+            id='DefaultNamedArg',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x: DefaultNamedArg("str", name="name")\n',
+
+            'from __future__ import annotations\n'
+            'x: DefaultNamedArg(str, name="name")\n',
+
+            id='DefaultNamedArg with one keyword argument',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x: DefaultNamedArg(name="name", type="str")\n',
+
+            'from __future__ import annotations\n'
+            'x: DefaultNamedArg(name="name", type=str)\n',
+
+            id='DefaultNamedArg with keyword arguments',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x: DefaultNamedArg(name="name", quox="str")\n',
+
+            'from __future__ import annotations\n'
+            'x: DefaultNamedArg(name="name", quox="str")\n',
+
+            id='DefaultNamedArg with invalid arguments',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x: DefaultNamedArg(name="name")\n',
+
+            'from __future__ import annotations\n'
+            'x: DefaultNamedArg(name="name")\n',
+
+            id='DefaultNamedArg with no type (invalid syntax)',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x: VarArg("str")\n',
+
+            'from __future__ import annotations\n'
+            'x: VarArg(str)\n',
+
+            id='VarArg',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x: List[List[List["MyClass"]]]\n',
+
+            'from __future__ import annotations\n'
+            'x: List[List[List[MyClass]]]\n',
+
+            id='Nested',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x: NamedTuple("X", [("foo", "int"), ("bar", "str")])\n',
+
+            'from __future__ import annotations\n'
+            'x: NamedTuple("X", [("foo", int), ("bar", str)])\n',
+
+            id='NamedTuple with types, no kwarg',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])\n',
+
+            'from __future__ import annotations\n'
+            'x: NamedTuple("X", fields=[("foo", int), ("bar", str)])\n',
+
+            id='NamedTuple with types, one kwarg',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x: NamedTuple(typename="X", fields=[("foo", "int")])\n',
+
+            'from __future__ import annotations\n'
+            'x: NamedTuple(typename="X", fields=[("foo", int)])\n',
+
+            id='NamedTuple with types, two kwargs',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x: NamedTuple("X", [("foo",), ("bar",)])\n',
+
+            'from __future__ import annotations\n'
+            'x: NamedTuple("X", [("foo",), ("bar",)])\n',
+
+            id='NamedTuple with length-1 tuples (invalid syntax)',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x: NamedTuple("X", ["foo", "bar"])\n',
+
+            'from __future__ import annotations\n'
+            'x: NamedTuple("X", ["foo", "bar"])\n',
+
+            id='NamedTuple with missing types (invalid syntax)',
+        ),
+        pytest.param(
+            'from __future__ import annotations\n'
+            'x: NamedTuple()\n',
+
+            'from __future__ import annotations\n'
+            'x: NamedTuple()\n',
+
+            id='NamedTuple with no args (invalid syntax)',
+        ),
+    ),
+)
+def test_fix_typing_pep563(s, expected):
+    ret = _fix_plugins(s, settings=Settings(min_version=(3, 7)))
+    assert ret == expected
+
+
[email protected](
+    sys.version_info < (3, 8),
+    reason='posonly args not available in Python3.7',
+)
+def test_fix_typing_pep563_posonlyargs():
+    s = (
+        'from __future__ import annotations\n'
+        'def foo(var0, /, var1: "MyClass") -> "MyClass":\n'
+        '   x: "MyClass"\n'
+    )
+    expected = (
+        'from __future__ import annotations\n'
+        'def foo(var0, /, var1: MyClass) -> MyClass:\n'
+        '   x: MyClass\n'
+    )
+    ret = _fix_plugins(s, settings=Settings(min_version=(3, 8)))
+    assert ret == expected
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pyupgrade-2.10.0/tests/features/typing_pep604_test.py 
new/pyupgrade-2.11.0/tests/features/typing_pep604_test.py
--- old/pyupgrade-2.10.0/tests/features/typing_pep604_test.py   2021-02-08 
01:10:19.000000000 +0100
+++ new/pyupgrade-2.11.0/tests/features/typing_pep604_test.py   2021-03-20 
22:13:59.000000000 +0100
@@ -165,6 +165,15 @@
 
             id='Optional rewrite multi-line',
         ),
+        pytest.param(
+            'from typing import Union, Sequence\n'
+            'def f(x: Union[Union[A, B], Sequence[Union[C, D]]]): pass\n',
+
+            'from typing import Union, Sequence\n'
+            'def f(x: A | B | Sequence[C | D]): pass\n',
+
+            id='nested unions',
+        ),
     ),
 )
 def test_fix_pep604_types(s, expected):

Reply via email to