This post was split from Bundling Numpy in an add-on to summarize and discuss best practices.
Problem
Many add-ons rely on non-standard Python packages to work, but only standard library packages and third party packages used by Anki itself are available out of the box.
Solution
Anki doesn’t offer a ready solution for this. The solution recommended in the add-ons guide is to package required modules with the add-on in a subfolder and adjust sys.path to make them available for import.
Steps
pip install -r requirements.txt -t src/vendorto download packages in your build script. A lot of popular add-ons simply include third party packages directly in their git repos, but this is not recommended.- In your add-on’s
__init__.pybefore importing any vendored package, make sure to modifysys.path:
sys.path.append(os.path.join(os.path.dirname(__file__), "vendor"))
Problems with the recommended solution
- As noted in the add-on docs, modules that rely on C extensions such as numpy are more complicated to package, as you’ll need to bundle different C modules for each platform and Python version supported.
- If two add-ons bundle the same package, the package imported first will win and override the other one. This can result in errors if the versions are incompatible.
Alternative solution
@abdo is working on a script that solves both problems:
- Packages that rely on C extensions are detected and handled by bundling required all extension files for the given platforms and Anki versions in the package’s installation folder.
- The package version conflict issue is solved by rewriting absolute imports in vendored packages so that the sys.path workaround is not needed.
Problems with the alternative solution
- While import rewriting appears to be working almost perfectly, some unit tests would be nice, as the logic was mostly generated by AI.
- The vendor.py script might need some adjustments to make it easier for other add-on developers to use, as it makes assumptions about the add-on’s structure.
- Some packages might make runtime assumptions about the way they are imported. We’re only aware of a minor issue in
structlogthat has a simple workaround. - A weird mypy error appeared with import rewriting in the
pydanticpackage. We’ve not got to the bottom of it yet, but it can be silenced with this in mypy.ini:
[mypy-src.vendor.pydantic.types]
follow_imports = skip
Open problems
- On Windows, packages with C extension files notoriously cause permission errors when the add-on is update/deleted. Possible solutions include:
- Storing vendored packages somewhere outside of the add-on’s folder. This is probably incompatible with import rewriting so it trades off one problem for the other.
- Deferring the update/deletion process to the next Anki startup before add-ons are loaded. Possible implementations include:
- Patching the relevant
aqt.addonsmethods to defer the process. This requires bundling an executable to restart Anki, update/delete the add-on and launch Anki again. We have a working demo of that: see updates.py and restart_anki.py. - Making Anki take care of the update/deletion process in this case with minimum add-on work, maybe via an add-on manifest key,
requires_restart, that makes Anki prompt the user to restart then completes the process at next startup.
- Patching the relevant