fix!: bump seqpro to 0.22.0 for per-variant allele indexing (#330) - #331
Merged
Conversation
Design for bumping seqpro 0.21.1 -> 0.22.0, which fixes the string-under-axis Ragged indexing bug (ML4GLand/SeqPro#71) that made RaggedVariants.alt[i]/ref[i] return one concatenated bytes per haplotype while start/ilen returned per-variant values. Off main, not streaming: the defect is in the shared RaggedVariants read path that the written Dataset returns too. Covers the pin changes (pyproject floor >=0.22, pixi py310 ==0.22.0, Cargo.toml unchanged since 0.22.0 touched no Rust), whole-tree verification plus a targeted audit of the six modules touching string-Ragged internals, a multi-byte-allele regression test to close the coverage gap that hid the bug, docs updates, and a breaking fix!: framing so commitizen cuts 0.41.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four tasks off the approved spec: (1) regression test written against the still-installed seqpro 0.21.1 so it is SEEN failing, then the pin bump turns it green in the same commit; (2) whole-tree pytest + cargo verification plus an audit of the six modules touching string-Ragged internals; (3) SKILL.md + dataset.md docs; (4) push and draft PR. Tasks 2 and 3 are independent and may run in parallel. Test code and the expected 0.21.1 failure mode are taken from seqpro 0.22.0's own #71 regression tests rather than guessed: peeling a group returns a Ragged where len() works and iteration yields bytes, and the multidim test uses the same (b, p, ~v) chain. On 0.21.1 the same expression returns concatenated bytes, so iteration yields ints. Records that the worktree already carries a working dev env at seqpro 0.21.1 with a built extension -- verified, not assumed -- which the red step depends on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Documents that rv.alt[b][h] / rv.ref[b][h] yield one allele per variant, aligned with rv.start / rv.ilen, and that this requires seqpro >= 0.22. Adds a Common gotchas entry noting the old concatenating behavior was invisible for SNP-only data. Additive only -- no existing prose demonstrated integer allele indexing, so nothing was false before this. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
RaggedVariants.alt[i]/ref[i] returned one concatenated bytes per haplotype while start/ilen returned per-variant values, because seqpro's Ragged.__getitem__ mapped a string-under-axis group straight to a byte range and dropped the interior str_offsets. zip(start, alt) was silently wrong -- and invisible for SNP-only data, since 1-byte alleles make the counts coincide. Fixed upstream in seqpro 0.22.0 (ML4GLand/SeqPro#71): integer indexing on a string-under-axis Ragged now returns a Ragged of strings. Bumps the pyproject floor to >=0.22 and the pixi py310 pin to ==0.22.0. Cargo.toml is untouched -- the 0.22.0 diff changed no Rust. Raising the floor also forecloses a latent skew: py311-313 declare no seqpro pin and inherit the floor, so once genoray's cap is lifted a lock refresh under >=0.20 would float them to 0.22 while py310 stayed pinned at 0.21. Adds the regression test whose absence let this ship: the existing suite asserts via to_ak().to_list(), which is structure-preserving and blind to the bug. The new test uses a multi-byte allele, without which it would pass on the buggy version too. BREAKING CHANGE: indexing a RaggedVariants allele field by integer (rv.alt[b][h], rv.ref[b][h]) now yields per-variant alleles instead of one concatenated bytes object. Callers relying on the concatenated value can recover it with b"".join(...). Requires seqpro>=0.22. Closes #330 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every genoray up to 3.3.0 pins seqpro>=0.21.1,<0.22, so none of them can satisfy the seqpro>=0.22 floor this branch introduces. The cap was lifted to <0.23 in d-laub/genoray#142 and released as genoray 3.3.1. The previous >=3.0.0,<4 range was not wrong -- because seqpro>=0.22 is a direct top-level constraint, pip/uv/pixi all reject the older genoray releases on their own rather than resolving to something broken. But they report it as a transitive seqpro conflict, which points away from the actual requirement. Declaring the real floor turns that into a direct, readable constraint failure. Resolution is unchanged: pixi.lock already selected genoray 3.3.1, and the only lock delta is the recorded requires-dist line. pixi.toml is bumped alongside pyproject.toml so the py310 feature keeps mirroring the pyproject floor. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
d-laub
marked this pull request as ready for review
July 29, 2026 23:30
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #330. Upstream fix: ML4GLand/SeqPro#71, released in seqpro 0.22.0.
What was wrong
RaggedVariants.alt[i]/ref[i]returned one concatenatedbytesper haplotype whilestart/ilenreturned per-variant values, even though all four fields share one offsets object.zip(rv.start[0][h], rv.alt[0][h])was silently wrong — and invisible for SNP-only data, because 1-byte alleles make the counts coincide. It only misbehaved once an indel entered the window.Root cause was upstream: seqpro's
Ragged.__getitem__mapped a string-under-axis group straight to a byte range and discarded the interiorstr_offsets.Changes
pyproject.toml:seqpro>=0.20→seqpro>=0.22pixi.toml: py310 pin==0.21.1→==0.22.0pixi.lockregeneratedto_ak().to_list(), which is structure-preserving and was blind to thisdocs/source/dataset.mddocument per-variant allele access and theseqpro>=0.22requirementCargo.tomlis untouched: the 0.22.0 diff changed no Rust, soseqpro-coreneeds no bump.Raising the floor also forecloses a latent skew —
py311/312/313declare no seqpro pin and inherit the floor, so now that genoray's cap is lifted a lock refresh under>=0.20would float them to 0.22 while py310 stayed pinned at 0.21.Also raises the genoray floor to >=3.3.1
Every genoray up to 3.3.0 pinned
seqpro>=0.21.1,<0.22, which madeseqpro>=0.22unsolvable. That cap was lifted to<0.23in d-laub/genoray#142 and released as genoray 3.3.1, sopixi.lockhere moves genoray 3.0.0 → 3.3.1.genoray>=3.0.0,<4was not wrong — becauseseqpro>=0.22is a direct top-level constraint, pip/uv/pixi all reject the older genoray releases themselves rather than resolving to something broken. But they report it as a transitive seqpro conflict, which points away from the actual requirement. So the floor is now declared directly, in bothpyproject.tomlandpixi.toml(the py310 feature mirrors the pyproject floor).Resolution is unchanged: the lock already selected genoray 3.3.1, and the only lock delta is the recorded
requires-distline. This changes the error message users get, not what they install.Breaking
Indexing an allele field by integer now yields per-variant alleles instead of one concatenated
bytes. Callers wanting the old value can useb"".join(...).major_version_zero = true, so this cuts 0.40.2 → 0.41.0.Verification
Full suite on a dedicated SLURM allocation (32 CPUs), against the real solved environment (seqpro 0.22.0 + genoray 3.3.1), no deselections:
1110 + 56 + 4 = 1170— the full collected count. Also green:cargo test --release(138 passed),pyrefly(0 errors),ruff check/ruff format --check, theapi.md-vs-__all__gate, a cleansphinx-build, andcz checkover the branch (so theBREAKING CHANGE:footer is machine-readable and the bump lands as a minor, not a patch).The regression test was confirmed RED on seqpro 0.21.1 before the bump (
list(rv.alt[0][0])yielded[65, 71, 71]— ints from iterating abytesblob — instead of[b"A", b"GG"]) and GREEN after.An audit of the six string-
Raggedconsumers found that all of them only construct string Raggeds or read fields by name /_rl.str_offsets/.to_chars(), none of which changed in 0.22.0.grep -rnE "\.(alt|ref)\[[0-9a-z_]" python/genvarloader/returns zero hits, so nothing in the package ever depended on the old concatenating behavior.Not included
Targets
main, notstreaming— the defect is in the sharedRaggedVariantsread path the writtenDatasetreturns too.streaminginherits the fix on its next routinemainmerge.🤖 Generated with Claude Code