diff --git a/pylib/anki/_legacy.py b/pylib/anki/_legacy.py
index 3d6e73358..b0b7d2293 100644
--- a/pylib/anki/_legacy.py
+++ b/pylib/anki/_legacy.py
@@ -3,7 +3,11 @@
from __future__ import annotations
-from typing import Any, Callable, Dict, Optional, Tuple, Union
+import functools
+import os
+import pathlib
+import traceback
+from typing import Any, Callable, Dict, Optional, Tuple, Union, no_type_check
import stringcase
@@ -18,20 +22,37 @@ def _target_to_string(target: DeprecatedAliasTarget) -> str:
return target[1] # type: ignore
+def partial_path(full_path: str, components: int) -> str:
+ path = pathlib.Path(full_path)
+ return os.path.join(*path.parts[-components:])
+
+
+def _print_deprecation_warning(old: str, doc: str) -> None:
+ path, linenum, fn, y = traceback.extract_stack(limit=5)[2]
+ path = partial_path(path, components=3)
+ print(f"{path}:{linenum}:{old} is deprecated: {doc}")
+
+
class DeprecatedNamesMixin:
"Expose instance methods/vars as camelCase for legacy callers."
+ # the @no_type_check lines are required to prevent mypy allowing arbitrary
+ # attributes on the consuming class
+
_deprecated_aliases: Dict[str, str] = {}
+ @no_type_check
def __getattr__(self, name: str) -> Any:
remapped = self._deprecated_aliases.get(name) or stringcase.snakecase(name)
if remapped == name:
raise AttributeError
out = getattr(self, remapped)
- print(f"please use {remapped} instead of {name} on {self}")
+ _print_deprecation_warning(f"'{name}'", f"please use '{remapped}'")
+
return out
+ @no_type_check
@classmethod
def register_deprecated_aliases(cls, **kwargs: DeprecatedAliasTarget) -> None:
"""Manually add aliases that are not a simple transform.
@@ -47,15 +68,15 @@ def deprecated(replaced_by: Optional[Callable] = None, info: str = "") -> Callab
"""Print a deprecation warning, telling users to use `replaced_by`, or show `doc`."""
def decorator(func: Callable) -> Callable:
+ @functools.wraps(func)
def decorated_func(*args: Any, **kwargs: Any) -> Any:
if replaced_by:
doc = f"please use {replaced_by.__name__} instead."
else:
doc = info
- print(
- f"'{func.__name__}' is deprecated, and will be removed in the future: ",
- doc,
- )
+
+ _print_deprecation_warning(f"{func.__name__}()", doc)
+
return func(*args, **kwargs)
return decorated_func
diff --git a/pylib/anki/cards.py b/pylib/anki/cards.py
index 809efd577..bd227bdcf 100644
--- a/pylib/anki/cards.py
+++ b/pylib/anki/cards.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: enable=invalid-name
+
from __future__ import annotations
import pprint
@@ -10,6 +12,7 @@ from typing import List, NewType, Optional
import anki # pylint: disable=unused-import
import anki._backend.backend_pb2 as _pb
from anki import hooks
+from anki._legacy import DeprecatedNamesMixin, deprecated
from anki.consts import *
from anki.models import NotetypeDict, TemplateDict
from anki.notes import Note
@@ -22,7 +25,7 @@ from anki.sound import AVTag
# Queue: same as above, and:
# -1=suspended, -2=user buried, -3=sched buried
# Due is used differently for different queues.
-# - new queue: note id or random int
+# - new queue: position
# - rev queue: integer day
# - lrn queue: integer timestamp
@@ -31,9 +34,8 @@ CardId = NewType("CardId", int)
BackendCard = _pb.Card
-class Card:
+class Card(DeprecatedNamesMixin):
_note: Optional[Note]
- timerStarted: Optional[float]
lastIvl: int
ord: int
nid: anki.notes.NoteId
@@ -50,7 +52,7 @@ class Card:
backend_card: Optional[BackendCard] = None,
) -> None:
self.col = col.weakref()
- self.timerStarted = None
+ self.timer_started: Optional[float] = None
self._render_output: Optional[anki.template.TemplateRenderOutput] = None
if id:
# existing card
@@ -63,31 +65,31 @@ class Card:
self._load_from_backend_card(_pb.Card())
def load(self) -> None:
- c = self.col._backend.get_card(self.id)
- assert c
- self._load_from_backend_card(c)
+ card = self.col._backend.get_card(self.id)
+ assert card
+ self._load_from_backend_card(card)
- def _load_from_backend_card(self, c: _pb.Card) -> None:
+ def _load_from_backend_card(self, card: _pb.Card) -> None:
self._render_output = None
self._note = None
- self.id = CardId(c.id)
- self.nid = anki.notes.NoteId(c.note_id)
- self.did = anki.decks.DeckId(c.deck_id)
- self.ord = c.template_idx
- self.mod = c.mtime_secs
- self.usn = c.usn
- self.type = CardType(c.ctype)
- self.queue = CardQueue(c.queue)
- self.due = c.due
- self.ivl = c.interval
- self.factor = c.ease_factor
- self.reps = c.reps
- self.lapses = c.lapses
- self.left = c.remaining_steps
- self.odue = c.original_due
- self.odid = anki.decks.DeckId(c.original_deck_id)
- self.flags = c.flags
- self.data = c.data
+ self.id = CardId(card.id)
+ self.nid = anki.notes.NoteId(card.note_id)
+ self.did = anki.decks.DeckId(card.deck_id)
+ self.ord = card.template_idx
+ self.mod = card.mtime_secs
+ self.usn = card.usn
+ self.type = CardType(card.ctype)
+ self.queue = CardQueue(card.queue)
+ self.due = card.due
+ self.ivl = card.interval
+ self.factor = card.ease_factor
+ self.reps = card.reps
+ self.lapses = card.lapses
+ self.left = card.remaining_steps
+ self.odue = card.original_due
+ self.odid = anki.decks.DeckId(card.original_deck_id)
+ self.flags = card.flags
+ self.data = card.data
def _to_backend_card(self) -> _pb.Card:
# mtime & usn are set by backend
@@ -131,10 +133,6 @@ class Card:
def answer_av_tags(self) -> List[AVTag]:
return self.render_output().answer_av_tags
- # legacy
- def css(self) -> str:
- return f""
-
def render_output(
self, reload: bool = False, browser: bool = False
) -> anki.template.TemplateRenderOutput:
@@ -157,31 +155,25 @@ class Card:
def note_type(self) -> NotetypeDict:
return self.col.models.get(self.note().mid)
- # legacy aliases
- flushSched = flush
- q = question
- a = answer
- model = note_type
-
def template(self) -> TemplateDict:
- m = self.model()
- if m["type"] == MODEL_STD:
- return self.model()["tmpls"][self.ord]
+ notetype = self.note_type()
+ if notetype["type"] == MODEL_STD:
+ return self.note_type()["tmpls"][self.ord]
else:
- return self.model()["tmpls"][0]
+ return self.note_type()["tmpls"][0]
- def startTimer(self) -> None:
- self.timerStarted = time.time()
+ def start_timer(self) -> None:
+ self.timer_started = time.time()
def current_deck_id(self) -> anki.decks.DeckId:
return anki.decks.DeckId(self.odid or self.did)
- def timeLimit(self) -> int:
+ def time_limit(self) -> int:
"Time limit for answering in milliseconds."
conf = self.col.decks.confForDid(self.current_deck_id())
return conf["maxTaken"] * 1000
- def shouldShowTimer(self) -> bool:
+ def should_show_timer(self) -> bool:
conf = self.col.decks.confForDid(self.current_deck_id())
return conf["timer"]
@@ -192,23 +184,19 @@ class Card:
def autoplay(self) -> bool:
return self.col.decks.confForDid(self.current_deck_id())["autoplay"]
- def timeTaken(self) -> int:
+ def time_taken(self) -> int:
"Time taken to answer card, in integer MS."
- total = int((time.time() - self.timerStarted) * 1000)
- return min(total, self.timeLimit())
+ total = int((time.time() - self.timer_started) * 1000)
+ return min(total, self.time_limit())
- # legacy
- def isEmpty(self) -> bool:
- return False
-
- def __repr__(self) -> str:
- d = dict(self.__dict__)
+ def description(self) -> str:
+ dict_copy = dict(self.__dict__)
# remove non-useful elements
- del d["_note"]
- del d["_render_output"]
- del d["col"]
- del d["timerStarted"]
- return f"{super().__repr__()} {pprint.pformat(d, width=300)}"
+ del dict_copy["_note"]
+ del dict_copy["_render_output"]
+ del dict_copy["col"]
+ del dict_copy["timerStarted"]
+ return f"{super().__repr__()} {pprint.pformat(dict_copy, width=300)}"
def user_flag(self) -> int:
return self.flags & 0b111
@@ -218,7 +206,18 @@ class Card:
assert 0 <= flag <= 7
self.flags = (self.flags & ~0b111) | flag
- # legacy
+ @deprecated(info="use card.render_output() directly")
+ def css(self) -> str:
+ return f""
- userFlag = user_flag
- setUserFlag = set_user_flag
+ @deprecated(info="handled by template rendering")
+ def is_empty(self) -> bool:
+ return False
+
+
+Card.register_deprecated_aliases(
+ flushSched=Card.flush,
+ q=Card.question,
+ a=Card.answer,
+ model=Card.note_type,
+)
diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py
index bdea0f3b7..a3bc3fcba 100644
--- a/pylib/anki/collection.py
+++ b/pylib/anki/collection.py
@@ -429,7 +429,7 @@ class Collection:
return Note(self, self.models.current(forDeck))
def addNote(self, note: Note) -> int:
- self.add_note(note, note.model()["did"])
+ self.add_note(note, note.note_type()["did"])
return len(note.cards())
def remNotes(self, ids: Sequence[NoteId]) -> None:
diff --git a/pylib/anki/exporting.py b/pylib/anki/exporting.py
index 9b5f15986..c06363e7b 100644
--- a/pylib/anki/exporting.py
+++ b/pylib/anki/exporting.py
@@ -118,8 +118,8 @@ class TextCardExporter(Exporter):
out = ""
for cid in ids:
c = self.col.getCard(cid)
- out += esc(c.q())
- out += "\t" + esc(c.a()) + "\n"
+ out += esc(c.question())
+ out += "\t" + esc(c.answer()) + "\n"
file.write(out.encode("utf-8"))
diff --git a/pylib/anki/notes.py b/pylib/anki/notes.py
index 555e48c25..9d0234b8c 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.model())
+ self._fmap = self.col.models.fieldMap(self.note_type())
def _to_backend_note(self) -> _pb.Note:
hooks.note_will_flush(self)
@@ -98,7 +98,7 @@ class Note(DeprecatedNamesMixin):
card.ord = ord
card.did = anki.decks.DEFAULT_DECK_ID
- model = custom_note_type or self.model()
+ model = custom_note_type or self.note_type()
template = copy.copy(
custom_template
or (
@@ -125,10 +125,10 @@ class Note(DeprecatedNamesMixin):
def card_ids(self) -> Sequence[anki.cards.CardId]:
return self.col.card_ids_of_note(self.id)
- def model(self) -> Optional[NotetypeDict]:
+ def note_type(self) -> Optional[NotetypeDict]:
return self.col.models.get(self.mid)
- _model = property(model)
+ _note_type = property(note_type)
def cloze_numbers_in_fields(self) -> Sequence[int]:
return self.col._backend.cloze_numbers_in_note(self._to_backend_note())
@@ -193,4 +193,6 @@ class Note(DeprecatedNamesMixin):
dupeOrEmpty = duplicate_or_empty = fields_check
-Note.register_deprecated_aliases(delTag=Note.remove_tag, _fieldOrd=Note._field_index)
+Note.register_deprecated_aliases(
+ delTag=Note.remove_tag, _fieldOrd=Note._field_index, model=Note.note_type
+)
diff --git a/pylib/anki/scheduler/v1.py b/pylib/anki/scheduler/v1.py
index 988eb7aab..d84054387 100644
--- a/pylib/anki/scheduler/v1.py
+++ b/pylib/anki/scheduler/v1.py
@@ -85,7 +85,7 @@ class Scheduler(V2):
card.did,
new_delta=new_delta,
review_delta=review_delta,
- milliseconds_delta=+card.timeTaken(),
+ milliseconds_delta=+card.time_taken(),
)
card.mod = intTime()
@@ -363,7 +363,7 @@ limit %d"""
ivl,
lastIvl,
card.factor,
- card.timeTaken(),
+ card.time_taken(),
type,
)
diff --git a/pylib/anki/scheduler/v2.py b/pylib/anki/scheduler/v2.py
index 1c7cb1c2b..8b358e394 100644
--- a/pylib/anki/scheduler/v2.py
+++ b/pylib/anki/scheduler/v2.py
@@ -105,7 +105,7 @@ class Scheduler(SchedulerBaseWithLegacy):
self.col.log(card)
if not self._burySiblingsOnAnswer:
self._burySiblings(card)
- card.startTimer()
+ card.start_timer()
return card
return None
@@ -494,7 +494,7 @@ limit ?"""
card.did,
new_delta=new_delta,
review_delta=review_delta,
- milliseconds_delta=+card.timeTaken(),
+ milliseconds_delta=+card.time_taken(),
)
# once a card has been answered once, the original due date
@@ -760,7 +760,7 @@ limit ?"""
ivl,
lastIvl,
card.factor,
- card.timeTaken(),
+ card.time_taken(),
type,
)
@@ -891,7 +891,7 @@ limit ?"""
-delay or card.ivl,
card.lastIvl,
card.factor,
- card.timeTaken(),
+ card.time_taken(),
type,
)
diff --git a/pylib/anki/scheduler/v3.py b/pylib/anki/scheduler/v3.py
index 35bdf0f98..4986e279a 100644
--- a/pylib/anki/scheduler/v3.py
+++ b/pylib/anki/scheduler/v3.py
@@ -78,7 +78,7 @@ class Scheduler(SchedulerBaseWithLegacy):
new_state=new_state,
rating=rating,
answered_at_millis=intTime(1000),
- milliseconds_taken=card.timeTaken(),
+ milliseconds_taken=card.time_taken(),
)
def answer_card(self, input: CardAnswer) -> OpChanges:
@@ -106,7 +106,7 @@ class Scheduler(SchedulerBaseWithLegacy):
card = Card(self.col)
card._load_from_backend_card(queued_card.card)
- card.startTimer()
+ card.start_timer()
return card
def _is_finished(self) -> bool:
diff --git a/pylib/anki/template.py b/pylib/anki/template.py
index a3786befb..332420f7d 100644
--- a/pylib/anki/template.py
+++ b/pylib/anki/template.py
@@ -155,7 +155,7 @@ class TemplateRenderContext:
self._fields: Optional[Dict] = None
self._latex_svg = False
if not notetype:
- self._note_type = note.model()
+ self._note_type = note.note_type()
else:
self._note_type = notetype
@@ -190,7 +190,7 @@ class TemplateRenderContext:
def card(self) -> Card:
"""Returns the card being rendered.
- Be careful not to call .q() or .a() on the card, or you'll create an
+ Be careful not to call .question() or .answer() on the card, or you'll create an
infinite loop."""
return self._card
diff --git a/pylib/tests/test_collection.py b/pylib/tests/test_collection.py
index f92daa365..5da64bfb0 100644
--- a/pylib/tests/test_collection.py
+++ b/pylib/tests/test_collection.py
@@ -72,7 +72,7 @@ def test_noteAddDelete():
assert col.cardCount() == 4
# check q/a generation
c0 = note.cards()[0]
- assert "three" in c0.q()
+ assert "three" in c0.question()
# it should not be a duplicate
assert not note.fields_check()
# now let's make a duplicate
@@ -138,15 +138,15 @@ def test_furigana():
n["Front"] = "foo[abc]"
col.addNote(n)
c = n.cards()[0]
- assert c.q().endswith("abc")
+ assert c.question().endswith("abc")
# and should avoid sound
n["Front"] = "foo[sound:abc.mp3]"
n.flush()
- assert "anki:play" in c.q(reload=True)
+ assert "anki:play" in c.question(reload=True)
# it shouldn't throw an error while people are editing
m["tmpls"][0]["qfmt"] = "{{kana:}}"
mm.save(m)
- c.q(reload=True)
+ c.question(reload=True)
def test_translate():
diff --git a/pylib/tests/test_decks.py b/pylib/tests/test_decks.py
index 297a63bad..03ad466e4 100644
--- a/pylib/tests/test_decks.py
+++ b/pylib/tests/test_decks.py
@@ -53,12 +53,12 @@ def test_remove():
deck1 = col.decks.id("deck1")
note = col.newNote()
note["Front"] = "1"
- note.model()["did"] = deck1
+ note.note_type()["did"] = deck1
col.addNote(note)
c = note.cards()[0]
assert c.did == deck1
assert col.cardCount() == 1
- col.decks.rem(deck1)
+ col.decks.remove([deck1])
assert col.cardCount() == 0
# if we try to get it, we get the default
assert col.decks.name(c.did) == "[no deck]"
@@ -142,7 +142,7 @@ def test_renameForDragAndDrop():
new_hsk_did = col.decks.id("hsk")
col.decks.renameForDragAndDrop(new_hsk_did, chinese_did)
assert deckNames() == ["Chinese", "Chinese::HSK", "Chinese::hsk+", "Languages"]
- col.decks.rem(new_hsk_did)
+ col.decks.remove([new_hsk_did])
# '' is a convenient alias for the top level DID
col.decks.renameForDragAndDrop(hsk_did, "")
diff --git a/pylib/tests/test_exporting.py b/pylib/tests/test_exporting.py
index d5659c921..fe3691896 100644
--- a/pylib/tests/test_exporting.py
+++ b/pylib/tests/test_exporting.py
@@ -36,7 +36,7 @@ def setup1():
note = col.newNote()
note["Front"] = "baz"
note["Back"] = "qux"
- note.model()["did"] = col.decks.id("new col")
+ note.note_type()["did"] = col.decks.id("new col")
col.addNote(note)
diff --git a/pylib/tests/test_flags.py b/pylib/tests/test_flags.py
index bd0d44c88..9408b7099 100644
--- a/pylib/tests/test_flags.py
+++ b/pylib/tests/test_flags.py
@@ -35,11 +35,3 @@ def test_flags():
col.set_user_flag_for_cards(0, [c.id])
c.load()
assert c.user_flag() == 0
-
- # should work with Cards method as well
- c.set_user_flag(2)
- assert c.user_flag() == 2
- c.set_user_flag(3)
- assert c.user_flag() == 3
- c.set_user_flag(0)
- assert c.user_flag() == 0
diff --git a/pylib/tests/test_importing.py b/pylib/tests/test_importing.py
index 2fbbe4903..b90ccc230 100644
--- a/pylib/tests/test_importing.py
+++ b/pylib/tests/test_importing.py
@@ -38,7 +38,7 @@ def test_anki2_mediadupes():
# add a note that references a sound
n = col.newNote()
n["Front"] = "[sound:foo.mp3]"
- mid = n.model()["id"]
+ mid = n.note_type()["id"]
col.addNote(n)
# add that sound to media folder
with open(os.path.join(col.media.dir(), "foo.mp3"), "w") as note:
diff --git a/pylib/tests/test_latex.py b/pylib/tests/test_latex.py
index 879df5b2c..5ad4b3529 100644
--- a/pylib/tests/test_latex.py
+++ b/pylib/tests/test_latex.py
@@ -23,7 +23,7 @@ def test_latex():
# but since latex couldn't run, there's nothing there
assert len(os.listdir(col.media.dir())) == 0
# check the error message
- msg = note.cards()[0].q()
+ msg = note.cards()[0].question()
assert "executing nolatex" in without_unicode_isolation(msg)
assert "installed" in msg
# check if we have latex installed, and abort test if we don't
@@ -35,12 +35,12 @@ def test_latex():
# check media db should cause latex to be generated
col.media.render_all_latex()
assert len(os.listdir(col.media.dir())) == 1
- assert ".png" in note.cards()[0].q()
+ assert ".png" in note.cards()[0].question()
# adding new notes should cause generation on question display
note = col.newNote()
note["Front"] = "[latex]world[/latex]"
col.addNote(note)
- note.cards()[0].q()
+ note.cards()[0].question()
assert len(os.listdir(col.media.dir())) == 2
# another note with the same media should reuse
note = col.newNote()
@@ -48,7 +48,7 @@ def test_latex():
col.addNote(note)
assert len(os.listdir(col.media.dir())) == 2
oldcard = note.cards()[0]
- assert ".png" in oldcard.q()
+ assert ".png" in oldcard.question()
# if we turn off building, then previous cards should work, but cards with
# missing media will show a broken image
anki.latex.build = False
@@ -56,7 +56,7 @@ def test_latex():
note["Front"] = "[latex]foo[/latex]"
col.addNote(note)
assert len(os.listdir(col.media.dir())) == 2
- assert ".png" in oldcard.q()
+ assert ".png" in oldcard.question()
# turn it on again so other test don't suffer
anki.latex.build = True
@@ -95,5 +95,5 @@ def _test_includes_bad_command(bad):
note = col.newNote()
note["Front"] = "[latex]%s[/latex]" % bad
col.addNote(note)
- q = without_unicode_isolation(note.cards()[0].q())
+ q = without_unicode_isolation(note.cards()[0].question())
return ("'%s' is not allowed on cards" % bad in q, "Card content: %s" % q)
diff --git a/pylib/tests/test_models.py b/pylib/tests/test_models.py
index 89080edd0..d411d5970 100644
--- a/pylib/tests/test_models.py
+++ b/pylib/tests/test_models.py
@@ -112,7 +112,7 @@ def test_templates():
# and should have updated the other cards' ordinals
c = note.cards()[0]
assert c.ord == 0
- assert stripHTML(c.q()) == "1"
+ assert stripHTML(c.question()) == "1"
# it shouldn't be possible to orphan notes by removing templates
t = mm.newTemplate("template name")
t["qfmt"] = "{{Front}}2"
@@ -158,14 +158,14 @@ def test_text():
note = col.newNote()
note["Front"] = "helloworld"
col.addNote(note)
- assert "helloworld" in note.cards()[0].q()
+ assert "helloworld" in note.cards()[0].question()
def test_cloze():
col = getEmptyCol()
col.models.setCurrent(col.models.byName("Cloze"))
note = col.newNote()
- assert note.model()["name"] == "Cloze"
+ assert note.note_type()["name"] == "Cloze"
# a cloze model with no clozes is not empty
note["Text"] = "nothing"
assert col.addNote(note)
@@ -173,30 +173,30 @@ def test_cloze():
note = col.newNote()
note["Text"] = "hello {{c1::world}}"
assert col.addNote(note) == 1
- assert "hello [...]" in note.cards()[0].q()
- assert "hello world" in note.cards()[0].a()
+ assert "hello [...]" in note.cards()[0].question()
+ assert "hello world" in note.cards()[0].answer()
# and with a comment
note = col.newNote()
note["Text"] = "hello {{c1::world::typical}}"
assert col.addNote(note) == 1
- assert "[typical]" in note.cards()[0].q()
- assert "world" in note.cards()[0].a()
+ assert "[typical]" in note.cards()[0].question()
+ assert "world" in note.cards()[0].answer()
# and with 2 clozes
note = col.newNote()
note["Text"] = "hello {{c1::world}} {{c2::bar}}"
assert col.addNote(note) == 2
(c1, c2) = note.cards()
- assert "[...] bar" in c1.q()
- assert "world bar" in c1.a()
- assert "world [...]" in c2.q()
- assert "world bar" in c2.a()
+ assert "[...] bar" in c1.question()
+ assert "world bar" in c1.answer()
+ assert "world [...]" in c2.question()
+ 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["Text"] = "a {{c1::b}} {{c1::c}}"
assert col.addNote(note) == 1
assert "b c" in (
- note.cards()[0].a()
+ note.cards()[0].answer()
)
# if we add another cloze, a card should be generated
cnt = col.cardCount()
@@ -218,11 +218,11 @@ def test_cloze_mathjax():
] = r"{{c1::ok}} \(2^2\) {{c2::not ok}} \(2^{{c3::2}}\) \(x^3\) {{c4::blah}} {{c5::text with \(x^2\) jax}}"
assert col.addNote(note)
assert len(note.cards()) == 5
- assert "class=cloze" in note.cards()[0].q()
- assert "class=cloze" in note.cards()[1].q()
- assert "class=cloze" not in note.cards()[2].q()
- assert "class=cloze" in note.cards()[3].q()
- assert "class=cloze" in note.cards()[4].q()
+ assert "class=cloze" in note.cards()[0].question()
+ assert "class=cloze" in note.cards()[1].question()
+ assert "class=cloze" not in note.cards()[2].question()
+ assert "class=cloze" in note.cards()[3].question()
+ assert "class=cloze" in note.cards()[4].question()
note = col.newNote()
note["Text"] = r"\(a\) {{c1::b}} \[ {{c1::c}} \]"
@@ -230,7 +230,7 @@ def test_cloze_mathjax():
assert len(note.cards()) == 1
assert (
note.cards()[0]
- .q()
+ .question()
.endswith(r"\(a\) [...] \[ [...] \]")
)
@@ -244,7 +244,7 @@ def test_typecloze():
note = col.newNote()
note["Text"] = "hello {{c1::world}}"
col.addNote(note)
- assert "[[type:cloze:Text]]" in note.cards()[0].q()
+ assert "[[type:cloze:Text]]" in note.cards()[0].question()
def test_chained_mods():
@@ -275,11 +275,11 @@ def test_chained_mods():
assert col.addNote(note) == 1
assert (
"This [sentence] demonstrates [chained] clozes."
- in note.cards()[0].q()
+ in note.cards()[0].question()
)
assert (
"This phrase demonstrates en chaine clozes."
- in note.cards()[0].a()
+ in note.cards()[0].answer()
)
@@ -309,16 +309,16 @@ def test_modelChange():
# switch cards
c0 = note.cards()[0]
c1 = note.cards()[1]
- assert "b123" in c0.q()
- assert "note" in c1.q()
+ assert "b123" in c0.question()
+ assert "note" in c1.question()
assert c0.ord == 0
assert c1.ord == 1
col.models.change(basic, [note.id], basic, noop, map)
note.load()
c0.load()
c1.load()
- assert "note" in c0.q()
- assert "b123" in c1.q()
+ assert "note" in c0.question()
+ assert "b123" in c1.question()
assert c0.ord == 1
assert c1.ord == 0
# .cards() returns cards in order
diff --git a/pylib/tests/test_schedv1.py b/pylib/tests/test_schedv1.py
index 0abb4d805..d73ba8861 100644
--- a/pylib/tests/test_schedv1.py
+++ b/pylib/tests/test_schedv1.py
@@ -78,7 +78,7 @@ def test_new():
# qs = ("2", "3", "2", "3")
# for n in range(4):
# c = col.sched.getCard()
- # assert qs[n] in c.q()
+ # assert qs[n] in c.question()
# col.sched.answerCard(c, 2)
@@ -90,7 +90,7 @@ def test_newLimits():
note = col.newNote()
note["Front"] = str(i)
if i > 4:
- note.model()["did"] = deck2
+ note.note_type()["did"] = deck2
col.addNote(note)
# give the child deck a different configuration
c2 = col.decks.add_config_returning_id("new conf")
@@ -224,17 +224,17 @@ def test_learn_collapsed():
col.reset()
# should get '1' first
c = col.sched.getCard()
- assert c.q().endswith("1")
+ assert c.question().endswith("1")
# pass it so it's due in 10 minutes
col.sched.answerCard(c, 2)
# get the other card
c = col.sched.getCard()
- assert c.q().endswith("2")
+ assert c.question().endswith("2")
# fail it so it's due in 1 minute
col.sched.answerCard(c, 1)
# we shouldn't get the same card again
c = col.sched.getCard()
- assert not c.q().endswith("2")
+ assert not c.question().endswith("2")
def test_learn_day():
@@ -316,7 +316,7 @@ def test_reviews():
c.reps = 3
c.lapses = 1
c.ivl = 100
- c.startTimer()
+ c.start_timer()
c.flush()
# save it for later use as well
cardcopy = copy.copy(c)
@@ -393,7 +393,7 @@ def test_button_spacing():
c.due = col.sched.today
c.reps = 1
c.ivl = 1
- c.startTimer()
+ c.start_timer()
c.flush()
col.reset()
ni = col.sched.nextIvlStr
@@ -575,7 +575,7 @@ def test_cram():
c.due = col.sched.today + 25
c.mod = 1
c.factor = STARTING_FACTOR
- c.startTimer()
+ c.start_timer()
c.flush()
col.reset()
assert col.sched.counts() == (0, 0, 0)
@@ -638,7 +638,7 @@ def test_cram():
assert col.sched.nextIvl(c, 2) == 600
assert col.sched.nextIvl(c, 3) == 86400
# delete the deck, returning the card mid-study
- col.decks.rem(col.decks.selected())
+ col.decks.remove([col.decks.selected()])
assert len(col.sched.deck_due_tree().children) == 1
c.load()
assert c.ivl == 1
@@ -964,7 +964,7 @@ def test_deckDue():
# and one that's a child
note = col.newNote()
note["Front"] = "two"
- default1 = note.model()["did"] = col.decks.id("Default::1")
+ default1 = note.note_type()["did"] = col.decks.id("Default::1")
col.addNote(note)
# make it a review card
c = note.cards()[0]
@@ -974,12 +974,12 @@ def test_deckDue():
# add one more with a new deck
note = col.newNote()
note["Front"] = "two"
- note.model()["did"] = col.decks.id("foo::bar")
+ note.note_type()["did"] = col.decks.id("foo::bar")
col.addNote(note)
# and one that's a sibling
note = col.newNote()
note["Front"] = "three"
- note.model()["did"] = col.decks.id("foo::baz")
+ note.note_type()["did"] = col.decks.id("foo::baz")
col.addNote(note)
col.reset()
assert len(col.decks.all_names_and_ids()) == 5
@@ -1010,12 +1010,12 @@ def test_deckFlow():
# and one that's a child
note = col.newNote()
note["Front"] = "two"
- note.model()["did"] = col.decks.id("Default::2")
+ note.note_type()["did"] = col.decks.id("Default::2")
col.addNote(note)
# and another that's higher up
note = col.newNote()
note["Front"] = "three"
- default1 = note.model()["did"] = col.decks.id("Default::1")
+ default1 = note.note_type()["did"] = col.decks.id("Default::1")
col.addNote(note)
# should get top level one first, then ::1, then ::2
col.reset()
@@ -1040,7 +1040,7 @@ def test_norelearn():
c.reps = 3
c.lapses = 1
c.ivl = 100
- c.startTimer()
+ c.start_timer()
c.flush()
col.reset()
col.sched.answerCard(c, 1)
@@ -1062,7 +1062,7 @@ def test_failmult():
c.factor = STARTING_FACTOR
c.reps = 3
c.lapses = 1
- c.startTimer()
+ c.start_timer()
c.flush()
conf = col.sched._cardConf(c)
conf["lapse"]["mult"] = 0.5
diff --git a/pylib/tests/test_schedv2.py b/pylib/tests/test_schedv2.py
index 5184e112a..b53fb6a17 100644
--- a/pylib/tests/test_schedv2.py
+++ b/pylib/tests/test_schedv2.py
@@ -90,7 +90,7 @@ def test_new():
# qs = ("2", "3", "2", "3")
# for n in range(4):
# c = col.sched.getCard()
- # assert qs[n] in c.q()
+ # assert qs[n] in c.question()
# col.sched.answerCard(c, 2)
@@ -102,7 +102,7 @@ def test_newLimits():
note = col.newNote()
note["Front"] = str(i)
if i > 4:
- note.model()["did"] = deck2
+ note.note_type()["did"] = deck2
col.addNote(note)
# give the child deck a different configuration
c2 = col.decks.add_config_returning_id("new conf")
@@ -270,17 +270,17 @@ def test_learn_collapsed():
col.reset()
# should get '1' first
c = col.sched.getCard()
- assert c.q().endswith("1")
+ assert c.question().endswith("1")
# pass it so it's due in 10 minutes
col.sched.answerCard(c, 3)
# get the other card
c = col.sched.getCard()
- assert c.q().endswith("2")
+ assert c.question().endswith("2")
# fail it so it's due in 1 minute
col.sched.answerCard(c, 1)
# we shouldn't get the same card again
c = col.sched.getCard()
- assert not c.q().endswith("2")
+ assert not c.question().endswith("2")
def test_learn_day():
@@ -374,7 +374,7 @@ def test_reviews():
c.reps = 3
c.lapses = 1
c.ivl = 100
- c.startTimer()
+ c.start_timer()
c.flush()
# save it for later use as well
cardcopy = copy.copy(c)
@@ -509,7 +509,7 @@ def test_button_spacing():
c.due = col.sched.today
c.reps = 1
c.ivl = 1
- c.startTimer()
+ c.start_timer()
c.flush()
col.reset()
ni = col.sched.nextIvlStr
@@ -731,7 +731,7 @@ def test_filt_reviewing_early_normal():
c.due = col.sched.today + 25
c.mod = 1
c.factor = STARTING_FACTOR
- c.startTimer()
+ c.start_timer()
c.flush()
col.reset()
assert col.sched.counts() == (0, 0, 0)
@@ -1082,7 +1082,7 @@ def test_deckDue():
# and one that's a child
note = col.newNote()
note["Front"] = "two"
- default1 = note.model()["did"] = col.decks.id("Default::1")
+ default1 = note.note_type()["did"] = col.decks.id("Default::1")
col.addNote(note)
# make it a review card
c = note.cards()[0]
@@ -1092,12 +1092,12 @@ def test_deckDue():
# add one more with a new deck
note = col.newNote()
note["Front"] = "two"
- note.model()["did"] = col.decks.id("foo::bar")
+ note.note_type()["did"] = col.decks.id("foo::bar")
col.addNote(note)
# and one that's a sibling
note = col.newNote()
note["Front"] = "three"
- note.model()["did"] = col.decks.id("foo::baz")
+ note.note_type()["did"] = col.decks.id("foo::baz")
col.addNote(note)
col.reset()
assert len(col.decks.all_names_and_ids()) == 5
@@ -1138,12 +1138,12 @@ def test_deckFlow():
# and one that's a child
note = col.newNote()
note["Front"] = "two"
- note.model()["did"] = col.decks.id("Default::2")
+ note.note_type()["did"] = col.decks.id("Default::2")
col.addNote(note)
# and another that's higher up
note = col.newNote()
note["Front"] = "three"
- default1 = note.model()["did"] = col.decks.id("Default::1")
+ default1 = note.note_type()["did"] = col.decks.id("Default::1")
col.addNote(note)
col.reset()
assert col.sched.counts() == (3, 0, 0)
@@ -1255,7 +1255,7 @@ def test_norelearn():
c.reps = 3
c.lapses = 1
c.ivl = 100
- c.startTimer()
+ c.start_timer()
c.flush()
col.reset()
col.sched.answerCard(c, 1)
@@ -1277,7 +1277,7 @@ def test_failmult():
c.factor = STARTING_FACTOR
c.reps = 3
c.lapses = 1
- c.startTimer()
+ c.start_timer()
c.flush()
conf = col.sched._cardConf(c)
conf["lapse"]["mult"] = 0.5
diff --git a/pylib/tests/test_template.py b/pylib/tests/test_template.py
index 9af10c625..3da192de1 100644
--- a/pylib/tests/test_template.py
+++ b/pylib/tests/test_template.py
@@ -15,4 +15,4 @@ def test_deferred_frontside():
note["Back"] = ""
col.addNote(note)
- assert "xxtest" in note.cards()[0].a()
+ assert "xxtest" in note.cards()[0].answer()
diff --git a/qt/aqt/addcards.py b/qt/aqt/addcards.py
index 0ea042be8..b833fd1bb 100644
--- a/qt/aqt/addcards.py
+++ b/qt/aqt/addcards.py
@@ -128,14 +128,14 @@ class AddCards(QDialog):
if old:
old_fields = list(old.keys())
new_fields = list(new.keys())
- for n, f in enumerate(new.model()["flds"]):
+ for n, f in enumerate(new.note_type()["flds"]):
field_name = f["name"]
# copy identical fields
if field_name in old_fields:
new[field_name] = old[field_name]
- elif n < len(old.model()["flds"]):
+ elif n < len(old.note_type()["flds"]):
# set non-identical fields by field index
- old_field_name = old.model()["flds"][n]["name"]
+ old_field_name = old.note_type()["flds"][n]["name"]
if old_field_name not in new_fields:
new.fields[n] = old.fields[n]
new.tags = old.tags
@@ -147,7 +147,7 @@ class AddCards(QDialog):
def _load_new_note(self, sticky_fields_from: Optional[Note] = None) -> None:
note = self._new_note()
if old_note := sticky_fields_from:
- flds = note.model()["flds"]
+ flds = note.note_type()["flds"]
# copy fields from old note
if old_note:
for n in range(min(len(note.fields), len(old_note.fields))):
diff --git a/qt/aqt/browser/previewer.py b/qt/aqt/browser/previewer.py
index 73afb8462..4d0425983 100644
--- a/qt/aqt/browser/previewer.py
+++ b/qt/aqt/browser/previewer.py
@@ -187,11 +187,11 @@ class Previewer(QDialog):
return
# need to force reload even if answer
- txt = c.q(reload=True)
+ txt = c.question(reload=True)
if self._state == "answer":
func = "_showAnswer"
- txt = c.a()
+ txt = c.answer()
txt = re.sub(r"\[\[type:[^]]+\]\]", "", txt)
bodyclass = theme_manager.body_classes_for_card_ord(c.ord)
diff --git a/qt/aqt/clayout.py b/qt/aqt/clayout.py
index b3a44e48e..e30b123f9 100644
--- a/qt/aqt/clayout.py
+++ b/qt/aqt/clayout.py
@@ -52,7 +52,7 @@ class CardLayout(QDialog):
self.ord = ord
self.col = self.mw.col.weakref()
self.mm = self.mw.col.models
- self.model = note.model()
+ self.model = note.note_type()
self.templates = self.model["tmpls"]
self.fill_empty_action_toggled = fill_empty
self.night_mode_is_enabled = self.mw.pm.night_mode()
@@ -493,11 +493,11 @@ class CardLayout(QDialog):
)
if self.pform.preview_front.isChecked():
- q = ti(self.mw.prepare_card_text_for_display(c.q()))
+ q = ti(self.mw.prepare_card_text_for_display(c.question()))
q = gui_hooks.card_will_show(q, c, "clayoutQuestion")
text = q
else:
- a = ti(self.mw.prepare_card_text_for_display(c.a()), type="a")
+ a = ti(self.mw.prepare_card_text_for_display(c.answer()), type="a")
a = gui_hooks.card_will_show(a, c, "clayoutAnswer")
text = a
diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py
index 8a49096be..7766dd9e8 100644
--- a/qt/aqt/editor.py
+++ b/qt/aqt/editor.py
@@ -325,7 +325,7 @@ $editorToolbar.then(({{ toolbar }}) => toolbar.appendGroup({{
def _onFields(self) -> None:
from aqt.fields import FieldDialog
- FieldDialog(self.mw, self.note.model(), parent=self.parentWindow)
+ FieldDialog(self.mw, self.note.note_type(), parent=self.parentWindow)
def onCardLayout(self) -> None:
self.call_after_note_saved(self._onCardLayout)
@@ -394,7 +394,7 @@ $editorToolbar.then(({{ toolbar }}) => toolbar.appendGroup({{
(type, num) = cmd.split(":", 1)
ord = int(num)
- model = self.note.model()
+ model = self.note.note_type()
fld = model["flds"][ord]
new_state = not fld["sticky"]
fld["sticky"] = new_state
@@ -463,7 +463,7 @@ $editorToolbar.then(({{ toolbar }}) => toolbar.appendGroup({{
)
if self.addMode:
- sticky = [field["sticky"] for field in self.note.model()["flds"]]
+ sticky = [field["sticky"] for field in self.note.note_type()["flds"]]
js += " setSticky(%s);" % json.dumps(sticky)
js = gui_hooks.editor_will_load_note(js, self.note, self)
@@ -478,7 +478,7 @@ $editorToolbar.then(({{ toolbar }}) => toolbar.appendGroup({{
def fonts(self) -> List[Tuple[str, int, bool]]:
return [
(gui_hooks.editor_will_use_font_for_field(f["font"]), f["size"], f["rtl"])
- for f in self.note.model()["flds"]
+ for f in self.note.note_type()["flds"]
]
def call_after_note_saved(
@@ -532,7 +532,7 @@ $editorToolbar.then(({{ toolbar }}) => toolbar.appendGroup({{
search=(
SearchNode(
dupe=SearchNode.Dupe(
- notetype_id=self.note.model()["id"],
+ notetype_id=self.note.note_type()["id"],
first_field=self.note.fields[0],
)
),
@@ -542,7 +542,7 @@ $editorToolbar.then(({{ toolbar }}) => toolbar.appendGroup({{
def fieldsAreBlank(self, previousNote: Optional[Note] = None) -> bool:
if not self.note:
return True
- m = self.note.model()
+ m = self.note.note_type()
for c, f in enumerate(self.note.fields):
f = f.replace("
", "").strip()
notChangedvalues = {"", "
"}
@@ -681,7 +681,7 @@ $editorToolbar.then(({{ toolbar }}) => toolbar.appendGroup({{
def _onCloze(self) -> None:
# check that the model is set up for cloze deletion
- if self.note.model()["type"] != MODEL_CLOZE:
+ if self.note.note_type()["type"] != MODEL_CLOZE:
if self.addMode:
tooltip(tr.editing_warning_cloze_deletions_will_not_work())
else:
@@ -1280,7 +1280,7 @@ gui_hooks.editor_will_munge_html.append(reverse_url_quoting)
def set_cloze_button(editor: Editor) -> None:
- if editor.note.model()["type"] == MODEL_CLOZE:
+ if editor.note.note_type()["type"] == MODEL_CLOZE:
editor.web.eval(
'$editorToolbar.then(({ templateButtons }) => templateButtons.showButton("cloze")); '
)
diff --git a/qt/aqt/importing.py b/qt/aqt/importing.py
index 0fa7a7154..c1af62f81 100644
--- a/qt/aqt/importing.py
+++ b/qt/aqt/importing.py
@@ -118,7 +118,7 @@ class ImportDialog(QDialog):
self.deck = aqt.deckchooser.DeckChooser(self.mw, self.frm.deckArea, label=False)
def modelChanged(self, unused: Any = None) -> None:
- self.importer.model = self.mw.col.models.current()
+ self.importer.note_type = self.mw.col.models.current()
self.importer.initMapping()
self.showMapping()
@@ -191,9 +191,9 @@ class ImportDialog(QDialog):
self.importer.tagModified = self.frm.tagModified.text()
self.mw.pm.profile["tagModified"] = self.importer.tagModified
self.mw.col.set_aux_notetype_config(
- self.importer.model["id"], "lastDeck", self.deck.selected_deck_id
+ self.importer.note_type["id"], "lastDeck", self.deck.selected_deck_id
)
- self.mw.col.models.save(self.importer.model, updateReqs=False)
+ self.mw.col.models.save(self.importer.note_type, updateReqs=False)
self.mw.progress.start()
self.mw.checkpoint(tr.actions_import())
@@ -273,7 +273,7 @@ class ImportDialog(QDialog):
qconnect(button.clicked, lambda _, s=self, n=num: s.changeMappingNum(n))
def changeMappingNum(self, n: int) -> None:
- f = ChangeMap(self.mw, self.importer.model, self.mapping[n]).getField()
+ f = ChangeMap(self.mw, self.importer.note_type, self.mapping[n]).getField()
try:
# make sure we don't have it twice
index = self.mapping.index(f)
diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py
index f39d3f0f9..326019f3a 100644
--- a/qt/aqt/reviewer.py
+++ b/qt/aqt/reviewer.py
@@ -218,7 +218,7 @@ class Reviewer:
if self.cardQueue:
# undone/edited cards to show
card = self.cardQueue.pop()
- card.startTimer()
+ card.start_timer()
self.hadCardQueue = True
else:
if self.hadCardQueue:
@@ -236,7 +236,7 @@ class Reviewer:
return
self._v3 = V3CardInfo.from_queue(output)
self.card = Card(self.mw.col, backend_card=self._v3.top_card().card)
- self.card.startTimer()
+ self.card.start_timer()
def get_next_states(self) -> Optional[NextStates]:
if v3 := self._v3:
@@ -320,7 +320,7 @@ class Reviewer:
self.typedAnswer: str = None
c = self.card
# grab the question and play audio
- q = c.q()
+ q = c.question()
# play audio?
if c.autoplay():
AnkiWebView.setPlaybackRequiresGesture(False)
@@ -370,7 +370,7 @@ class Reviewer:
return
self.state = "answer"
c = self.card
- a = c.a()
+ a = c.answer()
# play audio?
if c.autoplay():
sounds = c.answer_av_tags()
@@ -535,7 +535,7 @@ class Reviewer:
clozeIdx = self.card.ord + 1
fld = fld.split(":")[1]
# loop through fields for a match
- for f in self.card.model()["flds"]:
+ for f in self.card.note_type()["flds"]:
if f["name"] == fld:
self.typeCorrect = self.card.note()[f["name"]]
if clozeIdx:
@@ -733,7 +733,7 @@ time = %(time)d;
editkey=tr.actions_shortcut_key(val="E"),
more=tr.studying_more(),
downArrow=downArrow(),
- time=self.card.timeTaken() // 1000,
+ time=self.card.time_taken() // 1000,
)
def _showAnswerButton(self) -> None:
@@ -749,8 +749,8 @@ time = %(time)d;
""
% middle
)
- if self.card.shouldShowTimer():
- maxTime = self.card.timeLimit() / 1000
+ if self.card.should_show_timer():
+ maxTime = self.card.time_limit() / 1000
else:
maxTime = 0
self.bottom.web.eval("showQuestion(%s,%d);" % (json.dumps(middle), maxTime))
diff --git a/rslib/src/scheduler/queue/mod.rs b/rslib/src/scheduler/queue/mod.rs
index 64c5cac00..535fb2e69 100644
--- a/rslib/src/scheduler/queue/mod.rs
+++ b/rslib/src/scheduler/queue/mod.rs
@@ -7,7 +7,7 @@ mod learning;
mod main;
pub(crate) mod undo;
-use std::{collections::VecDeque, time::Instant};
+use std::collections::VecDeque;
pub(crate) use builder::{DueCard, NewCard};
pub(crate) use entry::{QueueEntry, QueueEntryKind};
@@ -205,9 +205,7 @@ impl Collection {
self.discard_undo_and_study_queues();
}
if self.state.card_queues.is_none() {
- let now = Instant::now();
self.state.card_queues = Some(self.build_queues(deck)?);
- println!("queue build in {:?}", now.elapsed());
}
Ok(self.state.card_queues.as_mut().unwrap())