Skip to main content

Async API

paperjam provides native async support powered by Rust and tokio. All CPU-bound operations have async counterparts that run on tokio's blocking thread pool, keeping your asyncio event loop responsive.

Under the hood, the paperjam-async Rust crate wraps paperjam-core operations with tokio::task::spawn_blocking, and the paperjam-py bindings expose these as native Python coroutines via pyo3-async-runtimes. No Python-level thread pool management is needed.

Top-level async functions

import paperjam

# Open a document without blocking
doc = await paperjam.aopen("report.pdf")
doc = await paperjam.aopen(pdf_bytes)
doc = await paperjam.aopen("locked.pdf", password="secret")

# Merge without blocking
merged = await paperjam.amerge([doc_a, doc_b])

# Render without opening a Document first
img = await paperjam.arender("slides.pdf", page=1, dpi=150)

# Convert to Markdown without opening a Document first
md = await paperjam.ato_markdown("report.pdf")

Document async methods

Every long-running document method has an async equivalent prefixed with a:

doc = await paperjam.aopen("report.pdf")

# Saving
await doc.asave("output.pdf")
data = await doc.asave_bytes()

# Rendering (requires render feature)
img = await doc.arender_page(1, dpi=150)
imgs = await doc.arender_pages(pages=[1, 2, 3])

# Extraction
tables = await doc.aextract_tables()
md = await doc.ato_markdown()
results = await doc.asearch("keyword")

# Comparison
diff_result = await doc.adiff(other_doc)

# Redaction
redacted, result = await doc.aredact_text("SSN:")

Page async methods

page = doc.pages[0]

text = await page.aextract_text()
tables = await page.aextract_tables()
md = await page.ato_markdown()

FastAPI example

from fastapi import FastAPI, UploadFile
from fastapi.responses import Response
import paperjam

app = FastAPI()

@app.post("/extract-text")
async def extract_text(file: UploadFile):
data = await file.read()
doc = await paperjam.aopen(data)
text = await doc.pages[0].aextract_text()
return {"text": text}

@app.post("/render-page")
async def render_page(file: UploadFile, page: int = 1, dpi: int = 150):
data = await file.read()
doc = await paperjam.aopen(data)
img = await doc.arender_page(page, dpi=dpi)
return Response(content=img.data, media_type="image/png")

@app.post("/to-markdown")
async def to_markdown(file: UploadFile):
data = await file.read()
doc = await paperjam.aopen(data)
md = await doc.ato_markdown(layout_aware=True)
return {"markdown": md}

Concurrency example

Because each async call runs on tokio's blocking thread pool, you can process multiple documents concurrently:

import asyncio
import paperjam

async def process_file(path: str) -> str:
doc = await paperjam.aopen(path)
return await doc.ato_markdown()

async def main():
paths = [f"report_{i}.pdf" for i in range(10)]
results = await asyncio.gather(*[process_file(p) for p in paths])
for path, md in zip(paths, results):
print(f"{path}: {len(md)} chars")

asyncio.run(main())

Naming convention

All async methods use the a prefix:

SyncAsync
paperjam.open()paperjam.aopen()
paperjam.merge()paperjam.amerge()
paperjam.to_markdown()paperjam.ato_markdown()
doc.save()doc.asave()
doc.save_bytes()doc.asave_bytes()
doc.render_page()doc.arender_page()
doc.render_pages()doc.arender_pages()
doc.extract_tables()doc.aextract_tables()
doc.to_markdown()doc.ato_markdown()
doc.search()doc.asearch()
doc.diff()doc.adiff()
doc.redact_text()doc.aredact_text()
page.extract_text()page.aextract_text()
page.extract_tables()page.aextract_tables()
page.to_markdown()page.ato_markdown()