Merge pull request #467 from alanhdu/monkeytype

Add some type annotations [1/n]
This commit is contained in:
Damien Elmes 2020-02-27 17:25:50 +10:00 committed by GitHub
commit 244326c130
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 82 additions and 54 deletions

View file

@ -5,7 +5,7 @@ import os
import time import time
from sqlite3 import Cursor from sqlite3 import Cursor
from sqlite3 import dbapi2 as sqlite from sqlite3 import dbapi2 as sqlite
from typing import Any, List from typing import Any, List, Type
DBError = sqlite.Error DBError = sqlite.Error
@ -110,5 +110,5 @@ class DB:
def _textFactory(self, data: bytes) -> str: def _textFactory(self, data: bytes) -> str:
return str(data, errors="ignore") return str(data, errors="ignore")
def cursor(self, factory=Cursor) -> Cursor: def cursor(self, factory: Type[Cursor] = Cursor) -> Cursor:
return self._db.cursor(factory) return self._db.cursor(factory)

View file

@ -446,7 +446,7 @@ class DeckManager:
return deck["name"] return deck["name"]
return _("[no deck]") return _("[no deck]")
def nameOrNone(self, did) -> Any: def nameOrNone(self, did: int) -> Any:
deck = self.get(did, default=False) deck = self.get(did, default=False)
if deck: if deck:
return deck["name"] return deck["name"]
@ -530,7 +530,7 @@ class DeckManager:
self, self,
force_default: bool = True, force_default: bool = True,
assume_no_child: bool = False, assume_no_child: bool = False,
default_deck=None, default_deck: Optional[Dict[str, Any]] = None,
) -> bool: ) -> bool:
"""Whether the default deck should appear in main window, browser side list, filter, deck selection... """Whether the default deck should appear in main window, browser side list, filter, deck selection...

View file

@ -18,7 +18,7 @@ class AnkiError(Exception):
class DeckRenameError(Exception): class DeckRenameError(Exception):
def __init__(self, description) -> None: def __init__(self, description: str) -> None:
super().__init__() super().__init__()
self.description = description self.description = description

View file

@ -33,14 +33,16 @@ if isMac:
os.environ["PATH"] += ":/usr/texbin:/Library/TeX/texbin" os.environ["PATH"] += ":/usr/texbin:/Library/TeX/texbin"
def on_card_did_render(output: TemplateRenderOutput, ctx: TemplateRenderContext): def on_card_did_render(
output: TemplateRenderOutput, ctx: TemplateRenderContext
) -> None:
output.question_text = render_latex( output.question_text = render_latex(
output.question_text, ctx.note_type(), ctx.col() output.question_text, ctx.note_type(), ctx.col()
) )
output.answer_text = render_latex(output.answer_text, ctx.note_type(), ctx.col()) output.answer_text = render_latex(output.answer_text, ctx.note_type(), ctx.col())
def render_latex(html: str, model: NoteType, col: anki.storage._Collection,) -> str: def render_latex(html: str, model: NoteType, col: anki.storage._Collection) -> str:
"Convert embedded latex tags in text to image links." "Convert embedded latex tags in text to image links."
html, err = render_latex_returning_errors(html, model, col) html, err = render_latex_returning_errors(html, model, col)
if err: if err:
@ -49,7 +51,10 @@ def render_latex(html: str, model: NoteType, col: anki.storage._Collection,) ->
def render_latex_returning_errors( def render_latex_returning_errors(
html: str, model: NoteType, col: anki.storage._Collection, expand_clozes=False html: str,
model: NoteType,
col: anki.storage._Collection,
expand_clozes: bool = False,
) -> Tuple[str, List[str]]: ) -> Tuple[str, List[str]]:
"""Returns (text, errors). """Returns (text, errors).

View file

