Merge pull request #369 from agentydragon/typecheck-models

Add types for models, templates and field dicts
This commit is contained in:
Damien Elmes 2019-12-22 08:19:40 +10:00 committed by GitHub
commit 079a00653e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 79 additions and 62 deletions

View file

@ -33,6 +33,7 @@ from anki.sched import Scheduler as V1Scheduler
from anki.schedv2 import Scheduler as V2Scheduler from anki.schedv2 import Scheduler as V2Scheduler
from anki.sound import stripSounds from anki.sound import stripSounds
from anki.tags import TagManager from anki.tags import TagManager
from anki.types import Model, Template
from anki.utils import (devMode, fieldChecksum, ids2str, intTime, joinFields, from anki.utils import (devMode, fieldChecksum, ids2str, intTime, joinFields,
maxID, splitFields, stripHTMLMedia) maxID, splitFields, stripHTMLMedia)
@ -354,7 +355,7 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
avail = self.models.availOrds(model, joinFields(note.fields)) avail = self.models.availOrds(model, joinFields(note.fields))
return self._tmplsFromOrds(model, avail) return self._tmplsFromOrds(model, avail)
def _tmplsFromOrds(self, model: Dict[str, Any], avail: List[int]) -> List: def _tmplsFromOrds(self, model: Model, avail: List[int]) -> List:
ok = [] ok = []
if model['type'] == MODEL_STD: if model['type'] == MODEL_STD:
for t in model['tmpls']: for t in model['tmpls']:
@ -456,7 +457,7 @@ insert into cards values (?,?,?,?,?,?,0,0,?,0,0,0,0,0,0,0,0,"")""",
cards.append(self._newCard(note, template, 1, flush=False, did=did)) cards.append(self._newCard(note, template, 1, flush=False, did=did))
return cards return cards
def _newCard(self, note: Note, template: Dict[str, Any], due: int, flush: bool = True, did: None = None) -> anki.cards.Card: def _newCard(self, note: Note, template: Template, due: int, flush: bool = True, did: None = None) -> anki.cards.Card:
"Create a new card." "Create a new card."
card = anki.cards.Card(self) card = anki.cards.Card(self)
card.nid = note.id card.nid = note.id
@ -465,7 +466,7 @@ insert into cards values (?,?,?,?,?,?,0,0,?,0,0,0,0,0,0,0,0,"")""",
# Use template did (deck override) if valid, otherwise did in argument, otherwise model did # Use template did (deck override) if valid, otherwise did in argument, otherwise model did
if not card.did: if not card.did:
if template['did'] and str(template['did']) in self.decks.decks: if template['did'] and str(template['did']) in self.decks.decks:
card.did = template['did'] card.did = int(template['did'])
elif did: elif did:
card.did = did card.did = did
else: else:

View file

@ -10,6 +10,7 @@ from typing import Any, Dict, List, Optional, Union
from anki.hooks import addHook from anki.hooks import addHook
from anki.lang import _ from anki.lang import _
from anki.types import Model
from anki.utils import call, checksum, isMac, namedtmp, stripHTML, tmpdir from anki.utils import call, checksum, isMac, namedtmp, stripHTML, tmpdir
pngCommands = [ pngCommands = [
@ -42,7 +43,8 @@ def stripLatex(text) -> Any:
text = text.replace(match.group(), "") text = text.replace(match.group(), "")
return text return text
def mungeQA(html: str, type: Optional[str], fields: Optional[Dict[str, str]], model: Dict[str, Any], data: Optional[List[Union[int, str]]], col) -> Any: def mungeQA(html: str, type: Optional[str], fields: Optional[Dict[str, str]],
model: Model, data: Optional[List[Union[int, str]]], col) -> Any:
"Convert TEXT with embedded latex tags to image links." "Convert TEXT with embedded latex tags to image links."
for match in regexps['standard'].finditer(html): for match in regexps['standard'].finditer(html):
html = html.replace(match.group(), _imgLink(col, match.group(1), model)) html = html.replace(match.group(), _imgLink(col, match.group(1), model))
@ -55,7 +57,7 @@ def mungeQA(html: str, type: Optional[str], fields: Optional[Dict[str, str]], mo
"\\begin{displaymath}" + match.group(1) + "\\end{displaymath}", model)) "\\begin{displaymath}" + match.group(1) + "\\end{displaymath}", model))
return html return html
def _imgLink(col, latex: str, model: Dict[str, Any]) -> Any: def _imgLink(col, latex: str, model: Model) -> str:
"Return an img link for LATEX, creating if necesssary." "Return an img link for LATEX, creating if necesssary."
txt = _latexFromHtml(col, latex) txt = _latexFromHtml(col, latex)
@ -80,13 +82,13 @@ def _imgLink(col, latex: str, model: Dict[str, Any]) -> Any:
else: else:
return link return link
def _latexFromHtml(col, latex: str) -> Any: def _latexFromHtml(col, latex: str) -> str:
"Convert entities and fix newlines." "Convert entities and fix newlines."
latex = re.sub("<br( /)?>|<div>", "\n", latex) latex = re.sub("<br( /)?>|<div>", "\n", latex)
latex = stripHTML(latex) latex = stripHTML(latex)
return latex return latex
def _buildImg(col, latex: str, fname: str, model: Dict[str, Any]) -> Any: def _buildImg(col, latex: str, fname: str, model: Model) -> Optional[str]:
# add header/footer # add header/footer
latex = (model["latexPre"] + "\n" + latex = (model["latexPre"] + "\n" +
latex + "\n" + latex + "\n" +
@ -129,7 +131,7 @@ package in the LaTeX header instead.""") % bad
return _errMsg(latexCmd[0], texpath) return _errMsg(latexCmd[0], texpath)
# add to media # add to media
shutil.copyfile(png, os.path.join(mdir, fname)) shutil.copyfile(png, os.path.join(mdir, fname))
return return None
finally: finally:
os.chdir(oldcwd) os.chdir(oldcwd)
log.close() log.close()

View file

@ -11,6 +11,7 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
from anki.consts import * from anki.consts import *
from anki.hooks import runHook from anki.hooks import runHook
from anki.lang import _ from anki.lang import _
from anki.types import Field, Model, Template
from anki.utils import checksum, ids2str, intTime, joinFields, splitFields from anki.utils import checksum, ids2str, intTime, joinFields, splitFields
# Models # Models
@ -18,7 +19,7 @@ from anki.utils import checksum, ids2str, intTime, joinFields, splitFields
# - careful not to add any lists/dicts/etc here, as they aren't deep copied # - careful not to add any lists/dicts/etc here, as they aren't deep copied
defaultModel = { defaultModel: Model = {
'sortf': 0, 'sortf': 0,
'did': 1, 'did': 1,
'latexPre': """\ 'latexPre': """\
@ -46,7 +47,7 @@ defaultModel = {
""" """
} }
defaultField: Dict[str, Any] = { defaultField: Field = {
'name': "", 'name': "",
'ord': None, 'ord': None,
'sticky': False, 'sticky': False,
@ -59,7 +60,7 @@ defaultField: Dict[str, Any] = {
'media': [], 'media': [],
} }
defaultTemplate = { defaultTemplate: Template = {
'name': "", 'name': "",
'ord': None, 'ord': None,
'qfmt': "", 'qfmt': "",
@ -73,7 +74,7 @@ defaultTemplate = {
} }
class ModelManager: class ModelManager:
models: Dict[str, Any] models: Dict[str, Model]
# Saving/loading registry # Saving/loading registry
############################################################# #############################################################
@ -88,7 +89,7 @@ class ModelManager:
self.changed = False self.changed = False
self.models = json.loads(json_) self.models = json.loads(json_)
def save(self, m: Optional[Dict[str, Any]] = None, templates: bool = False, updateReqs: bool = True) -> None: def save(self, m: Optional[Model] = None, templates: bool = False, updateReqs: bool = True) -> None:
"Mark M modified if provided, and schedule registry flush." "Mark M modified if provided, and schedule registry flush."
if m and m['id']: if m and m['id']:
m['mod'] = intTime() m['mod'] = intTime()
@ -125,7 +126,7 @@ class ModelManager:
m = self.get(self.col.conf['curModel']) m = self.get(self.col.conf['curModel'])
return m or list(self.models.values())[0] return m or list(self.models.values())[0]
def setCurrent(self, m: Dict[str, Any]) -> None: def setCurrent(self, m: Model) -> None:
self.col.conf['curModel'] = m['id'] self.col.conf['curModel'] = m['id']
self.col.setMod() self.col.setMod()
@ -148,7 +149,7 @@ class ModelManager:
if m['name'] == name: if m['name'] == name:
return m return m
def new(self, name: str) -> Dict[str, Any]: def new(self, name: str) -> Model:
"Create a new model, save it in the registry, and return it." "Create a new model, save it in the registry, and return it."
# caller should call save() after modifying # caller should call save() after modifying
m = defaultModel.copy() m = defaultModel.copy()
@ -160,7 +161,7 @@ class ModelManager:
m['id'] = None m['id'] = None
return m return m
def rem(self, m: Dict[str, Any]) -> None: def rem(self, m: Model) -> None:
"Delete model, and all its cards/notes." "Delete model, and all its cards/notes."
self.col.modSchema(check=True) self.col.modSchema(check=True)
current = self.current()['id'] == m['id'] current = self.current()['id'] == m['id']
@ -175,26 +176,26 @@ select id from cards where nid in (select id from notes where mid = ?)""",
if current: if current:
self.setCurrent(list(self.models.values())[0]) self.setCurrent(list(self.models.values())[0])
def add(self, m: Dict[str, Any]) -> None: def add(self, m: Model) -> None:
self._setID(m) self._setID(m)
self.update(m) self.update(m)
self.setCurrent(m) self.setCurrent(m)
self.save(m) self.save(m)
def ensureNameUnique(self, m: Dict[str, Any]) -> None: def ensureNameUnique(self, m: Model) -> None:
for mcur in self.all(): for mcur in self.all():
if (mcur['name'] == m['name'] and mcur['id'] != m['id']): if (mcur['name'] == m['name'] and mcur['id'] != m['id']):
m['name'] += "-" + checksum(str(time.time()))[:5] m['name'] += "-" + checksum(str(time.time()))[:5]
break break
def update(self, m: Dict[str, Any]) -> None: def update(self, m: Model) -> None:
"Add or update an existing model. Used for syncing and merging." "Add or update an existing model. Used for syncing and merging."
self.ensureNameUnique(m) self.ensureNameUnique(m)
self.models[str(m['id'])] = m self.models[str(m['id'])] = m
# mark registry changed, but don't bump mod time # mark registry changed, but don't bump mod time
self.save() self.save()
def _setID(self, m: Dict[str, Any]) -> None: def _setID(self, m: Model) -> None:
while 1: while 1:
id = str(intTime(1000)) id = str(intTime(1000))
if id not in self.models: if id not in self.models:
@ -210,17 +211,17 @@ select id from cards where nid in (select id from notes where mid = ?)""",
# Tools # Tools
################################################## ##################################################
def nids(self, m: Dict[str, Any]) -> Any: def nids(self, m: Model) -> Any:
"Note ids for M." "Note ids for M."
return self.col.db.list( return self.col.db.list(
"select id from notes where mid = ?", m['id']) "select id from notes where mid = ?", m['id'])
def useCount(self, m: Dict[str, Any]) -> Any: def useCount(self, m: Model) -> Any:
"Number of note using M." "Number of note using M."
return self.col.db.scalar( return self.col.db.scalar(
"select count() from notes where mid = ?", m['id']) "select count() from notes where mid = ?", m['id'])
def tmplUseCount(self, m, ord) -> Any: def tmplUseCount(self, m: Model, ord) -> Any:
return self.col.db.scalar(""" return self.col.db.scalar("""
select count() from cards, notes where cards.nid = notes.id select count() from cards, notes where cards.nid = notes.id
and notes.mid = ? and cards.ord = ?""", m['id'], ord) and notes.mid = ? and cards.ord = ?""", m['id'], ord)
@ -228,7 +229,7 @@ and notes.mid = ? and cards.ord = ?""", m['id'], ord)
# Copying # Copying
################################################## ##################################################
def copy(self, m: Dict[str, Any]) -> Any: def copy(self, m: Model) -> Any:
"Copy, save and return." "Copy, save and return."
m2 = copy.deepcopy(m) m2 = copy.deepcopy(m)
m2['name'] = _("%s copy") % m2['name'] m2['name'] = _("%s copy") % m2['name']
@ -238,30 +239,30 @@ and notes.mid = ? and cards.ord = ?""", m['id'], ord)
# Fields # Fields
################################################## ##################################################
def newField(self, name: str) -> Dict[str, Any]: def newField(self, name: str) -> Field:
assert(isinstance(name, str)) assert(isinstance(name, str))
f = defaultField.copy() f = defaultField.copy()
f['name'] = name f['name'] = name
return f return f
def fieldMap(self, m: Dict[str, Any]) -> Dict[Any, Tuple[Any, Any]]: def fieldMap(self, m: Model) -> Dict[str, Tuple[Any, Any]]:
"Mapping of field name -> (ord, field)." "Mapping of field name -> (ord, field)."
return dict((f['name'], (f['ord'], f)) for f in m['flds']) return dict((f['name'], (f['ord'], f)) for f in m['flds'])
def fieldNames(self, m) -> List: def fieldNames(self, m: Model) -> List[str]:
return [f['name'] for f in m['flds']] return [f['name'] for f in m['flds']]
def sortIdx(self, m: Dict[str, Any]) -> Any: def sortIdx(self, m: Model) -> Any:
return m['sortf'] return m['sortf']
def setSortIdx(self, m, idx) -> None: def setSortIdx(self, m: Model, idx: int) -> None:
assert 0 <= idx < len(m['flds']) assert 0 <= idx < len(m['flds'])
self.col.modSchema(check=True) self.col.modSchema(check=True)
m['sortf'] = idx m['sortf'] = idx
self.col.updateFieldCache(self.nids(m)) self.col.updateFieldCache(self.nids(m))
self.save(m, updateReqs=False) self.save(m, updateReqs=False)
def addField(self, m: Dict[str, Any], field: Dict[str, Any]) -> None: def addField(self, m: Model, field: Field) -> None:
# only mod schema if model isn't new # only mod schema if model isn't new
if m['id']: if m['id']:
self.col.modSchema(check=True) self.col.modSchema(check=True)
@ -273,7 +274,7 @@ and notes.mid = ? and cards.ord = ?""", m['id'], ord)
return fields return fields
self._transformFields(m, add) self._transformFields(m, add)
def remField(self, m: Dict[str, Any], field: Dict[str, Any]) -> None: def remField(self, m: Model, field: Field) -> None:
self.col.modSchema(check=True) self.col.modSchema(check=True)
# save old sort field # save old sort field
sortFldName = m['flds'][m['sortf']]['name'] sortFldName = m['flds'][m['sortf']]['name']
@ -296,7 +297,7 @@ and notes.mid = ? and cards.ord = ?""", m['id'], ord)
# saves # saves
self.renameField(m, field, None) self.renameField(m, field, None)
def moveField(self, m: Dict[str, Any], field: Dict[str, Any], idx: int) -> None: def moveField(self, m: Model, field: Field, idx: int) -> None:
self.col.modSchema(check=True) self.col.modSchema(check=True)
oldidx = m['flds'].index(field) oldidx = m['flds'].index(field)
if oldidx == idx: if oldidx == idx:
@ -317,7 +318,7 @@ and notes.mid = ? and cards.ord = ?""", m['id'], ord)
return fields return fields
self._transformFields(m, move) self._transformFields(m, move)
def renameField(self, m: Dict[str, Any], field: Dict[str, Any], newName: Optional[str]) -> None: def renameField(self, m: Model, field: Field, newName: Optional[str]) -> None:
self.col.modSchema(check=True) self.col.modSchema(check=True)
pat = r'{{([^{}]*)([:#^/]|[^:#/^}][^:}]*?:|)%s}}' pat = r'{{([^{}]*)([:#^/]|[^:#/^}][^:}]*?:|)%s}}'
def wrap(txt): def wrap(txt):
@ -335,11 +336,11 @@ and notes.mid = ? and cards.ord = ?""", m['id'], ord)
field['name'] = newName field['name'] = newName
self.save(m) self.save(m)
def _updateFieldOrds(self, m: Dict[str, Any]) -> None: def _updateFieldOrds(self, m: Model) -> None:
for c, f in enumerate(m['flds']): for c, f in enumerate(m['flds']):
f['ord'] = c f['ord'] = c
def _transformFields(self, m: Dict[str, Any], fn: Callable) -> None: def _transformFields(self, m: Model, fn: Callable) -> None:
# model hasn't been added yet? # model hasn't been added yet?
if not m['id']: if not m['id']:
return return
@ -354,12 +355,12 @@ and notes.mid = ? and cards.ord = ?""", m['id'], ord)
# Templates # Templates
################################################## ##################################################
def newTemplate(self, name: str) -> Dict[str, Any]: def newTemplate(self, name: str) -> Template:
t = defaultTemplate.copy() t = defaultTemplate.copy()
t['name'] = name t['name'] = name
return t return t
def addTemplate(self, m: Dict[str, Any], template: Dict[str, Union[str, None]]) -> None: def addTemplate(self, m: Model, template: Template) -> None:
"Note: should col.genCards() afterwards." "Note: should col.genCards() afterwards."
if m['id']: if m['id']:
self.col.modSchema(check=True) self.col.modSchema(check=True)
@ -367,7 +368,7 @@ and notes.mid = ? and cards.ord = ?""", m['id'], ord)
self._updateTemplOrds(m) self._updateTemplOrds(m)
self.save(m) self.save(m)
def remTemplate(self, m: Dict[str, Any], template: Dict[str, Any]) -> bool: def remTemplate(self, m: Model, template: Template) -> bool:
"False if removing template would leave orphan notes." "False if removing template would leave orphan notes."
assert len(m['tmpls']) > 1 assert len(m['tmpls']) > 1
# find cards using this template # find cards using this template
@ -397,11 +398,11 @@ update cards set ord = ord - 1, usn = ?, mod = ?
self.save(m) self.save(m)
return True return True
def _updateTemplOrds(self, m: Dict[str, Any]) -> None: def _updateTemplOrds(self, m: Model) -> None:
for c, t in enumerate(m['tmpls']): for c, t in enumerate(m['tmpls']):
t['ord'] = c t['ord'] = c
def moveTemplate(self, m, template, idx) -> None: def moveTemplate(self, m: Model, template: Template, idx: int) -> None:
oldidx = m['tmpls'].index(template) oldidx = m['tmpls'].index(template)
if oldidx == idx: if oldidx == idx:
return return
@ -420,7 +421,7 @@ update cards set ord = (case %s end),usn=?,mod=? where nid in (
select id from notes where mid = ?)""" % " ".join(map), select id from notes where mid = ?)""" % " ".join(map),
self.col.usn(), intTime(), m['id']) self.col.usn(), intTime(), m['id'])
def _syncTemplates(self, m: Dict[str, Any]) -> None: def _syncTemplates(self, m: Model) -> None:
rem = self.col.genCards(self.nids(m)) rem = self.col.genCards(self.nids(m))
# Model changing # Model changing
@ -428,7 +429,7 @@ select id from notes where mid = ?)""" % " ".join(map),
# - maps are ord->ord, and there should not be duplicate targets # - maps are ord->ord, and there should not be duplicate targets
# - newModel should be self if model is not changing # - newModel should be self if model is not changing
def change(self, m: Dict[str, Any], nids: List[int], newModel: Dict[str, Any], fmap: Any, cmap: Any) -> None: def change(self, m: Model, nids: List[int], newModel: Model, fmap: Any, cmap: Any) -> None:
self.col.modSchema(check=True) self.col.modSchema(check=True)
assert newModel['id'] == m['id'] or (fmap and cmap) assert newModel['id'] == m['id'] or (fmap and cmap)
if fmap: if fmap:
@ -437,7 +438,7 @@ select id from notes where mid = ?)""" % " ".join(map),
self._changeCards(nids, m, newModel, cmap) self._changeCards(nids, m, newModel, cmap)
self.col.genCards(nids) self.col.genCards(nids)
def _changeNotes(self, nids: List[int], newModel: Dict[str, Any], map: Dict[int, Union[None, int]]) -> None: def _changeNotes(self, nids: List[int], newModel: Model, map: Dict[int, Union[None, int]]) -> None:
d = [] d = []
nfields = len(newModel['flds']) nfields = len(newModel['flds'])
for (nid, flds) in self.col.db.execute( for (nid, flds) in self.col.db.execute(
@ -456,7 +457,7 @@ select id from notes where mid = ?)""" % " ".join(map),
"update notes set flds=:flds,mid=:mid,mod=:m,usn=:u where id = :nid", d) "update notes set flds=:flds,mid=:mid,mod=:m,usn=:u where id = :nid", d)
self.col.updateFieldCache(nids) self.col.updateFieldCache(nids)
def _changeCards(self, nids: List[int], oldModel: Dict[str, Any], newModel: Dict[str, Any], map: Dict[int, Union[None, int]]) -> None: def _changeCards(self, nids: List[int], oldModel: Model, newModel: Model, map: Dict[int, Union[None, int]]) -> None:
d = [] d = []
deleted = [] deleted = []
for (cid, ord) in self.col.db.execute( for (cid, ord) in self.col.db.execute(
@ -486,7 +487,7 @@ select id from notes where mid = ?)""" % " ".join(map),
# Schema hash # Schema hash
########################################################################## ##########################################################################
def scmhash(self, m: Dict[str, Any]) -> str: def scmhash(self, m: Model) -> str:
"Return a hash of the schema, to see if models are compatible." "Return a hash of the schema, to see if models are compatible."
s = "" s = ""
for f in m['flds']: for f in m['flds']:
@ -498,7 +499,7 @@ select id from notes where mid = ?)""" % " ".join(map),
# Required field/text cache # Required field/text cache
########################################################################## ##########################################################################
def _updateRequired(self, m: Dict[str, Any]) -> None: def _updateRequired(self, m: Model) -> None:
if m['type'] == MODEL_CLOZE: if m['type'] == MODEL_CLOZE:
# nothing to do # nothing to do
return return
@ -509,7 +510,7 @@ select id from notes where mid = ?)""" % " ".join(map),
req.append([t['ord'], ret[0], ret[1]]) req.append([t['ord'], ret[0], ret[1]])
m['req'] = req m['req'] = req
def _reqForTemplate(self, m: Dict[str, Any], flds: List[str], t: Dict[str, Any]) -> Tuple[Union[str, List[int]], ...]: def _reqForTemplate(self, m: Model, flds: List[str], t: Template) -> Tuple[Union[str, List[int]], ...]:
a = [] a = []
b = [] b = []
for f in flds: for f in flds:
@ -546,7 +547,7 @@ select id from notes where mid = ?)""" % " ".join(map),
req.append(i) req.append(i)
return type, req return type, req
def availOrds(self, m: Dict[str, Any], flds: str) -> List: def availOrds(self, m: Model, flds: str) -> List:
"Given a joined field string, return available template ordinals." "Given a joined field string, return available template ordinals."
if m['type'] == MODEL_CLOZE: if m['type'] == MODEL_CLOZE:
return self._availClozeOrds(m, flds) return self._availClozeOrds(m, flds)
@ -580,7 +581,7 @@ select id from notes where mid = ?)""" % " ".join(map),
avail.append(ord) avail.append(ord)
return avail return avail
def _availClozeOrds(self, m: Dict[str, Any], flds: str, allowEmpty: bool = True) -> List: def _availClozeOrds(self, m: Model, flds: str, allowEmpty: bool = True) -> List:
sflds = splitFields(flds) sflds = splitFields(flds)
map = self.fieldMap(m) map = self.fieldMap(m)
ords = set() ords = set()

View file

@ -1,17 +1,18 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# 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, Dict from typing import Any, Callable, List, Tuple
from anki.consts import MODEL_CLOZE from anki.consts import MODEL_CLOZE
from anki.lang import _ from anki.lang import _
from anki.types import Model
models = [] models: List[Tuple[Callable[[], str], Callable[[Any], Model]]] = []
# Basic # Basic
########################################################################## ##########################################################################
def _newBasicModel(col, name=None) -> Dict[str, Any]: def _newBasicModel(col, name=None) -> Model:
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"))
@ -24,7 +25,7 @@ def _newBasicModel(col, name=None) -> Dict[str, Any]:
mm.addTemplate(m, t) mm.addTemplate(m, t)
return m return m
def addBasicModel(col) -> Dict[str, Any]: def addBasicModel(col) -> Model:
m = _newBasicModel(col) m = _newBasicModel(col)
col.models.add(m) col.models.add(m)
return m return m
@ -34,7 +35,7 @@ models.append((lambda: _("Basic"), addBasicModel))
# Basic w/ typing # Basic w/ typing
########################################################################## ##########################################################################
def addBasicTypingModel(col) -> Dict[str, Any]: def addBasicTypingModel(col) -> Model:
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]
@ -48,7 +49,7 @@ models.append((lambda: _("Basic (type in the answer)"), addBasicTypingModel))
# Forward & Reverse # Forward & Reverse
########################################################################## ##########################################################################
def _newForwardReverse(col, name=None) -> Dict[str, Any]: def _newForwardReverse(col, name=None) -> Model:
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"))
@ -57,7 +58,7 @@ def _newForwardReverse(col, name=None) -> Dict[str, Any]:
mm.addTemplate(m, t) mm.addTemplate(m, t)
return m return m
def addForwardReverse(col) -> Dict[str, Any]: def addForwardReverse(col) -> Model:
m = _newForwardReverse(col) m = _newForwardReverse(col)
col.models.add(m) col.models.add(m)
return m return m
@ -67,7 +68,7 @@ models.append((lambda: _("Basic (and reversed card)"), addForwardReverse))
# Forward & Optional Reverse # Forward & Optional Reverse
########################################################################## ##########################################################################
def addForwardOptionalReverse(col) -> Dict[str, Any]: def addForwardOptionalReverse(col) -> Model:
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")
@ -84,7 +85,7 @@ models.append((lambda: _("Basic (optional reversed card)"),
# Cloze # Cloze
########################################################################## ##########################################################################
def addClozeModel(col) -> Dict[str, Any]: def addClozeModel(col) -> Model:
mm = col.models mm = col.models
m = mm.new(_("Cloze")) m = mm.new(_("Cloze"))
m['type'] = MODEL_CLOZE m['type'] = MODEL_CLOZE

12
anki/types.py Normal file
View file

@ -0,0 +1,12 @@
from typing import Any, Dict, Union
# Model attributes are stored in a dict keyed by strings. This type alias
# provides more descriptive function signatures than just 'Dict[str, Any]'
# for methods that operate on models.
# TODO: Use https://www.python.org/dev/peps/pep-0589/ when available in
# supported Python versions.
Model = Dict[str, Any]
Field = Dict[str, Any]
Template = Dict[str, Union[str, int, None]]

View file

@ -52,7 +52,7 @@ inTimeTable = {
"seconds": lambda n: ngettext("in %s second", "in %s seconds", n), "seconds": lambda n: ngettext("in %s second", "in %s seconds", n),
} }
def shortTimeFmt(type: str) -> Any: def shortTimeFmt(type: str) -> str:
return { return {
#T: year is an abbreviation for year. %s is a number of years #T: year is an abbreviation for year. %s is a number of years
"years": _("%sy"), "years": _("%sy"),
@ -84,7 +84,7 @@ def fmtTimeSpan(time: Union[int, float], pad: int = 0, point: int = 0, short: bo
timestr = "%%%(a)d.%(b)df" % {'a': pad, 'b': point} timestr = "%%%(a)d.%(b)df" % {'a': pad, 'b': point}
return locale.format_string(fmt % timestr, time) return locale.format_string(fmt % timestr, time)
def optimalPeriod(time: Union[int, float], point: int, unit: int) -> Tuple[str, Any]: def optimalPeriod(time: Union[int, float], point: int, unit: int) -> Tuple[str, int]:
if abs(time) < 60 or unit < 1: if abs(time) < 60 or unit < 1:
type = "seconds" type = "seconds"
point -= 1 point -= 1
@ -152,7 +152,7 @@ def stripHTML(s: str) -> str:
s = entsToTxt(s) s = entsToTxt(s)
return s return s
def stripHTMLMedia(s: str) -> Any: def stripHTMLMedia(s: str) -> str:
"Strip HTML but keep media filenames" "Strip HTML but keep media filenames"
s = reMedia.sub(" \\1 ", s) s = reMedia.sub(" \\1 ", s)
return stripHTML(s) return stripHTML(s)
@ -167,7 +167,7 @@ def minimizeHTML(s) -> str:
'<u>\\1</u>', s) '<u>\\1</u>', s)
return s return s
def htmlToTextLine(s) -> Any: def htmlToTextLine(s) -> str:
s = s.replace("<br>", " ") s = s.replace("<br>", " ")
s = s.replace("<br />", " ") s = s.replace("<br />", " ")
s = s.replace("<div>", " ") s = s.replace("<div>", " ")