PEP8 collection.py

This commit is contained in:
Damien Elmes 2021-06-27 15:12:22 +10:00
parent 17533e6a78
commit 1b15069b24
33 changed files with 329 additions and 377 deletions

View file

@ -49,3 +49,4 @@ good-names =
id, id,
tr, tr,
db, db,
ok,

View file

@ -1,6 +1,8 @@
# Copyright: Ankitects Pty Ltd and contributors # Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
# pylint: enable=invalid-name
from __future__ import annotations from __future__ import annotations
from typing import Any, Generator, List, Literal, Optional, Sequence, Tuple, Union, cast from typing import Any, Generator, List, Literal, Optional, Sequence, Tuple, Union, cast
@ -8,6 +10,8 @@ from typing import Any, Generator, List, Literal, Optional, Sequence, Tuple, Uni
import anki._backend.backend_pb2 as _pb import anki._backend.backend_pb2 as _pb
# protobuf we publicly export - listed first to avoid circular imports # protobuf we publicly export - listed first to avoid circular imports
from anki._legacy import DeprecatedNamesMixin, deprecated
SearchNode = _pb.SearchNode SearchNode = _pb.SearchNode
Progress = _pb.Progress Progress = _pb.Progress
EmptyCardsReport = _pb.EmptyCardsReport EmptyCardsReport = _pb.EmptyCardsReport
@ -24,8 +28,6 @@ BrowserColumns = _pb.BrowserColumns
import copy import copy
import os import os
import pprint
import re
import sys import sys
import time import time
import traceback import traceback
@ -52,7 +54,6 @@ from anki.sync import SyncAuth, SyncOutput, SyncStatus
from anki.tags import TagManager from anki.tags import TagManager
from anki.types import assert_exhaustive from anki.types import assert_exhaustive
from anki.utils import ( from anki.utils import (
devMode,
from_json_bytes, from_json_bytes,
ids2str, ids2str,
intTime, intTime,
@ -81,7 +82,7 @@ class LegacyCheckpoint:
LegacyUndoResult = Union[None, LegacyCheckpoint, LegacyReviewUndo] LegacyUndoResult = Union[None, LegacyCheckpoint, LegacyReviewUndo]
class Collection: class Collection(DeprecatedNamesMixin):
sched: Union[V1Scheduler, V2Scheduler, V3Scheduler] sched: Union[V1Scheduler, V2Scheduler, V3Scheduler]
def __init__( def __init__(
@ -104,7 +105,7 @@ class Collection:
self.decks = DeckManager(self) self.decks = DeckManager(self)
self.tags = TagManager(self) self.tags = TagManager(self)
self.conf = ConfigManager(self) self.conf = ConfigManager(self)
self._loadScheduler() self._load_scheduler()
def name(self) -> Any: def name(self) -> Any:
return os.path.splitext(os.path.basename(self.path))[0] return os.path.splitext(os.path.basename(self.path))[0]
@ -142,17 +143,17 @@ class Collection:
########################################################################## ##########################################################################
# for backwards compatibility, v3 is represented as 2 # for backwards compatibility, v3 is represented as 2
supportedSchedulerVersions = (1, 2) _supported_scheduler_versions = (1, 2)
def schedVer(self) -> Literal[1, 2]: def sched_ver(self) -> Literal[1, 2]:
ver = self.conf.get("schedVer", 1) ver = self.conf.get("schedVer", 1)
if ver in self.supportedSchedulerVersions: if ver in self._supported_scheduler_versions:
return ver return ver
else: else:
raise Exception("Unsupported scheduler version") raise Exception("Unsupported scheduler version")
def _loadScheduler(self) -> None: def _load_scheduler(self) -> None:
ver = self.schedVer() ver = self.sched_ver()
if ver == 1: if ver == 1:
self.sched = V1Scheduler(self) self.sched = V1Scheduler(self)
elif ver == 2: elif ver == 2:
@ -164,17 +165,17 @@ class Collection:
def upgrade_to_v2_scheduler(self) -> None: def upgrade_to_v2_scheduler(self) -> None:
self._backend.upgrade_scheduler() self._backend.upgrade_scheduler()
self.clear_python_undo() self.clear_python_undo()
self._loadScheduler() self._load_scheduler()
def v3_scheduler(self) -> bool: def v3_scheduler(self) -> bool:
return self.get_config_bool(Config.Bool.SCHED_2021) return self.get_config_bool(Config.Bool.SCHED_2021)
def set_v3_scheduler(self, enabled: bool) -> None: def set_v3_scheduler(self, enabled: bool) -> None:
if self.v3_scheduler() != enabled: if self.v3_scheduler() != enabled:
if enabled and self.schedVer() != 2: if enabled and self.sched_ver() != 2:
raise Exception("must upgrade to v2 scheduler first") raise Exception("must upgrade to v2 scheduler first")
self.set_config_bool(Config.Bool.SCHED_2021, enabled) self.set_config_bool(Config.Bool.SCHED_2021, enabled)
self._loadScheduler() self._load_scheduler()
# DB-related # DB-related
########################################################################## ##########################################################################
@ -193,14 +194,6 @@ class Collection:
def mod(self) -> int: def mod(self) -> int:
return self.db.scalar("select mod from col") return self.db.scalar("select mod from col")
# legacy
def setMod(self) -> None:
# this is now a no-op, as modifications to things like the config
# will mark the collection modified automatically
pass
flush = setMod
def modified_by_backend(self) -> bool: def modified_by_backend(self) -> bool:
# Until we can move away from long-running transactions, the Python # Until we can move away from long-running transactions, the Python
# code needs to know if the transaction should be committed, so we need # code needs to know if the transaction should be committed, so we need
@ -242,7 +235,6 @@ class Collection:
self._backend.close_collection(downgrade_to_schema11=downgrade) self._backend.close_collection(downgrade_to_schema11=downgrade)
self.db = None self.db = None
self.media.close() self.media.close()
self._closeLog()
def close_for_full_sync(self) -> None: def close_for_full_sync(self) -> None:
# save and cleanup, but backend will take care of collection close # save and cleanup, but backend will take care of collection close
@ -251,7 +243,6 @@ class Collection:
self._clear_caches() self._clear_caches()
self.db = None self.db = None
self.media.close() self.media.close()
self._closeLog()
def rollback(self) -> None: def rollback(self) -> None:
self._clear_caches() self._clear_caches()
@ -273,7 +264,7 @@ class Collection:
log_path = "" log_path = ""
should_log = not self.server and self._should_log should_log = not self.server and self._should_log
if should_log: if should_log:
log_path = self.path.replace(".anki2", "2.log") log_path = self.path.replace(".anki2", ".log")
# connect # connect
if not after_full_sync: if not after_full_sync:
@ -288,17 +279,18 @@ class Collection:
self.db = DBProxy(weakref.proxy(self._backend)) self.db = DBProxy(weakref.proxy(self._backend))
self.db.begin() self.db.begin()
self._openLog() def set_schema_modified(self) -> None:
def modSchema(self, check: bool) -> None:
"Mark schema modified. Call this first so user can abort if necessary."
if not self.schemaChanged():
if check and not hooks.schema_will_change(proceed=True):
raise AbortSchemaModification()
self.db.execute("update col set scm=?", intTime(1000)) self.db.execute("update col set scm=?", intTime(1000))
self.save() self.save()
def schemaChanged(self) -> bool: def mod_schema(self, check: bool) -> None:
"Mark schema modified. GUI catches this and will ask user if required."
if not self.schema_changed():
if check and not hooks.schema_will_change(proceed=True):
raise AbortSchemaModification()
self.set_schema_modified()
def schema_changed(self) -> bool:
"True if schema changed since last sync." "True if schema changed since last sync."
return self.db.scalar("select scm > ls from col") return self.db.scalar("select scm > ls from col")
@ -308,12 +300,6 @@ class Collection:
else: else:
return -1 return -1
def beforeUpload(self) -> None:
"Called before a full upload."
self.save(trx=False)
self._backend.before_upload()
self.close(save=False, downgrade=True)
# Object helpers # Object helpers
########################################################################## ##########################################################################
@ -341,7 +327,9 @@ class Collection:
# Utils # Utils
########################################################################## ##########################################################################
def nextID(self, type: str, inc: bool = True) -> Any: def nextID( # pylint: disable=invalid-name
self, type: str, inc: bool = True
) -> Any:
type = f"next{type.capitalize()}" type = f"next{type.capitalize()}"
id = self.conf.get(type, 1) id = self.conf.get(type, 1)
if inc: if inc:
@ -353,15 +341,6 @@ class Collection:
self.autosave() self.autosave()
self.sched.reset() self.sched.reset()
# Deletion logging
##########################################################################
def _logRem(self, ids: List[Union[int, NoteId]], type: int) -> None:
self.db.executemany(
"insert into graves values (%d, ?, %d)" % (self.usn(), type),
([x] for x in ids),
)
# Notes # Notes
########################################################################## ##########################################################################
@ -419,32 +398,16 @@ class Collection:
or None or None
) )
# legacy def note_count(self) -> int:
def noteCount(self) -> int:
return self.db.scalar("select count() from notes") return self.db.scalar("select count() from notes")
def newNote(self, forDeck: bool = True) -> Note:
"Return a new note with the current model."
return Note(self, self.models.current(forDeck))
def addNote(self, note: Note) -> int:
self.add_note(note, note.note_type()["did"])
return len(note.cards())
def remNotes(self, ids: Sequence[NoteId]) -> None:
self.remove_notes(ids)
def _remNotes(self, ids: List[NoteId]) -> None:
pass
# Cards # Cards
########################################################################## ##########################################################################
def isEmpty(self) -> bool: def is_empty(self) -> bool:
return not self.db.scalar("select 1 from cards limit 1") return not self.db.scalar("select 1 from cards limit 1")
def cardCount(self) -> Any: def card_count(self) -> Any:
return self.db.scalar("select count() from cards") return self.db.scalar("select count() from cards")
def remove_cards_and_orphaned_notes(self, card_ids: Sequence[CardId]) -> None: def remove_cards_and_orphaned_notes(self, card_ids: Sequence[CardId]) -> None:
@ -457,36 +420,17 @@ class Collection:
def get_empty_cards(self) -> EmptyCardsReport: def get_empty_cards(self) -> EmptyCardsReport:
return self._backend.get_empty_cards() return self._backend.get_empty_cards()
# legacy
def remCards(self, ids: List[CardId], notes: bool = True) -> None:
self.remove_cards_and_orphaned_notes(ids)
def emptyCids(self) -> List[CardId]:
print("emptyCids() will go away")
return []
# Card generation & field checksums/sort fields # Card generation & field checksums/sort fields
########################################################################## ##########################################################################
def after_note_updates( def after_note_updates(
self, nids: List[NoteId], mark_modified: bool, generate_cards: bool = True self, nids: List[NoteId], mark_modified: bool, generate_cards: bool = True
) -> None: ) -> None:
"If notes modified directly in database, call this afterwards."
self._backend.after_note_updates( self._backend.after_note_updates(
nids=nids, generate_cards=generate_cards, mark_notes_modified=mark_modified nids=nids, generate_cards=generate_cards, mark_notes_modified=mark_modified
) )
# legacy
def updateFieldCache(self, nids: List[NoteId]) -> None:
self.after_note_updates(nids, mark_modified=False, generate_cards=False)
# this also updates field cache
def genCards(self, nids: List[NoteId]) -> List[int]:
self.after_note_updates(nids, mark_modified=False, generate_cards=True)
# previously returned empty cards, no longer does
return []
# Finding cards # Finding cards
########################################################################## ##########################################################################
@ -591,21 +535,21 @@ class Collection:
return self._backend.field_names_for_notes(nids) return self._backend.field_names_for_notes(nids)
# returns array of ("dupestr", [nids]) # returns array of ("dupestr", [nids])
def findDupes(self, fieldName: str, search: str = "") -> List[Tuple[str, list]]: def find_dupes(self, field_name: str, search: str = "") -> List[Tuple[str, list]]:
nids = self.find_notes( nids = self.find_notes(
self.build_search_string(search, SearchNode(field_name=fieldName)) self.build_search_string(search, SearchNode(field_name=field_name))
) )
# go through notes # go through notes
vals: Dict[str, List[int]] = {} vals: Dict[str, List[int]] = {}
dupes = [] dupes = []
fields: Dict[int, int] = {} fields: Dict[int, int] = {}
def ordForMid(mid: NotetypeId) -> int: def ord_for_mid(mid: NotetypeId) -> int:
if mid not in fields: if mid not in fields:
model = self.models.get(mid) model = self.models.get(mid)
for c, f in enumerate(model["flds"]): for idx, field in enumerate(model["flds"]):
if f["name"].lower() == fieldName.lower(): if field["name"].lower() == field_name.lower():
fields[mid] = c fields[mid] = idx
break break
return fields[mid] return fields[mid]
@ -613,7 +557,7 @@ class Collection:
f"select id, mid, flds from notes where id in {ids2str(nids)}" f"select id, mid, flds from notes where id in {ids2str(nids)}"
): ):
flds = splitFields(flds) flds = splitFields(flds)
ord = ordForMid(mid) ord = ord_for_mid(mid)
if ord is None: if ord is None:
continue continue
val = flds[ord] val = flds[ord]
@ -626,10 +570,6 @@ class Collection:
dupes.append((val, vals[val])) dupes.append((val, vals[val]))
return dupes return dupes
findCards = find_cards
findNotes = find_notes
findReplace = find_and_replace
# Search Strings # Search Strings
########################################################################## ##########################################################################
@ -880,35 +820,6 @@ table.review-log {{ {revlog_style} }}
"Don't use this, it will likely go away in the future." "Don't use this, it will likely go away in the future."
return self._backend.congrats_info().SerializeToString() return self._backend.congrats_info().SerializeToString()
# legacy
def cardStats(self, card: Card) -> str:
return self.card_stats(card.id, include_revlog=False)
# Timeboxing
##########################################################################
# fixme: there doesn't seem to be a good reason why this code is in main.py
# instead of covered in reviewer, and the reps tracking is covered by both
# the scheduler and reviewer.py. in the future, we should probably move
# reps tracking to reviewer.py, and remove the startTimebox() calls from
# other locations like overview.py. We just need to make sure not to reset
# the count on things like edits, which we probably could do by checking
# the previous state in moveToState.
def startTimebox(self) -> None:
self._startTime = time.time()
self._startReps = self.sched.reps
def timeboxReached(self) -> Union[Literal[False], Tuple[Any, int]]:
"Return (elapsedTime, reps) if timebox reached, or False."
if not self.conf["timeLim"]:
# timeboxing disabled
return False
elapsed = time.time() - self._startTime
if elapsed > self.conf["timeLim"]:
return (self.conf["timeLim"], self.sched.reps - self._startReps)
return False
# Undo # Undo
########################################################################## ##########################################################################
@ -1070,10 +981,10 @@ table.review-log {{ {revlog_style} }}
) )
# update daily counts # update daily counts
n = card.queue idx = card.queue
if card.queue in (QUEUE_TYPE_DAY_LEARN_RELEARN, QUEUE_TYPE_PREVIEW): if card.queue in (QUEUE_TYPE_DAY_LEARN_RELEARN, QUEUE_TYPE_PREVIEW):
n = QUEUE_TYPE_LRN idx = QUEUE_TYPE_LRN
type = ("new", "lrn", "rev")[n] type = ("new", "lrn", "rev")[idx]
self.sched._updateStats(card, type, -1) self.sched._updateStats(card, type, -1)
self.sched.reps -= 1 self.sched.reps -= 1
@ -1082,20 +993,10 @@ table.review-log {{ {revlog_style} }}
return entry return entry
# legacy
clearUndo = clear_python_undo
markReview = save_card_review_undo_info
def undoName(self) -> Optional[str]:
"Undo menu item name, or None if undo unavailable."
status = self.undo_status()
return status.undo or None
# DB maintenance # DB maintenance
########################################################################## ##########################################################################
def fixIntegrity(self) -> Tuple[str, bool]: def fix_integrity(self) -> Tuple[str, bool]:
"""Fix possible problems and rebuild caches. """Fix possible problems and rebuild caches.
Returns tuple of (error: str, ok: bool). 'ok' will be true if no Returns tuple of (error: str, ok: bool). 'ok' will be true if no
@ -1106,8 +1007,8 @@ table.review-log {{ {revlog_style} }}
problems = list(self._backend.check_database()) problems = list(self._backend.check_database())
ok = not problems ok = not problems
problems.append(self.tr.database_check_rebuilt()) problems.append(self.tr.database_check_rebuilt())
except DBError as e: except DBError as err:
problems = [str(e.args[0])] problems = [str(err.args[0])]
ok = False ok = False
finally: finally:
try: try:
@ -1123,46 +1024,6 @@ table.review-log {{ {revlog_style} }}
self.db.execute("analyze") self.db.execute("analyze")
self.db.begin() self.db.begin()
# Logging
##########################################################################
def log(self, *args: Any, **kwargs: Any) -> None:
if not self._should_log:
return
def customRepr(x: Any) -> str:
if isinstance(x, str):
return x
return pprint.pformat(x)
path, num, fn, y = traceback.extract_stack(limit=2 + kwargs.get("stack", 0))[0]
buf = "[%s] %s:%s(): %s" % (
intTime(),
os.path.basename(path),
fn,
", ".join([customRepr(x) for x in args]),
)
self._logHnd.write(f"{buf}\n")
if devMode:
print(buf)
def _openLog(self) -> None:
if not self._should_log:
return
lpath = re.sub(r"\.anki2$", ".log", self.path)
if os.path.exists(lpath) and os.path.getsize(lpath) > 10 * 1024 * 1024:
lpath2 = f"{lpath}.old"
if os.path.exists(lpath2):
os.unlink(lpath2)
os.rename(lpath, lpath2)
self._logHnd = open(lpath, "a", encoding="utf8")
def _closeLog(self) -> None:
if not self._should_log:
return
self._logHnd.close()
self._logHnd = None
########################################################################## ##########################################################################
def set_user_flag_for_cards( def set_user_flag_for_cards(
@ -1210,6 +1071,104 @@ table.review-log {{ {revlog_style} }}
"Not intended for public consumption at this time." "Not intended for public consumption at this time."
return self._backend.render_markdown(markdown=text, sanitize=sanitize) return self._backend.render_markdown(markdown=text, sanitize=sanitize)
# Timeboxing
##########################################################################
# fixme: there doesn't seem to be a good reason why this code is in main.py
# instead of covered in reviewer, and the reps tracking is covered by both
# the scheduler and reviewer.py. in the future, we should probably move
# reps tracking to reviewer.py, and remove the startTimebox() calls from
# other locations like overview.py. We just need to make sure not to reset
# the count on things like edits, which we probably could do by checking
# the previous state in moveToState.
# pylint: disable=invalid-name
def startTimebox(self) -> None:
self._startTime = time.time()
self._startReps = self.sched.reps
def timeboxReached(self) -> Union[Literal[False], Tuple[Any, int]]:
"Return (elapsedTime, reps) if timebox reached, or False."
if not self.conf["timeLim"]:
# timeboxing disabled
return False
elapsed = time.time() - self._startTime
if elapsed > self.conf["timeLim"]:
return (self.conf["timeLim"], self.sched.reps - self._startReps)
return False
# Legacy
##########################################################################
@deprecated(info="no longer used")
def log(self, *args: Any, **kwargs: Any) -> None:
print(args, kwargs)
@deprecated(replaced_by=undo_status)
def undo_name(self) -> Optional[str]:
"Undo menu item name, or None if undo unavailable."
status = self.undo_status()
return status.undo or None
# @deprecated(replaced_by=new_note)
def newNote(self, forDeck: bool = True) -> Note:
"Return a new note with the current model."
return Note(self, self.models.current(forDeck))
# @deprecated(replaced_by=add_note)
def addNote(self, note: Note) -> int:
self.add_note(note, note.note_type()["did"])
return len(note.cards())
@deprecated(replaced_by=remove_notes)
def remNotes(self, ids: Sequence[NoteId]) -> None:
self.remove_notes(ids)
@deprecated(replaced_by=remove_notes)
def _remNotes(self, ids: List[NoteId]) -> None:
pass
@deprecated(replaced_by=card_stats)
def cardStats(self, card: Card) -> str:
return self.card_stats(card.id, include_revlog=False)
@deprecated(replaced_by=after_note_updates)
def updateFieldCache(self, nids: List[NoteId]) -> None:
self.after_note_updates(nids, mark_modified=False, generate_cards=False)
@deprecated(replaced_by=after_note_updates)
def genCards(self, nids: List[NoteId]) -> List[int]:
self.after_note_updates(nids, mark_modified=False, generate_cards=True)
# previously returned empty cards, no longer does
return []
@deprecated(info="no longer used")
def emptyCids(self) -> List[CardId]:
return []
@deprecated(info="handled by backend")
def _logRem(self, ids: List[Union[int, NoteId]], type: int) -> None:
self.db.executemany(
"insert into graves values (%d, ?, %d)" % (self.usn(), type),
([x] for x in ids),
)
@deprecated(info="no longer required")
def setMod(self) -> None:
pass
@deprecated(info="no longer required")
def flush(self) -> None:
pass
Collection.register_deprecated_aliases(
clearUndo=Collection.clear_python_undo,
markReview=Collection.save_card_review_undo_info,
findReplace=Collection.find_and_replace,
remCards=Collection.remove_cards_and_orphaned_notes,
)
# legacy name # legacy name
_Collection = Collection _Collection = Collection

View file

@ -349,7 +349,7 @@ class DeckManager(DeprecatedNamesMixin):
def remove_config(self, id: DeckConfigId) -> None: def remove_config(self, id: DeckConfigId) -> None:
"Remove a configuration and update all decks using it." "Remove a configuration and update all decks using it."
self.col.modSchema(check=True) self.col.mod_schema(check=True)
for deck in self.all(): for deck in self.all():
# ignore cram decks # ignore cram decks
if "conf" not in deck: if "conf" not in deck:

View file

@ -197,7 +197,7 @@ class AnkiExporter(Exporter):
def exportInto(self, path: str) -> None: def exportInto(self, path: str) -> None:
# sched info+v2 scheduler not compatible w/ older clients # sched info+v2 scheduler not compatible w/ older clients
self._v2sched = self.col.schedVer() != 1 and self.includeSched self._v2sched = self.col.sched_ver() != 1 and self.includeSched
# create a new collection at the target # create a new collection at the target
try: try:
@ -246,7 +246,7 @@ class AnkiExporter(Exporter):
# need to reset card state # need to reset card state
self.dst.sched.resetCards(cids) self.dst.sched.resetCards(cids)
# models - start with zero # models - start with zero
self.dst.modSchema(check=False) self.dst.mod_schema(check=False)
self.dst.models.remove_all_notetypes() self.dst.models.remove_all_notetypes()
for m in self.src.models.all(): for m in self.src.models.all():
if int(m["id"]) in mids: if int(m["id"]) in mids:
@ -298,7 +298,7 @@ class AnkiExporter(Exporter):
self.mediaFiles = list(media.keys()) self.mediaFiles = list(media.keys())
self.dst.crt = self.src.crt self.dst.crt = self.src.crt
# todo: tags? # todo: tags?
self.count = self.dst.cardCount() self.count = self.dst.card_count()
self.postExport() self.postExport()
self.dst.close(downgrade=True) self.dst.close(downgrade=True)
@ -426,8 +426,8 @@ class AnkiCollectionPackageExporter(AnkiPackageExporter):
def doExport(self, z, path): def doExport(self, z, path):
"Export collection. Caller must re-open afterwards." "Export collection. Caller must re-open afterwards."
# close our deck & write it into the zip file # close our deck & write it into the zip file
self.count = self.col.cardCount() self.count = self.col.card_count()
v2 = self.col.schedVer() != 1 v2 = self.col.sched_ver() != 1
mdir = self.col.media.dir() mdir = self.col.media.dir()
self.col.close(downgrade=True) self.col.close(downgrade=True)
if not v2: if not v2:

View file

@ -53,11 +53,11 @@ class Anki2Importer(Importer):
self.dst = self.col self.dst = self.col
self.src = Collection(self.file) self.src = Collection(self.file)
if not self._importing_v2 and self.col.schedVer() != 1: if not self._importing_v2 and self.col.sched_ver() != 1:
# any scheduling included? # any scheduling included?
if self.src.db.scalar("select 1 from cards where queue != 0 limit 1"): if self.src.db.scalar("select 1 from cards where queue != 0 limit 1"):
self.source_needs_upgrade = True self.source_needs_upgrade = True
elif self._importing_v2 and self.col.schedVer() == 1: elif self._importing_v2 and self.col.sched_ver() == 1:
raise Exception("must upgrade to new scheduler to import this file") raise Exception("must upgrade to new scheduler to import this file")
def _import(self) -> None: def _import(self) -> None:
@ -186,7 +186,7 @@ class Anki2Importer(Importer):
self.dst.db.executemany( self.dst.db.executemany(
"insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)", update "insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)", update
) )
self.dst.updateFieldCache(dirty) self.dst.after_note_updates(dirty, mark_modified=False, generate_cards=False)
# determine if note is a duplicate, and adjust mid and/or guid as required # determine if note is a duplicate, and adjust mid and/or guid as required
# returns true if note should be added # returns true if note should be added

View file

@ -400,7 +400,7 @@ and notes.mid = ? and cards.ord = ?""",
cmap: Optional[Dict[int, Optional[int]]], cmap: Optional[Dict[int, Optional[int]]],
) -> None: ) -> None:
# - maps are ord->ord, and there should not be duplicate targets # - maps are ord->ord, and there should not be duplicate targets
self.col.modSchema(check=True) self.col.mod_schema(check=True)
assert fmap assert fmap
field_map = self._convert_legacy_map(fmap, len(newModel["flds"])) field_map = self._convert_legacy_map(fmap, len(newModel["flds"]))
if ( if (

View file

@ -46,7 +46,6 @@ class Scheduler(V2):
self._haveQueues = False self._haveQueues = False
def answerCard(self, card: Card, ease: int) -> None: def answerCard(self, card: Card, ease: int) -> None:
self.col.log()
assert 1 <= ease <= 4 assert 1 <= ease <= 4
self.col.save_card_review_undo_info(card) self.col.save_card_review_undo_info(card)
if self._burySiblingsOnAnswer: if self._burySiblingsOnAnswer:

View file

@ -104,7 +104,6 @@ class Scheduler(SchedulerBaseWithLegacy):
self.reset() self.reset()
card = self._getCard() card = self._getCard()
if card: if card:
self.col.log(card)
if not self._burySiblingsOnAnswer: if not self._burySiblingsOnAnswer:
self._burySiblings(card) self._burySiblings(card)
card.start_timer() card.start_timer()
@ -452,7 +451,6 @@ limit ?"""
########################################################################## ##########################################################################
def answerCard(self, card: Card, ease: int) -> None: def answerCard(self, card: Card, ease: int) -> None:
self.col.log()
assert 1 <= ease <= 4 assert 1 <= ease <= 4
assert 0 <= card.queue <= 4 assert 0 <= card.queue <= 4
self.col.save_card_review_undo_info(card) self.col.save_card_review_undo_info(card)

View file

@ -665,7 +665,7 @@ select count(), avg(ivl), max(ivl) from cards where did in %s and queue = {QUEUE
[13, 3], [13, 3],
[14, 4], [14, 4],
] ]
if self.col.schedVer() != 1: if self.col.sched_ver() != 1:
ticks.insert(3, [4, 4]) ticks.insert(3, [4, 4])
txt = self._title( txt = self._title(
"Answer Buttons", "The number of times you have pressed each button." "Answer Buttons", "The number of times you have pressed each button."
@ -725,7 +725,7 @@ select count(), avg(ivl), max(ivl) from cards where did in %s and queue = {QUEUE
lim = "where " + " and ".join(lims) lim = "where " + " and ".join(lims)
else: else:
lim = "" lim = ""
if self.col.schedVer() == 1: if self.col.sched_ver() == 1:
ease4repl = "3" ease4repl = "3"
else: else:
ease4repl = "ease" ease4repl = "ease"
@ -815,7 +815,7 @@ order by thetype, ease"""
lim = self._revlogLimit() lim = self._revlogLimit()
if lim: if lim:
lim = " and " + lim lim = " and " + lim
if self.col.schedVer() == 1: if self.col.sched_ver() == 1:
sd = datetime.datetime.fromtimestamp(self.col.crt) sd = datetime.datetime.fromtimestamp(self.col.crt)
rolloverHour = sd.hour rolloverHour = sd.hour
else: else:

View file

@ -16,8 +16,8 @@ def test_delete():
col.reset() col.reset()
col.sched.answerCard(col.sched.getCard(), 2) col.sched.answerCard(col.sched.getCard(), 2)
col.remove_cards_and_orphaned_notes([cid]) col.remove_cards_and_orphaned_notes([cid])
assert col.cardCount() == 0 assert col.card_count() == 0
assert col.noteCount() == 0 assert col.note_count() == 0
assert col.db.scalar("select count() from notes") == 0 assert col.db.scalar("select count() from notes") == 0
assert col.db.scalar("select count() from cards") == 0 assert col.db.scalar("select count() from cards") == 0
assert col.db.scalar("select count() from graves") == 2 assert col.db.scalar("select count() from graves") == 2
@ -72,7 +72,7 @@ def test_gendeck():
note = col.new_note(cloze) note = col.new_note(cloze)
note["Text"] = "{{c1::one}}" note["Text"] = "{{c1::one}}"
col.addNote(note) col.addNote(note)
assert col.cardCount() == 1 assert col.card_count() == 1
assert note.cards()[0].did == 1 assert note.cards()[0].did == 1
# set the model to a new default col # set the model to a new default col
newId = col.decks.id("new") newId = col.decks.id("new")

View file

@ -62,14 +62,14 @@ def test_noteAddDelete():
t["afmt"] = "{{Front}}" t["afmt"] = "{{Front}}"
mm.add_template(m, t) mm.add_template(m, t)
mm.save(m) mm.save(m)
assert col.cardCount() == 2 assert col.card_count() == 2
# creating new notes should use both cards # creating new notes should use both cards
note = col.newNote() note = col.newNote()
note["Front"] = "three" note["Front"] = "three"
note["Back"] = "four" note["Back"] = "four"
n = col.addNote(note) n = col.addNote(note)
assert n == 2 assert n == 2
assert col.cardCount() == 4 assert col.card_count() == 4
# check q/a generation # check q/a generation
c0 = note.cards()[0] c0 = note.cards()[0]
assert "three" in c0.question() assert "three" in c0.question()

View file

@ -57,9 +57,9 @@ def test_remove():
col.addNote(note) col.addNote(note)
c = note.cards()[0] c = note.cards()[0]
assert c.did == deck1 assert c.did == deck1
assert col.cardCount() == 1 assert col.card_count() == 1
col.decks.remove([deck1]) col.decks.remove([deck1])
assert col.cardCount() == 0 assert col.card_count() == 0
# if we try to get it, we get the default # if we try to get it, we get the default
assert col.decks.name(c.did) == "[no deck]" assert col.decks.name(c.did) == "[no deck]"

View file

@ -65,7 +65,7 @@ def test_export_anki():
assert conf["id"] != 1 assert conf["id"] != 1
# connect to new deck # connect to new deck
col2 = aopen(newname) col2 = aopen(newname)
assert col2.cardCount() == 2 assert col2.card_count() == 2
# as scheduling was reset, should also revert decks to default conf # as scheduling was reset, should also revert decks to default conf
did = col2.decks.id("test", create=False) did = col2.decks.id("test", create=False)
assert did assert did
@ -82,7 +82,7 @@ def test_export_anki():
e.did = 1 e.did = 1
e.exportInto(newname) e.exportInto(newname)
col2 = aopen(newname) col2 = aopen(newname)
assert col2.cardCount() == 1 assert col2.card_count() == 1
def test_export_ankipkg(): def test_export_ankipkg():
@ -109,7 +109,6 @@ def test_export_anki_due():
note["Front"] = "foo" note["Front"] = "foo"
col.addNote(note) col.addNote(note)
col.crt -= 86400 * 10 col.crt -= 86400 * 10
col.flush()
col.sched.reset() col.sched.reset()
c = col.sched.getCard() c = col.sched.getCard()
col.sched.answerCard(c, 3) col.sched.answerCard(c, 3)

View file

@ -14,7 +14,7 @@ class DummyCollection:
return None return None
def test_findCards(): def test_find_cards():
col = getEmptyCol() col = getEmptyCol()
note = col.newNote() note = col.newNote()
note["Front"] = "dog" note["Front"] = "dog"
@ -49,84 +49,80 @@ def test_findCards():
col.save() col.save()
latestCardIds = [c.id for c in note.cards()] latestCardIds = [c.id for c in note.cards()]
# tag searches # tag searches
assert len(col.findCards("tag:*")) == 5 assert len(col.find_cards("tag:*")) == 5
assert len(col.findCards("tag:\\*")) == 1 assert len(col.find_cards("tag:\\*")) == 1
assert len(col.findCards("tag:%")) == 1 assert len(col.find_cards("tag:%")) == 1
assert len(col.findCards("tag:sheep_goat")) == 0 assert len(col.find_cards("tag:sheep_goat")) == 0
assert len(col.findCards('"tag:sheep goat"')) == 0 assert len(col.find_cards('"tag:sheep goat"')) == 0
assert len(col.findCards('"tag:* *"')) == 0 assert len(col.find_cards('"tag:* *"')) == 0
assert len(col.findCards("tag:animal_1")) == 2 assert len(col.find_cards("tag:animal_1")) == 2
assert len(col.findCards("tag:animal\\_1")) == 1 assert len(col.find_cards("tag:animal\\_1")) == 1
assert not col.findCards("tag:donkey") assert not col.find_cards("tag:donkey")
assert len(col.findCards("tag:sheep")) == 1 assert len(col.find_cards("tag:sheep")) == 1
assert len(col.findCards("tag:sheep tag:goat")) == 1 assert len(col.find_cards("tag:sheep tag:goat")) == 1
assert len(col.findCards("tag:sheep tag:monkey")) == 0 assert len(col.find_cards("tag:sheep tag:monkey")) == 0
assert len(col.findCards("tag:monkey")) == 1 assert len(col.find_cards("tag:monkey")) == 1
assert len(col.findCards("tag:sheep -tag:monkey")) == 1 assert len(col.find_cards("tag:sheep -tag:monkey")) == 1
assert len(col.findCards("-tag:sheep")) == 4 assert len(col.find_cards("-tag:sheep")) == 4
col.tags.bulk_add(col.db.list("select id from notes"), "foo bar") col.tags.bulk_add(col.db.list("select id from notes"), "foo bar")
assert len(col.findCards("tag:foo")) == len(col.findCards("tag:bar")) == 5 assert len(col.find_cards("tag:foo")) == len(col.find_cards("tag:bar")) == 5
col.tags.bulkRem(col.db.list("select id from notes"), "foo") col.tags.bulkRem(col.db.list("select id from notes"), "foo")
assert len(col.findCards("tag:foo")) == 0 assert len(col.find_cards("tag:foo")) == 0
assert len(col.findCards("tag:bar")) == 5 assert len(col.find_cards("tag:bar")) == 5
# text searches # text searches
assert len(col.findCards("cat")) == 2 assert len(col.find_cards("cat")) == 2
assert len(col.findCards("cat -dog")) == 1 assert len(col.find_cards("cat -dog")) == 1
assert len(col.findCards("cat -dog")) == 1 assert len(col.find_cards("cat -dog")) == 1
assert len(col.findCards("are goats")) == 1 assert len(col.find_cards("are goats")) == 1
assert len(col.findCards('"are goats"')) == 0 assert len(col.find_cards('"are goats"')) == 0
assert len(col.findCards('"goats are"')) == 1 assert len(col.find_cards('"goats are"')) == 1
# card states # card states
c = note.cards()[0] c = note.cards()[0]
c.queue = c.type = CARD_TYPE_REV c.queue = c.type = CARD_TYPE_REV
assert col.findCards("is:review") == [] assert col.find_cards("is:review") == []
c.flush() c.flush()
assert col.findCards("is:review") == [c.id] assert col.find_cards("is:review") == [c.id]
assert col.findCards("is:due") == [] assert col.find_cards("is:due") == []
c.due = 0 c.due = 0
c.queue = QUEUE_TYPE_REV c.queue = QUEUE_TYPE_REV
c.flush() c.flush()
assert col.findCards("is:due") == [c.id] assert col.find_cards("is:due") == [c.id]
assert len(col.findCards("-is:due")) == 4 assert len(col.find_cards("-is:due")) == 4
c.queue = QUEUE_TYPE_SUSPENDED c.queue = QUEUE_TYPE_SUSPENDED
# ensure this card gets a later mod time # ensure this card gets a later mod time
c.flush() c.flush()
col.db.execute("update cards set mod = mod + 1 where id = ?", c.id) col.db.execute("update cards set mod = mod + 1 where id = ?", c.id)
assert col.findCards("is:suspended") == [c.id] assert col.find_cards("is:suspended") == [c.id]
# nids # nids
assert col.findCards("nid:54321") == [] assert col.find_cards("nid:54321") == []
assert len(col.findCards(f"nid:{note.id}")) == 2 assert len(col.find_cards(f"nid:{note.id}")) == 2
assert len(col.findCards(f"nid:{n1id},{n2id}")) == 2 assert len(col.find_cards(f"nid:{n1id},{n2id}")) == 2
# templates # templates
assert len(col.findCards("card:foo")) == 0 assert len(col.find_cards("card:foo")) == 0
assert len(col.findCards('"card:card 1"')) == 4 assert len(col.find_cards('"card:card 1"')) == 4
assert len(col.findCards("card:reverse")) == 1 assert len(col.find_cards("card:reverse")) == 1
assert len(col.findCards("card:1")) == 4 assert len(col.find_cards("card:1")) == 4
assert len(col.findCards("card:2")) == 1 assert len(col.find_cards("card:2")) == 1
# fields # fields
assert len(col.findCards("front:dog")) == 1 assert len(col.find_cards("front:dog")) == 1
assert len(col.findCards("-front:dog")) == 4 assert len(col.find_cards("-front:dog")) == 4
assert len(col.findCards("front:sheep")) == 0 assert len(col.find_cards("front:sheep")) == 0
assert len(col.findCards("back:sheep")) == 2 assert len(col.find_cards("back:sheep")) == 2
assert len(col.findCards("-back:sheep")) == 3 assert len(col.find_cards("-back:sheep")) == 3
assert len(col.findCards("front:do")) == 0 assert len(col.find_cards("front:do")) == 0
assert len(col.findCards("front:*")) == 5 assert len(col.find_cards("front:*")) == 5
# ordering # ordering
col.conf["sortType"] = "noteCrt" col.conf["sortType"] = "noteCrt"
col.flush() assert col.find_cards("front:*", order=True)[-1] in latestCardIds
assert col.findCards("front:*", order=True)[-1] in latestCardIds assert col.find_cards("", order=True)[-1] in latestCardIds
assert col.findCards("", order=True)[-1] in latestCardIds
col.conf["sortType"] = "noteFld" col.conf["sortType"] = "noteFld"
col.flush() assert col.find_cards("", order=True)[0] == catCard.id
assert col.findCards("", order=True)[0] == catCard.id assert col.find_cards("", order=True)[-1] in latestCardIds
assert col.findCards("", order=True)[-1] in latestCardIds
col.conf["sortType"] = "cardMod" col.conf["sortType"] = "cardMod"
col.flush() assert col.find_cards("", order=True)[-1] in latestCardIds
assert col.findCards("", order=True)[-1] in latestCardIds assert col.find_cards("", order=True)[0] == firstCardId
assert col.findCards("", order=True)[0] == firstCardId
col.set_config_bool(Config.Bool.BROWSER_SORT_BACKWARDS, True) col.set_config_bool(Config.Bool.BROWSER_SORT_BACKWARDS, True)
col.flush() assert col.find_cards("", order=True)[0] in latestCardIds
assert col.findCards("", order=True)[0] in latestCardIds
assert ( assert (
col.find_cards("", order=col.get_browser_column("cardDue"), reverse=False)[0] col.find_cards("", order=col.get_browser_column("cardDue"), reverse=False)[0]
== firstCardId == firstCardId
@ -136,42 +132,42 @@ def test_findCards():
!= firstCardId != firstCardId
) )
# model # model
assert len(col.findCards("note:basic")) == 3 assert len(col.find_cards("note:basic")) == 3
assert len(col.findCards("-note:basic")) == 2 assert len(col.find_cards("-note:basic")) == 2
assert len(col.findCards("-note:foo")) == 5 assert len(col.find_cards("-note:foo")) == 5
# col # col
assert len(col.findCards("deck:default")) == 5 assert len(col.find_cards("deck:default")) == 5
assert len(col.findCards("-deck:default")) == 0 assert len(col.find_cards("-deck:default")) == 0
assert len(col.findCards("-deck:foo")) == 5 assert len(col.find_cards("-deck:foo")) == 5
assert len(col.findCards("deck:def*")) == 5 assert len(col.find_cards("deck:def*")) == 5
assert len(col.findCards("deck:*EFAULT")) == 5 assert len(col.find_cards("deck:*EFAULT")) == 5
assert len(col.findCards("deck:*cefault")) == 0 assert len(col.find_cards("deck:*cefault")) == 0
# full search # full search
note = col.newNote() note = col.newNote()
note["Front"] = "hello<b>world</b>" note["Front"] = "hello<b>world</b>"
note["Back"] = "abc" note["Back"] = "abc"
col.addNote(note) col.addNote(note)
# as it's the sort field, it matches # as it's the sort field, it matches
assert len(col.findCards("helloworld")) == 2 assert len(col.find_cards("helloworld")) == 2
# assert len(col.findCards("helloworld", full=True)) == 2 # assert len(col.find_cards("helloworld", full=True)) == 2
# if we put it on the back, it won't # if we put it on the back, it won't
(note["Front"], note["Back"]) = (note["Back"], note["Front"]) (note["Front"], note["Back"]) = (note["Back"], note["Front"])
note.flush() note.flush()
assert len(col.findCards("helloworld")) == 0 assert len(col.find_cards("helloworld")) == 0
# assert len(col.findCards("helloworld", full=True)) == 2 # assert len(col.find_cards("helloworld", full=True)) == 2
# assert len(col.findCards("back:helloworld", full=True)) == 2 # assert len(col.find_cards("back:helloworld", full=True)) == 2
# searching for an invalid special tag should not error # searching for an invalid special tag should not error
with pytest.raises(Exception): with pytest.raises(Exception):
len(col.findCards("is:invalid")) len(col.find_cards("is:invalid"))
# should be able to limit to parent col, no children # should be able to limit to parent col, no children
id = col.db.scalar("select id from cards limit 1") id = col.db.scalar("select id from cards limit 1")
col.db.execute( col.db.execute(
"update cards set did = ? where id = ?", col.decks.id("Default::Child"), id "update cards set did = ? where id = ?", col.decks.id("Default::Child"), id
) )
col.save() col.save()
assert len(col.findCards("deck:default")) == 7 assert len(col.find_cards("deck:default")) == 7
assert len(col.findCards("deck:default::child")) == 1 assert len(col.find_cards("deck:default::child")) == 1
assert len(col.findCards("deck:default -deck:default::*")) == 6 assert len(col.find_cards("deck:default -deck:default::*")) == 6
# properties # properties
id = col.db.scalar("select id from cards limit 1") id = col.db.scalar("select id from cards limit 1")
col.db.execute( col.db.execute(
@ -179,61 +175,61 @@ def test_findCards():
"where id = ?", "where id = ?",
id, id,
) )
assert len(col.findCards("prop:ivl>5")) == 1 assert len(col.find_cards("prop:ivl>5")) == 1
assert len(col.findCards("prop:ivl<5")) > 1 assert len(col.find_cards("prop:ivl<5")) > 1
assert len(col.findCards("prop:ivl>=5")) == 1 assert len(col.find_cards("prop:ivl>=5")) == 1
assert len(col.findCards("prop:ivl=9")) == 0 assert len(col.find_cards("prop:ivl=9")) == 0
assert len(col.findCards("prop:ivl=10")) == 1 assert len(col.find_cards("prop:ivl=10")) == 1
assert len(col.findCards("prop:ivl!=10")) > 1 assert len(col.find_cards("prop:ivl!=10")) > 1
assert len(col.findCards("prop:due>0")) == 1 assert len(col.find_cards("prop:due>0")) == 1
# due dates should work # due dates should work
assert len(col.findCards("prop:due=29")) == 0 assert len(col.find_cards("prop:due=29")) == 0
assert len(col.findCards("prop:due=30")) == 1 assert len(col.find_cards("prop:due=30")) == 1
# ease factors # ease factors
assert len(col.findCards("prop:ease=2.3")) == 0 assert len(col.find_cards("prop:ease=2.3")) == 0
assert len(col.findCards("prop:ease=2.2")) == 1 assert len(col.find_cards("prop:ease=2.2")) == 1
assert len(col.findCards("prop:ease>2")) == 1 assert len(col.find_cards("prop:ease>2")) == 1
assert len(col.findCards("-prop:ease>2")) > 1 assert len(col.find_cards("-prop:ease>2")) > 1
# recently failed # recently failed
if not isNearCutoff(): if not isNearCutoff():
# rated # rated
assert len(col.findCards("rated:1:1")) == 0 assert len(col.find_cards("rated:1:1")) == 0
assert len(col.findCards("rated:1:2")) == 0 assert len(col.find_cards("rated:1:2")) == 0
c = col.sched.getCard() c = col.sched.getCard()
col.sched.answerCard(c, 2) col.sched.answerCard(c, 2)
assert len(col.findCards("rated:1:1")) == 0 assert len(col.find_cards("rated:1:1")) == 0
assert len(col.findCards("rated:1:2")) == 1 assert len(col.find_cards("rated:1:2")) == 1
c = col.sched.getCard() c = col.sched.getCard()
col.sched.answerCard(c, 1) col.sched.answerCard(c, 1)
assert len(col.findCards("rated:1:1")) == 1 assert len(col.find_cards("rated:1:1")) == 1
assert len(col.findCards("rated:1:2")) == 1 assert len(col.find_cards("rated:1:2")) == 1
assert len(col.findCards("rated:1")) == 2 assert len(col.find_cards("rated:1")) == 2
assert len(col.findCards("rated:2:2")) == 1 assert len(col.find_cards("rated:2:2")) == 1
assert len(col.findCards("rated:0")) == len(col.findCards("rated:1")) assert len(col.find_cards("rated:0")) == len(col.find_cards("rated:1"))
# added # added
col.db.execute("update cards set id = id - 86400*1000 where id = ?", id) col.db.execute("update cards set id = id - 86400*1000 where id = ?", id)
assert len(col.findCards("added:1")) == col.cardCount() - 1 assert len(col.find_cards("added:1")) == col.card_count() - 1
assert len(col.findCards("added:2")) == col.cardCount() assert len(col.find_cards("added:2")) == col.card_count()
assert len(col.findCards("added:0")) == len(col.findCards("added:1")) assert len(col.find_cards("added:0")) == len(col.find_cards("added:1"))
else: else:
print("some find tests disabled near cutoff") print("some find tests disabled near cutoff")
# empty field # empty field
assert len(col.findCards("front:")) == 0 assert len(col.find_cards("front:")) == 0
note = col.newNote() note = col.newNote()
note["Front"] = "" note["Front"] = ""
note["Back"] = "abc2" note["Back"] = "abc2"
assert col.addNote(note) == 1 assert col.addNote(note) == 1
assert len(col.findCards("front:")) == 1 assert len(col.find_cards("front:")) == 1
# OR searches and nesting # OR searches and nesting
assert len(col.findCards("tag:monkey or tag:sheep")) == 2 assert len(col.find_cards("tag:monkey or tag:sheep")) == 2
assert len(col.findCards("(tag:monkey OR tag:sheep)")) == 2 assert len(col.find_cards("(tag:monkey OR tag:sheep)")) == 2
assert len(col.findCards("-(tag:monkey OR tag:sheep)")) == 6 assert len(col.find_cards("-(tag:monkey OR tag:sheep)")) == 6
assert len(col.findCards("tag:monkey or (tag:sheep sheep)")) == 2 assert len(col.find_cards("tag:monkey or (tag:sheep sheep)")) == 2
assert len(col.findCards("tag:monkey or (tag:sheep octopus)")) == 1 assert len(col.find_cards("tag:monkey or (tag:sheep octopus)")) == 1
# flag # flag
with pytest.raises(Exception): with pytest.raises(Exception):
col.findCards("flag:12") col.find_cards("flag:12")
def test_findReplace(): def test_findReplace():
@ -304,15 +300,15 @@ def test_findDupes():
note4["Front"] = "quuux" note4["Front"] = "quuux"
note4["Back"] = "nope" note4["Back"] = "nope"
col.addNote(note4) col.addNote(note4)
r = col.findDupes("Back") r = col.find_dupes("Back")
assert r[0][0] == "bar" assert r[0][0] == "bar"
assert len(r[0][1]) == 3 assert len(r[0][1]) == 3
# valid search # valid search
r = col.findDupes("Back", "bar") r = col.find_dupes("Back", "bar")
assert r[0][0] == "bar" assert r[0][0] == "bar"
assert len(r[0][1]) == 3 assert len(r[0][1]) == 3
# excludes everything # excludes everything
r = col.findDupes("Back", "invalid") r = col.find_dupes("Back", "invalid")
assert not r assert not r
# front isn't dupe # front isn't dupe
assert col.findDupes("Front") == [] assert col.find_dupes("Front") == []

View file

@ -17,16 +17,16 @@ def test_flags():
c.flush() c.flush()
# no flags to start with # no flags to start with
assert c.user_flag() == 0 assert c.user_flag() == 0
assert len(col.findCards("flag:0")) == 1 assert len(col.find_cards("flag:0")) == 1
assert len(col.findCards("flag:1")) == 0 assert len(col.find_cards("flag:1")) == 0
# set flag 2 # set flag 2
col.set_user_flag_for_cards(2, [c.id]) col.set_user_flag_for_cards(2, [c.id])
c.load() c.load()
assert c.user_flag() == 2 assert c.user_flag() == 2
assert c.flags & origBits == origBits assert c.flags & origBits == origBits
assert len(col.findCards("flag:0")) == 0 assert len(col.find_cards("flag:0")) == 0
assert len(col.findCards("flag:2")) == 1 assert len(col.find_cards("flag:2")) == 1
assert len(col.findCards("flag:3")) == 0 assert len(col.find_cards("flag:3")) == 0
# change to 3 # change to 3
col.set_user_flag_for_cards(3, [c.id]) col.set_user_flag_for_cards(3, [c.id])
c.load() c.load()

View file

@ -115,9 +115,9 @@ def test_anki2_diffmodel_templates():
imp.dupeOnSchemaChange = True imp.dupeOnSchemaChange = True
imp.run() imp.run()
# collection should contain the note we imported # collection should contain the note we imported
assert dst.noteCount() == 1 assert dst.note_count() == 1
# the front template should contain the text added in the 2nd package # the front template should contain the text added in the 2nd package
tcid = dst.findCards("")[0] # only 1 note in collection tcid = dst.find_cards("")[0] # only 1 note in collection
tnote = dst.getCard(tcid).note() tnote = dst.getCard(tcid).note()
assert "Changed Front Template" in tnote.cards()[0].template()["qfmt"] assert "Changed Front Template" in tnote.cards()[0].template()["qfmt"]
@ -138,7 +138,7 @@ def test_anki2_updates():
assert imp.added == 0 assert imp.added == 0
assert imp.updated == 0 assert imp.updated == 0
# importing a newer note should update # importing a newer note should update
assert dst.noteCount() == 1 assert dst.note_count() == 1
assert dst.db.scalar("select flds from notes").startswith("hello") assert dst.db.scalar("select flds from notes").startswith("hello")
col = getUpgradeDeckPath("update2.apkg") col = getUpgradeDeckPath("update2.apkg")
imp = AnkiPackageImporter(dst, col) imp = AnkiPackageImporter(dst, col)
@ -146,7 +146,7 @@ def test_anki2_updates():
assert imp.dupes == 0 assert imp.dupes == 0
assert imp.added == 0 assert imp.added == 0
assert imp.updated == 1 assert imp.updated == 1
assert dst.noteCount() == 1 assert dst.note_count() == 1
assert dst.db.scalar("select flds from notes").startswith("goodbye") assert dst.db.scalar("select flds from notes").startswith("goodbye")
@ -176,12 +176,12 @@ def test_csv():
i.run() i.run()
assert i.total == 0 assert i.total == 0
# and if dupes mode, will reimport everything # and if dupes mode, will reimport everything
assert col.cardCount() == 5 assert col.card_count() == 5
i.importMode = 2 i.importMode = 2
i.run() i.run()
# includes repeated field # includes repeated field
assert i.total == 6 assert i.total == 6
assert col.cardCount() == 11 assert col.card_count() == 11
col.close() col.close()
@ -330,7 +330,7 @@ def test_mnemo():
file = str(os.path.join(testDir, "support", "mnemo.db")) file = str(os.path.join(testDir, "support", "mnemo.db"))
i = MnemosyneImporter(col, file) i = MnemosyneImporter(col, file)
i.run() i.run()
assert col.cardCount() == 7 assert col.card_count() == 7
assert "a_longer_tag" in col.tags.all() assert "a_longer_tag" in col.tags.all()
assert col.db.scalar(f"select count() from cards where type = {CARD_TYPE_NEW}") == 1 assert col.db.scalar(f"select count() from cards where type = {CARD_TYPE_NEW}") == 1
col.close() col.close()

View file

@ -16,9 +16,9 @@ def test_modelDelete():
note["Front"] = "1" note["Front"] = "1"
note["Back"] = "2" note["Back"] = "2"
col.addNote(note) col.addNote(note)
assert col.cardCount() == 1 assert col.card_count() == 1
col.models.remove(col.models.current()["id"]) col.models.remove(col.models.current()["id"])
assert col.cardCount() == 0 assert col.card_count() == 0
def test_modelCopy(): def test_modelCopy():
@ -95,7 +95,7 @@ def test_templates():
note["Front"] = "1" note["Front"] = "1"
note["Back"] = "2" note["Back"] = "2"
col.addNote(note) col.addNote(note)
assert col.cardCount() == 2 assert col.card_count() == 2
(c, c2) = note.cards() (c, c2) = note.cards()
# first card should have first ord # first card should have first ord
assert c.ord == 0 assert c.ord == 0
@ -110,7 +110,7 @@ def test_templates():
# removing a template should delete its cards # removing a template should delete its cards
col.models.remove_template(m, m["tmpls"][0]) col.models.remove_template(m, m["tmpls"][0])
col.models.update(m) col.models.update(m)
assert col.cardCount() == 1 assert col.card_count() == 1
# and should have updated the other cards' ordinals # and should have updated the other cards' ordinals
c = note.cards()[0] c = note.cards()[0]
assert c.ord == 0 assert c.ord == 0
@ -146,7 +146,7 @@ def test_cloze_ordinals():
note = col.newNote() note = col.newNote()
note["Text"] = "{{c1::firstQ::firstA}}{{c2::secondQ::secondA}}" note["Text"] = "{{c1::firstQ::firstA}}{{c2::secondQ::secondA}}"
col.addNote(note) col.addNote(note)
assert col.cardCount() == 2 assert col.card_count() == 2
(c, c2) = note.cards() (c, c2) = note.cards()
# first card should have first ord # first card should have first ord
assert c.ord == 0 assert c.ord == 0
@ -202,10 +202,10 @@ def test_cloze():
note.cards()[0].answer() note.cards()[0].answer()
) )
# if we add another cloze, a card should be generated # if we add another cloze, a card should be generated
cnt = col.cardCount() cnt = col.card_count()
note["Text"] = "{{c2::hello}} {{c1::foo}}" note["Text"] = "{{c2::hello}} {{c1::foo}}"
note.flush() note.flush()
assert col.cardCount() == cnt + 1 assert col.card_count() == cnt + 1
# 0 or negative indices are not supported # 0 or negative indices are not supported
note["Text"] += "{{c0::zero}} {{c-1:foo}}" note["Text"] += "{{c0::zero}} {{c-1:foo}}"
note.flush() note.flush()

View file

@ -15,7 +15,7 @@ def getEmptyCol() -> Collection:
col = getEmptyColOrig() col = getEmptyColOrig()
# only safe in test environment # only safe in test environment
col.set_config("schedVer", 1) col.set_config("schedVer", 1)
col._loadScheduler() col._load_scheduler()
return col return col
@ -820,7 +820,7 @@ def test_ordcycle():
note["Front"] = "1" note["Front"] = "1"
note["Back"] = "1" note["Back"] = "1"
col.addNote(note) col.addNote(note)
assert col.cardCount() == 3 assert col.card_count() == 3
col.reset() col.reset()
# ordinals should arrive in order # ordinals should arrive in order
assert col.sched.getCard().ord == 0 assert col.sched.getCard().ord == 0

View file

@ -894,7 +894,7 @@ def test_ordcycle():
note["Front"] = "1" note["Front"] = "1"
note["Back"] = "1" note["Back"] = "1"
col.addNote(note) col.addNote(note)
assert col.cardCount() == 3 assert col.card_count() == 3
conf = col.decks.get_config(1) conf = col.decks.get_config(1)
conf["new"]["bury"] = False conf["new"]["bury"] = False

View file

@ -14,12 +14,12 @@ def test_stats():
col.addNote(note) col.addNote(note)
c = note.cards()[0] c = note.cards()[0]
# card stats # card stats
assert col.cardStats(c) assert col.card_stats(c.id, include_revlog=True)
col.reset() col.reset()
c = col.sched.getCard() c = col.sched.getCard()
col.sched.answerCard(c, 3) col.sched.answerCard(c, 3)
col.sched.answerCard(c, 2) col.sched.answerCard(c, 2)
assert col.cardStats(c) assert col.card_stats(c.id, include_revlog=True)
def test_graphs_empty(): def test_graphs_empty():

View file

@ -16,33 +16,33 @@ def getEmptyCol():
def test_op(): def test_op():
col = getEmptyCol() col = getEmptyCol()
# should have no undo by default # should have no undo by default
assert not col.undoName() assert not col.undo_status().undo
# let's adjust a study option # let's adjust a study option
col.save("studyopts") col.save("studyopts")
col.conf["abc"] = 5 col.conf["abc"] = 5
# it should be listed as undoable # it should be listed as undoable
assert col.undoName() == "studyopts" assert col.undo_status().undo == "studyopts"
# with about 5 minutes until it's clobbered # with about 5 minutes until it's clobbered
assert time.time() - col._last_checkpoint_at < 1 assert time.time() - col._last_checkpoint_at < 1
# undoing should restore the old value # undoing should restore the old value
col.undo_legacy() col.undo_legacy()
assert not col.undoName() assert not col.undo_status().undo
assert "abc" not in col.conf assert "abc" not in col.conf
# an (auto)save will clear the undo # an (auto)save will clear the undo
col.save("foo") col.save("foo")
assert col.undoName() == "foo" assert col.undo_status().undo == "foo"
col.save() col.save()
assert not col.undoName() assert not col.undo_status().undo
# and a review will, too # and a review will, too
col.save("add") col.save("add")
note = col.newNote() note = col.newNote()
note["Front"] = "one" note["Front"] = "one"
col.addNote(note) col.addNote(note)
col.reset() col.reset()
assert "add" in col.undoName().lower() assert "add" in col.undo_status().undo.lower()
c = col.sched.getCard() c = col.sched.getCard()
col.sched.answerCard(c, 2) col.sched.answerCard(c, 2)
assert col.undoName() == "Review" assert col.undo_status().undo == "Review"
def test_review(): def test_review():
@ -64,14 +64,14 @@ def test_review():
assert col.sched.counts() == (1, 1, 0) assert col.sched.counts() == (1, 1, 0)
assert c.queue == QUEUE_TYPE_LRN assert c.queue == QUEUE_TYPE_LRN
# undo # undo
assert col.undoName() assert col.undo_status().undo
col.undo_legacy() col.undo_legacy()
col.reset() col.reset()
assert col.sched.counts() == (2, 0, 0) assert col.sched.counts() == (2, 0, 0)
c.load() c.load()
assert c.queue == QUEUE_TYPE_NEW assert c.queue == QUEUE_TYPE_NEW
assert c.left % 1000 != 1 assert c.left % 1000 != 1
assert not col.undoName() assert not col.undo_status().undo
# we should be able to undo multiple answers too # we should be able to undo multiple answers too
c = col.sched.getCard() c = col.sched.getCard()
col.sched.answerCard(c, 3) col.sched.answerCard(c, 3)
@ -87,8 +87,8 @@ def test_review():
# performing a normal op will clear the review queue # performing a normal op will clear the review queue
c = col.sched.getCard() c = col.sched.getCard()
col.sched.answerCard(c, 3) col.sched.answerCard(c, 3)
assert col.undoName() == "Review" assert col.undo_status().undo == "Review"
col.save("foo") col.save("foo")
assert col.undoName() == "foo" assert col.undo_status().undo == "foo"
col.undo_legacy() col.undo_legacy()
assert not col.undoName() assert not col.undo_status().undo

View file

@ -521,7 +521,7 @@ class Browser(QMainWindow):
def createFilteredDeck(self) -> None: def createFilteredDeck(self) -> None:
search = self.current_search() search = self.current_search()
if self.mw.col.schedVer() != 1 and KeyboardModifiersPressed().alt: if self.mw.col.sched_ver() != 1 and KeyboardModifiersPressed().alt:
aqt.dialogs.open("FilteredDeckConfigDialog", self.mw, search_2=search) aqt.dialogs.open("FilteredDeckConfigDialog", self.mw, search_2=search)
else: else:
aqt.dialogs.open("FilteredDeckConfigDialog", self.mw, search=search) aqt.dialogs.open("FilteredDeckConfigDialog", self.mw, search=search)

View file

@ -65,7 +65,7 @@ class FindDuplicatesDialog(QDialog):
field = fields[form.fields.currentIndex()] field = fields[form.fields.currentIndex()]
QueryOp( QueryOp(
parent=self.browser, parent=self.browser,
op=lambda col: col.findDupes(field, search_text), op=lambda col: col.find_dupes(field, search_text),
success=self.show_duplicates_report, success=self.show_duplicates_report,
).run_in_background() ).run_in_background()

View file

@ -53,4 +53,4 @@ def check_db(mw: aqt.AnkiQt) -> None:
n += 1 n += 1
continue continue
mw.taskman.with_progress(mw.col.fixIntegrity, on_future_done) mw.taskman.with_progress(mw.col.fix_integrity, on_future_done)

View file

@ -349,7 +349,7 @@ class DeckBrowser:
###################################################################### ######################################################################
def _v1_upgrade_message(self) -> str: def _v1_upgrade_message(self) -> str:
if self.mw.col.schedVer() == 2: if self.mw.col.sched_ver() == 2:
return "" return ""
return f""" return f"""
@ -371,7 +371,7 @@ class DeckBrowser:
""" """
def _confirm_upgrade(self) -> None: def _confirm_upgrade(self) -> None:
self.mw.col.modSchema(check=True) self.mw.col.mod_schema(check=True)
self.mw.col.upgrade_to_v2_scheduler() self.mw.col.upgrade_to_v2_scheduler()
showInfo(tr.scheduling_update_done()) showInfo(tr.scheduling_update_done())

View file

@ -144,7 +144,7 @@ class DeckConf(QDialog):
showInfo(tr.scheduling_the_default_configuration_cant_be_removed(), self) showInfo(tr.scheduling_the_default_configuration_cant_be_removed(), self)
else: else:
gui_hooks.deck_conf_will_remove_config(self, self.deck, self.conf) gui_hooks.deck_conf_will_remove_config(self, self.deck, self.conf)
self.mw.col.modSchema(check=True) self.mw.col.mod_schema(check=True)
self.mw.col.decks.remove_config(self.conf["id"]) self.mw.col.decks.remove_config(self.conf["id"])
self.conf = None self.conf = None
self.deck["conf"] = 1 self.deck["conf"] = 1
@ -220,7 +220,7 @@ class DeckConf(QDialog):
f.revplim.setText(self.parentLimText("rev")) f.revplim.setText(self.parentLimText("rev"))
f.buryRev.setChecked(c.get("bury", True)) f.buryRev.setChecked(c.get("bury", True))
f.hardFactor.setValue(int(c.get("hardFactor", 1.2) * 100)) f.hardFactor.setValue(int(c.get("hardFactor", 1.2) * 100))
if self.mw.col.schedVer() == 1: if self.mw.col.sched_ver() == 1:
f.hardFactor.setVisible(False) f.hardFactor.setVisible(False)
f.hardFactorLabel.setVisible(False) f.hardFactorLabel.setVisible(False)
# lapse # lapse

View file

@ -106,7 +106,7 @@ def display_options_for_deck_id(deck_id: DeckId) -> None:
def display_options_for_deck(deck: DeckDict) -> None: def display_options_for_deck(deck: DeckDict) -> None:
if not deck["dyn"]: if not deck["dyn"]:
if KeyboardModifiersPressed().shift or aqt.mw.col.schedVer() == 1: if KeyboardModifiersPressed().shift or aqt.mw.col.sched_ver() == 1:
deck_legacy = aqt.mw.col.decks.get(DeckId(deck["id"])) deck_legacy = aqt.mw.col.decks.get(DeckId(deck["id"]))
aqt.deckconf.DeckConf(aqt.mw, deck_legacy) aqt.deckconf.DeckConf(aqt.mw, deck_legacy)
else: else:

View file

@ -95,7 +95,7 @@ class FilteredDeckConfigDialog(QDialog):
self.form.buttonBox.helpRequested, lambda: openHelp(HelpPage.FILTERED_DECK) self.form.buttonBox.helpRequested, lambda: openHelp(HelpPage.FILTERED_DECK)
) )
if self.col.schedVer() == 1: if self.col.sched_ver() == 1:
self.form.secondFilter.setVisible(False) self.form.secondFilter.setVisible(False)
restoreGeom(self, self.GEOMETRY_KEY) restoreGeom(self, self.GEOMETRY_KEY)
@ -127,7 +127,7 @@ class FilteredDeckConfigDialog(QDialog):
form.order.setCurrentIndex(term1.order) form.order.setCurrentIndex(term1.order)
form.limit.setValue(term1.limit) form.limit.setValue(term1.limit)
if self.col.schedVer() == 1: if self.col.sched_ver() == 1:
if config.delays: if config.delays:
form.steps.setText(self.listToUser(list(config.delays))) form.steps.setText(self.listToUser(list(config.delays)))
form.stepsOn.setChecked(True) form.stepsOn.setChecked(True)
@ -227,7 +227,7 @@ class FilteredDeckConfigDialog(QDialog):
"""Return a search node that matches learning cards if the old scheduler is enabled. """Return a search node that matches learning cards if the old scheduler is enabled.
If it's a rebuild, exclude cards from this filtered deck as those will be reset. If it's a rebuild, exclude cards from this filtered deck as those will be reset.
""" """
if self.col.schedVer() == 1: if self.col.sched_ver() == 1:
if self.deck.id: if self.deck.id:
return ( return (
self.col.group_searches( self.col.group_searches(
@ -252,7 +252,7 @@ class FilteredDeckConfigDialog(QDialog):
def _onReschedToggled(self, _state: int) -> None: def _onReschedToggled(self, _state: int) -> None:
self.form.previewDelayWidget.setVisible( self.form.previewDelayWidget.setVisible(
not self.form.resched.isChecked() and self.col.schedVer() > 1 not self.form.resched.isChecked() and self.col.sched_ver() > 1
) )
def _update_deck(self) -> bool: def _update_deck(self) -> bool:
@ -266,7 +266,7 @@ class FilteredDeckConfigDialog(QDialog):
config.reschedule = form.resched.isChecked() config.reschedule = form.resched.isChecked()
del config.delays[:] del config.delays[:]
if self.col.schedVer() == 1 and form.stepsOn.isChecked(): if self.col.sched_ver() == 1 and form.stepsOn.isChecked():
if (delays := self.userToList(form.steps)) is None: if (delays := self.userToList(form.steps)) is None:
return False return False
config.delays.extend(delays) config.delays.extend(delays)

View file

@ -499,7 +499,7 @@ def _replaceWithApkg(mw: aqt.AnkiQt, filename: str, backup: bool) -> None:
if not mw.loadCollection(): if not mw.loadCollection():
return return
if backup: if backup:
mw.col.modSchema(check=False) mw.col.mod_schema(check=False)
tooltip(tr.importing_importing_complete()) tooltip(tr.importing_importing_complete())

View file

@ -527,7 +527,7 @@ class AnkiQt(QMainWindow):
def _loadCollection(self) -> None: def _loadCollection(self) -> None:
cpath = self.pm.collectionPath() cpath = self.pm.collectionPath()
self.col = Collection(cpath, backend=self.backend, log=True) self.col = Collection(cpath, backend=self.backend)
self.setEnabled(True) self.setEnabled(True)
def reopen(self) -> None: def reopen(self) -> None:
@ -1358,7 +1358,7 @@ title="%s" %s>%s</button>""" % (
def confirm_schema_modification(self) -> bool: def confirm_schema_modification(self) -> bool:
"""If schema unmodified, ask user to confirm change. """If schema unmodified, ask user to confirm change.
True if confirmed or already modified.""" True if confirmed or already modified."""
if self.col.schemaChanged(): if self.col.schema_changed():
return True return True
return askUser(tr.qt_misc_the_requested_change_will_require_a()) return askUser(tr.qt_misc_the_requested_change_will_require_a())

View file

@ -143,7 +143,7 @@ class Overview:
def on_unbury(self) -> None: def on_unbury(self) -> None:
mode = UnburyDeck.Mode.ALL mode = UnburyDeck.Mode.ALL
if self.mw.col.schedVer() != 1: if self.mw.col.sched_ver() != 1:
info = self.mw.col.sched.congratulations_info() info = self.mw.col.sched.congratulations_info()
if info.have_sched_buried and info.have_user_buried: if info.have_sched_buried and info.have_user_buried:
opts = [ opts = [

View file

@ -203,7 +203,7 @@ for you than the default driver, please let us know on the Anki forums."""
self.form.autoSyncMedia.isChecked() and 15 or 0 self.form.autoSyncMedia.isChecked() and 15 or 0
) )
if self.form.fullSync.isChecked(): if self.form.fullSync.isChecked():
self.mw.col.modSchema(check=False) self.mw.col.mod_schema(check=False)
# Profile: backup # Profile: backup
###################################################################### ######################################################################

View file

@ -891,7 +891,7 @@ def supportText() -> str:
def schedVer() -> str: def schedVer() -> str:
try: try:
return str(mw.col.schedVer()) return str(mw.col.sched_ver())
except: except:
return "?" return "?"