From 79e80766858d720b4287637b0c5edf73441a8b7c Mon Sep 17 00:00:00 2001 From: Fabian Wood Date: Fri, 31 Jul 2020 00:56:48 +1000 Subject: [PATCH] Added typehints for qt profiles * `Any` used for pickle methods, this could probably be improved with some kind of Callable * str used for self.base, though this may be a problem for different OSes. Some type of os.PathLike might be good. * Line 75, type ignored: mypy was complaining about no. of args, and kwargs there didn't seem to be needed. Separate issue to test, though. --- qt/aqt/profiles.py | 73 +++++++++++++++++++++++----------------------- qt/mypy.ini | 2 ++ 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/qt/aqt/profiles.py b/qt/aqt/profiles.py index 4580a3e75..c46d7d628 100644 --- a/qt/aqt/profiles.py +++ b/qt/aqt/profiles.py @@ -72,7 +72,7 @@ class LoadMetaResult: class AnkiRestart(SystemExit): def __init__(self, *args, **kwargs): self.exitcode = kwargs.pop("exitcode", 0) - super().__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # type: ignore class ProfileManager: @@ -83,6 +83,7 @@ class ProfileManager: self.db = None self.profile: Optional[Dict] = None # instantiate base folder + self.base: str self._setBaseFolder(base) def setupMeta(self) -> LoadMetaResult: @@ -92,7 +93,7 @@ class ProfileManager: return res # profile load on startup - def openProfile(self, profile): + def openProfile(self, profile) -> None: if profile: if profile not in self.profiles(): QMessageBox.critical(None, "Error", "Requested profile does not exist.") @@ -105,13 +106,13 @@ class ProfileManager: # Base creation ###################################################################### - def ensureBaseExists(self): + def ensureBaseExists(self) -> None: self._ensureExists(self.base) # Folder migration ###################################################################### - def _oldFolderLocation(self): + def _oldFolderLocation(self) -> str: if isMac: return os.path.expanduser("~/Documents/Anki") elif isWin: @@ -153,7 +154,7 @@ class ProfileManager: confirmation = QMessageBox() confirmation.setIcon(QMessageBox.Warning) confirmation.setWindowIcon(icon) - confirmation.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) + confirmation.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) # type: ignore confirmation.setWindowTitle(window_title) confirmation.setText( "Anki needs to move its data folder from Documents/Anki to a new location. Proceed?" @@ -168,7 +169,7 @@ class ProfileManager: progress.setWindowTitle(window_title) progress.setText("Please wait...") progress.show() - app.processEvents() + app.processEvents() # type: ignore shutil.move(oldBase, self.base) progress.hide() @@ -198,8 +199,8 @@ class ProfileManager: # Profile load/save ###################################################################### - def profiles(self): - def names(): + def profiles(self) -> List: + def names() -> List: return self.db.list("select name from profiles where name != '_global'") n = names() @@ -209,9 +210,9 @@ class ProfileManager: return n - def _unpickle(self, data): + def _unpickle(self, data) -> Any: class Unpickler(pickle.Unpickler): - def find_class(self, module, name): + def find_class(self, module: str, name: str) -> Any: if module == "PyQt5.sip": try: import PyQt5.sip # pylint: disable=unused-import @@ -234,10 +235,10 @@ class ProfileManager: up = Unpickler(io.BytesIO(data), errors="ignore") return up.load() - def _pickle(self, obj): + def _pickle(self, obj) -> Any: return pickle.dumps(obj, protocol=0) - def load(self, name): + def load(self, name) -> bool: assert name != "_global" data = self.db.scalar( "select cast(data as blob) from profiles where name = ?", name @@ -261,32 +262,32 @@ details have been forgotten.""" self.save() return True - def save(self): + def save(self) -> None: sql = "update profiles set data = ? where name = ?" self.db.execute(sql, self._pickle(self.profile), self.name) self.db.execute(sql, self._pickle(self.meta), "_global") self.db.commit() - def create(self, name): + def create(self, name) -> None: prof = profileConf.copy() self.db.execute( "insert or ignore into profiles values (?, ?)", name, self._pickle(prof) ) self.db.commit() - def remove(self, name): + def remove(self, name) -> None: p = self.profileFolder() if os.path.exists(p): send2trash(p) self.db.execute("delete from profiles where name = ?", name) self.db.commit() - def trashCollection(self): + def trashCollection(self) -> None: p = self.collectionPath() if os.path.exists(p): send2trash(p) - def rename(self, name): + def rename(self, name) -> None: oldName = self.name oldFolder = self.profileFolder() self.name = name @@ -337,19 +338,19 @@ and no other programs are accessing your profile folders, then try again.""" # Folder handling ###################################################################### - def profileFolder(self, create=True): + def profileFolder(self, create=True) -> str: path = os.path.join(self.base, self.name) if create: self._ensureExists(path) return path - def addonFolder(self): + def addonFolder(self) -> str: return self._ensureExists(os.path.join(self.base, "addons21")) - def backupFolder(self): + def backupFolder(self) -> str: return self._ensureExists(os.path.join(self.profileFolder(), "backups")) - def collectionPath(self): + def collectionPath(self) -> str: return os.path.join(self.profileFolder(), "collection.anki2") # Downgrade @@ -377,12 +378,12 @@ and no other programs are accessing your profile folders, then try again.""" # Helpers ###################################################################### - def _ensureExists(self, path): + def _ensureExists(self, path: str) -> str: if not os.path.exists(path): os.makedirs(path) return path - def _setBaseFolder(self, cmdlineBase): + def _setBaseFolder(self, cmdlineBase: None) -> None: if cmdlineBase: self.base = os.path.abspath(cmdlineBase) elif os.environ.get("ANKI_BASE"): @@ -392,7 +393,7 @@ and no other programs are accessing your profile folders, then try again.""" self.maybeMigrateFolder() self.ensureBaseExists() - def _defaultBase(self): + def _defaultBase(self) -> str: if isWin: from aqt.winpaths import get_appdata @@ -419,7 +420,7 @@ and no other programs are accessing your profile folders, then try again.""" result.firstTime = not os.path.exists(path) - def recover(): + def recover() -> None: # if we can't load profile, start with a new one if self.db: try: @@ -471,7 +472,7 @@ create table if not exists profiles ) return result - def _ensureProfile(self): + def _ensureProfile(self) -> None: "Create a new profile if none exists." self.create(_("User 1")) p = os.path.join(self.base, "README.txt") @@ -486,7 +487,7 @@ create table if not exists profiles ###################################################################### # On first run, allow the user to choose the default language - def setDefaultLang(self): + def setDefaultLang(self) -> None: # create dialog class NoCloseDiag(QDialog): def reject(self): @@ -519,20 +520,20 @@ create table if not exists profiles f.lang.setCurrentRow(idx) d.exec_() - def _onLangSelected(self): + def _onLangSelected(self) -> None: f = self.langForm obj = anki.lang.langs[f.lang.currentRow()] code = obj[1] name = obj[0] en = "Are you sure you wish to display Anki's interface in %s?" r = QMessageBox.question( - None, "Anki", en % name, QMessageBox.Yes | QMessageBox.No, QMessageBox.No + None, "Anki", en % name, QMessageBox.Yes | QMessageBox.No, QMessageBox.No # type: ignore ) if r != QMessageBox.Yes: return self.setDefaultLang() self.setLang(code) - def setLang(self, code): + def setLang(self, code) -> None: self.meta["defaultLang"] = code sql = "update profiles set data = ? where name = ?" self.db.execute(sql, self._pickle(self.meta), "_global") @@ -542,10 +543,10 @@ create table if not exists profiles # OpenGL ###################################################################### - def _glPath(self): + def _glPath(self) -> str: return os.path.join(self.base, "gldriver") - def glMode(self): + def glMode(self) -> str: if isMac: return "auto" @@ -562,11 +563,11 @@ create table if not exists profiles return mode return "auto" - def setGlMode(self, mode): + def setGlMode(self, mode) -> None: with open(self._glPath(), "w") as file: file.write(mode) - def nextGlMode(self): + def nextGlMode(self) -> None: mode = self.glMode() if mode == "software": self.setGlMode("auto") @@ -591,7 +592,7 @@ create table if not exists profiles def last_addon_update_check(self) -> int: return self.meta.get("last_addon_update_check", 0) - def set_last_addon_update_check(self, secs): + def set_last_addon_update_check(self, secs) -> None: self.meta["last_addon_update_check"] = secs def night_mode(self) -> bool: @@ -642,7 +643,7 @@ create table if not exists profiles def auto_sync_media_minutes(self) -> int: return self.profile.get("autoSyncMediaMinutes", 15) - def set_auto_sync_media_minutes(self, val: int): + def set_auto_sync_media_minutes(self, val: int) -> None: self.profile["autoSyncMediaMinutes"] = val ###################################################################### diff --git a/qt/mypy.ini b/qt/mypy.ini index 9adcb3d1b..82da08e40 100644 --- a/qt/mypy.ini +++ b/qt/mypy.ini @@ -88,3 +88,5 @@ check_untyped_defs=true check_untyped_defs=true [mypy-aqt.modelchooser] check_untyped_defs=true +[mypy-aqt.profiles] +check_untyped_defs=true