NF: CardID type

This commit is contained in:
Arthur Milchior 2021-03-23 10:36:52 +01:00 committed by Damien Elmes
parent 6ac540927a
commit 986efeed19
15 changed files with 80 additions and 64 deletions

View File

@ -5,7 +5,7 @@ from __future__ import annotations
import pprint
import time
from typing import List, Optional
from typing import List, NewType, Optional
import anki # pylint: disable=unused-import
import anki._backend.backend_pb2 as _pb
@ -26,6 +26,9 @@ from anki.sound import AVTag
# - rev queue: integer day
# - lrn queue: integer timestamp
# types
CardID = NewType("CardID", int)
class Card:
_note: Optional[Note]
@ -33,9 +36,10 @@ class Card:
lastIvl: int
ord: int
nid: anki.notes.NoteID
id: CardID
def __init__(
self, col: anki.collection.Collection, id: Optional[int] = None
self, col: anki.collection.Collection, id: Optional[CardID] = None
) -> None:
self.col = col.weakref()
self.timerStarted = None
@ -56,7 +60,7 @@ class Card:
def _load_from_backend_card(self, c: _pb.Card) -> None:
self._render_output = None
self._note = None
self.id = c.id
self.id = CardID(c.id)
self.nid = anki.notes.NoteID(c.note_id)
self.did = c.deck_id
self.ord = c.template_idx

View File

@ -34,7 +34,7 @@ from dataclasses import dataclass, field
import anki.latex
from anki import hooks
from anki._backend import RustBackend
from anki.cards import Card
from anki.cards import Card, CardID
from anki.config import Config, ConfigManager
from anki.consts import *
from anki.dbproxy import DBProxy
@ -319,7 +319,7 @@ class Collection:
# Object creation helpers
##########################################################################
def get_card(self, id: int) -> Card:
def get_card(self, id: CardID) -> Card:
return Card(self, id)
def update_card(self, card: Card) -> None:
@ -379,7 +379,7 @@ class Collection:
hooks.notes_will_be_deleted(self, note_ids)
return self._backend.remove_notes(note_ids=note_ids, card_ids=[])
def remove_notes_by_card(self, card_ids: List[int]) -> None:
def remove_notes_by_card(self, card_ids: List[CardID]) -> None:
if hooks.notes_will_be_deleted.count():
nids = self.db.list(
f"select nid from cards where id in {ids2str(card_ids)}"
@ -387,8 +387,8 @@ 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: NoteID) -> Sequence[int]:
return self._backend.cards_of_note(note_id)
def card_ids_of_note(self, note_id: NoteID) -> Sequence[CardID]:
return [CardID(id) for id in self._backend.cards_of_note(note_id)]
def defaults_for_adding(
self, *, current_review_card: Optional[Card]
@ -447,11 +447,11 @@ class Collection:
def cardCount(self) -> Any:
return self.db.scalar("select count() from cards")
def remove_cards_and_orphaned_notes(self, card_ids: Sequence[int]) -> None:
def remove_cards_and_orphaned_notes(self, card_ids: Sequence[CardID]) -> None:
"You probably want .remove_notes_by_card() instead."
self._backend.remove_cards(card_ids=card_ids)
def set_deck(self, card_ids: Sequence[int], deck_id: int) -> OpChanges:
def set_deck(self, card_ids: Sequence[CardID], deck_id: int) -> OpChanges:
return self._backend.set_deck(card_ids=card_ids, deck_id=deck_id)
def get_empty_cards(self) -> EmptyCardsReport:
@ -459,10 +459,10 @@ class Collection:
# legacy
def remCards(self, ids: List[int], notes: bool = True) -> None:
def remCards(self, ids: List[CardID], notes: bool = True) -> None:
self.remove_cards_and_orphaned_notes(ids)
def emptyCids(self) -> List[int]:
def emptyCids(self) -> List[CardID]:
print("emptyCids() will go away")
return []
@ -495,7 +495,7 @@ class Collection:
query: str,
order: Union[bool, str, BuiltinSort.Kind.V] = False,
reverse: bool = False,
) -> Sequence[int]:
) -> Sequence[CardID]:
"""Return card ids matching the provided search.
To programmatically construct a search string, see .build_search_string().
@ -525,7 +525,9 @@ class Collection:
mode = _pb.SortOrder(
builtin=_pb.SortOrder.Builtin(kind=order, reverse=reverse)
)
return self._backend.search_cards(search=query, order=mode)
return [
CardID(id) for id in self._backend.search_cards(search=query, order=mode)
]
def find_notes(self, *terms: Union[str, SearchNode]) -> Sequence[NoteID]:
"""Return note ids matching the provided search or searches.
@ -740,7 +742,7 @@ class Collection:
return CollectionStats(self)
def card_stats(self, card_id: int, include_revlog: bool) -> str:
def card_stats(self, card_id: CardID, include_revlog: bool) -> str:
import anki.stats as st
if include_revlog:
@ -1033,7 +1035,7 @@ table.review-log {{ {revlog_style} }}
##########################################################################
def set_user_flag_for_cards(self, flag: int, cids: Sequence[int]) -> OpChanges:
def set_user_flag_for_cards(self, flag: int, cids: Sequence[CardID]) -> OpChanges:
return self._backend.set_flag(card_ids=cids, flag=flag)
def set_wants_abort(self) -> None:

View File

@ -11,6 +11,7 @@ from typing import Any, Dict, Iterable, List, NewType, Optional, Sequence, Tuple
import anki # pylint: disable=unused-import
import anki._backend.backend_pb2 as _pb
from anki.cards import CardID
from anki.collection import OpChanges, OpChangesWithCount, OpChangesWithID
from anki.consts import *
from anki.errors import NotFoundError
@ -406,7 +407,7 @@ class DeckManager:
return deck["name"]
return None
def setDeck(self, cids: List[int], did: int) -> None:
def setDeck(self, cids: List[CardID], did: int) -> None:
self.col.db.execute(
f"update cards set did=?,usn=?,mod=? where id in {ids2str(cids)}",
did,
@ -414,7 +415,7 @@ class DeckManager:
intTime(),
)
def cids(self, did: int, children: bool = False) -> List[int]:
def cids(self, did: int, children: bool = False) -> List[CardID]:
if not children:
return self.col.db.list("select id from cards where did=?", did)
dids = [did]
@ -422,7 +423,7 @@ class DeckManager:
dids.append(id)
return self.col.db.list(f"select id from cards where did in {ids2str(dids)}")
def for_card_ids(self, cids: List[int]) -> List[int]:
def for_card_ids(self, cids: List[CardID]) -> List[int]:
return self.col.db.list(f"select did from cards where id in {ids2str(cids)}")
# Deck selection

View File

@ -12,6 +12,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union
from zipfile import ZipFile
from anki import hooks
from anki.cards import CardID
from anki.collection import Collection
from anki.lang import TR
from anki.utils import ids2str, namedtmp, splitFields, stripHTML
@ -28,7 +29,7 @@ class Exporter:
self,
col: Collection,
did: Optional[int] = None,
cids: Optional[List[int]] = None,
cids: Optional[List[CardID]] = None,
) -> None:
self.col = col.weakref()
self.did = did

View File

