Skip to content

geno_lewm.data.variant_identity

variant_identity

Canonical, content-addressed genomic variant identities.

The v0.3 data contract uses one identity spelling everywhere: GRCh38, canonical chromosome names without a chr prefix, 1-based coordinates, and uppercase explicit A/C/G/T alleles. Human-friendly input spellings are normalized at construction time; serialized keys are validated strictly by CanonicalVariant.from_key so artifacts cannot drift silently.

This module removes redundant context from an allele pair, but it deliberately does not perform reference-aware left alignment across repeats. Dataset preparation must perform and receipt-bind that upstream transformation before constructing these identities; the reference sequence is intentionally not an implicit dependency of this pure value object.

SUPPORTED_ASSEMBLIES module-attribute

SUPPORTED_ASSEMBLIES: frozenset[str] = frozenset({'GRCh38'})

Reference assemblies accepted by the v0.3 identity contract.

CANONICAL_CHROMOSOMES module-attribute

CANONICAL_CHROMOSOMES: tuple[str, ...] = (*(map(str, range(1, 23))), 'X', 'Y', 'MT')

Canonical primary chromosome spellings, in genomic sort order.

CanonicalVariant dataclass

CanonicalVariant(assembly: str, chrom: str, pos: int, ref: str, alt: str)

One parsimonious assembly-qualified variant with a stable key and digest.

Shared allele context is trimmed deterministically. Repeat-aware left alignment requires a reference sequence and remains an upstream, receipt-bound dataset-preparation responsibility.

key property

key: str

Return the strict assembly:chrom:pos:ref:alt key.

digest property

digest: str

Return the SHA-256 digest of the canonical JSON identity.

from_key classmethod

from_key(value: str) -> CanonicalVariant

Parse an already-canonical variant key, rejecting spelling drift.

Source code in geno_lewm/data/variant_identity.py
@classmethod
def from_key(cls, value: str) -> CanonicalVariant:
    """Parse an already-canonical variant key, rejecting spelling drift."""
    if not isinstance(value, str):
        raise InputError(
            "value must be a canonical variant key",
            details={"value": value, "type": type(value).__name__},
        )
    parts = value.split(":")
    if len(parts) != 5:
        raise InputError(
            "value must be a canonical variant key",
            details={"value": value, "expected": "assembly:chrom:pos:ref:alt"},
        )
    assembly, chrom, pos_text, ref, alt = parts
    try:
        pos = int(pos_text)
        variant = cls(assembly=assembly, chrom=chrom, pos=pos, ref=ref, alt=alt)
    except (InputError, ValueError) as exc:
        raise InputError(
            "value must be a canonical variant key",
            details={"value": value, "expected": "assembly:chrom:pos:ref:alt"},
        ) from exc
    if variant.key != value:
        raise InputError(
            "value must be a canonical variant key",
            details={"value": value, "canonical": variant.key},
        )
    return variant

to_dict

to_dict() -> dict[str, object]

Return the canonical JSON-native identity payload.

Source code in geno_lewm/data/variant_identity.py
def to_dict(self) -> dict[str, object]:
    """Return the canonical JSON-native identity payload."""
    return {
        "assembly": self.assembly,
        "chrom": self.chrom,
        "pos": self.pos,
        "ref": self.ref,
        "alt": self.alt,
    }

canonicalize_assembly

canonicalize_assembly(value: str) -> str

Return the canonical assembly spelling accepted by v0.3.

Source code in geno_lewm/data/variant_identity.py
def canonicalize_assembly(value: str) -> str:
    """Return the canonical assembly spelling accepted by v0.3."""
    if not isinstance(value, str) or not value.strip():
        raise InputError(
            "assembly must be a non-empty string",
            details={"assembly": value, "type": type(value).__name__},
        )
    normalized = value.strip()
    if normalized.casefold() == "grch38":
        return "GRCh38"
    raise InputError(
        "assembly is not supported by the v0.3 identity contract",
        details={"assembly": value, "supported": sorted(SUPPORTED_ASSEMBLIES)},
    )

canonicalize_chromosome

canonicalize_chromosome(value: str) -> str

Return a primary chromosome without a chr prefix.

Source code in geno_lewm/data/variant_identity.py
def canonicalize_chromosome(value: str) -> str:
    """Return a primary chromosome without a ``chr`` prefix."""
    if not isinstance(value, str) or not value.strip():
        raise InputError(
            "chromosome must be a non-empty string",
            details={"chrom": value, "type": type(value).__name__},
        )
    normalized = value.strip()
    if normalized[:3].casefold() == "chr":
        normalized = normalized[3:]
    upper = normalized.upper()
    if upper == "M":
        upper = "MT"
    if upper.isdecimal():
        upper = str(int(upper))
    if upper not in CANONICAL_CHROMOSOMES:
        raise InputError(
            "chromosome must identify a canonical primary chromosome",
            details={"chrom": value, "supported": list(CANONICAL_CHROMOSOMES)},
        )
    return upper