Datagrunt 4.3.0: First-Class Parquet Reading and Writing

June 26, 2026by Martin Graham

Datagrunt has been able to write Parquet for a long time — CSVWriter.write_parquet() and, more recently, ExcelWriter.write_parquet() have shipped for releases. What it could never do was start from one. If your data already lived in a Parquet file, you were on your own.

Datagrunt 4.3.0 closes that gap with a full, symmetric pair: ParquetReader and ParquetWriter, designed to feel exactly like the CSV, Excel, and PDF classes you already know.


Reading a Parquet file

from datagrunt import ParquetReader

reader = ParquetReader("data.parquet")

reader.to_dataframe()      # as a Polars DataFrame
reader.to_arrow_table()    # ... as a PyArrow Table
reader.to_dicts()          # ... as a list of row dicts

A path that is not a Parquet file raises a ValueError at construction, and a missing path raises FileNotFoundError — mistakes surface immediately, not deep inside a parse. Empty or blank files return empty results rather than raising.

Reading is powered by a single canonical engine — Polars (pl.read_parquet) — so there is no engine argument to choose. Parquet is natively columnar and Arrow-backed, so no new dependency was needed: polars, pyarrow, and duckdb already ship with Datagrunt.


One table, no sheets

Where the defining feature of Excel is its multiple worksheets, Parquet is the opposite: a flat, single table. So ParquetReader drops the sheet dimension entirely — there is no .sheets property and no sheet= argument. The API is the clean, flat surface you know from the CSV classes.

You can still run SQL against the file — it is registered into an on-demand DuckDB connection under db_table:

reader.query_data(f"SELECT * FROM {reader.db_table} LIMIT 5")

The full Polars read surface, passed straight through

Because reading is Polars-only, you get the entire read_parquet option set for free via **read_options — set once on the constructor (applied to every read) or per call (per-call wins):

# Read only the columns you need
reader.to_dataframe(columns=["name", "city"])

# Cap the read
reader.to_dataframe(n_rows=100)

The key source is reserved — the file path is controlled exclusively through the constructor — and passing it raises a ValueError.

Same mechanism as the Excel reader. If you read the 4.2.0 Excel post, this is the identical passthrough: read options are keyword arguments forwarded verbatim to Polars. The only reason these examples look different from the Excel ones is that the underlying Polars functions differ. pl.read_parquet exposes its options as plain top-level keyword arguments (columns, n_rows, …), so you pass them directly — there is no dict to wrap them in. pl.read_excel instead groups its calamine options inside a parameter named read_options, which is why the Excel examples show read_options={...}. That’s a difference in the Polars signatures, not in Datagrunt.


Writing it back out

ParquetWriter reads a Parquet file and exports it to any of Datagrunt’s formats. And because writing is Polars-only too, **write_options pass straight through to the underlying writer — so you can re-encode a Parquet file with a different compression codec in one line:

from datagrunt import ParquetWriter

writer = ParquetWriter("data.parquet")

writer.write_csv("out.csv")
writer.write_json("out.json")
writer.write_json_newline_delimited("out.jsonl")
writer.write_excel("out.xlsx")

# Re-encode Parquet → Parquet; compression= passes through to Polars
writer.write_parquet("recompressed.parquet", compression="zstd")

Empty or blank source files produce a single empty (0-byte) output file rather than raising.


Consistent with the rest of Datagrunt

  • Same normalize_columns. Set it on the constructor (or per call) to get normalized, collision-safe column names everywhere — using the exact same implementation as the CSV and Excel APIs.
  • Automatic resource lifecycle. You never have to call .close(). The DuckDB connection that backs query_data is opened on demand and released when the reader goes out of scope; close() and the context-manager protocol are available for early release.
  • Preserve, not transform. Values are written verbatim. As with CSV and Excel, a value beginning with =, +, -, or @ is treated as a formula when opened in a spreadsheet application (CSV/formula injection, CWE-1236) — Datagrunt documents this and leaves sanitization to the application layer rather than silently mutating your data.

Upgrade today

Datagrunt 4.3.0 is available on PyPI:

uv pip install --upgrade datagrunt
from datagrunt import ParquetReader, ParquetWriter