diff --git a/pylib/anki/decks.py b/pylib/anki/decks.py index 9c8fa0d3e..79adf8512 100644 --- a/pylib/anki/decks.py +++ b/pylib/anki/decks.py @@ -19,6 +19,21 @@ from anki.utils import ids2str, intTime defaultDeck = 0 defaultDynamicDeck = 1 +NonFilteredDeck = Dict[str, Any] +FilteredDeck = Dict[str, Any] + +"""Any kind of deck """ +Deck = Union[NonFilteredDeck, FilteredDeck] + +"""Configuration of standard deck, as seen from the deck picker's gear.""" +Config = Dict[str, Any] + +"""Configurationf of some deck, filtered deck for filtered deck, config for standard deck""" +DeckConfig = Union[FilteredDeck, Config] + +""" New/lrn/rev conf, from deck config""" +QueueConfig = Dict[str, Any] + class DecksDictProxy: def __init__(self, col: anki.collection.Collection): @@ -64,7 +79,7 @@ class DeckManager: self.col = col.weakref() self.decks = DecksDictProxy(col) - def save(self, g: Dict = None) -> None: + def save(self, g: Union[Deck, Config] = None) -> None: "Can be called with either a deck or a deck configuration." if not g: print("col.decks.save() should be passed the changed deck") @@ -124,7 +139,7 @@ class DeckManager: except NotFoundError: return None - def get_legacy(self, did: int) -> Optional[Dict]: + def get_legacy(self, did: int) -> Optional[Deck]: try: return from_json_bytes(self.col.backend.get_deck_legacy(did)) except NotFoundError: @@ -133,10 +148,10 @@ class DeckManager: def have(self, id: int) -> bool: return not self.get_legacy(int(id)) - def get_all_legacy(self) -> List[Dict]: + def get_all_legacy(self) -> List[Deck]: return list(from_json_bytes(self.col.backend.get_all_decks_legacy()).values()) - def new_deck_legacy(self, filtered: bool) -> Dict: + def new_deck_legacy(self, filtered: bool) -> Deck: return from_json_bytes(self.col.backend.new_deck_legacy(filtered)) def deck_tree(self) -> pb.DeckTreeNode: @@ -154,7 +169,7 @@ class DeckManager: return match return None - def all(self) -> List: + def all(self) -> List[Deck]: "All decks. Expensive; prefer all_names_and_ids()" return self.get_all_legacy() @@ -162,7 +177,7 @@ class DeckManager: print("decks.allIds() is deprecated, use .all_names_and_ids()") return [str(x.id) for x in self.all_names_and_ids()] - def allNames(self, dyn: bool = True, force_default: bool = True) -> List: + def allNames(self, dyn: bool = True, force_default: bool = True) -> List[str]: print("decks.allNames() is deprecated, use .all_names_and_ids()") return [ x.name @@ -185,7 +200,7 @@ class DeckManager: def count(self) -> int: return len(self.all_names_and_ids()) - def get(self, did: Union[int, str], default: bool = True) -> Optional[Dict]: + def get(self, did: Union[int, str], default: bool = True) -> Optional[Deck]: if not did: if default: return self.get_legacy(1) @@ -200,14 +215,14 @@ class DeckManager: else: return None - def byName(self, name: str) -> Optional[Dict]: + def byName(self, name: str) -> Optional[Deck]: """Get deck with NAME, ignoring case.""" id = self.id_for_name(name) if id: return self.get_legacy(id) return None - def update(self, g: Dict[str, Any], preserve_usn=True) -> None: + def update(self, g: Deck, preserve_usn=True) -> None: "Add or update an existing deck. Used for syncing and merging." try: g["id"] = self.col.backend.add_or_update_deck_legacy( @@ -216,7 +231,7 @@ class DeckManager: except anki.rsbackend.DeckIsFilteredError: raise DeckRenameError("deck was filtered") - def rename(self, g: Dict[str, Any], newName: str) -> None: + def rename(self, g: Deck, newName: str) -> None: "Rename deck prefix to NAME if not exists. Updates children." g["name"] = newName self.update(g, preserve_usn=False) @@ -225,7 +240,9 @@ class DeckManager: # Drag/drop ############################################################# - def renameForDragAndDrop(self, draggedDeckDid: int, ontoDeckDid: Any) -> None: + def renameForDragAndDrop( + self, draggedDeckDid: int, ontoDeckDid: Optional[Union[int, str]] + ) -> None: draggedDeck = self.get(draggedDeckDid) draggedDeckName = draggedDeck["name"] ontoDeckName = self.get(ontoDeckDid)["name"] @@ -252,23 +269,23 @@ class DeckManager: else: return True - def _isParent(self, parentDeckName: str, childDeckName: str) -> Any: + def _isParent(self, parentDeckName: str, childDeckName: str) -> bool: return self.path(childDeckName) == self.path(parentDeckName) + [ self.basename(childDeckName) ] - def _isAncestor(self, ancestorDeckName: str, descendantDeckName: str) -> Any: + def _isAncestor(self, ancestorDeckName: str, descendantDeckName: str) -> bool: ancestorPath = self.path(ancestorDeckName) return ancestorPath == self.path(descendantDeckName)[0 : len(ancestorPath)] # Deck configurations ############################################################# - def all_config(self) -> List: + def all_config(self) -> List[Config]: "A list of all deck config." return list(from_json_bytes(self.col.backend.all_deck_config_legacy())) - def confForDid(self, did: int) -> Any: + def confForDid(self, did: int) -> DeckConfig: deck = self.get(did, default=False) assert deck if "conf" in deck: @@ -282,20 +299,20 @@ class DeckManager: # dynamic decks have embedded conf return deck - def get_config(self, conf_id: int) -> Any: + def get_config(self, conf_id: int) -> Optional[DeckConfig]: try: return from_json_bytes(self.col.backend.get_deck_config_legacy(conf_id)) except NotFoundError: return None - def update_config(self, conf: Dict[str, Any], preserve_usn=False) -> None: + def update_config(self, conf: DeckConfig, preserve_usn=False) -> None: conf["id"] = self.col.backend.add_or_update_deck_config_legacy( config=to_json_bytes(conf), preserve_usn_and_mtime=preserve_usn ) def add_config( - self, name: str, clone_from: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, name: str, clone_from: Optional[DeckConfig] = None + ) -> DeckConfig: if clone_from is not None: conf = copy.deepcopy(clone_from) conf["id"] = 0 @@ -306,7 +323,7 @@ class DeckManager: return conf def add_config_returning_id( - self, name: str, clone_from: Optional[Dict[str, Any]] = None + self, name: str, clone_from: Optional[DeckConfig] = None ) -> int: return self.add_config(name, clone_from)["id"] @@ -322,11 +339,11 @@ class DeckManager: self.save(g) self.col.backend.remove_deck_config(id) - def setConf(self, grp: Dict[str, Any], id: int) -> None: + def setConf(self, grp: DeckConfig, id: int) -> None: grp["conf"] = id self.save(grp) - def didsForConf(self, conf) -> List: + def didsForConf(self, conf) -> List[int]: dids = [] for deck in self.all(): if "conf" in deck and deck["conf"] == conf["id"]: @@ -353,13 +370,13 @@ class DeckManager: # Deck utils ############################################################# - def name(self, did: int, default: bool = False) -> Any: + def name(self, did: int, default: bool = False) -> str: deck = self.get(did, default=default) if deck: return deck["name"] return _("[no deck]") - def nameOrNone(self, did: int) -> Any: + def nameOrNone(self, did: int) -> Optional[str]: deck = self.get(did, default=False) if deck: return deck["name"] @@ -373,7 +390,7 @@ class DeckManager: intTime(), ) - def cids(self, did: int, children: bool = False) -> Any: + def cids(self, did: int, children: bool = False) -> List[int]: if not children: return self.col.db.list("select id from cards where did=?", did) dids = [did] @@ -387,15 +404,15 @@ class DeckManager: # Deck selection ############################################################# - def active(self) -> Any: + def active(self) -> List[int]: "The currrently active dids." return self.col.get_config("activeDecks", [1]) - def selected(self) -> Any: + def selected(self) -> int: "The currently selected did." return self.col.conf["curDeck"] - def current(self) -> Any: + def current(self) -> Deck: return self.get(self.selected()) def select(self, did: int) -> None: @@ -416,32 +433,33 @@ class DeckManager: ############################################################# @staticmethod - def path(name: str) -> Any: + def path(name: str) -> List[str]: return name.split("::") _path = path @classmethod - def basename(cls, name: str) -> Any: + def basename(cls, name: str) -> str: return cls.path(name)[-1] _basename = basename @classmethod - def immediate_parent_path(cls, name: str) -> Any: + def immediate_parent_path(cls, name: str) -> List[str]: return cls._path(name)[:-1] @classmethod - def immediate_parent(cls, name: str) -> Any: + def immediate_parent(cls, name: str) -> Optional[str]: pp = cls.immediate_parent_path(name) if pp: return "::".join(pp) + return None @classmethod - def key(cls, deck: Dict[str, Any]) -> List[str]: + def key(cls, deck: Deck) -> List[str]: return cls.path(deck["name"]) - def children(self, did: int) -> List[Tuple[Any, Any]]: + def children(self, did: int) -> List[Tuple[str, int]]: "All children of did, as (name, id)." name = self.get(did)["name"] actv = [] @@ -460,19 +478,22 @@ class DeckManager: out.extend(self.child_ids(parent_name)) return out - def childDids(self, did: int, childMap: Dict[int, Any]) -> List: - def gather(node, arr): + childMapNode = Dict[int, Any] + # Change to Dict[int, "DeckManager.childMapNode"] when MyPy allow recursive type + + def childDids(self, did: int, childMap: DeckManager.childMapNode) -> List: + def gather(node: DeckManager.childMapNode, arr): for did, child in node.items(): arr.append(did) gather(child, arr) - arr: List = [] + arr: List[int] = [] gather(childMap[did], arr) return arr - def childMap(self) -> Dict[Any, Dict[Any, dict]]: + def childMap(self) -> DeckManager.childMapNode: nameMap = self.nameMap() - childMap = {} + childMap: DeckManager.childMapNode = {} # go through all decks, sorted by name for deck in sorted(self.all(), key=self.key): @@ -487,31 +508,34 @@ class DeckManager: return childMap - def parents(self, did: int, nameMap: Optional[Any] = None) -> List: + def parents( + self, did: int, nameMap: Optional[Dict[str, Deck]] = None + ) -> List[Deck]: "All parents of did." # get parent and grandparent names - parents: List[str] = [] + parents_names: List[str] = [] for part in self.immediate_parent_path(self.get(did)["name"]): - if not parents: - parents.append(part) + if not parents_names: + parents_names.append(part) else: - parents.append(parents[-1] + "::" + part) + parents_names.append(parents_names[-1] + "::" + part) + parents: List[Deck] = [] # convert to objects - for c, p in enumerate(parents): + for parent_name in parents_names: if nameMap: - deck = nameMap[p] + deck = nameMap[parent_name] else: - deck = self.get(self.id(p)) - parents[c] = deck + deck = self.get(self.id(parent_name)) + parents.append(deck) return parents - def parentsByName(self, name: str) -> List: + def parentsByName(self, name: str) -> List[Deck]: "All existing parents of name" if "::" not in name: return [] names = self.immediate_parent_path(name) head = [] - parents = [] + parents: List[Deck] = [] while names: head.append(names.pop(0)) @@ -521,7 +545,7 @@ class DeckManager: return parents - def nameMap(self) -> dict: + def nameMap(self) -> Dict[str, Deck]: return dict((d["name"], d) for d in self.all()) # Dynamic decks @@ -533,5 +557,6 @@ class DeckManager: self.select(did) return did - def isDyn(self, did: Union[int, str]) -> Any: + # 1 for dyn, 0 for standard + def isDyn(self, did: Union[int, str]) -> int: return self.get(did)["dyn"] diff --git a/pylib/anki/hooks.py b/pylib/anki/hooks.py index ba48e4afa..27977dc52 100644 --- a/pylib/anki/hooks.py +++ b/pylib/anki/hooks.py @@ -175,20 +175,20 @@ card_will_flush = _CardWillFlushHook() class _DeckAddedHook: """Obsolete, do not use.""" - _hooks: List[Callable[[Dict[str, Any]], None]] = [] + _hooks: List[Callable[["anki.decks.Deck"], None]] = [] - def append(self, cb: Callable[[Dict[str, Any]], None]) -> None: - """(deck: Dict[str, Any])""" + def append(self, cb: Callable[["anki.decks.Deck"], None]) -> None: + """(deck: anki.decks.Deck)""" self._hooks.append(cb) - def remove(self, cb: Callable[[Dict[str, Any]], None]) -> None: + def remove(self, cb: Callable[["anki.decks.Deck"], None]) -> None: if cb in self._hooks: self._hooks.remove(cb) def count(self) -> int: return len(self._hooks) - def __call__(self, deck: Dict[str, Any]) -> None: + def __call__(self, deck: anki.decks.Deck) -> None: for hook in self._hooks: try: hook(deck) @@ -305,20 +305,20 @@ media_files_did_export = _MediaFilesDidExportHook() class _NoteTypeAddedHook: """Obsolete, do not use.""" - _hooks: List[Callable[[Dict[str, Any]], None]] = [] + _hooks: List[Callable[["anki.models.NoteType"], None]] = [] - def append(self, cb: Callable[[Dict[str, Any]], None]) -> None: - """(notetype: Dict[str, Any])""" + def append(self, cb: Callable[["anki.models.NoteType"], None]) -> None: + """(notetype: anki.models.NoteType)""" self._hooks.append(cb) - def remove(self, cb: Callable[[Dict[str, Any]], None]) -> None: + def remove(self, cb: Callable[["anki.models.NoteType"], None]) -> None: if cb in self._hooks: self._hooks.remove(cb) def count(self) -> int: return len(self._hooks) - def __call__(self, notetype: Dict[str, Any]) -> None: + def __call__(self, notetype: anki.models.NoteType) -> None: for hook in self._hooks: try: hook(notetype) @@ -397,20 +397,20 @@ class _SchedulerNewLimitForSingleDeckFilter: """Allows changing the number of new card for this deck (without considering descendants).""" - _hooks: List[Callable[[int, Dict[str, Any]], int]] = [] + _hooks: List[Callable[[int, "anki.decks.Deck"], int]] = [] - def append(self, cb: Callable[[int, Dict[str, Any]], int]) -> None: - """(count: int, deck: Dict[str, Any])""" + def append(self, cb: Callable[[int, "anki.decks.Deck"], int]) -> None: + """(count: int, deck: anki.decks.Deck)""" self._hooks.append(cb) - def remove(self, cb: Callable[[int, Dict[str, Any]], int]) -> None: + def remove(self, cb: Callable[[int, "anki.decks.Deck"], int]) -> None: if cb in self._hooks: self._hooks.remove(cb) def count(self) -> int: return len(self._hooks) - def __call__(self, count: int, deck: Dict[str, Any]) -> int: + def __call__(self, count: int, deck: anki.decks.Deck) -> int: for filter in self._hooks: try: count = filter(count, deck) @@ -428,20 +428,20 @@ class _SchedulerReviewLimitForSingleDeckFilter: """Allows changing the number of rev card for this deck (without considering descendants).""" - _hooks: List[Callable[[int, Dict[str, Any]], int]] = [] + _hooks: List[Callable[[int, "anki.decks.Deck"], int]] = [] - def append(self, cb: Callable[[int, Dict[str, Any]], int]) -> None: - """(count: int, deck: Dict[str, Any])""" + def append(self, cb: Callable[[int, "anki.decks.Deck"], int]) -> None: + """(count: int, deck: anki.decks.Deck)""" self._hooks.append(cb) - def remove(self, cb: Callable[[int, Dict[str, Any]], int]) -> None: + def remove(self, cb: Callable[[int, "anki.decks.Deck"], int]) -> None: if cb in self._hooks: self._hooks.remove(cb) def count(self) -> int: return len(self._hooks) - def __call__(self, count: int, deck: Dict[str, Any]) -> int: + def __call__(self, count: int, deck: anki.decks.Deck) -> int: for filter in self._hooks: try: count = filter(count, deck) diff --git a/pylib/anki/models.py b/pylib/anki/models.py index b71b91d79..ab1a11988 100644 --- a/pylib/anki/models.py +++ b/pylib/anki/models.py @@ -138,7 +138,7 @@ class ModelManager: # Current note type ############################################################# - def current(self, forDeck: bool = True) -> Any: + def current(self, forDeck: bool = True) -> NoteType: "Get current model." m = self.get(self.col.decks.current().get("mid")) if not forDeck or not m: @@ -243,21 +243,21 @@ class ModelManager: # Tools ################################################## - def nids(self, ntid: int) -> Any: + def nids(self, ntid: int) -> List[int]: "Note ids for M." if isinstance(ntid, dict): # legacy callers passed in note type ntid = ntid["id"] return self.col.db.list("select id from notes where mid = ?", ntid) - def useCount(self, m: NoteType) -> Any: + def useCount(self, m: NoteType) -> int: "Number of note using M." return self.col.db.scalar("select count() from notes where mid = ?", m["id"]) # Copying ################################################## - def copy(self, m: NoteType) -> Any: + def copy(self, m: NoteType) -> NoteType: "Copy, save and return." m2 = copy.deepcopy(m) m2["name"] = _("%s copy") % m2["name"] @@ -275,7 +275,7 @@ class ModelManager: def fieldNames(self, m: NoteType) -> List[str]: return [f["name"] for f in m["flds"]] - def sortIdx(self, m: NoteType) -> Any: + def sortIdx(self, m: NoteType) -> int: return m["sortf"] # Adding & changing fields @@ -406,7 +406,12 @@ and notes.mid = ? and cards.ord = ?""", # - newModel should be self if model is not changing def change( - self, m: NoteType, nids: List[int], newModel: NoteType, fmap: Any, cmap: Any + self, + m: NoteType, + nids: List[int], + newModel: NoteType, + fmap: Optional[Dict[int, Union[None, int]]], + cmap: Optional[Dict[int, Union[None, int]]], ) -> None: self.col.modSchema(check=True) assert newModel["id"] == m["id"] or (fmap and cmap) @@ -483,7 +488,9 @@ and notes.mid = ? and cards.ord = ?""", # Cloze ########################################################################## - def _availClozeOrds(self, m: NoteType, flds: str, allowEmpty: bool = True) -> List: + def _availClozeOrds( + self, m: NoteType, flds: str, allowEmpty: bool = True + ) -> List[int]: print("_availClozeOrds() is deprecated; use note.cloze_numbers_in_fields()") note = anki.rsbackend.BackendNote(fields=[flds]) return list(self.col.backend.cloze_numbers_in_note(note)) diff --git a/pylib/anki/sched.py b/pylib/anki/sched.py index 9895e87e9..29ae247c9 100644 --- a/pylib/anki/sched.py +++ b/pylib/anki/sched.py @@ -6,12 +6,13 @@ from __future__ import annotations import random import time from heapq import * -from typing import Any, Dict, List, Optional, Sequence, Tuple, Union +from typing import Any, List, Optional, Sequence, Tuple, Union import anki from anki import hooks from anki.cards import Card from anki.consts import * +from anki.decks import Deck, QueueConfig from anki.schedv2 import Scheduler as V2 from anki.utils import ids2str, intTime @@ -299,13 +300,13 @@ limit %d""" card.queue = QUEUE_TYPE_DAY_LEARN_RELEARN self._logLrn(card, ease, conf, leaving, type, lastLeft) - def _lrnConf(self, card: Card) -> Dict[str, Any]: + def _lrnConf(self, card: Card) -> QueueConfig: if card.type == CARD_TYPE_REV: return self._lapseConf(card) else: return self._newConf(card) - def _rescheduleAsRev(self, card: Card, conf: Dict[str, Any], early: bool) -> None: + def _rescheduleAsRev(self, card: Card, conf: QueueConfig, early: bool) -> None: lapse = card.type == CARD_TYPE_REV if lapse: if self._resched(card): @@ -338,7 +339,7 @@ limit %d""" return tot + tod * 1000 def _graduatingIvl( - self, card: Card, conf: Dict[str, Any], early: bool, adj: bool = True + self, card: Card, conf: QueueConfig, early: bool, adj: bool = True ) -> int: if card.type == CARD_TYPE_REV: # lapsed card being relearnt @@ -357,7 +358,7 @@ limit %d""" else: return ideal - def _rescheduleNew(self, card: Card, conf: Dict[str, Any], early: bool) -> None: + def _rescheduleNew(self, card: Card, conf: QueueConfig, early: bool) -> None: "Reschedule a new card that's graduated for the first time." card.ivl = self._graduatingIvl(card, conf, early) card.due = self.today + card.ivl @@ -367,7 +368,7 @@ limit %d""" self, card: Card, ease: int, - conf: Dict[str, Any], + conf: QueueConfig, leaving: bool, type: int, lastLeft: int, @@ -452,7 +453,7 @@ and due <= ? limit ?)""", def _deckRevLimit(self, did: int) -> int: return self._deckNewLimit(did, self._deckRevLimitSingle) - def _deckRevLimitSingle(self, d: Dict[str, Any]) -> int: # type: ignore[override] + def _deckRevLimitSingle(self, d: Deck) -> int: # type: ignore[override] if d["dyn"]: return self.reportLimit c = self.col.decks.confForDid(d["id"]) @@ -567,7 +568,7 @@ did = ? and queue = {QUEUE_TYPE_REV} and due <= ? limit ?""", card.queue = QUEUE_TYPE_DAY_LEARN_RELEARN return delay - def _nextLapseIvl(self, card: Card, conf: Dict[str, Any]) -> int: + def _nextLapseIvl(self, card: Card, conf: QueueConfig) -> int: return max(conf["minInt"], int(card.ivl * conf["mult"])) def _rescheduleRev(self, card: Card, ease: int) -> None: # type: ignore[override] @@ -607,7 +608,7 @@ did = ? and queue = {QUEUE_TYPE_REV} and due <= ? limit ?""", # interval capped? return min(interval, conf["maxIvl"]) - def _constrainedIvl(self, ivl: float, conf: Dict[str, Any], prev: int) -> int: # type: ignore[override] + def _constrainedIvl(self, ivl: float, conf: QueueConfig, prev: int) -> int: # type: ignore[override] "Integer interval after interval factor and prev+1 constraints applied." new = ivl * conf.get("ivlFct", 1) return int(max(new, prev + 1)) @@ -641,7 +642,7 @@ did = ? and queue = {QUEUE_TYPE_REV} and due <= ? limit ?""", self.col.decks.select(did) return ids - def _fillDyn(self, deck: Dict[str, Any]) -> Sequence[int]: # type: ignore[override] + def _fillDyn(self, deck: Deck) -> Sequence[int]: # type: ignore[override] search, limit, order = deck["terms"][0] orderlimit = self._dynOrder(order, limit) if search.strip(): @@ -707,7 +708,7 @@ did = ?, queue = %s, due = ?, usn = ? where id = ?""" # Leeches ########################################################################## - def _checkLeech(self, card: Card, conf: Dict[str, Any]) -> bool: + def _checkLeech(self, card: Card, conf: QueueConfig) -> bool: "Leech handler. True if card was a leech." lf = conf["leechFails"] if not lf: @@ -737,7 +738,7 @@ did = ?, queue = %s, due = ?, usn = ? where id = ?""" # Tools ########################################################################## - def _newConf(self, card: Card) -> Dict[str, Any]: + def _newConf(self, card: Card) -> QueueConfig: conf = self._cardConf(card) # normal deck if not card.odid: @@ -756,7 +757,7 @@ did = ?, queue = %s, due = ?, usn = ? where id = ?""" perDay=self.reportLimit, ) - def _lapseConf(self, card: Card) -> Dict[str, Any]: + def _lapseConf(self, card: Card) -> QueueConfig: conf = self._cardConf(card) # normal deck if not card.odid: diff --git a/pylib/anki/schedv2.py b/pylib/anki/schedv2.py index 8fc8f720e..e592b2a59 100644 --- a/pylib/anki/schedv2.py +++ b/pylib/anki/schedv2.py @@ -13,6 +13,7 @@ import anki # pylint: disable=unused-import from anki import hooks from anki.cards import Card from anki.consts import * +from anki.decks import Deck, DeckConfig, DeckManager, FilteredDeck, QueueConfig from anki.lang import _ from anki.rsbackend import ( CountsForDeckToday, @@ -355,7 +356,7 @@ order by due""" return None def _deckNewLimit( - self, did: int, fn: Optional[Callable[[Dict[str, Any]], int]] = None + self, did: int, fn: Optional[Callable[[Deck], int]] = None ) -> int: if not fn: fn = self._deckNewLimitSingle @@ -383,7 +384,7 @@ select count() from lim, ) - def _deckNewLimitSingle(self, g: Dict[str, Any]) -> int: + def _deckNewLimitSingle(self, g: DeckConfig) -> int: "Limit for deck without parent limits." if g["dyn"]: return self.dynReportLimit @@ -552,11 +553,11 @@ did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN} and due <= ? limit ?""", self._logLrn(card, ease, conf, leaving, type, lastLeft) - def _updateRevIvlOnFail(self, card: Card, conf: Dict[str, Any]) -> None: + def _updateRevIvlOnFail(self, card: Card, conf: QueueConfig) -> None: card.lastIvl = card.ivl card.ivl = self._lapseIvl(card, conf) - def _moveToFirstStep(self, card: Card, conf: Dict[str, Any]) -> Any: + def _moveToFirstStep(self, card: Card, conf: QueueConfig) -> Any: card.left = self._startingLeft(card) # relearning card? @@ -565,19 +566,19 @@ did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN} and due <= ? limit ?""", return self._rescheduleLrnCard(card, conf) - def _moveToNextStep(self, card: Card, conf: Dict[str, Any]) -> None: + def _moveToNextStep(self, card: Card, conf: QueueConfig) -> None: # decrement real left count and recalculate left today left = (card.left % 1000) - 1 card.left = self._leftToday(conf["delays"], left) * 1000 + left self._rescheduleLrnCard(card, conf) - def _repeatStep(self, card: Card, conf: Dict[str, Any]) -> None: + def _repeatStep(self, card: Card, conf: QueueConfig) -> None: delay = self._delayForRepeatingGrade(conf, card.left) self._rescheduleLrnCard(card, conf, delay=delay) def _rescheduleLrnCard( - self, card: Card, conf: Dict[str, Any], delay: Optional[int] = None + self, card: Card, conf: QueueConfig, delay: Optional[int] = None ) -> Any: # normal delay for the current step? if delay is None: @@ -608,7 +609,7 @@ did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN} and due <= ? limit ?""", card.queue = QUEUE_TYPE_DAY_LEARN_RELEARN return delay - def _delayForGrade(self, conf: Dict[str, Any], left: int) -> int: + def _delayForGrade(self, conf: QueueConfig, left: int) -> int: left = left % 1000 try: delay = conf["delays"][-left] @@ -620,7 +621,7 @@ did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN} and due <= ? limit ?""", delay = 1 return delay * 60 - def _delayForRepeatingGrade(self, conf: Dict[str, Any], left: int) -> Any: + def _delayForRepeatingGrade(self, conf: QueueConfig, left: int) -> Any: # halfway between last and next delay1 = self._delayForGrade(conf, left) if len(conf["delays"]) > 1: @@ -636,7 +637,7 @@ did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN} and due <= ? limit ?""", else: return self._newConf(card) - def _rescheduleAsRev(self, card: Card, conf: Dict[str, Any], early: bool) -> None: + def _rescheduleAsRev(self, card: Card, conf: QueueConfig, early: bool) -> None: lapse = card.type in (CARD_TYPE_REV, CARD_TYPE_RELEARNING) if lapse: @@ -680,7 +681,7 @@ did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN} and due <= ? limit ?""", return ok + 1 def _graduatingIvl( - self, card: Card, conf: Dict[str, Any], early: bool, fuzz: bool = True + self, card: Card, conf: QueueConfig, early: bool, fuzz: bool = True ) -> Any: if card.type in (CARD_TYPE_REV, CARD_TYPE_RELEARNING): bonus = early and 1 or 0 @@ -695,7 +696,7 @@ did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN} and due <= ? limit ?""", ideal = self._fuzzedIvl(ideal) return ideal - def _rescheduleNew(self, card: Card, conf: Dict[str, Any], early: bool) -> None: + def _rescheduleNew(self, card: Card, conf: QueueConfig, early: bool) -> None: "Reschedule a new card that's graduated for the first time." card.ivl = self._graduatingIvl(card, conf, early) card.due = self.today + card.ivl @@ -706,7 +707,7 @@ did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN} and due <= ? limit ?""", self, card: Card, ease: int, - conf: Dict[str, Any], + conf: QueueConfig, leaving: bool, type: int, lastLeft: int, @@ -791,7 +792,9 @@ and due <= ? limit ?)""", lim = min(lim, self._deckRevLimitSingle(parent, parentLimit=lim)) return hooks.scheduler_review_limit_for_single_deck(lim, d) - def _revForDeck(self, did: int, lim: int, childMap: Dict[int, Any]) -> Any: + def _revForDeck( + self, did: int, lim: int, childMap: DeckManager.childMapNode + ) -> Any: dids = [did] + self.col.decks.childDids(did, childMap) lim = min(lim, self.reportLimit) return self.col.db.scalar( @@ -888,7 +891,7 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l return delay - def _lapseIvl(self, card: Card, conf: Dict[str, Any]) -> Any: + def _lapseIvl(self, card: Card, conf: QueueConfig) -> Any: ivl = max(1, conf["minInt"], int(card.ivl * conf["mult"])) return ivl @@ -975,7 +978,7 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l return [ivl - fuzz, ivl + fuzz] def _constrainedIvl( - self, ivl: float, conf: Dict[str, Any], prev: int, fuzz: bool + self, ivl: float, conf: QueueConfig, prev: int, fuzz: bool ) -> int: ivl = int(ivl * conf.get("ivlFct", 1)) if fuzz: @@ -1058,7 +1061,7 @@ end) self.col.decks.select(did) return cnt - def _fillDyn(self, deck: Dict[str, Any]) -> int: + def _fillDyn(self, deck: FilteredDeck) -> int: start = -100000 total = 0 for search, limit, order in deck["terms"]: @@ -1167,7 +1170,7 @@ where id = ? # Leeches ########################################################################## - def _checkLeech(self, card: Card, conf: Dict[str, Any]) -> bool: + def _checkLeech(self, card: Card, conf: QueueConfig) -> bool: "Leech handler. True if card was a leech." lf = conf["leechFails"] if not lf: @@ -1190,7 +1193,7 @@ where id = ? # Tools ########################################################################## - def _cardConf(self, card: Card) -> Dict[str, Any]: + def _cardConf(self, card: Card) -> DeckConfig: return self.col.decks.confForDid(card.did) def _newConf(self, card: Card) -> Any: @@ -1229,7 +1232,7 @@ where id = ? resched=conf["resched"], ) - def _revConf(self, card: Card) -> Dict[str, Any]: + def _revConf(self, card: Card) -> QueueConfig: conf = self._cardConf(card) # normal deck if not card.odid: diff --git a/pylib/tools/genhooks.py b/pylib/tools/genhooks.py index 921a652fc..825880dd4 100644 --- a/pylib/tools/genhooks.py +++ b/pylib/tools/genhooks.py @@ -71,23 +71,25 @@ hooks = [ ), Hook( name="scheduler_new_limit_for_single_deck", - args=["count: int", "deck: Dict[str, Any]"], + args=["count: int", "deck: anki.decks.Deck"], return_type="int", doc="""Allows changing the number of new card for this deck (without considering descendants).""", ), Hook( name="scheduler_review_limit_for_single_deck", - args=["count: int", "deck: Dict[str, Any]"], + args=["count: int", "deck: anki.decks.Deck"], return_type="int", doc="""Allows changing the number of rev card for this deck (without considering descendants).""", ), # obsolete - Hook(name="deck_added", args=["deck: Dict[str, Any]"], doc="Obsolete, do not use."), + Hook( + name="deck_added", args=["deck: anki.decks.Deck"], doc="Obsolete, do not use." + ), Hook( name="note_type_added", - args=["notetype: Dict[str, Any]"], + args=["notetype: anki.models.NoteType"], doc="Obsolete, do not use.", ), Hook( diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 4b79d7897..2f0e95c81 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -254,7 +254,7 @@ class Editor: id: Optional[str] = None, toggleable: bool = False, disables: bool = True, - ): + ) -> str: if icon: if icon.startswith("qrc:/"): iconstr = icon diff --git a/qt/aqt/fields.py b/qt/aqt/fields.py index ea6652871..ba658f865 100644 --- a/qt/aqt/fields.py +++ b/qt/aqt/fields.py @@ -119,8 +119,8 @@ class FieldDialog(QDialog): def onDelete(self): if len(self.model["flds"]) < 2: return showWarning(_("Notes require at least one field.")) - c = self.mm.useCount(self.model) - c = ngettext("%d note", "%d notes", c) % c + count = self.mm.useCount(self.model) + c = ngettext("%d note", "%d notes", count) % count if not askUser(_("Delete field from %s?") % c): return if not self.change_tracker.mark_schema(): diff --git a/qt/aqt/gui_hooks.py b/qt/aqt/gui_hooks.py index bd25a03b9..71aec7506 100644 --- a/qt/aqt/gui_hooks.py +++ b/qt/aqt/gui_hooks.py @@ -7,12 +7,14 @@ See pylib/anki/hooks.py from __future__ import annotations -from typing import Any, Callable, Dict, List, Optional, Tuple +from typing import Any, Callable, List, Optional, Tuple import anki import aqt from anki.cards import Card +from anki.decks import Deck, DeckConfig from anki.hooks import runFilter, runHook +from anki.models import NoteType from aqt.qt import QDialog, QEvent, QMenu from aqt.tagedit import TagEdit @@ -827,20 +829,20 @@ collection_did_load = _CollectionDidLoadHook() class _CurrentNoteTypeDidChangeHook: - _hooks: List[Callable[[Dict[str, Any]], None]] = [] + _hooks: List[Callable[[NoteType], None]] = [] - def append(self, cb: Callable[[Dict[str, Any]], None]) -> None: - """(notetype: Dict[str, Any])""" + def append(self, cb: Callable[[NoteType], None]) -> None: + """(notetype: NoteType)""" self._hooks.append(cb) - def remove(self, cb: Callable[[Dict[str, Any]], None]) -> None: + def remove(self, cb: Callable[[NoteType], None]) -> None: if cb in self._hooks: self._hooks.remove(cb) def count(self) -> int: return len(self._hooks) - def __call__(self, notetype: Dict[str, Any]) -> None: + def __call__(self, notetype: NoteType) -> None: for hook in self._hooks: try: hook(notetype) @@ -1046,16 +1048,18 @@ class _DeckConfDidAddConfigHook: Config groups are created as clones of the current one. """ - _hooks: List[Callable[["aqt.deckconf.DeckConf", Any, Any, str, int], None]] = [] + _hooks: List[ + Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig, str, int], None] + ] = [] def append( - self, cb: Callable[["aqt.deckconf.DeckConf", Any, Any, str, int], None] + self, cb: Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig, str, int], None] ) -> None: - """(deck_conf: aqt.deckconf.DeckConf, deck: Any, config: Any, new_name: str, new_conf_id: int)""" + """(deck_conf: aqt.deckconf.DeckConf, deck: Deck, config: DeckConfig, new_name: str, new_conf_id: int)""" self._hooks.append(cb) def remove( - self, cb: Callable[["aqt.deckconf.DeckConf", Any, Any, str, int], None] + self, cb: Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig, str, int], None] ) -> None: if cb in self._hooks: self._hooks.remove(cb) @@ -1066,8 +1070,8 @@ class _DeckConfDidAddConfigHook: def __call__( self, deck_conf: aqt.deckconf.DeckConf, - deck: Any, - config: Any, + deck: Deck, + config: DeckConfig, new_name: str, new_conf_id: int, ) -> None: @@ -1086,13 +1090,17 @@ deck_conf_did_add_config = _DeckConfDidAddConfigHook() class _DeckConfDidLoadConfigHook: """Called once widget state has been set from deck config""" - _hooks: List[Callable[["aqt.deckconf.DeckConf", Any, Any], None]] = [] + _hooks: List[Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig], None]] = [] - def append(self, cb: Callable[["aqt.deckconf.DeckConf", Any, Any], None]) -> None: - """(deck_conf: aqt.deckconf.DeckConf, deck: Any, config: Any)""" + def append( + self, cb: Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig], None] + ) -> None: + """(deck_conf: aqt.deckconf.DeckConf, deck: Deck, config: DeckConfig)""" self._hooks.append(cb) - def remove(self, cb: Callable[["aqt.deckconf.DeckConf", Any, Any], None]) -> None: + def remove( + self, cb: Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig], None] + ) -> None: if cb in self._hooks: self._hooks.remove(cb) @@ -1100,7 +1108,7 @@ class _DeckConfDidLoadConfigHook: return len(self._hooks) def __call__( - self, deck_conf: aqt.deckconf.DeckConf, deck: Any, config: Any + self, deck_conf: aqt.deckconf.DeckConf, deck: Deck, config: DeckConfig ) -> None: for hook in self._hooks: try: @@ -1146,13 +1154,17 @@ deck_conf_did_setup_ui_form = _DeckConfDidSetupUiFormHook() class _DeckConfWillRemoveConfigHook: """Called before current config group is removed""" - _hooks: List[Callable[["aqt.deckconf.DeckConf", Any, Any], None]] = [] + _hooks: List[Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig], None]] = [] - def append(self, cb: Callable[["aqt.deckconf.DeckConf", Any, Any], None]) -> None: - """(deck_conf: aqt.deckconf.DeckConf, deck: Any, config: Any)""" + def append( + self, cb: Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig], None] + ) -> None: + """(deck_conf: aqt.deckconf.DeckConf, deck: Deck, config: DeckConfig)""" self._hooks.append(cb) - def remove(self, cb: Callable[["aqt.deckconf.DeckConf", Any, Any], None]) -> None: + def remove( + self, cb: Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig], None] + ) -> None: if cb in self._hooks: self._hooks.remove(cb) @@ -1160,7 +1172,7 @@ class _DeckConfWillRemoveConfigHook: return len(self._hooks) def __call__( - self, deck_conf: aqt.deckconf.DeckConf, deck: Any, config: Any + self, deck_conf: aqt.deckconf.DeckConf, deck: Deck, config: DeckConfig ) -> None: for hook in self._hooks: try: @@ -1177,16 +1189,16 @@ deck_conf_will_remove_config = _DeckConfWillRemoveConfigHook() class _DeckConfWillRenameConfigHook: """Called before config group is renamed""" - _hooks: List[Callable[["aqt.deckconf.DeckConf", Any, Any, str], None]] = [] + _hooks: List[Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig, str], None]] = [] def append( - self, cb: Callable[["aqt.deckconf.DeckConf", Any, Any, str], None] + self, cb: Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig, str], None] ) -> None: - """(deck_conf: aqt.deckconf.DeckConf, deck: Any, config: Any, new_name: str)""" + """(deck_conf: aqt.deckconf.DeckConf, deck: Deck, config: DeckConfig, new_name: str)""" self._hooks.append(cb) def remove( - self, cb: Callable[["aqt.deckconf.DeckConf", Any, Any, str], None] + self, cb: Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig, str], None] ) -> None: if cb in self._hooks: self._hooks.remove(cb) @@ -1195,7 +1207,11 @@ class _DeckConfWillRenameConfigHook: return len(self._hooks) def __call__( - self, deck_conf: aqt.deckconf.DeckConf, deck: Any, config: Any, new_name: str + self, + deck_conf: aqt.deckconf.DeckConf, + deck: Deck, + config: DeckConfig, + new_name: str, ) -> None: for hook in self._hooks: try: @@ -1212,13 +1228,17 @@ deck_conf_will_rename_config = _DeckConfWillRenameConfigHook() class _DeckConfWillSaveConfigHook: """Called before widget state is saved to config""" - _hooks: List[Callable[["aqt.deckconf.DeckConf", Any, Any], None]] = [] + _hooks: List[Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig], None]] = [] - def append(self, cb: Callable[["aqt.deckconf.DeckConf", Any, Any], None]) -> None: - """(deck_conf: aqt.deckconf.DeckConf, deck: Any, config: Any)""" + def append( + self, cb: Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig], None] + ) -> None: + """(deck_conf: aqt.deckconf.DeckConf, deck: Deck, config: DeckConfig)""" self._hooks.append(cb) - def remove(self, cb: Callable[["aqt.deckconf.DeckConf", Any, Any], None]) -> None: + def remove( + self, cb: Callable[["aqt.deckconf.DeckConf", Deck, DeckConfig], None] + ) -> None: if cb in self._hooks: self._hooks.remove(cb) @@ -1226,7 +1246,7 @@ class _DeckConfWillSaveConfigHook: return len(self._hooks) def __call__( - self, deck_conf: aqt.deckconf.DeckConf, deck: Any, config: Any + self, deck_conf: aqt.deckconf.DeckConf, deck: Deck, config: DeckConfig ) -> None: for hook in self._hooks: try: @@ -1355,20 +1375,20 @@ editor_did_init = _EditorDidInitHook() class _EditorDidInitButtonsHook: - _hooks: List[Callable[[List, "aqt.editor.Editor"], None]] = [] + _hooks: List[Callable[[List[str], "aqt.editor.Editor"], None]] = [] - def append(self, cb: Callable[[List, "aqt.editor.Editor"], None]) -> None: - """(buttons: List, editor: aqt.editor.Editor)""" + def append(self, cb: Callable[[List[str], "aqt.editor.Editor"], None]) -> None: + """(buttons: List[str], editor: aqt.editor.Editor)""" self._hooks.append(cb) - def remove(self, cb: Callable[[List, "aqt.editor.Editor"], None]) -> None: + def remove(self, cb: Callable[[List[str], "aqt.editor.Editor"], None]) -> None: if cb in self._hooks: self._hooks.remove(cb) def count(self) -> int: return len(self._hooks) - def __call__(self, buttons: List, editor: aqt.editor.Editor) -> None: + def __call__(self, buttons: List[str], editor: aqt.editor.Editor) -> None: for hook in self._hooks: try: hook(buttons, editor) diff --git a/qt/aqt/main.py b/qt/aqt/main.py index fca36d22c..e1d680a4f 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -14,7 +14,7 @@ import weakref import zipfile from argparse import Namespace from threading import Thread -from typing import Any, Callable, Dict, List, Optional, Sequence, TextIO, Tuple, cast +from typing import Any, Callable, List, Optional, Sequence, TextIO, Tuple, cast import anki import aqt @@ -27,6 +27,7 @@ import aqt.toolbar import aqt.webview from anki import hooks from anki.collection import Collection +from anki.decks import Deck from anki.hooks import runHook from anki.lang import _, ngettext from anki.rsbackend import RustBackend @@ -652,7 +653,7 @@ from the profile screen." self.maybe_check_for_addon_updates() self.deckBrowser.show() - def _selectedDeck(self) -> Optional[Dict[str, Any]]: + def _selectedDeck(self) -> Optional[Deck]: did = self.col.decks.selected() if not self.col.decks.nameOrNone(did): showInfo(_("Please select a deck.")) diff --git a/qt/aqt/models.py b/qt/aqt/models.py index 67ffd0d49..13426a306 100644 --- a/qt/aqt/models.py +++ b/qt/aqt/models.py @@ -185,6 +185,8 @@ class Models(QDialog): class AddModel(QDialog): + model: Optional[NoteType] + def __init__(self, mw: AnkiQt, parent: Optional[QWidget] = None): self.parent_ = parent or mw self.mw = mw diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index 756b2c018..a9d5b1096 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -194,20 +194,20 @@ hooks = [ ), Hook( name="deck_conf_did_load_config", - args=["deck_conf: aqt.deckconf.DeckConf", "deck: Any", "config: Any"], + args=["deck_conf: aqt.deckconf.DeckConf", "deck: Deck", "config: DeckConfig"], doc="Called once widget state has been set from deck config", ), Hook( name="deck_conf_will_save_config", - args=["deck_conf: aqt.deckconf.DeckConf", "deck: Any", "config: Any"], + args=["deck_conf: aqt.deckconf.DeckConf", "deck: Deck", "config: DeckConfig"], doc="Called before widget state is saved to config", ), Hook( name="deck_conf_did_add_config", args=[ "deck_conf: aqt.deckconf.DeckConf", - "deck: Any", - "config: Any", + "deck: Deck", + "config: DeckConfig", "new_name: str", "new_conf_id: int", ], @@ -224,15 +224,15 @@ hooks = [ ), Hook( name="deck_conf_will_remove_config", - args=["deck_conf: aqt.deckconf.DeckConf", "deck: Any", "config: Any"], + args=["deck_conf: aqt.deckconf.DeckConf", "deck: Deck", "config: DeckConfig"], doc="Called before current config group is removed", ), Hook( name="deck_conf_will_rename_config", args=[ "deck_conf: aqt.deckconf.DeckConf", - "deck: Any", - "config: Any", + "deck: Deck", + "config: DeckConfig", "new_name: str", ], doc="Called before config group is renamed", @@ -530,7 +530,7 @@ hooks = [ ################### Hook( name="editor_did_init_buttons", - args=["buttons: List", "editor: aqt.editor.Editor"], + args=["buttons: List[str]", "editor: aqt.editor.Editor"], ), Hook( name="editor_did_init_shortcuts", @@ -653,7 +653,7 @@ hooks = [ ################### Hook( name="current_note_type_did_change", - args=["notetype: Dict[str, Any]"], + args=["notetype: NoteType"], legacy_hook="currentModelChanged", legacy_no_args=True, ),