nebius/SWE-bench-extra huggingface dataset:
Repository: asottile/pyupgrade

Issue:
`set` with trailing comma after list causes syntax error
```python
set(
    [
        1, 2, 3,
    ],
)
```

currently gets rewritten to (`SyntaxError`)

```python
{
        1, 2, 3,
    ,
}
```

Comments:

Patch:
diff --git a/pyupgrade.py b/pyupgrade.py
index c7a8d26..2f8626c 100644
--- a/pyupgrade.py
+++ b/pyupgrade.py
@@ -233,6 +233,8 @@ def _victims(tokens, start, arg, gen):
                 ends.extend((i - 1, i))
             else:
                 ends.append(i)
+            if len(brace_stack) > 1 and tokens[i + 1].src == ',':
+                ends.append(i + 1)
 
         if is_end_brace:
             brace_stack.pop()
@@ -245,9 +247,9 @@ def _victims(tokens, start, arg, gen):
         while tokens[i].name in NON_CODING_TOKENS:
             i -= 1
         if tokens[i].src == ',':
-            ends = sorted(set(ends + [i]))
+            ends.append(i)
 
-    return Victims(starts, ends, first_comma_index, arg_index)
+    return Victims(starts, sorted(set(ends)), first_comma_index, arg_index)
 
 
 def _find_token(tokens, i, token):  # type: (List[Token], int, Token) -> int


Test patch:
diff --git a/tests/set_literals_test.py b/tests/set_literals_test.py
index 22ac4d6..50363be 100644
--- a/tests/set_literals_test.py
+++ b/tests/set_literals_test.py
@@ -85,6 +85,16 @@ def test_fix_sets_noop(s):
             '    x for x in y\n'
             '}',
         ),
+        (
+            'set(\n'
+            '    [\n'
+            '        99, 100,\n'
+            '    ],\n'
+            ')\n',
+            '{\n'
+            '        99, 100,\n'
+            '}\n',
+        ),
     ),
 )
 def test_sets(s, expected):
Repository: asottile/pyupgrade

Issue:
`str()` native literal should be just `''`
https://github.com/asottile/pyupgrade#forced-strnative-literals

```python
str()  # ''
```

Comments:

Patch:
diff --git a/README.md b/README.md
index cada007..ec67866 100644
--- a/README.md
+++ b/README.md
@@ -181,6 +181,7 @@ Availability:
 - `--py3-plus` is passed on the commandline.
 
 ```python
+str()       # "''"
 str("foo")  # "foo"
 ```
 
