Repository: asottile/pyupgrade

Issue:
--py36-plus: ValueError: Single '{' encountered in format string
Invalid format failures are not skipped currently:

```python
'{'.format(a)
```

```console
$ pyupgrade --py36-plus t.py
Traceback (most recent call last):
  File "/home/asottile/workspace/ridemonitor/venv/bin/pyupgrade", line 10, in <module>
    sys.exit(main())
  File "/home/asottile/workspace/ridemonitor/venv/lib/python3.6/site-packages/pyupgrade.py", line 2136, in main
    ret |= fix_file(filename, args)
  File "/home/asottile/workspace/ridemonitor/venv/lib/python3.6/site-packages/pyupgrade.py", line 2107, in fix_file
    contents_text = _fix_fstrings(contents_text)
  File "/home/asottile/workspace/ridemonitor/venv/lib/python3.6/site-packages/pyupgrade.py", line 2053, in _fix_fstrings
    visitor.visit(ast_obj)
  File "/usr/lib/python3.6/ast.py", line 253, in visit
    return visitor(node)
  File "/usr/lib/python3.6/ast.py", line 261, in generic_visit
    self.visit(item)
  File "/usr/lib/python3.6/ast.py", line 253, in visit
    return visitor(node)
  File "/usr/lib/python3.6/ast.py", line 263, in generic_visit
    self.visit(value)
  File "/usr/lib/python3.6/ast.py", line 253, in visit
    return visitor(node)
  File "/home/asottile/workspace/ridemonitor/venv/lib/python3.6/site-packages/pyupgrade.py", line 1996, in visit_Call
    for _, name, spec, _ in parse_format(node.func.value.s):
  File "/home/asottile/workspace/ridemonitor/venv/lib/python3.6/site-packages/pyupgrade.py", line 69, in parse_format
    parsed = tuple(_stdlib_parse_format(s))
ValueError: Single '{' encountered in format string
```

Discussion:

Patch:
diff --git a/pyupgrade.py b/pyupgrade.py
index 2347ec5..d24f9af 100644
--- a/pyupgrade.py
+++ b/pyupgrade.py
@@ -1982,18 +1982,12 @@ class FindSimpleFormats(ast.NodeVisitor):
         self.found = {}  # type: Dict[Offset, ast.Call]
 
     def visit_Call(self, node):  # type: (ast.Call) -> None
-        if (
-                isinstance(node.func, ast.Attribute) and
-                isinstance(node.func.value, ast.Str) and
-                node.func.attr == 'format' and
-                all(_simple_arg(arg) for arg in node.args) and
-                all(_simple_arg(k.value) for k in node.keywords) and
-                not _starargs(node)
-        ):
+        parsed = self._parse_call(node)
+        if parsed is not None:
             params = _format_params(node)
             seen = set()  # type: Set[str]
             i = 0
-            for _, name, spec, _ in parse_format(node.func.value.s):
+            for _, name, spec, _ in parsed:
                 # timid: difficult to rewrite correctly
                 if spec is not None and '{' in spec:
                     break
@@ -2018,6 +2012,23 @@ class FindSimpleFormats(ast.NodeVisitor):
 
         self.generic_visit(node)
 
+    def _parse_call(self, node):
+        # type: (ast.Call) -> Optional[Tuple[DotFormatPart, ...]]
+        if not (
+                isinstance(node.func, ast.Attribute) and
+                isinstance(node.func.value, ast.Str) and
+                node.func.attr == 'format' and
+                all(_simple_arg(arg) for arg in node.args) and
+                all(_simple_arg(k.value) for k in node.keywords) and
+                not _starargs(node)
+        ):
+            return None
+
+        try:
+            return parse_format(node.func.value.s)
+        except ValueError:
+            return None
+
 
 def _unparse(node):  # type: (ast.expr) -> str
     if isinstance(node, ast.Name):


Test patch:
diff --git a/tests/fstrings_test.py b/tests/fstrings_test.py
index b0b5da6..66f90ae 100644
--- a/tests/fstrings_test.py
+++ b/tests/fstrings_test.py
@@ -12,6 +12,8 @@ from pyupgrade import _fix_fstrings
     (
         # syntax error
         '(',
+        # invalid format strings
+        "'{'.format(a)", "'}'.format(a)",
         # weird syntax
         '"{}" . format(x)',
         # spans multiple lines