Skip to content

Python API

The SDK calls the default Rust public API through a native extension. It includes py.typed and checked public signatures. Start with the quickstart.

Entry Behavior
Project(namespace, *, default_deck=None) Validates the explicit stable namespace and configures the default deck
project.add(key, note) Atomically collects the note, model and owned media; duplicate note keys fail
project.add_asset(media) Includes an explicit raw HTML/CSS/script dependency
len(project) Number of successfully added notes
Note.basic(front, back) Built-in Basic model; strings mean Text
Note.cloze(text) Built-in Cloze model; strings mean Text and retain cloze syntax
note.field(key, content) Returns a new note with the field assignment; key validation occurs on add
note.deck(name) / .tag(tag) / .tags(tags) Returns a configured note
note.note_type The immutable owned model

Ordinary strings become Text. Content.text(value) and Content.html(value) are explicit constructors; Content.sequence(iterable) composes strings and content without rendering or losing media dependencies. A field does not accept a raw filename as a media reference.

NoteType.builder(key) returns an immutable builder. Its .name, .field, .template, .css, .cloze_field and .asset methods return updated builders. .build() validates the complete definition and returns an immutable NoteType. model.note() creates a note owning that model. NoteType.from_bundle(path, limits=MediaLimits()) loads the same model and its assets from the current bundle format; the optional budget applies separately to each asset before reading.

Field(key, name=None, required=False, sort=False) separates the stable key from the display name. Template(key, front, back, name=None, browser_front=None, browser_back=None, target_deck=None, generation=GenerationRule()) uses field keys in every source string. GenerationRule.all(keys) and .any(keys) configure card generation. The default infers Anki’s field-presence condition. A builder’s .cloze_field(key) declares custom cloze generation.

Entry Behavior
Media.file(path, *, limits=MediaLimits()) Reads an owned snapshot immediately
Media.bytes(data, media_type, *, limits=MediaLimits()) Owns bytes with an explicit MIME type
media.with_export_name(name) Returns a new filename value sharing the snapshot; previous references retain their names
media.filename, media.media_type, len(media) Read-only snapshot metadata
media.image() / .sound() Typed content that retains the snapshot

MediaLimits(max_bytes=256 << 20) controls each import before snapshot creation. Large media can spill to owned temporary storage. Naming conflicts are validated across all explicit and automatically collected assets.

Note.image_occlusion(media) returns an ImageOcclusionBuilder. Chain .mask(Mask.rect(key, x, y, width, height)), optionally .mode(OcclusionMode.HIDE_ONE_GUESS_ONE), then .build(). The default mode is HIDE_ALL_GUESS_ONE. Completion decodes the image and validates stable mask keys and finite in-bounds pixel coordinates. On the returned Note, set header, back_extra or comments normally. The generated image and occlusion fields cannot be assigned manually.

Entry Result
project.build(BuildOptions.to(path)) BuildOutput with a guaranteed persistent artifact
project.build(BuildOptions.temporary()) BuildOutput with an owned temporary artifact
options.update_from(path) Returns options for updating from complete baseline evidence
project.compare(CompareOptions.against(path)) Completed ComparisonReport, including a separate publication policy decision
options.update_policy(policy) Applies explicit risk policy to Update/Compare; invalid for Create
options.inspect_limits(limits) Applies finite limits independently to baseline and candidate

BuildOutput has .artifact, .report and .snapshot(). BuildReport exposes .counts (BuildCounts with notes/cards/media), .diagnostics, .comparison and .snapshot(). A report has no outcome or file owner. ComparisonReport exposes .findings, .policy, .allows_publication and .snapshot(). Snapshots are ordinary JSON-serializable dictionaries and contain no artifact ownership.

ApkgArtifact exposes .path, .persist_to(path) and .close(). Copying it with copy.copy retains another owner; a context manager releases that owner on exit. Temporary output disappears when its last owner is released. Persistent paths survive handle cleanup. A failed persist keeps the source handle usable.

UpdatePolicy() blocks High and Critical findings. .fail_on(RiskLevel.MEDIUM) changes the threshold. .allow('RISK.NOTE_REMOVED') explicitly accepts the whole registered category, preserving evidence. Unknown codes fail immediately. InspectLimits exposes all finite byte/count budgets, including max_identity_bytes; it cannot disable required inspections.

Failures raise SchemaError, AddError, MediaError, ImageOcclusionError, TemplateBundleError, CompareError, PolicyError, BuildError or PersistError. Each exposes kind, code, details, structured observations in source_details, and source-chain text in causes, and retains the native exception as __cause__. I/O causes retain a chained OSError where available. BuildError provides .report and .snapshot(); CompareError provides partial .report; PersistError details include actual publication facts.

Native Project operations release the GIL and reject simultaneous use of the same object with BINDING.PROJECT_BUSY; independent projects can run concurrently. After a process fork, recreate all native values and handles. Inherited handles reject operations with BINDING.FORKED_OBJECT, and their cleanup cannot remove parent-owned files. Child-created values operate and clean up normally. See diagnostics.

Failed additions leave the project unchanged. Structured context records the original note/model keys and the target, with optional conflict or media usage details. Rust exposes note::{AddContext, AddTarget, AddDetail} through AddError::context() / detail(); SDKs expose AddError.details with typed context/detail. Node fields use camelCase and are deeply frozen; Python uses snake_case TypedDicts. Tags retain their insertion index; field locations retain original sequence indices (null/None for the field, [] for its root) and optional UTF-8 byte ranges. Default-deck errors from build have no note/model keys and remain available in the source chain.

Typed images require image/* MIME at addition; sound references accept audio/* or video/*. Other categories raise NOTE.MEDIA_USAGE_INVALID. Constructors stay infallible; explicit assets and raw HTML are unaffected. The check uses retained import MIME, and does not certify playback. Build’s independent MIME/extension validation still applies. See the design for the complete target/detail table.

Batch media and prepared publication are included in the public 0.3.1 package.

from ankiforge import Project, Note, BuildOptions, BuildError
project = Project('review-once').add('one', Note.basic('Question', 'Answer'))
previous = project.build(BuildOptions.to('previous.apkg'))
previous.artifact.close()
prepared = project.prepare_publication(
BuildOptions.to('next.apkg').update_from('previous.apkg')
)
try:
report = prepared.report # observations; does not own the candidate
if report.comparison is None or report.comparison.allows_publication:
output = prepared.publish()
output.artifact.close() # persistent next.apkg remains
except BuildError as error:
print(error.snapshot()['result']) # actual publication/durability facts
raise
finally:
prepared.close() # idempotent; deletes an unused candidate

One publish attempt consumes the owner, including policy or I/O failure. Later calls raise PreparedPublicationStateError with code BUILD.PREPARED_UNAVAILABLE and reason closed or consumed; close does not cancel an already running publication. Relative paths bind at preparation invocation, and Project changes cannot modify the reviewed APKG. Reports survive close without retaining files. Change options or retry by preparing again. Duration excludes review waiting. Late errors can mean replacement succeeded with unconfirmed durability; inspect publication facts before deciding what to do. Native binding protocol 1 and contract bundle 2.1.0 are required; older same-version binaries are rejected.

View Markdown