Creating and editing *.apkg and *.anki2 safely


Keywords
anki, srs, memorization, spaced-repetition, anki-flashcards
License
MIT
Install
pip install ankisync2==0.2.4.1

Documentation

AnkiSync 2

PyPI version shields.io PyPI license

*.apkg and *.anki2 file structure is very simple, but with some quirks of incompleteness.

*.apkg file structure is a zip of at least two files.

.
├── example
│   ├── example.anki2
│   ├── media
│   ├── 1  # Media files with the names masked as integers
│   ├── 2
│   ├── 3
|   └── ...
└── example.apkg

*.anki2 is a SQLite file with foreign key disabled, and the usage of some JSON schemas instead of some tables

Also, *.anki2 is used internally at os.path.join(appdirs.user_data_dir('Anki2'), 'User 1', 'collection.anki2'), so editing the SQLite there will also edit the database.

The media file is a text file of at least a string of {}, which is actually a dictionary of keys -- stringified int; and values -- filenames.

Usage

Some extra tables are created if not exists.

from ankisync2.anki import Anki2, Apkg
apkg = Apkg("example.apkg")  # Or Apkg("example/") also works, otherwise the folder named 'example' will be created.
apkg.db.execute_sql(SQL, PARAMS)
apkg.zip(output="example1.apkg")

I also support adding media.

apkg.add_media("path/to/media.jpg")

To find the wanted cards and media, iterate though the Apkg and Apkg.iter_media object.

iter_apkg = iter(apkg)
for i in range(5):
    print(next(iter_apkg))

Creating a new *.apkg

You can create a new *.apkg via Apkg with any custom filename (and *.anki2 via Anki2()). A folder required to create *.apkg needs to be created first.

from ankisync2.anki import Apkg
apkg = Apkg("example")  # Create example folder

After that, the Apkg will require at least 1 card, which is connected to at least 1 note, 1 model, 1 template, and 1 deck; which should be created in this order.

  1. Model, Deck
  2. Template, Note
  3. Card
from ankisync2 import db
m = db.Models.create(name="foo", flds=["field1", "field2"])
d = db.Decks.create(name="bar::baz")
t = [
    db.Templates.create(name="fwd", mid=m.id, qfmt="{{field1}}", afmt="{{field2}}"),
    db.Templates.create(name="bwd", mid=m.id, qfmt="{{field2}}", afmt="{{field1}}")   
]
n = db.Notes.create(mid=m.id, flds=["data1", "<img src='media.jpg'>"], tags=["tag1", "tag2"])
c = [
    db.Cards.create(nid=n.id, did=d.id, ord=i)
    for i, _ in enumerate(t)
]

You can also add media, which is not related to the SQLite database.

apkg.add_media("path/to/media.jpg")

Finally, finalize with

apkg.zip(output="example1.apkg")
apkg.close()

Updating an *.apkg

This is also possible, by modifying db.Notes.data as sqlite_ext.JSONField, with peewee.signals.

It is now as simple as,

from ankisync2.anki import Apkg
from ankisync2 import db

apkg = Apkg("example1.apkg")

for n in db.Notes.filter(db.Notes.data["field1"] == "data1"):
    n.data["field3"] = "data2"
    n.save()

apkg.close()

JSON schema of Col.models, Col.decks, Col.conf and Col.dconf

I have created dataclasses for this at /ankisync2/builder.py. To serialize it, use dataclasses.asdict or

from ankisync2.util import DataclassJSONEncoder
import json
json.dumps(dataclassObject, cls=DataclassJSONEncoder)

For an example of how this works, please see /ankisync2/anki.py#L56

Using peewee framework

You can also use peewee ORM framework; and ArrayField, X1fField and JSONField will be automated. You can use Dataclasses and Lists directly, without converting them to string first.

Examples

Please see /example.ipynb.

Installation

pip install ankisync2

Related projects