Skip to main content

ingestlib

ingestlib — self-hosted document intelligence for RAG

Self-hosted document intelligence for RAG pipelines. One library that takes a raw document — PDF, DOCX, PPTX, or a PNG/JPEG/WebP image — and produces searchable, cited, retrieval-ready chunks, plus schema-driven extraction with verified citations: the territory of LlamaParse / Reducto / Unstructured.io, running on your own stack.

from ingestlib.services import ingest, retrieve

ingest("finance-10k.pdf")            # parse → classify → split → embed → vector store
result = retrieve("what were the total revenues?")
print(result.context)                # ranked chunks, each citing doc · page · section

Documentation: langmodule.github.io/ingestlib — guides for every stage, the full configuration reference, and the API docs.

What it does

Stage What you get
Parse Layout-aware markdown per page: tables as HTML (merged cells intact), formulas as LaTeX, charts converted to data tables (estimated values marked ~, printed callouts and growth labels captured), figures extracted as PNG crops with captions and AI descriptions — every block traceable to a bounding box on the page
Classify Document-type label (invoice, research_paper, …) — open-ended, or constrained to your rules (per call or preset in rules.yaml, with page targeting) — confidence and ranked alternatives included. Works standalone with no OCR
Split Sections (pages grouped by role: methods, results, … — LLM-discovered, or your own categories via rules) containing natural chunks — boundaries follow the content, tables never split, each chunk carries a [category › section › heading] breadcrumb in its embedding_text
Extract Your Pydantic schema, filled from the document — one instance (mode="one") or every instance in a batch (mode="many", e.g. all receipts in a scanned expense bundle). Every field carries verified provenance: page + region citations checked against the parse, values grounded in the cited source text, and honest confidence — uncited or ungrounded answers are capped, hallucinated citations dropped
Ingest The whole pipeline in one call — every stage's queryable output recorded in the internal registry (Postgres), its bytes (source, page images, figure crops, whole-doc markdown) in the artifact store (S3 or a local folder), vectors upserted, deduplicated by content checksum
Retrieve Question → hybrid search (dense embeddings + lexical sparse, merged) → rerank (Jina by default; Amazon Rerank or none via reranker: in config.yaml) → hits with scores and citations, plus a prompt-ready context block
Query databases The same retrieve() call also answers from your SQL databases — natural language → read-only generated SQL behind a permission boundary (read-only role + statement allowlist + LIMIT + timeout), with verified-query overrides for answers that must be exact. Postgres, MySQL, SQLite, DuckDB, Snowflake — merged with document results

Engines: PaddleOCR-VL-1.6 (0.9B VLM, runs on your GPU) for layout + recognition, Amazon Nova 2 Lite for judgment (chart reading, review, classification, chunk boundaries), Nova multimodal embeddings, eight vector stores (Pinecone, Qdrant, SQLite, Postgres/pgvector, MongoDB, Milvus, OpenSearch, Weaviate — all hybrid dense + sparse), S3 or a local folder for artifacts (artifact_store: s3 | local). ~$0.002/page in LLM spend. An OpenAI backend (GPT-5 vision-capable chat + text-embedding-3) ships alongside Bedrock — flip llm_provider: openai / embedding_provider: openai to run the whole pipeline on it instead — or ollama to keep every LLM call on your own machine. See below.

Quickstart

1. Requirements

  • Python 3.12+ and uv
  • AWS account with Bedrock access (us-east-1): Nova 2 Lite + Nova 2 multimodal embeddings — the default provider; the OpenAI backend or a local Ollama can run the whole pipeline instead (see below)
  • Vector database — none at all by default: the sqlite connector stores vectors in a local file. Or a Pinecone account (serverless, free tier works), a Qdrant server (local docker or Qdrant Cloud), a Postgres with pgvector (RDS/Supabase/Neon or self-hosted), a MongoDB with search (Atlas any tier or 8.2+ self-managed), a Milvus (local docker or Zilliz Cloud), an OpenSearch (Amazon domain or local docker), a Weaviate (local docker or Weaviate Cloud) — each just one connection URL
  • Internal registry (Postgres) — ingestlib's own metadata store, required for the corpus path (ingest/retrieve/lifecycle). Bring it up with the bundled compose file: docker compose -f infra/docker-compose.yml --profile registry up -d, then ingestlib registry init. Standalone operations (parse/classify/split/ extract on a single document) need no registry
  • Jina AI account for reranking (free tier: 100 RPM) — the default; or set reranker: aws (Amazon Rerank, same AWS credentials) or reranker: none in config.yaml and skip Jina entirely

