Datagrunt 3.0.0: Now With PDF Parsing

May 31, 2026by Martin Graham

Datagrunt started life as a tool for taming messy CSV files. With the 3.0.0 release, it takes on the other format that shows up in nearly every data project and causes nearly as many headaches: the PDF.

PDFs are everywhere — invoices, reports, bank statements, scanned forms, engineering drawings — and getting clean, structured data out of them usually means stitching together a handful of libraries and a lot of glue code. Datagrunt 3.0.0 brings PDF parsing under the same roof as its CSV tooling, with the same goal: hand you structured, ready-to-use data with as little ceremony as possible.

You might wonder: why not just hand the whole PDF to an LLM and let it pull the data out? It’s a fair question, and one we tested heavily. The answer that kept winning was to extract the text and images first, and then let an LLM post-process that structured output — reading the parsed JSON and working on the extracted image files outside the PDF. Splitting the document into clean parts up front, before any model gets involved, proved significantly more accurate and reliable than asking a model to parse the raw PDF in one shot. Datagrunt handles that crucial first step so your LLM can focus on what it does best.

What’s new in 3.0.0

  • Two new classes, PDFReader and PDFWriter — the PDF counterparts to CSVReader and CSVWriter.
  • Text, tables, and images in a single pass — every page is parsed into one unified document model.
  • DataFrames and dicts out of the box — get a Polars DataFrame, a PyArrow table, or plain Python dictionaries.
  • OCR for scanned pages — image-only PDFs are read with Tesseract; native-text PDFs need no OCR at all.
  • Automatic image de-duplication — repeated logos and page backgrounds collapse to a single file on disk.
  • Optional layout-table filtering — drop the decorative boxes that line-based detection sometimes mistakes for tables.

PDF support ships as an optional extra, so CSV-only installs stay lean:

uv pip install "datagrunt[pdf]"
# or
pip install "datagrunt[pdf]"

Reading a PDF

PDFReader mirrors the CSVReader API you already know. Point it at a file and convert to whatever shape you need:

from datagrunt import PDFReader

reader = PDFReader('report.pdf')

# The full, structured document: {"document": {"pages": [...]}}
document = reader.to_dicts()

# Or flatten every extracted element into a Polars DataFrame —
# one row per text block, table, or image
df = reader.to_dataframe()
print(df.head())

Each page is broken down into its elements — text blocks, tables, and images — so you can work with the parts you care about instead of one undifferentiated blob of text. Want a quick look before parsing the whole file? reader.get_sample() returns just the first page.

Writing JSON and extracting images

When you want results on disk rather than in memory, PDFWriter handles it:

from datagrunt import PDFWriter

writer = PDFWriter('report.pdf')

# Write the structured document to JSON and drop embedded
# images into their own directory
writer.write_json('report.json', image_output_dir='report_images')

# Or pull just the images out
writer.extract_images(output_dir='report_images')

When images are written to disk, Datagrunt automatically collapses byte-identical duplicates — that header logo repeated on every page becomes a single file, with every reference pointed at it. Prefer to keep every copy? Pass dedupe_images=False (or dedupe=False to extract_images). And if you’d rather have one element per line for streaming or loading into a warehouse, write_json_newline_delimited() has you covered.

Handling messy, real-world PDFs

Two features exist specifically because real documents are rarely tidy.

Scanned PDFs — the ones that are really just images of pages — need optical character recognition. Datagrunt takes care of that automatically when you have the Tesseract binary installed (brew install tesseract on macOS, apt-get install tesseract-ocr on Debian/Ubuntu, or the UB-Mannheim build on Windows). Native-text PDFs, tables, and embedded images all work without it; Tesseract only comes into play for image-only pages.

Graphically dense PDFs — think brochures and marketing one-pagers — lean on rule lines and boxes for layout, which line-based table detection can mistake for 1×N or N×1 “tables.” If that happens, pass drop_layout_tables=True to the reader or writer to keep only genuine tables (two or more rows and columns). It’s off by default, so nothing is dropped unless you ask.

Upgrading

3.0.0 is a major version bump, but if you use Datagrunt for CSVs today, your code keeps working — the CSVReader, CSVWriter, and CSVSchemaReportAIGenerated APIs are unchanged. The only breaking change was the removal of an unused internal export, so nearly everyone can upgrade without touching a line. Add the PDF extra whenever you’re ready to start parsing documents, and check the documentation for the complete PDFReader and PDFWriter reference.

Conclusion and project links

Datagrunt set out to make one annoying file format easier to live with. With 3.0.0, it does the same for a second. The philosophy hasn’t changed: lightweight, Pythonic, and focused on handing you clean data instead of getting in your way.

Project Links:

Contact: datagrunt@datagrunt.io

If Datagrunt helps you tame a pile of PDFs — or if you hit a snag — I’d love to hear about it. Open an issue on the issue tracker or send an email. Happy parsing!