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}]}),
         )
diff --git a/recipes/cipd_roller.py b/recipes/cipd_roller.py
index 5266cb5..0cd1c9f 100644
--- a/recipes/cipd_roller.py
+++ b/recipes/cipd_roller.py
@@ -136,7 +136,7 @@
         ][-1]
 
     basename = api.path.basename(cipd_json_path)
-    cipd_json = api.file.read_json('read {}'.format(basename), cipd_json_path)
+    cipd_json = api.file.read_json(f'read {basename}', cipd_json_path)
     packages = cipd_json
     if isinstance(cipd_json, dict):
         packages = cipd_json['packages']
@@ -148,16 +148,14 @@
             break
     else:
         raise api.step.StepFailure(
-            "couldn't find package {} in {}".format(
-                package_spec, cipd_json_path
-            )
+            f"couldn't find package {package_spec} in {cipd_json_path}"
         )
 
     assert package.get('platforms'), 'platforms empty in json'
     platforms = package.get('platforms')
     base, name = package_spec.rstrip('/').rsplit('/', 1)
     if _is_platform(name):
-        package_paths = ['{}/{}'.format(base, x) for x in platforms]
+        package_paths = [f'{base}/{x}' for x in platforms]
     else:
         package_paths = [package_spec]
 
@@ -195,7 +193,7 @@
         presentation.step_summary_text = '\n'.join(sorted(tags))
 
     if not tags:
-        err_lines = ['no common tags across "{}" refs of packages'.format(ref)]
+        err_lines = [f'no common tags across "{ref}" refs of packages']
         for package_path, package_tags in sorted(package_tags.items()):
             err_lines.append('')
             err_lines.append(package_path)
@@ -223,7 +221,7 @@
         return
 
     api.file.write_text(
-        'write {}'.format(basename),
+        f'write {basename}',
         cipd_json_path,
         api.json.dumps(cipd_json, indent=2, separators=(',', ': ')) + '\n',
     )
@@ -261,12 +259,10 @@
 
     def describe(package_path, *tags, **kwargs):
         return api.step_data(
-            'cipd describe {}'.format(package_path),
+            f'cipd describe {package_path}',
             api.cipd.example_describe(
                 package_path,
-                test_data_tags=[
-                    '{}:{}'.format(name, value) for name, value in tags
-                ],
+                test_data_tags=[f'{name}:{value}' for name, value in tags],
             ),
             **kwargs,
         )
@@ -296,27 +292,27 @@
 
     def relaxing_works(package_paths, tagname='git_revision'):
         return api.step_data(
-            'find shared tag.cipd describe {}'.format(package_paths[1]),
+            f'find shared tag.cipd describe {package_paths[1]}',
             api.cipd.example_describe(
-                package_paths[1], test_data_tags=['{}:{}'.format(tagname, 0)],
+                package_paths[1], test_data_tags=[f'{tagname}:{0}'],
             ),
         )
 
     def relaxing_does_not_work(package_paths, tagname='git_revision'):
         # No version of package_paths[1] with hoped-for tag.
         return api.step_data(
-            'find shared tag.cipd describe {}'.format(package_paths[0]),
+            f'find shared tag.cipd describe {package_paths[0]}',
             api.cipd.example_describe(package_paths[0]),
             retcode=1,
         ) + api.step_data(
-            'find shared tag.cipd describe {}'.format(package_paths[1]),
+            f'find shared tag.cipd describe {package_paths[1]}',
             api.cipd.example_describe(package_paths[1]),
             retcode=1,
         )
 
     def read_file_step_data(package_json_name, *packages):
         return api.step_data(
-            'read {}'.format(package_json_name),
+            f'read {package_json_name}',
             api.file.read_json({'packages': packages}),
         )
 
@@ -358,7 +354,7 @@
 
     yield (
         api.status_check.test('bad_package_spec', status='failure')
-        + properties(package_spec='bad-{}'.format(spec))
+        + properties(package_spec=f'bad-{spec}')
         + api.checkout.ci_test_data()
         + read_file_step_data('pigweed.json', package(spec, 'git_revision:123'))
     )