2. Install

uv add ingestlib               # or: pip install ingestlib

The core install covers the full default stack (including sqlite vectors). Server-backed vector stores are extras — add the one you'll use:

uv add "ingestlib[qdrant]"     # pinecone · qdrant · pgvector · mongodb
                               # · milvus · opensearch · weaviate · all

Or work from source:

git clone https://github.com/LangModule/ingestlib.git
cd ingestlib
uv sync

System dependency — LibreOffice (DOCX/PPTX → PDF conversion):

brew install --cask libreoffice          # macOS (binary is `soffice`)
sudo apt install libreoffice-core libreoffice-writer libreoffice-impress   # Linux

Optional — PostgreSQL client tools (pg_dump/pg_restore), only for ingestlib registry backup/restore:

brew install libpq                       # macOS
sudo apt install postgresql-client       # Linux

3. Start the OCR inference server

Parse runs PaddleOCR-VL-1.6 behind an inference server. First launch downloads ~1.8 GB of weights; later launches load from cache in seconds.

# Apple Silicon (Metal GPU)
uv run python -m mlx_vlm.server --port 8111 --model PaddlePaddle/PaddleOCR-VL-1.6

# NVIDIA (then set paddle_vl.backend: vllm-server in config.yaml)
vllm serve PaddlePaddle/PaddleOCR-VL-1.6 --port 8111

The layout model (PP-DocLayoutV3, ~126 MB) auto-downloads on the first parse.

4. Configure

ingestlib init scaffolds the setup files into the current directory:

uv run ingestlib init            # default stack: Bedrock + sqlite + Jina → config.yaml + .env
uv run ingestlib init --local    # zero-cloud: Ollama + sqlite + local artifacts — no keys at all

For the default stack, add Bedrock-enabled credentials:

aws configure --profile your-aws-profile

Edit config.yaml: pick your providers, vector store, reranker, and artifact store — everything else has working defaults (vector_store: sqlite needs no server and no keys) — and fill .env with the keys your choices need (Jina for the default reranker; --local needs none). The aws section is required only while a choice uses AWS (the default bedrock provider, s3 artifacts, the aws reranker, an Amazon OpenSearch domain) — delete it otherwise and the config loader will tell you if something still needs it. The S3 bucket (default ingestlib-{account_id}) and the vector indexes/collections are created automatically on first use — no manual setup. Prefer no cloud storage at all? artifact_store: local keeps every source file, page render, and figure crop in a plain folder beside your config.yaml — browsable in a file manager, and moving the artifact store between backends is a plain copy.

Config is discovered at call time, never at import: INGESTLIB_CONFIG=/path/to/config.yaml wins, otherwise the working directory and its parents are searched — so installed usage works the same as running inside this repo.

5. Verify

uv run ingestlib doctor

Doctor checks every configured choice with real calls — config discovery, LibreOffice, the OCR server, an LLM ping, an embedding (with its dimension), the reranker, the artifact store, the vector store, and the registry (reachable and migrated). Every failed line prints the one-sentence fix (wrong AWS profile → your available profiles, missing key → where to get one, model not pulled → the exact ollama pull).

6. Run

from ingestlib.services import ingest, retrieve

r = ingest("report.pdf")
print(r.status, r.category, r.chunks, r.durations)

res = retrieve("what does the report conclude?", top_k=5)
for hit in res.hits:
    print(hit.rerank_score, hit.citation, hit.chunk.heading)

Manage a corpus

Real corpora change. Re-ingesting an edited file replaces the old version (its vectors and artifacts are deleted — no stale chunks), a rename is a cheap move, and sync() reconciles a whole folder in one call:

from ingestlib.services import ingest, sync, remove, reindex

ingest("report.pdf")                       # edited file → status="replaced"
sync("corpus/", prune=True)                # add new, replace changed, drop deleted
remove("old.pdf")                          # erase one doc from both stores
reindex()                                  # rebuild the vector store from the registry

