NF: NoteID type

This commit is contained in:
Arthur Milchior 2021-03-25 13:50:31 +01:00 committed by Damien Elmes
parent 23083c3eb4
commit 6ac540927a
18 changed files with 82 additions and 59 deletions

View File

@ -32,6 +32,7 @@ class Card:
timerStarted: Optional[float]
lastIvl: int
ord: int
nid: anki.notes.NoteID
def __init__(
self, col: anki.collection.Collection, id: Optional[int] = None
@ -56,7 +57,7 @@ class Card:
self._render_output = None
self._note = None
self.id = c.id
self.nid = c.note_id
self.nid = anki.notes.NoteID(c.note_id)
self.did = c.deck_id
self.ord = c.template_idx
self.mod = c.mtime_secs

View File

@ -43,7 +43,7 @@ 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.notes import Note
from anki.notes import Note, NoteID
from anki.scheduler.v1 import Scheduler as V1Scheduler
from anki.scheduler.v2 import Scheduler as V2Scheduler
from anki.scheduler.v3 import Scheduler as V3TestScheduler
@ -327,7 +327,7 @@ class Collection:
Unlike card.flush(), this will invalidate any current checkpoint."""
self._backend.update_card(card=card._to_backend_card(), skip_undo_entry=False)
def get_note(self, id: int) -> Note:
def get_note(self, id: NoteID) -> Note:
return Note(self, id=id)
def update_note(self, note: Note) -> OpChanges:
@ -358,7 +358,7 @@ class Collection:
# Deletion logging
##########################################################################
def _logRem(self, ids: List[int], type: int) -> None:
def _logRem(self, ids: List[Union[int, NoteID]], type: int) -> None:
self.db.executemany(
"insert into graves values (%d, ?, %d)" % (self.usn(), type),
([x] for x in ids),
@ -372,10 +372,10 @@ class Collection:
def add_note(self, note: Note, deck_id: int) -> OpChanges:
out = self._backend.add_note(note=note._to_backend_note(), deck_id=deck_id)
note.id = out.note_id
note.id = NoteID(out.note_id)
return out.changes
def remove_notes(self, note_ids: Sequence[int]) -> OpChanges:
def remove_notes(self, note_ids: Sequence[NoteID]) -> OpChanges:
hooks.notes_will_be_deleted(self, note_ids)
return self._backend.remove_notes(note_ids=note_ids, card_ids=[])
@ -387,7 +387,7 @@ class Collection:
hooks.notes_will_be_deleted(self, nids)
self._backend.remove_notes(note_ids=[], card_ids=card_ids)
def card_ids_of_note(self, note_id: int) -> Sequence[int]:
def card_ids_of_note(self, note_id: NoteID) -> Sequence[int]:
return self._backend.cards_of_note(note_id)
def defaults_for_adding(
@ -406,7 +406,7 @@ class Collection:
home_deck_of_current_review_card=home_deck,
)
def default_deck_for_notetype(self, notetype_id: int) -> Optional[int]:
def default_deck_for_notetype(self, notetype_id: NoteID) -> Optional[int]:
"""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):
@ -432,10 +432,10 @@ class Collection:
self.add_note(note, note.model()["did"])
return len(note.cards())
def remNotes(self, ids: Sequence[int]) -> None:
def remNotes(self, ids: Sequence[NoteID]) -> None:
self.remove_notes(ids)
def _remNotes(self, ids: List[int]) -> None:
def _remNotes(self, ids: List[NoteID]) -> None:
pass
# Cards
@ -470,7 +470,7 @@ class Collection:
##########################################################################
def after_note_updates(
self, nids: List[int], mark_modified: bool, generate_cards: bool = True
self, nids: List[NoteID], mark_modified: bool, generate_cards: bool = True
) -> None:
self._backend.after_note_updates(
nids=nids, generate_cards=generate_cards, mark_notes_modified=mark_modified
@ -478,11 +478,11 @@ class Collection:
# legacy
def updateFieldCache(self, nids: List[int]) -> None:
def updateFieldCache(self, nids: List[NoteID]) -> None:
self.after_note_updates(nids, mark_modified=False, generate_cards=False)
# this also updates field cache
def genCards(self, nids: List[int]) -> List[int]:
def genCards(self, nids: List[NoteID]) -> List[int]:
self.after_note_updates(nids, mark_modified=False, generate_cards=True)
# previously returned empty cards, no longer does
return []
@ -527,7 +527,7 @@ class Collection:
)
return self._backend.search_cards(search=query, order=mode)
def find_notes(self, *terms: Union[str, SearchNode]) -> Sequence[int]:
def find_notes(self, *terms: Union[str, SearchNode]) -> Sequence[NoteID]:
"""Return note ids matching the provided search or searches.
If more than one search is provided, they will be ANDed together.
@ -538,12 +538,15 @@ class Collection:
Eg: col.find_notes(SearchNode(deck="test"), "foo") will return notes
that have a card in deck called "test", and have the text "foo".
"""
return self._backend.search_notes(self.build_search_string(*terms))
return [
NoteID(did)
for did in self._backend.search_notes(self.build_search_string(*terms))
]
def find_and_replace(
self,
*,
note_ids: Sequence[int],
note_ids: Sequence[NoteID],
search: str,
replacement: str,
regex: bool = False,

View File

@ -6,6 +6,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Optional, Set
from anki.hooks import *
from anki.notes import NoteID
if TYPE_CHECKING:
from anki.collection import Collection
@ -29,7 +30,7 @@ class Finder:
def findReplace(
col: Collection,
nids: List[int],
nids: List[NoteID],
src: str,
dst: str,
regex: bool = False,
@ -48,7 +49,7 @@ def findReplace(
).count
def fieldNamesForNotes(col: Collection, nids: List[int]) -> List[str]:
def fieldNamesForNotes(col: Collection, nids: List[NoteID]) -> List[str]:
return list(col.field_names_for_note_ids(nids))

View File

@ -10,6 +10,7 @@ from anki.consts import *
from anki.decks import DeckManager
from anki.importing.base import Importer
from anki.lang import TR
from anki.notes import NoteID
from anki.utils import intTime, joinFields, splitFields, stripHTMLMedia
GUID = 1
@ -79,7 +80,7 @@ class Anki2Importer(Importer):
def _importNotes(self) -> None:
# build guid -> (id,mod,mid) hash & map of existing note ids
self._notes: Dict[str, Tuple[int, int, int]] = {}
self._notes: Dict[str, Tuple[NoteID, int, int]] = {}
existing = {}
for id, guid, mod, mid in self.dst.db.execute(
"select id, guid, mod, mid from notes"

View File

@ -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.notes import NoteID
from anki.utils import (
fieldChecksum,
guid64,
@ -19,9 +20,9 @@ from anki.utils import (
timestampID,
)
type_tagsMapped = Tuple[int, int, str, str, int, str, str]
type_tagsModified = Tuple[int, int, str, str, int, str]
type_tagsElse = Tuple[int, int, str, int, str]
type_tagsMapped = Tuple[int, int, str, str, NoteID, str, str]
type_tagsModified = Tuple[int, int, str, str, NoteID, str]
type_tagsElse = Tuple[int, int, str, NoteID, str]
type_udpates = Union[type_tagsMapped, type_tagsModified, type_tagsElse]
# Stores a list of fields, tags and deck
@ -127,7 +128,7 @@ class NoteImporter(Importer):
if f == "_tags":
self._tagsMapped = True
# gather checks for duplicate comparison
csums: Dict[str, List[int]] = {}
csums: Dict[str, List[NoteID]] = {}
for csum, id in self.col.db.execute(
"select csum, id from notes where mid = ?", self.model["id"]
):
@ -138,12 +139,12 @@ class NoteImporter(Importer):
firsts: Dict[str, bool] = {}
fld0idx = self.mapping.index(self.model["flds"][0]["name"])
self._fmap = self.col.models.fieldMap(self.model)
self._nextID = timestampID(self.col.db, "notes")
self._nextID = NoteID(timestampID(self.col.db, "notes"))
# loop through the notes
updates: List[type_udpates] = []
updateLog = []
new = []
self._ids: List[int] = []
self._ids: List[NoteID] = []
self._cards: List[Tuple] = []
dupeCount = 0
dupes: List[str] = []
@ -242,9 +243,9 @@ class NoteImporter(Importer):
def newData(
self, n: ForeignNote
) -> Tuple[int, str, int, int, int, str, str, str, int, int, str]:
) -> Tuple[NoteID, str, int, int, int, str, str, str, int, int, str]:
id = self._nextID
self._nextID += 1
self._nextID = NoteID(self._nextID + 1)
self._ids.append(id)
self.processFields(n)
# note id for card updates later
@ -266,14 +267,14 @@ class NoteImporter(Importer):
def addNew(
self,
rows: List[Tuple[int, str, int, int, int, str, str, str, int, int, str]],
rows: List[Tuple[NoteID, str, int, int, int, str, str, str, int, int, str]],
) -> None:
self.col.db.executemany(
"insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)", rows
)
def updateData(
self, n: ForeignNote, id: int, sflds: List[str]
self, n: ForeignNote, id: NoteID, sflds: List[str]
) -> Optional[type_udpates]:
self._ids.append(id)
self.processFields(n, sflds)

View File

@ -257,7 +257,7 @@ class ModelManager:
# Tools
##################################################
def nids(self, ntid: int) -> List[int]:
def nids(self, ntid: int) -> List[anki.notes.NoteID]:
"Note ids for M."
if isinstance(ntid, dict):
# legacy callers passed in note type
@ -420,7 +420,7 @@ and notes.mid = ? and cards.ord = ?""",
def change(
self,
m: NoteType,
nids: List[int],
nids: List[anki.notes.NoteID],
newModel: NoteType,
fmap: Optional[Dict[int, Union[None, int]]],
cmap: Optional[Dict[int, Union[None, int]]],
@ -434,7 +434,10 @@ and notes.mid = ? and cards.ord = ?""",
self.col.after_note_updates(nids, mark_modified=True)
def _changeNotes(
self, nids: List[int], newModel: NoteType, map: Dict[int, Union[None, int]]
self,
nids: List[anki.notes.NoteID],
newModel: NoteType,
map: Dict[int, Union[None, int]],
) -> None:
d = []
nfields = len(newModel["flds"])
@ -464,7 +467,7 @@ and notes.mid = ? and cards.ord = ?""",
def _changeCards(
self,
nids: List[int],
nids: List[anki.notes.NoteID],
oldModel: NoteType,
newModel: NoteType,
map: Dict[int, Union[None, int]],

View File

@ -5,7 +5,7 @@ from __future__ import annotations
import copy
import pprint
from typing import Any, List, Optional, Sequence, Tuple
from typing import Any, List, NewType, Optional, Sequence, Tuple
import anki # pylint: disable=unused-import
import anki._backend.backend_pb2 as _pb
@ -16,6 +16,9 @@ from anki.utils import joinFields
DuplicateOrEmptyResult = _pb.NoteIsDuplicateOrEmptyOut.State
# types
NoteID = NewType("NoteID", int)
class Note:
# not currently exposed
@ -26,7 +29,7 @@ class Note:
self,
col: anki.collection.Collection,
model: Optional[NoteType] = None,
id: Optional[int] = None,
id: Optional[NoteID] = None,
) -> None:
assert not (model and id)
self.col = col.weakref()
@ -46,7 +49,7 @@ class Note:
self._load_from_backend_note(n)
def _load_from_backend_note(self, n: _pb.Note) -> None:
self.id = n.id
self.id = NoteID(n.id)
self.guid = n.guid
self.mid = n.notetype_id
self.mod = n.mtime_secs

View File

@ -6,6 +6,7 @@ from typing import List, Optional, Tuple
from anki.cards import Card
from anki.consts import CARD_TYPE_RELEARNING, QUEUE_TYPE_DAY_LEARN_RELEARN
from anki.decks import DeckConfigDict
from anki.notes import NoteID
from anki.scheduler.base import SchedulerBase, UnburyCurrentDeck
from anki.utils import from_json_bytes, ids2str
@ -18,7 +19,7 @@ class SchedulerBaseWithLegacy(SchedulerBase):
) -> None:
self.set_due_date(card_ids, f"{min_interval}-{max_interval}!")
def buryNote(self, nid: int) -> None:
def buryNote(self, nid: NoteID) -> None:
note = self.col.get_note(nid)
self.bury_cards(note.card_ids())

View File

@ -19,6 +19,7 @@ import anki # pylint: disable=unused-import
import anki._backend.backend_pb2 as _pb
import anki.collection
from anki.collection import OpChangesWithCount
from anki.notes import NoteID
from anki.utils import ids2str
# public exports
@ -68,11 +69,11 @@ class TagManager:
# Bulk addition/removal from specific notes
#############################################################
def bulk_add(self, note_ids: Sequence[int], tags: str) -> OpChangesWithCount:
def bulk_add(self, note_ids: Sequence[NoteID], tags: str) -> OpChangesWithCount:
"""Add space-separate tags to provided notes, returning changed count."""
return self.col._backend.add_note_tags(note_ids=note_ids, tags=tags)
def bulk_remove(self, note_ids: Sequence[int], tags: str) -> OpChangesWithCount:
def bulk_remove(self, note_ids: Sequence[NoteID], tags: str) -> OpChangesWithCount:
return self.col._backend.remove_note_tags(note_ids=note_ids, tags=tags)
# Find&replace
@ -175,12 +176,12 @@ class TagManager:
) -> None:
print("tags.register() is deprecated and no longer works")
def bulkAdd(self, ids: List[int], tags: str, add: bool = True) -> None:
def bulkAdd(self, ids: List[NoteID], tags: str, add: bool = True) -> None:
"Add tags in bulk. TAGS is space-separated."
if add:
self.bulk_add(ids, tags)
else:
self.bulk_remove(ids, tags)
def bulkRem(self, ids: List[int], tags: str) -> None:
def bulkRem(self, ids: List[NoteID], tags: str) -> None:
self.bulkAdd(ids, tags, False)

View File

@ -23,7 +23,7 @@ hooks = [
Hook(name="schema_will_change", args=["proceed: bool"], return_type="bool"),
Hook(
name="notes_will_be_deleted",
args=["col: anki.collection.Collection", "ids: Sequence[int]"],
args=["col: anki.collection.Collection", "ids: Sequence[anki.notes.NoteID]"],
legacy_hook="remNotes",
),
Hook(name="media_files_did_export", args=["count: int"]),

View File

@ -8,7 +8,7 @@ import aqt.editor
import aqt.forms
from anki.collection import OpChanges, SearchNode
from anki.consts import MODEL_CLOZE
from anki.notes import DuplicateOrEmptyResult, Note
from anki.notes import DuplicateOrEmptyResult, Note, NoteID
from anki.utils import htmlToTextLine, isMac
from aqt import AnkiQt, gui_hooks
from aqt.note_ops import add_note
@ -47,7 +47,7 @@ class AddCards(QDialog):
self.setupEditor()
self.setupButtons()
self._load_new_note()
self.history: List[int] = []
self.history: List[NoteID] = []
self._last_added_note: Optional[Note] = None
restoreGeom(self, "add")
addCloseShortcut(self)
@ -109,7 +109,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: int) -> None:
def on_notetype_change(self, notetype_id: NoteID) -> 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
@ -178,7 +178,7 @@ class AddCards(QDialog):
gui_hooks.add_cards_will_show_history_menu(self, m)
m.exec_(self.historyButton.mapToGlobal(QPoint(0, 0)))
def editHistory(self, nid: int) -> None:
def editHistory(self, nid: NoteID) -> None:
aqt.dialogs.open("Browser", self.mw, search=(SearchNode(nid=nid),))
def add_current_note(self) -> None:

View File

@ -28,6 +28,7 @@ from anki.consts import *
from anki.errors import NotFoundError
from anki.lang import without_unicode_isolation
from anki.models import NoteType
from anki.notes import NoteID
from anki.stats import CardStats
from anki.tags import MARKED_TAG
from anki.utils import ids2str, isMac, isWin
@ -1054,7 +1055,7 @@ QTableView {{ gridline-color: {grid} }}
for idx in self.form.tableView.selectionModel().selectedRows()
]
def selected_notes(self) -> List[int]:
def selected_notes(self) -> List[NoteID]:
return self.col.db.list(
"""
select distinct nid from cards
@ -1073,7 +1074,7 @@ where id in %s"""
% ",".join([str(s) for s in self.selected_notes()])
)
def oneModelNotes(self) -> List[int]:
def oneModelNotes(self) -> List[NoteID]:
sf = self.selected_notes()
if not sf:
return []
@ -1603,7 +1604,7 @@ where id in %s"""
class ChangeModel(QDialog):
def __init__(self, browser: Browser, nids: List[int]) -> None:
def __init__(self, browser: Browser, nids: List[NoteID]) -> None:
QDialog.__init__(self, browser)
self.browser = browser
self.nids = nids

View File

@ -7,6 +7,7 @@ from typing import List, Optional, Sequence
import aqt
from anki.lang import TR
from anki.notes import NoteID
from aqt import AnkiQt, QWidget
from aqt.qt import QDialog, Qt
from aqt.utils import (
@ -31,7 +32,7 @@ def find_and_replace(
*,
mw: AnkiQt,
parent: QWidget,
note_ids: Sequence[int],
note_ids: Sequence[NoteID],
search: str,
replacement: str,
regex: bool,
@ -82,7 +83,9 @@ def find_and_replace_tag(
class FindAndReplaceDialog(QDialog):
COMBO_NAME = "BrowserFindAndReplace"
def __init__(self, parent: QWidget, *, mw: AnkiQt, note_ids: Sequence[int]) -> None:
def __init__(
self, parent: QWidget, *, mw: AnkiQt, note_ids: Sequence[NoteID]
) -> None:
super().__init__(parent)
self.mw = mw
self.note_ids = note_ids

View File

@ -54,6 +54,7 @@ from anki.collection import (
)
from anki.decks import DeckDict
from anki.hooks import runHook
from anki.notes import NoteID
from anki.sound import AVTag, SoundOrVideoTag
from anki.types import assert_exhaustive
from anki.utils import devMode, ids2str, intTime, isMac, isWin, splitFields
@ -1534,7 +1535,7 @@ title="%s" %s>%s</button>""" % (
# Log note deletion
##########################################################################
def onRemNotes(self, col: Collection, nids: Sequence[int]) -> None:
def onRemNotes(self, col: Collection, nids: Sequence[NoteID]) -> None:
path = os.path.join(self.pm.profileFolder(), "deleted.txt")
existed = os.path.exists(path)
with open(path, "ab") as f:

View File

@ -5,7 +5,7 @@ from __future__ import annotations
from typing import Callable, Sequence
from anki.notes import Note
from anki.notes import Note, NoteID
from aqt import AnkiQt
from aqt.main import PerformOpOptionalSuccessCallback
@ -30,7 +30,7 @@ def update_note(*, mw: AnkiQt, note: Note, after_hooks: Callable[[], None]) -> N
def remove_notes(
*,
mw: AnkiQt,
note_ids: Sequence[int],
note_ids: Sequence[NoteID],
success: PerformOpOptionalSuccessCallback = None,
) -> None:
mw.perform_op(lambda: mw.col.remove_notes(note_ids), success=success)

View File

@ -2,6 +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 aqt import AnkiQt, gui_hooks
from aqt.qt import *
from aqt.utils import TR, HelpPage, shortcut, tr
@ -29,7 +30,7 @@ class NoteTypeChooser(QHBoxLayout):
widget: QWidget,
starting_notetype_id: int,
on_button_activated: Optional[Callable[[], None]] = None,
on_notetype_changed: Optional[Callable[[int], None]] = None,
on_notetype_changed: Optional[Callable[[NoteID], None]] = None,
show_prefix_label: bool = True,
) -> None:
QHBoxLayout.__init__(self)

View File

@ -9,6 +9,7 @@ import aqt
from anki.collection import CARD_TYPE_NEW, Config
from anki.decks import DeckID
from anki.lang import TR
from anki.notes import NoteID
from anki.scheduler import FilteredDeckForUpdate
from aqt import AnkiQt
from aqt.main import PerformOpOptionalSuccessCallback
@ -143,7 +144,7 @@ def suspend_cards(
def suspend_note(
*,
mw: AnkiQt,
note_id: int,
note_id: NoteID,
success: PerformOpOptionalSuccessCallback = None,
) -> None:
mw.taskman.run_in_background(
@ -168,7 +169,7 @@ def bury_cards(
def bury_note(
*,
mw: AnkiQt,
note_id: int,
note_id: NoteID,
success: PerformOpOptionalSuccessCallback = None,
) -> None:
mw.taskman.run_in_background(

View File

@ -7,6 +7,7 @@ from typing import Callable, Sequence
from anki.collection import OpChangesWithCount
from anki.lang import TR
from anki.notes import NoteID
from aqt import AnkiQt, QWidget
from aqt.main import PerformOpOptionalSuccessCallback
from aqt.utils import showInfo, tooltip, tr
@ -15,7 +16,7 @@ from aqt.utils import showInfo, tooltip, tr
def add_tags(
*,
mw: AnkiQt,
note_ids: Sequence[int],
note_ids: Sequence[NoteID],
space_separated_tags: str,
success: PerformOpOptionalSuccessCallback = None,
) -> None:
@ -27,7 +28,7 @@ def add_tags(
def remove_tags_for_notes(
*,
mw: AnkiQt,
note_ids: Sequence[int],
note_ids: Sequence[NoteID],
space_separated_tags: str,
success: PerformOpOptionalSuccessCallback = None,
) -> None: