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):