Mass-migrate string formatting to f-strings

Using https://pypi.org/project/flynt. No expectation diffs. Inspired by
http://fxrev.dev/796162.

Bug: b/240137096
Change-Id: I1f0824fca1bfb6ec096b05e318625df76fa0be4d
Reviewed-on: https://pigweed-review.googlesource.com/c/infra/recipes/+/127391
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Pigweed-Auto-Submit: Rob Mohr <mohrr@google.com>
Reviewed-by: Ted Pudlik <tpudlik@google.com>
diff --git a/recipe_modules/build/api.py b/recipe_modules/build/api.py
index e52798b..d6e898f 100644
--- a/recipe_modules/build/api.py
+++ b/recipe_modules/build/api.py
@@ -32,7 +32,7 @@
         cmd = ['gn', 'gen']
 
         for gn_arg in options.gn_args:
-            cmd.append('--args={}'.format(gn_arg))
+            cmd.append(f'--args={gn_arg}')
 
         # Infrequently needed but harmless to always add this.
         cmd.append('--export-compile-commands')
@@ -97,7 +97,7 @@
             with self.m.step.nest('longest build steps'):
                 for dur, name in steps[0:10]:
                     with self.m.step.nest(name) as pres:
-                        pres.step_summary_text = '{:.1f}s'.format(dur)
+                        pres.step_summary_text = f'{dur:.1f}s'
 
     def save_logs(self, build_dir=None, export_dir=None, pres=None):
         """Save common build logs from the build directory.
diff --git a/recipe_modules/checkout/api.py b/recipe_modules/checkout/api.py
index 2ff67d2..4940ca8 100644
--- a/recipe_modules/checkout/api.py
+++ b/recipe_modules/checkout/api.py
@@ -119,17 +119,15 @@
     def gerrit_url(self):
         if not self.number:
             return self.gitiles_url
-        return 'https://{}-review.googlesource.com/c/{}'.format(
-            self.gerrit_name, self.number
-        )
+        return f'https://{self.gerrit_name}-review.googlesource.com/c/{self.number}'
 
     @property
     def gitiles_url(self):
-        return '{}/+/{}'.format(self.remote, self.ref)
+        return f'{self.remote}/+/{self.ref}'
 
     @property
     def name(self):
-        return '{}:{}'.format(self.gerrit_name, self.number)
+        return f'{self.gerrit_name}:{self.number}'
 
 
 @attr.s
@@ -306,7 +304,7 @@
         if not match:
             return  # pragma: no cover
 
-        gerrit_review_host = '{}'.format(match.group('host'))
+        gerrit_review_host = f"{match.group('host')}"
         if '-review' not in gerrit_review_host:
             gerrit_review_host = gerrit_review_host.replace('.', '-review.', 1)
         return gerrit_review_host
@@ -349,9 +347,7 @@
                 if remote.fetch.startswith('..'):
                     rest = remote.fetch[2:]
                     parsed = urllib.parse.urlparse(manifest_remote)
-                    remote.fetch = (
-                        '{}://{}'.format(parsed.scheme, parsed.netloc,) + rest
-                    )
+                    remote.fetch = f'{parsed.scheme}://{parsed.netloc}' + rest
                 remote.fetch = Url(remote.fetch)
                 remote.fetch.https = self.m.sso.sso_to_https(remote.fetch.url)
                 manifest.remotes[remote.name] = remote
@@ -369,11 +365,11 @@
                 elif 'remote' in defaults:
                     remote = defaults['remote']
                 else:  # pragma: no cover
-                    assert False, 'remote not specified for {}'.format(name)
+                    assert False, f'remote not specified for {name}'
 
                 assert (
                     remote in manifest.remotes
-                ), 'Remote {} does not exist'.format(remote)
+                ), f'Remote {remote} does not exist'
 
                 if 'revision' in project.attrib:
                     revision = project.attrib['revision']
@@ -382,7 +378,7 @@
                 elif 'revision' in defaults:
                     revision = defaults['revision']
                 else:  # pragma: no cover
-                    assert False, 'revision not specified for {}'.format(name)
+                    assert False, f'revision not specified for {name}'
 
                 if 'upstream' in project.attrib:
                     upstream = project.attrib['upstream']
@@ -425,13 +421,11 @@
         """Process a LUCI GerritChange and return a Change object."""
 
         assert change.host
-        ref = 'refs/changes/{:02}/{}/{}'.format(
-            change.change % 100, change.change, change.patchset,
-        )
+        ref = f'refs/changes/{change.change % 100:02}/{change.change}/{change.patchset}'
         host = change.host.replace(
             '-review.googlesource.com', '.googlesource.com'
         )
-        remote = 'https://{}/{}'.format(host, change.project).strip('/')
+        remote = f'https://{host}/{change.project}'.strip('/')
         gerrit_name = host.split('.')[0]
         details = self.m.gerrit.change_details(
             'details',
@@ -514,7 +508,7 @@
         try:
             results = self.m.gerrit.change_query(
                 'number',
-                'commit:{}'.format(commit_hash),
+                f'commit:{commit_hash}',
                 host=host,
                 max_attempts=5,
                 timeout=30,
@@ -544,9 +538,7 @@
                     commit = bb_input.gitiles_commit
                     assert commit.host
                     if commit.project:
-                        remote = 'https://{}/{}'.format(
-                            commit.host, commit.project
-                        )
+                        remote = f'https://{commit.host}/{commit.project}'
 
                     host = commit.host.replace(
                         '.googlesource.com', '-review.googlesource.com'
@@ -577,7 +569,7 @@
                 # even if this is wrong it's close enough to have utility.
                 head = self.m.git.get_remote_branch_head(remote, branch)
                 gerrit_name = urllib.parse.urlparse(remote).netloc.split('.')[0]
-                host = '{}-review.googlesource.com'.format(gerrit_name)
+                host = f'{gerrit_name}-review.googlesource.com'
                 result = self._number_details(host, head)
 
                 results.append(
@@ -608,7 +600,7 @@
                 head = self.m.git.get_remote_branch_head(
                     repo,
                     branch,
-                    step_name='git ls-remote {}'.format(branch),
+                    step_name=f'git ls-remote {branch}',
                     step_test_data=lambda: self.m.raw_io.test_api.stream_output_text(
                         ''
                     ),
@@ -633,7 +625,7 @@
         kwargs = {'cwd': cwd} if cwd else {}
         change.applied = True
 
-        apply_step = 'apply {}'.format(change.name)
+        apply_step = f'apply {change.name}'
         with self.m.context(**kwargs), self.m.step.nest(apply_step) as pres:
             pres.links['gerrit'] = change.gerrit_url
             pres.links['gitiles'] = change.gitiles_url
@@ -680,7 +672,7 @@
                 with self.m.default_timeout():
                     self.m.git.fetch(
                         remote,
-                        'refs/heads/{}'.format(change.branch),
+                        f'refs/heads/{change.branch}',
                         prune=False,
                         step_name='git fetch branch',
                     )
@@ -688,7 +680,7 @@
                 self.m.git(
                     'git set upstream',
                     'branch',
-                    '--set-upstream-to={}'.format(remote_branch),
+                    f'--set-upstream-to={remote_branch}',
                 )
 
             if not change.submitted:
@@ -754,9 +746,7 @@
             return None
 
         def handle_unapplied_change(change):
-            with self.m.step.nest(
-                'failed to apply {}'.format(change.name)
-            ) as pres:
+            with self.m.step.nest(f'failed to apply {change.name}') as pres:
                 pres.status = 'WARNING'
                 pres.links['gerrit'] = change.gerrit_url
                 pres.links['gitiles'] = change.gitiles_url
@@ -787,8 +777,8 @@
                 applied.extend(changes)
 
         with self.m.step.nest('status') as pres:
-            pres.step_summary_text = 'applied {}\nnot applied {}'.format(
-                applied, failed_to_apply,
+            pres.step_summary_text = (
+                f'applied {applied}\nnot applied {failed_to_apply}'
             )
 
         return StatusOfChanges(
@@ -883,19 +873,14 @@
                         for sub in excluded_submodules:
                             if sub not in submodule_paths:
                                 raise self.m.step.InfraFailure(
-                                    'excluded submodule {} is not a '
-                                    'submodule'.format(sub)
+                                    f'excluded submodule {sub} is not a submodule'
                                 )
-                            with self.m.step.nest(
-                                'excluding submodule {}'.format(sub)
-                            ):
+                            with self.m.step.nest(f'excluding submodule {sub}'):
                                 pass
                             submodule_paths.remove(sub)
 
                     for sub in submodule_paths:
-                        with self.m.step.nest(
-                            'including submodule {}'.format(sub)
-                        ):
+                        with self.m.step.nest(f'including submodule {sub}'):
                             pass
 
                     if submodules or submodule_paths:
@@ -1124,7 +1109,7 @@
             pres.step_summary_text = str(branch_names)
 
         matching_branches = self._matching_branches(
-            ctx.options.remote, branch_names, name='{} has branch'.format(kind)
+            ctx.options.remote, branch_names, name=f'{kind} has branch'
         )
         if not matching_branches:
             with self.m.step.nest('no branch names match'):
@@ -1132,9 +1117,7 @@
 
         if len(matching_branches) > 1:
             with self.m.step.nest(
-                'too many matching branches ({})'.format(
-                    ', '.join(matching_branches)
-                )
+                f"too many matching branches ({', '.join(matching_branches)})"
             ) as pres:
                 pres.step_summary_text = (
                     "Can't figure out which {} branch to use. Remove some "
@@ -1144,7 +1127,7 @@
 
         manifest_branch = matching_branches.pop()
         self.m.step(
-            'changing {} branch to {}'.format(kind, manifest_branch), None,
+            f'changing {kind} branch to {manifest_branch}', None,
         )
         return manifest_branch
 
@@ -1202,9 +1185,7 @@
                             self.m.git(
                                 'git branch',
                                 'branch',
-                                '--set-upstream-to=origin/{}'.format(
-                                    manifest_branch
-                                ),
+                                f'--set-upstream-to=origin/{manifest_branch}',
                             )
 
                         self._apply_change(
@@ -1276,7 +1257,7 @@
         parts = name.split('/')
         if options.use_repo and parts[-1] == 'manifest':
             parts.pop(-1)
-        return 'checkout {}'.format(parts[-1])
+        return f'checkout {parts[-1]}'
 
     def __call__(self, options, root=None, name=None):
         """Checkout code."""
@@ -1319,13 +1300,11 @@
 
             if ctx.status:
                 for change in ctx.status.applied:
-                    pres.links[
-                        'applied {}'.format(change.name)
-                    ] = change.gerrit_url
+                    pres.links[f'applied {change.name}'] = change.gerrit_url
 
                 for change in ctx.status.not_applied:
                     pres.links[
-                        'failed to apply {}'.format(change.name)
+                        f'failed to apply {change.name}'
                     ] = change.gerrit_url
 
             snapshot_dir = self.m.path['start_dir'].join('snapshot')
diff --git a/recipe_modules/checkout/resources/submodule_status.py b/recipe_modules/checkout/resources/submodule_status.py
index 3605013..d59e94e 100755
--- a/recipe_modules/checkout/resources/submodule_status.py
+++ b/recipe_modules/checkout/resources/submodule_status.py
@@ -163,7 +163,7 @@
         line.strip(),
     )
     if not match:
-        raise ValueError('unrecognized submodule status line "{}"'.format(line))
+        raise ValueError(f'unrecognized submodule status line "{line}"')
 
     try:
         submodule = data[match.group('path')]
diff --git a/recipe_modules/checkout/test_api.py b/recipe_modules/checkout/test_api.py
index eeb305a..6688d7a 100644
--- a/recipe_modules/checkout/test_api.py
+++ b/recipe_modules/checkout/test_api.py
@@ -150,13 +150,11 @@
         self, git_repo=REPO, name='pigweed', branch=None, **kwargs
     ):
         if branch:
-            kwargs['git_ref'] = 'refs/heads/{}'.format(branch)
+            kwargs['git_ref'] = f'refs/heads/{branch}'
         ret = self.m.buildbucket.ci_build(git_repo=git_repo, **kwargs)
         if branch:
             ret += self.override_step_data(
-                'checkout {}.change data.process gitiles commit.number'.format(
-                    name
-                ),
+                f'checkout {name}.change data.process gitiles commit.number',
                 self.m.json.output([{'_number': '1234', 'branch': branch}]),
             )
         return ret
@@ -171,7 +169,7 @@
 
     def manifest_test_data(self, name='pigweed', raw_xml=DEFAULT_MANIFEST):
         return self.step_data(
-            'checkout {}.read manifest.read file'.format(name),
+            f'checkout {name}.read manifest.read file',
             self.m.file.read_text(raw_xml),
         )
 
@@ -179,9 +177,7 @@
         self, branch='main', num_parents=1, index=0, name='pigweed', message='',
     ):
         return self.override_step_data(
-            'checkout {}.change data.process gerrit changes.{}.details'.format(
-                name, index
-            ),
+            f'checkout {name}.change data.process gerrit changes.{index}.details',
             self.m.json.output(
                 {
                     'branch': branch,
@@ -204,9 +200,7 @@
         # evaluate to True when converted to bool. Gitiles returns an empty
         # dictionary when the branch does not exist.
         return self.m.git.get_remote_branch_head(
-            'checkout {}.manifest has branch.git ls-remote {}'.format(
-                name, branch,
-            ),
+            f'checkout {name}.manifest has branch.git ls-remote {branch}',
             'a' * 40,
         )
 
@@ -214,9 +208,9 @@
         files = set(files)
         files.add('.repo')
         return self.step_data(
-            'checkout {}.ls'.format(name),
+            f'checkout {name}.ls',
             stdout=self.m.raw_io.output_text(
-                ''.join(('{}\n'.format(x) for x in sorted(files)))
+                ''.join((f'{x}\n' for x in sorted(files)))
             ),
         )
 
@@ -284,7 +278,7 @@
         sub_data = {sub['path']: sub for sub in submodules}
 
         res = self.step_data(
-            '{}submodule status'.format(prefix), self.m.json.output(sub_data),
+            f'{prefix}submodule status', self.m.json.output(sub_data),
         )
 
         return res
@@ -306,14 +300,14 @@
 
     def change_applied(self, name):
         return self.post_process(
-            post_process.DoesNotRunRE, '.*failed to apply {}.*'.format(name)
+            post_process.DoesNotRunRE, f'.*failed to apply {name}.*'
         ) + self.post_process(
             post_process.MustRunRE, r'.*apply {}.git.*'.format(name)
         )
 
     def change_not_applied(self, name):
         return self.post_process(
-            post_process.MustRunRE, '.*failed to apply {}.*'.format(name)
+            post_process.MustRunRE, f'.*failed to apply {name}.*'
         ) + self.post_process(
             post_process.DoesNotRunRE, r'.*apply {}.git.*'.format(name)
         )
diff --git a/recipe_modules/checkout/tests/repo.py b/recipe_modules/checkout/tests/repo.py
index 310b821..9567a76 100644
--- a/recipe_modules/checkout/tests/repo.py
+++ b/recipe_modules/checkout/tests/repo.py
@@ -46,7 +46,7 @@
 
     def cl(project, change, patchset=1, name='pigweed'):
         return api.checkout.cl(
-            host='{}-review.googlesource.com'.format(name),
+            host=f'{name}-review.googlesource.com',
             project=project,
             change=change,
             patchset=patchset,
diff --git a/recipe_modules/checkout/tests/submodule.py b/recipe_modules/checkout/tests/submodule.py
index f241ae6..0277720 100644
--- a/recipe_modules/checkout/tests/submodule.py
+++ b/recipe_modules/checkout/tests/submodule.py
@@ -93,7 +93,7 @@
 
     def cl(project, change, patchset=1, name='pigweed'):
         return api.checkout.cl(
-            host='{}-review.googlesource.com'.format(name),
+            host=f'{name}-review.googlesource.com',
             project=project,
             change=change,
             patchset=patchset,
diff --git a/recipe_modules/cipd_upload/test_api.py b/recipe_modules/cipd_upload/test_api.py
index 9c93955..08ac1a3 100644
--- a/recipe_modules/cipd_upload/test_api.py
+++ b/recipe_modules/cipd_upload/test_api.py
@@ -23,9 +23,7 @@
         self, query, prefix='', platform='linux-amd64', instances=1
     ):
         package, rest = query.split(None, 1)
-        name = '{}cipd.cipd search {}/{} {}'.format(
-            prefix, package, platform, rest
-        )
+        name = f'{prefix}cipd.cipd search {package}/{platform} {rest}'
 
         return self.step_data(
             name, self.m.cipd.example_search(package, instances=instances),
diff --git a/recipe_modules/cq_deps/api.py b/recipe_modules/cq_deps/api.py
index 9adc55c..b4e985f 100644
--- a/recipe_modules/cq_deps/api.py
+++ b/recipe_modules/cq_deps/api.py
@@ -38,27 +38,23 @@
 
     @property
     def host(self):
-        return '{}-review.googlesource.com'.format(self.gerrit_name)
+        return f'{self.gerrit_name}-review.googlesource.com'
 
     @property
     def link(self):
-        return 'https://{}/{}'.format(self.host, self.change)
+        return f'https://{self.host}/{self.change}'
 
     @property
     def name(self):
-        return '{}:{}'.format(self.gerrit_name, self.change)
+        return f'{self.gerrit_name}:{self.change}'
 
     @property
     def remote(self):
-        return 'https://{}.googlesource.com/{}'.format(
-            self.gerrit_name, self.project
-        )
+        return f'https://{self.gerrit_name}.googlesource.com/{self.project}'
 
     @property
     def gerrit_url(self):
-        return 'https://{}-review.googlesource.com/c/{}'.format(
-            self.gerrit_name, self.change
-        )
+        return f'https://{self.gerrit_name}-review.googlesource.com/c/{self.change}'
 
     def __str__(self):
         return self.name
@@ -75,9 +71,9 @@
 
     def _lookup_cl(self, gerrit_name, commit):
         query_results = self.m.gerrit.change_query(
-            'number {}'.format(commit),
-            'commit:{}'.format(commit),
-            host='{}-review.googlesource.com'.format(gerrit_name),
+            f'number {commit}',
+            f'commit:{commit}',
+            host=f'{gerrit_name}-review.googlesource.com',
             max_attempts=2,
             timeout=30,
             # The strange value for _number is to ensure different hashes result
@@ -146,9 +142,7 @@
                         x for x in current.parents if x.name not in seen
                     )
 
-                    with self.m.step.nest(
-                        'parents {}'.format(current.name)
-                    ) as pres:
+                    with self.m.step.nest(f'parents {current.name}') as pres:
                         if current.parents:
                             pres.step_summary_text = ', '.join(
                                 x.name for x in current.parents
@@ -267,7 +261,7 @@
         parent changes themselves.
         """
 