diff --git a/pyupgrade.py b/pyupgrade.py
index a51cb50..9bbe886 100644
--- a/pyupgrade.py
+++ b/pyupgrade.py
@@ -1271,10 +1271,15 @@ class FindPy3Plus(ast.NodeVisitor):
         elif (
                 isinstance(node.func, ast.Name) and
                 node.func.id == 'str' and
-                len(node.args) == 1 and
-                isinstance(node.args[0], ast.Str) and
                 not node.keywords and
-                not _starargs(node)
+                not _starargs(node) and
+                (
+                    len(node.args) == 0 or
+                    (
+                        len(node.args) == 1 and
+                        isinstance(node.args[0], ast.Str)
+                    )
+                )
         ):
             self.native_literals.add(_ast_to_offset(node))
         elif (
@@ -1788,7 +1793,10 @@ def _fix_py3_plus(contents_text):  # type: (str) -> str
             func_args, end = _parse_call_args(tokens, j)
             if any(tok.name == 'NL' for tok in tokens[i:end]):
                 continue
-            _replace_call(tokens, i, end, func_args, '{args[0]}')
+            if func_args:
+                _replace_call(tokens, i, end, func_args, '{args[0]}')
+            else:
+                tokens[i:end] = [token._replace(name='STRING', src="''")]
         elif token.offset in visitor.simple_ids:
             tokens[i] = Token(
                 token.name,


Test patch:
diff --git a/tests/native_literals_test.py b/tests/native_literals_test.py
index a894e2d..a82820a 100644
--- a/tests/native_literals_test.py
+++ b/tests/native_literals_test.py
@@ -24,6 +24,7 @@ def test_fix_native_literals_noop(s):
 @pytest.mark.parametrize(
     ('s', 'expected'),
     (
+        ('str()', "''"),
         ('str("foo")', '"foo"'),
         ('str("""\nfoo""")', '"""\nfoo"""'),
     ),
Repository: asottile/pyupgrade

Issue:
Upgrade python 2 octal literals
Along the same lines as the long literals

Comments:

Patch:
diff --git a/README.md b/README.md
index 9230fe5..f5d6db7 100644
--- a/README.md
+++ b/README.md
@@ -77,6 +77,14 @@ Availability:
 123456789123456789123456789L  # 123456789123456789123456789
 ```
 
+### Octal literals
+
+Availability:
+- If `pyupgrade` is running in python 2.
+```python
+0755  # 0o755
+```
+
 
 ## Planned features
 
diff --git a/pyupgrade.py b/pyupgrade.py
index d305ad2..cac40ea 100644
--- a/pyupgrade.py
+++ b/pyupgrade.py
@@ -435,6 +435,20 @@ def _fix_long_literals(contents_text):
     return tokens_to_src(tokens)
 
 
+def _fix_octal_literals(contents_text):
+    def _fix_octal(s):
+        if not s.startswith('0') or not s.isdigit() or s == len(s) * '0':
+            return s
+        else:  # pragma: no cover (py2 only)
+            return '0o' + s[1:]
+
+    tokens = src_to_tokens(contents_text)
+    for i, token in enumerate(tokens):
+        if token.name == 'NUMBER':
+            tokens[i] = token._replace(src=_fix_octal(token.src))
+    return tokens_to_src(tokens)
+
+
 def fix_file(filename, args):
     with open(filename, 'rb') as f:
         contents_bytes = f.read()
@@ -450,6 +464,7 @@ def fix_file(filename, args):
     contents_text = _fix_format_literals(contents_text)
     contents_text = _fix_unicode_literals(contents_text, args.py3_only)
     contents_text = _fix_long_literals(contents_text)
+    contents_text = _fix_octal_literals(contents_text)
 
     if contents_text != contents_text_orig:
         print('Rewriting {}'.format(filename))


Test patch:
diff --git a/tests/pyupgrade_test.py b/tests/pyupgrade_test.py
index bbc1a05..712a4e8 100644
--- a/tests/pyupgrade_test.py
+++ b/tests/pyupgrade_test.py
@@ -9,6 +9,7 @@ import pytest
 from pyupgrade import _fix_dictcomps
 from pyupgrade import _fix_format_literals
 from pyupgrade import _fix_long_literals
+from pyupgrade import _fix_octal_literals
 from pyupgrade import _fix_sets
 from pyupgrade import _fix_unicode_literals
 from pyupgrade import _imports_unicode_literals
@@ -309,6 +310,27 @@ def test_long_literals(s, expected):
     assert _fix_long_literals(s) == expected
 
 
+@pytest.mark.parametrize(
+    ('s', 'expected'),
+    (
+        # Any number of zeros is considered a legal token
+        ('0', '0'),
+        ('00', '00'),
+        # Don't modify non octal literals
+        ('1', '1'),
+        ('12345', '12345'),
+        ('1.2345', '1.2345'),
+    ),
+)
+def test_noop_octal_literals(s, expected):
+    assert _fix_octal_literals(s) == expected
+
+
+@pytest.mark.xfail(sys.version_info >= (3,), reason='python2 "feature"')
+def test_fix_octal_literal():
+    assert _fix_octal_literals('0755') == '0o755'
+
+
 def test_main_trivial():
     assert main(()) == 0
Repository: asottile/pyupgrade

Issue:
Possible bug `"/\N{SNOWMAN}".encode("utf-8")` -> `br"/\N{SNOWMAN}"`
Taken from running pyupgrade on Werkzeug's test suite. I think this is the wrong behavior as `"/\N{SNOWMAN}".encode("utf-8")` != `br"/\N{SNOWMAN}"` and therefore this tools should not convert the former to the latter.

Comments:

Patch:
diff --git a/pyupgrade.py b/pyupgrade.py
index 25d7e99..1d0636f 100644
--- a/pyupgrade.py
+++ b/pyupgrade.py
@@ -656,6 +656,7 @@ def _fix_encode_to_binary(tokens, i):  # type: (List[Token], int) -> None
                 not _is_ascii(rest) or
                 '\\u' in escapes or
                 '\\U' in escapes or
+                '\\N' in escapes or
                 ('\\x' in escapes and not latin1_ok) or
                 'f' in prefix.lower()
         ):


Test patch:
diff --git a/tests/binary_literals_test.py b/tests/binary_literals_test.py
index 885d122..1c543fd 100644
--- a/tests/binary_literals_test.py
+++ b/tests/binary_literals_test.py
@@ -13,6 +13,7 @@ from pyupgrade import _fix_tokens
         '"☃".encode("UTF-8")',
         '"\\u2603".encode("UTF-8")',
         '"\\U0001f643".encode("UTF-8")',
+        '"\\N{SNOWMAN}".encode("UTF-8")',
         '"\\xa0".encode("UTF-8")',
         # not byte literal compatible
         '"y".encode("utf16")',
Repository: asottile/pyupgrade

Issue:
six: ensure_str / ensure_binary / ensure_text of a literal string
while we generally can't fix the ensure_* variants, we can for string literals (as we're already doing for `str('...')` for example)

Comments:

Patch:
diff --git a/README.md b/README.md
index eeb1a5f..7a7a469 100644
--- a/README.md
+++ b/README.md
@@ -331,6 +331,11 @@ six.get_function_globals(fn)            # fn.__globals__
 six.assertCountEqual(self, a1, a2)      # self.assertCountEqual(a1, a2)
 six.assertRaisesRegex(self, e, r, fn)   # self.assertRaisesRegex(e, r, fn)
 six.assertRegex(self, s, r)             # self.assertRegex(s, r)
+
+# note: only for *literals*
+six.ensure_binary('...')                # b'...'
+six.ensure_str('...')                   # '...'
+six.ensure_text('...')                  # '...'
 ```
 
 ### `open` alias
diff --git a/pyupgrade.py b/pyupgrade.py
index 07ed723..d110895 100644
--- a/pyupgrade.py
+++ b/pyupgrade.py
@@ -1420,7 +1420,7 @@ class FindPy3Plus(ast.NodeVisitor):
             # _is_six() enforces this
             assert isinstance(arg, (ast.Name, ast.Attribute))
             self.six_type_ctx[_ast_to_offset(node.args[1])] = arg
-        elif self._is_six(node.func, ('b',)):
+        elif self._is_six(node.func, ('b', 'ensure_binary')):
             self.six_b.add(_ast_to_offset(node))
         elif self._is_six(node.func, SIX_CALLS) and not _starargs(node):
             self.six_calls[_ast_to_offset(node)] = node
@@ -1457,8 +1457,10 @@ class FindPy3Plus(ast.NodeVisitor):
         ):
             self.super_calls[_ast_to_offset(node)] = node
         elif (
-                isinstance(node.func, ast.Name) and
-                node.func.id == 'str' and
+                (
+                    self._is_six(node.func, ('ensure_str', 'ensure_text')) or
+                    isinstance(node.func, ast.Name) and node.func.id == 'str'
+                ) and
                 not node.keywords and
                 not _starargs(node) and
                 (


Test patch:
diff --git a/tests/native_literals_test.py b/tests/native_literals_test.py
index a82820a..7b6c8f0 100644
--- a/tests/native_literals_test.py
+++ b/tests/native_literals_test.py
@@ -27,6 +27,8 @@ def test_fix_native_literals_noop(s):
         ('str()', "''"),
         ('str("foo")', '"foo"'),
         ('str("""\nfoo""")', '"""\nfoo"""'),
+        ('six.ensure_str("foo")', '"foo"'),
+        ('six.ensure_text("foo")', '"foo"'),
     ),
 )
 def test_fix_native_literals(s, expected):
diff --git a/tests/six_test.py b/tests/six_test.py
index f840c3f..83ce558 100644
--- a/tests/six_test.py
+++ b/tests/six_test.py
@@ -98,6 +98,10 @@ def test_fix_six_noop(s):
             r'six.b("\x12\xef")',
             r'b"\x12\xef"',
         ),
+        (
+            'six.ensure_binary("foo")',
+            'b"foo"',
+        ),
         (
             'from six import b\n\n' r'b("\x12\xef")',
             'from six import b\n\n' r'b"\x12\xef"',
Repository: asottile/pyupgrade

Issue:
open() rewrite doesn't always consider other positional args
Minimal input:

```py
with open('blah.txt', 'r', 'utf-8') as fp:
    pass
```

Actual output:

```py
with open('blah.txt', 'utf-8') as fp:
    pass
```

Expected:

```py
with open('blah.txt', encoding='utf-8') as fp:
    pass
```

The rewrite causes the following Python error upon executing the code:

```
Traceback (most recent call last):
  File "blah.py", line 1, in <module>
    with open('blah.txt', 'utf-8') as fp:
ValueError: invalid mode: 'utf-8'
```

Discovered in a real code base:

https://github.com/andialbrecht/sqlparse/blob/28a2acdd9b307c95d2111f1f831b1a5afc634691/sqlparse/cli.py#L175


Comments:

Patch:
diff --git a/pyupgrade.py b/pyupgrade.py
index a49ef68..45a13bb 100644
--- a/pyupgrade.py
+++ b/pyupgrade.py
@@ -1127,7 +1127,7 @@ SIX_NATIVE_STR = frozenset(('ensure_str', 'ensure_text', 'text_type'))
 U_MODE_REMOVE = frozenset(('U', 'Ur', 'rU', 'r', 'rt', 'tr'))
 U_MODE_REPLACE_R = frozenset(('Ub', 'bU'))
 U_MODE_REMOVE_U = frozenset(('rUb', 'Urb', 'rbU', 'Ubr', 'bUr', 'brU'))
-U_MODE_ALL = U_MODE_REMOVE | U_MODE_REPLACE_R | U_MODE_REMOVE_U
+U_MODE_REPLACE = U_MODE_REPLACE_R | U_MODE_REMOVE_U
 
 
 def _all_isinstance(
@@ -1570,10 +1570,12 @@ class FindPy3Plus(ast.NodeVisitor):
         elif (
                 isinstance(node.func, ast.Name) and
                 node.func.id == 'open' and
-                len(node.args) >= 2 and
                 not _starargs(node) and
-                isinstance(node.args[1], ast.Str) and
-                node.args[1].s in U_MODE_ALL
+                len(node.args) >= 2 and
+                isinstance(node.args[1], ast.Str) and (
+                    node.args[1].s in U_MODE_REPLACE or
+                    (len(node.args) == 2 and node.args[1].s in U_MODE_REMOVE)
+                )
         ):
             self.open_mode_calls.add(_ast_to_offset(node))
         elif (


Test patch:
diff --git a/tests/open_mode_test.py b/tests/open_mode_test.py
index dd306a8..1398792 100644
--- a/tests/open_mode_test.py
+++ b/tests/open_mode_test.py
@@ -13,6 +13,8 @@ from pyupgrade import _fix_py3_plus
         'open("foo", "Uw")',
         # TODO: could maybe be rewritten to remove t?
         'open("foo", "wt")',
+        # don't remove this, they meant to use `encoding=`
+        'open("foo", "r", "utf-8")',
     ),
 )
 def test_fix_open_mode_noop(s):
Repository: asottile/pyupgrade

Issue:
py2 / py3 branch rewrite breaks indentation
```python
import six

if six.PY2:  # pragma: no cover (py2)
    def wsgi_to_text(s):
        # Use latin-1, the encoding of ip addresses isn't important (they'll
        # always be either ascii, or invalidated by the ipaddress module)
        return s.decode('LATIN-1')

    def text_to_wsgi(s):
        return s.encode('US-ASCII')
else:  # pragma: no cover (py3)
    def wsgi_to_text(s):
        return s

    def text_to_wsgi(s):
        return s

def f():
    pass
```

currently rewritten to

```python
import six

def wsgi_to_text(s):
    return s

    def text_to_wsgi(s):
    return s


def f():
    pass
```

Comments:

Patch:
diff --git a/pyupgrade.py b/pyupgrade.py
index cb91999..f6e8e94 100644
--- a/pyupgrade.py
+++ b/pyupgrade.py
@@ -1771,7 +1771,7 @@ class Block(NamedTuple):
         diff = self._minimum_indent(tokens) - self._initial_indent(tokens)
         for i in range(self.block, self.end):
             if (
-                    tokens[i - 1].name in ('NL', 'NEWLINE') and
+                    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:])


Test patch:
diff --git a/tests/versioned_branches_test.py b/tests/versioned_branches_test.py
index b0512f1..2f93791 100644
--- a/tests/versioned_branches_test.py
+++ b/tests/versioned_branches_test.py
@@ -264,6 +264,25 @@ def test_fix_py2_block_noop(s):
 
             id='six.PY2, comment after',
         ),
+        pytest.param(
+            'if six.PY2:\n'
+            '    def f():\n'
+            '        print("py2")\n'
+            '    def g():\n'
+            '        print("py2")\n'
+            'else:\n'
+            '    def f():\n'
+            '        print("py3")\n'
+            '    def g():\n'
+            '        print("py3")\n',
+
+            'def f():\n'
+            '    print("py3")\n'
+            'def g():\n'
+            '    print("py3")\n',
+
+            id='six.PY2 multiple functions',
+        ),
         pytest.param(
             'if True:\n'
             '    if six.PY3:\n'
Repository: asottile/pyupgrade

Issue:
Rewriting TypedDict can lead to SyntaxError
pyupgrade checks if the keys of a TypedDict are strings, but not if they are valid for usage in class syntax.

Here's a minimal input:

```python
from typing_extensions import TypedDict

MyDict = TypedDict("MyDict", {"my-key": str, "another key": int})
```


This is the rewrite produced by `pyupgrade --py36-plus minimal.py`:

```python
from typing_extensions import TypedDict

class MyDict(TypedDict):
    my-key: str
    another key: int
```

The `-` and the space in the keys are invalid syntax.

Comments:

Patch:
diff --git a/pyupgrade.py b/pyupgrade.py
index f6e8e94..d740aa0 100644
--- a/pyupgrade.py
+++ b/pyupgrade.py
@@ -2449,6 +2449,7 @@ class FindPy36Plus(ast.NodeVisitor):
                         isinstance(tup, ast.Tuple) and
                         len(tup.elts) == 2 and
                         isinstance(tup.elts[0], ast.Str) and
+                        tup.elts[0].s.isidentifier() and
                         tup.elts[0].s not in _KEYWORDS
                         for tup in node.value.args[1].elts
                     )
