Skip to main content

Quillmark — Python bindings

Python bindings for Quillmark, a schema-driven document engine.

Maintained by TTQ.

Installation

pip install quillmark

Quick Start

from quillmark import Quillmark, Quill, Document, OutputFormat

engine = Quillmark()                       # backend registry + render dispatcher
quill = Quill.from_path("path/to/quill")   # portable, declarative config data

markdown = """~~~
$quill: my_quill
$kind: main
title: Hello World
~~~

# Hello
"""

parsed = Document.from_markdown(markdown)
result = engine.render(quill, parsed, OutputFormat.PDF)
result.artifacts[0].save("output.pdf")

API surface

Python is a Tier-1 binding: field I/O flows through quill.writer(doc) and quill.view(doc), the schema-bound write/read front doors. Document carries the quill-free surface — parse, storage, structure, $ext / $seed, and remove_field. There is no opaque field store and no anchor-preserving content lane (install / revise / apply_change + the import_markdown / export_markdown / rebase / map_pos codec); those are WASM-only by scope, serving live editors that Python does not target. See prose/canon/BINDINGS.md.

Names follow snake_case; the shared model (the Document / Card shapes, Diagnostics, the storage DTO) is identical to the @quillmark/wasm package's. Python renders in one shot via engine.render; the iterative render-session and canvas-preview surface is WASM-only (see prose/canon/PREVIEW.md).

Capability principle: a Quill is portable, declarative config data — quill.metadata is a pure, infallible snapshot of the quill: section. The format probe (supported_formats) and rendering (render) are resolved by the engine, against a quill; they raise QuillmarkError (code engine::backend_not_found) only if the declared backend isn't registered.

Quillmark

engine = Quillmark()
engine.registered_backends()              # ['typst', 'pdfform'] (order not guaranteed)
engine.render(quill, parsed, OutputFormat.PDF)   # ppi=, pages=, producer= optional
engine.supported_formats(quill)           # [OutputFormat.PDF, ...] (raises if backend unregistered)

Quill

quill = Quill.from_path("path/to/quill")  # pure config load — no backend resolved here

quill.backend_id            # "typst" (declared backend)
quill.blueprint             # auto-generated annotated Markdown blueprint
quill.schema                # structured dict of the quill's document schema
quill.metadata              # pure config snapshot of the quill: section (never raises)
quill.quill_ref             # "name@version"

diags   = quill.validate(parsed)          # list of validation::* diagnostic dicts ([] = valid)
seed    = quill.seed_document()           # starter Document seeded from `example:` values
main    = quill.seed_main()               # just the $kind: main card (dict, like doc.main)
card    = quill.seed_card("note")         # one starter composable card (dict), None if kind undeclared

writer  = quill.writer(doc)               # schema-bound typed write front door
reader  = quill.view(doc)                 # schema-bound interpreted read front door

Writerquill.writer(doc)

The typed write front door. Resolves each field's type from the bound quill, so a name the schema does not declare is a typo (UnknownField), not a fallback. Holds both handles by reference and owns neither — ephemeral by convention: bind, write, discard.

w = quill.writer(doc)
w.set("title", "On Taro")                 # typed-commit one field (mismatch raises now)
w.set_all({"title": "T", "author": "A"})  # atomic batch; one diagnostic per bad field
w.set_body("A **taro** essay.")           # typed body write (edit semantics)
w.revise_field("bio", "make it **bold**") # typed *and* anchor-preserving richtext write
w.add_card("quotes", {"author": "Basho"}, "…", at=None)  # make + typed commit + insert (at appends/inserts)
w.remove_card(0)
w.card(0).set("author", "Issa")           # a CardWriter: .index, .kind, .set, .set_all, .set_body, .revise_field

Viewquill.view(doc)

The interpreted read front door and the read twin of Writer. One get reads each field by its declared type: a richtext field to its markdown projection, every other type its canonical value verbatim.

v = quill.view(doc)
v.get("bio")                              # richtext → markdown str; scalar → its value; absent → None
                                          # undeclared name raises UnknownField; undecodable content raises FieldRichtextDecode
v.get_body()                              # the main body markdown (quill-free body read)
v.card(0).kind                            # the composable card's $kind
v.card(0).get("author")                   # a card field, interpreted by its $kind schema
v.card(0).get_body()

RenderResult / Artifact

result.artifacts            # [Artifact, ...]
result.warnings             # [Diagnostic, ...]
result.format               # OutputFormat
result.render_time_ms       # float

artifact.format             # OutputFormat
artifact.bytes              # bytes
artifact.mime_type          # 'application/pdf', 'image/svg+xml', ...
artifact.save("out.pdf")

Document

doc = Document("my_quill")                       # blank canvas: $quill only, no fields, no cards
doc = Document.from_markdown(markdown)
emitted = doc.to_markdown()

stored   = doc.to_json()
restored = Document.from_json(stored)
maybe    = Document.try_from_json(blob)          # None when not a DTO

Document.schema_version_of(blob)                 # raw tag (incl. unknown futures)
Document.current_schema_version()                # what this build writes