@ -5,6 +5,7 @@ import os
import unicodedata
from typing import Any, Dict, List, Optional, Tuple
from anki.cards import CardID
from anki.collection import Collection
from anki.consts import *
from anki.decks import DeckManager
@ -306,7 +307,7 @@ class Anki2Importer(Importer):
if self.source_needs_upgrade:
self.src.upgrade_to_v2_scheduler()
# build map of (guid, ord) -> cid and used id cache
self._cards: Dict[Tuple[str, int], int] = {}
self._cards: Dict[Tuple[str, int], CardID] = {}
existing = {}
for guid, ord, cid in self.dst.db.execute(
"select f.guid, c.ord, c.id from cards c, notes f " "where c.nid = f.id"

View File

@ -24,6 +24,7 @@ class Note:
# not currently exposed
flags = 0
data = ""
id: NoteID
def __init__(
self,
@ -122,7 +123,7 @@ class Note:
def cards(self) -> List[anki.cards.Card]:
return [self.col.getCard(id) for id in self.card_ids()]
def card_ids(self) -> Sequence[int]:
def card_ids(self) -> Sequence[anki.cards.CardID]:
return self.col.card_ids_of_note(self.id)
def model(self) -> Optional[NoteType]:

View File

@ -13,6 +13,7 @@ SchedTimingToday = _pb.SchedTimingTodayOut
from typing import List, Optional, Sequence
from anki.cards import CardID
from anki.consts import CARD_TYPE_NEW, NEW_CARDS_RANDOM, QUEUE_TYPE_NEW, QUEUE_TYPE_REV
from anki.decks import DeckConfigDict, DeckID, DeckTreeNode
from anki.notes import Note
@ -107,10 +108,10 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l
# Suspending & burying
##########################################################################
def unsuspend_cards(self, ids: Sequence[int]) -> OpChanges:
def unsuspend_cards(self, ids: Sequence[CardID]) -> OpChanges:
return self.col._backend.restore_buried_and_suspended_cards(ids)
def unbury_cards(self, ids: List[int]) -> OpChanges:
def unbury_cards(self, ids: List[CardID]) -> OpChanges:
return self.col._backend.restore_buried_and_suspended_cards(ids)
def unbury_cards_in_current_deck(
@ -119,12 +120,12 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l
) -> None:
self.col._backend.unbury_cards_in_current_deck(mode)
def suspend_cards(self, ids: Sequence[int]) -> OpChanges:
def suspend_cards(self, ids: Sequence[CardID]) -> OpChanges:
return self.col._backend.bury_or_suspend_cards(
card_ids=ids, mode=BuryOrSuspend.SUSPEND
)
def bury_cards(self, ids: Sequence[int], manual: bool = True) -> OpChanges:
def bury_cards(self, ids: Sequence[CardID], manual: bool = True) -> OpChanges:
if manual:
mode = BuryOrSuspend.BURY_USER
else:
@ -137,13 +138,13 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l
# Resetting/rescheduling
##########################################################################
def schedule_cards_as_new(self, card_ids: List[int]) -> OpChanges:
def schedule_cards_as_new(self, card_ids: List[CardID]) -> OpChanges:
"Put cards at the end of the new queue."
return self.col._backend.schedule_cards_as_new(card_ids=card_ids, log=True)
def set_due_date(
self,
card_ids: List[int],
card_ids: List[CardID],
days: str,
config_key: Optional[Config.String.Key.V] = None,
) -> OpChanges:
@ -162,7 +163,7 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l
config_key=key, # type: ignore
)
def resetCards(self, ids: List[int]) -> None:
def resetCards(self, ids: List[CardID]) -> None:
"Completely reset cards for export."
sids = ids2str(ids)
assert self.col.db
@ -184,7 +185,7 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l
def reposition_new_cards(
self,
card_ids: Sequence[int],
card_ids: Sequence[CardID],
starting_from: int,
step_size: int,
randomize: bool,
@ -223,7 +224,7 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l
# legacy
def sortCards(
self,
cids: List[int],
cids: List[CardID],
start: int = 1,
step: int = 1,
shuffle: bool = False,

View File

@ -3,7 +3,7 @@
from typing import List, Optional, Tuple
from anki.cards import Card
from anki.cards import Card, CardID
from anki.consts import CARD_TYPE_RELEARNING, QUEUE_TYPE_DAY_LEARN_RELEARN
from anki.decks import DeckConfigDict
from anki.notes import NoteID
@ -15,7 +15,7 @@ class SchedulerBaseWithLegacy(SchedulerBase):
"Legacy aliases and helpers. These will go away in the future."
def reschedCards(
self, card_ids: List[int], min_interval: int, max_interval: int
self, card_ids: List[CardID], min_interval: int, max_interval: int
) -> None:
self.set_due_date(card_ids, f"{min_interval}-{max_interval}!")
@ -80,7 +80,7 @@ due = (case when odue>0 then odue else due end), odue = 0, odid = 0, usn = ? whe
self.col.usn(),
)
def remFromDyn(self, cids: List[int]) -> None:
def remFromDyn(self, cids: List[CardID]) -> None:
self.emptyDyn(None, f"id in {ids2str(cids)} and odid")
# used by v2 scheduler and some add-ons

View File

@ -11,7 +11,7 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
import anki # pylint: disable=unused-import
import anki._backend.backend_pb2 as _pb
from anki import hooks
from anki.cards import Card
from anki.cards import Card, CardID
from anki.consts import *
from anki.decks import DeckConfigDict, DeckDict
from anki.lang import FormatTimeSpan
@ -144,7 +144,7 @@ class Scheduler(SchedulerBaseWithLegacy):
def _resetNew(self) -> None:
self._newDids = self.col.decks.active()[:]
self._newQueue: List[int] = []
self._newQueue: List[CardID] = []
self._updateNewCardRatio()
def _fillNew(self, recursing: bool = False) -> bool:
@ -301,8 +301,8 @@ select count() from cards where did in %s and queue = {QUEUE_TYPE_PREVIEW}
def _resetLrn(self) -> None:
self._updateLrnCutoff(force=True)
self._resetLrnCount()
self._lrnQueue: List[Tuple[int, int]] = []
self._lrnDayQueue: List[int] = []
self._lrnQueue: List[Tuple[int, CardID]] = []
self._lrnDayQueue: List[CardID] = []
self._lrnDids = self.col.decks.active()[:]
# sub-day learning
@ -397,7 +397,7 @@ did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN} and due <= ? limit ?""",
return hooks.scheduler_review_limit_for_single_deck(lim, d)
def _resetRev(self) -> None:
self._revQueue: List[int] = []
self._revQueue: List[CardID] = []
def _fillRev(self, recursing: bool = False) -> bool:
"True if a review card can be fetched."
@ -1072,7 +1072,7 @@ limit ?"""
##########################################################################
def _burySiblings(self, card: Card) -> None:
toBury: List[int] = []
toBury: List[CardID] = []
nconf = self._newConf(card)
buryNew = nconf.get("bury", True)
rconf = self._revConf(card)

View File

@ -22,7 +22,7 @@ from typing import (
import aqt
import aqt.forms
from anki.cards import Card
from anki.cards import Card, CardID
from anki.collection import BrowserRow, Collection, Config, OpChanges, SearchNode
from anki.consts import *
from anki.errors import NotFoundError
@ -97,7 +97,7 @@ class SearchContext:
browser: Browser
order: Union[bool, str] = True
# if set, provided card ids will be used instead of the regular search
card_ids: Optional[Sequence[int]] = None
card_ids: Optional[Sequence[CardID]] = None
# Data model
@ -170,13 +170,13 @@ class DataModel(QAbstractTableModel):
self.activeCols: List[str] = self.col.get_config(
"activeCols", ["noteFld", "template", "cardDue", "deck"]
)
self.cards: Sequence[int] = []
self.cards: Sequence[CardID] = []
self._rows: Dict[int, CellRow] = {}
self._last_refresh = 0.0
# serve stale content to avoid hitting the DB?
self.block_updates = False
def get_id(self, index: QModelIndex) -> int:
def get_id(self, index: QModelIndex) -> CardID:
return self.cards[index.row()]
def get_cell(self, index: QModelIndex) -> Cell:
@ -198,7 +198,7 @@ class DataModel(QAbstractTableModel):
self._rows[cid] = self._fetch_row_from_backend(cid)
return self._rows[cid]
def _fetch_row_from_backend(self, cid: int) -> CellRow:
def _fetch_row_from_backend(self, cid: CardID) -> CellRow:
try:
row = CellRow(*self.col.browser_row_for_card(cid))
except NotFoundError:
@ -1049,7 +1049,7 @@ QTableView {{ gridline-color: {grid} }}
# Menu helpers
######################################################################
def selected_cards(self) -> List[int]:
def selected_cards(self) -> List[CardID]:
return [
self.model.cards[idx.row()]
for idx in self.form.tableView.selectionModel().selectedRows()
@ -1068,7 +1068,7 @@ where id in %s"""
)
)
def selectedNotesAsCards(self) -> List[int]:
def selectedNotesAsCards(self) -> List[CardID]:
return self.col.db.list(
"select id from cards where nid in (%s)"
% ",".join([str(s) for s in self.selected_notes()])
@ -1590,7 +1590,7 @@ where id in %s"""
def onCardList(self) -> None:
self.form.tableView.setFocus()
def focusCid(self, cid: int) -> None:
def focusCid(self, cid: CardID) -> None:
try:
row = list(self.model.cards).index(cid)
except ValueError:

View File

@ -5,12 +5,14 @@ from __future__ import annotations
from typing import Sequence
from anki.cards import CardID
from anki.decks import DeckID
from aqt import AnkiQt
def set_card_deck(*, mw: AnkiQt, card_ids: Sequence[int], deck_id: int) -> None:
def set_card_deck(*, mw: AnkiQt, card_ids: Sequence[CardID], deck_id: DeckID) -> None:
mw.perform_op(lambda: mw.col.set_deck(card_ids, deck_id))
def set_card_flag(*, mw: AnkiQt, card_ids: Sequence[int], flag: int) -> None:
def set_card_flag(*, mw: AnkiQt, card_ids: Sequence[CardID], flag: int) -> None:
mw.perform_op(lambda: mw.col.set_user_flag_for_cards(flag, card_ids))

View File

@ -5,9 +5,10 @@ from __future__ import annotations
import re
from concurrent.futures import Future
from typing import Any
from typing import Any, List
import aqt
from anki.cards import CardID
from anki.collection import EmptyCardsReport
from aqt import gui_hooks
from aqt.qt import QDialog, QDialogButtonBox, qconnect
@ -88,14 +89,14 @@ class EmptyCardsDialog(QDialog):
self.mw.taskman.run_in_background(delete, on_done)
def _delete_cards(self, keep_notes: bool) -> int:
to_delete = []
to_delete: List[CardID] = []
note: EmptyCardsReport.NoteWithEmptyCards
for note in self.report.notes:
if keep_notes and note.will_delete_note:
# leave first card
to_delete.extend(note.card_ids[1:])
to_delete.extend([CardID(id) for id in note.card_ids[1:]])
else:
to_delete.extend(note.card_ids)
to_delete.extend([CardID(id) for id in note.card_ids])
self.mw.col.remove_cards_and_orphaned_notes(to_delete)
return len(to_delete)

View File

@ -11,6 +11,7 @@ from typing import List, Optional
import aqt
from anki import hooks
from anki.cards import CardID
from anki.exporting import Exporter, exporters
from aqt.qt import *
from aqt.utils import (
@ -29,7 +30,7 @@ class ExportDialog(QDialog):
self,
mw: aqt.main.AnkiQt,
did: Optional[int] = None,
cids: Optional[List[int]] = None,
cids: Optional[List[CardID]] = None,
):
QDialog.__init__(self, mw, Qt.Window)
self.mw = mw

View File

@ -13,7 +13,7 @@ from typing import Any, Callable, List, Match, Optional, Sequence, Tuple, Union
from PyQt5.QtCore import Qt
from anki import hooks
from anki.cards import Card
from anki.cards import Card, CardID
from anki.collection import Config, OpChanges
from anki.tags import MARKED_TAG
from anki.utils import stripHTML
@ -74,7 +74,7 @@ class Reviewer:
self.card: Optional[Card] = None
self.cardQueue: List[Card] = []
self.hadCardQueue = False
self._answeredIds: List[int] = []
self._answeredIds: List[CardID] = []
self._recordedAudio: Optional[str] = None
self.typeCorrect: str = None # web init happens before this is set
self.state: Optional[str] = None

View File

@ -6,6 +6,7 @@ from __future__ import annotations
from typing import List, Optional, Sequence
import aqt
from anki.cards import CardID
from anki.collection import CARD_TYPE_NEW, Config
from anki.decks import DeckID
from anki.lang import TR
@ -21,7 +22,7 @@ def set_due_date_dialog(
*,
mw: aqt.AnkiQt,
parent: QWidget,
card_ids: List[int],
card_ids: List[CardID],
config_key: Optional[Config.String.Key.V],
) -> None:
if not card_ids:
@ -54,7 +55,7 @@ def set_due_date_dialog(
)
def forget_cards(*, mw: aqt.AnkiQt, parent: QWidget, card_ids: List[int]) -> None:
def forget_cards(*, mw: aqt.AnkiQt, parent: QWidget, card_ids: List[CardID]) -> None:
if not card_ids:
return
@ -67,7 +68,7 @@ def forget_cards(*, mw: aqt.AnkiQt, parent: QWidget, card_ids: List[int]) -> Non
def reposition_new_cards_dialog(
*, mw: AnkiQt, parent: QWidget, card_ids: Sequence[int]
*, mw: AnkiQt, parent: QWidget, card_ids: Sequence[CardID]
) -> None:
assert mw.col.db
row = mw.col.db.first(
@ -112,7 +113,7 @@ def reposition_new_cards(
*,
mw: AnkiQt,
parent: QWidget,
card_ids: Sequence[int],
card_ids: Sequence[CardID],
starting_from: int,
step_size: int,
randomize: bool,
@ -135,7 +136,7 @@ def reposition_new_cards(
def suspend_cards(
*,
mw: AnkiQt,
card_ids: Sequence[int],
card_ids: Sequence[CardID],
success: PerformOpOptionalSuccessCallback = None,
) -> None:
mw.perform_op(lambda: mw.col.sched.suspend_cards(card_ids), success=success)
@ -153,14 +154,14 @@ def suspend_note(
)
def unsuspend_cards(*, mw: AnkiQt, card_ids: Sequence[int]) -> None:
def unsuspend_cards(*, mw: AnkiQt, card_ids: Sequence[CardID]) -> None:
mw.perform_op(lambda: mw.col.sched.unsuspend_cards(card_ids))
def bury_cards(
*,
mw: AnkiQt,
card_ids: Sequence[int],
card_ids: Sequence[CardID],
success: PerformOpOptionalSuccessCallback = None,
) -> None:
mw.perform_op(lambda: mw.col.sched.bury_cards(card_ids), success=success)