From 7ea862931cd342ac01e424371b468115b0b1ca76 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Tue, 23 Mar 2021 12:41:24 +0100 Subject: [PATCH] NF: NoteTypeID type --- pylib/anki/collection.py | 6 +++--- pylib/anki/importing/anki2.py | 11 ++++++----- pylib/anki/importing/noteimp.py | 7 +++++-- pylib/anki/media.py | 3 ++- pylib/anki/models.py | 35 +++++++++++++++++---------------- pylib/anki/notes.py | 5 +++-- qt/aqt/addcards.py | 5 +++-- qt/aqt/models.py | 4 ++-- qt/aqt/notetypechooser.py | 18 ++++++++++------- qt/aqt/sidebar.py | 8 ++++++-- 10 files changed, 59 insertions(+), 43 deletions(-) diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index 761d66e65..b0798c27b 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -42,7 +42,7 @@ from anki.decks import DeckID, DeckManager from anki.errors import AnkiError, DBError from anki.lang import TR, FormatTimeSpan 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.scheduler.v1 import Scheduler as V1Scheduler from anki.scheduler.v2 import Scheduler as V2Scheduler @@ -406,7 +406,7 @@ class Collection: 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, return the last deck used with the provided notetype, if any..""" if self.get_config_bool(Config.Bool.ADDING_DEFAULTS_TO_CURRENT_DECK): @@ -578,7 +578,7 @@ class Collection: dupes = [] fields: Dict[int, int] = {} - def ordForMid(mid: int) -> int: + def ordForMid(mid: NoteTypeID) -> int: if mid not in fields: model = self.models.get(mid) for c, f in enumerate(model["flds"]): diff --git a/pylib/anki/importing/anki2.py b/pylib/anki/importing/anki2.py index 5cd5824d9..bf92198d4 100644 --- a/pylib/anki/importing/anki2.py +++ b/pylib/anki/importing/anki2.py @@ -11,6 +11,7 @@ from anki.consts import * from anki.decks import DeckID, DeckManager from anki.importing.base import Importer from anki.lang import TR +from anki.models import NoteTypeID from anki.notes import NoteID from anki.utils import intTime, joinFields, splitFields, stripHTMLMedia @@ -81,7 +82,7 @@ class Anki2Importer(Importer): def _importNotes(self) -> None: # 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 = {} for id, guid, mod, mid in self.dst.db.execute( "select id, guid, mod, mid from notes" @@ -217,9 +218,9 @@ class Anki2Importer(Importer): def _prepareModels(self) -> None: "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." # already processed this mid? if srcMid in self._modelMap: @@ -248,7 +249,7 @@ class Anki2Importer(Importer): self.dst.models.update(model) break # as they don't match, try next id - mid += 1 + mid = NoteTypeID(mid + 1) # save map and return new mid self._modelMap[srcMid] = mid return mid @@ -432,7 +433,7 @@ insert or ignore into revlog values (?,?,?,?,?,?,?,?,?)""", # the user likely used subdirectories pass - def _mungeMedia(self, mid: int, fieldsStr: str) -> str: + def _mungeMedia(self, mid: NoteTypeID, fieldsStr: str) -> str: fields = splitFields(fieldsStr) def repl(match): diff --git a/pylib/anki/importing/noteimp.py b/pylib/anki/importing/noteimp.py index 5bb713fbe..35bb4fa71 100644 --- a/pylib/anki/importing/noteimp.py +++ b/pylib/anki/importing/noteimp.py @@ -10,6 +10,7 @@ from anki.config import Config from anki.consts import NEW_CARDS_RANDOM, STARTING_FACTOR from anki.importing.base import Importer from anki.lang import TR +from anki.models import NoteTypeID from anki.notes import NoteID from anki.utils import ( fieldChecksum, @@ -243,7 +244,7 @@ class NoteImporter(Importer): def newData( 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 self._nextID = NoteID(self._nextID + 1) self._ids.append(id) @@ -267,7 +268,9 @@ class NoteImporter(Importer): def addNew( 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: self.col.db.executemany( "insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)", rows diff --git a/pylib/anki/media.py b/pylib/anki/media.py index 3becaf196..cb37d9e62 100644 --- a/pylib/anki/media.py +++ b/pylib/anki/media.py @@ -17,6 +17,7 @@ import anki import anki._backend.backend_pb2 as _pb from anki.consts import * from anki.latex import render_latex, render_latex_returning_errors +from anki.models import NoteTypeID from anki.sound import SoundOrVideoTag from anki.template import av_tags_to_native from anki.utils import intTime @@ -159,7 +160,7 @@ class MediaManager: ########################################################################## def filesInStr( - self, mid: int, string: str, includeRemote: bool = False + self, mid: NoteTypeID, string: str, includeRemote: bool = False ) -> List[str]: l = [] model = self.col.models.get(mid) diff --git a/pylib/anki/models.py b/pylib/anki/models.py index 735b1ec57..1050f9faa 100644 --- a/pylib/anki/models.py +++ b/pylib/anki/models.py @@ -8,7 +8,7 @@ import pprint import sys import time 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._backend.backend_pb2 as _pb @@ -35,6 +35,7 @@ NoteTypeNameIDUseCount = _pb.NoteTypeNameIDUseCount NoteType = Dict[str, Any] Field = Dict[str, Any] Template = Dict[str, Union[str, int, None]] +NoteTypeID = NewType("NoteTypeID", int) class ModelsDictProxy: @@ -47,7 +48,7 @@ class ModelsDictProxy: def __getitem__(self, item: Any) -> Any: 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: self._warn() @@ -114,16 +115,16 @@ class ModelManager: # need to cache responses from the backend. Please do not # access the cache directly! - _cache: Dict[int, NoteType] = {} + _cache: Dict[NoteTypeID, NoteType] = {} def _update_cache(self, nt: NoteType) -> None: 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: 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) def _clear_cache(self) -> None: @@ -143,11 +144,11 @@ class ModelManager: def allNames(self) -> List[str]: return [n.name for n in self.all_names_and_ids()] - def ids(self) -> List[int]: - return [n.id for n in self.all_names_and_ids()] + def ids(self) -> List[NoteTypeID]: + return [NoteTypeID(n.id) for n in self.all_names_and_ids()] # only used by importing code - def have(self, id: int) -> bool: + def have(self, id: NoteTypeID) -> bool: if isinstance(id, str): id = int(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"]) if 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: self.col.conf["curModel"] = m["id"] @@ -170,13 +171,13 @@ class ModelManager: # Retrieving and creating models ############################################################# - def id_for_name(self, name: str) -> Optional[int]: + def id_for_name(self, name: str) -> Optional[NoteTypeID]: try: - return self.col._backend.get_notetype_id_by_name(name) + return NoteTypeID(self.col._backend.get_notetype_id_by_name(name)) except NotFoundError: return None - def get(self, id: int) -> Optional[NoteType]: + def get(self, id: NoteTypeID) -> Optional[NoteType]: "Get model with ID, or None." # deal with various legacy input types if id is None: @@ -195,7 +196,7 @@ class ModelManager: def all(self) -> List[NoteType]: "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]: "Get model with NAME." @@ -222,10 +223,10 @@ class ModelManager: def remove_all_notetypes(self) -> None: 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) - def remove(self, id: int) -> None: + def remove(self, id: NoteTypeID) -> None: "Modifies schema." self._remove_from_cache(id) self.col._backend.remove_notetype(id) @@ -257,7 +258,7 @@ class ModelManager: # Tools ################################################## - def nids(self, ntid: int) -> List[anki.notes.NoteID]: + def nids(self, ntid: NoteTypeID) -> List[anki.notes.NoteID]: "Note ids for M." if isinstance(ntid, dict): # legacy callers passed in note type @@ -403,7 +404,7 @@ class ModelManager: self.reposition_template(m, template, idx) 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( """ select count() from cards, notes where cards.nid = notes.id diff --git a/pylib/anki/notes.py b/pylib/anki/notes.py index 27893275f..00de732f8 100644 --- a/pylib/anki/notes.py +++ b/pylib/anki/notes.py @@ -11,7 +11,7 @@ import anki # pylint: disable=unused-import import anki._backend.backend_pb2 as _pb from anki import hooks from anki.consts import MODEL_STD -from anki.models import NoteType, Template +from anki.models import NoteType, NoteTypeID, Template from anki.utils import joinFields DuplicateOrEmptyResult = _pb.NoteIsDuplicateOrEmptyOut.State @@ -25,6 +25,7 @@ class Note: flags = 0 data = "" id: NoteID + mid: NoteTypeID def __init__( self, @@ -52,7 +53,7 @@ class Note: def _load_from_backend_note(self, n: _pb.Note) -> None: self.id = NoteID(n.id) self.guid = n.guid - self.mid = n.notetype_id + self.mid = NoteTypeID(n.notetype_id) self.mod = n.mtime_secs self.usn = n.usn self.tags = list(n.tags) diff --git a/qt/aqt/addcards.py b/qt/aqt/addcards.py index 66f13c037..c0a878130 100644 --- a/qt/aqt/addcards.py +++ b/qt/aqt/addcards.py @@ -9,6 +9,7 @@ import aqt.forms from anki.collection import OpChanges, SearchNode from anki.consts import MODEL_CLOZE from anki.decks import DeckID +from anki.models import NoteTypeID from anki.notes import DuplicateOrEmptyResult, Note, NoteID from anki.utils import htmlToTextLine, isMac from aqt import AnkiQt, gui_hooks @@ -65,7 +66,7 @@ class AddCards(QDialog): self.notetype_chooser = NoteTypeChooser( mw=self.mw, 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_notetype_changed=self.on_notetype_change, ) @@ -110,7 +111,7 @@ class AddCards(QDialog): def show_notetype_selector(self) -> None: 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? if deck_id := self.mw.col.default_deck_for_notetype(notetype_id): self.deck_chooser.selected_deck_id = deck_id diff --git a/qt/aqt/models.py b/qt/aqt/models.py index a893178cc..84fcf197a 100644 --- a/qt/aqt/models.py +++ b/qt/aqt/models.py @@ -8,7 +8,7 @@ from typing import Any, List, Optional, Sequence import aqt.clayout from anki import stdmodels 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 aqt import AnkiQt, gui_hooks from aqt.qt import * @@ -33,7 +33,7 @@ class Models(QDialog): mw: AnkiQt, parent: Optional[QWidget] = None, fromMain: bool = False, - selected_notetype_id: Optional[int] = None, + selected_notetype_id: Optional[NoteTypeID] = None, ): self.mw = mw parent = parent or mw diff --git a/qt/aqt/notetypechooser.py b/qt/aqt/notetypechooser.py index 870a696e4..96205f36c 100644 --- a/qt/aqt/notetypechooser.py +++ b/qt/aqt/notetypechooser.py @@ -2,7 +2,7 @@ # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html from typing import List, Optional -from anki.notes import NoteID +from anki.models import NoteTypeID from aqt import AnkiQt, gui_hooks from aqt.qt import * from aqt.utils import TR, HelpPage, shortcut, tr @@ -23,14 +23,16 @@ class NoteTypeChooser(QHBoxLayout): deleted. """ + _selected_notetype_id: NoteTypeID + def __init__( self, *, mw: AnkiQt, widget: QWidget, - starting_notetype_id: int, + starting_notetype_id: NoteTypeID, 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, ) -> None: QHBoxLayout.__init__(self) @@ -42,7 +44,7 @@ class NoteTypeChooser(QHBoxLayout): self.on_button_activated = self.choose_notetype self._setup_ui(show_label=show_prefix_label) 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 self.on_notetype_changed = None self.selected_notetype_id = starting_notetype_id @@ -119,7 +121,7 @@ class NoteTypeChooser(QHBoxLayout): self.selected_notetype_id = id @property - def selected_notetype_id(self) -> int: + def selected_notetype_id(self) -> NoteTypeID: # theoretically this should not be necessary, as we're listening to # resets self._ensure_selected_notetype_valid() @@ -127,7 +129,7 @@ class NoteTypeChooser(QHBoxLayout): return self._selected_notetype_id @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: self._selected_notetype_id = id self._ensure_selected_notetype_valid() @@ -140,7 +142,9 @@ class NoteTypeChooser(QHBoxLayout): def _ensure_selected_notetype_valid(self) -> None: 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: self.button.setText(self.selected_notetype_name().replace("&", "&&")) diff --git a/qt/aqt/sidebar.py b/qt/aqt/sidebar.py index e773b28fe..d8be37abb 100644 --- a/qt/aqt/sidebar.py +++ b/qt/aqt/sidebar.py @@ -9,6 +9,7 @@ from typing import Dict, Iterable, List, Optional, Tuple, cast import aqt from anki.collection import Config, OpChanges, SearchJoiner, SearchNode from anki.decks import DeckID, DeckTreeNode +from anki.models import NoteTypeID from anki.notes import Note from anki.tags import TagTreeNode from anki.types import assert_exhaustive @@ -1291,11 +1292,14 @@ class SidebarTreeView(QTreeView): def manage_notetype(self, item: SidebarItem) -> None: 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: - 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) # Helpers