diff --git a/recipes/cq_label.py b/recipes/cq_label.py
index 4f2e1e0..75bf618 100644
--- a/recipes/cq_label.py
+++ b/recipes/cq_label.py
@@ -120,13 +120,13 @@
 
             if value:
                 if dry_run:
-                    action = 'would set {} to {}'.format(label, value)
+                    action = f'would set {label} to {value}'
                     pres.step_summary_text = action
                     with api.step.nest(action):
                         pass
 
                 else:
-                    action = 'set {} to {}'.format(label, value)
+                    action = f'set {label} to {value}'
                     pres.step_summary_text = action
                     api.gerrit.set_review(
                         action,
@@ -143,7 +143,7 @@
 
 
 def _remove_running_labelled(api, host, projects, label, cq_account, dry_run):
-    project_part = ' OR '.join('project:{}'.format(p) for p in projects)
+    project_part = ' OR '.join(f'project:{p}' for p in projects)
     query_string = (
         f'is:open '
         f'(label:Commit-Queue+1 OR label:Commit-Queue+2) '
diff --git a/recipes/docs_builder.py b/recipes/docs_builder.py
index c95c814..f831529 100644
--- a/recipes/docs_builder.py
+++ b/recipes/docs_builder.py
@@ -46,9 +46,7 @@
     html = api.build.dir.join('docs', 'gen', 'docs', 'html')
 
     if props.dry_run:
-        path = 'testing/swarming-{}/{}'.format(
-            api.swarming.task_id, checkout.revision()
-        )
+        path = f'testing/swarming-{api.swarming.task_id}/{checkout.revision()}'
     else:
         path = checkout.revision()
 
@@ -80,11 +78,10 @@
             multithreaded=True,
         )
 
-    link = '{}/?rev={}'.format(base_url.rstrip("/"), path)
+    link = f"{base_url.rstrip('/')}/?rev={path}"
 
     return result.RawResult(
-        summary_markdown='Docs available at {}.'.format(link),
-        status=common.SUCCESS,
+        summary_markdown=f'Docs available at {link}.', status=common.SUCCESS,
     )
 
 
diff --git a/recipes/envtest.py b/recipes/envtest.py
index 8392886..a152830 100644
--- a/recipes/envtest.py
+++ b/recipes/envtest.py
@@ -83,7 +83,7 @@
     sh_source = ''.join(x + '\n' for x in commands)
     base = 'run.bat' if api.platform.is_win else 'run.sh'
     sh_path = setup_path_dir.join(base)
-    api.file.write_text('write {}'.format(base), sh_path, sh_source)
+    api.file.write_text(f'write {base}', sh_path, sh_source)
 
     with api.macos_sdk(), api.step.defer_results():
         with api.context(cwd=run, env=env):
@@ -102,10 +102,10 @@
                 '*/*.log',
             ]:
                 for path in api.file.glob_paths(
-                    'glob environment/{}'.format(pattern), env_root, pattern
+                    f'glob environment/{pattern}', env_root, pattern
                 ).get_result():
                     log = api.file.read_text(
-                        'read {}'.format(api.path.basename(path)), path
+                        f'read {api.path.basename(path)}', path
                     ).get_result()
                     pres.logs[api.path.relpath(path, env_root)] = log
 
diff --git a/recipes/pigweed.py b/recipes/pigweed.py
index 2ef1565..5320403 100644
--- a/recipes/pigweed.py
+++ b/recipes/pigweed.py
@@ -42,7 +42,7 @@
 
 
 def run_build_steps(api, presentation):
-    builder_name = '{}-subbuild'.format(api.buildbucket.build.builder.builder)
+    builder_name = f'{api.buildbucket.build.builder.builder}-subbuild'
 
     extra_props = {'parent_id': api.buildbucket_util.id}
 
@@ -68,7 +68,7 @@
         # better propagate error messages. If the child summary is multiple
         # lines, start it on a new line.
         subbuild_summary = output_build.summary_markdown.strip()
-        summary = '[build](%s) %s' % (build_url, description)
+        summary = f'[build]({build_url}) {description}'
         if subbuild_summary:
             summary += ':'
             # If the subbuild summary is already multiple lines, start it on a
