Python API¶
fqxv ships a read-only Python package for consuming .fqxv archives
directly from Python — no subprocess, no intermediate FASTQ file. Compression
stays in the CLI; the Python side reads.
uv pip install fqxv
The wheels are abi3 (one per platform, CPython ≥ 3.9) and carry the native
codecs, so there is no separate Rust toolchain to install. To build from a
checkout instead:
uv pip install maturin
maturin develop # from crates/fqxv-python/
Iterating records¶
fqxv.open() streams records in original file order. It works on every
archive layout, including globally-reordered (--order shuffle) archives, and
decodes on a background thread so memory stays bounded no matter the archive
size.
import fqxv
for rec in fqxv.open("reads.fqxv"):
print(rec.name, rec.sequence, rec.quality) # all bytes
print(len(rec)) # sequence length
Each Record exposes name (the header with the leading @ stripped),
sequence, and quality as bytes. Bytes — not str — because reads are
stored losslessly and read names are not guaranteed to be UTF-8.
In-memory input works anywhere a path does — pass bytes instead of a filename:
data = open("reads.fqxv", "rb").read()
n = sum(1 for _ in fqxv.open(data))
open() also works as a context manager, and breaking out early is safe (the
decode thread is stopped cleanly):
with fqxv.open("reads.fqxv") as reader:
for rec in reader:
if rec.sequence.startswith(b"ACGT"):
break # no hang; the decoder is torn down on exit
Pass threads= to control the decode pool (default 0 = a sensible default).
Whole-archive convenience¶
# Write interleaved FASTQ to a file; returns the read count.
n = fqxv.decompress_to_path("reads.fqxv", "reads.fastq")
# ...or get the FASTQ bytes directly.
raw = fqxv.decompress_to_bytes("reads.fqxv")
# Metadata only — no payload decode.
info = fqxv.inspect("reads.fqxv")
print(info.reads, info.blocks, info.format_version, info.platform)
inspect() returns an Info with reads, blocks, group_size,
reordered, keep_order, regenerated_names, plus_normalized,
format_version, seq_order, quality_binning, names_bytes,
sequence_bytes, quality_bytes, platform, and whole_file_crc.
Column projection & random access¶
An .fqxv archive carries a footer index with per-stream byte offsets and CRCs.
That makes it Parquet-shaped: you can fetch and decode a
single column, or a single row group, without touching the rest. Read names are
typically under 1% of the archive, so an ID-only pass reads ~100× less.
# Just the read names (IDs) — a fraction of the archive.
ids = fqxv.read_names("reads.fqxv")
# Just the sequences, skipping quality entirely.
seqs = fqxv.read_sequences("reads.fqxv") # list[bytes], one per read
# Restrict to specific row groups.
first_group = fqxv.read_sequences("reads.fqxv", groups=[0])
read_names, read_sequences, and read_qualities each return a flat
list[bytes] across the requested groups (all groups when groups is omitted),
verifying every fetched stream against its stored CRC before decoding.
Inspect the index and decode a whole row group as records:
idx = fqxv.open_index("reads.fqxv")
print(idx.total_reads, idx.num_groups)
for g in idx.groups():
print(g.block_offset, g.read_count)
block0 = fqxv.read_block("reads.fqxv", 0) # list[Record]
Reordered archives
Projection and open_index require the plain (per-block) layout. A
globally-reordered archive (--order shuffle) has no footer index — its
streams are mutually dependent — so these raise fqxv.FqxvError. Use
fqxv.open() to iterate those. Check fqxv.inspect(path).reordered if you
need to branch. Compressing with a smaller --block-reads makes projection
finer-grained on the plain layout.
Errors¶
Decode and I/O failures raise exceptions: missing/unreadable files raise
OSError; a corrupt archive, an unsupported format version, or a projection on
a reordered archive raise fqxv.FqxvError.
try:
list(fqxv.open("truncated.fqxv"))
except fqxv.FqxvError as e:
print("archive problem:", e)
API reference¶
| Function | Returns | Notes |
|---|---|---|
open(source, *, threads=0) |
Reader |
Iterator of Record; every layout |
decompress_to_path(source, dest, *, threads=0) |
int |
Read count; writes interleaved FASTQ |
decompress_to_bytes(source, *, threads=0) |
bytes |
Interleaved FASTQ |
inspect(source) |
Info |
Header + footer metadata |
open_index(source) |
Index |
Footer row-group index (plain layout) |
read_names(source, groups=None) |
list[bytes] |
Names for the groups (or all) |
read_sequences(source, groups=None) |
list[bytes] |
Sequences for the groups (or all) |
read_qualities(source, groups=None) |
list[bytes] |
Qualities for the groups (or all) |
read_block(source, group) |
list[Record] |
Decode one whole row group |
source is a path (str / os.PathLike) or bytes. Record has
.name / .sequence / .quality (bytes) and len(); Index has
.total_reads / .num_groups / .whole_file_crc and .groups().