The same verbs are on the CLI — a corpus is managed from the shell, no Python needed:

ingestlib ingest report.pdf docs/          # files or folders
ingestlib sync corpus/ --prune --dry-run   # preview, then drop --dry-run
ingestlib list                             # every stored document
ingestlib remove report.pdf                # erase one
ingestlib reindex                          # rebuild the index (provider switch, new store)
ingestlib recollect                        # re-sort the corpus after editing classify rules
ingestlib search "what were the risks?"    # cited retrieval from the shell

Documents carry a logical identity (namespace + source path), so lifecycle knows a re-ingest from a brand-new file. Full guide: Manage a corpus.

Query your databases

retrieve() can also answer from your SQL databases, alongside the document corpus, in the same call. Declare the databases in a sources.yaml sidecar (beside config.yaml, like rules.yaml — see sources.example.yaml) and pass their names:

from ingestlib.services import retrieve

# documents AND databases behind one question
result = retrieve("how many prescriptions are ready?", sources=["prescriptions"])
for r in result.results:
    print(r.source_type, r.content, r.provenance["sql"])   # the exact query that ran

The model generates read-only SQL from your schema + tables hints, bounded by a permission boundary whose floor is a read-only database role — so a wrong query is a wrong read, never a write. A statement allowlist, an injected LIMIT, and a timeout are defense in depth; verified queries let you pin reviewed SQL for answers that must be exact. On a wide schema ingestlib retrieves only the tables a question needs — plus their foreign-key bridges — instead of dumping every table into the prompt (schema_rag: auto). Generate hints for a cryptic schema with ingestlib describe-schema, and measure generation accuracy on your own schema with ingestlib eval-sql. Each SQL backend needs its pip extra (ingestlib[postgres] · [mysql] · [duckdb] · [snowflake]; sqlite needs none). From the shell: ingestlib search "…" --sources prescriptions. Full guide: Query databases (SQL).

Serve it to agents (MCP)

ingestlib mcp exposes the whole loop — search, extract, ingest, sync, remove, reindex, recollect, verify, and registry management — as MCP tools, so Claude Desktop, Cursor, or any agent can drive your self-hosted corpus with citations, nothing leaving your machine.

pip install "ingestlib[mcp]"
ingestlib mcp                                # stdio (local clients)
ingestlib mcp --transport http --port 8000   # remote; needs MCP_TOKEN (bearer auth)

Read tools (search/extract/classify/list/doctor) are always on; the write tools hide under --read-only. Guide: Serve to agents (MCP).

Using the operations directly

Every operation also works standalone:

from ingestlib.operations import parse, classify, split

result = parse("report.pdf")            # ParseResult: pages, regions, figures
print(result.markdown)                  # whole-document markdown
result.save_images("out/")              # extracted figures/charts as PNGs

label = classify("report.pdf")          # no OCR needed — native text + embedded images
chunks = split(result, category=label.category)
for c in chunks.chunks:
    print(c.token_estimate, c.embedding_text.splitlines()[0])

And the fourth operation pulls structured data out — your schema, filled and cited:

from pydantic import BaseModel
from ingestlib.operations import extract

class Receipt(BaseModel):
    merchant: str
    total: float
    currency: str

report = extract(parse("expenses.pdf"), schema=Receipt, mode="many")
for item in report.items:
    print(item.value.merchant, item.value.total, item.citation)
# BART 20.0 p.10  ·  Hilton 214.6 p.2  ·  …
print(report.items[0].fields["total"].grounded)   # True — verified in the cited region

