Skip to main content

pqax for Python

pqax reads, validates, and writes PQAX files: the footprint of Parquet tables, as a zip of JSON tables. The format is defined by the serialization and the object model, and the validation rules by the conformance kit.

Version 0.1.0
Format Reads every version 0.x. Writes version 0.1.
Python 3.10 or later
Dependencies None. to_dataframe uses pandas, or polars 1.0 or later, when one is installed.

Install

pip install pqax                       # the package and the pqax command
pip install "pqax[pandas]"             # with pandas, for to_dataframe
pip install "pqax[polars]"             # with polars, for to_dataframe

From the root of the repository:

pip install -e src/python              # the package and the pqax command
pip install -e "src/python[pandas]"    # with pandas, for to_dataframe
pip install -e "src/python[dev]"       # with pytest and jsonschema, for the tests

Python versions: 3.10 or later. The tests run on 3.10 and 3.14, each without extras and with both pandas and polars. The package has no dependency: the pandas and polars extras only install the library that to_dataframe uses.

Read a file

import pqax

with pqax.open("footprint.pqax") as doc:
    print(doc.manifest["formatVersion"], doc.part_names)
    tables = doc.summary["tables"]               # read when the file opens
    for table_id, size in zip(tables["id"], tables.get("fileSize", [])):
        print(table_id, size)
    chunks = doc.table("columnChunks")           # read now, from all its parts, and kept
    print(doc.row_count("columnChunks"), sum(chunks["compressedSize"]))
    for row in doc.rows("files"):                # one dictionary for each row
        print(row["id"], row["fileSize"])

A table is a dictionary of fields, and a field is a list with one value for each row, as in the file. The keys keep the names of the file, such as fileCount. A value that the file does not have is None.

  • Opening a file reads the zip directory, manifest.json, and summary.json. A detail part is read the first time a table that it holds is asked for.
  • doc.table(name) concatenates the parts of a table in the order of the manifest. A field that a part leaves out is None in the rows of that part. A table that the file does not have is {}.
  • A field that has no value in any row is not in the dictionary: use table.get("field").
  • A value whose JSON type is not the type of its field, such as a string where the model says integer, reads as None. doc.raw(name) returns the table as parsed, with that value and with the members of a table that are not arrays.
  • Fields and tables that this version does not define are returned like the others. Members of the manifest that it does not define stay in doc.manifest.

A PQAX file embedded in a VPAX file:

with pqax.open_embedded("model.vpax") as doc:    # the part Footprint.pqax, stored or deflated
    print(doc.embedded_part, doc.embedded_compression)

The part is read into memory, and the VPAX file is closed before open_embedded returns.

Validate a file

result = pqax.validate("footprint.pqax")         # or pqax.validate_embedded("model.vpax")
print(result.is_valid, result.checks)
for issue in result.issues:
    print(issue.code, issue.message, issue.part, issue.table, issue.field)

The validator reports every issue that it finds, with the codes of section 4.1 of the contracts: container.*, json.*, manifest.*, part.*, table.*, depth.*, totals.*, and, for validate_embedded, embedded.partMissing. A rule that cannot be evaluated because an earlier one failed is skipped. A file that cannot be opened raises OSError. A file that is not a zip archive is an issue, container.manifestMissing, and so is a VPAX file given to validate.

Data frames

frame = pqax.to_dataframe(doc.table("columnChunks"))             # pandas when installed, else polars
frame = pqax.to_dataframe(doc.summary["tables"], library="polars")

With pandas, an integer or boolean column with None values gets a nullable type, so it does not turn into floats. Lists and objects stay as Python objects. Timestamps stay strings; pqax.parse_timestamp turns one into a datetime. The rows of doc.rows(name) give the same frame as doc.table(name).

to_dataframe needs polars 1.0 or later, and raises ImportError with an earlier version, which loses the values of a column that mixes types.

Write a file

from datetime import datetime, timezone

builder = pqax.Builder({"collectorApp": "my-tool", "level": 2, "startedAt": datetime.now(timezone.utc)})
source = builder.add_source(kind="Storage folder")
table = builder.add_table(source=source, name="sales", format="Delta", detailDepth="Files")
file_id = builder.add_file(table=table, path="part-0001.parquet", fileSize=47820)
builder.update("tables", table, fileCount=1, fileSize=47820)
builder.write("footprint.pqax")
Method What it does
add_source(**fields), add_table, add_column, and one for each of the 21 tables Adds a row. For a table with an id, it keeps a positive id that the row brings, assigns the next free id when the row has none (greater than every id so far in the table, so from 1), and returns the id. For a table without, it returns the position of the row, from 0. The plural names, such as add_files, are the same methods.
add(table, **fields) Adds a row to any table, also one that this version does not define, with any field
extend(table, columns, detail=None) Adds many rows from a columnar table. detail places a table that the format does not define.
update(table, id_or_position, **fields) Sets fields of a row already added, such as totals that are known after the detail
write(path_or_file) Writes the file. It raises ValueError, naming the table and the id, when a table has an id in more than one row.
Builder.from_document(doc) A builder with every table of a document, to rewrite a file

The fields are named as in the file. An add_ method refuses a field that its table does not have, unless the name has a producer's prefix and an underscore, as in acme_note. A field set to None has no value. A datetime becomes a timestamp in UTC with three decimals; a naive datetime is taken as UTC.

write lays out the file as the serialization says: manifest.json first, then summary.json, always present, then one part for each detail table, named after it, as files.json. A table with more than rows_per_part rows, 250,000 by default and at most, continues in parts numbered from 1, as columnChunks.1.json. A field with no value in any row of a part is left out of that part, and so is a table with no rows. The JSON is compact UTF-8 with no byte order mark, the entries are deflated at level 9 by default (compression_level), and every entry has the same date, so the same footprint gives the same bytes. A path is written through a temporary file.

from_document keeps the ids, the collection, the obfuscation fields, the other members of the manifest, and the fields and tables that this version does not define. It loses what the reader does not keep: the entries that the manifest does not list, the members of a table that are not arrays, and the members of an element of parts other than name and tables. It writes format version 0.1, and its own part names.

Command line

python -m pqax check footprint.pqax      # exit code 0 when valid, 1 when not, 2 on a usage or I/O error
python -m pqax info footprint.pqax       # format, version, collection, parts with their tables and rows
python -m pqax info model.vpax           # a VPAX file: the PQAX file that it embeds as Footprint.pqax

check prints one line for each issue, its code first, then a summary line. Both commands take --part NAME for another embedded part. The installed package also has a pqax command.

API

Name What it is
open(path_or_file) A PqaxDocument. The source is a path, the bytes of a file, or a binary file object, which is left open.
open_embedded(path_or_file, part_name="Footprint.pqax") A PqaxDocument of the PQAX file that a package holds as a part
PqaxDocument manifest, summary, row_count(name), table(name), rows(name), raw(name, part=None), table_names, part_names, format_version, collection, embedded_part, embedded_compression, close(), and the with statement
validate(path_or_file), validate_embedded(path_or_file, part_name) A ValidationResult: is_valid, checks, issues, codes
Issue code, message, part, table, field
to_dataframe(table, library=None) A pandas or polars data frame
Builder, BuilderOptions The writer
format_timestamp(datetime), parse_timestamp(text) Timestamps in the form of the format, 2026-10-05T08:15:30.000Z
TABLES, SUMMARY_TABLES, DETAIL_TABLES, FIELDS, REQUIRED, REFERENCES The names of the format

Errors:

Error When
ManifestMissingError The archive has no manifest.json
FormatError format is not pqax
UnknownVersionError The first number of formatVersion is not 0
JsonError A part that is read is not valid JSON
PartMissingError A detail part that the manifest lists is not in the archive, when its table is read (manifest.partMissing), or the embedded part is not in the package (embedded.partMissing)
ContainerError The file is not a zip archive, or an entry cannot be read: encrypted, compressed with another method, or corrupt

All derive from PqaxError, whose code is the validation code that the error matches, when there is one. A reader refuses a file for the first four, when it opens it, and open_embedded also for a missing embedded part. Opening also fails when the manifest or the summary cannot be read from the zip, for example because the entry is encrypted. A summary.json that the manifest lists and the archive does not have gives empty summary tables, and validate reports it. A detail part that is broken fails only the read of its tables.

Limits

  • The reader keeps a table in memory once it is read. A part of 250,000 column chunks takes about 0.15 s to parse.
  • A part that holds more than 1 GB of JSON text is not read, and is reported or raised as such.
  • The rows of a part are the length of its longest array, not what the manifest states, so that a file that states more rows than it holds cannot make the reader allocate them. In a valid file the two agree.
  • The reader replaces a value of another JSON type with None only at the level of the field. An element of a list, or a member of a nested object, is returned as the file has it.
  • container.zip64 looks at the central directory and its locator, as the kit says. A ZIP64 field in a local header alone is not reported.
  • No pandas or polars type is chosen for timestamps, lists, maps, or nested objects.
  • On Python 3.10, parse_timestamp reads fewer of the other forms of ISO 8601, as datetime.fromisoformat does there: a fraction of seconds with other than three or six digits, a comma before the fraction, an offset without a colon, and the basic form, such as 20261005T081530Z, raise ValueError. The form of the format reads on every version.

Metadata

Release files for pqax 0.1.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 pqax 0.1.0
File Size Uploaded Provenance
pqax-0.1.0.tar.gz 41.6 kB With provenance Details

Built distribution (wheel)

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

Total release size: 84.4 kB

Release files / pqax-0.1.0.tar.gz

Download URL pqax-0.1.0.tar.gz
Size 41.6 kB
Tags Source
SHA-256 checksum
How to use checksums
baf5e33fb9aee608d3746d6cc2473affb7a5721c3289ed294b3b5ecfe9237f93
BLAKE2b-256 checksum
How to use checksums
51c1af7f2b60f91b1db24b4640fa65c8770622ca37db349b72ef881768173e6d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

Release files / pqax-0.1.0-py3-none-any.whl

Download URL pqax-0.1.0-py3-none-any.whl
Size 42.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ab48c52af85088e12e5edc02fec98c5456fb00d1bbdde04800463c43ffa39b97
BLAKE2b-256 checksum
How to use checksums
c8f4d356124c0115d5ea86036c0f213e2fa28afd6af6c420f58319d117f4e49f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

Security for pqax 0.1.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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pqax-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

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