Skip to main content

PyStreamPDF

PyPI CI Python 3.9+ License: Apache 2.0

Token-efficient PDF extraction for RAG: parse PDF structure and split content into token-budget-aware chunks.

What it is

PyStreamPDF combines a Rust/PDFium-backed core for real PDF parsing with a Python layer for semantic chunking, token-budget-aware caching, and OCR/validation. Instead of sending an entire document to a model, it parses PDF structure and splits content into semantic, token-budget-aware chunks so you can send only the relevant parts. It ships as pystreampdf on PyPI and imports as pystreampdf.

Features / Highlights

  • Semantic chunking — SemanticChunker splits text into token-aware chunks by element type rather than naive word-count splitting.
  • Token-budget system — TokenBudgetConfig / BudgetRule scale allocation within a fixed 500–1000 token range using keyword/field-based multipliers.
  • Dual-tier cache — PDFCache: L1 memory + L2 disk, HMAC-SHA256-signed on disk with owner-only (0600) permissions; a tampered cache file fails verification before any deserialization.
  • Real PDF security handling — pystreampdf._core.PyPdfDocument is backed by PDFium's actual encryption/permission APIs (is_encrypted(), permissions(), open_with_password()); a wrong or missing password fails closed.
  • Honest failure signaling — unparseable PDFs raise a real error; no fabricated placeholder content.
  • OCR and validation pipelines — ocr/ and validation/ are real implementations with dedicated test coverage.
  • Exact or heuristic token counts — uses len(text) / 4 by default (clearly labeled approximate); install the optional tiktoken extra for real BPE (cl100k_base) counts.
  • Optional MCP/DAB connector — exposes document-processing tools over MCP, binds to 127.0.0.1 only, read-only by default.

Who it's for / Use cases

  • RAG pipeline authors who want to reduce the volume of text sent per query without losing the parts that matter.
  • Token-cost-conscious teams that need to budget tokens per document type.
  • Document-processing services that need real encryption/permission detection and a cache that fails closed on tampering.
  • Agent/tool builders who want PDF extraction surfaced over MCP locally.

What it is not

  • There is no Document class and no .extract_relevant() / .token_savings API. That class never existed in the source. The real exported API is SemanticChunker, PDFCache, TokenBudgetConfig, and the Rust-backed pystreampdf.open().
  • No committed token-savings benchmark. Earlier "70% reduction" / "10-50x" claims were never backed by a checked-in benchmark and have been retracted. Actual savings depend on your documents and how narrowly you scope queries.
  • Prebuilt wheels are macOS arm64 only. A source distribution ships as of 2.3.0, so other platforms build from source, but that still requires a local Rust toolchain and a discoverable libpdfium (see scripts/download_pdfium.sh).
  • OCR/layout fixtures are generated at test time, not checked in. tests/ now renders real two-column, mildly rotated/skewed, and Times-Roman (22 pt) pages, rasterizes them with PyMuPDF at ~150 dpi, and embeds the images as text-free scanned PDFs (tests/test_scanned_and_span_fixtures.py). The OCR assertions run against real Tesseract output and skip when Tesseract is unavailable; they are not mocked. A reportlab table-with-spans fixture covers merged-cell text extraction. This is fixture coverage, not an OCR quality benchmark.
  • Cross-project integration is limited but real. The MCP connector imports the canonical BaseMCPConnector from statguardian._mcp_connector when StatGuardian is installed (with a local fallback for older installs), verified by tests/test_mcp_shared_base.py. No other StatGuardian, PyInferenceManager, or PyStreamMCP integration is demonstrated here.
  • The Rust-backed pystreampdf.open() / load_index() silently becomes None if the compiled _core extension is unavailable (e.g. a from-source install without maturin develop), falling back to the pure-Python path rather than erroring.
  • Remote MCP exposure is bearer-token protected and supports TLS/mTLS. The connector binds to 127.0.0.1 with read-only permissions by default. allow_remote=True requires a non-empty auth_token; DAB stays on loopback and a stdlib reverse proxy enforces Authorization: Bearer <token> plus a per-IP rate limit before forwarding. Pass both tls_cert and tls_key to serve HTTPS directly from the proxy (stdlib ssl.SSLContext, TLS 1.2+); without them traffic is plaintext HTTP. Set client_ca (with TLS) to require and verify a client certificate (ssl.CERT_REQUIRED). Request bodies over max_body_bytes (default 10 MiB) get a 413, and the proxy writes a basic access log (pystreampdf._mcp_connector.access: method/path/status/remote, no auth headers or query strings). Rate-limit buckets are in-memory per process and reset on restart; there is no persistence or cross-worker sharing. For untrusted networks a hardened reverse proxy is still recommended. Request and response bodies are streamed in chunks rather than fully buffered; chunked request bodies are rejected with 501 (see SECURITY.md).

Project status

Beta. Current version 2.3.0 (Development Status :: 4 - Beta). The full pytest tests/ suite on this change passes locally — 600 passed / 4 skipped (optional-dependency skips), and the last confirmed-green CI run reported cargo test — 23 passed / 0 failed across Python 3.10/3.11/3.12. See docs/PRODUCT_VISION.md for what is real vs. partial.

Installation

pip install pystreampdf

# With exact token counting (tiktoken)
pip install "pystreampdf[tiktoken]"

Requires Python 3.9+.

Optional integrations

Every integration below is optional: the base install and the test suite run without it, and the relevant tests skip (or assert the actionable error) rather than fail when it is missing.

  • PyMuPDF (pip install pymupdf) — rasterizes scanned pages for the OCR pipeline (pystreampdf.ocr.pipeline). Without it, scanned-page OCR raises a clear ImportError with the install hint.
  • tiktoken (pip install "pystreampdf[tiktoken]") — real BPE (cl100k_base) token counts; falls back to the len(text) / 4 heuristic.
  • pytest-asyncio — a dev/test extra only (pip install "pystreampdf[dev]"), required to run the tests/test_mcp_tools.py async tests.
  • Tesseract + pytesseract + Pillow — real OCR for scanned PDFs (brew install tesseract, pip install pytesseract pillow). Or use the OS package manager for Tesseract; the OCR tests run against generated rasterized fixtures and skip with an explicit reason when the binary/package is missing.

Optional high-fidelity extraction (Docling)

The native Rust/PDFium path is the default. For complex layouts, tables or scanned pages, install the MIT-licensed Docling backend and use it as an extraction source while keeping PyStreamPDF's chunk and budget types:

pip install "pystreampdf[docling]"
from pystreampdf import extract_with_docling

result = extract_with_docling("report.pdf", target_tokens=500)
for chunk in result.chunks:
    print(chunk.chunk_type, chunk.page_start, chunk.estimated_tokens)

Docling downloads its own layout/OCR models on first use. It is optional and is not part of the base install.

Quick start

from pystreampdf import SemanticChunker, ElementType

with open("financial_report.pdf", "rb") as f:
    text = f.read().decode("latin-1", errors="ignore")  # or use a real PDF text extractor first

chunker = SemanticChunker(target_tokens=500)
chunks = chunker.chunk_content(
    text, element_type=ElementType.TEXT, page_start=1, page_end=1
)
print(f"Split into {len(chunks)} semantic chunks")

Usage

Budget-aware caching and chunking:

from pystreampdf import SemanticChunker, PDFCache, TokenBudgetConfig, BudgetRule, ElementType

budget_config = TokenBudgetConfig(
    base_budget=800,
    rules=[BudgetRule("complex", 1.1, match_fields=["filename"])]
)

cache = PDFCache(
    memory_limit_mb=500,
    disk_cache_dir="./pdf_cache",
    token_budget_config=budget_config,
)

def extract_pdf(pdf_path):
    chunks, preview, title, pages = cache.get_or_process(pdf_path, process_fn=extract_chunks)
    return chunks

chunker = SemanticChunker(target_tokens=500)
chunks = chunker.chunk_content(text, element_type=ElementType.TEXT, page_start=1, page_end=1)

Exact vs. heuristic token counts. By default counts use len(text) / 4. With the tiktoken extra installed, pystreampdf.tokenizer.is_exact() returns True and cl100k_base is used; check pystreampdf.tokenizer.TOKENIZER_MODE to see which mode produced a given number.

Rust-backed document API (examples/basic_parse.py) — document/page/structure inspection, requires the compiled _core extension:

import pystreampdf
doc = pystreampdf.open("report.pdf")  # None if _core isn't available

MCP connector — optional, local by default, read-only:

# see examples/mcp_pystreampdf.py

Documentation

Run the tests with:

make test          # cargo test && pytest tests/

Known issues and deferred work

Blocked / failed:

  • Linux/Windows wheels, Docling/PyMuPDF benchmark.

Rejected / deferred:

  • A full PDF RAG platform; Docling owns layout understanding.
  • Persisting/sharing rate-limit buckets across restarts or workers (the proxy keeps them in memory by design; use an external limiter for multi-process deployments).

Full shipped/blocked/rejected detail: QUEUE_STATUS.md.

Contributing

Contributions are welcome. See CONTRIBUTING.md and CODE_OF_CONDUCT.md. Security issues: see SECURITY.md for the reporting process and the cache/MCP security model.

License

This project is licensed under the Apache License 2.0.

Metadata

Release files for PyStreamPDF 2.4.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 PyStreamPDF 2.4.0
File Size Uploaded Provenance
pystreampdf-2.4.0.tar.gz 127.0 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for PyStreamPDF 2.4.0
File Interpreter ABI Platform
pystreampdf-2.4.0-cp312-cp312-macosx_11_0_arm64.whl CPython 3.12 CPython 3.12 macOS 11.0+ ARM64 Details

Total release size: 1.7 MB

Release files / pystreampdf-2.4.0.tar.gz

Download URL pystreampdf-2.4.0.tar.gz
Size 127.0 kB
Tags Source
SHA-256 checksum
How to use checksums
7948c9314dacbd7fdb563f14c500465dfc90deca0c565725cc6b84da798bc252
BLAKE2b-256 checksum
How to use checksums
45ed99eaf7e32cd0714e49eacdf6593d896de70bcb3ac720c60f732ea63a7c74
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Release files / pystreampdf-2.4.0-cp312-cp312-macosx_11_0_arm64.whl

Download URL pystreampdf-2.4.0-cp312-cp312-macosx_11_0_arm64.whl
Size 1.6 MB
Tags CPython 3.12 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
d6a767e1d1004f5fbeb0d080ee78d5fb02f3082f1128b44145e1ba4e9b5bf4ec
BLAKE2b-256 checksum
How to use checksums
6bf0264d02d24abeb4f8b2c52c299830c5cc86d5412e2cb1d663e73709cfdf05
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Security for PyStreamPDF 2.4.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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