diff --git a/pylib/anki/cards.py b/pylib/anki/cards.py
index 9b5068b45..bfb4a937b 100644
--- a/pylib/anki/cards.py
+++ b/pylib/anki/cards.py
@@ -123,7 +123,7 @@ class Card:
# legacy
def css(self) -> str:
- return "" % self.render_output().css
+ return f""
def render_output(
self, reload: bool = False, browser: bool = False
diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py
index d909832a7..b493835aa 100644
--- a/pylib/anki/collection.py
+++ b/pylib/anki/collection.py
@@ -329,7 +329,7 @@ class Collection:
##########################################################################
def nextID(self, type: str, inc: bool = True) -> Any:
- type = "next" + type.capitalize()
+ type = f"next{type.capitalize()}"
id = self.conf.get(type, 1)
if inc:
self.conf[type] = id + 1
@@ -368,7 +368,7 @@ class Collection:
def remove_notes_by_card(self, card_ids: List[int]) -> None:
if hooks.notes_will_be_deleted.count():
nids = self.db.list(
- "select nid from cards where id in " + ids2str(card_ids)
+ f"select nid from cards where id in {ids2str(card_ids)}"
)
hooks.notes_will_be_deleted(self, nids)
self._backend.remove_notes(note_ids=[], card_ids=card_ids)
@@ -503,7 +503,7 @@ class Collection:
return fields[mid]
for nid, mid, flds in self.db.all(
- "select id, mid, flds from notes where id in " + ids2str(nids)
+ f"select id, mid, flds from notes where id in {ids2str(nids)}"
):
flds = splitFields(flds)
ord = ordForMid(mid)
@@ -794,7 +794,7 @@ table.review-log {{ {revlog_style} }}
fn,
", ".join([customRepr(x) for x in args]),
)
- self._logHnd.write(buf + "\n")
+ self._logHnd.write(f"{buf}\n")
if devMode:
print(buf)
@@ -803,7 +803,7 @@ table.review-log {{ {revlog_style} }}
return
lpath = re.sub(r"\.anki2$", ".log", self.path)
if os.path.exists(lpath) and os.path.getsize(lpath) > 10 * 1024 * 1024:
- lpath2 = lpath + ".old"
+ lpath2 = f"{lpath}.old"
if os.path.exists(lpath2):
os.unlink(lpath2)
os.rename(lpath, lpath2)
diff --git a/pylib/anki/decks.py b/pylib/anki/decks.py
index fdbe4d892..17efb366e 100644
--- a/pylib/anki/decks.py
+++ b/pylib/anki/decks.py
@@ -385,7 +385,7 @@ class DeckManager:
def setDeck(self, cids: List[int], did: int) -> None:
self.col.db.execute(
- "update cards set did=?,usn=?,mod=? where id in " + ids2str(cids),
+ f"update cards set did=?,usn=?,mod=? where id in {ids2str(cids)}",
did,
self.col.usn(),
intTime(),
@@ -397,7 +397,7 @@ class DeckManager:
dids = [did]
for name, id in self.children(did):
dids.append(id)
- return self.col.db.list("select id from cards where did in " + ids2str(dids))
+ return self.col.db.list(f"select id from cards where did in {ids2str(dids)}")
def for_card_ids(self, cids: List[int]) -> List[int]:
return self.col.db.list(f"select did from cards where id in {ids2str(cids)}")
@@ -465,12 +465,12 @@ class DeckManager:
name = self.get(did)["name"]
actv = []
for g in self.all_names_and_ids():
- if g.name.startswith(name + "::"):
+ if g.name.startswith(f"{name}::"):
actv.append((g.name, g.id))
return actv
def child_ids(self, parent_name: str) -> Iterable[int]:
- prefix = parent_name + "::"
+ prefix = f"{parent_name}::"
return (d.id for d in self.all_names_and_ids() if d.name.startswith(prefix))
def deck_and_child_ids(self, deck_id: int) -> List[int]:
@@ -519,7 +519,7 @@ class DeckManager:
if not parents_names:
parents_names.append(part)
else:
- parents_names.append(parents_names[-1] + "::" + part)
+ parents_names.append(f"{parents_names[-1]}::{part}")
parents: List[Deck] = []
# convert to objects
for parent_name in parents_names:
diff --git a/pylib/anki/errors.py b/pylib/anki/errors.py
index 955d6009f..0fd0a5abb 100644
--- a/pylib/anki/errors.py
+++ b/pylib/anki/errors.py
@@ -103,4 +103,4 @@ class DeckRenameError(Exception):
self.description = description
def __str__(self) -> str:
- return "Couldn't rename deck: " + self.description
+ return f"Couldn't rename deck: {self.description}"
diff --git a/pylib/anki/importing/anki2.py b/pylib/anki/importing/anki2.py
index c0de3e3a0..df4e9bb39 100644
--- a/pylib/anki/importing/anki2.py
+++ b/pylib/anki/importing/anki2.py
@@ -265,7 +265,7 @@ class Anki2Importer(Importer):
tmpname = "::".join(DeckManager.path(name)[1:])
name = self.deckPrefix
if tmpname:
- name += "::" + tmpname
+ name += f"::{tmpname}"
# manually create any parents so we can pull in descriptions
head = ""
for parent in DeckManager.immediate_parent_path(name):
@@ -441,7 +441,7 @@ insert or ignore into revlog values (?,?,?,?,?,?,?,?,?)""",
return match.group(0)
# if model-local file exists from a previous import, use that
name, ext = os.path.splitext(fname)
- lname = "%s_%s%s" % (name, mid, ext)
+ lname = f"{name}_{mid}{ext}"
if self.dst.media.have(lname):
return match.group(0).replace(fname, lname)
# if missing or the same, pass unmodified
diff --git a/pylib/anki/importing/apkg.py b/pylib/anki/importing/apkg.py
index 36801aa4c..4c574c44d 100644
--- a/pylib/anki/importing/apkg.py
+++ b/pylib/anki/importing/apkg.py
@@ -25,7 +25,7 @@ class AnkiPackageImporter(Anki2Importer):
except KeyError:
suffix = ".anki2"
- col = z.read("collection" + suffix)
+ col = z.read(f"collection{suffix}")
colpath = tmpfile(suffix=".anki2")
with open(colpath, "wb") as f:
f.write(col)
diff --git a/pylib/anki/importing/csvfile.py b/pylib/anki/importing/csvfile.py
index bcf3b5fe9..e4595a377 100644
--- a/pylib/anki/importing/csvfile.py
+++ b/pylib/anki/importing/csvfile.py
@@ -78,7 +78,7 @@ class TextImporter(NoteImporter):
return re.sub(r"^\#.*$", "__comment", s)
self.data = [
- sub(x) + "\n" for x in self.data.split("\n") if sub(x) != "__comment"
+ f"{sub(x)}\n" for x in self.data.split("\n") if sub(x) != "__comment"
]
if self.data:
if self.data[0].startswith("tags:"):
diff --git a/pylib/anki/importing/noteimp.py b/pylib/anki/importing/noteimp.py
index e3617330b..7095a6408 100644
--- a/pylib/anki/importing/noteimp.py
+++ b/pylib/anki/importing/noteimp.py
@@ -229,7 +229,7 @@ class NoteImporter(Importer):
else:
unchanged = 0
part3 = self.col.tr(TR.IMPORTING_NOTE_UNCHANGED, count=unchanged)
- self.log.append("%s, %s, %s." % (part1, part2, part3))
+ self.log.append(f"{part1}, {part2}, {part3}.")
self.log.extend(updateLog)
self.total = len(self._ids)
diff --git a/pylib/anki/latex.py b/pylib/anki/latex.py
index 486331406..cfbf6734b 100644
--- a/pylib/anki/latex.py
+++ b/pylib/anki/latex.py
@@ -111,7 +111,7 @@ def _save_latex_image(
svg: bool,
) -> Optional[str]:
# add header/footer
- latex = header + "\n" + extracted.latex_body + "\n" + footer
+ latex = f"{header}\n{extracted.latex_body}\n{footer}"
# it's only really secure if run in a jail, but these are the most common
tmplatex = latex.replace("\\includegraphics", "")
for bad in (
@@ -127,7 +127,7 @@ def _save_latex_image(
"\\shipout",
):
# don't mind if the sequence is only part of a command
- bad_re = "\\" + bad + "[^a-zA-Z]"
+ bad_re = f"\\{bad}[^a-zA-Z]"
if re.search(bad_re, tmplatex):
return col.tr(TR.MEDIA_FOR_SECURITY_REASONS_IS_NOT, val=bad)
@@ -146,7 +146,7 @@ def _save_latex_image(
texfile.write(latex)
texfile.close()
oldcwd = os.getcwd()
- png_or_svg = namedtmp("tmp.%s" % ext)
+ png_or_svg = namedtmp(f"tmp.{ext}")
try:
# generate png/svg
os.chdir(tmpdir())
@@ -165,14 +165,14 @@ def _save_latex_image(
def _errMsg(col: anki.collection.Collection, type: str, texpath: str) -> Any:
- msg = col.tr(TR.MEDIA_ERROR_EXECUTING, val=type) + "
"
- msg += col.tr(TR.MEDIA_GENERATED_FILE, val=texpath) + "
"
+ msg = f"{col.tr(TR.MEDIA_ERROR_EXECUTING, val=type)}
"
+ msg += f"{col.tr(TR.MEDIA_GENERATED_FILE, val=texpath)}
"
try:
with open(namedtmp("latex_log.txt", rm=False)) as f:
log = f.read()
if not log:
raise Exception()
- msg += "" + html.escape(log) + "
"
+ msg += f"{html.escape(log)}
"
except:
msg += col.tr(TR.MEDIA_HAVE_YOU_INSTALLED_LATEX_AND_DVIPNGDVISVGM)
return msg
diff --git a/pylib/anki/media.py b/pylib/anki/media.py
index c9b3b293e..3becaf196 100644
--- a/pylib/anki/media.py
+++ b/pylib/anki/media.py
@@ -24,7 +24,7 @@ from anki.utils import intTime
def media_paths_from_col_path(col_path: str) -> Tuple[str, str]:
media_folder = re.sub(r"(?i)\.(anki2)$", ".media", col_path)
- media_db = media_folder + ".db2"
+ media_db = f"{media_folder}.db2"
return (media_folder, media_db)
diff --git a/pylib/anki/models.py b/pylib/anki/models.py
index e85641032..3b6a25222 100644
--- a/pylib/anki/models.py
+++ b/pylib/anki/models.py
@@ -440,7 +440,7 @@ and notes.mid = ? and cards.ord = ?""",
d = []
nfields = len(newModel["flds"])
for (nid, flds) in self.col.db.execute(
- "select id, flds from notes where id in " + ids2str(nids)
+ f"select id, flds from notes where id in {ids2str(nids)}"
):
newflds = {}
flds = splitFields(flds)
@@ -473,7 +473,7 @@ and notes.mid = ? and cards.ord = ?""",
d = []
deleted = []
for (cid, ord) in self.col.db.execute(
- "select id, ord from cards where nid in " + ids2str(nids)
+ f"select id, ord from cards where nid in {ids2str(nids)}"
):
# if the src model is a cloze, we ignore the map, as the gui
# doesn't currently support mapping them
diff --git a/pylib/anki/sched.py b/pylib/anki/sched.py
index ad4967a64..7e98e9c6e 100644
--- a/pylib/anki/sched.py
+++ b/pylib/anki/sched.py
@@ -77,7 +77,7 @@ class Scheduler(V2):
self._answerRevCard(card, ease)
review_delta = +1
else:
- raise Exception("Invalid queue '%s'" % card)
+ raise Exception(f"Invalid queue '{card}'")
self.update_stats(
card.did,
@@ -374,7 +374,7 @@ limit %d"""
def removeLrn(self, ids: Optional[List[int]] = None) -> None:
"Remove cards from the learning queues."
if ids:
- extra = " and id in " + ids2str(ids)
+ extra = f" and id in {ids2str(ids)}"
else:
# benchmarks indicate it's about 10x faster to search all decks
# with the index than scan the table
diff --git a/pylib/anki/schedv2.py b/pylib/anki/schedv2.py
index c2f5cdd72..8aa18459c 100644
--- a/pylib/anki/schedv2.py
+++ b/pylib/anki/schedv2.py
@@ -118,7 +118,7 @@ class Scheduler:
self._answerRevCard(card, ease)
review_delta = +1
else:
- raise Exception("Invalid queue '%s'" % card)
+ raise Exception(f"Invalid queue '{card}'")
self.update_stats(
card.did,
@@ -1120,7 +1120,7 @@ due = (case when odue>0 then odue else due end), odue = 0, odid = 0, usn = ? whe
)
def remFromDyn(self, cids: List[int]) -> None:
- self.emptyDyn(None, "id in %s and odid" % ids2str(cids))
+ self.emptyDyn(None, f"id in {ids2str(cids)} and odid")
# Leeches
##########################################################################
diff --git a/pylib/anki/tags.py b/pylib/anki/tags.py
index ec12cbf37..686cd6ad7 100644
--- a/pylib/anki/tags.py
+++ b/pylib/anki/tags.py
@@ -58,13 +58,13 @@ class TagManager:
def byDeck(self, did: int, children: bool = False) -> List[str]:
basequery = "select n.tags from cards c, notes n WHERE c.nid = n.id"
if not children:
- query = basequery + " AND c.did=?"
+ query = f"{basequery} AND c.did=?"
res = self.col.db.list(query, did)
return list(set(self.split(" ".join(res))))
dids = [did]
for name, id in self.col.decks.children(did):
dids.append(id)
- query = basequery + " AND c.did IN " + ids2str(dids)
+ query = f"{basequery} AND c.did IN {ids2str(dids)}"
res = self.col.db.list(query)
return list(set(self.split(" ".join(res))))
@@ -127,7 +127,7 @@ class TagManager:
"Join tags into a single string, with leading and trailing spaces."
if not tags:
return ""
- return " %s " % " ".join(tags)
+ return f" {' '.join(tags)} "
def addToStr(self, addtags: str, tags: str) -> str:
"Add tags if they don't exist, and canonify."
@@ -142,7 +142,7 @@ class TagManager:
def wildcard(pat: str, repl: str) -> Match:
pat = re.escape(pat).replace("\\*", ".*")
- return re.match("^" + pat + "$", repl, re.IGNORECASE)
+ return re.match(f"^{pat}$", repl, re.IGNORECASE)
currentTags = self.split(tags)
for tag in self.split(deltags):
diff --git a/pylib/anki/template.py b/pylib/anki/template.py
index 253e4a50b..94fd00a21 100644
--- a/pylib/anki/template.py
+++ b/pylib/anki/template.py
@@ -306,7 +306,7 @@ def apply_custom_filters(
)
# legacy hook - the second and fifth argument are no longer used.
field_text = anki.hooks.runFilter(
- "fmod_" + filter_name,
+ f"fmod_{filter_name}",
field_text,
"",
ctx.note().items(),
diff --git a/pylib/anki/utils.py b/pylib/anki/utils.py
index 69853de7b..330797c0f 100644
--- a/pylib/anki/utils.py
+++ b/pylib/anki/utils.py
@@ -141,7 +141,7 @@ def timestampID(db: DBProxy, table: str) -> int:
# be careful not to create multiple objects without flushing them, or they
# may share an ID.
t = intTime(1000)
- while db.scalar("select id from %s where id = ?" % table, t):
+ while db.scalar(f"select id from {table} where id = ?", t):
t += 1
return t
@@ -150,7 +150,7 @@ def maxID(db: DBProxy) -> int:
"Return the first safe ID to use."
now = intTime(1000)
for tbl in "cards", "notes":
- now = max(now, db.scalar("select max(id) from %s" % tbl) or 0)
+ now = max(now, db.scalar(f"select max(id) from {tbl}") or 0)
return now + 1
@@ -326,14 +326,14 @@ def platDesc() -> str:
try:
system = platform.system()
if isMac:
- theos = "mac:%s" % (platform.mac_ver()[0])
+ theos = f"mac:{platform.mac_ver()[0]}"
elif isWin:
- theos = "win:%s" % (platform.win32_ver()[0])
+ theos = f"win:{platform.win32_ver()[0]}"
elif system == "Linux":
import distro # pytype: disable=import-error # pylint: disable=import-error
r = distro.linux_distribution(full_distribution_name=False)
- theos = "lin:%s:%s" % (r[0], r[1])
+ theos = f"lin:{r[0]}:{r[1]}"
else:
theos = system
break
@@ -365,7 +365,7 @@ class TimedLog:
def versionWithBuild() -> str:
from anki.buildinfo import buildhash, version
- return "%s (%s)" % (version, buildhash)
+ return f"{version} ({buildhash})"
def pointVersion() -> int: