### Install mypy and aqt[qt6] with pip
Source: https://github.com/ankitects/addon-docs/blob/main/src/editor-setup.md
Run these commands in the PyCharm Python Console to install mypy for type checking and aqt with Qt6 support, which are essential for Anki add-on development and type completion.
```python
import subprocess
subprocess.check_call(["pip3", "install", "--upgrade", "pip"])
subprocess.check_call(["pip3", "install", "mypy", "aqt[qt6]"])
```
--------------------------------
### Install Anki Module
Source: https://github.com/ankitects/addon-docs/blob/main/src/command-line-use.md
Install the Anki Python module using pip. This is the recommended way to interact with Anki collections programmatically.
```shell
$ pip install anki
```
--------------------------------
### Set up Virtual Environment for Monkeytype
Source: https://github.com/ankitects/addon-docs/blob/main/src/editing-and-mypy.md
These commands set up a Python virtual environment, install aqt and monkeytype, and prepare to run monkeytype against the Anki binary.
```shell
% /usr/local/bin/python3.8 -m venv pyenv
% cd pyenv && . bin/activate
(pyenv) % pip install aqt monkeytype
(pyenv) % monkeytype run bin/anki
```
--------------------------------
### Install Anki and Mypy for Type Completion
Source: https://github.com/ankitects/addon-docs/blob/main/src/editing-and-mypy.md
Run these commands in the PyCharm Python Console to install necessary packages for Anki type completion and mypy. Ensure you are using a 64-bit Python version 3.8 or 3.9.
```python
import subprocess
subprocess.check_call(["pip3", "install", "--upgrade", "pip"])
subprocess.check_call(["pip3", "install", "mypy", "aqt"])
```
--------------------------------
### Anki Console Startup Script for Windows
Source: https://github.com/ankitects/addon-docs/blob/main/src/console-output.md
On Windows, start Anki using the `anki-console.bat` script to display a separate console window. This helps in viewing stdout for debugging and warnings.
```batch
anki-console.bat
```
--------------------------------
### Setting up Monkeytype for Add-on Type Hinting
Source: https://github.com/ankitects/addon-docs/blob/main/src/mypy.md
This sequence of shell commands demonstrates how to set up a virtual environment, install necessary packages (aqt, monkeytype), and run monkeytype to gather runtime type information for an add-on.
```shell
% /usr/local/bin/python3.8 -m venv pyenv
% cd pyenv && . bin/activate
(pyenv) % pip install aqt monkeytype
(pyenv) % monkeytype run bin/anki
```
```shell
(pyenv) % PYTHONPATH=~/Library/Application\ Support/Anki2/addons21 monkeytype apply test
```
--------------------------------
### Database Operations
Source: https://context7.com/ankitects/addon-docs/llms.txt
Examples of how to interact with Anki's database using the `mw.col.db` object. This includes fetching data with `list()` and `all()`, iterating with `execute()`, and performing updates with `execute()` and `executemany()`.
```APIDOC
## Database Operations
### Description
Interact with Anki's database using various methods provided by `mw.col.db`.
### Methods
- **`list(sql)`**: Executes an SQL query and returns the first column of each row as a list.
- **`all(sql)`**: Executes an SQL query and returns all rows as a list of lists.
- **`execute(sql, *params)`**: Executes an SQL statement. Can be used for fetching rows (iterating over results) or for updates/deletes.
- **`executemany(sql, data)`**: Executes an SQL statement multiple times with different data, suitable for bulk operations.
### Examples
#### Fetching Data
```python
# Fetch first column of specific rows
ids = mw.col.db.list("select id from cards limit 3")
# Fetch all columns for specific rows as lists
ids_and_ivl = mw.col.db.all("select id, ivl from cards")
```
#### Iterating and Updating
```python
# Iterate over results from execute()
for id, ivl in mw.col.db.execute("select id, ivl from cards limit 3"):
showInfo(f"card id {id} has ivl {ivl}")
# Update a single record using execute() with named arguments
newIvl = 10
cardId = 123
mw.col.db.execute("update cards set ivl = ? where id = ?", newIvl, cardId)
# Bulk update using executemany()
data = [[11, 456], [12, 789]]
mw.col.db.executemany("update cards set ivl = ? where id = ?", data)
```
```
--------------------------------
### Get All Rows from Anki DB
Source: https://github.com/ankitects/addon-docs/blob/main/src/the-anki-module.md
Use all() to retrieve all rows from a query, where each row is represented as a list. Suitable for fetching multiple columns.
```python
ids_and_ivl = mw.col.db.all("select id, ivl from cards")
```
--------------------------------
### Get Card Question and Answer
Source: https://github.com/ankitects/addon-docs/blob/main/src/the-anki-module.md
Retrieves the question and answer for each card ID found. This requires fetching the card object for each ID.
```python
for id in ids:
card = mw.col.get_card(id)
question = card.question()
answer = card.answer()
```
--------------------------------
### Trigger Code Completion in PyCharm
Source: https://github.com/ankitects/addon-docs/blob/main/src/editing-and-mypy.md
After installing Anki's source code, type this to test code completion. If a spinner appears, wait for it to finish before typing.
```python
from anki import hooks
hooks.
```
--------------------------------
### Anki Database Operations
Source: https://context7.com/ankitects/addon-docs/llms.txt
Demonstrates various methods for querying and updating the Anki database using mw.col.db. Examples include fetching single columns, multiple columns, iterating over results, and performing updates with placeholders.
```python
ids = mw.col.db.list("select id from cards limit 3")
```
```python
ids_and_ivl = mw.col.db.all("select id, ivl from cards")
```
```python
for id, ivl in mw.col.db.execute("select id, ivl from cards limit 3"):
showInfo("card id %d has ivl %d" % (id, ivl))
```
```python
mw.col.db.execute("update cards set ivl = ? where id = ?", newIvl, cardId)
```
```python
data = [[newIvl1, cardId1], [newIvl2, cardId2]]
mw.col.db.executemany("update cards set ivl = ? where id = ?", data)
```
--------------------------------
### Anatomy of a Hook Call in Anki Source
Source: https://github.com/ankitects/addon-docs/blob/main/src/hooks-and-filters.md
This is an example of how a hook is defined within Anki's source code, indicating where add-on functions will be executed.
```python
gui_hooks.reviewer_did_show_question(card)
```
--------------------------------
### Get a Due Card
Source: https://github.com/ankitects/addon-docs/blob/main/src/the-anki-module.md
Retrieves the next due card from the current deck. Use when the current deck is finished.
```python
card = mw.col.sched.getCard()
if not card:
# current deck is finished
```
--------------------------------
### Legacy Hooks API
Source: https://context7.com/ankitects/addon-docs/llms.txt
Utilize the older hook system for compatibility with pre-2.1.20 add-ons. Use `addHook` to register callbacks and `runHook`/`runFilter` to trigger them. The `onLeech` example shows modifying card data, while `onFocusLost` demonstrates filtering and modifying note fields.
```python
from anki.hooks import addHook
from aqt import mw
def onLeech(card):
"""Move leech cards to a 'Difficult' deck"""
card.did = mw.col.decks.id("Difficult")
card.odid = 0
if card.odue:
card.due = card.odue
card.odue = 0
addHook("leech", onLeech)
```
```python
# Filter example - auto-generate field content
def onFocusLost(flag, note, fieldIndex):
# Only process Japanese note types
if "japanese" not in note.model()['name'].lower():
return flag
# Check if source field and destination exists
if note["Reading"]:
return flag # Already filled
# Generate reading and mark as changed
note["Reading"] = generate_reading(note["Expression"])
return True
addHook('editFocusLost', onFocusLost)
```
--------------------------------
### Example of Anki API Deprecation Warning
Source: https://github.com/ankitects/addon-docs/blob/main/src/console-output.md
Anki uses stdout to display warnings about deprecated API usage. Address these warnings promptly to maintain add-on compatibility and performance.
```text
addons21/mytest/__init__.py:10:getNote is deprecated: please use 'get_note'
```
--------------------------------
### Direct Database Access for Card Count
Source: https://context7.com/ankitects/addon-docs/llms.txt
This example shows how to directly query the Anki SQLite database to retrieve the total number of cards. Use this for advanced operations, but note that direct writes may not sync automatically.
```python
from aqt import mw
from aqt.utils import showInfo
# scalar() returns a single item
count = mw.col.db.scalar("select count() from cards")
showInfo("card count: %d" % count)
```
--------------------------------
### Perform Actions on Card Display with Javascript Hooks
Source: https://github.com/ankitects/addon-docs/blob/main/src/reviewer-javascript.md
Utilize `onUpdateHook` to execute Javascript after the new card is in the DOM but before it's shown, or `onShownHook` after the fade-in. This example scrolls the window to a specific position.
```python
from aqt import gui_hooks
def prepare(html, card, context):
return html + """
"""
gui_hooks.card_will_show.append(prepare)
```
--------------------------------
### Get List of Values from Anki DB
Source: https://github.com/ankitects/addon-docs/blob/main/src/the-anki-module.md
Use list() to retrieve a list of values from the first column of query results. Useful for fetching IDs or other single-column data.
```python
ids = mw.col.db.list("select id from cards limit 3")
```
--------------------------------
### Update UI from Background Operation
Source: https://github.com/ankitects/addon-docs/blob/main/src/background-ops.md
To update the UI during a background operation, use `aqt.mw.taskman.run_on_main()` to schedule the UI update on the main thread. This example shows updating a progress indicator.
```python
import time
# Assuming aqt.mw, last_progress, remaining, total are defined elsewhere
if time.time() - last_progress >= 0.1:
aqt.mw.taskman.run_on_main(
lambda: aqt.mw.progress.update(
label=f"Remaining: {remaining}",
value=total - remaining,
max=total,
)
)
last_progress = time.time()
```
--------------------------------
### Get Add-on Configuration in Anki 2.1
Source: https://github.com/ankitects/addon-docs/blob/main/src/porting2.0.md
This Python code retrieves add-on configuration using `mw.addonManager.getConfig(__name__)`. It provides a fallback for older Anki versions or environments where the config manager might not be available.
```python
if getattr(getattr(mw, "addonManager", None), "getConfig", None):
config = mw.addonManager.getConfig(__name__)
else:
config = dict(optionA=123, optionB=456)
```
--------------------------------
### Add Editor Buttons with Hooks
Source: https://github.com/ankitects/addon-docs/blob/main/src/hooks-and-filters.md
Use the 'setupEditorButtons' hook to add custom buttons to the Anki editor. This example shows how to add a 'strike' button that wraps selected text in tags using JavaScript evaluation.
```python
from aqt.utils import showInfo
from anki.hooks import addHook
# cross out the currently selected text
def onStrike(editor):
editor.web.eval("wrap('', '');")
def addMyButton(buttons, editor):
editor._links['strike'] = onStrike
return buttons + [editor._addButton(
"iconname", # "/full/path/to/icon.png",
"strike", # link name
"tooltip")]
addHook("setupEditorButtons", addMyButton)
```
--------------------------------
### Perform Undoable Collection Operation
Source: https://github.com/ankitects/addon-docs/blob/main/src/background-ops.md
Use `CollectionOp` for undoable operations that modify the Anki collection. This example demonstrates removing notes using a predefined `CollectionOp` from `aqt.operations.note`.
```python
from aqt.operations.note import remove_notes
from aqt import mw
def my_ui_action(note_ids: list[int]) -> None:
remove_notes(parent=mw, note_ids=note_ids).run_in_background()
```
--------------------------------
### Legacy Hook: Handling Leech Discoveries
Source: https://github.com/ankitects/addon-docs/blob/main/src/hooks-and-filters.md
Use addHook to register a callback function that executes when a specific event, like a card becoming a leech, occurs. This example demonstrates moving a leech card to a 'Difficult' deck and handling potential cram deck scenarios.
```python
from anki.hooks import addHook
from aqt import mw
def onLeech(card):
# can modify without .flush(), as scheduler will do it for us
card.did = mw.col.decks.id("Difficult")
# if the card was in a cram deck, we have to put back the original due
# time and original deck
card.odid = 0
if card.odue:
card.due = card.odue
card.odue = 0
addHook("leech", onLeech)
```
--------------------------------
### Example of Incorrect Hook Usage Caught by Mypy
Source: https://github.com/ankitects/addon-docs/blob/main/src/editing-and-mypy.md
This code demonstrates an incorrect way to append a function to a hook, which mypy will flag. The error message indicates the expected function signature.
```python
from aqt import gui_hooks
def myfunc() -> None:
print("myfunc")
gui_hooks.reviewer_did_show_answer.append(myfunc)
```
--------------------------------
### Editor Buttons Hook
Source: https://context7.com/ankitects/addon-docs/llms.txt
Add custom buttons to the Anki editor using the `setupEditorButtons` hook. This example shows how to add a 'Strikethrough' button that wraps selected text in `` tags.
```python
from aqt.utils import showInfo
from anki.hooks import addHook
def onStrike(editor):
"""Cross out the currently selected text"""
editor.web.eval("wrap('', '');")
def addMyButton(buttons, editor):
editor._links['strike'] = onStrike
return buttons + [editor._addButton(
"iconname", # or "/full/path/to/icon.png"
"strike", # link name
"Strikethrough")] # tooltip
addHook("setupEditorButtons", addMyButton)
```
--------------------------------
### Create .ankiaddon Package (Unix)
Source: https://github.com/ankitects/addon-docs/blob/main/src/sharing.md
Use this command on Unix-based systems to create a properly formatted .ankiaddon zip file from your add-on's directory. Ensure you are in the add-on's root directory before executing.
```bash
cd myaddon && zip -r ../myaddon.ankiaddon *
```
--------------------------------
### Create Custom Dialog with PyQt
Source: https://context7.com/ankitects/addon-docs/llms.txt
Build custom dialogs and widgets using PyQt. Import from `aqt.qt` for cross-version compatibility. Ensure a reference to the dialog is kept to prevent garbage collection.
```python
from aqt import mw
from aqt.qt import *
def show_custom_dialog():
# Create dialog
dialog = QDialog(mw)
dialog.setWindowTitle("My Add-on Settings")
# Layout
layout = QVBoxLayout()
# Add widgets
label = QLabel("Enter value:")
layout.addWidget(label)
line_edit = QLineEdit()
line_edit.setPlaceholderText("Type here...")
layout.addWidget(line_edit)
# Buttons
button_box = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok |
QDialogButtonBox.StandardButton.Cancel
)
button_box.accepted.connect(dialog.accept)
button_box.rejected.connect(dialog.reject)
layout.addWidget(button_box)
dialog.setLayout(layout)
# IMPORTANT: Keep reference to prevent garbage collection
mw.myDialog = dialog
if dialog.exec():
return line_edit.text()
return None
# Add to menu
action = QAction("My Settings...", mw)
qconnect(action.triggered, show_custom_dialog)
mw.form.menuTools.addAction(action)
```
--------------------------------
### Anki Add-on Packaging Structure
Source: https://context7.com/ankitects/addon-docs/llms.txt
Organize your add-on files for packaging into a `.ankiaddon` file. The `user_files/` directory preserves contents on upgrade. `manifest.json` is required for distribution outside AnkiWeb.
```plaintext
# Folder structure:
# addons21/myaddon/__init__.py
# addons21/myaddon/config.json
# addons21/myaddon/config.md
# addons21/myaddon/web/my-addon.css
# addons21/myaddon/web/my-addon.js
# addons21/myaddon/user_files/README.txt # preserved on upgrade
```
```json
# manifest.json (required for distribution outside AnkiWeb)
{
"package": "myaddon",
"name": "My Awesome Add-on",
"conflicts": ["other_addon_id"],
"mod": 1699900000
}
```
```bash
# Create .ankiaddon file (Unix):
# $ cd myaddon && zip -r ../myaddon.ankiaddon *
# IMPORTANT:
# - Don't include __pycache__ folders
# - Don't include the top-level folder name
# - user_files/ contents are preserved on upgrade
```
--------------------------------
### Evaluating JavaScript with a callback
Source: https://github.com/ankitects/addon-docs/blob/main/src/porting2.0.md
Use evalWithCallback() to get the result of a JavaScript expression asynchronously, ensuring proper handling of return values.
```python
self.web.evalWithCallback("some_js_expression", self.my_callback)
```
--------------------------------
### Get Single Value from Anki DB
Source: https://github.com/ankitects/addon-docs/blob/main/src/the-anki-module.md
Use scalar() to retrieve a single value from the database. This is useful for counts or specific single-row, single-column queries.
```python
showInfo("card count: %d" % mw.col.db.scalar("select count() from cards"))
```
--------------------------------
### Load Add-on Configuration
Source: https://github.com/ankitects/addon-docs/blob/main/src/addon-config.md
Retrieve the add-on's configuration. If the config has not been customized, default values from `config.json` are used. Returns None if no `config.json` exists.
```python
from aqt import mw
config = mw.addonManager.getConfig(__name__)
print("var is", config['myvar'])
```
--------------------------------
### Manage Add-on Configuration with config.json
Source: https://context7.com/ankitects/addon-docs/llms.txt
Store and retrieve add-on settings using a JSON configuration file. Anki provides automatic GUI support for editing these settings, or you can register a custom action.
```json
# config.json (ship with your add-on)
# {"myvar": 5, "feature_enabled": true, "api_key": ""}
```
```markdown
# config.md (optional documentation shown to users)
# This add-on supports the following settings:
# - **myvar**: Number of items to display
# - **feature_enabled**: Enable experimental features
```
```python
from aqt import mw
def load_config():
# Get config (returns None if no config.json exists)
config = mw.addonManager.getConfig(__name__)
if config is None:
return {"myvar": 5} # fallback defaults
print("myvar is", config['myvar'])
return config
def save_config(config):
# Programmatically save config changes
mw.addonManager.writeConfig(__name__, config)
def show_options():
# Custom options dialog
print("Showing options...")
# Register custom config action (replaces default JSON editor)
mw.addonManager.setConfigAction(__name__, show_options)
```
--------------------------------
### Add Javascript to Card HTML
Source: https://github.com/ankitects/addon-docs/blob/main/src/reviewer-javascript.md
Use the `card_will_show` hook to append custom Javascript to the card's HTML. This example changes the background color of the review screen.
```python
from aqt import gui_hooks
def prepare(html, card, context):
return html + """
"""
gui_hooks.card_will_show.append(prepare)
```
--------------------------------
### Import Qt objects from aqt.qt
Source: https://github.com/ankitects/addon-docs/blob/main/src/porting2.0.md
Import all Qt objects like QDialog from aqt.qt to avoid specifying the Qt version, ensuring compatibility with both PyQt4 and PyQt5.
```python
from aqt.qt import *
```
--------------------------------
### Add-on Module Folder Structure
Source: https://github.com/ankitects/addon-docs/blob/main/src/addon-folders.md
Anki looks for an '__init__.py' file within each add-on's dedicated folder inside the main add-ons directory.
```python
addons21/myaddon/__init__.py
```
--------------------------------
### Create a Basic Add-on Menu Item
Source: https://context7.com/ankitects/addon-docs/llms.txt
This snippet shows how to add a custom menu item to Anki's 'Tools' menu that displays the current card count. It requires importing necessary modules from `aqt` and `aqt.utils`.
```python
# addons21/myaddon/__init__.py
# import the main window object (mw) from aqt
from aqt import mw
# import the "show info" tool from utils.py
from aqt.utils import showInfo, qconnect
# import all of the Qt GUI library
from aqt.qt import *
# Function to be called when the menu item is activated
def testFunction() -> None:
# get the number of cards in the current collection
cardCount = mw.col.card_count()
# show a message box
showInfo("Card count: %d" % cardCount)
# create a new menu item, "test"
action = QAction("test", mw)
# set it to call testFunction when it's clicked
qconnect(action.triggered, testFunction)
# and add it to the tools menu
mw.form.menuTools.addAction(action)
```
--------------------------------
### Run Anki from Terminal on Linux
Source: https://github.com/ankitects/addon-docs/blob/main/src/console-output.md
To view console output on Linux, open a terminal and run Anki with the `anki` command. This allows you to see stdout for debugging purposes.
```bash
anki
```
--------------------------------
### Incorrect Hook Usage with MyPy
Source: https://github.com/ankitects/addon-docs/blob/main/src/mypy.md
MyPy reports an error when a hook is called with an incorrect function signature. This example shows the expected error message and the corrected function signature.
```python
from aqt import gui_hooks
def myfunc() -> None:
print("myfunc")
gui_hooks.reviewer_did_show_answer.append(myfunc)
```
```python
from anki.cards import Card
def myfunc(card: Card) -> None:
print("myfunc")
```
--------------------------------
### Conditional import for Qt versions
Source: https://github.com/ankitects/addon-docs/blob/main/src/porting2.0.md
Implement conditional imports to support both PyQt4 and PyQt5, allowing add-ons to run on different Anki versions.
```python
try:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtNetwork import *
from PyQt5.QtWebEngineWidgets import *
from PyQt5.QtWebChannel import *
except ImportError:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtWidgets import *
from PyQt4.QtNetwork import *
# Webkit is not available in PyQt5
# from PyQt4.QtWebKit import *
# WebEngine is not available in PyQt4
# from PyQt4.QtWebEngineWidgets import *
# from PyQt4.QtWebChannel import *
# For Anki 2.1.20+
from anki.qt.qt import * # noqa
```
--------------------------------
### Review and Answer a Card
Source: https://context7.com/ankitects/addon-docs/llms.txt
This code demonstrates how to retrieve a due card from the scheduler and answer it using a specified ease level. Ensure the `mw.col.sched.getCard()` method returns a card before proceeding.
```python
from aqt import mw
def review_card():
# Get a due card from the scheduler
card = mw.col.sched.getCard()
if not card:
# current deck is finished
return
# Get the question and answer HTML
question = card.question()
answer = card.answer()
# Answer the card (ease: 1=Again, 2=Hard, 3=Good, 4=Easy)
ease = 3 # Good
mw.col.sched.answerCard(card, ease)
```
--------------------------------
### Set Custom Configuration Action
Source: https://github.com/ankitects/addon-docs/blob/main/src/addon-config.md
Register a function to be called when the user clicks the config button for this add-on in the GUI. This allows add-ons to manage options in their own GUI.
```python
mw.addonManager.setConfigAction(__name__, myOptionsFunc)
```
--------------------------------
### Save Add-on Configuration
Source: https://github.com/ankitects/addon-docs/blob/main/src/addon-config.md
Programmatically save changes to the add-on's configuration. This updates the user's `meta.json` file.
```python
mw.addonManager.writeConfig(__name__, config)
```
--------------------------------
### Show Console Output on macOS
Source: https://github.com/ankitects/addon-docs/blob/main/src/console-output.md
To view console output on macOS, open Terminal.app and run Anki using this command. This is useful for debugging and seeing warnings.
```bash
/Applications/Anki.app/Contents/MacOS/anki
```
--------------------------------
### Anki Debug Console Usage
Source: https://context7.com/ankitects/addon-docs/llms.txt
Access Anki's built-in REPL for testing and debugging. Use `pp()` for pretty printing objects. Evaluate expressions by pressing Ctrl+Return.
```python
# In Anki's Debug Console (Ctrl+Shift+; or Cmd+Shift+;)
# Type expressions and press Ctrl+Return to evaluate
>>> mw
```
```python
>>> print(mw)
```
```python
>>> pp(mw.reviewer.card)
```
```python
>>> pp(card()) # shortcut for mw.reviewer.card.__dict__
{
'_note': ,
'col': ,
'did': 1,
'due': -1,
'factor': 2350,
'id': 1307820012852,
...
}
```
```python
>>> pp(bcard()) # shortcut for selected card in browser
```
```python
# For programmatic debugging with pdb:
from aqt.qt import debug; debug()
```
--------------------------------
### Convert Python 2 to Python 3 with 2to3
Source: https://github.com/ankitects/addon-docs/blob/main/src/porting2.0.md
Use the 2to3 tool to automatically convert Python 2 scripts to Python 3. Specify an output directory and use the -W and -n flags for conversion and no-backup respectively.
```bash
2to3-3.8 --output-dir=aqt3 -W -n aqt
mv aqt aqt-old
mv aqt3 aqt
```
--------------------------------
### Using `wrap()` for Method Wrapping (Around)
Source: https://github.com/ankitects/addon-docs/blob/main/src/monkey-patching.md
Execute custom code both before and after the original function using `wrap()`. Pass the string "around" as the third argument and accept the `_old` parameter to call the original function.
```python
from anki.hooks import wrap
from aqt.editor import Editor
def mySetupButtons(self, _old):
ret = _old(self)
return ret
Editor.setupButtons = wrap(Editor.setupButtons, mySetupButtons, "around")
```
--------------------------------
### Create a Basic Anki Add-on
Source: https://github.com/ankitects/addon-docs/blob/main/src/a-basic-addon.md
This Python code adds a 'test' menu item to the 'Tools' menu in Anki. When clicked, it displays a message box showing the total number of cards in the collection. Ensure you have the necessary Anki modules imported.
```python
# import the main window object (mw) from aqt
from aqt import mw
# import the "show info" tool from utils.py
from aqt.utils import showInfo, qconnect
# import all of the Qt GUI library
from aqt.qt import *
# We're going to add a menu item below. First we want to create a function to
# be called when the menu item is activated.
def testFunction() -> None:
# get the number of cards in the current collection, which is stored in
# the main window
cardCount = mw.col.card_count()
# show a message box
showInfo("Card count: %d" % cardCount)
# create a new menu item, "test"
action = QAction("test", mw)
# set it to call testFunction when it's clicked
qconnect(action.triggered, testFunction)
# and add it to the tools menu
mw.form.menuTools.addAction(action)
```
--------------------------------
### Legacy Filter: Japanese Model Field Auto-population
Source: https://github.com/ankitects/addon-docs/blob/main/src/hooks-and-filters.md
A simplified example of the 'editFocusLost' filter used by the Japanese Support add-on. It automatically populates a destination field with readings from a source field for Japanese models, only if the destination is empty and the event originated from the source field.
```python
def onFocusLost(flag, n, fidx):
from aqt import mw
# japanese model?
if "japanese" not in n.model()['name'].lower():
return flag
# have src and dst fields?
for c, name in enumerate(mw.col.models.fieldNames(n.model())):
for f in srcFields:
if name == f:
src = f
srcIdx = c
for f in dstFields:
if name == f:
dst = f
if not src or not dst:
return flag
# dst field already filled?
if n[dst]:
return flag
# event coming from src field?
if fidx != srcIdx:
return flag
# grab source text
srcTxt = mw.col.media.strip(n[src])
if not srcTxt:
return flag
# update field
try:
n[dst] = mecab.reading(srcTxt)
except Exception, e:
mecab = None
raise
return True
addHook('editFocusLost', onFocusLost)
```
--------------------------------
### Perform Insert/Update with execute()
Source: https://github.com/ankitects/addon-docs/blob/main/src/the-anki-module.md
Use execute() with named arguments (?) for insert or update operations. Note that these changes will not sync.
```python
mw.col.db.execute("update cards set ivl = ? where id = ?", newIvl, cardId)
```
--------------------------------
### Using setStateShortcuts for custom shortcuts
Source: https://github.com/ankitects/addon-docs/blob/main/src/porting2.0.md
Utilize setStateShortcuts() to hook into shortcut adjustments for specific states, as WebEngine no longer provides keyPressEvent().
```python
self.web.setStateShortcuts(self.shortcuts)
```
--------------------------------
### Using `wrap()` for Method Wrapping (Before)
Source: https://github.com/ankitects/addon-docs/blob/main/src/monkey-patching.md
Modify a method by executing your custom code before the original function runs. Pass the string "before" as the third argument to `wrap()`.
```python
from anki.hooks import wrap
from aqt.editor import Editor
def mySetupButtons(self, _old):
ret = _old(self)
return ret
Editor.setupButtons = wrap(Editor.setupButtons, mySetupButtons, "before")
```
--------------------------------
### Register Web Exports for Add-on Assets
Source: https://github.com/ankitects/addon-docs/blob/main/src/hooks-and-filters.md
Register web assets like CSS and JS files from your add-on's 'web' subfolder. These can then be accessed under the \/_addons subpath in webviews. Ensure the path pattern correctly matches your asset files.
```python
from aqt import mw
mw.addonManager.setWebExports(__name__, r"web/.*(css|js)")
```
--------------------------------
### Hooks API - Legacy Hooks
Source: https://context7.com/ankitects/addon-docs/llms.txt
Older hook system using addHook(), runHook(), and runFilter() for compatibility with pre-2.1.20 add-ons.
```APIDOC
## Hooks API - Legacy Hooks
### Description
Older hook system using `addHook()`, `runHook()`, and `runFilter()` for compatibility with pre-2.1.20 add-ons.
### Methods
- **`addHook(hook_name, callback_function)`**: Registers a callback function for a given hook.
- **`runHook(hook_name, *args)`**: Executes all registered callbacks for a given hook.
- **`runFilter(hook_name, value, *args)`**: Executes registered filter callbacks and returns the modified value.
### Parameters
- **`hook_name`** (string) - The name of the hook.
- **`callback_function`** (function) - The function to be called when the hook is triggered.
- **`value`** - The initial value passed to filter hooks.
- **`*args`** - Additional arguments passed to the hook callbacks.
### Examples
#### Basic Hook Example
```python
from anki.hooks import addHook
from aqt import mw
def onLeech(card):
"""Move leech cards to a 'Difficult' deck"""
card.did = mw.col.decks.id("Difficult")
card.odid = 0
if card.odue:
card.due = card.odue
card.odue = 0
addHook("leech", onLeech)
```
#### Filter Hook Example
```python
from anki.hooks import addHook
from aqt import mw
def onFocusLost(flag, note, fieldIndex):
"""Example filter hook to auto-generate field content"""
# Only process Japanese note types
if "japanese" not in note.model()['name'].lower():
return flag
# Check if source field and destination exists
if note["Reading"]:
return flag # Already filled
# Generate reading and mark as changed
note["Reading"] = generate_reading(note["Expression"])
return True
addHook('editFocusLost', onFocusLost)
```
```
--------------------------------
### Webview Hooks - Injecting HTML/CSS/JS
Source: https://context7.com/ankitects/addon-docs/llms.txt
Modify webview content and respond to JavaScript messages from the card display.
```APIDOC
## Webview Hooks - Injecting HTML/CSS/JS
### Description
Modify webview content by injecting custom HTML, CSS, and JavaScript, and handle messages sent from JavaScript.
### Hooks
- **`gui_hooks.webview_will_set_content`**: Triggered before webview content is set. Allows modification of CSS and JS.
- **`gui_hooks.webview_did_receive_js_message`**: Triggered when a JavaScript message is received from the webview.
### Parameters
- **`web_content`** (WebContent object) - Contains lists for `css` and `js` to be injected.
- **`context`** - Contextual information about the webview.
- **`handled`** (boolean) - Indicates if the message has been handled.
- **`message`** (string) - The message received from JavaScript.
### Examples
#### Injecting CSS and JS
```python
from aqt import mw, gui_hooks
from aqt.webview import WebContent
# Register web exports for your add-on assets
mw.addonManager.setWebExports(__name__, r"web/.*(css|js)")
def on_webview_will_set_content(web_content: WebContent, context) -> None:
"""Inject custom CSS and JS into webviews"""
addon_package = mw.addonManager.addonFromModule(__name__)
web_content.css.append(f"/_addons/{addon_package}/web/my-addon.css")
web_content.js.append(f"/_addons/{addon_package}/web/my-addon.js")
gui_hooks.webview_will_set_content.append(on_webview_will_set_content)
```
#### Handling JavaScript Messages
```python
from aqt import mw, gui_hooks
def on_js_message(handled, message, context):
"""Handle messages sent from JavaScript via pycmd()"""
if message.startswith("myAddon:"):
# Process custom message
data = message[8:] # Remove prefix
print(f"Received: {data}")
return True, None # Mark as handled
return handled # Pass through to other handlers
gui_hooks.webview_did_receive_js_message.append(on_js_message)
```
```
--------------------------------
### Hooks API - Editor Buttons
Source: https://context7.com/ankitects/addon-docs/llms.txt
Add custom buttons to the editor using the setupEditorButtons hook.
```APIDOC
## Hooks API - Editor Buttons
### Description
Add custom buttons to the Anki editor interface.
### Method
`addHook("setupEditorButtons", callback_function)`
### Parameters
- **`buttons`** (list) - The current list of editor buttons.
- **`editor`** (object) - The editor instance.
- **`callback_function`** (function) - A function that takes `buttons` and `editor` as arguments and returns the modified list of buttons.
### Example
```python
from aqt.utils import showInfo
from anki.hooks import addHook
def onStrike(editor):
"""Cross out the currently selected text"""
editor.web.eval("wrap('', '');")
def addMyButton(buttons, editor):
"""Adds a 'Strikethrough' button to the editor"""
editor._links['strike'] = onStrike
return buttons + [editor._addButton(
"iconname", # or "/full/path/to/icon.png"
"strike", # link name
"Strikethrough")] # tooltip
addHook("setupEditorButtons", addMyButton)
```
```
--------------------------------
### Schedule Reviews for Tomorrow
Source: https://github.com/ankitects/addon-docs/blob/main/src/the-anki-module.md
Sets the due date for all currently due cards to tomorrow. Requires Anki 2.1.55+.
```python
ids = mw.col.find_cards("is:due")
mw.col.sched.set_due_date(ids, "1")
```
--------------------------------
### Run Non-Blocking Read-Only Operations with QueryOp
Source: https://context7.com/ankitects/addon-docs/llms.txt
Use QueryOp for read-only or non-undoable operations to avoid freezing the UI. It allows updating progress on the main thread.
```python
from anki.collection import Collection
from aqt.operations import QueryOp
from aqt.utils import showInfo
from aqt import mw
import time
def my_background_op(col: Collection, note_ids: list[int]) -> int:
"""Long-running operation that runs in background thread"""
count = 0
total = len(note_ids)
last_progress = time.time()
for i, note_id in enumerate(note_ids):
note = col.get_note(note_id)
# Process note...
count += 1
# Update progress on main thread (throttled)
if time.time() - last_progress >= 0.1:
remaining = total - i
mw.taskman.run_on_main(
lambda r=remaining, t=total: mw.progress.update(
label=f"Processing: {t - r}/{t}",
value=t - r,
max=t,
)
)
last_progress = time.time()
return count
def on_success(count: int) -> None:
showInfo(f"Processed {count} notes")
def my_ui_action(note_ids: list[int]):
op = QueryOp(
parent=mw,
op=lambda col: my_background_op(col, note_ids),
success=on_success,
)
# Show progress window and run in background
op.with_progress().run_in_background()
# For operations that don't touch the collection (e.g., network requests)
# use without_collection() to allow concurrent execution:
# op.without_collection().run_in_background()
```
--------------------------------
### Apply Monkeytype Generated Types
Source: https://github.com/ankitects/addon-docs/blob/main/src/editing-and-mypy.md
After gathering runtime type information with monkeytype, use this command to apply the generated type hints to your add-on code. Ensure PYTHONPATH is set correctly.
```shell
(pyenv) % PYTHONPATH=~/Library/Application\ Support/Anki2/addons21 monkeytype apply test
```
--------------------------------
### Update Deck Configuration
Source: https://github.com/ankitects/addon-docs/blob/main/src/the-anki-module.md
Changes the 'perDay' setting for new cards in a deck's configuration and saves it. Use `col.decks.get_config(config_id)` to retrieve the configuration.
```python
config = col.decks.get_config(config_id)
config["new"]["perDay"] = 20
col.decks.save(config)
```
--------------------------------
### Relative import for single .py add-ons
Source: https://github.com/ankitects/addon-docs/blob/main/src/porting2.0.md
When migrating a single .py add-on to a folder structure, use a relative import in __init__.py to import the original script.
```python
from . import demo
```
--------------------------------
### Webview Hooks - Injecting HTML/CSS/JS
Source: https://context7.com/ankitects/addon-docs/llms.txt
Modify webview content and respond to JavaScript messages from the card display. This involves registering web exports for add-on assets and using `gui_hooks.webview_will_set_content` to inject CSS/JS, and `gui_hooks.webview_did_receive_js_message` to handle messages from JavaScript.
```python
from aqt import mw, gui_hooks
from aqt.webview import WebContent
# Register web exports for your add-on assets
mw.addonManager.setWebExports(__name__, r"web/.*(css|js)")
def on_webview_will_set_content(web_content: WebContent, context) -> None:
"""Inject custom CSS and JS into webviews"""
addon_package = mw.addonManager.addonFromModule(__name__)
web_content.css.append(f"/_addons/{addon_package}/web/my-addon.css")
web_content.js.append(f"/_addons/{addon_package}/web/my-addon.js")
gui_hooks.webview_will_set_content.append(on_webview_will_set_content)
```
```python
def on_js_message(handled, message, context):
"""Handle messages sent from JavaScript via pycmd()"""
if message.startswith("myAddon:"):
# Process custom message
data = message[8:] # Remove prefix
print(f"Received: {data}")
return True, None # Mark as handled
return handled # Pass through to other handlers
gui_hooks.webview_did_receive_js_message.append(on_js_message)
```
--------------------------------
### AnkiWeb Add-on Folder Naming
Source: https://github.com/ankitects/addon-docs/blob/main/src/addon-folders.md
When an add-on is downloaded from AnkiWeb, Anki uses the add-on's ID as the folder name.
```python
addons21/48927303923/__init__.py
```
--------------------------------
### Trigger PDB Debugger in Anki
Source: https://github.com/ankitects/addon-docs/blob/main/src/debugging.md
Insert this line into your code to trigger the PDB debugger when that point is reached. This requires running Anki on Linux or from source.
```python
from aqt.qt import debug; debug()
```
--------------------------------
### Run Background Operation with QueryOp
Source: https://github.com/ankitects/addon-docs/blob/main/src/background-ops.md
Use QueryOp for long-running, non-undoable operations. The operation runs in a background thread, and a success callback is executed on completion. Ensure UI interactions are handled on the main thread.
```python
from anki.collection import Collection
from aqt.operations import QueryOp
from aqt.utils import showInfo
from aqt import mw
def my_background_op(col: Collection, note_ids: list[int]) -> int:
# some long-running op, eg
for id in note_ids:
note = col.get_note(note_id)
# ...
return 123
def on_success(count: int) -> None:
showInfo(f"my_background_op() returned {count}")
def my_ui_action(note_ids: list[int]):
op = QueryOp(
# the active window (main window in this case)
parent=mw,
# the operation is passed the collection for convenience; you can
# ignore it if you wish
op=lambda col: my_background_op(col, note_ids),
# this function will be called if op completes successfully,
# and it is given the return value of the op
success=on_success,
)
# if with_progress() is not called, no progress window will be shown.
# note: QueryOp.with_progress() was broken until Anki 2.1.50
op.with_progress().run_in_background()
```
--------------------------------
### Type Check Add-on Code with Mypy
Source: https://github.com/ankitects/addon-docs/blob/main/src/editing-and-mypy.md
Use this command in the terminal to run mypy on your add-on. It will report errors if Anki functions are called incorrectly.
```bash
mypy myaddon
```
--------------------------------
### Wrap Anki Methods with wrap() Helper
Source: https://context7.com/ankitects/addon-docs/llms.txt
Use the wrap() helper from anki.hooks to override or extend existing Anki functions when hooks are not available. It supports wrapping before, after, or around the original method.
```python
from anki.hooks import wrap
from aqt.editor import Editor
from aqt.utils import showInfo
def buttonPressed(self):
showInfo("Custom button pressed!")
def mySetupButtons(self):
"""Add custom button after original buttons"""
self._addButton(
"mybutton",
lambda s=self: buttonPressed(s),
text="MyBtn",
size=False
)
# Wrap after original (default)
Editor.setupButtons = wrap(Editor.setupButtons, mySetupButtons)
# Wrap before original
# Editor.setupButtons = wrap(Editor.setupButtons, mySetupButtons, "before")
# Wrap around original (full control)
def mySetupButtonsAround(self, _old):
# Before code
print("Before setup")
ret = _old(self)
# After code
print("After setup")
return ret
Editor.setupButtons = wrap(Editor.setupButtons, mySetupButtonsAround, "around")
```
--------------------------------
### Using `wrap()` for Method Wrapping (After)
Source: https://github.com/ankitects/addon-docs/blob/main/src/monkey-patching.md
Utilize Anki's `wrap()` function for a convenient way to modify methods. By default, `wrap()` executes your custom code after the original function. Note the use of `lambda` to pass the editor instance correctly.
```python
from anki.hooks import wrap
from aqt.editor import Editor
from aqt.utils import showInfo
def buttonPressed(self):
showInfo("pressed " + `self`)
def mySetupButtons(self):
# - size=False tells Anki not to use a small button
# - the lambda is necessary to pass the editor instance to the
# callback, as we're passing in a function rather than a bound
# method
self._addButton("mybutton", lambda s=self: buttonPressed(self),
text="PressMe", size=False)
Editor.setupButtons = wrap(Editor.setupButtons, mySetupButtons)
```
--------------------------------
### Accessing Web Exports in Webviews
Source: https://github.com/ankitects/addon-docs/blob/main/src/hooks-and-filters.md
Append the subpaths of your registered web exports to the web_content fields within a function that subscribes to gui_hooks.webview_will_set_content. This makes your add-on's assets available in the webview.
```python
from aqt.webview import WebContent
def on_webview_will_set_content(web_content: WebContent, context) -> None:
addon_package = mw.addonManager.addonFromModule(__name__)
web_content.css.append(f"/_addons/{addon_package}/web/my-addon.css")
web_content.js.append(f"/_addons/{addon_package}/web/my-addon.js")
```
--------------------------------
### Register a Function to a New Style Hook
Source: https://github.com/ankitects/addon-docs/blob/main/src/hooks-and-filters.md
Use this pattern to register a Python function to be called when a specific Anki event occurs. Ensure the function signature matches the hook's expected arguments.
```python
from aqt import gui_hooks
def myfunc(card):
print("question shown, card question is:", card.q())
gui_hooks.reviewer_did_show_question.append(myfunc)
```
--------------------------------
### Reviewer JavaScript - Card Display Modification
Source: https://context7.com/ankitects/addon-docs/llms.txt
Inject JavaScript into cards during review, preview, and card layout screens using the `gui_hooks.card_will_show` hook. This allows modification of card HTML and execution of JavaScript functions like `onUpdateHook` and `onShownHook`.
```python
from aqt import gui_hooks
def prepare(html, card, context):
"""Modify card HTML before display
Context values: "reviewQuestion", "reviewAnswer", "clayoutQuestion",
"clayoutAnswer", "previewQuestion", "previewAnswer"
"""
return html + """
"""
gui_hooks.card_will_show.append(prepare)
```
--------------------------------
### Access Anki Collection
Source: https://github.com/ankitects/addon-docs/blob/main/src/command-line-use.md
Open an Anki collection and access its scheduling information using the `anki` module. Ensure the collection path is correct.
```python
from anki.collection import Collection
col = Collection("/path/to/collection.anki2")
print(col.sched.deck_due_tree())
```
--------------------------------
### Basic Monkey Patching: Overwriting a Function
Source: https://github.com/ankitects/addon-docs/blob/main/src/monkey-patching.md
Replace an existing Anki function with your custom version. This method is simple but fragile, as updates to Anki may break your add-on.
```python
from aqt.editor import Editor
def mySetupButtons(self):
Editor.setupButtons = mySetupButtons
```