fastanki reads and writes Anki’s collection format and speaks the AnkiWeb sync protocol directly, in Python. There’s no dependency on the Anki application or its Rust library: your cards live in a small sqlite file of fastanki’s own, and reach your desktop and phone the same way any Anki client’s changes do, by syncing through AnkiWeb. Media files aren’t synced yet, so create text-only cards.
Install latest from pypi
$ pip install fastankifrom fastanki import *
import os, tempfileadd_card lets you create a new card with a single function call. Just pass your field values as keyword arguments. By default it uses the Basic note type and Default deck, but you can specify any model, deck, or tags you like.
notezh = add_card(fields={'Front':'你好','Back':'hello'})find_cards searches your collection and returns a list of Card objects. Criteria are keyword arguments, all optional and combined with AND:
deck='Spanish'matches that deck and its subdeckstag='vocab'matches a tagadded_days=7matches cards added in the last weekis_due=Truematches cards due for review- Any other keyword is a field name, matched as a case-insensitive substring:
Front='hello' where="n.id=?", args=(nid,)drops through to SQL overnotes njoined withcards c
cards = find_cards(deck='Default')
cards[Card(1764738198390, nid=1764738198390, due=49, ivl=0, queue=0)]
cards[0]Card 1764738198390 (nid: 1764738198390, due: 49, ivl: 0d, queue: 0)
find_card_ids(deck='Default')[1764738198390]
find_notes takes the same criteria and returns one Note per matching note, where find_cards may return several cards for a note (a Cloze note generates one card per cloze number, for instance).
notes = find_notes(fields={'Back':'hello'})
notes[Note(1764738198390, Front='你好', Back='hello', tags=[])]
note = notes[0]
noteFront: 你好 | Back: hello
find_note_ids(fields={'Back':'hello'})[1764738198390]
update_note modifies an existing note’s fields and/or tags. Pass either a Note object or a note ID, along with any fields you want to change as keyword arguments. For tags:
tags=['a','b']— replaces all tagsadd_tags='newtag'— adds without removing existing tags
update_note(note, Back="updated answer", tags='testtag')Front: 你好 | Back: updated answer | 🏷️ testtag
update_note(note, add_tags='moretagz')Front: 你好 | Back: updated answer | 🏷️ testtag, moretagz
get_note(note.id)Front: 你好 | Back: updated answer | 🏷️ moretagz, testtag
del_note([notezh, note])✓ 1 change(s)
sync connects to AnkiWeb: pass your credentials the first time, and they’re saved (as a host key, not your password) for later calls. The first sync of a fresh collection is a full download of your existing AnkiWeb collection; after that, syncs exchange deltas in both directions. fastanki will never replace a non-empty server collection without an explicit upload=True.
sync(user=os.environ['ANKI_USER'], passw=os.environ['ANKI_PASS']) # first time
sync() # after thathost_number: 5
anki_tools()&`[add_fb_card, find_notes, find_note_ids, find_cards, find_card_ids, get_note, del_card, update_fb_note, sync]`
Here are the available tools:
&[add_card, add_fb_card, add_cloze_card, find_notes, find_note_ids, find_cards, find_card_ids, get_note, del_note, update_fb_note, sync].
Try to find all my notes. List the IDs and contents you see.
Delete them.
Try finding all notes again.
Try adding a note of your choice using add_fb_card and tell me the id.
Try finding all notes again.
OK try get_note with it.
Delete it now.
OK create, update, and verify a note now.
Try the various find ones that we haven’t done yet.
Sure. Delete that note, then sync.
The functional API opens and closes the collection on every call. For a batch of work, Collection keeps it open, and everything the functions above do is a method here.
col = Collection.open()
col.path.namecol.notetypes(), col.decks()n = col.add(Front='adiós', Back='goodbye', deck='Spanish::Vocab', tags=['spanish'])
ncol.due_counts('Spanish')col.find_notes(deck='Spanish')col.remove_deck('Spanish')
col.close()Collection is also a context manager, so a one-shot batch reads naturally:
with Collection.open() as c: c.add(Front='hola', Back='hello')