Repository: berkerpeksag/astor

Issue:
New fstring failures
I'm not sure how to handle this one, so I did not submit a PR yet.

Source (from Python 3.7's dataclasses.py):
`f"{f.name}={{self.{f.name}!r}}"`

Parsed as:
```
JoinedStr(
    values=[
        FormattedValue(value=Attribute(value=Name(id='f'), attr='name'), conversion=-1, format_spec=None),
        Str(s='={self.'),
        FormattedValue(value=Attribute(value=Name(id='f'), attr='name'), conversion=-1, format_spec=None),
        Str(s='!r}')]),
```

which becomes a syntax error after rtrip:
`f'{f.name}={self.{f.name}!r}'`

```
  File "<fstring>", line 1
    (self.{f.name})
          ^
SyntaxError: invalid syntax
```

Discussion:
Kodiologist: You got it, chief.
Kodiologist: Do you need anything more from me here?

Patch:
diff --git a/.travis.yml b/.travis.yml
index 221a0c7..f5a2cb8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,6 +14,11 @@ matrix:
   - python: 3.7
     sudo: required
     dist: xenial
+  - python: 3.8-dev
+    sudo: required
+    dist: xenial
+  allow_failures:
+  - python: 3.8-dev
 cache: pip
 install:
   - pip install tox-travis
diff --git a/astor/code_gen.py b/astor/code_gen.py
index 157d2cc..1856254 100644
--- a/astor/code_gen.py
+++ b/astor/code_gen.py
@@ -566,7 +566,8 @@ class SourceGenerator(ExplicitNodeVisitor):
             def recurse(node):
                 for value in node.values:
                     if isinstance(value, ast.Str):
-                        self.write(value.s)
+                        # Double up braces to escape them.
+                        self.write(value.s.replace('{', '{{').replace('}', '}}'))
                     elif isinstance(value, ast.FormattedValue):
                         with self.delimit('{}'):
                             self.visit(value.value)
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 05c7b3f..bbc0752 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -13,7 +13,11 @@ New features
 Bug fixes
 ~~~~~~~~~
 
-*
+* Fixed code generation with escaped braces in f-strings.
+  (Reported by Felix Yan in `Issue 124`_ and fixed by Kodi Arfer in `PR 125`_.)
+
+.. _`Issue 124`: https://github.com/berkerpeksag/astor/issues/124
+.. _`PR 125`: https://github.com/berkerpeksag/astor/pull/125
 
 0.7.1 - 2018-07-06
 ------------------


Test patch:
diff --git a/tests/test_code_gen.py b/tests/test_code_gen.py
index 3a8be7b..3bd1844 100644
--- a/tests/test_code_gen.py
+++ b/tests/test_code_gen.py
@@ -517,6 +517,16 @@ class CodegenTestCase(unittest.TestCase, Comparisons):
         '''
         self.assertSrcRoundtripsGtVer(source, (3, 6))
 
+    def test_fstring_escaped_braces(self):
+        source = '''
+        x = f'{{hello world}}'
+        '''
+        self.assertSrcRoundtripsGtVer(source, (3, 6))
+        source = '''
+        x = f'{f.name}={{self.{f.name}!r}}'
+        '''
+        self.assertSrcRoundtripsGtVer(source, (3, 6))
+
     def test_docstring_function(self):
         source = '''
         def f(arg):