Kodiologist: You got it, chief.
Kodiologist: Do you need anything more from me here?
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
 ------------------

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

pip install -e .
parse_log_pytest
pytest
nose pytest
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
-e git+https://github.com/berkerpeksag/astor.git@55dd323f7d8d696610c703c0296763c567685c31#egg=astor
exceptiongroup @ file:///croot/exceptiongroup_1706031385326/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
nose==1.3.7
packaging @ file:///croot/packaging_1734472117206/work
pluggy @ file:///croot/pluggy_1733169602837/work
pytest @ file:///croot/pytest_1738938843180/work
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work

name: astor
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - exceptiongroup=1.2.0=py39h06a4308_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - packaging=24.2=py39h06a4308_0
  - pip=25.0=py39h06a4308_0
  - pluggy=1.5.0=py39h06a4308_0
  - pytest=8.3.4=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py39h06a4308_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - nose==1.3.7
prefix: /opt/conda/envs/astor

tests/test_code_gen.py::CodegenTestCase::test_fstring_escaped_braces
tests/test_code_gen.py::CodegenTestCase::test_annassign tests/test_code_gen.py::CodegenTestCase::test_arguments tests/test_code_gen.py::CodegenTestCase::test_complex tests/test_code_gen.py::CodegenTestCase::test_comprehension tests/test_code_gen.py::CodegenTestCase::test_del_statement tests/test_code_gen.py::CodegenTestCase::test_dictionary_literals tests/test_code_gen.py::CodegenTestCase::test_docstring_function tests/test_code_gen.py::CodegenTestCase::test_docstring_method tests/test_code_gen.py::CodegenTestCase::test_inf tests/test_code_gen.py::CodegenTestCase::test_multiple_call_unpackings tests/test_code_gen.py::CodegenTestCase::test_nan tests/test_code_gen.py::CodegenTestCase::test_pow tests/test_code_gen.py::CodegenTestCase::test_right_hand_side_dictionary_unpacking tests/test_code_gen.py::CodegenTestCase::test_slicing tests/test_code_gen.py::CodegenTestCase::test_try_expect tests/test_code_gen.py::CodegenTestCase::test_tuple_corner_cases tests/test_code_gen.py::CodegenTestCase::test_unary tests/test_code_gen.py::CodegenTestCase::test_unicode_literals tests/test_code_gen.py::CodegenTestCase::test_yield
tests/test_code_gen.py::CodegenTestCase::test_async_comprehension tests/test_code_gen.py::CodegenTestCase::test_async_def_with_for tests/test_code_gen.py::CodegenTestCase::test_class_definition_with_starbases_and_kwargs tests/test_code_gen.py::CodegenTestCase::test_compile_types tests/test_code_gen.py::CodegenTestCase::test_docstring_class tests/test_code_gen.py::CodegenTestCase::test_docstring_module tests/test_code_gen.py::CodegenTestCase::test_double_await tests/test_code_gen.py::CodegenTestCase::test_elif tests/test_code_gen.py::CodegenTestCase::test_empty_iterable_literals tests/test_code_gen.py::CodegenTestCase::test_fstring_trailing_newline tests/test_code_gen.py::CodegenTestCase::test_fstrings tests/test_code_gen.py::CodegenTestCase::test_imports tests/test_code_gen.py::CodegenTestCase::test_matrix_multiplication tests/test_code_gen.py::CodegenTestCase::test_non_string_leakage tests/test_code_gen.py::CodegenTestCase::test_output_formatting tests/test_code_gen.py::CodegenTestCase::test_pass_arguments_node tests/test_code_gen.py::CodegenTestCase::test_with
.travis.yml docs/changelog.rst astor/code_gen.py
.travis.yml docs/changelog.rst astor/code_gen.py