diff --git a/recipes/pw_presubmit.py b/recipes/pw_presubmit.py
index b8d2e67..cc69c04 100644
--- a/recipes/pw_presubmit.py
+++ b/recipes/pw_presubmit.py
@@ -58,7 +58,7 @@
     ]
 
     return api.step(
-        'sign {}'.format(name), cmd, stdout=api.raw_io.output_text(),
+        f'sign {name}', cmd, stdout=api.raw_io.output_text(),
     ).stdout
 
 
@@ -192,9 +192,7 @@
                     continue  # pragma: no cover
 
                 for entry in api.file.listdir(
-                    'ls {}/{}'.format(
-                        step.name, presubmit.options.export_dir_name,
-                    ),
+                    f'ls {step.name}/{presubmit.options.export_dir_name}',
                     step.export_dir,
                     recursive=True,
                 ):
@@ -253,7 +251,7 @@
             pres.links['browse'] = browse_link
 
             return result.RawResult(
-                summary_markdown='[artifacts]({})'.format(browse_link),
+                summary_markdown=f'[artifacts]({browse_link})',
                 status=common.SUCCESS,
             )
 
@@ -265,12 +263,12 @@
         return api.path.exists(
             api.path['start_dir'].join('presubmit', step_name, 'export')
         ) + api.step_data(
-            'upload.ls {}/export'.format(step_name), api.file.listdir(files),
+            f'upload.ls {step_name}/export', api.file.listdir(files),
         )
 
     def signature(step_name, filename):
         return api.step_data(
-            'upload.sign {}/export/{}'.format(step_name, filename),
+            f'upload.sign {step_name}/export/{filename}',
             stdout=api.raw_io.output_text('John Hancock'),
         )
 
diff --git a/recipes/repo_roller.py b/recipes/repo_roller.py
index 9483a62..7b5d202 100644
--- a/recipes/repo_roller.py
+++ b/recipes/repo_roller.py
@@ -83,7 +83,7 @@
     if commit and commit.project:
         new_revision = commit.id
         host = commit.host
-        bb_remote = 'https://{}/{}'.format(host, commit.project)
+        bb_remote = f'https://{host}/{commit.project}'
 
     tree = _TreeBuilder()
     parser = xml.etree.ElementTree.XMLParser(target=tree)
@@ -128,8 +128,8 @@
             fetch_host = proj_attrib['fetch'].strip('/')
             if fetch_host.startswith('..'):
                 parsed = urllib.parse.urlparse(checkout.options.remote)