@@ -2475,7 +2476,9 @@ class FindPy36Plus(ast.NodeVisitor):
                     isinstance(node.value.args[1], ast.Dict) and
                     node.value.args[1].keys and
                     all(
-                        isinstance(k, ast.Str)
+                        isinstance(k, ast.Str) and
+                        k.s.isidentifier() and
+                        k.s not in _KEYWORDS
                         for k in node.value.args[1].keys
                     )
             ):


Test patch:
diff --git a/tests/typing_named_tuple_test.py b/tests/typing_named_tuple_test.py
index 3e38502..083d672 100644
--- a/tests/typing_named_tuple_test.py
+++ b/tests/typing_named_tuple_test.py
@@ -41,6 +41,10 @@ from pyupgrade import _fix_py36_plus
             'C = typing.NamedTuple("C", [("def", int)])',
             id='uses keyword',
         ),
+        pytest.param(
+            'C = typing.NamedTuple("C", [("not-ok", int)])',
+            id='uses non-identifier',
+        ),
         pytest.param(
             'C = typing.NamedTuple("C", *types)',
             id='NamedTuple starargs',
diff --git a/tests/typing_typed_dict_test.py b/tests/typing_typed_dict_test.py
index fb39c6b..eea6ef4 100644
--- a/tests/typing_typed_dict_test.py
+++ b/tests/typing_typed_dict_test.py
@@ -19,6 +19,14 @@ from pyupgrade import _fix_py36_plus
             'D = typing.TypedDict("D", {1: str})',
             id='key is not a string',
         ),