-        with self.m.step.nest('resolve deps for {}'.format(change)) as pres:
+        with self.m.step.nest(f'resolve deps for {change}') as pres:
             deps = []
             for dep in self._parse_commit_message(change):
                 deps.append(dep)
@@ -283,7 +277,7 @@
 
         try:
             step = self.m.gerrit.change_details(
-                'details {}'.format(change),
+                f'details {change}',
                 change_id=str(change.change),
                 host=change.host,
                 query_params=['CURRENT_COMMIT', 'CURRENT_REVISION',],
diff --git a/recipe_modules/cq_deps/test_api.py b/recipe_modules/cq_deps/test_api.py
index 609d1fe..ed1345e 100644
--- a/recipe_modules/cq_deps/test_api.py
+++ b/recipe_modules/cq_deps/test_api.py
@@ -23,12 +23,12 @@
     def lookup_cl(self, commit_hash, number=None, prefix='', found=True):
         if not found:
             return self.override_step_data(
-                '{}resolve CL deps.number {}'.format(prefix, commit_hash),
+                f'{prefix}resolve CL deps.number {commit_hash}',
                 self.m.json.output(None),
             )
 
         return self.override_step_data(
-            '{}resolve CL deps.number {}'.format(prefix, commit_hash),
+            f'{prefix}resolve CL deps.number {commit_hash}',
             self.m.json.output([{'_number': number}] if number else []),
         )
 
@@ -52,7 +52,7 @@
         number = int(name.split(':')[1])
 
         if not parent:
-            parent = 'parent-{}'.format(number)
+            parent = f'parent-{number}'
 
         assert status in ('NEW', 'MERGED', 'ABANDONED')
 
@@ -100,9 +100,7 @@
         results = []
         for name in names:
             results.append(
-                self.post_process(
-                    post_process.MustRun, 'final deps.{}'.format(name),
-                )
+                self.post_process(post_process.MustRun, f'final deps.{name}',)
             )
 
         return sum(results[1:], results[0])
@@ -112,7 +110,7 @@
         for name in names:
             results.append(
                 self.post_process(
-                    post_process.DoesNotRun, 'final deps.{}'.format(name),
+                    post_process.DoesNotRun, f'final deps.{name}',
                 )
             )
 
@@ -121,5 +119,5 @@
     def assert_disabled(self, prefix=''):
         return self.post_process(
             post_process.MustRun,
-            '{}resolve CL deps.dependency processing disabled'.format(prefix),
+            f'{prefix}resolve CL deps.dependency processing disabled',
         )
diff --git a/recipe_modules/default_timeout/api.py b/recipe_modules/default_timeout/api.py
index 2903017..298921f 100644
--- a/recipe_modules/default_timeout/api.py
+++ b/recipe_modules/default_timeout/api.py
@@ -45,9 +45,7 @@
 
         soft_deadline = current_time + timeout
 
-        with self.m.step.nest(
-            'timeout {}'.format(nice_duration(timeout))
-        ) as pres:
+        with self.m.step.nest(f'timeout {nice_duration(timeout)}') as pres:
             pres.step_summary_text = f'soft_deadline: {soft_deadline}'
 
         deadline = sections_pb2.Deadline()
diff --git a/recipe_modules/environment/api.py b/recipe_modules/environment/api.py
index 9ba78ca..468ae91 100644
--- a/recipe_modules/environment/api.py
+++ b/recipe_modules/environment/api.py
@@ -128,11 +128,11 @@
         venv_dir = env.dir.join('venv')
 
         self.m.file.ensure_directory(
-            'mkdir {}'.format(self.m.path.basename(env.dir)), env.dir,
+            f'mkdir {self.m.path.basename(env.dir)}', env.dir,
         )
 
         self.m.file.ensure_directory(
-            'mkdir {}'.format(self.m.path.basename(venv_dir)), venv_dir,
+            f'mkdir {self.m.path.basename(venv_dir)}', venv_dir,
         )
 
         cmd = [
@@ -190,8 +190,7 @@
                                 continue
 
                             self.m.file.read_text(
-                                'read {}'.format(self.m.path.basename(entry)),
-                                entry,
+                                f'read {self.m.path.basename(entry)}', entry,
                             )
 
         json_data = self.m.file.read_json(
diff --git a/recipe_modules/pw_presubmit/api.py b/recipe_modules/pw_presubmit/api.py
index e213952..77bbd89 100644
--- a/recipe_modules/pw_presubmit/api.py
+++ b/recipe_modules/pw_presubmit/api.py
@@ -249,8 +249,7 @@
 
                 if step.export_dir:
                     self.m.file.ensure_directory(
-                        'mkdir {}'.format(ctx.options.export_dir_name),
-                        step.export_dir,
+                        f'mkdir {ctx.options.export_dir_name}', step.export_dir,
                     )
                 if log_dir and log_dir != step.export_dir:
                     self.m.file.ensure_directory('create log dir', log_dir)
diff --git a/recipe_modules/repo/api.py b/recipe_modules/repo/api.py
index d0d20ca..881db51 100644
--- a/recipe_modules/repo/api.py
+++ b/recipe_modules/repo/api.py
@@ -309,7 +309,7 @@
 
         def step_test_data():
             data = '\n'.join(
-                '%s|src/%s|cros|refs/heads/main' % (p, p)
+                f'{p}|src/{p}|cros|refs/heads/main'
                 for p in projects or ['a', 'b', 'c']
             )
             return self.m.raw_io.test_api.stream_output_text(data)
@@ -492,7 +492,7 @@
                         # problems when switching to a different manifest repo or branch.
                         for manifest_dir in ('manifests', 'manifests.git'):
                             self.m.file.rmtree(
-                                'remove .repo/%s' % manifest_dir,
+                                f'remove .repo/{manifest_dir}',
                                 root_path.join('.repo', manifest_dir),
                             )
 
diff --git a/recipe_modules/roll_util/api.py b/recipe_modules/roll_util/api.py
index cdb309d..837d845 100644
--- a/recipe_modules/roll_util/api.py
+++ b/recipe_modules/roll_util/api.py
@@ -122,7 +122,7 @@
     @direction.validator
     def check(self, _, value):  # pragma: no cover
         if value not in _Direction:
-            raise ValueError('invalid direction: {}'.format(value))
+            raise ValueError(f'invalid direction: {value}')
         if value == _Direction.CURRENT:
             raise ValueError('attempt to do a no-op roll')
 
@@ -147,9 +147,7 @@
         ]
 
         if _is_hash(self.old_revision) and self.direction == _Direction.FORWARD:
-            log_cmd.append(
-                '{}..{}'.format(self.old_revision, self.new_revision)
-            )
+            log_cmd.append(f'{self.old_revision}..{self.new_revision}')
         else:
             log_cmd.extend(('--max-count', '5', self.new_revision))
 
@@ -168,7 +166,7 @@
             owner = None
             reviewers = []
 
-            full_host = '{}-review.googlesource.com'.format(self.gerrit_name)
+            full_host = f'{self.gerrit_name}-review.googlesource.com'
 
             changes = []
 
@@ -178,7 +176,7 @@
             if i < 10:
                 changes = self._api.gerrit.change_query(
                     'get change-id',
-                    'commit:{}'.format(commit_hash),
+                    f'commit:{commit_hash}',
                     host=full_host,
                     test_data=self._api.json.test_api.output(
                         [{'_number': 12345}]
@@ -188,7 +186,7 @@
             if changes and len(changes) == 1:
                 number = changes[0]['_number']
                 step = self._api.gerrit.change_details(
-                    'get {}'.format(number),
+                    f'get {number}',
                     number,
                     host=full_host,
                     test_data=self._api.json.test_api.output(
@@ -299,7 +297,7 @@
 def _pprint_dict(d):
     result = []
     for k, v in sorted(d.items()):
-        result.append('{!r}: {!r}\n'.format(k, v))
+        result.append(f'{k!r}: {v!r}\n')
     return ''.join(result)
 
 
@@ -332,7 +330,7 @@
         prefix = 'pigweed.infra.roller.'
         if prefix not in email and not email.endswith('gserviceaccount.com'):
             user, domain = author.email.split('@')
-            email = '{}@{}{}'.format(user, prefix, domain)
+            email = f'{user}@{prefix}{domain}'
 
         return Account(author.name, email,)
 
@@ -351,11 +349,11 @@
             test_data = self.m.json.test_api.output([])
 
         return self.m.gerrit.account_query(
-            email, 'email:{}'.format(email), host=host, test_data=test_data,
+            email, f'email:{email}', host=host, test_data=test_data,
         ).json.output
 
     def include_cc(self, account, cc_domains, host):
-        with self.m.step.nest('cc {}'.format(account.email)) as pres:
+        with self.m.step.nest(f'cc {account.email}') as pres:
             domain = account.email.split('@', 1)[1]
             if domain.endswith('gserviceaccount.com'):
                 pres.step_summary_text = 'not CCing, robot account'
@@ -397,9 +395,7 @@
             footer=tuple(self.footer),
         )
 
-        with self.m.step.nest(
-            'message for {}'.format(roll.project_name)
-        ) as pres:
+        with self.m.step.nest(f'message for {roll.project_name}') as pres:
             pres.logs['template'] = template
             pres.logs['kwargs'] = _pprint_dict(kwargs)
             pres.logs['message'] = message.render()
@@ -480,7 +476,7 @@
             )
         ]
         texts.extend(x.render(with_footer=False) for x in messages)
-        texts.append('\n'.join('{}'.format(x) for x in self.footer))
+        texts.append('\n'.join(f'{x}' for x in self.footer))
 
         return '\n\n'.join(texts)
 
@@ -498,7 +494,7 @@
             else:
                 result = self._single_roll_message(*rolls).render()
             if self._commit_divider:
-                result += '\n{}'.format(self._commit_divider)
+                result += f'\n{self._commit_divider}'
             return result
 
     Direction = _Direction
@@ -579,8 +575,8 @@
             pres.step_summary_text = fmt.format(
                 old=old_revision[0:7], new=new_revision[0:7]
             )
-            pres.links[old_revision] = '{}/+/{}'.format(remote, old_revision)
-            pres.links[new_revision] = '{}/+/{}'.format(remote, new_revision)
+            pres.links[old_revision] = f'{remote}/+/{old_revision}'
+            pres.links[new_revision] = f'{remote}/+/{new_revision}'
 
     def normalize_remote(self, remote, base):
         """Convert relative paths to absolute paths.
diff --git a/recipe_modules/roll_util/test_api.py b/recipe_modules/roll_util/test_api.py
index 79ef0c5..314c22b 100644
--- a/recipe_modules/roll_util/test_api.py
+++ b/recipe_modules/roll_util/test_api.py
@@ -57,12 +57,12 @@
     def format_prefix(self, p):
         if not p:
             return p
-        return '{}.'.format(p.rstrip('.'))
+        return f"{p.rstrip('.')}."
 
     def commit_data(self, name, *commits, prefix=''):
         assert isinstance(name, str)
         return self.step_data(
-            '{}{}.git log'.format(prefix, name),
+            f'{prefix}{name}.git log',
             stdout=self.m.raw_io.output_text('\0'.join(commits)),
         )
 
@@ -77,23 +77,23 @@
     def noop_roll(self, prefix='', name='get roll direction'):
         prefix = self.format_prefix(prefix)
         return self.step_data(
-            '{}{}.is forward'.format(prefix, name), retcode=0
-        ) + self.step_data('{}{}.is backward'.format(prefix, name), retcode=0)
+            f'{prefix}{name}.is forward', retcode=0
+        ) + self.step_data(f'{prefix}{name}.is backward', retcode=0)
 
     def forward_roll(self, prefix='', name='get roll direction'):
         prefix = self.format_prefix(prefix)
         return self.step_data(
-            '{}{}.is forward'.format(prefix, name), retcode=0
-        ) + self.step_data('{}{}.is backward'.format(prefix, name), retcode=1)
+            f'{prefix}{name}.is forward', retcode=0
+        ) + self.step_data(f'{prefix}{name}.is backward', retcode=1)
 
     def backward_roll(self, prefix='', name='get roll direction'):
         prefix = self.format_prefix(prefix)
         return self.step_data(
-            '{}{}.is forward'.format(prefix, name), retcode=1
-        ) + self.step_data('{}{}.is backward'.format(prefix, name), retcode=0)
+            f'{prefix}{name}.is forward', retcode=1
+        ) + self.step_data(f'{prefix}{name}.is backward', retcode=0)
 
     def rebased_roll(self, prefix='', name='get roll direction'):
         prefix = self.format_prefix(prefix)
         return self.step_data(
-            '{}{}.is forward'.format(prefix, name), retcode=1
-        ) + self.step_data('{}{}.is backward'.format(prefix, name), retcode=1)
+            f'{prefix}{name}.is forward', retcode=1
+        ) + self.step_data(f'{prefix}{name}.is backward', retcode=1)
diff --git a/recipe_modules/roll_util/tests/single_roll.py b/recipe_modules/roll_util/tests/single_roll.py
index 9d5ae93..32fb8a1 100644
--- a/recipe_modules/roll_util/tests/single_roll.py
+++ b/recipe_modules/roll_util/tests/single_roll.py
@@ -157,7 +157,7 @@
         + api.roll_util.commit_data(
             'proj',
             *[
-                api.roll_util.commit('{:040}'.format(i), 'Commit {}'.format(i))
+                api.roll_util.commit(f'{i:040}', f'Commit {i}')
                 for i in range(0, 200)
             ],
         )
diff --git a/recipe_modules/util/api.py b/recipe_modules/util/api.py
index fd03fa7..c9a4dbe 100644
--- a/recipe_modules/util/api.py
+++ b/recipe_modules/util/api.py
@@ -83,11 +83,11 @@
         result = None
         with self.m.step.nest('checking comments'):
             for i, comment in enumerate(comments):
-                with self.m.step.nest('comment ({})'.format(i)) as pres:
+                with self.m.step.nest(f'comment ({i})') as pres:
                     pres.step_summary_text = comment
                     match = re.search(rx, comment)
                     if match:
-                        pres.step_summary_text = 'MATCH: {}'.format(comment)
+                        pres.step_summary_text = f'MATCH: {comment}'
                         result = match
                         break
 
diff --git a/recipe_modules/util/test_api.py b/recipe_modules/util/test_api.py
index 546c6ac..91f0dde 100644
--- a/recipe_modules/util/test_api.py
+++ b/recipe_modules/util/test_api.py
@@ -23,6 +23,6 @@
         if prefix and not prefix.endswith('.'):
             prefix += '.'
         return self.step_data(
-            '{}list change comments'.format(prefix),
+            f'{prefix}list change comments',
             self.m.json.output({'/PATCHSET_LEVEL': [{'message': comment}]}),
         )