-                fetch_host = '{}://{}{}'.format(
-                    parsed.scheme, parsed.netloc, fetch_host[2:]
+                fetch_host = (
+                    f'{parsed.scheme}://{parsed.netloc}{fetch_host[2:]}'
                 )
 
             manifest_remote = fetch_host + '/' + proj_attrib['name'].strip('/')
diff --git a/recipes/run_script.py b/recipes/run_script.py
index 4fd885b..8f1dc54 100644
--- a/recipes/run_script.py
+++ b/recipes/run_script.py
@@ -43,7 +43,7 @@
             cmd.append(arg)
 
         with api.context(cwd=checkout.root):
-            api.step('run {}'.format(props.script), cmd)
+            api.step(f'run {props.script}', cmd)
 
 
 def GenTests(api):
diff --git a/recipes/static_checks.py b/recipes/static_checks.py
index f62d497..70195b9 100644
--- a/recipes/static_checks.py
+++ b/recipes/static_checks.py
@@ -178,8 +178,8 @@
             return
 
     with api.step.nest('does not match') as pres:
-        pres.step_summary_text = '{!r} does not match {!r}'.format(
-            details['message'], regexp,
+        pres.step_summary_text = (
+            f"{details['message']!r} does not match {regexp!r}"
         )
 
     if failure_message:
@@ -203,7 +203,7 @@
         match = regex.search(details['message'])
         if match:
             raise api.step.StepFailure(
-                'found "{}" in commit message'.format(match.group(0))
+                f'found "{match.group(0)}" in commit message'
             )
 
 
@@ -377,19 +377,18 @@
 
             # Make sure the nth warning is also found.
             if n > 1:
-                result += must_run(api, 'check requires.warning ({})'.format(n))
+                result += must_run(api, f'check requires.warning ({n})')
 
             # Make sure the nth warning is the last one.
             result += api.post_process(
-                post_process.DoesNotRun,
-                'check requires.warning ({})'.format(n + 1),
+                post_process.DoesNotRun, f'check requires.warning ({n + 1})',
             )
 
             return result
 
     def requires_test(name, msg, warnings=0, votes=None, **kwargs):
         status = 'failure' if warnings else 'success'
-        res = api.status_check.test('requires.{}'.format(name), status=status)
+        res = api.status_check.test(f'requires.{name}', status=status)
         res += api.properties(**kwargs)
         res += api.checkout.try_test_data()
         res += change_details(api, msg, votes=votes)
@@ -433,7 +432,7 @@
 
 def tested_tests(api):
     def test(name, msg, status='success', require_tested=True, **kwargs):
-        res = api.status_check.test('tested.{}'.format(name), status=status)
+        res = api.status_check.test(f'tested.{name}', status=status)
         res += api.properties(require_tested=require_tested, **kwargs)
         res += api.checkout.try_test_data()
         res += change_details(api, msg)
@@ -465,7 +464,7 @@
         if properties:
             final_props.update(properties)
         return (
-            api.status_check.test('docs.{}'.format(name), status=status)
+            api.status_check.test(f'docs.{name}', status=status)
             + api.properties(**final_props)
             + change_details(api, msg=msg, **kwargs)
         )
@@ -565,7 +564,7 @@
         votes = kwargs.pop('votes', {'Readability-Trivial': None})
 
         return (
-            api.status_check.test('readability.{}'.format(name), status=status)
+            api.status_check.test(f'readability.{name}', status=status)
             + api.properties(**final_props)
             + change_details(api, msg=msg, votes=votes, **kwargs,)
             + api.cq(api.cq.FULL_RUN)
@@ -695,7 +694,7 @@
 
 def regexp_tests(api):
     def test(name, msg, regexp, failure_msg='', status='success', **kwargs):
-        res = api.status_check.test('regexp.{}'.format(name), status=status)
+        res = api.status_check.test(f'regexp.{name}', status=status)
         res += api.properties(
             commit_message_regexp=regexp,
             commit_message_regexp_failure_message=failure_msg,
diff --git a/recipes/submodule_roller.py b/recipes/submodule_roller.py
index 3d25970..b5c60a6 100644
--- a/recipes/submodule_roller.py
+++ b/recipes/submodule_roller.py
@@ -114,7 +114,7 @@
         submodule.dir = checkout.root.join(submodule.path)
 
         with api.step.nest(submodule.name) as pres:
-            section = 'submodule "{}"'.format(submodule.name)
+            section = f'submodule "{submodule.name}"'
             if not parser.has_section(section):
                 sections = parser.sections()
                 submodules = sorted(
@@ -232,7 +232,7 @@
                 branches[k.replace('_branch', '')] = v
 
         for x in branches:
-            del submodules['{}_branch'.format(x)]
+            del submodules[f'{x}_branch']
 
         text = []
         for k, v in submodules.items():
@@ -242,7 +242,7 @@
                 )
             )
             if k in branches:
-                text.append('\tbranch = {}\n'.format(branches[k]))
+                text.append(f'\tbranch = {branches[k]}\n')
 
         return api.step_data(
             'read .gitmodules', api.file.read_text(''.join(text))
diff --git a/recipes/target_to_cipd.py b/recipes/target_to_cipd.py
index 521fff6..78ba004 100644
--- a/recipes/target_to_cipd.py
+++ b/recipes/target_to_cipd.py
@@ -80,7 +80,7 @@
                 )
                 if not sources:  # pragma: no cover
                     api.file.listdir('ls build', api.build.dir, recursive=True)
-                    raise api.step.StepFailure('no matches for {}'.format(glob))
+                    raise api.step.StepFailure(f'no matches for {glob}')
                 for source in sources:
                     with api.step.nest('source') as pres:
                         pres.step_summary_text = '\n'.join(
@@ -92,14 +92,10 @@
                                 replacement.old, replacement.new
                             )
                         dest = pkg_dir.join(relpath)
-                        pres.step_summary_text += '\n{}'.format(dest)
+                        pres.step_summary_text += f'\n{dest}'
                     dirname = api.path.dirname(dest)
-                    api.file.ensure_directory(
-                        'mkdir {}'.format(dirname), dirname
-                    )
-                    api.file.copy(
-                        'copy {} {}'.format(source, dest), source, dest
-                    )
+                    api.file.ensure_directory(f'mkdir {dirname}', dirname)
+                    api.file.copy(f'copy {source} {dest}', source, dest)
 
     else:
         if api.path.isdir(export_dir):
@@ -130,13 +126,11 @@
         if props.roller_name:
             api.scheduler.emit_trigger(
                 api.scheduler.GitilesTrigger(
-                    change.remote,
-                    'refs/heads/{}'.format(change.branch),
-                    change.ref,
+                    change.remote, f'refs/heads/{change.branch}', change.ref,
                 ),
                 api.buildbucket.build.builder.project,
                 (props.roller_name,),
-                'trigger {}'.format(props.roller_name),
+                f'trigger {props.roller_name}',
             )
 
 
diff --git a/recipes/tokendb_check.py b/recipes/tokendb_check.py
index e116455..1d13513 100644
--- a/recipes/tokendb_check.py
+++ b/recipes/tokendb_check.py
@@ -35,9 +35,9 @@
 def _read_tokens(api, path, revision):
     """Read token hashes from a given path at a specific revision."""
     step = api.git(
-        'show {}'.format(revision),
+        f'show {revision}',
         'show',
-        '{}:{}'.format(revision, path),
+        f'{revision}:{path}',
         stdout=api.raw_io.output(),
     )
     step.presentation.logs['stdout'] = step.stdout
@@ -110,7 +110,7 @@
 
 def GenTests(api):
     def diff(path, added, removed):
-        return '{} {} {}'.format(added, removed, path)
+        return f'{added} {removed} {path}'
 
     def tokens(path, old_contents, new_contents):
         return api.step_data(
diff --git a/recipes/tokendb_updater.py b/recipes/tokendb_updater.py
index 043a74e..43efa08 100644
--- a/recipes/tokendb_updater.py
+++ b/recipes/tokendb_updater.py
@@ -73,10 +73,10 @@
 
     tokendb_host = checkout.gerrit_host().replace('-review.', '.')
     if props.tokendb_host:
-        tokendb_host = '{}.googlesource.com'.format(props.tokendb_host)
+        tokendb_host = f'{props.tokendb_host}.googlesource.com'
     tokendb_project = props.tokendb_project or checkout.gerrit_project()
-    tokendb_remote = 'https://{}/{}'.format(
-        tokendb_host.rstrip('/'), tokendb_project.strip('/')
+    tokendb_remote = (
+        f"https://{tokendb_host.rstrip('/')}/{tokendb_project.strip('/')}"
     )
 
     # If the token database is in the top-level repo we just use that. If not
@@ -105,7 +105,7 @@
     if generated_tokendb_path != tokendb_path:
         api.file.copy('copy', generated_tokendb_path, tokendb_path)
 
-    message = 'Update token db for commit {}'.format(checkout.revision()[0:15])
+    message = f'Update token db for commit {checkout.revision()[0:15]}'
 
     change = api.auto_roller.attempt_roll(
         props.auto_roller_options,
diff --git a/recipes/txt_roller.py b/recipes/txt_roller.py
index cfb5f64..4f17a79 100644
--- a/recipes/txt_roller.py
+++ b/recipes/txt_roller.py
@@ -58,7 +58,7 @@
     if commit and commit.project:
         new_revision = commit.id
         host = commit.host
-        bb_remote = 'https://{}/{}'.format(host, commit.project)
+        bb_remote = f'https://{host}/{commit.project}'
 
     # If we still don't have a revision then it wasn't in the trigger. (Perhaps
     # this was manually triggered.) In this case we update to the
diff --git a/recipes/xrefs.py b/recipes/xrefs.py
index ce6bf47..cf27d0f 100644
--- a/recipes/xrefs.py
+++ b/recipes/xrefs.py
@@ -72,7 +72,7 @@
             api.swarming.task_id, name, checkout.revision()
         )
     else:
-        final_kzip_name = '{}/{}.kzip'.format(name, checkout.revision())
+        final_kzip_name = f'{name}/{checkout.revision()}.kzip'
 
     api.kythe.extract_and_upload(
         checkout_dir=checkout.root,