mode="one" fills a single instance from the whole document (a 10-K's headline financials); mode="many" finds every instance across the pages. A ParseResult input gives region-level citations on scans; a raw path reads the native text layer with page-level citations and no OCR server. Confidence is honest: a field whose citation doesn't check out is capped, and a value not found in its cited text is flagged grounded=False.

A stored document reads back explicitly — queryable metadata from the registry, bytes from the blob store:

from ingestlib.services import get_document

doc = get_document(doc_id)              # the stored document, from the registry
print(doc.category, doc.chunk_count)    # queryable metadata
doc.markdown()                          # whole-document markdown (blob store)
[e["schema_name"] for e in doc.extractions]   # any persisted extractions

Classification & split rules

Classify and split are open-ended by default — the LLM decides the document type and discovers the section vocabulary. Both can instead follow your rules: pass them per call, or preset them once in rules.yaml beside your config.yaml, and every bare call and the whole ingest pipeline uses them automatically:

classify("doc.pdf",
         {"invoice": "Itemized charges, tax info, and payment terms",
          "sec_filing": "10-K/10-Q style regulatory filings"},
         target_pages="1,3,5-7", max_pages=5)   # read only these pages
split("report.pdf",
      vocabulary={"financial_statements": "Balance sheets, income statements",
                  "notes": "Footnotes and disclosures"},
      unmatched="other")   # pages fitting nothing: other (default) | require | skip
# rules.yaml — beside your config.yaml; infra stays in config.yaml,
# what your documents MEAN lives here
classify:
  max_pages: 5
  rules:                       # up to 20 — result is one of these or "uncategorized"
    invoice: "Itemized charges, tax info, and payment terms"
    sec_filing: "10-K/10-Q style regulatory filings"
split:
  unmatched: other             # require | other | skip
  categories:                  # up to 50 — YOUR sections; Pass 1 is skipped
    financial_statements: "Balance sheets, income statements, cash flows"
    notes: "Footnotes and disclosures"

Classify returns one of your labels or "uncategorized", with confidence, reasoning, and ranked alternatives. Split labels every page against your sections — unmatched pages become an honest other section (default), get forced into the nearest category (require), or are dropped entirely (skip). Precedence everywhere: explicit arguments beat the preset, and {} forces the open-ended default even when a preset exists.

OpenAI backend

The same LLM surface Bedrock provides is also available on OpenAI — GPT-5 chat with vision, thinking mode, schema-enforced structured output, and text-embedding-3 embeddings. Add OPENAI_API_KEY to .env and pick models in config.yaml's openai: section (defaults: gpt-5-mini, text-embedding-3-small).

To run the whole ingest/retrieve pipeline on it, switch the providers in config.yaml — every LLM and embedding call routes accordingly:

llm_provider: openai          # chart reading, review, classify, chunking
embedding_provider: openai    # chunk + query embeddings

Combined with artifact_store: local, vector_store: sqlite, and reranker: jina (or none), the pipeline needs no AWS at all. Two rules: switching embedding_provider changes the vector space, so re-ingest afterward (skip_existing=False) — vectors from different embedding models never mix in one index. And text embeddings only: OpenAI has no image-embedding model.

The backend is also importable directly, ignoring the config switch:

from ingestlib.foundations.llm import Image
from ingestlib.foundations.llm.openai import chat, chat_structured, embed_text

chat("Read this chart", images=[Image(png_bytes, "png")])   # vision works
embed_text("a chunk of text")                               # 1024-dim default

Local backend (Ollama)

The third provider keeps every LLM call on your machine — the fully air-gapped pipeline. Point config.yaml at a local Ollama (or any OpenAI-compatible server: vLLM, LM Studio):

llm_provider: ollama
embedding_provider: ollama

ollama:                                    # these are the defaults
  base_url: http://localhost:11434/v1
  llm_model_id: qwen3.5:9b
  embedding_model_id: qwen3-embedding:0.6b
ollama pull qwen3.5:9b
ollama pull qwen3-embedding:0.6b

No API key. Vision, schema-enforced structured output, and 1024-dim embeddings all verified on the reference stack — and local embedding quality is measured, not assumed: on the retrieval eval, qwen3-embedding matched or beat the cloud stack (perfect reranked hit@1/3/5 = 1.00). Two honest notes: use the GGUF builds, not -mlx (Ollama's MLX engine silently drops images and schema enforcement), and a local 9B won't match the cloud models on dense charts — test with your own documents before committing. Combined with artifact_store: local and vector_store: sqlite, nothing leaves your machine but the optional Jina rerank call (reranker: none closes even that).

Architecture

src/ingestlib/
├── services/       ingest · retrieve · lifecycle (remove · sync · reindex · recollect) · verify — the product
├── operations/     parse · classify · split · extract — the tools (each standalone)
├── storage/        registry (Postgres metadata hub) · artifacts (S3 | local, bytes only) · base (VectorStore contract) · 8 connectors
│                   (pinecone · qdrant · sqlite · pgvector · mongodb · milvus
│                    · opensearch · weaviate)
├── sources/        structured retrieval — SQL databases & the corpus as queryable Sources
├── foundations/    llm (Bedrock Nova · OpenAI GPT-5 · Ollama Qwen · Jina) · ocr (PaddleOCR-VL)
├── cli/            the `ingestlib` command — init · doctor · ingest · sync · list · show · collections · remove · reindex · recollect · verify · search · describe-schema · eval-sql · registry · mcp
├── mcp/            MCP server (ingestlib[mcp]) — expose the pipeline to agents
├── utils/          logger · files · sync · aws
└── config.py       config.yaml + .env + rules.yaml + sources.yaml → typed configs

src/ingestlib_registry/   the registry's Postgres schema + Alembic migrations (a standalone package ingestlib depends on)

Strict downward dependencies. The VectorStore contract means backends drop in as connectors — all eight ship hybrid search: Pinecone (dense + hosted sparse model, merged client-side), Qdrant (dense + BM25 with server-side IDF and RRF fusion; local docker or cloud), SQLite (sqlite-vec KNN + built-in FTS5 BM25 with porter stemming, RRF fusion — one local file, no server, no keys), Postgres/pgvector (HNSW cosine + built-in full-text over a generated weighted tsvector, RRF fusion — the extension and table bootstrap automatically), MongoDB (Atlas Vector Search + Atlas Search true BM25, RRF fusion — Atlas any tier or self-managed 8.2+; both search indexes bootstrap automatically), Milvus (dense ANN + server-computed BM25 sparse, fused server-side with RRF — local docker or Zilliz Cloud), OpenSearch (faiss HNSW k-NN + Lucene BM25, RRF fused client-side — an Amazon OpenSearch domain SigV4-signed with your aws profile, or local docker), and Weaviate (HNSW dense + native BM25 fused server-side in one hybrid call — local docker or Weaviate Cloud). Pick one with vector_store: pinecone | qdrant | sqlite | pgvector | mongodb | milvus | opensearch | weaviate in config.yaml. Connection secrets sit in .env together (sqlite needs none) — only the selected connector ever builds a client.

Logging

INGESTLIB_LOG_LEVEL=INFO           # DEBUG | INFO | WARNING | ERROR (default INFO)
INGESTLIB_LOG_THIRD_PARTY=1        # also show paddlex/httpx/botocore chatter
INGESTLIB_LOG_COLOR=0              # disable colored output

Testing

Tests hit real APIs, never mocks. Pure logic runs always; server-hitting suites are opt-in via env gates. The sqlite connector's full suite runs ungated in make test — there is no server, so in-process IS the real thing.

make test                  # fast suite (~630 tests, ~2min; e2e groups skip)
make test-openai           # OpenAI backend       (skips without OPENAI_API_KEY)
make test-ollama           # Ollama backend       (needs a local Ollama + models)
make test-parse            # parse e2e            (needs VL server + LLM provider)
make test-classify         # classify e2e         (needs the LLM provider)
make test-split            # split e2e            (needs the LLM provider)
make test-extract          # extract e2e          (needs the LLM provider; scans need the VL server)
make test-s3               # artifact store e2e   (needs AWS)
make test-pinecone         # vector connector e2e (needs Pinecone + embeddings)
make test-qdrant           # vector connector e2e (needs a Qdrant server + embeddings)
make test-sqlite           # vector connector suite (no gate — nothing to need)
make test-pgvector         # vector connector e2e (needs Postgres at PGVECTOR_URL)
make test-mongodb          # vector connector e2e (needs MongoDB at MONGODB_URL)
make test-milvus           # vector connector e2e (needs Milvus at MILVUS_URL)
make test-opensearch       # vector connector e2e (needs OpenSearch at OPENSEARCH_URL)
make test-weaviate         # vector connector e2e (needs Weaviate at WEAVIATE_URL)
make test-services         # full product e2e     (needs the entire stack)
make test-sources          # structured retrieval — SQL sources (deterministic; e2e gated)
make test-cli              # CLI: init/doctor + corpus commands (no gate)
make test-lifecycle        # remove/sync/reindex/recollect + replace-aware ingest (gates on the registry)
make test-mcp              # MCP server: tools, read_only, http auth (no gate)
make test-all              # everything
make eval                  # retrieval quality eval (see below)
make docs                  # live-preview the documentation site

Fixtures live in tests/data/ — 15 real PDFs (research papers, earnings decks, insurance forms, 10-Ks, a 16-page receipt scan, a password-protected one), document images incl. WebP, and real DOCX/PPTX files. The server-backed connector suites need their servers: docker compose -f infra/docker-compose.yml --profile all up -d starts every local store (or --profile qdrant etc. for just one).

Retrieval quality

Beyond pass/fail tests, evals/ measures retrieval quality: 22 ground-truth questions over the fixture corpus, run through the real retrieve() flow under dense/hybrid × rerank on/off, scored by hit@k and MRR. Measured so far (consistent across all eight connectors): with reranking, every answer lands in the top 3 hits (hit@3 = 1.00); hit@1 ranges 0.86–1.00 across runs. The fully-local stack holds the same bar — Ollama embeddings scored a perfect 1.00 across hit@1/3/5 and MRR on dense+rerank. Each run saves a timestamped snapshot to evals/results/, so quality changes are visible over time.

Disk footprint

Component Size Location
Python deps ~1.4 GB core (+ your vector-store extra) .venv/
PaddleOCR-VL-1.6 weights ~1.8 GB ~/.cache/huggingface/hub/
PP-DocLayoutV3 ~126 MB ~/.paddlex/official_models/
LibreOffice ~600 MB system

Scope

English documents; PDF / DOCX / PPTX / PNG / JPEG / WebP input. Images, charts, and tables inside documents are fully extracted and interpreted, and a single image file parses as a one-page document; handwriting is out of scope by design.

The studio

ingestlib-studio is the visual companion: a local web UI with a setup wizard, try-before-you-commit pipeline runs, page-by-page review with hover-to-highlight bounding boxes, committed ingestion with live progress, a content-rules editor, and a retrieval playground where every answer points to its source on the page.

Roadmap

  • XLSX input (tables-first, not a PDF conversion)

Recently shipped: the internal registry — a Postgres metadata hub that makes the whole corpus queryable, with reindex/recollect/verify for rebuilding and auditing it and event-driven backups (v1.5); schema-RAG for wide databases — retrieve the relevant tables (with foreign-key closure) instead of dumping the whole schema, plus describe-schema auto-documentation and the eval-sql accuracy harness (v1.4); structured retrieval — query your SQL databases alongside documents through one retrieve() call, behind a read-only permission boundary (v1.3); an MCP server to serve the corpus to agents (v1.2); document lifecycle — replace-aware ingestion, folder sync(), reindex(), and the corpus CLI (v1.1).

License

See LICENSE.

Release files for ingestlib 1.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ingestlib 1.5.0
File Size Uploaded
ingestlib-1.5.0.tar.gz 14.5 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for ingestlib 1.5.0
File Interpreter ABI Platform
ingestlib-1.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 14.8 MB

Release files / ingestlib-1.5.0.tar.gz

Download URL ingestlib-1.5.0.tar.gz
Size 14.5 MB
Tags Source
SHA-256 checksum
How to use checksums
e9cafdbb941ded89a109fae2b26c8c9db6527c0933e38b1a731cd2d3a5c7a337
BLAKE2b-256 checksum
How to use checksums
b921aeab5e092e9552527af0c9a5e31d85e2bd46ebbcff8d85cc7f75a798ba31
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / ingestlib-1.5.0-py3-none-any.whl

Download URL ingestlib-1.5.0-py3-none-any.whl
Size 272.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7058dde6a65e041ace0274e9efc87edd4784d9656ed26ae16f09a578dd5ae935
BLAKE2b-256 checksum
How to use checksums
cfeeb698e42f74fe9fd663c23dd7fcb5b14870f475c01f4a9848be1af68e77fb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

1.5.0 This release

2 release files

1.4.0

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release 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