type hints for scheduler

This commit is contained in:
Damien Elmes 2019-12-19 13:54:44 +10:00
parent a02d203604
commit 9c16d59086
2 changed files with 151 additions and 148 deletions

View file

@ -13,9 +13,10 @@ from anki.utils import ids2str, intTime, fmtTimeSpan
from anki.lang import _ from anki.lang import _
from anki.consts import * from anki.consts import *
from anki.hooks import runHook from anki.hooks import runHook
from typing import Any, List, Optional, Tuple, TypeVar
_T = TypeVar('_T') from anki.cards import Card
#from anki.collection import _Collection
from typing import Any, Callable, Dict, List, Optional, Union, Tuple
# queue types: 0=new/cram, 1=lrn, 2=rev, 3=day lrn, -1=suspended, -2=buried # queue types: 0=new/cram, 1=lrn, 2=rev, 3=day lrn, -1=suspended, -2=buried
# revlog types: 0=lrn, 1=rev, 2=relrn, 3=cram # revlog types: 0=lrn, 1=rev, 2=relrn, 3=cram
@ -57,7 +58,7 @@ class Scheduler:
self._resetNew() self._resetNew()
self._haveQueues = True self._haveQueues = True
def answerCard(self, card, ease) -> None: def answerCard(self, card: Card, ease: int) -> None:
self.col.log() self.col.log()
assert 1 <= ease <= 4 assert 1 <= ease <= 4
self.col.markReview(card) self.col.markReview(card)
@ -96,7 +97,7 @@ class Scheduler:
card.usn = self.col.usn() card.usn = self.col.usn()
card.flushSched() card.flushSched()
def counts(self, card=None) -> tuple: def counts(self, card: None = None) -> tuple:
counts = [self.newCount, self.lrnCount, self.revCount] counts = [self.newCount, self.lrnCount, self.revCount]
if card: if card:
idx = self.countIdx(card) idx = self.countIdx(card)
@ -124,12 +125,12 @@ order by due""" % self._deckLimit(),
ret = [x[1] for x in sorted(daysd.items())] ret = [x[1] for x in sorted(daysd.items())]
return ret return ret
def countIdx(self, card) -> Any: def countIdx(self, card: Card) -> Any:
if card.queue == 3: if card.queue == 3:
return 1 return 1
return card.queue return card.queue
def answerButtons(self, card) -> int: def answerButtons(self, card: Card) -> int:
if card.odue: if card.odue:
# normal review in dyn deck? # normal review in dyn deck?
if card.odid and card.queue == 2: if card.odid and card.queue == 2:
@ -163,7 +164,7 @@ order by due""" % self._deckLimit(),
# Rev/lrn/time daily stats # Rev/lrn/time daily stats
########################################################################## ##########################################################################
def _updateStats(self, card, type, cnt=1) -> None: def _updateStats(self, card: Card, type: str, cnt: int = 1) -> None:
key = type+"Today" key = type+"Today"
for g in ([self.col.decks.get(card.did)] + for g in ([self.col.decks.get(card.did)] +
self.col.decks.parents(card.did)): self.col.decks.parents(card.did)):
@ -182,7 +183,7 @@ order by due""" % self._deckLimit(),
g['revToday'][1] -= rev g['revToday'][1] -= rev
self.col.decks.save(g) self.col.decks.save(g)
def _walkingCount(self, limFn=None, cntFn=None) -> Any: def _walkingCount(self, limFn: Optional[Callable] = None, cntFn: Optional[Callable] = None) -> Any:
tot = 0 tot = 0
pcounts = {} pcounts = {}
# for each of the active decks # for each of the active decks
@ -253,7 +254,7 @@ order by due""" % self._deckLimit(),
def deckDueTree(self) -> Any: def deckDueTree(self) -> Any:
return self._groupChildren(self.deckDueList()) return self._groupChildren(self.deckDueList())
def _groupChildren(self, grps) -> Tuple[Tuple[Any, Any, Any, Any, Any, Any], ...]: def _groupChildren(self, grps: List[List]) -> Tuple[Tuple[Any, Any, Any, Any, Any, Any], ...]:
# first, split the group names into components # first, split the group names into components
for g in grps: for g in grps:
g[0] = g[0].split("::") g[0] = g[0].split("::")
@ -262,7 +263,7 @@ order by due""" % self._deckLimit(),
# then run main function # then run main function
return self._groupChildrenMain(grps) return self._groupChildrenMain(grps)
def _groupChildrenMain(self, grps) -> Tuple[Tuple[Any, Any, Any, Any, Any, Any], ...]: def _groupChildrenMain(self, grps: List[List[Union[List[str], int]]]) -> Tuple[Tuple[Any, Any, Any, Any, Any, Any], ...]:
tree = [] tree = []
# group and recurse # group and recurse
def key(grp): def key(grp):
@ -395,7 +396,7 @@ did = ? and queue = 0 limit ?)""", did, lim)
elif self.newCardModulus: elif self.newCardModulus:
return self.reps and self.reps % self.newCardModulus == 0 return self.reps and self.reps % self.newCardModulus == 0
def _deckNewLimit(self, did, fn=None) -> Any: def _deckNewLimit(self, did: int, fn: Optional[Callable] = None) -> Any:
if not fn: if not fn:
fn = self._deckNewLimitSingle fn = self._deckNewLimitSingle
sel = self.col.decks.get(did) sel = self.col.decks.get(did)
@ -409,7 +410,7 @@ did = ? and queue = 0 limit ?)""", did, lim)
lim = min(rem, lim) lim = min(rem, lim)
return lim return lim
def _newForDeck(self, did, lim) -> Any: def _newForDeck(self, did: int, lim: int) -> Any:
"New count for a single deck." "New count for a single deck."
if not lim: if not lim:
return 0 return 0
@ -418,7 +419,7 @@ did = ? and queue = 0 limit ?)""", did, lim)
select count() from select count() from
(select 1 from cards where did = ? and queue = 0 limit ?)""", did, lim) (select 1 from cards where did = ? and queue = 0 limit ?)""", did, lim)
def _deckNewLimitSingle(self, g) -> Any: def _deckNewLimitSingle(self, g: Dict[str, Any]) -> Any:
"Limit for deck without parent limits." "Limit for deck without parent limits."
if g['dyn']: if g['dyn']:
return self.reportLimit return self.reportLimit
@ -468,7 +469,7 @@ limit %d""" % (self._deckLimit(), self.reportLimit), lim=self.dayCutoff)
self._lrnQueue.sort() self._lrnQueue.sort()
return self._lrnQueue return self._lrnQueue
def _getLrnCard(self, collapse=False) -> Any: def _getLrnCard(self, collapse: bool = False) -> Any:
if self._fillLrn(): if self._fillLrn():
cutoff = time.time() cutoff = time.time()
if collapse: if collapse:
@ -509,10 +510,10 @@ did = ? and queue = 3 and due <= ? limit ?""",
self.lrnCount -= 1 self.lrnCount -= 1
return self.col.getCard(self._lrnDayQueue.pop()) return self.col.getCard(self._lrnDayQueue.pop())
def _answerLrnCard(self, card, ease) -> None: def _answerLrnCard(self, card: Card, ease: int) -> None:
# ease 1=no, 2=yes, 3=remove # ease 1=no, 2=yes, 3=remove
conf = self._lrnConf(card) conf = self._lrnConf(card)
if card.odid and not card.wasNew: if card.odid and not card.wasNew: # type: ignore
type = 3 type = 3
elif card.type == 2: elif card.type == 2:
type = 2 type = 2
@ -571,7 +572,7 @@ did = ? and queue = 3 and due <= ? limit ?""",
card.queue = 3 card.queue = 3
self._logLrn(card, ease, conf, leaving, type, lastLeft) self._logLrn(card, ease, conf, leaving, type, lastLeft)
def _delayForGrade(self, conf, left) -> Any: def _delayForGrade(self, conf: Dict[str, Any], left: int) -> Any:
left = left % 1000 left = left % 1000
try: try:
delay = conf['delays'][-left] delay = conf['delays'][-left]
@ -583,13 +584,13 @@ did = ? and queue = 3 and due <= ? limit ?""",
delay = 1 delay = 1
return delay*60 return delay*60
def _lrnConf(self, card) -> Any: def _lrnConf(self, card: Card) -> Any:
if card.type == 2: if card.type == 2:
return self._lapseConf(card) return self._lapseConf(card)
else: else:
return self._newConf(card) return self._newConf(card)
def _rescheduleAsRev(self, card, conf, early) -> None: def _rescheduleAsRev(self, card: Card, conf: Dict[str, Any], early: bool) -> None:
lapse = card.type == 2 lapse = card.type == 2
if lapse: if lapse:
if self._resched(card): if self._resched(card):
@ -612,7 +613,7 @@ did = ? and queue = 3 and due <= ? limit ?""",
card.queue = card.type = 0 card.queue = card.type = 0
card.due = self.col.nextID("pos") card.due = self.col.nextID("pos")
def _startingLeft(self, card) -> int: def _startingLeft(self, card: Card) -> int:
if card.type == 2: if card.type == 2:
conf = self._lapseConf(card) conf = self._lapseConf(card)
else: else:
@ -621,7 +622,7 @@ did = ? and queue = 3 and due <= ? limit ?""",
tod = self._leftToday(conf['delays'], tot) tod = self._leftToday(conf['delays'], tot)
return tot + tod*1000 return tot + tod*1000
def _leftToday(self, delays, left, now=None) -> int: def _leftToday(self, delays: Any, left: int, now: None = None) -> int:
"The number of steps that can be completed by the day cutoff." "The number of steps that can be completed by the day cutoff."
if not now: if not now:
now = intTime() now = intTime()
@ -634,7 +635,7 @@ did = ? and queue = 3 and due <= ? limit ?""",
ok = i ok = i
return ok+1 return ok+1
def _graduatingIvl(self, card, conf, early, adj=True) -> Any: def _graduatingIvl(self, card: Card, conf: Dict[str, Any], early: bool, adj: bool = True) -> Any:
if card.type == 2: if card.type == 2:
# lapsed card being relearnt # lapsed card being relearnt
if card.odid: if card.odid:
@ -652,13 +653,13 @@ did = ? and queue = 3 and due <= ? limit ?""",
else: else:
return ideal return ideal
def _rescheduleNew(self, card, conf, early) -> None: def _rescheduleNew(self, card: Card, conf: Dict[str, Any], early: bool) -> None:
"Reschedule a new card that's graduated for the first time." "Reschedule a new card that's graduated for the first time."
card.ivl = self._graduatingIvl(card, conf, early) card.ivl = self._graduatingIvl(card, conf, early)
card.due = self.today+card.ivl card.due = self.today+card.ivl
card.factor = conf['initialFactor'] card.factor = conf['initialFactor']
def _logLrn(self, card, ease, conf, leaving, type, lastLeft) -> None: def _logLrn(self, card: Card, ease: int, conf: Dict[str, Any], leaving: bool, type: int, lastLeft: int) -> None:
lastIvl = -(self._delayForGrade(conf, lastLeft)) lastIvl = -(self._delayForGrade(conf, lastLeft))
ivl = card.ivl if leaving else -(self._delayForGrade(conf, card.left)) ivl = card.ivl if leaving else -(self._delayForGrade(conf, card.left))
def log(): def log():
@ -673,7 +674,7 @@ did = ? and queue = 3 and due <= ? limit ?""",
time.sleep(0.01) time.sleep(0.01)
log() log()
def removeLrn(self, ids=None) -> None: def removeLrn(self, ids: Optional[List[int]] = None) -> None:
"Remove cards from the learning queues." "Remove cards from the learning queues."
if ids: if ids:
extra = " and id in "+ids2str(ids) extra = " and id in "+ids2str(ids)
@ -692,7 +693,7 @@ where queue in (1,3) and type = 2
self.forgetCards(self.col.db.list( self.forgetCards(self.col.db.list(
"select id from cards where queue in (1,3) %s" % extra)) "select id from cards where queue in (1,3) %s" % extra))
def _lrnForDeck(self, did) -> Any: def _lrnForDeck(self, did: int) -> Any:
cnt = self.col.db.scalar( cnt = self.col.db.scalar(
""" """
select sum(left/1000) from select sum(left/1000) from
@ -708,16 +709,16 @@ and due <= ? limit ?)""",
# Reviews # Reviews
########################################################################## ##########################################################################
def _deckRevLimit(self, did) -> Any: def _deckRevLimit(self, did: int) -> Any:
return self._deckNewLimit(did, self._deckRevLimitSingle) return self._deckNewLimit(did, self._deckRevLimitSingle)
def _deckRevLimitSingle(self, d) -> Any: def _deckRevLimitSingle(self, d: Dict[str, Any]) -> Any:
if d['dyn']: if d['dyn']:
return self.reportLimit return self.reportLimit
c = self.col.decks.confForDid(d['id']) c = self.col.decks.confForDid(d['id'])
return max(0, c['rev']['perDay'] - d['revToday'][1]) return max(0, c['rev']['perDay'] - d['revToday'][1])
def _revForDeck(self, did, lim) -> Any: def _revForDeck(self, did: int, lim: int) -> Any:
lim = min(lim, self.reportLimit) lim = min(lim, self.reportLimit)
return self.col.db.scalar( return self.col.db.scalar(
""" """
@ -792,7 +793,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
# Answering a review card # Answering a review card
########################################################################## ##########################################################################
def _answerRevCard(self, card, ease) -> None: def _answerRevCard(self, card: Card, ease: int) -> None:
delay = 0 delay = 0
if ease == 1: if ease == 1:
delay = self._rescheduleLapse(card) delay = self._rescheduleLapse(card)
@ -800,7 +801,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
self._rescheduleRev(card, ease) self._rescheduleRev(card, ease)
self._logRev(card, ease, delay) self._logRev(card, ease, delay)
def _rescheduleLapse(self, card) -> Any: def _rescheduleLapse(self, card: Card) -> Any:
conf = self._lapseConf(card) conf = self._lapseConf(card)
card.lastIvl = card.ivl card.lastIvl = card.ivl
if self._resched(card): if self._resched(card):
@ -836,10 +837,10 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
card.queue = 3 card.queue = 3
return delay return delay
def _nextLapseIvl(self, card, conf) -> Any: def _nextLapseIvl(self, card: Card, conf: Dict[str, Any]) -> Any:
return max(conf['minInt'], int(card.ivl*conf['mult'])) return max(conf['minInt'], int(card.ivl*conf['mult']))
def _rescheduleRev(self, card, ease) -> None: def _rescheduleRev(self, card: Card, ease: int) -> None:
# update interval # update interval
card.lastIvl = card.ivl card.lastIvl = card.ivl
if self._resched(card): if self._resched(card):
@ -854,7 +855,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
card.odid = 0 card.odid = 0
card.odue = 0 card.odue = 0
def _logRev(self, card, ease, delay) -> None: def _logRev(self, card: Card, ease: int, delay: Union[int, float]) -> None:
def log(): def log():
self.col.db.execute( self.col.db.execute(
"insert into revlog values (?,?,?,?,?,?,?,?,?)", "insert into revlog values (?,?,?,?,?,?,?,?,?)",
@ -871,7 +872,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
# Interval management # Interval management
########################################################################## ##########################################################################
def _nextRevIvl(self, card, ease) -> Any: def _nextRevIvl(self, card: Card, ease: int) -> Any:
"Ideal next interval for CARD, given EASE." "Ideal next interval for CARD, given EASE."
delay = self._daysLate(card) delay = self._daysLate(card)
conf = self._revConf(card) conf = self._revConf(card)
@ -889,11 +890,11 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
# interval capped? # interval capped?
return min(interval, conf['maxIvl']) return min(interval, conf['maxIvl'])
def _fuzzedIvl(self, ivl) -> int: def _fuzzedIvl(self, ivl: int) -> int:
min, max = self._fuzzIvlRange(ivl) min, max = self._fuzzIvlRange(ivl)
return random.randint(min, max) return random.randint(min, max)
def _fuzzIvlRange(self, ivl) -> List: def _fuzzIvlRange(self, ivl: int) -> List:
if ivl < 2: if ivl < 2:
return [1, 1] return [1, 1]
elif ivl == 2: elif ivl == 2:
@ -908,22 +909,22 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
fuzz = max(fuzz, 1) fuzz = max(fuzz, 1)
return [ivl-fuzz, ivl+fuzz] return [ivl-fuzz, ivl+fuzz]
def _constrainedIvl(self, ivl, conf, prev) -> int: def _constrainedIvl(self, ivl: float, conf: Dict[str, Any], prev: int) -> int:
"Integer interval after interval factor and prev+1 constraints applied." "Integer interval after interval factor and prev+1 constraints applied."
new = ivl * conf.get('ivlFct', 1) new = ivl * conf.get('ivlFct', 1)
return int(max(new, prev+1)) return int(max(new, prev+1))
def _daysLate(self, card) -> Any: def _daysLate(self, card: Card) -> Any:
"Number of days later than scheduled." "Number of days later than scheduled."
due = card.odue if card.odid else card.due due = card.odue if card.odid else card.due
return max(0, self.today - due) return max(0, self.today - due)
def _updateRevIvl(self, card, ease) -> None: def _updateRevIvl(self, card: Card, ease: int) -> None:
idealIvl = self._nextRevIvl(card, ease) idealIvl = self._nextRevIvl(card, ease)
card.ivl = min(max(self._adjRevIvl(card, idealIvl), card.ivl+1), card.ivl = min(max(self._adjRevIvl(card, idealIvl), card.ivl+1),
self._revConf(card)['maxIvl']) self._revConf(card)['maxIvl'])
def _adjRevIvl(self, card, idealIvl) -> int: def _adjRevIvl(self, card: Card, idealIvl: int) -> int:
if self._spreadRev: if self._spreadRev:
idealIvl = self._fuzzedIvl(idealIvl) idealIvl = self._fuzzedIvl(idealIvl)
return idealIvl return idealIvl
@ -931,7 +932,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
# Dynamic deck handling # Dynamic deck handling
########################################################################## ##########################################################################
def rebuildDyn(self, did=None) -> Any: def rebuildDyn(self, did: Optional[int] = None) -> Any:
"Rebuild a dynamic deck." "Rebuild a dynamic deck."
did = did or self.col.decks.selected() did = did or self.col.decks.selected()
deck = self.col.decks.get(did) deck = self.col.decks.get(did)
@ -945,7 +946,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
self.col.decks.select(did) self.col.decks.select(did)
return ids return ids
def _fillDyn(self, deck) -> Any: def _fillDyn(self, deck: Dict[str, Any]) -> Any:
search, limit, order = deck['terms'][0] search, limit, order = deck['terms'][0]
orderlimit = self._dynOrder(order, limit) orderlimit = self._dynOrder(order, limit)
if search.strip(): if search.strip():
@ -961,7 +962,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
self._moveToDyn(deck['id'], ids) self._moveToDyn(deck['id'], ids)
return ids return ids
def emptyDyn(self, did, lim=None) -> None: def emptyDyn(self, did: Optional[int], lim: Optional[str] = None) -> None:
if not lim: if not lim:
lim = "did = %s" % did lim = "did = %s" % did
self.col.log(self.col.db.list("select id from cards where %s" % lim)) self.col.log(self.col.db.list("select id from cards where %s" % lim))
@ -972,10 +973,10 @@ else type end), type = (case when type = 1 then 0 else type end),
due = odue, odue = 0, odid = 0, usn = ? where %s""" % lim, due = odue, odue = 0, odid = 0, usn = ? where %s""" % lim,
self.col.usn()) self.col.usn())
def remFromDyn(self, cids) -> None: def remFromDyn(self, cids: List[int]) -> None:
self.emptyDyn(None, "id in %s and odid" % ids2str(cids)) self.emptyDyn(None, "id in %s and odid" % ids2str(cids))
def _dynOrder(self, o, l) -> str: def _dynOrder(self, o: int, l: int) -> str:
if o == DYN_OLDEST: if o == DYN_OLDEST:
t = "(select max(id) from revlog where cid=c.id)" t = "(select max(id) from revlog where cid=c.id)"
elif o == DYN_RANDOM: elif o == DYN_RANDOM:
@ -1000,7 +1001,7 @@ due = odue, odue = 0, odid = 0, usn = ? where %s""" % lim,
t = "c.due" t = "c.due"
return t + " limit %d" % l return t + " limit %d" % l
def _moveToDyn(self, did, ids) -> None: def _moveToDyn(self, did: int, ids: List[int]) -> None:
deck = self.col.decks.get(did) deck = self.col.decks.get(did)
data = [] data = []
t = intTime(); u = self.col.usn() t = intTime(); u = self.col.usn()
@ -1019,7 +1020,7 @@ odid = (case when odid then odid else did end),
odue = (case when odue then odue else due end), odue = (case when odue then odue else due end),
did = ?, queue = %s, due = ?, usn = ? where id = ?""" % queue, data) did = ?, queue = %s, due = ?, usn = ? where id = ?""" % queue, data)
def _dynIvlBoost(self, card) -> Any: def _dynIvlBoost(self, card: Card) -> Any:
assert card.odid and card.type == 2 assert card.odid and card.type == 2
assert card.factor assert card.factor
elapsed = card.ivl - (card.odue - self.today) elapsed = card.ivl - (card.odue - self.today)
@ -1031,7 +1032,7 @@ did = ?, queue = %s, due = ?, usn = ? where id = ?""" % queue, data)
# Leeches # Leeches
########################################################################## ##########################################################################
def _checkLeech(self, card, conf) -> Optional[bool]: def _checkLeech(self, card: Card, conf: Dict[str, Any]) -> Optional[bool]:
"Leech handler. True if card was a leech." "Leech handler. True if card was a leech."
lf = conf['leechFails'] lf = conf['leechFails']
if not lf: if not lf:
@ -1060,10 +1061,10 @@ did = ?, queue = %s, due = ?, usn = ? where id = ?""" % queue, data)
# Tools # Tools
########################################################################## ##########################################################################
def _cardConf(self, card) -> Any: def _cardConf(self, card: Card) -> Any:
return self.col.decks.confForDid(card.did) return self.col.decks.confForDid(card.did)
def _newConf(self, card) -> Any: def _newConf(self, card: Card) -> Any:
conf = self._cardConf(card) conf = self._cardConf(card)
# normal deck # normal deck
if not card.odid: if not card.odid:
@ -1083,7 +1084,7 @@ did = ?, queue = %s, due = ?, usn = ? where id = ?""" % queue, data)
perDay=self.reportLimit perDay=self.reportLimit
) )
def _lapseConf(self, card) -> Any: def _lapseConf(self, card: Card) -> Any:
conf = self._cardConf(card) conf = self._cardConf(card)
# normal deck # normal deck
if not card.odid: if not card.odid:
@ -1102,7 +1103,7 @@ did = ?, queue = %s, due = ?, usn = ? where id = ?""" % queue, data)
resched=conf['resched'], resched=conf['resched'],
) )
def _revConf(self, card) -> Any: def _revConf(self, card: Card) -> Any:
conf = self._cardConf(card) conf = self._cardConf(card)
# normal deck # normal deck
if not card.odid: if not card.odid:
@ -1113,7 +1114,7 @@ did = ?, queue = %s, due = ?, usn = ? where id = ?""" % queue, data)
def _deckLimit(self) -> str: def _deckLimit(self) -> str:
return ids2str(self.col.decks.active()) return ids2str(self.col.decks.active())
def _resched(self, card) -> Any: def _resched(self, card: Card) -> Any:
conf = self._cardConf(card) conf = self._cardConf(card)
if not conf['dyn']: if not conf['dyn']:
return True return True
@ -1206,7 +1207,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
# Next time reports # Next time reports
########################################################################## ##########################################################################
def nextIvlStr(self, card, ease, short=False) -> Any: def nextIvlStr(self, card: Card, ease: int, short: bool = False) -> Any:
"Return the next interval for CARD as a string." "Return the next interval for CARD as a string."
ivl = self.nextIvl(card, ease) ivl = self.nextIvl(card, ease)
if not ivl: if not ivl:
@ -1216,7 +1217,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
s = "<"+s s = "<"+s
return s return s
def nextIvl(self, card, ease) -> Any: def nextIvl(self, card: Card, ease: int) -> Any:
"Return the next interval for CARD, in seconds." "Return the next interval for CARD, in seconds."
if card.queue in (0,1,3): if card.queue in (0,1,3):
return self._nextLrnIvl(card, ease) return self._nextLrnIvl(card, ease)
@ -1231,7 +1232,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
return self._nextRevIvl(card, ease)*86400 return self._nextRevIvl(card, ease)*86400
# this isn't easily extracted from the learn code # this isn't easily extracted from the learn code
def _nextLrnIvl(self, card, ease) -> Any: def _nextLrnIvl(self, card: Card, ease: int) -> Any:
if card.queue == 0: if card.queue == 0:
card.left = self._startingLeft(card) card.left = self._startingLeft(card)
conf = self._lrnConf(card) conf = self._lrnConf(card)
@ -1256,7 +1257,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
# Suspending # Suspending
########################################################################## ##########################################################################
def suspendCards(self, ids) -> None: def suspendCards(self, ids: List[int]) -> None:
"Suspend cards." "Suspend cards."
self.col.log(ids) self.col.log(ids)
self.remFromDyn(ids) self.remFromDyn(ids)
@ -1265,7 +1266,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
"update cards set queue=-1,mod=?,usn=? where id in "+ "update cards set queue=-1,mod=?,usn=? where id in "+
ids2str(ids), intTime(), self.col.usn()) ids2str(ids), intTime(), self.col.usn())
def unsuspendCards(self, ids) -> None: def unsuspendCards(self, ids: List[int]) -> None:
"Unsuspend cards." "Unsuspend cards."
self.col.log(ids) self.col.log(ids)
self.col.db.execute( self.col.db.execute(
@ -1273,7 +1274,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
"where queue = -1 and id in "+ ids2str(ids), "where queue = -1 and id in "+ ids2str(ids),
intTime(), self.col.usn()) intTime(), self.col.usn())
def buryCards(self, cids) -> None: def buryCards(self, cids: List[int]) -> None:
self.col.log(cids) self.col.log(cids)
self.remFromDyn(cids) self.remFromDyn(cids)
self.removeLrn(cids) self.removeLrn(cids)
@ -1281,7 +1282,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
update cards set queue=-2,mod=?,usn=? where id in """+ids2str(cids), update cards set queue=-2,mod=?,usn=? where id in """+ids2str(cids),
intTime(), self.col.usn()) intTime(), self.col.usn())
def buryNote(self, nid) -> None: def buryNote(self, nid: int) -> None:
"Bury all cards for note until next session." "Bury all cards for note until next session."
cids = self.col.db.list( cids = self.col.db.list(
"select id from cards where nid = ? and queue >= 0", nid) "select id from cards where nid = ? and queue >= 0", nid)
@ -1290,7 +1291,7 @@ update cards set queue=-2,mod=?,usn=? where id in """+ids2str(cids),
# Sibling spacing # Sibling spacing
########################################################################## ##########################################################################
def _burySiblings(self, card) -> None: def _burySiblings(self, card: Card) -> None:
toBury = [] toBury = []
nconf = self._newConf(card) nconf = self._newConf(card)
buryNew = nconf.get("bury", True) buryNew = nconf.get("bury", True)
@ -1327,7 +1328,7 @@ and (queue=0 or (queue=2 and due<=?))""",
# Resetting # Resetting
########################################################################## ##########################################################################
def forgetCards(self, ids) -> None: def forgetCards(self, ids: List[int]) -> None:
"Put cards at the end of the new queue." "Put cards at the end of the new queue."
self.remFromDyn(ids) self.remFromDyn(ids)
self.col.db.execute( self.col.db.execute(
@ -1339,7 +1340,7 @@ and (queue=0 or (queue=2 and due<=?))""",
self.sortCards(ids, start=pmax+1) self.sortCards(ids, start=pmax+1)
self.col.log(ids) self.col.log(ids)
def reschedCards(self, ids, imin, imax) -> None: def reschedCards(self, ids: List[int], imin: int, imax: int) -> None:
"Put cards in review queue with a new interval in days (min, max)." "Put cards in review queue with a new interval in days (min, max)."
d = [] d = []
t = self.today t = self.today
@ -1374,7 +1375,7 @@ usn=:usn,mod=:mod,factor=:fact where id=:id""",
# Repositioning new cards # Repositioning new cards
########################################################################## ##########################################################################
def sortCards(self, cids, start=1, step=1, shuffle=False, shift=False) -> None: def sortCards(self, cids: List[int], start: int = 1, step: int = 1, shuffle: bool = False, shift: bool = False) -> None:
scids = ids2str(cids) scids = ids2str(cids)
now = intTime() now = intTime()
nids = [] nids = []
@ -1414,11 +1415,11 @@ and due >= ? and queue = 0""" % scids, now, self.col.usn(), shiftby, low)
self.col.db.executemany( self.col.db.executemany(
"update cards set due=:due,mod=:now,usn=:usn where id = :cid", d) "update cards set due=:due,mod=:now,usn=:usn where id = :cid", d)
def randomizeCards(self, did) -> None: def randomizeCards(self, did: int) -> None:
cids = self.col.db.list("select id from cards where did = ?", did) cids = self.col.db.list("select id from cards where did = ?", did)
self.sortCards(cids, shuffle=True) self.sortCards(cids, shuffle=True)
def orderCards(self, did) -> None: def orderCards(self, did: int) -> None:
cids = self.col.db.list("select id from cards where did = ? order by id", did) cids = self.col.db.list("select id from cards where did = ? order by id", did)
self.sortCards(cids) self.sortCards(cids)

View file

@ -13,7 +13,6 @@ from anki.utils import ids2str, intTime, fmtTimeSpan
from anki.lang import _ from anki.lang import _
from anki.consts import * from anki.consts import *
from anki.hooks import runHook from anki.hooks import runHook
from typing import Any, List, Optional, Tuple
# card types: 0=new, 1=lrn, 2=rev, 3=relrn # card types: 0=new, 1=lrn, 2=rev, 3=relrn
# queue types: 0=new, 1=(re)lrn, 2=rev, 3=day (re)lrn, # queue types: 0=new, 1=(re)lrn, 2=rev, 3=day (re)lrn,
@ -22,6 +21,9 @@ from typing import Any, List, Optional, Tuple
# positive revlog intervals are in days (rev), negative in seconds (lrn) # positive revlog intervals are in days (rev), negative in seconds (lrn)
# odue/odid store original due/did when cards moved to filtered deck # odue/odid store original due/did when cards moved to filtered deck
from anki.cards import Card
#from anki.collection import _Collection
from typing import Any, Callable, Dict, List, Optional, Union, Tuple
class Scheduler: class Scheduler:
name = "std2" name = "std2"
haveCustomStudy = True haveCustomStudy = True
@ -59,7 +61,7 @@ class Scheduler:
self._resetNew() self._resetNew()
self._haveQueues = True self._haveQueues = True
def answerCard(self, card, ease) -> None: def answerCard(self, card: Card, ease: int) -> None:
self.col.log() self.col.log()
assert 1 <= ease <= 4 assert 1 <= ease <= 4
assert 0 <= card.queue <= 4 assert 0 <= card.queue <= 4
@ -74,7 +76,7 @@ class Scheduler:
card.usn = self.col.usn() card.usn = self.col.usn()
card.flushSched() card.flushSched()
def _answerCard(self, card, ease) -> None: def _answerCard(self, card: Card, ease: int) -> None:
if self._previewingCard(card): if self._previewingCard(card):
self._answerCardPreview(card, ease) self._answerCardPreview(card, ease)
return return
@ -104,7 +106,7 @@ class Scheduler:
if card.odue: if card.odue:
card.odue = 0 card.odue = 0
def _answerCardPreview(self, card, ease) -> None: def _answerCardPreview(self, card: Card, ease: int) -> None:
assert 1 <= ease <= 2 assert 1 <= ease <= 2
if ease == 1: if ease == 1:
@ -117,7 +119,7 @@ class Scheduler:
self._restorePreviewCard(card) self._restorePreviewCard(card)
self._removeFromFiltered(card) self._removeFromFiltered(card)
def counts(self, card=None) -> tuple: def counts(self, card: None = None) -> tuple:
counts = [self.newCount, self.lrnCount, self.revCount] counts = [self.newCount, self.lrnCount, self.revCount]
if card: if card:
idx = self.countIdx(card) idx = self.countIdx(card)
@ -142,12 +144,12 @@ order by due""" % self._deckLimit(),
ret = [x[1] for x in sorted(daysd.items())] ret = [x[1] for x in sorted(daysd.items())]
return ret return ret
def countIdx(self, card) -> Any: def countIdx(self, card: Card) -> Any:
if card.queue in (3,4): if card.queue in (3,4):
return 1 return 1
return card.queue return card.queue
def answerButtons(self, card) -> int: def answerButtons(self, card: Card) -> int:
conf = self._cardConf(card) conf = self._cardConf(card)
if card.odid and not conf['resched']: if card.odid and not conf['resched']:
return 2 return 2
@ -156,7 +158,7 @@ order by due""" % self._deckLimit(),
# Rev/lrn/time daily stats # Rev/lrn/time daily stats
########################################################################## ##########################################################################
def _updateStats(self, card, type, cnt=1) -> None: def _updateStats(self, card: Card, type: str, cnt: int = 1) -> None:
key = type+"Today" key = type+"Today"
for g in ([self.col.decks.get(card.did)] + for g in ([self.col.decks.get(card.did)] +
self.col.decks.parents(card.did)): self.col.decks.parents(card.did)):
@ -175,7 +177,7 @@ order by due""" % self._deckLimit(),
g['revToday'][1] -= rev g['revToday'][1] -= rev
self.col.decks.save(g) self.col.decks.save(g)
def _walkingCount(self, limFn=None, cntFn=None) -> Any: def _walkingCount(self, limFn: Optional[Callable] = None, cntFn: Optional[Callable] = None) -> Any:
tot = 0 tot = 0
pcounts = {} pcounts = {}
# for each of the active decks # for each of the active decks
@ -249,7 +251,7 @@ order by due""" % self._deckLimit(),
def deckDueTree(self) -> Any: def deckDueTree(self) -> Any:
return self._groupChildren(self.deckDueList()) return self._groupChildren(self.deckDueList())
def _groupChildren(self, grps) -> Tuple[Tuple[Any, Any, Any, Any, Any, Any], ...]: def _groupChildren(self, grps: List[List]) -> Tuple[Tuple[Any, Any, Any, Any, Any, Any], ...]:
# first, split the group names into components # first, split the group names into components
for g in grps: for g in grps:
g[0] = g[0].split("::") g[0] = g[0].split("::")
@ -258,7 +260,7 @@ order by due""" % self._deckLimit(),
# then run main function # then run main function
return self._groupChildrenMain(grps) return self._groupChildrenMain(grps)
def _groupChildrenMain(self, grps) -> Tuple[Tuple[Any, Any, Any, Any, Any, Any], ...]: def _groupChildrenMain(self, grps: List[List[Union[List[str], int]]]) -> Tuple[Tuple[Any, Any, Any, Any, Any, Any], ...]:
tree = [] tree = []
# group and recurse # group and recurse
def key(grp): def key(grp):
@ -402,7 +404,7 @@ did = ? and queue = 0 limit ?)""", did, lim)
elif self.newCardModulus: elif self.newCardModulus:
return self.reps and self.reps % self.newCardModulus == 0 return self.reps and self.reps % self.newCardModulus == 0
def _deckNewLimit(self, did, fn=None) -> Any: def _deckNewLimit(self, did: int, fn: None = None) -> Any:
if not fn: if not fn:
fn = self._deckNewLimitSingle fn = self._deckNewLimitSingle
sel = self.col.decks.get(did) sel = self.col.decks.get(did)
@ -416,7 +418,7 @@ did = ? and queue = 0 limit ?)""", did, lim)
lim = min(rem, lim) lim = min(rem, lim)
return lim return lim
def _newForDeck(self, did, lim) -> Any: def _newForDeck(self, did: int, lim: int) -> Any:
"New count for a single deck." "New count for a single deck."
if not lim: if not lim:
return 0 return 0
@ -425,7 +427,7 @@ did = ? and queue = 0 limit ?)""", did, lim)
select count() from select count() from
(select 1 from cards where did = ? and queue = 0 limit ?)""", did, lim) (select 1 from cards where did = ? and queue = 0 limit ?)""", did, lim)
def _deckNewLimitSingle(self, g) -> Any: def _deckNewLimitSingle(self, g: Dict[str, Any]) -> Any:
"Limit for deck without parent limits." "Limit for deck without parent limits."
if g['dyn']: if g['dyn']:
return self.dynReportLimit return self.dynReportLimit
@ -443,14 +445,14 @@ select id from cards where did in %s and queue = 0 limit ?)"""
########################################################################## ##########################################################################
# scan for any newly due learning cards every minute # scan for any newly due learning cards every minute
def _updateLrnCutoff(self, force) -> bool: def _updateLrnCutoff(self, force: bool) -> bool:
nextCutoff = intTime() + self.col.conf['collapseTime'] nextCutoff = intTime() + self.col.conf['collapseTime']
if nextCutoff - self._lrnCutoff > 60 or force: if nextCutoff - self._lrnCutoff > 60 or force:
self._lrnCutoff = nextCutoff self._lrnCutoff = nextCutoff
return True return True
return False return False
def _maybeResetLrn(self, force) -> None: def _maybeResetLrn(self, force: bool) -> None:
if self._updateLrnCutoff(force): if self._updateLrnCutoff(force):
self._resetLrn() self._resetLrn()
@ -493,7 +495,7 @@ limit %d""" % (self._deckLimit(), self.reportLimit), lim=cutoff)
self._lrnQueue.sort() self._lrnQueue.sort()
return self._lrnQueue return self._lrnQueue
def _getLrnCard(self, collapse=False) -> Any: def _getLrnCard(self, collapse: bool = False) -> Any:
self._maybeResetLrn(force=collapse and self.lrnCount == 0) self._maybeResetLrn(force=collapse and self.lrnCount == 0)
if self._fillLrn(): if self._fillLrn():
cutoff = time.time() cutoff = time.time()
@ -535,7 +537,7 @@ did = ? and queue = 3 and due <= ? limit ?""",
self.lrnCount -= 1 self.lrnCount -= 1
return self.col.getCard(self._lrnDayQueue.pop()) return self.col.getCard(self._lrnDayQueue.pop())
def _answerLrnCard(self, card, ease) -> None: def _answerLrnCard(self, card: Card, ease: int) -> None:
conf = self._lrnConf(card) conf = self._lrnConf(card)
if card.type in (2,3): if card.type in (2,3):
type = 2 type = 2
@ -566,11 +568,11 @@ did = ? and queue = 3 and due <= ? limit ?""",
self._logLrn(card, ease, conf, leaving, type, lastLeft) self._logLrn(card, ease, conf, leaving, type, lastLeft)
def _updateRevIvlOnFail(self, card, conf) -> None: def _updateRevIvlOnFail(self, card: Card, conf: Dict[str, Any]) -> None:
card.lastIvl = card.ivl card.lastIvl = card.ivl
card.ivl = self._lapseIvl(card, conf) card.ivl = self._lapseIvl(card, conf)
def _moveToFirstStep(self, card, conf) -> Any: def _moveToFirstStep(self, card: Card, conf: Dict[str, Any]) -> Any:
card.left = self._startingLeft(card) card.left = self._startingLeft(card)
# relearning card? # relearning card?
@ -579,18 +581,18 @@ did = ? and queue = 3 and due <= ? limit ?""",
return self._rescheduleLrnCard(card, conf) return self._rescheduleLrnCard(card, conf)
def _moveToNextStep(self, card, conf) -> None: def _moveToNextStep(self, card: Card, conf: Dict[str, Any]) -> None:
# decrement real left count and recalculate left today # decrement real left count and recalculate left today
left = (card.left % 1000) - 1 left = (card.left % 1000) - 1
card.left = self._leftToday(conf['delays'], left)*1000 + left card.left = self._leftToday(conf['delays'], left)*1000 + left
self._rescheduleLrnCard(card, conf) self._rescheduleLrnCard(card, conf)
def _repeatStep(self, card, conf) -> None: def _repeatStep(self, card: Card, conf: Dict[str, Any]) -> None:
delay = self._delayForRepeatingGrade(conf, card.left) delay = self._delayForRepeatingGrade(conf, card.left)
self._rescheduleLrnCard(card, conf, delay=delay) self._rescheduleLrnCard(card, conf, delay=delay)
def _rescheduleLrnCard(self, card, conf, delay=None) -> Any: def _rescheduleLrnCard(self, card: Card, conf: Dict[str, Any], delay: Optional[int] = None) -> Any:
# normal delay for the current step? # normal delay for the current step?
if delay is None: if delay is None:
delay = self._delayForGrade(conf, card.left) delay = self._delayForGrade(conf, card.left)
@ -620,7 +622,7 @@ did = ? and queue = 3 and due <= ? limit ?""",
card.queue = 3 card.queue = 3
return delay return delay
def _delayForGrade(self, conf, left) -> Any: def _delayForGrade(self, conf: Dict[str, Any], left: int) -> Any:
left = left % 1000 left = left % 1000
try: try:
delay = conf['delays'][-left] delay = conf['delays'][-left]
@ -632,7 +634,7 @@ did = ? and queue = 3 and due <= ? limit ?""",
delay = 1 delay = 1
return delay*60 return delay*60
def _delayForRepeatingGrade(self, conf, left) -> Any: def _delayForRepeatingGrade(self, conf: Dict[str, Any], left: int) -> Any:
# halfway between last and next # halfway between last and next
delay1 = self._delayForGrade(conf, left) delay1 = self._delayForGrade(conf, left)
if len(conf['delays']) > 1: if len(conf['delays']) > 1:
@ -642,13 +644,13 @@ did = ? and queue = 3 and due <= ? limit ?""",
avg = (delay1+max(delay1, delay2))//2 avg = (delay1+max(delay1, delay2))//2
return avg return avg
def _lrnConf(self, card) -> Any: def _lrnConf(self, card: Card) -> Any:
if card.type in (2, 3): if card.type in (2, 3):
return self._lapseConf(card) return self._lapseConf(card)
else: else:
return self._newConf(card) return self._newConf(card)
def _rescheduleAsRev(self, card, conf, early) -> None: def _rescheduleAsRev(self, card: Card, conf: Dict[str, Any], early: bool) -> None:
lapse = card.type in (2,3) lapse = card.type in (2,3)
if lapse: if lapse:
@ -660,14 +662,14 @@ did = ? and queue = 3 and due <= ? limit ?""",
if card.odid: if card.odid:
self._removeFromFiltered(card) self._removeFromFiltered(card)
def _rescheduleGraduatingLapse(self, card, early=False) -> None: def _rescheduleGraduatingLapse(self, card: Card, early: bool = False) -> None:
if early: if early:
card.ivl += 1 card.ivl += 1
card.due = self.today+card.ivl card.due = self.today+card.ivl
card.queue = 2 card.queue = 2
card.type = 2 card.type = 2
def _startingLeft(self, card) -> int: def _startingLeft(self, card: Card) -> int:
if card.type == 3: if card.type == 3:
conf = self._lapseConf(card) conf = self._lapseConf(card)
else: else:
@ -676,7 +678,7 @@ did = ? and queue = 3 and due <= ? limit ?""",
tod = self._leftToday(conf['delays'], tot) tod = self._leftToday(conf['delays'], tot)
return tot + tod*1000 return tot + tod*1000
def _leftToday(self, delays, left, now=None) -> int: def _leftToday(self, delays: Union[List[int], List[Union[float, int]]], left: int, now: None = None) -> int:
"The number of steps that can be completed by the day cutoff." "The number of steps that can be completed by the day cutoff."
if not now: if not now:
now = intTime() now = intTime()
@ -689,7 +691,7 @@ did = ? and queue = 3 and due <= ? limit ?""",
ok = i ok = i
return ok+1 return ok+1
def _graduatingIvl(self, card, conf, early, fuzz=True) -> Any: def _graduatingIvl(self, card: Card, conf: Dict[str, Any], early: bool, fuzz: bool = True) -> Any:
if card.type in (2,3): if card.type in (2,3):
bonus = early and 1 or 0 bonus = early and 1 or 0
return card.ivl + bonus return card.ivl + bonus
@ -703,14 +705,14 @@ did = ? and queue = 3 and due <= ? limit ?""",
ideal = self._fuzzedIvl(ideal) ideal = self._fuzzedIvl(ideal)
return ideal return ideal
def _rescheduleNew(self, card, conf, early) -> None: def _rescheduleNew(self, card: Card, conf: Dict[str, Any], early: bool) -> None:
"Reschedule a new card that's graduated for the first time." "Reschedule a new card that's graduated for the first time."
card.ivl = self._graduatingIvl(card, conf, early) card.ivl = self._graduatingIvl(card, conf, early)
card.due = self.today+card.ivl card.due = self.today+card.ivl
card.factor = conf['initialFactor'] card.factor = conf['initialFactor']
card.type = card.queue = 2 card.type = card.queue = 2
def _logLrn(self, card, ease, conf, leaving, type, lastLeft) -> None: def _logLrn(self, card: Card, ease: int, conf: Dict[str, Any], leaving: bool, type: int, lastLeft: int) -> None:
lastIvl = -(self._delayForGrade(conf, lastLeft)) lastIvl = -(self._delayForGrade(conf, lastLeft))
ivl = card.ivl if leaving else -(self._delayForGrade(conf, card.left)) ivl = card.ivl if leaving else -(self._delayForGrade(conf, card.left))
def log(): def log():
@ -725,7 +727,7 @@ did = ? and queue = 3 and due <= ? limit ?""",
time.sleep(0.01) time.sleep(0.01)
log() log()
def _lrnForDeck(self, did) -> Any: def _lrnForDeck(self, did: int) -> Any:
cnt = self.col.db.scalar( cnt = self.col.db.scalar(
""" """
select count() from select count() from
@ -745,7 +747,7 @@ and due <= ? limit ?)""",
d = self.col.decks.get(self.col.decks.selected(), default=False) d = self.col.decks.get(self.col.decks.selected(), default=False)
return self._deckRevLimitSingle(d) return self._deckRevLimitSingle(d)
def _deckRevLimitSingle(self, d, parentLimit=None) -> Any: def _deckRevLimitSingle(self, d: Dict[str, Any], parentLimit: Optional[int] = None) -> Any:
# invalid deck selected? # invalid deck selected?
if not d: if not d:
return 0 return 0
@ -766,7 +768,7 @@ and due <= ? limit ?)""",
lim = min(lim, self._deckRevLimitSingle(parent, parentLimit=lim)) lim = min(lim, self._deckRevLimitSingle(parent, parentLimit=lim))
return lim return lim
def _revForDeck(self, did, lim, childMap) -> Any: def _revForDeck(self, did: int, lim: int, childMap: Dict[int, Any]) -> Any:
dids = [did] + self.col.decks.childDids(did, childMap) dids = [did] + self.col.decks.childDids(did, childMap)
lim = min(lim, self.reportLimit) lim = min(lim, self.reportLimit)
return self.col.db.scalar( return self.col.db.scalar(
@ -829,7 +831,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
# Answering a review card # Answering a review card
########################################################################## ##########################################################################
def _answerRevCard(self, card, ease) -> None: def _answerRevCard(self, card: Card, ease: int) -> None:
delay = 0 delay = 0
early = card.odid and (card.odue > self.today) early = card.odid and (card.odue > self.today)
type = early and 3 or 1 type = early and 3 or 1
@ -841,7 +843,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
self._logRev(card, ease, delay, type) self._logRev(card, ease, delay, type)
def _rescheduleLapse(self, card) -> Any: def _rescheduleLapse(self, card: Card) -> Any:
conf = self._lapseConf(card) conf = self._lapseConf(card)
card.lapses += 1 card.lapses += 1
@ -863,11 +865,11 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
return delay return delay
def _lapseIvl(self, card, conf) -> Any: def _lapseIvl(self, card: Card, conf: Dict[str, Any]) -> Any:
ivl = max(1, conf['minInt'], int(card.ivl*conf['mult'])) ivl = max(1, conf['minInt'], int(card.ivl*conf['mult']))
return ivl return ivl
def _rescheduleRev(self, card, ease, early) -> None: def _rescheduleRev(self, card: Card, ease: int, early: Union[bool, int]) -> None:
# update interval # update interval
card.lastIvl = card.ivl card.lastIvl = card.ivl
if early: if early:
@ -882,7 +884,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
# card leaves filtered deck # card leaves filtered deck
self._removeFromFiltered(card) self._removeFromFiltered(card)
def _logRev(self, card, ease, delay, type) -> None: def _logRev(self, card: Card, ease: int, delay: int, type: int) -> None:
def log(): def log():
self.col.db.execute( self.col.db.execute(
"insert into revlog values (?,?,?,?,?,?,?,?,?)", "insert into revlog values (?,?,?,?,?,?,?,?,?)",
@ -899,7 +901,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
# Interval management # Interval management
########################################################################## ##########################################################################
def _nextRevIvl(self, card, ease, fuzz) -> int: def _nextRevIvl(self, card: Card, ease: int, fuzz: bool) -> int:
"Next review interval for CARD, given EASE." "Next review interval for CARD, given EASE."
delay = self._daysLate(card) delay = self._daysLate(card)
conf = self._revConf(card) conf = self._revConf(card)
@ -921,11 +923,11 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
(card.ivl + delay) * fct * conf['ease4'], conf, ivl3, fuzz) (card.ivl + delay) * fct * conf['ease4'], conf, ivl3, fuzz)
return ivl4 return ivl4
def _fuzzedIvl(self, ivl) -> int: def _fuzzedIvl(self, ivl: int) -> int:
min, max = self._fuzzIvlRange(ivl) min, max = self._fuzzIvlRange(ivl)
return random.randint(min, max) return random.randint(min, max)
def _fuzzIvlRange(self, ivl) -> List: def _fuzzIvlRange(self, ivl: int) -> List:
if ivl < 2: if ivl < 2:
return [1, 1] return [1, 1]
elif ivl == 2: elif ivl == 2:
@ -940,7 +942,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
fuzz = max(fuzz, 1) fuzz = max(fuzz, 1)
return [ivl-fuzz, ivl+fuzz] return [ivl-fuzz, ivl+fuzz]
def _constrainedIvl(self, ivl, conf, prev, fuzz) -> int: def _constrainedIvl(self, ivl: Union[int, float], conf: Dict[str, Any], prev: int, fuzz: bool) -> int:
ivl = int(ivl * conf.get('ivlFct', 1)) ivl = int(ivl * conf.get('ivlFct', 1))
if fuzz: if fuzz:
ivl = self._fuzzedIvl(ivl) ivl = self._fuzzedIvl(ivl)
@ -948,19 +950,19 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
ivl = min(ivl, conf['maxIvl']) ivl = min(ivl, conf['maxIvl'])
return int(ivl) return int(ivl)
def _daysLate(self, card) -> Any: def _daysLate(self, card: Card) -> Any:
"Number of days later than scheduled." "Number of days later than scheduled."
due = card.odue if card.odid else card.due due = card.odue if card.odid else card.due
return max(0, self.today - due) return max(0, self.today - due)
def _updateRevIvl(self, card, ease) -> None: def _updateRevIvl(self, card: Card, ease: int) -> None:
card.ivl = self._nextRevIvl(card, ease, fuzz=True) card.ivl = self._nextRevIvl(card, ease, fuzz=True)
def _updateEarlyRevIvl(self, card, ease) -> None: def _updateEarlyRevIvl(self, card: Card, ease: int) -> None:
card.ivl = self._earlyReviewIvl(card, ease) card.ivl = self._earlyReviewIvl(card, ease)
# next interval for card when answered early+correctly # next interval for card when answered early+correctly
def _earlyReviewIvl(self, card, ease) -> int: def _earlyReviewIvl(self, card: Card, ease: int) -> int:
assert card.odid and card.type == 2 assert card.odid and card.type == 2
assert card.factor assert card.factor
assert ease > 1 assert ease > 1
@ -998,7 +1000,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
# Dynamic deck handling # Dynamic deck handling
########################################################################## ##########################################################################
def rebuildDyn(self, did=None) -> Optional[int]: def rebuildDyn(self, did: Optional[int] = None) -> Optional[int]:
"Rebuild a dynamic deck." "Rebuild a dynamic deck."
did = did or self.col.decks.selected() did = did or self.col.decks.selected()
deck = self.col.decks.get(did) deck = self.col.decks.get(did)
@ -1012,7 +1014,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
self.col.decks.select(did) self.col.decks.select(did)
return cnt return cnt
def _fillDyn(self, deck) -> int: def _fillDyn(self, deck: Dict[str, Any]) -> int:
start = -100000 start = -100000
total = 0 total = 0
for search, limit, order in deck['terms']: for search, limit, order in deck['terms']:
@ -1030,7 +1032,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
total += len(ids) total += len(ids)
return total return total
def emptyDyn(self, did, lim=None) -> None: def emptyDyn(self, did: Optional[int], lim: Optional[str] = None) -> None:
if not lim: if not lim:
lim = "did = %s" % did lim = "did = %s" % did
self.col.log(self.col.db.list("select id from cards where %s" % lim)) self.col.log(self.col.db.list("select id from cards where %s" % lim))
@ -1041,10 +1043,10 @@ due = (case when odue>0 then odue else due end), odue = 0, odid = 0, usn = ? whe
self._restoreQueueSnippet, lim), self._restoreQueueSnippet, lim),
self.col.usn()) self.col.usn())
def remFromDyn(self, cids) -> None: def remFromDyn(self, cids: List[int]) -> None:
self.emptyDyn(None, "id in %s and odid" % ids2str(cids)) self.emptyDyn(None, "id in %s and odid" % ids2str(cids))
def _dynOrder(self, o, l) -> str: def _dynOrder(self, o: int, l: int) -> str:
if o == DYN_OLDEST: if o == DYN_OLDEST:
t = "(select max(id) from revlog where cid=c.id)" t = "(select max(id) from revlog where cid=c.id)"
elif o == DYN_RANDOM: elif o == DYN_RANDOM:
@ -1066,7 +1068,7 @@ due = (case when odue>0 then odue else due end), odue = 0, odid = 0, usn = ? whe
t = "c.due, c.ord" t = "c.due, c.ord"
return t + " limit %d" % l return t + " limit %d" % l
def _moveToDyn(self, did, ids, start=-100000) -> None: def _moveToDyn(self, did: int, ids: List[int], start: int = -100000) -> None:
deck = self.col.decks.get(did) deck = self.col.decks.get(did)
data = [] data = []
u = self.col.usn() u = self.col.usn()
@ -1090,13 +1092,13 @@ where id = ?
""" % queue """ % queue
self.col.db.executemany(query, data) self.col.db.executemany(query, data)
def _removeFromFiltered(self, card) -> None: def _removeFromFiltered(self, card: Card) -> None:
if card.odid: if card.odid:
card.did = card.odid card.did = card.odid
card.odue = 0 card.odue = 0
card.odid = 0 card.odid = 0
def _restorePreviewCard(self, card) -> None: def _restorePreviewCard(self, card: Card) -> None:
assert card.odid assert card.odid
card.due = card.odue card.due = card.odue
@ -1114,7 +1116,7 @@ where id = ?
# Leeches # Leeches
########################################################################## ##########################################################################
def _checkLeech(self, card, conf) -> Optional[bool]: def _checkLeech(self, card: Card, conf: Dict[str, Any]) -> Optional[bool]:
"Leech handler. True if card was a leech." "Leech handler. True if card was a leech."
lf = conf['leechFails'] lf = conf['leechFails']
if not lf: if not lf:
@ -1137,10 +1139,10 @@ where id = ?
# Tools # Tools
########################################################################## ##########################################################################
def _cardConf(self, card) -> Any: def _cardConf(self, card: Card) -> Any:
return self.col.decks.confForDid(card.did) return self.col.decks.confForDid(card.did)
def _newConf(self, card) -> Any: def _newConf(self, card: Card) -> Any:
conf = self._cardConf(card) conf = self._cardConf(card)
# normal deck # normal deck
if not card.odid: if not card.odid:
@ -1159,7 +1161,7 @@ where id = ?
perDay=self.reportLimit perDay=self.reportLimit
) )
def _lapseConf(self, card) -> Any: def _lapseConf(self, card: Card) -> Any:
conf = self._cardConf(card) conf = self._cardConf(card)
# normal deck # normal deck
if not card.odid: if not card.odid:
@ -1177,7 +1179,7 @@ where id = ?
resched=conf['resched'], resched=conf['resched'],
) )
def _revConf(self, card) -> Any: def _revConf(self, card: Card) -> Any:
conf = self._cardConf(card) conf = self._cardConf(card)
# normal deck # normal deck
if not card.odid: if not card.odid:
@ -1188,11 +1190,11 @@ where id = ?
def _deckLimit(self) -> str: def _deckLimit(self) -> str:
return ids2str(self.col.decks.active()) return ids2str(self.col.decks.active())
def _previewingCard(self, card) -> Any: def _previewingCard(self, card: Card) -> Any:
conf = self._cardConf(card) conf = self._cardConf(card)
return conf['dyn'] and not conf['resched'] return conf['dyn'] and not conf['resched']
def _previewDelay(self, card) -> Any: def _previewDelay(self, card: Card) -> Any:
return self._cardConf(card).get("previewDelay", 10)*60 return self._cardConf(card).get("previewDelay", 10)*60
# Daily cutoff # Daily cutoff
@ -1310,7 +1312,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
# Next time reports # Next time reports
########################################################################## ##########################################################################
def nextIvlStr(self, card, ease, short=False) -> Any: def nextIvlStr(self, card: Card, ease: int, short: bool = False) -> Any:
"Return the next interval for CARD as a string." "Return the next interval for CARD as a string."
ivl = self.nextIvl(card, ease) ivl = self.nextIvl(card, ease)
if not ivl: if not ivl:
@ -1320,7 +1322,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
s = "<"+s s = "<"+s
return s return s
def nextIvl(self, card, ease) -> Any: def nextIvl(self, card: Card, ease: int) -> Any:
"Return the next interval for CARD, in seconds." "Return the next interval for CARD, in seconds."
# preview mode? # preview mode?
if self._previewingCard(card): if self._previewingCard(card):
@ -1346,7 +1348,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
return self._nextRevIvl(card, ease, fuzz=False)*86400 return self._nextRevIvl(card, ease, fuzz=False)*86400
# this isn't easily extracted from the learn code # this isn't easily extracted from the learn code
def _nextLrnIvl(self, card, ease) -> Any: def _nextLrnIvl(self, card: Card, ease: int) -> Any:
if card.queue == 0: if card.queue == 0:
card.left = self._startingLeft(card) card.left = self._startingLeft(card)
conf = self._lrnConf(card) conf = self._lrnConf(card)
@ -1378,14 +1380,14 @@ else
end) end)
""" """
def suspendCards(self, ids) -> None: def suspendCards(self, ids: List[int]) -> None:
"Suspend cards." "Suspend cards."
self.col.log(ids) self.col.log(ids)
self.col.db.execute( self.col.db.execute(
"update cards set queue=-1,mod=?,usn=? where id in "+ "update cards set queue=-1,mod=?,usn=? where id in "+
ids2str(ids), intTime(), self.col.usn()) ids2str(ids), intTime(), self.col.usn())
def unsuspendCards(self, ids) -> None: def unsuspendCards(self, ids: List[int]) -> None:
"Unsuspend cards." "Unsuspend cards."
self.col.log(ids) self.col.log(ids)
self.col.db.execute( self.col.db.execute(
@ -1393,7 +1395,7 @@ end)
"where queue = -1 and id in %s") % (self._restoreQueueSnippet, ids2str(ids)), "where queue = -1 and id in %s") % (self._restoreQueueSnippet, ids2str(ids)),
intTime(), self.col.usn()) intTime(), self.col.usn())
def buryCards(self, cids, manual=True) -> None: def buryCards(self, cids: List[int], manual: bool = True) -> None:
queue = manual and -3 or -2 queue = manual and -3 or -2
self.col.log(cids) self.col.log(cids)
self.col.db.execute(""" self.col.db.execute("""
@ -1413,7 +1415,7 @@ update cards set queue=?,mod=?,usn=? where id in """+ids2str(cids),
self.col.db.execute( self.col.db.execute(
"update cards set %s where queue in (-2, -3)" % self._restoreQueueSnippet) "update cards set %s where queue in (-2, -3)" % self._restoreQueueSnippet)
def unburyCardsForDeck(self, type="all") -> None: def unburyCardsForDeck(self, type: str = "all") -> None:
if type == "all": if type == "all":
queue = "queue in (-2, -3)" queue = "queue in (-2, -3)"
elif type == "manual": elif type == "manual":
@ -1434,7 +1436,7 @@ update cards set queue=?,mod=?,usn=? where id in """+ids2str(cids),
# Sibling spacing # Sibling spacing
########################################################################## ##########################################################################
def _burySiblings(self, card) -> None: def _burySiblings(self, card: Card) -> None:
toBury = [] toBury = []
nconf = self._newConf(card) nconf = self._newConf(card)
buryNew = nconf.get("bury", True) buryNew = nconf.get("bury", True)
@ -1468,7 +1470,7 @@ and (queue=0 or (queue=2 and due<=?))""",
# Resetting # Resetting
########################################################################## ##########################################################################
def forgetCards(self, ids) -> None: def forgetCards(self, ids: List[int]) -> None:
"Put cards at the end of the new queue." "Put cards at the end of the new queue."
self.remFromDyn(ids) self.remFromDyn(ids)
self.col.db.execute( self.col.db.execute(
@ -1480,7 +1482,7 @@ and (queue=0 or (queue=2 and due<=?))""",
self.sortCards(ids, start=pmax+1) self.sortCards(ids, start=pmax+1)
self.col.log(ids) self.col.log(ids)
def reschedCards(self, ids, imin, imax) -> None: def reschedCards(self, ids: List[int], imin: int, imax: int) -> None:
"Put cards in review queue with a new interval in days (min, max)." "Put cards in review queue with a new interval in days (min, max)."
d = [] d = []
t = self.today t = self.today
@ -1496,7 +1498,7 @@ usn=:usn,mod=:mod,factor=:fact where id=:id""",
d) d)
self.col.log(ids) self.col.log(ids)
def resetCards(self, ids) -> None: def resetCards(self, ids: List[int]) -> None:
"Completely reset cards for export." "Completely reset cards for export."
sids = ids2str(ids) sids = ids2str(ids)
# we want to avoid resetting due number of existing new cards on export # we want to avoid resetting due number of existing new cards on export
@ -1515,7 +1517,7 @@ usn=:usn,mod=:mod,factor=:fact where id=:id""",
# Repositioning new cards # Repositioning new cards
########################################################################## ##########################################################################
def sortCards(self, cids, start=1, step=1, shuffle=False, shift=False) -> None: def sortCards(self, cids: List[int], start: int = 1, step: int = 1, shuffle: bool = False, shift: bool = False) -> None:
scids = ids2str(cids) scids = ids2str(cids)
now = intTime() now = intTime()
nids = [] nids = []
@ -1555,11 +1557,11 @@ and due >= ? and queue = 0""" % scids, now, self.col.usn(), shiftby, low)
self.col.db.executemany( self.col.db.executemany(
"update cards set due=:due,mod=:now,usn=:usn where id = :cid", d) "update cards set due=:due,mod=:now,usn=:usn where id = :cid", d)
def randomizeCards(self, did) -> None: def randomizeCards(self, did: int) -> None:
cids = self.col.db.list("select id from cards where did = ?", did) cids = self.col.db.list("select id from cards where did = ?", did)
self.sortCards(cids, shuffle=True) self.sortCards(cids, shuffle=True)
def orderCards(self, did) -> None: def orderCards(self, did: int) -> None:
cids = self.col.db.list("select id from cards where did = ? order by id", did) cids = self.col.db.list("select id from cards where did = ? order by id", did)
self.sortCards(cids) self.sortCards(cids)
@ -1571,7 +1573,7 @@ and due >= ? and queue = 0""" % scids, now, self.col.usn(), shiftby, low)
self.orderCards(did) self.orderCards(did)
# for post-import # for post-import
def maybeRandomizeDeck(self, did=None) -> None: def maybeRandomizeDeck(self, did: Optional[int] = None) -> None:
if not did: if not did:
did = self.col.decks.selected() did = self.col.decks.selected()
conf = self.col.decks.confForDid(did) conf = self.col.decks.confForDid(did)
@ -1594,7 +1596,7 @@ else type end),
due = odue, odue = 0, odid = 0, usn = ? where odid != 0""", due = odue, odue = 0, odid = 0, usn = ? where odid != 0""",
self.col.usn()) self.col.usn())
def _removeAllFromLearning(self, schedVer=2) -> None: def _removeAllFromLearning(self, schedVer: int = 2) -> None:
# remove review cards from relearning # remove review cards from relearning
if schedVer == 1: if schedVer == 1:
self.col.db.execute(""" self.col.db.execute("""
@ -1630,7 +1632,7 @@ where queue < 0""" % (intTime(), self.col.usn()))
# adding 'hard' in v2 scheduler means old ease entries need shifting # adding 'hard' in v2 scheduler means old ease entries need shifting
# up or down # up or down
def _remapLearningAnswers(self, sql) -> None: def _remapLearningAnswers(self, sql: str) -> None:
self.col.db.execute("update revlog set %s and type in (0,2)" % sql) self.col.db.execute("update revlog set %s and type in (0,2)" % sql)
def moveToV1(self) -> None: def moveToV1(self) -> None: