Patch release. Focus: internal hygiene — finishing the Annotator.__del__ removal started in v2.1.1, backing the py.typed promise with a real type checker in CI, sharpening the coverage gate from line-only to line+branch, and moving the last bit of business logic out of the CLI into the exporter where it belongs. No user-facing behavior changes; existing v2.1.x caches and reports stay current.
Upgrade
pip install --upgrade allelix
No cache invalidation. v2.1.2 ships zero schema or interpreter changes on any annotator cache. JSON schema_version stays at 5. Existing v2.1.x caches remain current; only the standard allelix db update freshness check runs.
Highlights
Annotator.__del__safety net fully removed. v2.1.1 closed the production-side audit (every primary and enrichment annotator wired into the pipeline'scontextlib.ExitStack, CLI utility paths using explicittry / finally close()); v2.1.2 finishes the test-side refactor and drops the destructor entirely. Cleanup is now deterministic — the moment the context exits, the SQLite connection / file handle is released — instead of GC-timed. ThePytestUnraisableExceptionWarning-as-error filter inpyproject.tomlstays in place to keep any future leak loud.- mypy added to the CI gate. The package shipped
py.typed(PEP 561 — "trust the type annotations in this package") since v1.x but no type checker actually verified the annotations on every PR. CI now runsmypy allelix/after the ruff format check. The baseline is intentionally permissive (no strict mode, nodisallow_untyped_defs) so the gate landed without negotiating dozens of remediation PRs; individual flags can be tightened over time. The baseline pass also surfaced a handful of real type-narrowing gaps in the build-detection state machine and a loop-variable reuse in the ClinVar batch annotator — both fixed in this patch. - Coverage gate sharpened from line-only to line + branch. Pre-v2.1.2 the gate was line coverage only — a line counted as covered the moment any input crossed it, which is exactly how the v2.0.x case/strand carrier-match and download-truncation correctness bugs shipped green despite the failing branches never being exercised.
--cov-branchnow runs in the defaultpytestinvocation. The 92% floor was calibrated against the new branch metric (92.43% with primary annotators at 95%+); the floor only moves up from here. - PLINK gnomAD coordinate resolution moved out of the CLI. The CLI package previously held the gnomAD coordinate-resolution and multi-allelic disambiguation logic for the PLINK exporter — business logic in the presentation layer.
allelix.exporters.plink.resolve_ref_alt_via_gnomad()now owns the GnomadAnnotator lifecycle and the match-then-fallback disambiguation (single-coord direct; multi-coord prefer pair ⊆ {ref,alt}, else any orientation-valid record). The CLI handler is now a thin wrapper around it. Same ref/alt selection rules, same warning text on failure / unavailability. - README header rebuilt. GitHub-style centered layout — logo, tagline, nav (Website · Quickstart · Changelog), badges — replaces the prior inline status banner. All badges auto-populate from PyPI (
pyversions, latest version, license classifier) and from GitHub Actions (CI status), so the header has no manual-bump maintenance burden. Roadmap section refreshed to milestone-aligned themes (v2.2 → v3.0); the stale "Annotator-level strand awareness" entry is gone — that shipped in v2.1.0's ADR-0035 cluster.
Known limitations
Carried forward from v2.1.x — no new structural debt added in this patch.
- ClinVar per-(classification, condition) pairing still surfaces the primary aggregate classification plus the union of associated conditions; per-1:1 pairing requires the structured
submission_summary.txt.gzexport and is tracked for a future minor release. - PharmGKB → ClinPGx rebrand. PharmGKB officially rebranded to ClinPGx in July 2025 (redirects live). The allelix-internal annotator name, cache filename, and source string remain
pharmgkbfor backward compatibility; the rename bundles with the next schema-breaking change so users rebuild caches once. - mypy baseline is permissive. No strict mode and tests are excluded. Ratchet planned over subsequent patches.
- Branch-coverage follow-on tests deferred. The audit identified specific missing branches (lowercase / minus-strand carrier match, truncated download
OSError, signal-migration success path, real loader bodies for cadd / gnomad / alphamissense / snpedia). Branch coverage is now measured; the new tests land in the next minor.
Changed
Annotator.__del__removed. See Highlights above.- PLINK ref/alt resolution extracted to
exporters/plink.py. See Highlights above. - README header rebuilt with auto-populating badges; roadmap section refreshed. See Highlights above.
CI / Tooling
- mypy in the CI gate. See Highlights above.
- Branch coverage in the default pytest invocation. See Highlights above.
Verification
Full pytest: 1,578 pass / 0 skip / 0 warnings, 92.43% (line+branch) coverage. Ruff clean. mypy clean (59 source files). Real-data protocol against the full openSNP-derived fixture set: all 8 parser formats green; user1190 cross-parser identity holds at 538 annotations across all 6 array representations; PLINK export bit-exact via the new resolver; ADR-0035 enrichment threading active (30% gnomAD AF, 9% AlphaMissense, 30% CADD, 78% alt-populated on the user1190 openSNP-CC0 fixture analyzed via the MHG-format transcode); JSON schema_version = 5 on every report. No carrier-count shift expected on any input — v2.1.2 is hygiene / refactor / CI only, no behavior surface touched.