@ -63,7 +63,7 @@ class Note:
self.mid, self.mid,
self.mod, self.mod,
self.usn, self.usn,
self.tags, tags,
fields, fields,
self.flags, self.flags,
self.data, self.data,
@ -74,7 +74,7 @@ from notes where id = ?""",
self.id, self.id,
) )
self.fields = splitFields(fields) self.fields = splitFields(fields)
self.tags = self.col.tags.split(self.tags) self.tags = self.col.tags.split(tags)
self._model = self.col.models.get(self.mid) self._model = self.col.models.get(self.mid)
self._fmap = self.col.models.fieldMap(self._model) self._fmap = self.col.models.fieldMap(self._model)
self.scm = self.col.scm self.scm = self.col.scm

View file

@ -6,7 +6,7 @@ from __future__ import annotations
import datetime import datetime
import json import json
import time import time
from typing import Any, Dict, List, Optional, Tuple from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
import anki import anki
from anki.consts import * from anki.consts import *
@ -17,6 +17,7 @@ from anki.utils import ids2str
# Card stats # Card stats
########################################################################## ##########################################################################
PERIOD_MONTH = 0 PERIOD_MONTH = 0
PERIOD_YEAR = 1 PERIOD_YEAR = 1
PERIOD_LIFE = 2 PERIOD_LIFE = 2
@ -71,15 +72,15 @@ class CardStats:
self.txt += "</table>" self.txt += "</table>"
return self.txt return self.txt
def addLine(self, k, v) -> None: def addLine(self, k: str, v: Union[int, str]) -> None:
self.txt += self.makeLine(k, v) self.txt += self.makeLine(k, v)
def makeLine(self, k, v) -> str: def makeLine(self, k: str, v: Union[str, int]) -> str:
txt = "<tr><td align=left style='padding-right: 3px;'>" txt = "<tr><td align=left style='padding-right: 3px;'>"
txt += "<b>%s</b></td><td>%s</td></tr>" % (k, v) txt += "<b>%s</b></td><td>%s</td></tr>" % (k, v)
return txt return txt
def date(self, tm) -> str: def date(self, tm: float) -> str:
return time.strftime("%Y-%m-%d", time.localtime(tm)) return time.strftime("%Y-%m-%d", time.localtime(tm))
def time(self, tm: float) -> str: def time(self, tm: float) -> str:
@ -114,7 +115,7 @@ class CollectionStats:
self.wholeCollection = False self.wholeCollection = False
# assumes jquery & plot are available in document # assumes jquery & plot are available in document
def report(self, type=PERIOD_MONTH) -> str: def report(self, type: int = PERIOD_MONTH) -> str:
# 0=month, 1=year, 2=deck life # 0=month, 1=year, 2=deck life
self.type = type self.type = type
from .statsbg import bg from .statsbg import bg
@ -131,7 +132,7 @@ class CollectionStats:
txt += self._section(self.footer()) txt += self._section(self.footer())
return "<center>%s</center>" % txt return "<center>%s</center>" % txt
def _section(self, txt) -> str: def _section(self, txt: str) -> str:
return "<div class=section>%s</div>" % txt return "<div class=section>%s</div>" % txt
css = """ css = """
@ -212,7 +213,7 @@ from revlog where id > ? """
# Due and cumulative due # Due and cumulative due
###################################################################### ######################################################################
def get_start_end_chunk(self, by="review") -> Tuple[int, Optional[int], int]: def get_start_end_chunk(self, by: str = "review") -> Tuple[int, Optional[int], int]:
start = 0 start = 0
if self.type == PERIOD_MONTH: if self.type == PERIOD_MONTH:
end, chunk = 31, 1 end, chunk = 31, 1
@ -273,7 +274,7 @@ from revlog where id > ? """
txt += self._dueInfo(tot, len(totd) * chunk) txt += self._dueInfo(tot, len(totd) * chunk)
return txt return txt
def _dueInfo(self, tot, num) -> str: def _dueInfo(self, tot: int, num: int) -> str:
i: List[str] = [] i: List[str] = []
self._line( self._line(
i, _("Total"), self.col.tr(TR.STATISTICS_REVIEWS, reviews=tot), i, _("Total"), self.col.tr(TR.STATISTICS_REVIEWS, reviews=tot),
@ -290,7 +291,9 @@ and due = ?"""
self._line(i, _("Due tomorrow"), tomorrow) self._line(i, _("Due tomorrow"), tomorrow)
return self._lineTbl(i) return self._lineTbl(i)
def _due(self, start=None, end=None, chunk=1) -> Any: def _due(
self, start: Optional[int] = None, end: Optional[int] = None, chunk: int = 1
) -> Any:
lim = "" lim = ""
if start is not None: if start is not None:
lim += " and due-:today >= %d" % start lim += " and due-:today >= %d" % start
@ -414,7 +417,13 @@ group by day order by day"""
return self._section(txt1) + self._section(txt2) return self._section(txt1) + self._section(txt2)
def _ansInfo( def _ansInfo(
self, totd, studied, first, unit, convHours=False, total=None self,
totd: List[Tuple[int, float]],
studied: int,
first: int,
unit: str,
convHours: bool = False,
total: Optional[int] = None,
) -> Tuple[str, int]: ) -> Tuple[str, int]:
assert totd assert totd
tot = totd[-1][1] tot = totd[-1][1]
@ -460,14 +469,16 @@ group by day order by day"""
) )
return self._lineTbl(i), int(tot) return self._lineTbl(i), int(tot)
def _splitRepData(self, data, spec) -> Tuple[List[dict], List[Tuple[Any, Any]]]: def _splitRepData(
self, data: List[Tuple[Any, ...]], spec: Sequence[Tuple[int, str, str]],
) -> Tuple[List[Dict[str, Any]], List[Tuple[Any, Any]]]:
sep: Dict[int, Any] = {} sep: Dict[int, Any] = {}
totcnt = {} totcnt = {}
totd: Dict[int, Any] = {} totd: Dict[int, Any] = {}
alltot = [] alltot = []
allcnt = 0 allcnt: float = 0
for (n, col, lab) in spec: for (n, col, lab) in spec:
totcnt[n] = 0 totcnt[n] = 0.0
totd[n] = [] totd[n] = []
for row in data: for row in data:
for (n, col, lab) in spec: for (n, col, lab) in spec:
@ -497,7 +508,7 @@ group by day order by day"""
) )
return (ret, alltot) return (ret, alltot)
def _added(self, num=7, chunk=1) -> Any: def _added(self, num: Optional[int] = 7, chunk: int = 1) -> Any:
lims = [] lims = []
if num is not None: if num is not None:
lims.append( lims.append(
@ -525,7 +536,7 @@ group by day order by day"""
chunk=chunk, chunk=chunk,
) )
def _done(self, num=7, chunk=1) -> Any: def _done(self, num: Optional[int] = 7, chunk: int = 1) -> Any:
lims = [] lims = []
if num is not None: if num is not None:
lims.append( lims.append(
@ -637,7 +648,7 @@ group by day order by day)"""
) )
return txt + self._lineTbl(i) return txt + self._lineTbl(i)
def _ivls(self) -> Tuple[list, int]: def _ivls(self) -> Tuple[List[Any], int]:
start, end, chunk = self.get_start_end_chunk() start, end, chunk = self.get_start_end_chunk()
lim = "and grp <= %d" % end if end else "" lim = "and grp <= %d" % end if end else ""
data = [ data = [
@ -712,7 +723,7 @@ select count(), avg(ivl), max(ivl) from cards where did in %s and queue = {QUEUE
txt += self._easeInfo(eases) txt += self._easeInfo(eases)
return txt return txt
def _easeInfo(self, eases) -> str: def _easeInfo(self, eases: List[Tuple[int, int, int]]) -> str:
types = {PERIOD_MONTH: [0, 0], PERIOD_YEAR: [0, 0], PERIOD_LIFE: [0, 0]} types = {PERIOD_MONTH: [0, 0], PERIOD_YEAR: [0, 0], PERIOD_LIFE: [0, 0]}
for (type, ease, cnt) in eases: for (type, ease, cnt) in eases:
if ease == 1: if ease == 1:
@ -909,7 +920,9 @@ when you answer "good" on a review."""
) )
return txt return txt
def _line(self, i, a, b, bold=True) -> None: def _line(
self, i: List[str], a: str, b: Union[int, str], bold: bool = True
) -> None:
# T: Symbols separating first and second column in a statistics table. Eg in "Total: 3 reviews". # T: Symbols separating first and second column in a statistics table. Eg in "Total: 3 reviews".
colon = _(":") colon = _(":")
if bold: if bold:
@ -923,7 +936,7 @@ when you answer "good" on a review."""
% (a, colon, b) % (a, colon, b)
) )
def _lineTbl(self, i) -> str: def _lineTbl(self, i: List[str]) -> str:
return "<table width=400>" + "".join(i) + "</table>" return "<table width=400>" + "".join(i) + "</table>"
def _factors(self) -> Any: def _factors(self) -> Any:
@ -969,7 +982,14 @@ from cards where did in %s"""
###################################################################### ######################################################################
def _graph( def _graph(
self, id, data, conf=None, type="bars", xunit=1, ylabel=_("Cards"), ylabel2="" self,
id: str,
data: Any,
conf: Optional[Any] = None,
type: str = "bars",
xunit: int = 1,
ylabel: str = _("Cards"),
ylabel2: str = "",
) -> str: ) -> str:
if conf is None: if conf is None:
conf = {} conf = {}
@ -1088,10 +1108,10 @@ $(function () {
self.col.decks.active() self.col.decks.active()
) )
def _title(self, title, subtitle="") -> str: def _title(self, title: str, subtitle: str = "") -> str:
return "<h1>%s</h1>%s" % (title, subtitle) return "<h1>%s</h1>%s" % (title, subtitle)
def _deckAge(self, by) -> int: def _deckAge(self, by: str) -> int:
lim = self._revlogLimit() lim = self._revlogLimit()
if lim: if lim:
lim = " where " + lim lim = " where " + lim
@ -1112,7 +1132,7 @@ $(function () {
return None return None
return end * chunk return end * chunk
def _avgDay(self, tot, num, unit) -> str: def _avgDay(self, tot: float, num: int, unit: str) -> str:
vals = [] vals = []
try: try:
vals.append(_("%(a)0.1f %(b)s/day") % dict(a=tot / float(num), b=unit)) vals.append(_("%(a)0.1f %(b)s/day") % dict(a=tot / float(num), b=unit))

View file

@ -1,8 +1,9 @@
# Copyright: Ankitects Pty Ltd and contributors # Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from typing import Any, Callable, List, Tuple, Union from typing import Any, Callable, List, Optional, Tuple, Union
from anki.collection import _Collection
from anki.consts import MODEL_CLOZE from anki.consts import MODEL_CLOZE
from anki.lang import _ from anki.lang import _
from anki.models import NoteType from anki.models import NoteType
@ -13,7 +14,7 @@ models: List[Tuple[Union[Callable[[], str], str], Callable[[Any], NoteType]]] =
########################################################################## ##########################################################################
def _newBasicModel(col, name=None) -> NoteType: def _newBasicModel(col: _Collection, name: Optional[str] = None) -> NoteType:
mm = col.models mm = col.models
m = mm.new(name or _("Basic")) m = mm.new(name or _("Basic"))
fm = mm.newField(_("Front")) fm = mm.newField(_("Front"))
@ -27,7 +28,7 @@ def _newBasicModel(col, name=None) -> NoteType:
return m return m
def addBasicModel(col) -> NoteType: def addBasicModel(col: _Collection) -> NoteType:
m = _newBasicModel(col) m = _newBasicModel(col)
col.models.add(m) col.models.add(m)
return m return m
@ -39,7 +40,7 @@ models.append((lambda: _("Basic"), addBasicModel))
########################################################################## ##########################################################################
def addBasicTypingModel(col) -> NoteType: def addBasicTypingModel(col: _Collection) -> NoteType:
mm = col.models mm = col.models
m = _newBasicModel(col, _("Basic (type in the answer)")) m = _newBasicModel(col, _("Basic (type in the answer)"))
t = m["tmpls"][0] t = m["tmpls"][0]
@ -55,7 +56,7 @@ models.append((lambda: _("Basic (type in the answer)"), addBasicTypingModel))
########################################################################## ##########################################################################
def _newForwardReverse(col, name=None) -> NoteType: def _newForwardReverse(col: _Collection, name: Optional[str] = None) -> NoteType:
mm = col.models mm = col.models
m = _newBasicModel(col, name or _("Basic (and reversed card)")) m = _newBasicModel(col, name or _("Basic (and reversed card)"))
t = mm.newTemplate(_("Card 2")) t = mm.newTemplate(_("Card 2"))
@ -65,7 +66,7 @@ def _newForwardReverse(col, name=None) -> NoteType:
return m return m
def addForwardReverse(col) -> NoteType: def addForwardReverse(col: _Collection) -> NoteType:
m = _newForwardReverse(col) m = _newForwardReverse(col)
col.models.add(m) col.models.add(m)
return m return m
@ -77,7 +78,7 @@ models.append((lambda: _("Basic (and reversed card)"), addForwardReverse))
########################################################################## ##########################################################################
def addForwardOptionalReverse(col) -> NoteType: def addForwardOptionalReverse(col: _Collection) -> NoteType:
mm = col.models mm = col.models
m = _newForwardReverse(col, _("Basic (optional reversed card)")) m = _newForwardReverse(col, _("Basic (optional reversed card)"))
av = _("Add Reverse") av = _("Add Reverse")
@ -95,7 +96,7 @@ models.append((lambda: _("Basic (optional reversed card)"), addForwardOptionalRe
########################################################################## ##########################################################################
def addClozeModel(col) -> NoteType: def addClozeModel(col: _Collection) -> NoteType:
mm = col.models mm = col.models
m = mm.new(_("Cloze")) m = mm.new(_("Cloze"))
m["type"] = MODEL_CLOZE m["type"] = MODEL_CLOZE

View file

@ -13,7 +13,7 @@ from __future__ import annotations
import json import json
import re import re
from typing import Callable, Dict, List, Tuple from typing import Callable, Collection, Dict, List, Optional, Tuple
import anki # pylint: disable=unused-import import anki # pylint: disable=unused-import
from anki import hooks from anki import hooks
@ -29,7 +29,7 @@ class TagManager:
self.col = col self.col = col
self.tags: Dict[str, int] = {} self.tags: Dict[str, int] = {}
def load(self, json_) -> None: def load(self, json_: str) -> None:
self.tags = json.loads(json_) self.tags = json.loads(json_)
self.changed = False self.changed = False
@ -41,7 +41,7 @@ class TagManager:
# Registering and fetching tags # Registering and fetching tags
############################################################# #############################################################
def register(self, tags, usn=None) -> None: def register(self, tags: Collection[str], usn: Optional[int] = None) -> None:
"Given a list of tags, add any missing ones to tag registry." "Given a list of tags, add any missing ones to tag registry."
found = False found = False
for t in tags: for t in tags:
@ -55,7 +55,7 @@ class TagManager:
def all(self) -> List: def all(self) -> List:
return list(self.tags.keys()) return list(self.tags.keys())
def registerNotes(self, nids=None) -> None: def registerNotes(self, nids: Optional[List[int]] = None) -> None:
"Add any missing tags from notes to the tags list." "Add any missing tags from notes to the tags list."
# when called without an argument, the old list is cleared first. # when called without an argument, the old list is cleared first.
if nids: if nids:
@ -94,7 +94,7 @@ class TagManager:
# Bulk addition/removal from notes # Bulk addition/removal from notes
############################################################# #############################################################
def bulkAdd(self, ids, tags, add=True) -> None: def bulkAdd(self, ids: List[int], tags: str, add: bool = True) -> None:
"Add tags in bulk. TAGS is space-separated." "Add tags in bulk. TAGS is space-separated."
newTags = self.split(tags) newTags = self.split(tags)
if not newTags: if not newTags:
@ -137,23 +137,23 @@ class TagManager:
[fix(row) for row in res], [fix(row) for row in res],
) )
def bulkRem(self, ids, tags) -> None: def bulkRem(self, ids: List[int], tags: str) -> None:
self.bulkAdd(ids, tags, False) self.bulkAdd(ids, tags, False)
# String-based utilities # String-based utilities
########################################################################## ##########################################################################
def split(self, tags) -> List[str]: def split(self, tags: str) -> List[str]:
"Parse a string and return a list of tags." "Parse a string and return a list of tags."
return [t for t in tags.replace("\u3000", " ").split(" ") if t] return [t for t in tags.replace("\u3000", " ").split(" ") if t]
def join(self, tags) -> str: def join(self, tags: List[str]) -> str:
"Join tags into a single string, with leading and trailing spaces." "Join tags into a single string, with leading and trailing spaces."
if not tags: if not tags:
return "" return ""
return " %s " % " ".join(tags) return " %s " % " ".join(tags)
def addToStr(self, addtags, tags) -> str: def addToStr(self, addtags: str, tags: str) -> str:
"Add tags if they don't exist, and canonify." "Add tags if they don't exist, and canonify."
currentTags = self.split(tags) currentTags = self.split(tags)
for tag in self.split(addtags): for tag in self.split(addtags):
@ -161,7 +161,7 @@ class TagManager:
currentTags.append(tag) currentTags.append(tag)
return self.join(self.canonify(currentTags)) return self.join(self.canonify(currentTags))
def remFromStr(self, deltags, tags) -> str: def remFromStr(self, deltags: str, tags: str) -> str:
"Delete tags if they exist." "Delete tags if they exist."
def wildcard(pat, str): def wildcard(pat, str):
@ -183,7 +183,7 @@ class TagManager:
# List-based utilities # List-based utilities
########################################################################## ##########################################################################
def canonify(self, tagList) -> List[str]: def canonify(self, tagList: List[str]) -> List[str]:
"Strip duplicates, adjust case to match existing tags, and sort." "Strip duplicates, adjust case to match existing tags, and sort."
strippedTags = [] strippedTags = []
for t in tagList: for t in tagList:
@ -194,7 +194,7 @@ class TagManager:
strippedTags.append(s) strippedTags.append(s)
return sorted(set(strippedTags)) return sorted(set(strippedTags))
def inList(self, tag, tags) -> bool: def inList(self, tag: str, tags: List[str]) -> bool:
"True if TAG is in TAGS. Ignore case." "True if TAG is in TAGS. Ignore case."
return tag.lower() in [t.lower() for t in tags] return tag.lower() in [t.lower() for t in tags]

View file

@ -143,7 +143,9 @@ def templates_for_card(card: Card, browser: bool) -> Tuple[str, str]:
return q, a # type: ignore return q, a # type: ignore
def fields_for_rendering(col: anki.storage._Collection, card: Card, note: Note): def fields_for_rendering(
col: anki.storage._Collection, card: Card, note: Note
) -> Dict[str, str]:
# fields from note # fields from note
fields = dict(note.items()) fields = dict(note.items())