Skip to content

Generalize phasing API; remove Isovar-named identifiers - #379

Merged
iskandr merged 2 commits into
mainfrom
generic-phasing-api
May 14, 2026
Merged

Generalize phasing API; remove Isovar-named identifiers#379
iskandr merged 2 commits into
mainfrom
generic-phasing-api

Conversation

@iskandr

@iskandr iskandr commented May 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #378. Filed companion isovar tickets: openvax/isovar#184.

Varcode's phasing API named one upstream tool (Isovar) on every public identifier — IsovarPhaseResolver, IsovarAssemblyProvider — even though varcode imports nothing isovar-shaped and isovar already declares varcode>=4.15. This made the dependency direction read as circular, forced non-Isovar sources to lie about their identity, and bundled two unrelated concerns (read-phasing + mutant-transcript construction) into a single Protocol.

This PR makes varcode's phasing surface fully generic. Implementations of the Protocols live in the packages that produce the underlying evidence (Isovar, long-read tools, custom assemblers, test stubs).

Renames

Before After
IsovarAssemblyProvider (single Protocol) Removed. Split into two narrower Protocols.
has_contig(variant, transcript) has_evidence(variant) on ReadPhasingSource
variants_in_contig(variant, transcript) partners_in_cis(variant) on ReadPhasingSource
mutant_transcript(variant, transcript) Unchanged signature, now on MutantTranscriptSource
IsovarPhaseResolver ReadPhaseResolver
resolver.source == "isovar" resolver.source == "read_phasing"

ReadPhaseResolver requires a ReadPhasingSource. The mutant_transcript(...) method on the resolver is optional — it returns None silently when the wrapped source doesn't also satisfy MutantTranscriptSource, instead of raising. So a phasing-only source (e.g. the new isovar.IsovarReadPhasing from openvax/isovar#183) plugs in cleanly without needing to fabricate transcript reconstruction.

Why these names

  • ReadPhasingSource — generic across data sources (Isovar, long-read tools, hand-rolled). Doesn't pretend any one upstream is special.
  • has_evidence / partners_in_cis — neutral. The old has_contig was Isovar-vocabulary; long-read tools don't have "contigs," they have fragments. The phasing question is just "do you have evidence for this variant?" and "who's in cis with it?".
  • MutantTranscriptSource — separate concern. A consumer that only needs phasing shouldn't be forced to also build transcripts.

Dependency direction stays one-way

Files touched

  • varcode/phasing.py — full rewrite of Protocol definitions + ReadPhaseResolver. VCFPhaseResolver and helpers untouched.
  • varcode/__init__.py — updated re-exports + __all__.
  • varcode/variant.py, varcode/variant_collection.pyphase_resolver= docstrings updated.
  • tests/test_isovar_phase_resolver.pytests/test_read_phase_resolver.py (git rename + body update). 18 tests, all on the new Protocol shape.
  • tests/test_haplotype_effect.py — stub provider renamed to _StubReadPhasingSource matching the new shape; 4 tests on the resolver-driven path.
  • tests/test_vcf_phase_resolver.py — interface-symmetry test now references ReadPhaseResolver.
  • docs/germline.md — recipe and resolver list updated.
  • docs/api.md — adds entries for ReadPhasingSource and MutantTranscriptSource; renames the resolver entry.
  • CHANGELOG.md — Breaking entry.
  • varcode/version.py — 4.23.0 → 4.24.0.

Test plan

  • pytest tests/test_read_phase_resolver.py — 18 cases (Protocol conformance, phasing semantics, optional MutantTranscript channel, effect-attachment end-to-end). All pass.
  • pytest tests/test_haplotype_effect.py — 7 cases (joint cDNA building, HaplotypeEffect emission via the new resolver). All pass.
  • pytest tests/test_vcf_phase_resolver.py — 25 cases, unchanged behavior. All pass.
  • pytest tests/test_germline_effects.py — 34 cases, unchanged behavior. All pass.
  • pytest tests/ — 1188 passed, 2 pre-existing skips.

Out of scope

  • The isovar-side rework of VarcodeAdapter — tracked in Split VarcodeAdapter into two narrower providers; drop Varcode-named API isovar#184. Until that lands, downstream users of VarcodeAdapter see a runtime error when wiring it into ReadPhaseResolver (the methods no longer match the Protocol). The new isovar adapter PR is small.
  • Renaming IsovarPhaseResolver is the only public-surface rename in this PR; lower-level helper names are unchanged.

References

Closes #378. The phasing Protocol bundled two unrelated concerns
(read-phasing + mutant-transcript construction) and named them after
Isovar, even though varcode imports nothing isovar-shaped. That
made the dependency direction look circular (isovar already declares
varcode>=4.15) and forced any non-Isovar source to either lie about
its identity or build a wrapper.

Split into two narrow Protocols, both fully generic:

- ReadPhasingSource: has_evidence(variant) + partners_in_cis(variant).
- MutantTranscriptSource: mutant_transcript(variant, transcript).

IsovarPhaseResolver renamed to ReadPhaseResolver. Accepts any
ReadPhasingSource; routes mutant_transcript() to the wrapped source
when it also satisfies MutantTranscriptSource (otherwise returns None
silently rather than raising). Source tag flipped from "isovar" to
"read_phasing".

The actual implementation classes (Isovar's IsovarReadPhasing in
openvax/isovar#183; the heavier mutant-transcript adapter tracked in
openvax/isovar#184) stay in isovar, where the dependency direction
already supports them. Varcode no longer mentions Isovar by name on
any public symbol.

Hard rename, no deprecation alias.

Test suite: tests/test_isovar_phase_resolver.py renamed to
tests/test_read_phase_resolver.py with the stub updated to satisfy
both new Protocols. test_haplotype_effect.py + test_vcf_phase_resolver.py
updated to match. 1188/1190 pass; 2 pre-existing skips.
iskandr added a commit to openvax/isovar that referenced this pull request May 14, 2026
Varcode is dropping Isovar-named identifiers from its public surface
(openvax/varcode#378, openvax/varcode#379). Replace the docstring
reference to the old `IsovarAssemblyProvider` Protocol with the new
`ReadPhasingSource` + `MutantTranscriptSource` split, name the matching
consumer (`ReadPhaseResolver`), and point at #184 for the
companion heavy-adapter rework that will split `VarcodeAdapter` into
two narrower classes.

No code change; the adapter's method names (`has_evidence`,
`partners_in_cis`) already match the new `ReadPhasingSource` Protocol.
….source; trim Isovar-named examples from docstrings
@iskandr
iskandr merged commit 68d0218 into main May 14, 2026
8 checks passed
@iskandr
iskandr deleted the generic-phasing-api branch May 14, 2026 21:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make phasing API generic: drop Isovar-specific names from public surface

1 participant