+        pytest.param(
+            'D = typing.TypedDict("D", {"a-b": str})',
+            id='key is not an identifier',
+        ),
+        pytest.param(
+            'D = typing.TypedDict("D", {"class": str})',
+            id='key is a keyword',
+        ),
         pytest.param(
             'D = typing.TypedDict("D", {**d, "a": str})',
             id='dictionary splat operator',
Repository: asottile/pyupgrade

Issue:
pyupgrade on f-strings with .format() results in invalid f-string
I understand that this is an edge-case because the input was buggy, but it _does_ convert running code into non-running code.

```
$ cat a.py
print(f"foo {0}".format("bar"))
$ python a.py
foo 0
$ pyupgrade a.py
Rewriting a.py
$ cat a.py
print(f"foo {}".format("bar"))
$ python a.py
  File "a.py", line 1
    print(f"foo {}".format("bar"))
         ^
SyntaxError: f-string: empty expression not allowed
```

```
$ pip freeze | grep pyupgrade
pyupgrade==2.7.0
```

I'm not sure what the right behavior is in this case; maybe just don't touch numbered args inside an f-string?

Comments:

Patch:
diff --git a/pyupgrade.py b/pyupgrade.py
index c463fcf..54bbe0d 100644
--- a/pyupgrade.py
+++ b/pyupgrade.py
@@ -587,6 +587,11 @@ def _fix_format_literal(tokens: List[Token], end: int) -> None:
     parsed_parts = []
     last_int = -1
     for i in parts:
+        # f'foo {0}'.format(...) would get turned into a SyntaxError
+        prefix, _ = parse_string_literal(tokens[i].src)
+        if 'f' in prefix.lower():
+            return
+
         try:
             parsed = parse_format(tokens[i].src)
         except ValueError:


Test patch:
diff --git a/tests/format_literals_test.py b/tests/format_literals_test.py
index f3610a1..0719138 100644
--- a/tests/format_literals_test.py
+++ b/tests/format_literals_test.py
@@ -51,6 +51,8 @@ def test_intentionally_not_round_trip(s, expected):
         '("{0}" # {1}\n"{2}").format(1, 2, 3)',
         # TODO: this works by accident (extended escape treated as placeholder)
         r'"\N{snowman} {}".format(1)',
+        # don't touch f-strings (these are wrong but don't make it worse)
+        'f"{0}".format(a)',
     ),
 )
 def test_format_literals_noop(s):