Document.format_rules()                          # card-yaml authoring rules (static text)
Document.quill_ref_hint()                        # $quill reference grammar (static text)
Document.blueprint_instruction("taro")           # LLM/MCP blueprint header for a quill

doc.clone()
doc.equals(other)
doc.card_count
doc.main; doc.cards; doc.body; doc.warnings      # total-read snapshots (dicts); body is a content dict
doc.set_quill_ref("other@1.0")

# Structure (quill-free — a card kind is a name, not a schema fact):
doc.insert_card(Document.make_card("note", {"x": 1}, "..."), at=None)  # at appends/inserts
doc.remove_card(0)                               # returns the Card dict, or None
doc.move_card(2, 0); doc.set_card_kind(0, "summary")
doc.remove_field("title")                        # remove has no lane; card=i targets a composable card

# Out-of-band consumer state (never rendered):
doc.store_ext({"agent": {"pinned": True}})       # whole $ext map; card=i for a composable card
doc.store_ext_namespace("agent", {"n": 1})       # one slot, siblings preserved; card=i too
doc.remove_ext_namespace("agent"); doc.remove_ext()
doc.store_seed_namespace("note", {"tag": "T"})   # per-kind $seed overlay; new cards spawn with it
doc.remove_seed_namespace("note")

Setting a field's value is the writer's job (quill.writer(doc).set(...)) — a field write needs the schema, and Document is quill-free. Reading a field's interpreted value is the view's (quill.view(doc).get(...)).

Schema model

