just_dna_enricher.ensembl¶
just_dna_enricher.ensembl ¶
Live Ensembl resolution — for the rare rsIDs the frozen snapshot slice misses.
Two backends, tried in order with a fallback the user asked for:
V2 — the beta GraphQL variation API (endpoint + query shape leeched from ensembl-mcp, with
fastmcp/eliot dropped and stdlib logging in their place); and
V1 — the legacy REST variation API (rest.ensembl.org), which is the workhorse for a bare
rsID → coordinate (the beta GraphQL variant lookup needs a composite region:pos:rsid id, so
a bare rsID falls through to REST). V1 also serves as the explicit fallback on 500/503 from
V2.
tenacity retries each backend on transient transport/timeout errors; a 5xx is not retried but
triggers the V2→V1 fallback. All network lives here (never in format/compiler). GRCh38 only.
EnsemblError ¶
Bases: RuntimeError
A live Ensembl query failed on all backends (after retries + fallback).
EnsemblResolver
dataclass
¶
EnsemblResolver(
settings: EnsemblSettings = EnsemblSettings(),
_client: Client | None = None,
_read_base: Callable[[str, int], str | None]
| None = None,
)
Resolve a bare rsID to its GRCh38 loci via live Ensembl (V2 GraphQL → V1 REST fallback).
resolve_rsid ¶
Return ([{chrom, start, ref, alts}, ...], source) for a bare rsID.
Tries V2 GraphQL, then falls back to V1 REST on a 5xx (or when V2 yields nothing). source is
ensembl-graphql or ensembl-rest — recorded per row so the manifest can report provenance.
Three outcomes, because two of them used to be one (S20). A non-empty list is an answer;
[] is also an answer — Ensembl was reached and has no GRCh38 locus for this rsID — and
None means Ensembl could not be asked at all, so its answer is unchecked rather than
empty. Fusing the last two into ([], None) made a failed request read as a definite negative,
and loci: [] plus "Ensembl has no locus" is exactly the fingerprint of a fabricated rsID: a
consumer checking which ids in a machine-written document were real put two published variants
in the fabricated pile on a flaky run. This is the tri-state rule the rest of the tree keeps —
an unreachable source reports unknown, never the negative.
A 4xx is an answer, not a failure: Ensembl 400s on rsIDs it cannot resolve (rs3216883,
which dbSNP reports as merged), so only a 5xx, a transport error or a timeout — the cases where
nothing came back at all — return None. An empty answer now carries its source too, so a
caller can record which link said nothing; that omission was the only trace the old code
left, and it was a missing element in a set nobody diffs.
None with a source is the fourth outcome (RM268): Ensembl answered, and nothing it said
could be placed. REST spells an insertion or a deletion with one side - at an interbase
start, and such a locus is written only once the reference base before the event has been
read and prefixed to every allele. When that base cannot be read, the locus is withheld, and an
answer whose every locus was withheld is unchecked rather than empty, exactly like a request
that failed; the source is returned so a caller can say which of the two happened.