Repository: asottile/pyupgrade

Issue:
exception stack trace is replaced by a bare `raise`
With python 3.7.4, the formatted exception stack trace will be replaced by a bare `raise`, and then make a syntax error.

But the strange thing is that this occurs only when the statement is executed in the pre-commit hook of git, the replacement disappears if the statement is executed directly.

```python
poetry run pyupgrade --py37-plus
```

- Before
 
```python
async def general_exception_handler(request: Request, exc: Exception):
    return JSONResponse(
        status_code=HTTP_500_INTERNAL_SERVER_ERROR,
        content={
            'request': f'{request.method} {request.url}',
            'message': str(exc),
            'detail': jsonable_encoder(traceback.format_exception(*sys.exc_info())),
        },
    )
```

- After

```python
async def general_exception_handler(request: Request, exc: Exception):
    return JSONResponse(
        status_code=HTTP_500_INTERNAL_SERVER_ERROR,
        content={
            'request': f'{request.method} {request.url}',
            'message': str(exc),
            # the exception stack info is replaced by a bare `raise`
            'detail': jsonable_encoder(raise),
        },
    )
```


Comments:

Patch:
diff --git a/pyupgrade.py b/pyupgrade.py
index 54bbe0d..8b47463 100644
--- a/pyupgrade.py
+++ b/pyupgrade.py
@@ -1558,7 +1558,7 @@ class FindPy3Plus(ast.NodeVisitor):
         elif (
                 isinstance(self._previous_node, ast.Expr) and
                 self._is_six(node.func, ('reraise',)) and
-                not _starargs(node) or self._is_star_sys_exc_info(node)
+                (not _starargs(node) or self._is_star_sys_exc_info(node))
         ):
             self.six_reraise.add(_ast_to_offset(node))
         elif (


Test patch:
diff --git a/tests/six_test.py b/tests/six_test.py
index 0a973c0..9ddffd7 100644
--- a/tests/six_test.py
+++ b/tests/six_test.py
@@ -41,6 +41,7 @@ from pyupgrade import _fix_py3_plus
             'isinstance("foo", text_type)\n',
             id='relative import might not be six',
         ),
+        ('traceback.format_exc(*sys.exc_info())'),
     ),
 )
 def test_fix_six_noop(s):