A field's cell is inferred from whether the schema declares a default::

  • Unendorsed (no default:) — the blueprint renders the !must_fill marker (carrying the field's example as a suggested value when one exists). An absent Unendorsed field zero-fills silently. A !must_fill marker left in the document is non-fatal: it emits the validation::must_fill warning and still renders. Partial documents are accepted; engine.render(quill, doc) only raises for malformed input.
  • Endorsed (with default:) — the blueprint renders the default value with a type-only # <type> annotation (shippable as-is), and the default is used when the document omits the field.

There is no required: axis on FieldSchema.

Error contract

A single exception type — QuillmarkError — is raised for every failure mode. Every raised exception carries a non-empty .diagnostics list of Diagnostic objects. This matches the WASM binding's contract.

try:
    Document.from_markdown(bad_md)
except QuillmarkError as exc:
    for d in exc.diagnostics:
        print(d.severity, d.code, d.message, d.path)
        print(str(d))   # canonical pretty-printed text (matches CLI / WASM)

EditError-shaped failures (invalid field names, kind names, out-of-range indices) prefix the message with [EditError::<Variant>] — the same format WASM uses — so callers can pattern-match on the message when they need to.

Changelog

See the changelog and the GitHub Releases page for release notes and version history.

Development

uv venv
uv pip install -e ".[dev]"
uv run pytest

License

Apache-2.0

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

quillmark-0.95.1.tar.gz (2.8 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

quillmark-0.95.1-cp310-abi3-win_amd64.whl (16.1 MB view details)

Uploaded CPython 3.10+Windows x86-64

quillmark-0.95.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (17.2 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ x86-64

quillmark-0.95.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (16.5 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ ARM64

quillmark-0.95.1-cp310-abi3-macosx_11_0_arm64.whl (15.1 MB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

File details

Details for the file quillmark-0.95.1.tar.gz.

File metadata

  • Download URL: quillmark-0.95.1.tar.gz
  • Upload date:
  • Size: 2.8 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for quillmark-0.95.1.tar.gz
Algorithm Hash digest
SHA256 a4caee0fd21dbbd373c5dd0acc9e6913264928cc976512b43b629326f71feaa5
MD5 e0dfae1f1f71b992c0b2c8b4dda9ae4d
BLAKE2b-256 afdec6fdff7deea651dd693fba12fa139692079696b84338e56d28d3b70d0da6

See more details on using hashes here.

Provenance

The following attestation bundles were made for quillmark-0.95.1.tar.gz:

Publisher: release.yml on borb-sh/quillmark

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file quillmark-0.95.1-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: quillmark-0.95.1-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 16.1 MB
  • Tags: CPython 3.10+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for quillmark-0.95.1-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 18f0d90e06bb33aefc69ae3d6e7d54380b99561cd6c021e78d810d8a34a5fb4e
MD5 2205f63f025a04125ad02598e99c1e16
BLAKE2b-256 e29233109a14d62ed32a358d42f505419a3b64c16f30800f98f2583cf27f62b3

See more details on using hashes here.

Provenance

The following attestation bundles were made for quillmark-0.95.1-cp310-abi3-win_amd64.whl:

Publisher: release.yml on borb-sh/quillmark

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file quillmark-0.95.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for quillmark-0.95.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 789cf76e277218279619c65cd892bfe9f0d911496337bae40ea73bd4936c3324
MD5 9f18b41dcf14415695accc2415dac0d4
BLAKE2b-256 8ccf904d7995b96c11f9581bff2a39cc3cd56ae40abc29a092e6e413f9171bf4

See more details on using hashes here.

Provenance

The following attestation bundles were made for quillmark-0.95.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on borb-sh/quillmark

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file quillmark-0.95.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for quillmark-0.95.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 4fcb80daebc6ca5f612d09a72ee895dd1aac3e572ab4bf6798def63f4e4292e3
MD5 cc216cd090d204079daf6cce5a5c83d3
BLAKE2b-256 bd867bafccd0a79e698731a29e66b30832361242354ca03ac978a1edb28935dc

See more details on using hashes here.

Provenance

The following attestation bundles were made for quillmark-0.95.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on borb-sh/quillmark

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file quillmark-0.95.1-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for quillmark-0.95.1-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 24c8e52150b154c2cd4c472bcf765f8c5b60e3b0bd755f44b41f6417d663bca4
MD5 1c81a8fd9329a21bbae13697a61fc52f
BLAKE2b-256 ecbbbe6260aa62ccc7563f1499c7fb96f7836983788a7d853df173d88ca991bb

See more details on using hashes here.

Provenance

The following attestation bundles were made for quillmark-0.95.1-cp310-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on borb-sh/quillmark

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.112.0

5 files

0.111.0

5 files

0.110.0

5 files

0.109.1

5 files

0.109.0

5 files

0.108.3

5 files

0.108.2

5 files

0.108.1

5 files

0.108.0

5 files

0.107.0

5 files

0.106.0

5 files

0.105.0

4 files

0.104.0

4 files

0.103.0

4 files

0.102.0

4 files

0.101.0

4 files

0.100.0

4 files

0.99.0

4 files

0.98.0

5 files

0.97.0

5 files

0.96.0

5 files

This release

0.95.1 This release

5 files

0.94.0

5 files

0.92.1

5 files

0.92.0

5 files

0.91.0

5 files

0.90.0

5 files

0.89.1

5 files

0.88.0

5 files

0.87.3

5 files

0.87.2

5 files

0.87.1

5 files

0.87.0

5 files

0.86.0

5 files

0.85.0

5 files

0.84.0

5 files

0.83.0

5 files

0.82.0

5 files

0.81.0

5 files

0.80.0

5 files

0.79.0

5 files

0.78.0

5 files

0.77.0

5 files

0.76.0

5 files

0.75.0

5 files

0.74.1

5 files

0.74.0

5 files

0.73.0

5 files

0.72.0

5 files

0.71.1

5 files

0.71.0

5 files

0.70.0

5 files

0.69.1

5 files

0.69.0

5 files

0.68.0

5 files

0.67.0

5 files

0.66.2

5 files

0.66.1

5 files

0.66.0

5 files

0.65.1

5 files

0.64.0

5 files

0.63.0

5 files

0.62.0

5 files

0.61.0

5 files

0.60.0

5 files

0.59.0

5 files

0.58.0

5 files

0.57.0

5 files

0.56.0

5 files

0.55.0

5 files

0.54.1

5 files

0.54.0

5 files

0.53.1

5 files

0.53.0

5 files

0.52.0

5 files

0.51.1

5 files

0.51.0

5 files

0.50.0

5 files

0.49.1

5 files

0.49.0

5 files

0.48.0

5 files

0.47.0

5 files

0.46.0

5 files

0.45.0

5 files

0.44.0

5 files

0.43.0

5 files

0.42.2

5 files

0.42.0

5 files

0.41.2

5 files

0.41.1

5 files

0.41.0

5 files

0.40.3

5 files

0.39.0

5 files

0.38.0

5 files

0.37.1

5 files

0.37.0

5 files

0.36.0

5 files

0.35.1

5 files

0.35.0

5 files

0.34.0

5 files

0.33.0

5 files

0.32.2

5 files

0.32.1

5 files

0.32.0

5 files

0.28.0

5 files

0.27.0

5 files

0.26.0

5 files

0.25.0

5 files

0.24.3

5 files

0.24.2

5 files

0.24.1

5 files

0.24.0

5 files

0.23.1

5 files

0.22.2

5 files

0.22.1

5 files

0.22.0

5 files

0.21.3

5 files

0.21.2

5 files

0.21.1

5 files

0.21.0

5 files

0.20.2

5 files

0.20.1

5 files

0.20.0

5 files

0.19.1

5 files

0.19.0

5 files

0.18.0

5 files

0.17.0

5 files

0.16.0

5 files

0.15.1

5 files

0.15.0

5 files

0.14.0

5 files

0.13.3

5 files

0.13.2

5 files

0.13.1

5 files

0.13.0

5 files

0.12.0

5 files

0.11.0

5 files

0.10.0

5 files

0.9.0

5 files

0.8.5

5 files

0.8.4

5 files

0.8.3

5 files

0.8.2

5 files

0.8.1

5 files

0.8.0

5 files

0.7.0

5 files

0.6.14

5 files

0.6.7

5 files

0.6.6

5 files

0.6.5

5 files

0.6.4

5 files

0.6.3

5 files

0.6.2

5 files

0.6.1

5 files

0.6.0

5 files

0.5.1

5 files

0.5.0

5 files

0.4.1

5 files

0.4.0

5 files

0.1.20

5 files

0.1.19

5 files

0.1.18

5 files

0.1.17

5 files

0.1.14

5 files

0.1.13

5 files

0.1.12

6 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page