Porting tips for Anki 23.10

Version checks

Add-ons that were manually deconstructing Anki’s version string and making assumptions about the number of components it has should instead switch to
Anki’s built-in tools for getting the version number.

For example:

from anki import buildinfo
if int(buildinfo.version.split(".")[2]) < 45: # < 2.1.45

…should become:

from anki.utils import pointVersion
if pointVersion() < 45: ...

For Anki 2.1.x releases, it will return the x.

For Anki 23.10+, it packs the version into 6 digits for YY-MM-patch, so if you wished to check if the user was on 23.10+, you’d do:

if pointVersion() >= 231000:

And if you wanted to check 23.10.2, it would be 231002.

pointVersion() has been around since 2.1.20, and will be kept around in the future despite being camel case. If you don’t need to support older Anki versions:

  • point_version() has been around since 2.1.49.
  • 23.10 introduces a clearer-named int_version() alias.

QueryOp is serialized

QueryOp()s are now serialized by default, as only one operation should be accessing the collection at once. If you use QueryOp() for non-collection tasks like network requests and want multiple ops to run in parallel, you can opt-out of this with .without_collection()

save(), checkpoint() and flush() are deprecated

Old Anki versions held a long-running transaction, and would commit it every
5 minutes or when col.checkpoint() was called, which offered a crude 1-step undo,
but meant you could lose a few minutes of work if Anki crashed. Anki 2.1.45 introduced a new undo system that supports multi-step undo/redo. As of 23.10,
.checkpoint() is now a no-op.

If your add-on calls .checkpoint() before some operation, you can remove the checkpoint call. As long as you make sure to perform changes in a CollectionOp and use undoable actions, you can take advantage of the undo/redo system.

Similarly, col.save() and col.autosave() no longer do anything, as changes are now saved as they’re made.

If you currently use card.flush() or note.flush(), please transition to col.update_note() / col.update_card() in a CollectionOp, so that the user can undo
the changes.

Qt5 compatibility

The compatibility code that has been in place for the last 2 years is now off by default, as it increases startup time and can lead to some confusing edge cases.

Imports

To support both Qt5 and Qt6, you will need to change lines like:

import sip
from PyQt5.QtWidgets import QDialog

…to the recommended approach:

from aqt.qt import sip, QDialog

Enumerations

Qt6 requires enums to be prefixed with their type name. For example:

        self.setWizardStyle(QWizard.ClassicStyle)

becomes

        self.setWizardStyle(QWizard.WizardStyle.ClassicStyle)

and

        f.setStyleHint(QFont.Monospace)

becomes

        f.setStyleHint(QFont.StyleHint.Monospace)

You can find the various enum names in the docs.

Temporary workarounds

Users with add-ons that the add-on author has not updated yet can either switch to the Qt5 version of Anki, or temporarily set an environment variable ENABLE_QT5_COMPAT to 1 to have Anki install the previous compatibility code. In an early 2024 update, ENABLE_QT5_COMPAT will be removed, so this is not a long term solution.

Removal of the v1/v2 schedulers

Code that imports from anki.sched will break, as the old schedulers have been removed. Typically you don’t need to do this unless you’re monkey patching the scheduler, which won’t work with v3. If you just need to call some scheduler method, you can do that via col.sched.foo()

Check the console

Make sure you monitor the console for deprecation warnings:

https://addon-docs.ankiweb.net/console-output.html

Take advantage of mypy

A reminder that Anki’s codebase is fully typed. By investing some time setting up mypy, you can catch many of these changes at build time.

12 Likes

Thank you for the help, Damien.

Is there an option for when I don’t want the user to be able to undo the action? Context: I am updating a bunch of notes in bulk and it wouldn’t make sense for the user to undo the last few of these changes. In fact it would be good if this wasn’t even possible for him, just in case he presses Ctrl+Z afterwards by mistake.

Edit: Seems like CollectionOp was the magic word to look for: Background Operations - Writing Anki Add-ons And I think this is what I need:

self._backend.update_notes(
    notes=[note], skip_undo_entry=True
)
1 Like

col.merge_undo_entries() might help, but the cumulative operation would still be user-undoable.

:+1: I think it would be nice to have more control over whether backend operations add an undo entry or not. We already have corresponding flags in some places, but not all, and a general solution would probably be the right approach.

To illustrate another use case: Adding a notetype via mw.col.models.add() currently necessarily creates an undo entry. If this is not triggered by a user action (e.g. an add-on registering its notetype at Anki launch), then as a user I would be quite confused if undoing one review too many would suddenly get rid of the note type and potentially cause the add-on to throw an error the next time it tries to use it (if it isn’t aware of and doesn’t handle this edge case).

1 Like

skip_undo_entry is not publicly exposed at the moment, so please wait until I update update_card/note to add it as an argument in the next rc. merge_undo_entries() shouldn’t be required if you have all the notes in advance, as update_notes() can update multiple notes at the same time.

For startup actions, you can do any raw SQL change like ‘update col set mod=mod’ to clear any undo entries.

1 Like

At the moment I’m doing this in order to be compatible with Anki 2.0 - 23.10:

def update_single_note_without_undo(note):
  try: #Preferred by Anki 23.10+
    mw.col._backend.update_notes(
      notes = [note._to_backend_note()],
      skip_undo_entry = True
    )
  except:
    note.flush()

Seems to work fine for my purposes. Should I change it?

Anything in Anki that starts with a _ is private and may change/break at any time. The next rc will expose skip_undo_entry in mw.col.update_notes().

1 Like

Perhaps the information here could be added to https://addon-docs.ankiweb.net/ ? Or at least a link to this page.

They’re linked from the change notes of the new release - presumably people are more likely to look there than the add-on writing guide to learn about changes?

It’s just that https://addon-docs.ankiweb.net/ already has a section on Porting 2.0 Add-ons (to 2.1). So it would be in keeping with that to also have a section on Porting 2.1 Add-ons to 23.10+. Perhaps redundant, but enhances discoverability.

Ok, I’ve added a link.

1 Like