### 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 ```