Core API

One-call functions over the pure-python engine

Each call opens the default collection, does its work, and closes up, so there’s no session state to manage. fb variants take plain front/back strings and scalar ids.

import tempfile
from fastcore.test import *
os.environ['FASTANKI_DIR'] = tempfile.mkdtemp()

This notebook runs against a throwaway data folder; without that override, everything below uses ~/.fastanki.

Cards and notes


source

add_cloze_card

def add_cloze_card(
    text:str, # Cloze text with `{c1::hidden}` deletions
    back_extra:str='', # Extra info shown on the back of every card
    deck:str='Default', # Deck name (`::` for nesting; created if needed)
    tags:str=None, # Space-separated tags
):

Add a Cloze card ({c1::hidden} syntax), returning the new note id.


source

add_fb_card

def add_fb_card(
    front:str, # Front (question) text
    back:str, # Back (answer) text
    deck:str='Default', # Deck name (`::` for nesting; created if needed)
    tags:str=None, # Space-separated tags
):

Add a Basic card, returning the new note id.


source

add_card

def add_card(
    model:str='Basic', # Notetype name (the tool description lists each notetype's fields)
    deck:str='Default', # Deck name (`::` for nesting; created if needed)
    tags:str=None, # Space-separated tags
    fields:dict=None, # Field name -> value, e.g. {'Front':'2+2', 'Back':'4'}
):

Add a card of any notetype, returning the new note id. Field names must match the notetype.

nid = add_fb_card('What is the capital of France?', 'Paris', tags='geo')
note = add_card(deck='Spanish::Vocab', tags='spanish', fields={'Front':'hola','Back':'hello'})
note
1784759309644
czid = add_cloze_card('Minus times {{c1::minus}} is {{c2::plus}}', tags='maths')
test_eq(type(czid), int)

To put an image or sound on a card, first add_media the file, then cite the returned name in a field with <img src="name"> or [sound:name]. The name can differ from what you passed (see fastanki.media for the rules), so always use the returned one:


source

add_media

def add_media(
    path:str, # File to copy into the collection's media folder
    fname:str=None, # Name to store it under (default: the file's own name)
):

Add a media file to the collection, returning the filename to cite in fields: or [sound:name].

p = Path(tempfile.mkdtemp())/'chart.png'
p.write_bytes(b'png bytes here')
name = add_media(p)
test_eq(name, 'chart.png')
imgid = add_fb_card(f'What trend does this show? <img src="{name}">', 'up and to the right')
test_eq((Path(os.environ['FASTANKI_DIR'])/'collection.media'/name).read_bytes(), b'png bytes here')

Finding, updating, removing


source

find_note_ids

def find_note_ids(
    deck:str=None, # Deck name (matches subdecks too)
    tag:str=None, # Tag to match
    added_days:int=None, # Only notes added in the last this-many days
    fields:dict=None, # Field name -> case-insensitive substring, e.g. {'Front':'hello'}
):

Ids of notes matching all given criteria.


source

find_notes

def find_notes(
    deck:str=None, # Deck name (matches subdecks too)
    tag:str=None, # Tag to match
    added_days:int=None, # Only notes added in the last this-many days
    fields:dict=None, # Field name -> case-insensitive substring, e.g. {'Front':'hello'}
):

Notes matching all given criteria.


source

find_card_ids

def find_card_ids(
    deck:str=None, # Deck name (matches subdecks too)
    tag:str=None, # Tag to match
    added_days:int=None, # Only cards added in the last this-many days
    is_due:bool=None, # Only cards due for review
    fields:dict=None, # Field name -> case-insensitive substring, e.g. {'Front':'hello'}
):

Ids of cards matching all given criteria.


source

find_cards

def find_cards(
    deck:str=None, # Deck name (matches subdecks too)
    tag:str=None, # Tag to match
    added_days:int=None, # Only cards added in the last this-many days
    is_due:bool=None, # Only cards due for review
    fields:dict=None, # Field name -> case-insensitive substring, e.g. {'Front':'hello'}
):

Cards matching all given criteria.


source

get_note

def get_note(
    note_id:int, # Id of the note to retrieve
):

Retrieve a note by id.

test_eq(find_note_ids(tag='geo'), [nid])
test_eq(find_note_ids(deck='Spanish'), [note])
test_eq(find_note_ids(fields={'Front':'capital'}), [nid])
test_eq(len(find_cards()), 5)
get_note(nid)

Front: What is the capital of France? | Back: Paris | 🏷 geo


source

del_note

def del_note(
    notes:list, # Note ids (or `Note` objects) to delete, along with their cards
):

Delete note(s) (and their cards) by Note or id, singly or in a list.


source

update_fb_note

def update_fb_note(
    note_id:int, # Id of the Basic note to update
    front:str='', # New Front text (empty leaves it unchanged)
    back:str='', # New Back text (empty leaves it unchanged)
    tags:str=None, # Space-separated tags, replacing all existing tags
    add_tags:str=None, # Space-separated tags to add, keeping existing ones
):

Update a Basic note’s front/back and/or tags.


source

update_note

def update_note(
    note, tags:NoneType=None, add_tags:NoneType=None, **fields
):

Update fields and/or tags of a Note or note id; tags replaces, add_tags appends.

n2 = update_note(note, Back='hello!', add_tags='greeting')
test_eq(n2['Back'], 'hello!')
test_eq(n2.tags, ['spanish','greeting'])
test_eq(update_fb_note(nid, back='Paris, France').fields['Back'], 'Paris, France')
test_eq(del_note([nid, czid]), 2)
test_eq(find_note_ids(tag='geo'), [])

Syncing


source

sync

def sync(
    user:str=None, # AnkiWeb email (only needed the first time)
    passw:str=None, # AnkiWeb password (only the first time; a host key is saved after)
    endpoint:str=None, # Sync server URL (defaults to AnkiWeb)
    upload:bool=False, # Force-upload the local collection, replacing the server copy
    media:bool=True, # Also sync media files
):

Sync the default collection with AnkiWeb. Pass credentials the first time; they’re saved after that.

The first sync of a fresh collection is a full one: by default that’s a download (the server copy wins), and replacing a non-empty server copy with a fresh empty collection is refused unless the server side is empty too. Pass upload=True deliberately to push your local copy wholesale.

sync(user=os.environ['ANKI_USER'], passw=os.environ['ANKI_PASS'])  # first time
sync()  # after that

Tool use


source

anki_tools

def anki_tools():

Call self as a function.

anki_tools()
&`[add_card, add_fb_card, add_cloze_card, add_media, find_notes, find_note_ids, find_cards, find_card_ids, get_note, del_note, update_fb_note, sync]`

add_card handles any notetype via a fields dict, and rejects unknown field names with a clear error:

czid = add_card(model='Cloze', fields={'Text':'{{c1::pi}} ~ 3.14'})
test_eq(get_note(czid)['Text'], '{{c1::pi}} ~ 3.14')
test_fail(lambda: add_card(fields={'Nope':'x'}), contains='Nope')   # unknown field -> clear error