PEP8 cards.py

This commit is contained in:
Damien Elmes 2021-06-27 12:12:23 +10:00
parent 1cabe9507c
commit 2a93355824
26 changed files with 209 additions and 197 deletions

View file

@ -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

View file

@ -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"<style>{self.render_output().css}</style>"
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"<style>{self.render_output().css}</style>"
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,
)

View file

@ -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:

View file

@ -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"))

View file

@ -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
)

View file

@ -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,
)

View file

@ -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,
)

View file

@ -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:

View file

@ -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

View file

@ -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():

View file

@ -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, "")

View file

@ -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)

View file

@ -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

View file

@ -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:

View file

@ -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)

View file

@ -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"] = "hello<b>world"
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 <span class=cloze>[...]</span>" in note.cards()[0].q()
assert "hello <span class=cloze>world</span>" in note.cards()[0].a()
assert "hello <span class=cloze>[...]</span>" in note.cards()[0].question()
assert "hello <span class=cloze>world</span>" in note.cards()[0].answer()
# and with a comment
note = col.newNote()
note["Text"] = "hello {{c1::world::typical}}"
assert col.addNote(note) == 1
assert "<span class=cloze>[typical]</span>" in note.cards()[0].q()
assert "<span class=cloze>world</span>" in note.cards()[0].a()
assert "<span class=cloze>[typical]</span>" in note.cards()[0].question()
assert "<span class=cloze>world</span>" 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 "<span class=cloze>[...]</span> bar" in c1.q()
assert "<span class=cloze>world</span> bar" in c1.a()
assert "world <span class=cloze>[...]</span>" in c2.q()
assert "world <span class=cloze>bar</span>" in c2.a()
assert "<span class=cloze>[...]</span> bar" in c1.question()
assert "<span class=cloze>world</span> bar" in c1.answer()
assert "world <span class=cloze>[...]</span>" in c2.question()
assert "world <span class=cloze>bar</span>" 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 "<span class=cloze>b</span> <span class=cloze>c</span>" 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\) <span class=cloze>[...]</span> \[ [...] \]")
)
@ -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 <span class=cloze>[sentence]</span> demonstrates <span class=cloze>[chained]</span> clozes."
in note.cards()[0].q()
in note.cards()[0].question()
)
assert (
"This <span class=cloze>phrase</span> demonstrates <span class=cloze>en chaine</span> 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

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -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))):

View file

@ -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)

View file

@ -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

View file

@ -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("<br>", "").strip()
notChangedvalues = {"", "<br>"}
@ -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")); '
)

View file

@ -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)

View file

@ -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;
"<table cellpadding=0><tr><td class=stat2 align=center>%s</td></tr></table>"
% 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))

View file

@ -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())