Merge pull request #732 from Arthur-Milchior/typing

Typing
This commit is contained in:
Damien Elmes 2020-08-13 20:19:30 +10:00 committed by GitHub
commit c3f8f7dd94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 228 additions and 167 deletions

View File

@ -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"]

View File

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

View File

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

View File

@ -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:

View File

@ -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:

View File

@ -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(

View File

@ -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

View File

@ -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():

View File

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

View File

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

View File

@ -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

View File

@ -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,
),