diff --git a/pylib/anki/db.py b/pylib/anki/db.py
index eebc5be67..ae5d55eac 100644
--- a/pylib/anki/db.py
+++ b/pylib/anki/db.py
@@ -47,7 +47,7 @@ class DB:
res = self._db.execute(sql, a)
if self.echo:
# print a, ka
- print(sql, "%0.3fms" % ((time.time() - t) * 1000))
+ print(sql, f"{(time.time() - t) * 1000:0.3f}ms")
if self.echo == "2":
print(a, ka)
return res
@@ -57,7 +57,7 @@ class DB:
t = time.time()
self._db.executemany(sql, l)
if self.echo:
- print(sql, "%0.3fms" % ((time.time() - t) * 1000))
+ print(sql, f"{(time.time() - t) * 1000:0.3f}ms")
if self.echo == "2":
print(l)
@@ -65,7 +65,7 @@ class DB:
t = time.time()
self._db.commit()
if self.echo:
- print("commit %0.3fms" % ((time.time() - t) * 1000))
+ print(f"commit {(time.time() - t) * 1000:0.3f}ms")
def executescript(self, sql: str) -> None:
self.mod = True
diff --git a/pylib/anki/decks.py b/pylib/anki/decks.py
index a102a2145..b40216b48 100644
--- a/pylib/anki/decks.py
+++ b/pylib/anki/decks.py
@@ -234,9 +234,9 @@ class DeckManager(DeprecatedNamesMixin):
dids = set(dids)
if include_subdecks:
dids.update([child[1] for did in dids for child in self.children(did)])
+ str_ids = ids2str(dids)
count = self.col.db.scalar(
- "select count() from cards where did in {0} or "
- "odid in {0}".format(ids2str(dids))
+ f"select count() from cards where did in {str_ids} or odid in {str_ids}"
)
return count
diff --git a/pylib/anki/importing/mnemo.py b/pylib/anki/importing/mnemo.py
index cc508552c..fb1305da7 100644
--- a/pylib/anki/importing/mnemo.py
+++ b/pylib/anki/importing/mnemo.py
@@ -149,7 +149,7 @@ acq_reps+ret_reps, lapses, card_type_id from cards"""
mm = self.col.models
t = mm.new_template("Back")
t["qfmt"] = "{{Back}}"
- t["afmt"] = t["qfmt"] + "\n\n
\n\n{{Front}}" # type: ignore
+ t["afmt"] = f"{t['qfmt']}\n\n
\n\n{{{{Front}}}}" # type: ignore
mm.add_template(m, t)
self._addFronts(notes, m)
@@ -161,19 +161,15 @@ acq_reps+ret_reps, lapses, card_type_id from cards"""
mm.addField(m, fm)
t = mm.new_template("Recognition")
t["qfmt"] = "{{Expression}}"
- t["afmt"] = (
- cast(str, t["qfmt"])
- + """\n\n
\n\n\
-{{Pronunciation}}
\n{{Meaning}}
\n{{Notes}}"""
- )
+ t[
+ "afmt"
+ ] = f"{cast(str, t['qfmt'])}\n\n
\n\n{{{{Pronunciation}}}}
\n{{{{Meaning}}}}
\n{{{{Notes}}}}"
mm.add_template(m, t)
t = mm.new_template("Production")
t["qfmt"] = "{{Meaning}}"
- t["afmt"] = (
- cast(str, t["qfmt"])
- + """\n\n
\n\n\
-{{Expression}}
\n{{Pronunciation}}
\n{{Notes}}"""
- )
+ t[
+ "afmt"
+ ] = f"{cast(str, t['qfmt'])}\n\n
\n\n{{{{Expression}}}}
\n{{{{Pronunciation}}}}
\n{{{{Notes}}}}"
mm.add_template(m, t)
mm.add(m)
self._addFronts(notes, m, fields=("f", "p_1", "m_1", "n"))
diff --git a/pylib/anki/models.py b/pylib/anki/models.py
index 932b461c1..1f172f9b8 100644
--- a/pylib/anki/models.py
+++ b/pylib/anki/models.py
@@ -214,7 +214,7 @@ class ModelManager(DeprecatedNamesMixin):
def ensure_name_unique(self, notetype: NotetypeDict) -> None:
existing_id = self.id_for_name(notetype["name"])
if existing_id is not None and existing_id != notetype["id"]:
- notetype["name"] += "-" + checksum(str(time.time()))[:5]
+ notetype["name"] += f"-{checksum(str(time.time()))[:5]}"
def update_dict(self, notetype: NotetypeDict) -> OpChanges:
"Update a NotetypeDict. Caller will need to re-load notetype if new fields/cards added."
diff --git a/pylib/anki/scheduler/legacy.py b/pylib/anki/scheduler/legacy.py
index c289400c3..85afb717d 100644
--- a/pylib/anki/scheduler/legacy.py
+++ b/pylib/anki/scheduler/legacy.py
@@ -71,10 +71,9 @@ else
end)
"""
self.col.db.execute(
- """
-update cards set did = odid, %s,
-due = (case when odue>0 then odue else due end), odue = 0, odid = 0, usn = ? where %s"""
- % (queue, lim),
+ f"""
+update cards set did = odid, {queue},
+due = (case when odue>0 then odue else due end), odue = 0, odid = 0, usn = ? where {lim}""",
self.col.usn(),
)
diff --git a/pylib/anki/scheduler/v1.py b/pylib/anki/scheduler/v1.py
index adbd7a177..0a4ddccc3 100644
--- a/pylib/anki/scheduler/v1.py
+++ b/pylib/anki/scheduler/v1.py
@@ -381,9 +381,7 @@ limit %d"""
else:
# benchmarks indicate it's about 10x faster to search all decks
# with the index than scan the table
- extra = " and did in " + ids2str(
- d.id for d in self.col.decks.all_names_and_ids()
- )
+ extra = f" and did in {ids2str(d.id for d in self.col.decks.all_names_and_ids())}"
# review cards in relearning
self.col.db.execute(
f"""
diff --git a/pylib/anki/scheduler/v2.py b/pylib/anki/scheduler/v2.py
index 1eddef19b..750bb4635 100644
--- a/pylib/anki/scheduler/v2.py
+++ b/pylib/anki/scheduler/v2.py
@@ -1141,7 +1141,7 @@ and (queue={QUEUE_TYPE_NEW} or (queue={QUEUE_TYPE_REV} and due<=?))""",
return self.col.tr.scheduling_end()
s = self.col.format_timespan(ivl_secs, FormatTimeSpan.ANSWER_BUTTONS)
if ivl_secs < self.col.conf["collapseTime"]:
- s = "<" + s
+ s = f"<{s}"
return s
def _is_finished(self) -> bool:
diff --git a/pylib/anki/utils.py b/pylib/anki/utils.py
index 53a573c7a..e59629af0 100644
--- a/pylib/anki/utils.py
+++ b/pylib/anki/utils.py
@@ -91,7 +91,7 @@ def htmlToTextLine(s: str) -> str:
def ids2str(ids: Iterable[Union[int, str]]) -> str:
"""Given a list of integers, return a string '(int1,int2,...)'."""
- return "(%s)" % ",".join(str(i) for i in ids)
+ return f"({','.join(str(i) for i in ids)})"
def timestampID(db: DBProxy, table: str) -> int:
diff --git a/pylib/tests/run_mypy.py b/pylib/tests/run_mypy.py
index 122b3a08d..fc5194a89 100644
--- a/pylib/tests/run_mypy.py
+++ b/pylib/tests/run_mypy.py
@@ -32,7 +32,7 @@ if __name__ == "__main__":
# strip off prefix
for entry in sys.path:
if "__mypy_" in entry:
- typeshed = entry[4:] + "\\mypy\\typeshed"
+ typeshed = f"{entry[4:]}\\mypy\\typeshed"
args.append("--custom-typeshed-dir")
args.append(typeshed)
diff --git a/pylib/tests/test_importing.py b/pylib/tests/test_importing.py
index 40dc4d7c5..7cc5a4f6b 100644
--- a/pylib/tests/test_importing.py
+++ b/pylib/tests/test_importing.py
@@ -63,7 +63,7 @@ def test_anki2_mediadupes():
note.write("bar")
imp = Anki2Importer(empty, col.path)
imp.run()
- assert sorted(os.listdir(empty.media.dir())) == ["foo.mp3", "foo_%s.mp3" % mid]
+ assert sorted(os.listdir(empty.media.dir())) == ["foo.mp3", f"foo_{mid}.mp3"]
n = empty.get_note(empty.db.scalar("select id from notes"))
assert "_" in n.fields[0]
# if the localized media file already exists, we rewrite the note and
@@ -73,8 +73,8 @@ def test_anki2_mediadupes():
note.write("bar")
imp = Anki2Importer(empty, col.path)
imp.run()
- assert sorted(os.listdir(empty.media.dir())) == ["foo.mp3", "foo_%s.mp3" % mid]
- assert sorted(os.listdir(empty.media.dir())) == ["foo.mp3", "foo_%s.mp3" % mid]
+ assert sorted(os.listdir(empty.media.dir())) == ["foo.mp3", f"foo_{mid}.mp3"]
+ assert sorted(os.listdir(empty.media.dir())) == ["foo.mp3", f"foo_{mid}.mp3"]
n = empty.get_note(empty.db.scalar("select id from notes"))
assert "_" in n.fields[0]
diff --git a/pylib/tests/test_latex.py b/pylib/tests/test_latex.py
index 5ad4b3529..89c8229a3 100644
--- a/pylib/tests/test_latex.py
+++ b/pylib/tests/test_latex.py
@@ -93,7 +93,7 @@ def test_latex():
def _test_includes_bad_command(bad):
col = getEmptyCol()
note = col.newNote()
- note["Front"] = "[latex]%s[/latex]" % bad
+ note["Front"] = f"[latex]{bad}[/latex]"
col.addNote(note)
q = without_unicode_isolation(note.cards()[0].question())
- return ("'%s' is not allowed on cards" % bad in q, "Card content: %s" % q)
+ return (f"'{bad}' is not allowed on cards" in q, f"Card content: {q}")
diff --git a/pylib/tests/test_schedv1.py b/pylib/tests/test_schedv1.py
index 056d49c69..77fb7c06f 100644
--- a/pylib/tests/test_schedv1.py
+++ b/pylib/tests/test_schedv1.py
@@ -910,7 +910,7 @@ def test_timing():
# add a few review cards, due today
for i in range(5):
note = col.newNote()
- note["Front"] = "num" + str(i)
+ note["Front"] = f"num{str(i)}"
col.addNote(note)
c = note.cards()[0]
c.type = CARD_TYPE_REV
diff --git a/pylib/tests/test_schedv2.py b/pylib/tests/test_schedv2.py
index e374d733a..fbf5cbb0c 100644
--- a/pylib/tests/test_schedv2.py
+++ b/pylib/tests/test_schedv2.py
@@ -1019,7 +1019,7 @@ def test_timing():
# add a few review cards, due today
for i in range(5):
note = col.newNote()
- note["Front"] = "num" + str(i)
+ note["Front"] = f"num{str(i)}"
col.addNote(note)
c = note.cards()[0]
c.type = CARD_TYPE_REV
diff --git a/pylib/tools/hookslib.py b/pylib/tools/hookslib.py
index 1628d4929..c4331b22b 100644
--- a/pylib/tools/hookslib.py
+++ b/pylib/tools/hookslib.py
@@ -36,7 +36,7 @@ class Hook:
types = []
for arg in self.args or []:
(name, type) = arg.split(":")
- type = '"' + type.strip() + '"'
+ type = f'"{type.strip()}"'
types.append(type)
types_str = ", ".join(types)
return f"Callable[[{types_str}], {self.return_type or 'None'}]"
@@ -60,7 +60,7 @@ class Hook:
return "hook"
def classname(self) -> str:
- return "_" + stringcase.pascalcase(self.full_name())
+ return f"_{stringcase.pascalcase(self.full_name())}"
def list_code(self) -> str:
return f"""\
@@ -126,7 +126,7 @@ class {self.classname()}:
# legacy support
anki.hooks.runHook({self.legacy_args()})
"""
- return out + "\n\n"
+ return f"{out}\n\n"
def filter_fire_code(self) -> str:
arg_names = self.arg_names()
@@ -150,16 +150,16 @@ class {self.classname()}:
out += f"""\
return {arg_names[0]}
"""
- return out + "\n\n"
+ return f"{out}\n\n"
def write_file(path: str, hooks: List[Hook], prefix: str, suffix: str):
hooks.sort(key=attrgetter("name"))
- code = prefix + "\n"
+ code = f"{prefix}\n"
for hook in hooks:
code += hook.code()
- code += "\n" + suffix
+ code += f"\n{suffix}"
# work around issue with latest black
if sys.platform == "win32" and "HOME" in os.environ:
diff --git a/pylib/tools/protoc_wrapper.py b/pylib/tools/protoc_wrapper.py
index f10af3464..42c1dc3d8 100644
--- a/pylib/tools/protoc_wrapper.py
+++ b/pylib/tools/protoc_wrapper.py
@@ -21,11 +21,11 @@ else:
subprocess.run(
[
protoc,
- "--plugin=protoc-gen-mypy=" + mypy_protobuf,
+ f"--plugin=protoc-gen-mypy={mypy_protobuf}",
"--python_out=.",
"--mypy_out=.",
- "-I" + prefix,
- "-Iexternal/ankidesktop/" + prefix,
+ f"-I{prefix}",
+ f"-Iexternal/ankidesktop/{prefix}",
*protos,
],
# mypy prints to stderr on success :-(