diff --git a/pylib/anki/importing/csvfile.py b/pylib/anki/importing/csvfile.py
index ee2c3e204..861651fdf 100644
--- a/pylib/anki/importing/csvfile.py
+++ b/pylib/anki/importing/csvfile.py
@@ -56,7 +56,7 @@ class TextImporter(NoteImporter):
log.append(_("Aborted: %s") % str(e))
self.log = log
self.ignored = ignored
- self.fileobj.close()
+ self.close()
return notes
def open(self) -> None:
@@ -134,6 +134,20 @@ class TextImporter(NoteImporter):
self.open()
return self.numFields
+ def close(self):
+ if self.fileobj:
+ self.fileobj.close()
+ self.fileobj = None
+
+ def __del__(self):
+ self.close()
+ try:
+ super().__del__ # type: ignore
+ except AttributeError:
+ pass
+ else:
+ super().__del__(self) # type: ignore
+
def noteFromFields(self, fields: List[str]) -> ForeignNote:
note = ForeignNote()
note.fields.extend([x for x in fields])
diff --git a/pylib/anki/importing/supermemo_xml.py b/pylib/anki/importing/supermemo_xml.py
index fb9461ae7..248a1fa10 100644
--- a/pylib/anki/importing/supermemo_xml.py
+++ b/pylib/anki/importing/supermemo_xml.py
@@ -300,7 +300,8 @@ class SupermemoXmlImporter(NoteImporter):
# OPEN AND LOAD
def openAnything(self, source):
- "Open any source / actually only openig of files is used"
+ """Open any source / actually only opening of files is used
+ @return an open handle which must be closed after use, i.e., handle.close()"""
if source == "-":
return sys.stdin
diff --git a/pylib/anki/latex.py b/pylib/anki/latex.py
index 9aeac75e9..6202980c0 100644
--- a/pylib/anki/latex.py
+++ b/pylib/anki/latex.py
@@ -138,7 +138,8 @@ package in the LaTeX header instead."""
if call(latexCmd, stdout=log, stderr=log):
return _errMsg(latexCmd[0], texpath)
# add to media
- data = open(png_or_svg, "rb").read()
+ with open(png_or_svg, "rb") as file:
+ data = file.read()
col.media.write_data(extracted.filename, data)
os.unlink(png_or_svg)
return None
diff --git a/pylib/anki/sync.py b/pylib/anki/sync.py
index 60be194f1..3a597cdde 100644
--- a/pylib/anki/sync.py
+++ b/pylib/anki/sync.py
@@ -639,7 +639,8 @@ class FullSyncer(HttpSyncer):
if cont == "upgradeRequired":
hooks.sync_stage_did_change("upgradeRequired")
return None
- open(tpath, "wb").write(cont)
+ with open(tpath, "wb") as file:
+ file.write(cont)
# check the received file is ok
d = DB(tpath)
assert d.scalar("pragma integrity_check") == "ok"
@@ -665,6 +666,7 @@ class FullSyncer(HttpSyncer):
return False
# apply some adjustments, then upload
self.col.beforeUpload()
- if self.req("upload", open(self.col.path, "rb")) != b"OK":
- return False
+ with open(self.col.path, "rb") as file:
+ if self.req("upload", file) != b"OK":
+ return False
return True
diff --git a/pylib/tests/test_exporting.py b/pylib/tests/test_exporting.py
index 83f6ca93e..f8903cf72 100644
--- a/pylib/tests/test_exporting.py
+++ b/pylib/tests/test_exporting.py
@@ -151,11 +151,13 @@ def test_export_textnote():
os.close(fd)
os.unlink(f)
e.exportInto(f)
- assert open(f).readline() == "foo\tbar
\ttag tag2\n"
+ with open(f) as file:
+ assert file.readline() == "foo\tbar
\ttag tag2\n"
e.includeTags = False
e.includeHTML = False
e.exportInto(f)
- assert open(f).readline() == "foo\tbar\n"
+ with open(f) as file:
+ assert file.readline() == "foo\tbar\n"
def test_exporters():
diff --git a/pylib/tools/hookslib.py b/pylib/tools/hookslib.py
index c3539cf44..f06775507 100644
--- a/pylib/tools/hookslib.py
+++ b/pylib/tools/hookslib.py
@@ -154,11 +154,14 @@ def update_file(path: str, hooks: List[Hook]):
for hook in hooks:
code += hook.code()
- orig = open(path).read()
+ with open(path) as file:
+ orig = file.read()
+
new = re.sub(
"(?s)# @@AUTOGEN@@.*?# @@AUTOGEN@@\n",
f"# @@AUTOGEN@@\n\n{code}# @@AUTOGEN@@\n",
orig,
)
- open(path, "wb").write(new.encode("utf8"))
+ with open(path, "wb") as file:
+ file.write(new.encode("utf8"))
diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py
index 3948ef105..730cb356c 100644
--- a/qt/aqt/editor.py
+++ b/qt/aqt/editor.py
@@ -808,7 +808,8 @@ to a cloze type first, via Edit>Change Note Type."""
req = urllib.request.Request(
url, None, {"User-Agent": "Mozilla/5.0 (compatible; Anki)"}
)
- filecontents = urllib.request.urlopen(req).read()
+ with urllib.request.urlopen(req) as response:
+ filecontents = response.read()
else:
reqs = HttpClient()
reqs.timeout = 30
@@ -1129,7 +1130,8 @@ class EditorWebView(AnkiWebView):
if not os.path.exists(path):
return
- data = open(path, "rb").read()
+ with open(path, "rb") as file:
+ data = file.read()
fname = self.editor._addPastedImage(data, ext)
if fname:
return self.editor.fnameToLink(fname)
diff --git a/qt/aqt/importing.py b/qt/aqt/importing.py
index 7c5204202..7c757cd48 100644
--- a/qt/aqt/importing.py
+++ b/qt/aqt/importing.py
@@ -505,7 +505,8 @@ def _replaceWithApkg(mw, filename, backup):
if os.path.exists(dest) and size == os.stat(dest).st_size:
continue
data = z.read(cStr)
- open(dest, "wb").write(data)
+ with open(dest, "wb") as file:
+ file.write(data)
z.close()
diff --git a/qt/aqt/main.py b/qt/aqt/main.py
index 124b0b9eb..0a70d3e5a 100644
--- a/qt/aqt/main.py
+++ b/qt/aqt/main.py
@@ -567,7 +567,8 @@ from the profile screen."
self.data = data
# create the file in calling thread to ensure the same
# file is not created twice
- open(self.path, "wb").close()
+ with open(self.path, "wb") as file:
+ pass
def run(self):
z = zipfile.ZipFile(self.path, "w", zipfile.ZIP_DEFLATED)
diff --git a/qt/aqt/profiles.py b/qt/aqt/profiles.py
index ea373102d..4bd22e631 100644
--- a/qt/aqt/profiles.py
+++ b/qt/aqt/profiles.py
@@ -467,11 +467,12 @@ create table if not exists profiles
"Create a new profile if none exists."
self.create(_("User 1"))
p = os.path.join(self.base, "README.txt")
- open(p, "w", encoding="utf8").write(
- without_unicode_isolation(
- tr(TR.PROFILES_FOLDER_README, link=appHelpSite + "#startupopts")
+ with open(p, "w", encoding="utf8") as file:
+ file.write(
+ without_unicode_isolation(
+ tr(TR.PROFILES_FOLDER_README, link=appHelpSite + "#startupopts")
+ )
)
- )
# Default language
######################################################################
@@ -544,7 +545,8 @@ create table if not exists profiles
if not os.path.exists(path):
return "software"
- mode = open(path, "r").read().strip()
+ with open(path, "r") as file:
+ mode = file.read().strip()
if mode == "angle" and isWin:
return mode
@@ -553,7 +555,8 @@ create table if not exists profiles
return "auto"
def setGlMode(self, mode):
- open(self._glPath(), "w").write(mode)
+ with open(self._glPath(), "w") as file:
+ file.write(mode)
def nextGlMode(self):
mode = self.glMode()
diff --git a/qt/aqt/webview.py b/qt/aqt/webview.py
index 1c5f78f6e..92819d64a 100644
--- a/qt/aqt/webview.py
+++ b/qt/aqt/webview.py
@@ -40,13 +40,16 @@ class AnkiWebPage(QWebEnginePage): # type: ignore
self._channel.registerObject("py", self._bridge)
self.setWebChannel(self._channel)
- js = QFile(":/qtwebchannel/qwebchannel.js")
- assert js.open(QIODevice.ReadOnly)
- js = bytes(js.readAll()).decode("utf-8")
+ qwebchannel = ":/qtwebchannel/qwebchannel.js"
+ jsfile = QFile(qwebchannel)
+ if not jsfile.open(QIODevice.ReadOnly):
+ print(f"Error opening '{qwebchannel}': {jsfile.error()}", file=sys.stderr)
+ jstext = bytes(jsfile.readAll()).decode("utf-8")
+ jsfile.close()
script = QWebEngineScript()
script.setSourceCode(
- js
+ jstext
+ """
var pycmd;
new QWebChannel(qt.webChannelTransport, function(channel) {
diff --git a/qt/po/scripts/duplicate-string.py b/qt/po/scripts/duplicate-string.py
index ee0b681cf..a32e82cb8 100644
--- a/qt/po/scripts/duplicate-string.py
+++ b/qt/po/scripts/duplicate-string.py
@@ -38,7 +38,8 @@ def dupe_key(fname, old, new):
if not os.path.exists(fname):
return
- orig = open(fname).read()
+ with open(fname) as file:
+ orig = file.read()
obj = parse(orig)
for ent in obj.body:
if isinstance(ent, Junk):
@@ -68,7 +69,8 @@ def dupe_key(fname, old, new):
raise Exception(f"introduced junk! {fname} {ent}")
# it's ok, write it out
- open(fname, "w").write(modified)
+ with open(fname, "w") as file:
+ file.write(modified)
i18ndir = os.path.join(os.path.dirname(ftl_filename), "..")
diff --git a/qt/po/scripts/extract-po-string.py b/qt/po/scripts/extract-po-string.py
index b8bad1b3f..488c2d97a 100644
--- a/qt/po/scripts/extract-po-string.py
+++ b/qt/po/scripts/extract-po-string.py
@@ -107,7 +107,8 @@ def plural_text(key, lang, translation):
def add_simple_message(fname, key, message):
orig = ""
if os.path.exists(fname):
- orig = open(fname).read()
+ with open(fname) as file:
+ orig = file.read()
obj = parse(orig)
for ent in obj.body:
@@ -126,7 +127,8 @@ def add_simple_message(fname, key, message):
raise Exception(f"introduced junk! {fname} {ent}")
# it's ok, write it out
- open(fname, "w").write(modified)
+ with open(fname, "w") as file:
+ file.write(modified)
def add_message(fname, key, translation):
diff --git a/qt/po/scripts/extract-po-strings.py b/qt/po/scripts/extract-po-strings.py
index 699825cbd..40e779ba0 100644
--- a/qt/po/scripts/extract-po-strings.py
+++ b/qt/po/scripts/extract-po-strings.py
@@ -55,5 +55,6 @@ for lang in folders:
continue
langs.setdefault(lang, {})[entry.msgid] = msgstr
-open("strings.json", "w").write(json.dumps(langs))
+with open("strings.json", "w") as file:
+ file.write(json.dumps(langs))
print("wrote to strings.json")