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")