diff --git a/pylib/.pylintrc b/pylib/.pylintrc index 683ac5d40..275d49355 100644 --- a/pylib/.pylintrc +++ b/pylib/.pylintrc @@ -45,4 +45,7 @@ disable= arguments-differ, [BASIC] -good-names = id \ No newline at end of file +good-names = + id, + tr, + db, \ No newline at end of file diff --git a/pylib/anki/_legacy.py b/pylib/anki/_legacy.py index d20ea3dc4..3603cb39d 100644 --- a/pylib/anki/_legacy.py +++ b/pylib/anki/_legacy.py @@ -28,7 +28,7 @@ def partial_path(full_path: str, components: int) -> str: def print_deprecation_warning(msg: str, frame: int = 2) -> None: - path, linenum, fn, y = traceback.extract_stack(limit=5)[frame] + path, linenum, _, _ = traceback.extract_stack(limit=5)[frame] path = partial_path(path, components=3) print(f"{path}:{linenum}:{msg}") diff --git a/pylib/anki/exporting.py b/pylib/anki/exporting.py index 102dc3e74..5cd2ec0b2 100644 --- a/pylib/anki/exporting.py +++ b/pylib/anki/exporting.py @@ -1,6 +1,8 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +# pylint: disable=invalid-name + import json import os import re diff --git a/pylib/anki/importing/anki2.py b/pylib/anki/importing/anki2.py index c017bc690..019eff9ca 100644 --- a/pylib/anki/importing/anki2.py +++ b/pylib/anki/importing/anki2.py @@ -1,6 +1,8 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +# pylint: disable=invalid-name + import os import unicodedata from typing import Any, Dict, List, Optional, Tuple diff --git a/pylib/anki/importing/apkg.py b/pylib/anki/importing/apkg.py index 4c574c44d..d9075f650 100644 --- a/pylib/anki/importing/apkg.py +++ b/pylib/anki/importing/apkg.py @@ -1,6 +1,8 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +# pylint: disable=invalid-name + import json import os import unicodedata diff --git a/pylib/anki/importing/mnemo.py b/pylib/anki/importing/mnemo.py index 3f699799e..cc508552c 100644 --- a/pylib/anki/importing/mnemo.py +++ b/pylib/anki/importing/mnemo.py @@ -1,6 +1,8 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +# pylint: disable=invalid-name + import re import time from typing import cast @@ -134,7 +136,7 @@ acq_reps+ret_reps, lapses, card_type_id from cards""" model["name"] = "Mnemosyne-FrontOnly" mm = self.col.models mm.save(model) - mm.setCurrent(model) + mm.set_current(model) self.model = model self._fields = len(model["flds"]) self.initMapping() @@ -145,34 +147,34 @@ acq_reps+ret_reps, lapses, card_type_id from cards""" m = addBasicModel(self.col) m["name"] = "Mnemosyne-FrontBack" mm = self.col.models - t = mm.newTemplate("Back") + t = mm.new_template("Back") t["qfmt"] = "{{Back}}" t["afmt"] = t["qfmt"] + "\n\n
\n\n{{Front}}" # type: ignore - mm.addTemplate(m, t) + mm.add_template(m, t) self._addFronts(notes, m) def _addVocabulary(self, notes): mm = self.col.models m = mm.new("Mnemosyne-Vocabulary") for f in "Expression", "Pronunciation", "Meaning", "Notes": - fm = mm.newField(f) + fm = mm.new_field(f) mm.addField(m, fm) - t = mm.newTemplate("Recognition") + t = mm.new_template("Recognition") t["qfmt"] = "{{Expression}}" t["afmt"] = ( cast(str, t["qfmt"]) + """\n\n
\n\n\ {{Pronunciation}}
\n{{Meaning}}
\n{{Notes}}""" ) - mm.addTemplate(m, t) - t = mm.newTemplate("Production") + mm.add_template(m, t) + t = mm.new_template("Production") t["qfmt"] = "{{Meaning}}" t["afmt"] = ( cast(str, t["qfmt"]) + """\n\n
\n\n\ {{Expression}}
\n{{Pronunciation}}
\n{{Notes}}""" ) - mm.addTemplate(m, t) + mm.add_template(m, t) mm.add(m) self._addFronts(notes, m, fields=("f", "p_1", "m_1", "n")) @@ -206,7 +208,7 @@ acq_reps+ret_reps, lapses, card_type_id from cards""" model["name"] = "Mnemosyne-Cloze" mm = self.col.models mm.save(model) - mm.setCurrent(model) + mm.set_current(model) self.model = model self._fields = len(model["flds"]) self.initMapping() diff --git a/pylib/anki/importing/noteimp.py b/pylib/anki/importing/noteimp.py index bc73ef644..419b6e927 100644 --- a/pylib/anki/importing/noteimp.py +++ b/pylib/anki/importing/noteimp.py @@ -1,6 +1,8 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +# pylint: disable=invalid-name + import html import unicodedata from typing import Dict, List, Optional, Tuple, Union @@ -130,7 +132,7 @@ class NoteImporter(Importer): csums[csum] = [id] firsts: Dict[str, bool] = {} fld0idx = self.mapping.index(self.model["flds"][0]["name"]) - self._fmap = self.col.models.fieldMap(self.model) + self._fmap = self.col.models.field_map(self.model) self._nextID = NoteId(timestampID(self.col.db, "notes")) # loop through the notes updates: List[Updates] = [] diff --git a/pylib/anki/importing/pauker.py b/pylib/anki/importing/pauker.py index 8850c6c2a..01dd190bd 100644 --- a/pylib/anki/importing/pauker.py +++ b/pylib/anki/importing/pauker.py @@ -24,7 +24,7 @@ class PaukerImporter(NoteImporter): model = addForwardReverse(self.col) model["name"] = "Pauker" self.col.models.save(model, updateReqs=False) - self.col.models.setCurrent(model) + self.col.models.set_current(model) self.model = model self.initMapping() NoteImporter.run(self) diff --git a/pylib/anki/models.py b/pylib/anki/models.py index 5454b097a..e5678b507 100644 --- a/pylib/anki/models.py +++ b/pylib/anki/models.py @@ -1,17 +1,19 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +# pylint: enable=invalid-name + from __future__ import annotations import copy import pprint import sys import time -import traceback from typing import Any, Dict, List, NewType, Optional, Sequence, Tuple, Union import anki # pylint: disable=unused-import import anki._backend.backend_pb2 as _pb +from anki._legacy import DeprecatedNamesMixin, deprecated, print_deprecation_warning from anki.collection import OpChanges, OpChangesWithId from anki.consts import * from anki.errors import NotFoundError @@ -40,8 +42,9 @@ class ModelsDictProxy: self._col = col.weakref() def _warn(self) -> None: - traceback.print_stack(file=sys.stdout) - print("add-on should use methods on col.models, not col.models.models dict") + print_deprecation_warning( + "add-on should use methods on col.decks, not col.decks.decks dict" + ) def __getitem__(self, item: Any) -> Any: self._warn() @@ -72,7 +75,7 @@ class ModelsDictProxy: return self._col.models.have(item) -class ModelManager: +class ModelManager(DeprecatedNamesMixin): # Saving/loading registry ############################################################# @@ -83,27 +86,9 @@ class ModelManager: self._cache = {} def __repr__(self) -> str: - d = dict(self.__dict__) - del d["col"] - return f"{super().__repr__()} {pprint.pformat(d, width=300)}" - - def save( - self, - m: NotetypeDict = None, - # no longer used - templates: bool = False, - updateReqs: bool = True, - ) -> None: - "Save changes made to provided note type." - if not m: - print("col.models.save() should be passed the changed notetype") - return - - self.update(m, preserve_usn=False) - - # legacy - def flush(self) -> None: - pass + attrs = dict(self.__dict__) + del attrs["col"] + return f"{super().__repr__()} {pprint.pformat(attrs, width=300)}" # Caching ############################################################# @@ -114,8 +99,8 @@ class ModelManager: _cache: Dict[NotetypeId, NotetypeDict] = {} - def _update_cache(self, nt: NotetypeDict) -> None: - self._cache[nt["id"]] = nt + def _update_cache(self, notetype: NotetypeDict) -> None: + self._cache[notetype["id"]] = notetype def _remove_from_cache(self, ntid: NotetypeId) -> None: if ntid in self._cache: @@ -136,14 +121,6 @@ class ModelManager: def all_use_counts(self) -> Sequence[NotetypeNameIdUseCount]: return self.col._backend.get_notetype_names_and_counts() - # legacy - - def allNames(self) -> List[str]: - return [n.name 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: NotetypeId) -> bool: if isinstance(id, str): @@ -153,19 +130,15 @@ class ModelManager: # Current note type ############################################################# - def current(self, forDeck: bool = True) -> NotetypeDict: - "Get current model." - m = self.get(self.col.decks.current().get("mid")) - if not forDeck or not m: - m = self.get(self.col.conf["curModel"]) - if m: - return m + def current(self, for_deck: bool = True) -> NotetypeDict: + "Get current model. In new code, prefer col.defaults_for_adding()" + notetype = self.get(self.col.decks.current().get("mid")) + if not for_deck or not notetype: + notetype = self.get(self.col.conf["curModel"]) + if notetype: + return notetype return self.get(NotetypeId(self.all_names_and_ids()[0].id)) - def setCurrent(self, m: NotetypeDict) -> None: - """Legacy. The current notetype is now updated on note add.""" - self.col.set_config("curModel", m["id"]) - # Retrieving and creating models ############################################################# @@ -183,20 +156,20 @@ class ModelManager: elif isinstance(id, str): id = int(id) - nt = self._get_cached(id) - if not nt: + notetype = self._get_cached(id) + if not notetype: try: - nt = from_json_bytes(self.col._backend.get_notetype_legacy(id)) - self._update_cache(nt) + notetype = from_json_bytes(self.col._backend.get_notetype_legacy(id)) + self._update_cache(notetype) except NotFoundError: return None - return nt + return notetype def all(self) -> List[NotetypeDict]: "Get all models." return [self.get(NotetypeId(nt.id)) for nt in self.all_names_and_ids()] - def byName(self, name: str) -> Optional[NotetypeDict]: + def by_name(self, name: str) -> Optional[NotetypeDict]: "Get model with NAME." id = self.id_for_name(name) if id: @@ -207,67 +180,53 @@ class ModelManager: def new(self, name: str) -> NotetypeDict: "Create a new model, and return it." # caller should call save() after modifying - nt = from_json_bytes( + notetype = from_json_bytes( self.col._backend.get_stock_notetype_legacy(StockNotetypeKind.BASIC) ) - nt["flds"] = [] - nt["tmpls"] = [] - nt["name"] = name - return nt - - def rem(self, m: NotetypeDict) -> None: - "Delete model, and all its cards/notes." - self.remove(m["id"]) + notetype["flds"] = [] + notetype["tmpls"] = [] + notetype["name"] = name + return notetype def remove_all_notetypes(self) -> None: - for nt in self.all_names_and_ids(): - self._remove_from_cache(NotetypeId(nt.id)) - self.col._backend.remove_notetype(nt.id) + for notetype in self.all_names_and_ids(): + self._remove_from_cache(NotetypeId(notetype.id)) + self.col._backend.remove_notetype(notetype.id) def remove(self, id: NotetypeId) -> OpChanges: "Modifies schema." self._remove_from_cache(id) return self.col._backend.remove_notetype(id) - def add(self, m: NotetypeDict) -> OpChangesWithId: + def add(self, notetype: NotetypeDict) -> OpChangesWithId: "Replaced with add_dict()" - self.ensureNameUnique(m) - out = self.col._backend.add_notetype_legacy(to_json_bytes(m)) - m["id"] = out.id - self._mutate_after_write(m) + self.ensure_name_unique(notetype) + out = self.col._backend.add_notetype_legacy(to_json_bytes(notetype)) + notetype["id"] = out.id + self._mutate_after_write(notetype) return out - def add_dict(self, m: NotetypeDict) -> OpChangesWithId: + def add_dict(self, notetype: NotetypeDict) -> OpChangesWithId: "Notetype needs to be fetched from DB after adding." - self.ensureNameUnique(m) - return self.col._backend.add_notetype_legacy(to_json_bytes(m)) + self.ensure_name_unique(notetype) + return self.col._backend.add_notetype_legacy(to_json_bytes(notetype)) - def ensureNameUnique(self, m: NotetypeDict) -> None: - existing_id = self.id_for_name(m["name"]) - if existing_id is not None and existing_id != m["id"]: - m["name"] += "-" + checksum(str(time.time()))[:5] + def ensure_name_unique(self, notetype: NotetypeDict) -> None: + existing_id = self.id_for_name(notetype["name"]) + if existing_id is not None and existing_id != notetype["id"]: + notetype["name"] += "-" + checksum(str(time.time()))[:5] - def update(self, m: NotetypeDict, preserve_usn: bool = True) -> None: - "Add or update an existing model. Use .update_dict() instead." - self._remove_from_cache(m["id"]) - self.ensureNameUnique(m) - m["id"] = self.col._backend.add_or_update_notetype( - json=to_json_bytes(m), preserve_usn_and_mtime=preserve_usn - ) - self.setCurrent(m) - self._mutate_after_write(m) - - def update_dict(self, m: NotetypeDict) -> OpChanges: + def update_dict(self, notetype: NotetypeDict) -> OpChanges: "Update a NotetypeDict. Caller will need to re-load notetype if new fields/cards added." - self._remove_from_cache(m["id"]) - self.ensureNameUnique(m) - return self.col._backend.update_notetype_legacy(to_json_bytes(m)) + self._remove_from_cache(notetype["id"]) + self.ensure_name_unique(notetype) + return self.col._backend.update_notetype_legacy(to_json_bytes(notetype)) - def _mutate_after_write(self, nt: NotetypeDict) -> None: + def _mutate_after_write(self, notetype: NotetypeDict) -> None: # existing code expects the note type to be mutated to reflect # the changes made when adding, such as ordinal assignment :-( - updated = self.get(nt["id"]) - nt.update(updated) + updated = self.get(notetype["id"]) + notetype.update(updated) # Tools ################################################## @@ -279,147 +238,115 @@ class ModelManager: ntid = ntid["id"] return self.col.db.list("select id from notes where mid = ?", ntid) - def useCount(self, m: NotetypeDict) -> int: + def use_count(self, notetype: NotetypeDict) -> int: "Number of note using M." - return self.col.db.scalar("select count() from notes where mid = ?", m["id"]) + return self.col.db.scalar( + "select count() from notes where mid = ?", notetype["id"] + ) # Copying ################################################## - def copy(self, m: NotetypeDict, add: bool = True) -> NotetypeDict: + def copy(self, notetype: NotetypeDict, add: bool = True) -> NotetypeDict: "Copy, save and return." - m2 = copy.deepcopy(m) - m2["name"] = without_unicode_isolation( - self.col.tr.notetypes_copy(val=m2["name"]) + cloned = copy.deepcopy(notetype) + cloned["name"] = without_unicode_isolation( + self.col.tr.notetypes_copy(val=cloned["name"]) ) - m2["id"] = 0 + cloned["id"] = 0 if add: - self.add(m2) - return m2 + self.add(cloned) + return cloned # Fields ################################################## - def fieldMap(self, m: NotetypeDict) -> Dict[str, Tuple[int, FieldDict]]: + def field_map(self, notetype: NotetypeDict) -> Dict[str, Tuple[int, FieldDict]]: "Mapping of field name -> (ord, field)." - return {f["name"]: (f["ord"], f) for f in m["flds"]} + return {f["name"]: (f["ord"], f) for f in notetype["flds"]} - def fieldNames(self, m: NotetypeDict) -> List[str]: - return [f["name"] for f in m["flds"]] + def field_names(self, notetype: NotetypeDict) -> List[str]: + return [f["name"] for f in notetype["flds"]] - def sortIdx(self, m: NotetypeDict) -> int: - return m["sortf"] + def sort_idx(self, notetype: NotetypeDict) -> int: + return notetype["sortf"] # Adding & changing fields ################################################## def new_field(self, name: str) -> FieldDict: assert isinstance(name, str) - nt = from_json_bytes( + notetype = from_json_bytes( self.col._backend.get_stock_notetype_legacy(StockNotetypeKind.BASIC) ) - field = nt["flds"][0] + field = notetype["flds"][0] field["name"] = name field["ord"] = None return field - def add_field(self, m: NotetypeDict, field: FieldDict) -> None: + def add_field(self, notetype: NotetypeDict, field: FieldDict) -> None: "Modifies schema." - m["flds"].append(field) + notetype["flds"].append(field) - def remove_field(self, m: NotetypeDict, field: FieldDict) -> None: + def remove_field(self, notetype: NotetypeDict, field: FieldDict) -> None: "Modifies schema." - m["flds"].remove(field) + notetype["flds"].remove(field) - def reposition_field(self, m: NotetypeDict, field: FieldDict, idx: int) -> None: + def reposition_field( + self, notetype: NotetypeDict, field: FieldDict, idx: int + ) -> None: "Modifies schema." - oldidx = m["flds"].index(field) + oldidx = notetype["flds"].index(field) if oldidx == idx: return - m["flds"].remove(field) - m["flds"].insert(idx, field) + notetype["flds"].remove(field) + notetype["flds"].insert(idx, field) - def rename_field(self, m: NotetypeDict, field: FieldDict, new_name: str) -> None: - assert field in m["flds"] + def rename_field( + self, notetype: NotetypeDict, field: FieldDict, new_name: str + ) -> None: + assert field in notetype["flds"] field["name"] = new_name - def set_sort_index(self, nt: NotetypeDict, idx: int) -> None: + def set_sort_index(self, notetype: NotetypeDict, idx: int) -> None: "Modifies schema." - assert 0 <= idx < len(nt["flds"]) - nt["sortf"] = idx - - # legacy - - newField = new_field - - def addField(self, m: NotetypeDict, field: FieldDict) -> None: - self.add_field(m, field) - if m["id"]: - self.save(m) - - def remField(self, m: NotetypeDict, field: FieldDict) -> None: - self.remove_field(m, field) - self.save(m) - - def moveField(self, m: NotetypeDict, field: FieldDict, idx: int) -> None: - self.reposition_field(m, field, idx) - self.save(m) - - def renameField(self, m: NotetypeDict, field: FieldDict, newName: str) -> None: - self.rename_field(m, field, newName) - self.save(m) + assert 0 <= idx < len(notetype["flds"]) + notetype["sortf"] = idx # Adding & changing templates ################################################## def new_template(self, name: str) -> TemplateDict: - nt = from_json_bytes( + notetype = from_json_bytes( self.col._backend.get_stock_notetype_legacy(StockNotetypeKind.BASIC) ) - template = nt["tmpls"][0] + template = notetype["tmpls"][0] template["name"] = name template["qfmt"] = "" template["afmt"] = "" template["ord"] = None return template - def add_template(self, m: NotetypeDict, template: TemplateDict) -> None: + def add_template(self, notetype: NotetypeDict, template: TemplateDict) -> None: "Modifies schema." - m["tmpls"].append(template) + notetype["tmpls"].append(template) - def remove_template(self, m: NotetypeDict, template: TemplateDict) -> None: + def remove_template(self, notetype: NotetypeDict, template: TemplateDict) -> None: "Modifies schema." - assert len(m["tmpls"]) > 1 - m["tmpls"].remove(template) + assert len(notetype["tmpls"]) > 1 + notetype["tmpls"].remove(template) def reposition_template( - self, m: NotetypeDict, template: TemplateDict, idx: int + self, notetype: NotetypeDict, template: TemplateDict, idx: int ) -> None: "Modifies schema." - oldidx = m["tmpls"].index(template) + oldidx = notetype["tmpls"].index(template) if oldidx == idx: return - m["tmpls"].remove(template) - m["tmpls"].insert(idx, template) - - # legacy - - newTemplate = new_template - - def addTemplate(self, m: NotetypeDict, template: TemplateDict) -> None: - self.add_template(m, template) - if m["id"]: - self.save(m) - - def remTemplate(self, m: NotetypeDict, template: TemplateDict) -> None: - self.remove_template(m, template) - self.save(m) - - def moveTemplate(self, m: NotetypeDict, template: TemplateDict, idx: int) -> None: - self.reposition_template(m, template, idx) - self.save(m) + notetype["tmpls"].remove(template) + notetype["tmpls"].insert(idx, template) def template_use_count(self, ntid: NotetypeId, ord: int) -> int: return self.col.db.scalar( @@ -464,9 +391,9 @@ and notes.mid = ? and cards.ord = ?""", # legacy API - used by unit tests and add-ons - def change( + def change( # pylint: disable=invalid-name self, - m: NotetypeDict, + notetype: NotetypeDict, nids: List[anki.notes.NoteId], newModel: NotetypeDict, fmap: Dict[int, Optional[int]], @@ -476,7 +403,11 @@ and notes.mid = ? and cards.ord = ?""", self.col.modSchema(check=True) assert fmap field_map = self._convert_legacy_map(fmap, len(newModel["flds"])) - if not cmap or newModel["type"] == MODEL_CLOZE or m["type"] == MODEL_CLOZE: + if ( + not cmap + or newModel["type"] == MODEL_CLOZE + or notetype["type"] == MODEL_CLOZE + ): template_map = [] else: template_map = self._convert_legacy_map(cmap, len(newModel["tmpls"])) @@ -486,7 +417,7 @@ and notes.mid = ? and cards.ord = ?""", note_ids=nids, new_fields=field_map, new_templates=template_map, - old_notetype_id=m["id"], + old_notetype_id=notetype["id"], new_notetype_id=newModel["id"], current_schema=self.col.db.scalar("select scm from col"), ) @@ -510,21 +441,107 @@ and notes.mid = ? and cards.ord = ?""", # Schema hash ########################################################################## - def scmhash(self, m: NotetypeDict) -> str: + def scmhash(self, notetype: NotetypeDict) -> str: "Return a hash of the schema, to see if models are compatible." - s = "" - for f in m["flds"]: - s += f["name"] - for t in m["tmpls"]: - s += t["name"] - return checksum(s) + buf = "" + for field in notetype["flds"]: + buf += field["name"] + for template in notetype["tmpls"]: + buf += template["name"] + return checksum(buf) - # Cloze + # Legacy ########################################################################## + # pylint: disable=invalid-name + + @deprecated(info="use note.cloze_numbers_in_fields()") def _availClozeOrds( - self, m: NotetypeDict, flds: str, allowEmpty: bool = True + self, notetype: NotetypeDict, flds: str, allow_empty: bool = True ) -> List[int]: - print("_availClozeOrds() is deprecated; use note.cloze_numbers_in_fields()") note = _pb.Note(fields=[flds]) return list(self.col._backend.cloze_numbers_in_note(note)) + + # @deprecated(replaced_by=add_template) + def addTemplate(self, notetype: NotetypeDict, template: TemplateDict) -> None: + self.add_template(notetype, template) + if notetype["id"]: + self.update(notetype) + + # @deprecated(replaced_by=remove_template) + def remTemplate(self, notetype: NotetypeDict, template: TemplateDict) -> None: + self.remove_template(notetype, template) + self.update(notetype) + + # @deprecated(replaced_by=reposition_template) + def move_template( + self, notetype: NotetypeDict, template: TemplateDict, idx: int + ) -> None: + self.reposition_template(notetype, template, idx) + self.update(notetype) + + # @deprecated(replaced_by=add_field) + def addField(self, notetype: NotetypeDict, field: FieldDict) -> None: + self.add_field(notetype, field) + if notetype["id"]: + self.update(notetype) + + # @deprecated(replaced_by=remove_field) + def remField(self, notetype: NotetypeDict, field: FieldDict) -> None: + self.remove_field(notetype, field) + self.update(notetype) + + # @deprecated(replaced_by=reposition_field) + def moveField(self, notetype: NotetypeDict, field: FieldDict, idx: int) -> None: + self.reposition_field(notetype, field, idx) + self.update(notetype) + + # @deprecated(replaced_by=rename_field) + def renameField( + self, notetype: NotetypeDict, field: FieldDict, new_name: str + ) -> None: + self.rename_field(notetype, field, new_name) + self.update(notetype) + + @deprecated(replaced_by=remove) + def rem(self, m: NotetypeDict) -> None: + "Delete model, and all its cards/notes." + self.remove(m["id"]) + + # @deprecated(info="not needed; is updated on note add") + def set_current(self, m: NotetypeDict) -> None: + self.col.set_config("curModel", m["id"]) + + @deprecated(replaced_by=all_names_and_ids) + def all_names(self) -> List[str]: + return [n.name for n in self.all_names_and_ids()] + + @deprecated(replaced_by=all_names_and_ids) + def ids(self) -> List[NotetypeId]: + return [NotetypeId(n.id) for n in self.all_names_and_ids()] + + @deprecated(info="no longer required") + def flush(self) -> None: + pass + + # @deprecated(replaced_by=update_dict) + def update(self, notetype: NotetypeDict, preserve_usn: bool = True) -> None: + "Add or update an existing model. Use .update_dict() instead." + self._remove_from_cache(notetype["id"]) + self.ensure_name_unique(notetype) + notetype["id"] = self.col._backend.add_or_update_notetype( + json=to_json_bytes(notetype), preserve_usn_and_mtime=preserve_usn + ) + self.set_current(notetype) + self._mutate_after_write(notetype) + + # @deprecated(replaced_by=update_dict) + def save(self, notetype: NotetypeDict = None, **legacy_kwargs: bool) -> None: + "Save changes made to provided note type." + if not notetype: + print_deprecation_warning( + "col.models.save() should be passed the changed notetype" + ) + return + + self.update(notetype, preserve_usn=False) diff --git a/pylib/anki/notes.py b/pylib/anki/notes.py index 9d0234b8c..356c34ff5 100644 --- a/pylib/anki/notes.py +++ b/pylib/anki/notes.py @@ -61,7 +61,7 @@ class Note(DeprecatedNamesMixin): self.usn = note.usn self.tags = list(note.tags) self.fields = list(note.fields) - self._fmap = self.col.models.fieldMap(self.note_type()) + self._fmap = self.col.models.field_map(self.note_type()) def _to_backend_note(self) -> _pb.Note: hooks.note_will_flush(self) diff --git a/pylib/anki/scheduler/legacy.py b/pylib/anki/scheduler/legacy.py index 12368e789..c289400c3 100644 --- a/pylib/anki/scheduler/legacy.py +++ b/pylib/anki/scheduler/legacy.py @@ -1,6 +1,8 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +# pylint: disable=invalid-name + from typing import List, Optional, Tuple from anki.cards import Card, CardId diff --git a/pylib/anki/scheduler/v1.py b/pylib/anki/scheduler/v1.py index e9a3ef353..878b2b17d 100644 --- a/pylib/anki/scheduler/v1.py +++ b/pylib/anki/scheduler/v1.py @@ -1,6 +1,8 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +# pylint: disable=invalid-name + from __future__ import annotations import random diff --git a/pylib/anki/scheduler/v2.py b/pylib/anki/scheduler/v2.py index 0ee10194d..53049fad7 100644 --- a/pylib/anki/scheduler/v2.py +++ b/pylib/anki/scheduler/v2.py @@ -1,6 +1,8 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +# pylint: disable=invalid-name + from __future__ import annotations import random diff --git a/pylib/tests/test_cards.py b/pylib/tests/test_cards.py index a4e9e9daf..1ab9dc615 100644 --- a/pylib/tests/test_cards.py +++ b/pylib/tests/test_cards.py @@ -44,10 +44,10 @@ def test_genrem(): m = col.models.current() mm = col.models # adding a new template should automatically create cards - t = mm.newTemplate("rev") + t = mm.new_template("rev") t["qfmt"] = "{{Front}}2" t["afmt"] = "" - mm.addTemplate(m, t) + mm.add_template(m, t) mm.save(m, templates=True) assert len(note.cards()) == 2 # if the template is changed to remove cards, they'll be removed @@ -68,9 +68,8 @@ def test_genrem(): def test_gendeck(): col = getEmptyCol() - cloze = col.models.byName("Cloze") - col.models.setCurrent(cloze) - note = col.newNote() + cloze = col.models.by_name("Cloze") + note = col.new_note(cloze) note["Text"] = "{{c1::one}}" col.addNote(note) assert col.cardCount() == 1 diff --git a/pylib/tests/test_collection.py b/pylib/tests/test_collection.py index 5da64bfb0..cc55fdf49 100644 --- a/pylib/tests/test_collection.py +++ b/pylib/tests/test_collection.py @@ -57,10 +57,10 @@ def test_noteAddDelete(): # test multiple cards - add another template m = col.models.current() mm = col.models - t = mm.newTemplate("Reverse") + t = mm.new_template("Reverse") t["qfmt"] = "{{Back}}" t["afmt"] = "{{Front}}" - mm.addTemplate(m, t) + mm.add_template(m, t) mm.save(m) assert col.cardCount() == 2 # creating new notes should use both cards diff --git a/pylib/tests/test_find.py b/pylib/tests/test_find.py index 274bad872..348a26c04 100644 --- a/pylib/tests/test_find.py +++ b/pylib/tests/test_find.py @@ -37,10 +37,10 @@ def test_findCards(): m = col.models.current() m = col.models.copy(m) mm = col.models - t = mm.newTemplate("Reverse") + t = mm.new_template("Reverse") t["qfmt"] = "{{Back}}" t["afmt"] = "{{Front}}" - mm.addTemplate(m, t) + mm.add_template(m, t) mm.save(m) note = col.newNote() note["Front"] = "test" diff --git a/pylib/tests/test_importing.py b/pylib/tests/test_importing.py index b90ccc230..71f39cc16 100644 --- a/pylib/tests/test_importing.py +++ b/pylib/tests/test_importing.py @@ -189,7 +189,7 @@ def test_csv2(): col = getEmptyCol() mm = col.models m = mm.current() - note = mm.newField("Three") + note = mm.new_field("Three") mm.addField(m, note) mm.save(m) n = col.newNote() @@ -213,7 +213,7 @@ def test_tsv_tag_modified(): col = getEmptyCol() mm = col.models m = mm.current() - note = mm.newField("Top") + note = mm.new_field("Top") mm.addField(m, note) mm.save(m) n = col.newNote() @@ -249,7 +249,7 @@ def test_tsv_tag_multiple_tags(): col = getEmptyCol() mm = col.models m = mm.current() - note = mm.newField("Top") + note = mm.new_field("Top") mm.addField(m, note) mm.save(m) n = col.newNote() @@ -283,7 +283,7 @@ def test_csv_tag_only_if_modified(): col = getEmptyCol() mm = col.models m = mm.current() - note = mm.newField("Left") + note = mm.new_field("Left") mm.addField(m, note) mm.save(m) n = col.newNote() diff --git a/pylib/tests/test_models.py b/pylib/tests/test_models.py index d411d5970..25f06dd07 100644 --- a/pylib/tests/test_models.py +++ b/pylib/tests/test_models.py @@ -17,7 +17,7 @@ def test_modelDelete(): note["Back"] = "2" col.addNote(note) assert col.cardCount() == 1 - col.models.rem(col.models.current()) + col.models.remove(col.models.current()["id"]) assert col.cardCount() == 0 @@ -47,7 +47,7 @@ def test_fields(): assert "{{NewFront}}" in m["tmpls"][0]["qfmt"] h = col.models.scmhash(m) # add a field - field = col.models.newField("foo") + field = col.models.new_field("foo") col.models.addField(m, field) assert col.get_note(col.models.nids(m)[0]).fields == ["1", "2", ""] assert col.models.scmhash(m) != h @@ -65,7 +65,7 @@ def test_fields(): col.models.moveField(m, m["flds"][1], 0) assert col.get_note(col.models.nids(m)[0]).fields == ["1", ""] # add another and put in middle - field = col.models.newField("baz") + field = col.models.new_field("baz") col.models.addField(m, field) note = col.get_note(col.models.nids(m)[0]) note["baz"] = "2" @@ -86,10 +86,10 @@ def test_templates(): col = getEmptyCol() m = col.models.current() mm = col.models - t = mm.newTemplate("Reverse") + t = mm.new_template("Reverse") t["qfmt"] = "{{Back}}" t["afmt"] = "{{Front}}" - mm.addTemplate(m, t) + mm.add_template(m, t) mm.save(m) note = col.newNote() note["Front"] = "1" @@ -101,23 +101,26 @@ def test_templates(): assert c.ord == 0 assert c2.ord == 1 # switch templates - col.models.moveTemplate(m, c.template(), 1) + col.models.reposition_template(m, c.template(), 1) + col.models.update(m) c.load() c2.load() assert c.ord == 1 assert c2.ord == 0 # removing a template should delete its cards - col.models.remTemplate(m, m["tmpls"][0]) + col.models.remove_template(m, m["tmpls"][0]) + col.models.update(m) assert col.cardCount() == 1 # and should have updated the other cards' ordinals c = note.cards()[0] assert c.ord == 0 assert stripHTML(c.question()) == "1" # it shouldn't be possible to orphan notes by removing templates - t = mm.newTemplate("template name") + t = mm.new_template("template name") t["qfmt"] = "{{Front}}2" - mm.addTemplate(m, t) - col.models.remTemplate(m, m["tmpls"][0]) + mm.add_template(m, t) + col.models.remove_template(m, m["tmpls"][0]) + col.models.update(m) assert ( col.db.scalar( "select count() from cards where nid not in (select id from notes)" @@ -128,17 +131,17 @@ def test_templates(): def test_cloze_ordinals(): col = getEmptyCol() - col.models.setCurrent(col.models.byName("Cloze")) - m = col.models.current() + m = col.models.by_name("Cloze") mm = col.models # We replace the default Cloze template - t = mm.newTemplate("ChainedCloze") + t = mm.new_template("ChainedCloze") t["qfmt"] = "{{text:cloze:Text}}" t["afmt"] = "{{text:cloze:Text}}" - mm.addTemplate(m, t) + mm.add_template(m, t) mm.save(m) - col.models.remTemplate(m, m["tmpls"][0]) + col.models.remove_template(m, m["tmpls"][0]) + col.models.update(m) note = col.newNote() note["Text"] = "{{c1::firstQ::firstA}}{{c2::secondQ::secondA}}" @@ -163,26 +166,26 @@ def test_text(): def test_cloze(): col = getEmptyCol() - col.models.setCurrent(col.models.byName("Cloze")) - note = col.newNote() + m = col.models.by_name("Cloze") + note = col.new_note(m) assert note.note_type()["name"] == "Cloze" # a cloze model with no clozes is not empty note["Text"] = "nothing" assert col.addNote(note) # try with one cloze - note = col.newNote() + note = col.new_note(m) note["Text"] = "hello {{c1::world}}" assert col.addNote(note) == 1 assert "hello [...]" in note.cards()[0].question() assert "hello world" in note.cards()[0].answer() # and with a comment - note = col.newNote() + note = col.new_note(m) note["Text"] = "hello {{c1::world::typical}}" assert col.addNote(note) == 1 assert "[typical]" in note.cards()[0].question() assert "world" in note.cards()[0].answer() # and with 2 clozes - note = col.newNote() + note = col.new_note(m) note["Text"] = "hello {{c1::world}} {{c2::bar}}" assert col.addNote(note) == 2 (c1, c2) = note.cards() @@ -192,7 +195,7 @@ def test_cloze(): assert "world bar" in c2.answer() # if there are multiple answers for a single cloze, they are given in a # list - note = col.newNote() + note = col.new_note(m) note["Text"] = "a {{c1::b}} {{c1::c}}" assert col.addNote(note) == 1 assert "b c" in ( @@ -211,8 +214,8 @@ def test_cloze(): def test_cloze_mathjax(): col = getEmptyCol() - col.models.setCurrent(col.models.byName("Cloze")) - note = col.newNote() + m = col.models.by_name("Cloze") + note = col.new_note(m) note[ "Text" ] = r"{{c1::ok}} \(2^2\) {{c2::not ok}} \(2^{{c3::2}}\) \(x^3\) {{c4::blah}} {{c5::text with \(x^2\) jax}}" @@ -224,7 +227,7 @@ def test_cloze_mathjax(): assert "class=cloze" in note.cards()[3].question() assert "class=cloze" in note.cards()[4].question() - note = col.newNote() + note = col.new_note(m) note["Text"] = r"\(a\) {{c1::b}} \[ {{c1::c}} \]" assert col.addNote(note) assert len(note.cards()) == 1 @@ -237,11 +240,10 @@ def test_cloze_mathjax(): def test_typecloze(): col = getEmptyCol() - m = col.models.byName("Cloze") - col.models.setCurrent(m) + m = col.models.by_name("Cloze") m["tmpls"][0]["qfmt"] = "{{cloze:Text}}{{type:cloze:Text}}" col.models.save(m) - note = col.newNote() + note = col.new_note(m) note["Text"] = "hello {{c1::world}}" col.addNote(note) assert "[[type:cloze:Text]]" in note.cards()[0].question() @@ -249,17 +251,17 @@ def test_typecloze(): def test_chained_mods(): col = getEmptyCol() - col.models.setCurrent(col.models.byName("Cloze")) - m = col.models.current() + m = col.models.by_name("Cloze") mm = col.models # We replace the default Cloze template - t = mm.newTemplate("ChainedCloze") + t = mm.new_template("ChainedCloze") t["qfmt"] = "{{cloze:text:Text}}" t["afmt"] = "{{cloze:text:Text}}" - mm.addTemplate(m, t) + mm.add_template(m, t) mm.save(m) - col.models.remTemplate(m, m["tmpls"][0]) + col.models.remove_template(m, m["tmpls"][0]) + col.models.update(m) note = col.newNote() q1 = 'phrase' @@ -285,14 +287,14 @@ def test_chained_mods(): def test_modelChange(): col = getEmptyCol() - cloze = col.models.byName("Cloze") + cloze = col.models.by_name("Cloze") # enable second template and add a note m = col.models.current() mm = col.models - t = mm.newTemplate("Reverse") + t = mm.new_template("Reverse") t["qfmt"] = "{{Back}}" t["afmt"] = "{{Front}}" - mm.addTemplate(m, t) + mm.add_template(m, t) mm.save(m) basic = m note = col.newNote() @@ -360,7 +362,8 @@ def test_modelChange(): assert note["Text"] == "f2" assert len(note.cards()) == 2 # back the other way, with deletion of second ord - col.models.remTemplate(basic, basic["tmpls"][1]) + col.models.remove_template(basic, basic["tmpls"][1]) + col.models.update(basic) assert col.db.scalar("select count() from cards where nid = ?", note.id) == 2 map = {0: 0} col.models.change(cloze, [note.id], basic, map, map) @@ -375,14 +378,14 @@ def test_req(): col = getEmptyCol() mm = col.models - basic = mm.byName("Basic") + basic = mm.by_name("Basic") assert "req" in basic reqSize(basic) r = basic["req"][0] assert r[0] == 0 assert r[1] in ("any", "all") assert r[2] == [0] - opt = mm.byName("Basic (optional reversed card)") + opt = mm.by_name("Basic (optional reversed card)") reqSize(opt) r = opt["req"][0] assert r[1] in ("any", "all") @@ -397,7 +400,7 @@ def test_req(): mm.save(opt, templates=True) assert opt["req"][1] == [1, "none", []] - opt = mm.byName("Basic (type in the answer)") + opt = mm.by_name("Basic (type in the answer)") reqSize(opt) r = opt["req"][0] assert r[1] in ("any", "all") diff --git a/pylib/tests/test_schedv1.py b/pylib/tests/test_schedv1.py index d75622290..38f02b299 100644 --- a/pylib/tests/test_schedv1.py +++ b/pylib/tests/test_schedv1.py @@ -63,10 +63,10 @@ def test_new(): # # the default order should ensure siblings are not seen together, and # # should show all cards # m = col.models.current(); mm = col.models - # t = mm.newTemplate("Reverse") + # t = mm.new_template("Reverse") # t['qfmt'] = "{{Back}}" # t['afmt'] = "{{Front}}" - # mm.addTemplate(m, t) + # mm.add_template(m, t) # mm.save(m) # note = col.newNote() # note['Front'] = u"2"; note['Back'] = u"2" @@ -562,8 +562,8 @@ def test_suspend(): def test_cram(): col = getEmptyCol() - opt = col.models.byName("Basic (and reversed card)") - col.models.setCurrent(opt) + opt = col.models.by_name("Basic (and reversed card)") + col.models.set_current(opt) note = col.newNote() note["Front"] = "one" col.addNote(note) @@ -806,14 +806,14 @@ def test_ordcycle(): # add two more templates and set second active m = col.models.current() mm = col.models - t = mm.newTemplate("Reverse") + t = mm.new_template("Reverse") t["qfmt"] = "{{Back}}" t["afmt"] = "{{Front}}" - mm.addTemplate(m, t) - t = mm.newTemplate("f2") + mm.add_template(m, t) + t = mm.new_template("f2") t["qfmt"] = "{{Front}}2" t["afmt"] = "{{Back}}" - mm.addTemplate(m, t) + mm.add_template(m, t) mm.save(m) # create a new note; it should have 3 cards note = col.newNote() diff --git a/pylib/tests/test_schedv2.py b/pylib/tests/test_schedv2.py index ccbfd81c4..5254b82c6 100644 --- a/pylib/tests/test_schedv2.py +++ b/pylib/tests/test_schedv2.py @@ -75,10 +75,10 @@ def test_new(): # # the default order should ensure siblings are not seen together, and # # should show all cards # m = col.models.current(); mm = col.models - # t = mm.newTemplate("Reverse") + # t = mm.new_template("Reverse") # t['qfmt'] = "{{Back}}" # t['afmt'] = "{{Front}}" - # mm.addTemplate(m, t) + # mm.add_template(m, t) # mm.save(m) # note = col.newNote() # note['Front'] = u"2"; note['Back'] = u"2" @@ -880,14 +880,14 @@ def test_ordcycle(): # add two more templates and set second active m = col.models.current() mm = col.models - t = mm.newTemplate("Reverse") + t = mm.new_template("Reverse") t["qfmt"] = "{{Back}}" t["afmt"] = "{{Front}}" - mm.addTemplate(m, t) - t = mm.newTemplate("f2") + mm.add_template(m, t) + t = mm.new_template("f2") t["qfmt"] = "{{Front}}2" t["afmt"] = "{{Back}}" - mm.addTemplate(m, t) + mm.add_template(m, t) mm.save(m) # create a new note; it should have 3 cards note = col.newNote() diff --git a/pylib/tools/hookslib.py b/pylib/tools/hookslib.py index ab499c896..1628d4929 100644 --- a/pylib/tools/hookslib.py +++ b/pylib/tools/hookslib.py @@ -77,13 +77,13 @@ class Hook: class {self.classname()}: {classdoc}{self.list_code()} - def append(self, cb: {self.callable()}) -> None: + def append(self, callback: {self.callable()}) -> None: '''{appenddoc}''' - self._hooks.append(cb) + self._hooks.append(callback) - def remove(self, cb: {self.callable()}) -> None: - if cb in self._hooks: - self._hooks.remove(cb) + def remove(self, callback: {self.callable()}) -> None: + if callback in self._hooks: + self._hooks.remove(callback) def count(self) -> int: return len(self._hooks) diff --git a/qt/aqt/clayout.py b/qt/aqt/clayout.py index e30b123f9..b00e1fe38 100644 --- a/qt/aqt/clayout.py +++ b/qt/aqt/clayout.py @@ -226,7 +226,7 @@ class CardLayout(QDialog): tform.style_button.setText(tr.card_templates_template_styling()) tform.groupBox.setTitle(tr.card_templates_template_box()) - cnt = self.mw.col.models.useCount(self.model) + cnt = self.mw.col.models.use_count(self.model) self.tform.changes_affect_label.setText( self.col.tr.card_templates_changes_will_affect_notes(count=cnt) ) @@ -633,14 +633,14 @@ class CardLayout(QDialog): return name def onAddCard(self) -> None: - cnt = self.mw.col.models.useCount(self.model) + cnt = self.mw.col.models.use_count(self.model) txt = tr.card_templates_this_will_create_card_proceed(count=cnt) if not askUser(txt): return if not self.change_tracker.mark_schema(): return name = self._newCardName() - t = self.mm.newTemplate(name) + t = self.mm.new_template(name) old = self.current_template() t["qfmt"] = old["qfmt"] t["afmt"] = old["afmt"] diff --git a/qt/aqt/fields.py b/qt/aqt/fields.py index acc7c7b28..679957705 100644 --- a/qt/aqt/fields.py +++ b/qt/aqt/fields.py @@ -136,7 +136,7 @@ class FieldDialog(QDialog): if not self.change_tracker.mark_schema(): return self.saveField() - f = self.mm.newField(name) + f = self.mm.new_field(name) self.mm.add_field(self.model, f) self.fillFields() self.form.fieldList.setCurrentRow(len(self.model["flds"]) - 1) @@ -145,7 +145,7 @@ class FieldDialog(QDialog): if len(self.model["flds"]) < 2: showWarning(tr.fields_notes_require_at_least_one_field()) return - count = self.mm.useCount(self.model) + count = self.mm.use_count(self.model) c = tr.browsing_note_count(count=count) if not askUser(tr.fields_delete_field_from(val=c)): return diff --git a/qt/aqt/modelchooser.py b/qt/aqt/modelchooser.py index f779a77b4..bb1680316 100644 --- a/qt/aqt/modelchooser.py +++ b/qt/aqt/modelchooser.py @@ -76,7 +76,7 @@ class ModelChooser(QHBoxLayout): edit = QPushButton(tr.qt_misc_manage(), clicked=self.onEdit) # type: ignore def nameFunc() -> List[str]: - return sorted(self.deck.models.allNames()) + return sorted(self.deck.models.all_names()) ret = StudyDeck( self.mw, @@ -92,7 +92,7 @@ class ModelChooser(QHBoxLayout): ) if not ret.name: return - m = self.deck.models.byName(ret.name) + m = self.deck.models.by_name(ret.name) self.deck.conf["curModel"] = m["id"] cdeck = self.deck.decks.current() cdeck["mid"] = m["id"] diff --git a/qt/aqt/notetypechooser.py b/qt/aqt/notetypechooser.py index 2f3b0a774..406534e9c 100644 --- a/qt/aqt/notetypechooser.py +++ b/qt/aqt/notetypechooser.py @@ -99,7 +99,7 @@ class NotetypeChooser(QHBoxLayout): qconnect(edit.clicked, self.onEdit) def nameFunc() -> List[str]: - return sorted(self.mw.col.models.allNames()) + return sorted(self.mw.col.models.all_names()) ret = StudyDeck( self.mw, @@ -116,7 +116,7 @@ class NotetypeChooser(QHBoxLayout): if not ret.name: return - notetype = self.mw.col.models.byName(ret.name) + notetype = self.mw.col.models.by_name(ret.name) if (id := notetype["id"]) != self._selected_notetype_id: self.selected_notetype_id = id