[build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] name = "pdfmux" version = "1.8.7" description = "Self-healing PDF extraction for RAG — #2 of 8 engines on opendataloader-bench (0.903). Flags pages it can't read instead of dropping them, and certifies any extractor's output for silent drops. Verify signed manifests offline, free. Per-page confidence, 7-tool MCP server, LangChain/LlamaIndex loaders. LlamaParse alternative." readme = "README.md" license = "MIT" requires-python = ">=3.11" authors = [ { name = "Nameet Potnis" }, ] keywords = [ "pdf", "pdf-to-markdown", "pdf-extraction", "rag", "llm", "langchain", "llamaindex", "mcp", "ocr", "confidence-scoring", "self-healing", "llamaparse-alternative", "document-ai", "pymupdf-alternative", "docling-alternative", "table-extraction", "structured-extraction", "ai-document-processing", "pdf-parser", "scanned-pdf", "document-ingestion", "rag-pipeline", "claude-desktop", ] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Text Processing :: Markup :: Markdown", "Topic :: Scientific/Engineering :: Artificial Intelligence", "Topic :: Office/Business", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed", "Environment :: Console", ] dependencies = [ "typer>=0.9.0", # UPPER BOUNDS ARE LOAD-BEARING — do not relax without running # tests/test_dependency_regression.py against the new resolve. # pymupdf4llm renumbered 0.3.4 -> 1.28.0 to track pymupdf. On 1.28.0 # to_markdown() returns headings only and drops body prose entirely: # eval/fixtures/good-digital-00.pdf yields 0 chars where 0.3.4 yields 218 # (raw fitz sees 214). That silently guts every extraction pdfmux makes — # the exact failure mode this product exists to catch. Found 2026-07-20. "pymupdf>=1.24.0,<1.29", # garbage filter handles 1.27 find_tables() regression "pymupdf4llm>=0.0.10,<1.0", "rich>=13.0.0", "python-bidi>=0.6.0", # BiDi algorithm for Arabic/Hebrew RTL text reordering "cryptography>=42.0", # offline Ed25519 verification of Cloud signed manifests (pdfmux verify-manifest) ] [project.optional-dependencies] # mcp LOWER bound is load-bearing: mcp 2.0.0 deleted mcp.server.fastmcp and # replaced FastMCP with mcp.server.mcpserver.MCPServer. mcp_server.py and # mcp_extract.py now import the 2.x path, which does not exist in 1.x — so # >=2.0.0 is a floor, not a preference. Do NOT re-add an upper bound to dodge a # future bump (that is what commit 6a4f78a did); port the import surface instead. serve = ["mcp>=2.0.0", "uvicorn>=0.30.0"] tables = ["docling>=2.0.0"] opendataloader = ["opendataloader-pdf>=2.0.0"] ocr = ["rapidocr>=3.0.0", "onnxruntime>=1.19.0"] ocr-heavy = ["surya-ocr>=0.6.0"] marker = ["marker-pdf>=1.0.0"] llm = ["google-genai>=1.0.0"] llm-claude = ["anthropic>=0.40.0"] llm-openai = ["openai>=1.0.0"] llm-ollama = ["ollama>=0.4.0"] llm-mistral = ["mistralai>=2.7.1"] llm-all = ["pdfmux[llm,llm-claude,llm-openai,llm-ollama,llm-mistral]"] langchain = ["langchain-core>=0.2.0"] llamaindex = ["llama-index-core>=0.10.0"] watch = ["watchdog>=3.0.0"] all = ["pdfmux[serve,tables,ocr,opendataloader,marker,llm-all,langchain,llamaindex,watch]"] dev = [ "pytest>=8.0.0", "pytest-asyncio>=0.23.0", "ruff>=0.15.22", # tests/test_profiles.py exercises profile save/load, which needs PyYAML. # It used to arrive transitively and the suite passed by luck; pinning the # PDF stack removed that accident. Declared here so the profiles feature is # actually covered rather than skipped by an environment quirk. "pyyaml>=6.0", ] [project.scripts] pdfmux = "pdfmux.cli:app" pdfmux-extract = "pdfmux.mcp_extract:run_server" [project.urls] Homepage = "https://pdfmux.com" Documentation = "https://github.com/NameetP/pdfmux#readme" Repository = "https://github.com/NameetP/pdfmux" Issues = "https://github.com/NameetP/pdfmux/issues" Changelog = "https://github.com/NameetP/pdfmux/blob/main/CHANGELOG.md" [tool.hatch.build.targets.wheel] packages = ["src/pdfmux"] [tool.ruff] target-version = "py311" line-length = 100 [tool.ruff.lint] select = ["E", "F", "I", "N", "W", "UP"] [tool.ruff.lint.per-file-ignores] "tests/**" = ["E501", "F401", "I001", "N818"] "src/pdfmux/mcp_server.py" = ["E501"] # MCP tool docstrings are user-facing "src/pdfmux/cli.py" = ["E501"] # CLI help text is user-facing "src/pdfmux/headings.py" = ["E501"] # regex patterns for date matching "src/pdfmux/image_table_ocr.py" = ["E741"] # short loop variables "eval/ab_models.py" = ["E501"] # report tables + example commands are presentation strings [tool.pytest.ini_options] testpaths = ["tests"] asyncio_mode = "auto"