Skip to content

Conversation

@rmunn
Copy link

@rmunn rmunn commented Jan 23, 2026

Modern Anki versions have moved to Python 3.13 and PyQt6, which require some changes to the plugin code to get it to work. Details are in each commit message; summary:

  • Plugin install location is now %APPDATA%\Anki2\addons21\(plugin name)
  • Python distutils module no longer available since Python 3.12
  • Top-level plugin folder no longer automatically in import path since Python 3.6
  • Anki plugins now required to include a manifest.json file
  • PyQt6 moved some constant names into different namespaces

This has been tested and works with the most recent version of Anki (25.09) and will probably work on versions as far back as Anki 2.1, as when I changed the Qt code I tried to ensure it would still work under Qt5, which older versions of Anki 2.1 used to include. I have not yet tested older versions of Anki, just the most recent release.

I have also updated the helper scripts for developers (CopyFilesToAddons and so on) to take into account Anki's new plugin storage location, and used those scripts in testing Anki so I know they work.

HOW TO TEST

Run the BuildZip.bat or BuildZip.sh script included in this PR, which should create a FlashGrab.zip file in the root of the Git repo. Open up Anki, go to the Tools -> Add-ons manager, and choose "Install from file". Install the FlashGrab.zip file and restart Anki. You should now be able to import a LIFT file (exported from FieldWorks) into Anki, which will create a deck of Anki flashcards called "lift-dictionary".

rmunn added 10 commits January 23, 2026 12:55
Python 3.6 switched how package-local imports work, so the top-level
__init__.py file of a package is no longer found in sys.path by default.
One solution would be to change all the imports throughout the package
to be relative imports using the `from . import pkg` syntax, but that
would involve a lot of changes. Instead, we simply re-add the top-level
directory to sys.path, and all the existing imports can be unchanged.

Python 3.10 deprecated the distutils package, and Python 3.12 removed it
from the standard library. There was only one function that FlashGrab
was actually using, the "newer" function, so we just reimplement it.
Also, instead of DistutilsFileError which is no longer available, all
errors in copying files will now throw OSError. Finally, the log
function from distutils has been replaced with simple print() calls,
which will not be visible to the user anyway since Anki runs without a
terminal console by default.
Older versions of Anki used Qt5, but more recent versions use Qt6.
Most things are unchanged between 5 and 6, but a few constants or
methods changed namespace. We try both locations, old and new, so
that the plugin will work on older and newer versions of Anki.
The manifest.json file requires at least two keys, the name of the addon
package (determines the folder name it will be stored under) and the
user-visible name of the addon. Since the name is now FlashGrab instead
of SyncXml, we will use FlashGrab as the name of the folder where Anki
should store the addon.
The manifest.json file needs to be copied into the addons folder as
well, plus the syncx.py file has now been replaced by __init__.py.
Docstrings were warning about \C not being a valid string escape.
Anki documentation specifically asks for __pycache__ folders not to be
included in plugins, and says that the plugin .zip will be rejected if
it contains any __pycache__ folders. I suspect they'll reject the .zip
even if the __pycache__ folders are empty, so let's make sure we don't
include any __pycache__ folders, even empty, in the install.
Anki 2.1 changed the location where plugins are stored (because it
introduced breaking changes to the plugin interface, so that old-style
plugins would no longer run). Since this plugin is now compatible with
Anki 2.1 and later, the CopyFilesToAddons scripts for developers should
copy it to the plugin's new location.
@rmunn
Copy link
Author

rmunn commented Jan 23, 2026

Note that this PR does not contain a fix for #17, because that can be dealt with once we have a working plugin again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant