NF: NoteTypeID type

This commit is contained in:
Arthur Milchior 2021-03-23 12:41:24 +01:00 committed by Damien Elmes
parent b54410200e
commit 7ea862931c
10 changed files with 59 additions and 43 deletions

View File

@ -42,7 +42,7 @@ from anki.decks import DeckID, DeckManager
from anki.errors import AnkiError, DBError from anki.errors import AnkiError, DBError
from anki.lang import TR, FormatTimeSpan from anki.lang import TR, FormatTimeSpan
from anki.media import MediaManager, media_paths_from_col_path from anki.media import MediaManager, media_paths_from_col_path
from anki.models import ModelManager, NoteType from anki.models import ModelManager, NoteType, NoteTypeID
from anki.notes import Note, NoteID from anki.notes import Note, NoteID
from anki.scheduler.v1 import Scheduler as V1Scheduler from anki.scheduler.v1 import Scheduler as V1Scheduler
from anki.scheduler.v2 import Scheduler as V2Scheduler from anki.scheduler.v2 import Scheduler as V2Scheduler
@ -406,7 +406,7 @@ class Collection:
home_deck_of_current_review_card=home_deck, home_deck_of_current_review_card=home_deck,
) )
def default_deck_for_notetype(self, notetype_id: NoteID) -> Optional[DeckID]: def default_deck_for_notetype(self, notetype_id: NoteTypeID) -> Optional[DeckID]:
"""If 'change deck depending on notetype' is enabled in the preferences, """If 'change deck depending on notetype' is enabled in the preferences,
return the last deck used with the provided notetype, if any..""" return the last deck used with the provided notetype, if any.."""
if self.get_config_bool(Config.Bool.ADDING_DEFAULTS_TO_CURRENT_DECK): if self.get_config_bool(Config.Bool.ADDING_DEFAULTS_TO_CURRENT_DECK):
@ -578,7 +578,7 @@ class Collection:
dupes = [] dupes = []
fields: Dict[int, int] = {} fields: Dict[int, int] = {}
def ordForMid(mid: int) -> int: def ordForMid(mid: NoteTypeID) -> int:
if mid not in fields: if mid not in fields:
model = self.models.get(mid) model = self.models.get(mid)
for c, f in enumerate(model["flds"]): for c, f in enumerate(model["flds"]):

View File

@ -11,6 +11,7 @@ from anki.consts import *
from anki.decks import DeckID, DeckManager from anki.decks import DeckID, DeckManager
from anki.importing.base import Importer from anki.importing.base import Importer
from anki.lang import TR from anki.lang import TR
from anki.models import NoteTypeID
from anki.notes import NoteID from anki.notes import NoteID
from anki.utils import intTime, joinFields, splitFields, stripHTMLMedia from anki.utils import intTime, joinFields, splitFields, stripHTMLMedia
@ -81,7 +82,7 @@ class Anki2Importer(Importer):
def _importNotes(self) -> None: def _importNotes(self) -> None:
# build guid -> (id,mod,mid) hash & map of existing note ids # build guid -> (id,mod,mid) hash & map of existing note ids
self._notes: Dict[str, Tuple[NoteID, int, int]] = {} self._notes: Dict[str, Tuple[NoteID, int, NoteTypeID]] = {}
existing = {} existing = {}
for id, guid, mod, mid in self.dst.db.execute( for id, guid, mod, mid in self.dst.db.execute(
"select id, guid, mod, mid from notes" "select id, guid, mod, mid from notes"
@ -217,9 +218,9 @@ class Anki2Importer(Importer):
def _prepareModels(self) -> None: def _prepareModels(self) -> None:
"Prepare index of schema hashes." "Prepare index of schema hashes."
self._modelMap: Dict[int, int] = {} self._modelMap: Dict[NoteTypeID, NoteTypeID] = {}
def _mid(self, srcMid: int) -> Any: def _mid(self, srcMid: NoteTypeID) -> Any:
"Return local id for remote MID." "Return local id for remote MID."
# already processed this mid? # already processed this mid?
if srcMid in self._modelMap: if srcMid in self._modelMap:
@ -248,7 +249,7 @@ class Anki2Importer(Importer):
self.dst.models.update(model) self.dst.models.update(model)
break break
# as they don't match, try next id # as they don't match, try next id
mid += 1 mid = NoteTypeID(mid + 1)
# save map and return new mid # save map and return new mid
self._modelMap[srcMid] = mid self._modelMap[srcMid] = mid
return mid return mid
@ -432,7 +433,7 @@ insert or ignore into revlog values (?,?,?,?,?,?,?,?,?)""",
# the user likely used subdirectories # the user likely used subdirectories
pass pass
def _mungeMedia(self, mid: int, fieldsStr: str) -> str: def _mungeMedia(self, mid: NoteTypeID, fieldsStr: str) -> str:
fields = splitFields(fieldsStr) fields = splitFields(fieldsStr)
def repl(match): def repl(match):

View File

@ -10,6 +10,7 @@ from anki.config import Config
from anki.consts import NEW_CARDS_RANDOM, STARTING_FACTOR from anki.consts import NEW_CARDS_RANDOM, STARTING_FACTOR
from anki.importing.base import Importer from anki.importing.base import Importer
from anki.lang import TR from anki.lang import TR
from anki.models import NoteTypeID
from anki.notes import NoteID from anki.notes import NoteID
from anki.utils import ( from anki.utils import (
fieldChecksum, fieldChecksum,
@ -243,7 +244,7 @@ class NoteImporter(Importer):
def newData( def newData(
self, n: ForeignNote self, n: ForeignNote
) -> Tuple[NoteID, str, int, int, int, str, str, str, int, int, str]: ) -> Tuple[NoteID, str, NoteTypeID, int, int, str, str, str, int, int, str]:
id = self._nextID id = self._nextID
self._nextID = NoteID(self._nextID + 1) self._nextID = NoteID(self._nextID + 1)
self._ids.append(id) self._ids.append(id)
@ -267,7 +268,9 @@ class NoteImporter(Importer):
def addNew( def addNew(
self, self,
rows: List[Tuple[NoteID, str, int, int, int, str, str, str, int, int, str]], rows: List[
Tuple[NoteID, str, NoteTypeID, int, int, str, str, str, int, int, str]
],
) -> None: ) -> None:
self.col.db.executemany( self.col.db.executemany(
"insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)", rows "insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)", rows

View File

@ -17,6 +17,7 @@ import anki
import anki._backend.backend_pb2 as _pb import anki._backend.backend_pb2 as _pb
from anki.consts import * from anki.consts import *
from anki.latex import render_latex, render_latex_returning_errors from anki.latex import render_latex, render_latex_returning_errors
from anki.models import NoteTypeID
from anki.sound import SoundOrVideoTag from anki.sound import SoundOrVideoTag
from anki.template import av_tags_to_native from anki.template import av_tags_to_native
from anki.utils import intTime from anki.utils import intTime
@ -159,7 +160,7 @@ class MediaManager:
########################################################################## ##########################################################################
def filesInStr( def filesInStr(
self, mid: int, string: str, includeRemote: bool = False self, mid: NoteTypeID, string: str, includeRemote: bool = False
) -> List[str]: ) -> List[str]:
l = [] l = []
model = self.col.models.get(mid) model = self.col.models.get(mid)

View File

@ -8,7 +8,7 @@ import pprint
import sys import sys
import time import time
import traceback import traceback
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union from typing import Any, Dict, List, NewType, Optional, Sequence, Tuple, Union
import anki # pylint: disable=unused-import import anki # pylint: disable=unused-import
import anki._backend.backend_pb2 as _pb import anki._backend.backend_pb2 as _pb
@ -35,6 +35,7 @@ NoteTypeNameIDUseCount = _pb.NoteTypeNameIDUseCount
NoteType = Dict[str, Any] NoteType = Dict[str, Any]
Field = Dict[str, Any] Field = Dict[str, Any]
Template = Dict[str, Union[str, int, None]] Template = Dict[str, Union[str, int, None]]
NoteTypeID = NewType("NoteTypeID", int)
class ModelsDictProxy: class ModelsDictProxy:
@ -47,7 +48,7 @@ class ModelsDictProxy:
def __getitem__(self, item: Any) -> Any: def __getitem__(self, item: Any) -> Any:
self._warn() self._warn()
return self._col.models.get(int(item)) return self._col.models.get(NoteTypeID(int(item)))
def __setitem__(self, key: str, val: Any) -> None: def __setitem__(self, key: str, val: Any) -> None:
self._warn() self._warn()
@ -114,16 +115,16 @@ class ModelManager:
# need to cache responses from the backend. Please do not # need to cache responses from the backend. Please do not
# access the cache directly! # access the cache directly!
_cache: Dict[int, NoteType] = {} _cache: Dict[NoteTypeID, NoteType] = {}
def _update_cache(self, nt: NoteType) -> None: def _update_cache(self, nt: NoteType) -> None:
self._cache[nt["id"]] = nt self._cache[nt["id"]] = nt
def _remove_from_cache(self, ntid: int) -> None: def _remove_from_cache(self, ntid: NoteTypeID) -> None:
if ntid in self._cache: if ntid in self._cache:
del self._cache[ntid] del self._cache[ntid]
def _get_cached(self, ntid: int) -> Optional[NoteType]: def _get_cached(self, ntid: NoteTypeID) -> Optional[NoteType]:
return self._cache.get(ntid) return self._cache.get(ntid)
def _clear_cache(self) -> None: def _clear_cache(self) -> None:
@ -143,11 +144,11 @@ class ModelManager:
def allNames(self) -> List[str]: def allNames(self) -> List[str]:
return [n.name for n in self.all_names_and_ids()] return [n.name for n in self.all_names_and_ids()]
def ids(self) -> List[int]: def ids(self) -> List[NoteTypeID]:
return [n.id for n in self.all_names_and_ids()] return [NoteTypeID(n.id) for n in self.all_names_and_ids()]
# only used by importing code # only used by importing code
def have(self, id: int) -> bool: def have(self, id: NoteTypeID) -> bool:
if isinstance(id, str): if isinstance(id, str):
id = int(id) id = int(id)
return any(True for e in self.all_names_and_ids() if e.id == id) return any(True for e in self.all_names_and_ids() if e.id == id)
@ -162,7 +163,7 @@ class ModelManager:
m = self.get(self.col.conf["curModel"]) m = self.get(self.col.conf["curModel"])
if m: if m:
return m return m
return self.get(self.all_names_and_ids()[0].id) return self.get(NoteTypeID(self.all_names_and_ids()[0].id))
def setCurrent(self, m: NoteType) -> None: def setCurrent(self, m: NoteType) -> None:
self.col.conf["curModel"] = m["id"] self.col.conf["curModel"] = m["id"]
@ -170,13 +171,13 @@ class ModelManager:
# Retrieving and creating models # Retrieving and creating models
############################################################# #############################################################
def id_for_name(self, name: str) -> Optional[int]: def id_for_name(self, name: str) -> Optional[NoteTypeID]:
try: try:
return self.col._backend.get_notetype_id_by_name(name) return NoteTypeID(self.col._backend.get_notetype_id_by_name(name))
except NotFoundError: except NotFoundError:
return None return None
def get(self, id: int) -> Optional[NoteType]: def get(self, id: NoteTypeID) -> Optional[NoteType]:
"Get model with ID, or None." "Get model with ID, or None."
# deal with various legacy input types # deal with various legacy input types
if id is None: if id is None:
@ -195,7 +196,7 @@ class ModelManager:
def all(self) -> List[NoteType]: def all(self) -> List[NoteType]:
"Get all models." "Get all models."
return [self.get(nt.id) for nt in self.all_names_and_ids()] return [self.get(NoteTypeID(nt.id)) for nt in self.all_names_and_ids()]
def byName(self, name: str) -> Optional[NoteType]: def byName(self, name: str) -> Optional[NoteType]:
"Get model with NAME." "Get model with NAME."
@ -222,10 +223,10 @@ class ModelManager:
def remove_all_notetypes(self) -> None: def remove_all_notetypes(self) -> None:
for nt in self.all_names_and_ids(): for nt in self.all_names_and_ids():
self._remove_from_cache(nt.id) self._remove_from_cache(NoteTypeID(nt.id))
self.col._backend.remove_notetype(nt.id) self.col._backend.remove_notetype(nt.id)
def remove(self, id: int) -> None: def remove(self, id: NoteTypeID) -> None:
"Modifies schema." "Modifies schema."
self._remove_from_cache(id) self._remove_from_cache(id)
self.col._backend.remove_notetype(id) self.col._backend.remove_notetype(id)
@ -257,7 +258,7 @@ class ModelManager:
# Tools # Tools
################################################## ##################################################
def nids(self, ntid: int) -> List[anki.notes.NoteID]: def nids(self, ntid: NoteTypeID) -> List[anki.notes.NoteID]:
"Note ids for M." "Note ids for M."
if isinstance(ntid, dict): if isinstance(ntid, dict):
# legacy callers passed in note type # legacy callers passed in note type
@ -403,7 +404,7 @@ class ModelManager:
self.reposition_template(m, template, idx) self.reposition_template(m, template, idx)
self.save(m) self.save(m)
def template_use_count(self, ntid: int, ord: int) -> int: def template_use_count(self, ntid: NoteTypeID, ord: int) -> int:
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

View File

@ -11,7 +11,7 @@ import anki # pylint: disable=unused-import
import anki._backend.backend_pb2 as _pb import anki._backend.backend_pb2 as _pb
from anki import hooks from anki import hooks
from anki.consts import MODEL_STD from anki.consts import MODEL_STD
from anki.models import NoteType, Template from anki.models import NoteType, NoteTypeID, Template
from anki.utils import joinFields from anki.utils import joinFields
DuplicateOrEmptyResult = _pb.NoteIsDuplicateOrEmptyOut.State DuplicateOrEmptyResult = _pb.NoteIsDuplicateOrEmptyOut.State
@ -25,6 +25,7 @@ class Note:
flags = 0 flags = 0
data = "" data = ""
id: NoteID id: NoteID
mid: NoteTypeID
def __init__( def __init__(
self, self,
@ -52,7 +53,7 @@ class Note:
def _load_from_backend_note(self, n: _pb.Note) -> None: def _load_from_backend_note(self, n: _pb.Note) -> None:
self.id = NoteID(n.id) self.id = NoteID(n.id)
self.guid = n.guid self.guid = n.guid
self.mid = n.notetype_id self.mid = NoteTypeID(n.notetype_id)
self.mod = n.mtime_secs self.mod = n.mtime_secs
self.usn = n.usn self.usn = n.usn
self.tags = list(n.tags) self.tags = list(n.tags)

View File

@ -9,6 +9,7 @@ import aqt.forms
from anki.collection import OpChanges, SearchNode from anki.collection import OpChanges, SearchNode
from anki.consts import MODEL_CLOZE from anki.consts import MODEL_CLOZE
from anki.decks import DeckID from anki.decks import DeckID
from anki.models import NoteTypeID
from anki.notes import DuplicateOrEmptyResult, Note, NoteID from anki.notes import DuplicateOrEmptyResult, Note, NoteID
from anki.utils import htmlToTextLine, isMac from anki.utils import htmlToTextLine, isMac
from aqt import AnkiQt, gui_hooks from aqt import AnkiQt, gui_hooks
@ -65,7 +66,7 @@ class AddCards(QDialog):
self.notetype_chooser = NoteTypeChooser( self.notetype_chooser = NoteTypeChooser(
mw=self.mw, mw=self.mw,
widget=self.form.modelArea, widget=self.form.modelArea,
starting_notetype_id=defaults.notetype_id, starting_notetype_id=NoteTypeID(defaults.notetype_id),
on_button_activated=self.show_notetype_selector, on_button_activated=self.show_notetype_selector,
on_notetype_changed=self.on_notetype_change, on_notetype_changed=self.on_notetype_change,
) )
@ -110,7 +111,7 @@ class AddCards(QDialog):
def show_notetype_selector(self) -> None: def show_notetype_selector(self) -> None:
self.editor.call_after_note_saved(self.notetype_chooser.choose_notetype) self.editor.call_after_note_saved(self.notetype_chooser.choose_notetype)
def on_notetype_change(self, notetype_id: NoteID) -> None: def on_notetype_change(self, notetype_id: NoteTypeID) -> None:
# need to adjust current deck? # need to adjust current deck?
if deck_id := self.mw.col.default_deck_for_notetype(notetype_id): if deck_id := self.mw.col.default_deck_for_notetype(notetype_id):
self.deck_chooser.selected_deck_id = deck_id self.deck_chooser.selected_deck_id = deck_id

View File

@ -8,7 +8,7 @@ from typing import Any, List, Optional, Sequence
import aqt.clayout import aqt.clayout
from anki import stdmodels from anki import stdmodels
from anki.lang import without_unicode_isolation from anki.lang import without_unicode_isolation
from anki.models import NoteType, NoteTypeNameIDUseCount from anki.models import NoteType, NoteTypeID, NoteTypeNameIDUseCount
from anki.notes import Note from anki.notes import Note
from aqt import AnkiQt, gui_hooks from aqt import AnkiQt, gui_hooks
from aqt.qt import * from aqt.qt import *
@ -33,7 +33,7 @@ class Models(QDialog):
mw: AnkiQt, mw: AnkiQt,
parent: Optional[QWidget] = None, parent: Optional[QWidget] = None,
fromMain: bool = False, fromMain: bool = False,
selected_notetype_id: Optional[int] = None, selected_notetype_id: Optional[NoteTypeID] = None,
): ):
self.mw = mw self.mw = mw
parent = parent or mw parent = parent or mw

View File

@ -2,7 +2,7 @@
# 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 List, Optional from typing import List, Optional
from anki.notes import NoteID from anki.models import NoteTypeID
from aqt import AnkiQt, gui_hooks from aqt import AnkiQt, gui_hooks
from aqt.qt import * from aqt.qt import *
from aqt.utils import TR, HelpPage, shortcut, tr from aqt.utils import TR, HelpPage, shortcut, tr
@ -23,14 +23,16 @@ class NoteTypeChooser(QHBoxLayout):
deleted. deleted.
""" """
_selected_notetype_id: NoteTypeID
def __init__( def __init__(
self, self,
*, *,
mw: AnkiQt, mw: AnkiQt,
widget: QWidget, widget: QWidget,
starting_notetype_id: int, starting_notetype_id: NoteTypeID,
on_button_activated: Optional[Callable[[], None]] = None, on_button_activated: Optional[Callable[[], None]] = None,
on_notetype_changed: Optional[Callable[[NoteID], None]] = None, on_notetype_changed: Optional[Callable[[NoteTypeID], None]] = None,
show_prefix_label: bool = True, show_prefix_label: bool = True,
) -> None: ) -> None:
QHBoxLayout.__init__(self) QHBoxLayout.__init__(self)
@ -42,7 +44,7 @@ class NoteTypeChooser(QHBoxLayout):
self.on_button_activated = self.choose_notetype self.on_button_activated = self.choose_notetype
self._setup_ui(show_label=show_prefix_label) self._setup_ui(show_label=show_prefix_label)
gui_hooks.state_did_reset.append(self.reset_state) gui_hooks.state_did_reset.append(self.reset_state)
self._selected_notetype_id = 0 self._selected_notetype_id = NoteTypeID(0)
# triggers UI update; avoid firing changed hook on startup # triggers UI update; avoid firing changed hook on startup
self.on_notetype_changed = None self.on_notetype_changed = None
self.selected_notetype_id = starting_notetype_id self.selected_notetype_id = starting_notetype_id
@ -119,7 +121,7 @@ class NoteTypeChooser(QHBoxLayout):
self.selected_notetype_id = id self.selected_notetype_id = id
@property @property
def selected_notetype_id(self) -> int: def selected_notetype_id(self) -> NoteTypeID:
# theoretically this should not be necessary, as we're listening to # theoretically this should not be necessary, as we're listening to
# resets # resets
self._ensure_selected_notetype_valid() self._ensure_selected_notetype_valid()
@ -127,7 +129,7 @@ class NoteTypeChooser(QHBoxLayout):
return self._selected_notetype_id return self._selected_notetype_id
@selected_notetype_id.setter @selected_notetype_id.setter
def selected_notetype_id(self, id: int) -> None: def selected_notetype_id(self, id: NoteTypeID) -> None:
if id != self._selected_notetype_id: if id != self._selected_notetype_id:
self._selected_notetype_id = id self._selected_notetype_id = id
self._ensure_selected_notetype_valid() self._ensure_selected_notetype_valid()
@ -140,7 +142,9 @@ class NoteTypeChooser(QHBoxLayout):
def _ensure_selected_notetype_valid(self) -> None: def _ensure_selected_notetype_valid(self) -> None:
if not self.mw.col.models.get(self._selected_notetype_id): if not self.mw.col.models.get(self._selected_notetype_id):
self.selected_notetype_id = self.mw.col.models.all_names_and_ids()[0].id self.selected_notetype_id = NoteTypeID(
self.mw.col.models.all_names_and_ids()[0].id
)
def _update_button_label(self) -> None: def _update_button_label(self) -> None:
self.button.setText(self.selected_notetype_name().replace("&", "&&")) self.button.setText(self.selected_notetype_name().replace("&", "&&"))

View File

@ -9,6 +9,7 @@ from typing import Dict, Iterable, List, Optional, Tuple, cast
import aqt import aqt
from anki.collection import Config, OpChanges, SearchJoiner, SearchNode from anki.collection import Config, OpChanges, SearchJoiner, SearchNode
from anki.decks import DeckID, DeckTreeNode from anki.decks import DeckID, DeckTreeNode
from anki.models import NoteTypeID
from anki.notes import Note from anki.notes import Note
from anki.tags import TagTreeNode from anki.tags import TagTreeNode
from anki.types import assert_exhaustive from anki.types import assert_exhaustive
@ -1291,11 +1292,14 @@ class SidebarTreeView(QTreeView):
def manage_notetype(self, item: SidebarItem) -> None: def manage_notetype(self, item: SidebarItem) -> None:
Models( Models(
self.mw, parent=self.browser, fromMain=True, selected_notetype_id=item.id self.mw,
parent=self.browser,
fromMain=True,
selected_notetype_id=NoteTypeID(item.id),
) )
def manage_template(self, item: SidebarItem) -> None: def manage_template(self, item: SidebarItem) -> None:
note = Note(self.col, self.col.models.get(item._parent_item.id)) note = Note(self.col, self.col.models.get(NoteTypeID(item._parent_item.id)))
CardLayout(self.mw, note, ord=item.id, parent=self, fill_empty=True) CardLayout(self.mw, note, ord=item.id, parent=self, fill_empty=True)
# Helpers # Helpers