allelix 2.1.1__tar.gz → 2.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {allelix-2.1.1 → allelix-2.1.2}/PKG-INFO +29 -30
- {allelix-2.1.1 → allelix-2.1.2}/README.md +26 -29
- {allelix-2.1.1 → allelix-2.1.2}/allelix/annotators/__init__.py +10 -12
- {allelix-2.1.1 → allelix-2.1.2}/allelix/annotators/base.py +0 -38
- {allelix-2.1.1 → allelix-2.1.2}/allelix/annotators/cadd.py +9 -7
- {allelix-2.1.1 → allelix-2.1.2}/allelix/annotators/clinvar.py +3 -2
- {allelix-2.1.1 → allelix-2.1.2}/allelix/cli/_helpers.py +7 -4
- {allelix-2.1.1 → allelix-2.1.2}/allelix/cli/utility.py +6 -36
- {allelix-2.1.1 → allelix-2.1.2}/allelix/config.py +3 -1
- {allelix-2.1.1 → allelix-2.1.2}/allelix/databases/cpic_loader.py +2 -2
- {allelix-2.1.1 → allelix-2.1.2}/allelix/databases/gwas_loader.py +2 -2
- {allelix-2.1.1 → allelix-2.1.2}/allelix/databases/snpedia_parser.py +6 -4
- {allelix-2.1.1 → allelix-2.1.2}/allelix/exporters/plink.py +50 -1
- {allelix-2.1.1 → allelix-2.1.2}/allelix/reports/_pipeline.py +10 -5
- {allelix-2.1.1 → allelix-2.1.2}/allelix.egg-info/PKG-INFO +29 -30
- {allelix-2.1.1 → allelix-2.1.2}/allelix.egg-info/requires.txt +2 -0
- {allelix-2.1.1 → allelix-2.1.2}/pyproject.toml +37 -2
- {allelix-2.1.1 → allelix-2.1.2}/LICENSE +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/__init__.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/annotators/alphamissense.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/annotators/gnomad.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/annotators/gwas.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/annotators/pharmgkb.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/annotators/snpedia.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/cli/__init__.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/cli/_options.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/cli/analyze.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/cli/config.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/cli/db.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/cli/focused.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/compare.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/data/__init__.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/data/clinvar_clnsig_snapshot.yaml +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/data/high_value_snps.yaml +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/databases/__init__.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/databases/_versions.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/databases/alphamissense_loader.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/databases/cadd_loader.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/databases/gnomad_loader.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/databases/loader_utils.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/databases/manager.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/databases/pharmgkb_loader.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/databases/schema.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/databases/snpedia_loader.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/exporters/__init__.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/models.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/parsers/__init__.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/parsers/_helpers.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/parsers/ancestrydna.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/parsers/base.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/parsers/ftdna.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/parsers/ftdna_illumina.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/parsers/livingdna.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/parsers/myhappygenes.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/parsers/myheritage.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/parsers/twentythreeandme.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/parsers/vcf.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/py.typed +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/reports/__init__.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/reports/diff.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/reports/high_value.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/reports/html.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/reports/json_report.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/reports/methylation.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/reports/terminal.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/utils/__init__.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/utils/allele.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix/utils/build_detect.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix.egg-info/SOURCES.txt +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix.egg-info/dependency_links.txt +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix.egg-info/entry_points.txt +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/allelix.egg-info/top_level.txt +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/setup.cfg +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/tests/test_cli.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/tests/test_cli_helpers.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/tests/test_compare.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/tests/test_config.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/tests/test_end_to_end.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/tests/test_mock_data_invariants.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/tests/test_models.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/tests/test_registry.py +0 -0
- {allelix-2.1.1 → allelix-2.1.2}/tests/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: allelix
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.2
|
|
4
4
|
Summary: Open-source genotype analysis toolkit. Format-agnostic ingestion, database-agnostic annotation, offline-first.
|
|
5
5
|
Author: Allelix
|
|
6
6
|
Maintainer-email: dial481 <dial481@users.noreply.github.com>
|
|
@@ -29,35 +29,33 @@ Requires-Dist: pysam>=0.22; extra == "cadd"
|
|
|
29
29
|
Provides-Extra: vcf-index
|
|
30
30
|
Requires-Dist: pysam>=0.22; extra == "vcf-index"
|
|
31
31
|
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: mypy>=1.11; extra == "dev"
|
|
32
33
|
Requires-Dist: pre-commit>=3.7; extra == "dev"
|
|
33
34
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
34
35
|
Requires-Dist: pytest-cov<8,>=7; extra == "dev"
|
|
35
36
|
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
37
|
+
Requires-Dist: types-PyYAML>=6.0; extra == "dev"
|
|
36
38
|
Dynamic: license-file
|
|
37
39
|
|
|
40
|
+
<div align="center">
|
|
41
|
+
|
|
42
|
+
<img src="https://allelix.io/apple-touch-icon.png" width="180" alt="Allelix" />
|
|
43
|
+
|
|
44
|
+
**Open-source genotype analysis. Format-agnostic. Offline-first.**
|
|
45
|
+
|
|
46
|
+
[Website](https://allelix.io) · [Quickstart](#quickstart) · [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md)
|
|
47
|
+
|
|
48
|
+
[](https://www.python.org/downloads/)
|
|
49
|
+
[](https://pypi.org/project/allelix/)
|
|
50
|
+
[](LICENSE)
|
|
51
|
+
[](https://github.com/allelix/allelix/actions/workflows/ci.yml)
|
|
52
|
+
[](#privacy)
|
|
53
|
+
|
|
54
|
+
</div>
|
|
55
|
+
|
|
38
56
|
# Allelix
|
|
39
57
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
> **Status:** Production — eight parser formats (including VCF + gVCF),
|
|
43
|
-
> four annotators (ClinVar + ClinPGx + GWAS Catalog + SNPedia), three
|
|
44
|
-
> enrichment sources (gnomAD population frequencies + AlphaMissense
|
|
45
|
-
> pathogenicity + CADD deleteriousness), licensable-source gating for
|
|
46
|
-
> commercial users, dual-build ClinVar caches (GRCh37 + GRCh38),
|
|
47
|
-
> HTML/JSON/terminal reports, methylation + pharmacogenomics focused
|
|
48
|
-
> commands, report diffing, persistent config with commercial-mode
|
|
49
|
-
> safety switch. Build auto-detection from position data (ADR-0021).
|
|
50
|
-
> No regex on prose anywhere in production. **Latest: v2.1.0** —
|
|
51
|
-
> ADR-0035 'Cluster B' coordinated model change: `Variant.ref` carries
|
|
52
|
-
> the forward-strand reference allele, per-Annotation `alt` threading
|
|
53
|
-
> on GWAS / SNPedia / ClinPGx enables allele-specific gnomAD /
|
|
54
|
-
> AlphaMissense / CADD enrichment on array inputs, structured GWAS
|
|
55
|
-
> fields (`trait` / `p_value` / `phecode`) replace prose re-parsing in
|
|
56
|
-
> the rollup, and ClinVar / ClinPGx carrier matching is strand-aware
|
|
57
|
-
> with multi-allelic safety guard. JSON `schema_version` bumped 4 → 5
|
|
58
|
-
> (additive, v4 readers unaffected).
|
|
59
|
-
> Release notes:
|
|
60
|
-
> [`CHANGELOG.md`](https://github.com/allelix/allelix/blob/main/CHANGELOG.md).
|
|
58
|
+
> **Status:** Production. Eight parser formats (including VCF + gVCF), four annotators (ClinVar + ClinPGx + GWAS Catalog + SNPedia), three enrichment sources (gnomAD + AlphaMissense + CADD), licensable-source gating for commercial users, dual-build ClinVar caches (GRCh37 + GRCh38), HTML/JSON/terminal reports, methylation + pharmacogenomics focused commands, report diffing, persistent config with commercial-mode safety switch. Build auto-detection from position data (ADR-0021). No regex on prose anywhere in production. The [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md) tracks every release.
|
|
61
59
|
|
|
62
60
|
## Quickstart
|
|
63
61
|
|
|
@@ -101,16 +99,17 @@ See [Development](#development) for source installs and running tests, [Managing
|
|
|
101
99
|
|
|
102
100
|
Adding a new format means adding one file to `allelix/parsers/` and registering an instance in the `PARSERS` list in `allelix/parsers/__init__.py`.
|
|
103
101
|
|
|
104
|
-
###
|
|
102
|
+
### Roadmap
|
|
105
103
|
|
|
106
|
-
|
|
|
104
|
+
| Release | Theme |
|
|
107
105
|
|---|---|
|
|
108
|
-
|
|
|
109
|
-
|
|
|
110
|
-
|
|
|
111
|
-
|
|
|
112
|
-
|
|
|
113
|
-
|
|
106
|
+
| **v2.2** | Annotation model expansion (cross-source conflict surfacing, risk-allele display, structured MTAG flag), custom-panel UX improvements, methylation panel retune + citations, ClinVar source switch (VCF → `variant_summary.txt`), CLI cleanups (`db clean` / `db path`). |
|
|
107
|
+
| **v2.3** | Per-source magnitude decomposition, Good / Bad / Neutral annotation repute, JSON v3 (AI-legible output contract), plausibility flagging via zygosity × gnomAD MAF, ClinVar review-status weighting, functional-medicine +/− notation. |
|
|
108
|
+
| **v2.4** | ACMG/AMP automated classification engine, variant consequence annotation (VEP-equivalent). |
|
|
109
|
+
| **v2.5+** | CNVs and repeat-expansion support, dbSNP resolution for rsID-less VCFs, gnomAD genomes (non-coding population frequencies), supplemental genotype file merging. |
|
|
110
|
+
| **v3.0** | Ancestry estimation (PCA + 1KG/HGDP reference panels), Polygenic Risk Score (PRS) integration. |
|
|
111
|
+
|
|
112
|
+
Issues and milestones are tracked at [github.com/allelix/allelix/issues](https://github.com/allelix/allelix/issues). Annotator-level strand-aware carrier matching (R-1) shipped in v2.1.0's ADR-0035 cluster.
|
|
114
113
|
|
|
115
114
|
## Supported Databases
|
|
116
115
|
|
|
@@ -1,26 +1,22 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="https://allelix.io/apple-touch-icon.png" width="180" alt="Allelix" />
|
|
4
|
+
|
|
5
|
+
**Open-source genotype analysis. Format-agnostic. Offline-first.**
|
|
6
|
+
|
|
7
|
+
[Website](https://allelix.io) · [Quickstart](#quickstart) · [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md)
|
|
8
|
+
|
|
9
|
+
[](https://www.python.org/downloads/)
|
|
10
|
+
[](https://pypi.org/project/allelix/)
|
|
11
|
+
[](LICENSE)
|
|
12
|
+
[](https://github.com/allelix/allelix/actions/workflows/ci.yml)
|
|
13
|
+
[](#privacy)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
1
17
|
# Allelix
|
|
2
18
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
> **Status:** Production — eight parser formats (including VCF + gVCF),
|
|
6
|
-
> four annotators (ClinVar + ClinPGx + GWAS Catalog + SNPedia), three
|
|
7
|
-
> enrichment sources (gnomAD population frequencies + AlphaMissense
|
|
8
|
-
> pathogenicity + CADD deleteriousness), licensable-source gating for
|
|
9
|
-
> commercial users, dual-build ClinVar caches (GRCh37 + GRCh38),
|
|
10
|
-
> HTML/JSON/terminal reports, methylation + pharmacogenomics focused
|
|
11
|
-
> commands, report diffing, persistent config with commercial-mode
|
|
12
|
-
> safety switch. Build auto-detection from position data (ADR-0021).
|
|
13
|
-
> No regex on prose anywhere in production. **Latest: v2.1.0** —
|
|
14
|
-
> ADR-0035 'Cluster B' coordinated model change: `Variant.ref` carries
|
|
15
|
-
> the forward-strand reference allele, per-Annotation `alt` threading
|
|
16
|
-
> on GWAS / SNPedia / ClinPGx enables allele-specific gnomAD /
|
|
17
|
-
> AlphaMissense / CADD enrichment on array inputs, structured GWAS
|
|
18
|
-
> fields (`trait` / `p_value` / `phecode`) replace prose re-parsing in
|
|
19
|
-
> the rollup, and ClinVar / ClinPGx carrier matching is strand-aware
|
|
20
|
-
> with multi-allelic safety guard. JSON `schema_version` bumped 4 → 5
|
|
21
|
-
> (additive, v4 readers unaffected).
|
|
22
|
-
> Release notes:
|
|
23
|
-
> [`CHANGELOG.md`](https://github.com/allelix/allelix/blob/main/CHANGELOG.md).
|
|
19
|
+
> **Status:** Production. Eight parser formats (including VCF + gVCF), four annotators (ClinVar + ClinPGx + GWAS Catalog + SNPedia), three enrichment sources (gnomAD + AlphaMissense + CADD), licensable-source gating for commercial users, dual-build ClinVar caches (GRCh37 + GRCh38), HTML/JSON/terminal reports, methylation + pharmacogenomics focused commands, report diffing, persistent config with commercial-mode safety switch. Build auto-detection from position data (ADR-0021). No regex on prose anywhere in production. The [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md) tracks every release.
|
|
24
20
|
|
|
25
21
|
## Quickstart
|
|
26
22
|
|
|
@@ -64,16 +60,17 @@ See [Development](#development) for source installs and running tests, [Managing
|
|
|
64
60
|
|
|
65
61
|
Adding a new format means adding one file to `allelix/parsers/` and registering an instance in the `PARSERS` list in `allelix/parsers/__init__.py`.
|
|
66
62
|
|
|
67
|
-
###
|
|
63
|
+
### Roadmap
|
|
68
64
|
|
|
69
|
-
|
|
|
65
|
+
| Release | Theme |
|
|
70
66
|
|---|---|
|
|
71
|
-
|
|
|
72
|
-
|
|
|
73
|
-
|
|
|
74
|
-
|
|
|
75
|
-
|
|
|
76
|
-
|
|
67
|
+
| **v2.2** | Annotation model expansion (cross-source conflict surfacing, risk-allele display, structured MTAG flag), custom-panel UX improvements, methylation panel retune + citations, ClinVar source switch (VCF → `variant_summary.txt`), CLI cleanups (`db clean` / `db path`). |
|
|
68
|
+
| **v2.3** | Per-source magnitude decomposition, Good / Bad / Neutral annotation repute, JSON v3 (AI-legible output contract), plausibility flagging via zygosity × gnomAD MAF, ClinVar review-status weighting, functional-medicine +/− notation. |
|
|
69
|
+
| **v2.4** | ACMG/AMP automated classification engine, variant consequence annotation (VEP-equivalent). |
|
|
70
|
+
| **v2.5+** | CNVs and repeat-expansion support, dbSNP resolution for rsID-less VCFs, gnomAD genomes (non-coding population frequencies), supplemental genotype file merging. |
|
|
71
|
+
| **v3.0** | Ancestry estimation (PCA + 1KG/HGDP reference panels), Polygenic Risk Score (PRS) integration. |
|
|
72
|
+
|
|
73
|
+
Issues and milestones are tracked at [github.com/allelix/allelix/issues](https://github.com/allelix/allelix/issues). Annotator-level strand-aware carrier matching (R-1) shipped in v2.1.0's ADR-0035 cluster.
|
|
77
74
|
|
|
78
75
|
## Supported Databases
|
|
79
76
|
|
|
@@ -57,18 +57,16 @@ def get_annotators(
|
|
|
57
57
|
return [clinvar, pharmgkb, gwas, snpedia, gnomad, alphamissense, cadd]
|
|
58
58
|
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
]
|
|
71
|
-
}
|
|
60
|
+
_ANNOTATOR_CLASS_LIST: list[type[Annotator]] = [
|
|
61
|
+
ClinVarAnnotator,
|
|
62
|
+
PharmGKBAnnotator,
|
|
63
|
+
GWASCatalogAnnotator,
|
|
64
|
+
SNPediaAnnotator,
|
|
65
|
+
GnomadAnnotator,
|
|
66
|
+
AlphaMissenseAnnotator,
|
|
67
|
+
CaddAnnotator,
|
|
68
|
+
]
|
|
69
|
+
_ANNOTATOR_CLASSES: dict[str, type[Annotator]] = {cls.name: cls for cls in _ANNOTATOR_CLASS_LIST}
|
|
72
70
|
|
|
73
71
|
|
|
74
72
|
def get_annotator_class(name: str) -> type[Annotator] | None:
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
import contextlib
|
|
8
7
|
from abc import ABC, abstractmethod
|
|
9
8
|
from dataclasses import dataclass
|
|
10
9
|
from enum import Enum, auto
|
|
@@ -141,43 +140,6 @@ class Annotator(ABC):
|
|
|
141
140
|
"""Bind the annotator to a data directory (created elsewhere)."""
|
|
142
141
|
self.data_dir = data_dir
|
|
143
142
|
|
|
144
|
-
def __del__(self) -> None:
|
|
145
|
-
"""Safety-net resource release on GC. Deliberately retained.
|
|
146
|
-
|
|
147
|
-
Status (v2.1.1 audit, GH #36):
|
|
148
|
-
- **Production callsites are clean.** ``run_analysis`` wires every
|
|
149
|
-
primary + enrichment annotator into ``contextlib.ExitStack``
|
|
150
|
-
(v2.0.2 fix). ``allelix/cli/utility.py`` lines 221 and 476 each
|
|
151
|
-
construct a ``GnomadAnnotator`` inside ``try / finally
|
|
152
|
-
gnomad.close()``. ``allelix/annotators/__init__.py`` returns a
|
|
153
|
-
list that the pipeline immediately wraps in ``ExitStack``.
|
|
154
|
-
- **Test callsites NOT yet clean.** ~27 sites in
|
|
155
|
-
``tests/reports/test_pipeline.py`` instantiate annotators
|
|
156
|
-
directly without a context manager or explicit close. The
|
|
157
|
-
pipeline closes them via its own ``ExitStack`` during
|
|
158
|
-
``run_analysis``, but the test still holds the closed-instance
|
|
159
|
-
reference, and the underlying sqlite3 ``Connection``'s GC
|
|
160
|
-
finalizer surfaces a ``ResourceWarning`` during the next test's
|
|
161
|
-
startup. pytest's ``error::PytestUnraisableExceptionWarning``
|
|
162
|
-
filter then converts it to a hard failure on the gate.
|
|
163
|
-
|
|
164
|
-
Removing ``__del__`` without first refactoring the test sites
|
|
165
|
-
therefore fails the gate (verified — 3 failures, all in
|
|
166
|
-
``tests/reports/test_pipeline.py``, triggered by leaks from
|
|
167
|
-
earlier tests in the same file). The test-site refactor is
|
|
168
|
-
tracked separately; ``__del__`` stays as the safety net until
|
|
169
|
-
that lands. v2.1.x task — see the follow-up issue filed at
|
|
170
|
-
``#36``-closure-time.
|
|
171
|
-
|
|
172
|
-
``contextlib.suppress(Exception)`` is deliberate — ``__del__``
|
|
173
|
-
must never raise. GC timing and shutdown-ordering edges are
|
|
174
|
-
explicitly silenced; this is exactly the "if you must keep
|
|
175
|
-
``__del__``, make absolutely sure it can never raise"
|
|
176
|
-
mitigation the audit recommended.
|
|
177
|
-
"""
|
|
178
|
-
with contextlib.suppress(Exception):
|
|
179
|
-
self.close()
|
|
180
|
-
|
|
181
143
|
def __enter__(self) -> Annotator:
|
|
182
144
|
"""Return self for `with` usage."""
|
|
183
145
|
return self
|
|
@@ -24,7 +24,7 @@ from __future__ import annotations
|
|
|
24
24
|
|
|
25
25
|
import logging
|
|
26
26
|
import sqlite3
|
|
27
|
-
from typing import TYPE_CHECKING, ClassVar
|
|
27
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
28
28
|
|
|
29
29
|
from allelix.annotators.base import Annotator, LicenseDescriptor
|
|
30
30
|
from allelix.databases._versions import CADD_SCHEMA_VERSION
|
|
@@ -90,9 +90,11 @@ class CaddAnnotator(Annotator):
|
|
|
90
90
|
self._conn: sqlite3.Connection | None = None
|
|
91
91
|
self._full_mode = full_mode
|
|
92
92
|
self._tabix_path = data_dir / CADD_FULL_FILENAME
|
|
93
|
-
|
|
93
|
+
# pysam ships no type stubs (see [tool.mypy.overrides]); the tabix
|
|
94
|
+
# handle is held as Any so call sites can use `.fetch()` / `.close()`.
|
|
95
|
+
self._tabix: Any = None
|
|
94
96
|
self._indel_tabix_path = data_dir / CADD_INDEL_FILENAME
|
|
95
|
-
self._indel_tabix:
|
|
97
|
+
self._indel_tabix: Any = None
|
|
96
98
|
|
|
97
99
|
def _connection(self) -> sqlite3.Connection:
|
|
98
100
|
if self._conn is None:
|
|
@@ -104,11 +106,11 @@ class CaddAnnotator(Annotator):
|
|
|
104
106
|
self._conn = sqlite3.connect(self._db_path)
|
|
105
107
|
return self._conn
|
|
106
108
|
|
|
107
|
-
def _open_tabix(self) ->
|
|
109
|
+
def _open_tabix(self) -> Any: # noqa: ANN401 — pysam ships no type stubs
|
|
108
110
|
"""Open the tabix file for full-mode queries."""
|
|
109
111
|
if self._tabix is None:
|
|
110
112
|
try:
|
|
111
|
-
import pysam
|
|
113
|
+
import pysam
|
|
112
114
|
except ImportError:
|
|
113
115
|
raise ImportError(
|
|
114
116
|
"Full CADD mode requires pysam. Install with: pip install 'allelix[cadd]'"
|
|
@@ -122,7 +124,7 @@ class CaddAnnotator(Annotator):
|
|
|
122
124
|
self._tabix = pysam.TabixFile(str(self._tabix_path))
|
|
123
125
|
return self._tabix
|
|
124
126
|
|
|
125
|
-
def _open_indel_tabix(self) ->
|
|
127
|
+
def _open_indel_tabix(self) -> Any: # noqa: ANN401 — pysam ships no type stubs
|
|
126
128
|
"""Open the indel tabix file. Returns None if file doesn't exist."""
|
|
127
129
|
if self._indel_tabix is None:
|
|
128
130
|
if not self._indel_tabix_path.exists():
|
|
@@ -131,7 +133,7 @@ class CaddAnnotator(Annotator):
|
|
|
131
133
|
if not tbi.exists():
|
|
132
134
|
return None
|
|
133
135
|
try:
|
|
134
|
-
import pysam
|
|
136
|
+
import pysam
|
|
135
137
|
except ImportError:
|
|
136
138
|
return None
|
|
137
139
|
self._indel_tabix = pysam.TabixFile(str(self._indel_tabix_path))
|
|
@@ -500,9 +500,10 @@ class ClinVarAnnotator(Annotator):
|
|
|
500
500
|
for rows in clinvar_rows.values():
|
|
501
501
|
rows.sort()
|
|
502
502
|
for v in build_variants:
|
|
503
|
-
|
|
504
|
-
if not
|
|
503
|
+
pos_rows = clinvar_rows.get((v.chromosome, v.position))
|
|
504
|
+
if not pos_rows:
|
|
505
505
|
continue
|
|
506
|
+
rows = pos_rows
|
|
506
507
|
user_alleles = {v.allele1, v.allele2}
|
|
507
508
|
# Stamp only on a UNIQUE consistent match. Multiple matches
|
|
508
509
|
# mean the Variant model can't distinguish them (the 1/1
|
|
@@ -16,7 +16,7 @@ import logging
|
|
|
16
16
|
import re
|
|
17
17
|
import sys
|
|
18
18
|
import time
|
|
19
|
-
from typing import TYPE_CHECKING, NamedTuple
|
|
19
|
+
from typing import TYPE_CHECKING, NamedTuple, cast
|
|
20
20
|
|
|
21
21
|
import click
|
|
22
22
|
from rich.console import Console
|
|
@@ -34,7 +34,10 @@ from allelix.reports.terminal import render_terminal, render_terminal_diff
|
|
|
34
34
|
if TYPE_CHECKING:
|
|
35
35
|
from pathlib import Path
|
|
36
36
|
|
|
37
|
+
from allelix.annotators.alphamissense import AlphaMissenseAnnotator
|
|
37
38
|
from allelix.annotators.base import Annotator
|
|
39
|
+
from allelix.annotators.cadd import CaddAnnotator
|
|
40
|
+
from allelix.annotators.gnomad import GnomadAnnotator
|
|
38
41
|
from allelix.parsers.base import GenotypeParser
|
|
39
42
|
|
|
40
43
|
console = Console()
|
|
@@ -489,9 +492,9 @@ def _run_analysis_command(
|
|
|
489
492
|
ready,
|
|
490
493
|
skipped_count_provider=lambda: counter.count,
|
|
491
494
|
build_override=build,
|
|
492
|
-
gnomad=gnomad_annotator,
|
|
493
|
-
alphamissense=am_annotator,
|
|
494
|
-
cadd=cadd_annotator,
|
|
495
|
+
gnomad=cast("GnomadAnnotator | None", gnomad_annotator),
|
|
496
|
+
alphamissense=cast("AlphaMissenseAnnotator | None", am_annotator),
|
|
497
|
+
cadd=cast("CaddAnnotator | None", cadd_annotator),
|
|
495
498
|
high_value_rsids=hv_rsids,
|
|
496
499
|
)
|
|
497
500
|
finally:
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import TYPE_CHECKING
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
9
|
|
|
10
10
|
import click
|
|
11
11
|
from rich.table import Table
|
|
@@ -205,7 +205,7 @@ def _execute_tabix_extract( # pragma: no cover
|
|
|
205
205
|
wanted: set[str],
|
|
206
206
|
sample: str | None,
|
|
207
207
|
data_dir: Path | None,
|
|
208
|
-
pysam:
|
|
208
|
+
pysam: Any, # noqa: ANN401 — pysam ships no type stubs
|
|
209
209
|
) -> dict[str, Variant] | None:
|
|
210
210
|
"""Real tabix execution.
|
|
211
211
|
|
|
@@ -443,7 +443,7 @@ def export_plink_cmd(
|
|
|
443
443
|
coding when available; falls back to monomorphic (A2=0) for positions
|
|
444
444
|
without gnomAD coverage.
|
|
445
445
|
"""
|
|
446
|
-
from allelix.exporters.plink import
|
|
446
|
+
from allelix.exporters.plink import export_plink, resolve_ref_alt_via_gnomad
|
|
447
447
|
|
|
448
448
|
parser = _helpers._resolve_parser(file_path, fmt, sample=sample)
|
|
449
449
|
prefix = output_prefix if output_prefix else file_path.with_suffix("")
|
|
@@ -462,45 +462,15 @@ def export_plink_cmd(
|
|
|
462
462
|
key=lambda v: (chrom_order.get(v.chromosome, 99), v.chromosome, v.position),
|
|
463
463
|
)
|
|
464
464
|
|
|
465
|
-
variant_by_rsid: dict[str, Variant] = {}
|
|
466
|
-
for v in variants:
|
|
467
|
-
if not v.is_no_call:
|
|
468
|
-
variant_by_rsid[v.rsid] = v
|
|
469
|
-
rsids = set(variant_by_rsid)
|
|
465
|
+
variant_by_rsid: dict[str, Variant] = {v.rsid: v for v in variants if not v.is_no_call}
|
|
470
466
|
|
|
471
|
-
ref_alt_map: dict[str, tuple[str, str]] = {}
|
|
472
|
-
gnomad = None
|
|
473
467
|
try:
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
gnomad = GnomadAnnotator(resolved)
|
|
477
|
-
if gnomad.is_ready():
|
|
478
|
-
coord_map = gnomad.bulk_resolve_coordinates(rsids)
|
|
479
|
-
for rsid, coords in coord_map.items():
|
|
480
|
-
if len(coords) == 1:
|
|
481
|
-
_, _, ref, alt = coords[0]
|
|
482
|
-
ref_alt_map[rsid] = (ref, alt)
|
|
483
|
-
else:
|
|
484
|
-
v = variant_by_rsid[rsid]
|
|
485
|
-
pair = {v.allele1, v.allele2}
|
|
486
|
-
for _, _, ref, alt in coords:
|
|
487
|
-
if _orient_genotype(
|
|
488
|
-
v.allele1, v.allele2, ref, alt
|
|
489
|
-
) is not None and pair <= {ref, alt}:
|
|
490
|
-
ref_alt_map[rsid] = (ref, alt)
|
|
491
|
-
break
|
|
492
|
-
else:
|
|
493
|
-
for _, _, ref, alt in coords:
|
|
494
|
-
if _orient_genotype(v.allele1, v.allele2, ref, alt) is not None:
|
|
495
|
-
ref_alt_map[rsid] = (ref, alt)
|
|
496
|
-
break
|
|
468
|
+
ref_alt_map = resolve_ref_alt_via_gnomad(variant_by_rsid, resolved)
|
|
497
469
|
except Exception:
|
|
498
470
|
console.print(
|
|
499
471
|
"[yellow]gnomAD coordinate resolution failed; using fallback allele coding.[/yellow]"
|
|
500
472
|
)
|
|
501
|
-
|
|
502
|
-
if gnomad is not None:
|
|
503
|
-
gnomad.close()
|
|
473
|
+
ref_alt_map = {}
|
|
504
474
|
|
|
505
475
|
written, skipped, indel_skip, mono = export_plink(
|
|
506
476
|
iter(variants), prefix, effective_build, ref_alt_map or None
|
|
@@ -15,6 +15,8 @@ from allelix.annotators.base import permission as check_permission
|
|
|
15
15
|
if TYPE_CHECKING:
|
|
16
16
|
from pathlib import Path
|
|
17
17
|
|
|
18
|
+
from allelix.annotators.base import Annotator
|
|
19
|
+
|
|
18
20
|
logger = logging.getLogger(__name__)
|
|
19
21
|
|
|
20
22
|
CONFIG_FILENAME = "config.toml"
|
|
@@ -46,7 +48,7 @@ class AllelixConfig:
|
|
|
46
48
|
def is_enabled(
|
|
47
49
|
self,
|
|
48
50
|
source_name: str,
|
|
49
|
-
annotator_classes: dict[str, type] | None = None,
|
|
51
|
+
annotator_classes: dict[str, type[Annotator]] | None = None,
|
|
50
52
|
) -> bool:
|
|
51
53
|
"""Check if a source is enabled, respecting the permission ladder.
|
|
52
54
|
|
|
@@ -211,8 +211,8 @@ def fetch_cpic_allele_functions(
|
|
|
211
211
|
|
|
212
212
|
out: dict[tuple[str, str], str] = {}
|
|
213
213
|
for row in location_values:
|
|
214
|
-
rsid = location_to_rsid.get(row.get("locationid"))
|
|
215
|
-
function_class = allele_to_function.get(row.get("alleledefinitionid"))
|
|
214
|
+
rsid = location_to_rsid.get(row.get("locationid")) # type: ignore[arg-type]
|
|
215
|
+
function_class = allele_to_function.get(row.get("alleledefinitionid")) # type: ignore[arg-type]
|
|
216
216
|
base = (row.get("variantallele") or "").strip().upper()
|
|
217
217
|
if not rsid or function_class is None:
|
|
218
218
|
continue
|
|
@@ -15,7 +15,7 @@ import os
|
|
|
15
15
|
import re
|
|
16
16
|
import sqlite3
|
|
17
17
|
from datetime import UTC, datetime
|
|
18
|
-
from typing import TYPE_CHECKING
|
|
18
|
+
from typing import TYPE_CHECKING, cast
|
|
19
19
|
|
|
20
20
|
from allelix.databases.schema import GWAS_SCHEMA
|
|
21
21
|
|
|
@@ -458,7 +458,7 @@ def iter_gwas_records(tsv_path: Path) -> Iterator[dict[str, object]]:
|
|
|
458
458
|
if existing is None:
|
|
459
459
|
best[key] = record
|
|
460
460
|
else:
|
|
461
|
-
ep = existing["p_value"]
|
|
461
|
+
ep = cast("float | None", existing["p_value"])
|
|
462
462
|
if p_value is not None and (ep is None or p_value < ep):
|
|
463
463
|
best[key] = record
|
|
464
464
|
|
|
@@ -61,8 +61,10 @@ def _parse_title_alleles(title: str) -> tuple[str, str] | None:
|
|
|
61
61
|
|
|
62
62
|
def _tmpl_param(tmpl: object, name: str) -> str:
|
|
63
63
|
"""Extract a named parameter from a mwparserfromhell template."""
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
# mwparserfromhell ships no type stubs (see [[tool.mypy.overrides]]); the
|
|
65
|
+
# parameter is typed as `object` at the boundary, then unwrapped here.
|
|
66
|
+
if tmpl.has(name): # type: ignore[attr-defined]
|
|
67
|
+
return str(tmpl.get(name).value).strip() # type: ignore[attr-defined]
|
|
66
68
|
return ""
|
|
67
69
|
|
|
68
70
|
|
|
@@ -302,9 +304,9 @@ def _parse_raw_pages_inner(conn: sqlite3.Connection, *, verbose: bool = False) -
|
|
|
302
304
|
|
|
303
305
|
repute = _tmpl_param(tmpl, "repute") or None
|
|
304
306
|
summary = _tmpl_param(tmpl, "summary") or None
|
|
305
|
-
|
|
307
|
+
snp_gene = gene_map.get(snp_id) or None
|
|
306
308
|
|
|
307
|
-
batch.append((snp_id, allele1, allele2, magnitude, repute, summary,
|
|
309
|
+
batch.append((snp_id, allele1, allele2, magnitude, repute, summary, snp_gene, scraped_at))
|
|
308
310
|
|
|
309
311
|
if len(batch) >= 1000:
|
|
310
312
|
conn.executemany(
|
|
@@ -9,7 +9,7 @@ from typing import TYPE_CHECKING
|
|
|
9
9
|
from allelix.utils.allele import complement, is_strand_ambiguous
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
|
-
from collections.abc import Iterator
|
|
12
|
+
from collections.abc import Iterator, Mapping
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
|
|
15
15
|
from allelix.models import Variant
|
|
@@ -132,6 +132,55 @@ def export_plink(
|
|
|
132
132
|
return written, skipped, indels, monomorphic
|
|
133
133
|
|
|
134
134
|
|
|
135
|
+
def resolve_ref_alt_via_gnomad(
|
|
136
|
+
variant_by_rsid: Mapping[str, Variant], data_dir: Path
|
|
137
|
+
) -> dict[str, tuple[str, str]]:
|
|
138
|
+
"""Return per-rsid (ref, alt) for PLINK A1/A2 coding via gnomAD.
|
|
139
|
+
|
|
140
|
+
For each rsid in ``variant_by_rsid``, looks up gnomAD's coordinate
|
|
141
|
+
record(s). When gnomAD returns a single record, that ref/alt pair is
|
|
142
|
+
used directly. When gnomAD returns multiple records (multi-allelic
|
|
143
|
+
positions), the user's allele pair is matched against each candidate
|
|
144
|
+
via ``_orient_genotype`` and the first match that's also a subset of
|
|
145
|
+
``{ref, alt}`` wins; if none is a subset, the first orientation-valid
|
|
146
|
+
record wins as a fallback.
|
|
147
|
+
|
|
148
|
+
Returns an empty dict if gnomAD isn't ready. Raises on any underlying
|
|
149
|
+
lookup error — the CLI catches and prints the "failed" warning, then
|
|
150
|
+
proceeds with empty (monomorphic) coding.
|
|
151
|
+
"""
|
|
152
|
+
from allelix.annotators.gnomad import GnomadAnnotator
|
|
153
|
+
|
|
154
|
+
gnomad = GnomadAnnotator(data_dir)
|
|
155
|
+
try:
|
|
156
|
+
if not gnomad.is_ready():
|
|
157
|
+
return {}
|
|
158
|
+
coord_map = gnomad.bulk_resolve_coordinates(set(variant_by_rsid))
|
|
159
|
+
result: dict[str, tuple[str, str]] = {}
|
|
160
|
+
for rsid, coords in coord_map.items():
|
|
161
|
+
if len(coords) == 1:
|
|
162
|
+
_, _, ref, alt = coords[0]
|
|
163
|
+
result[rsid] = (ref, alt)
|
|
164
|
+
continue
|
|
165
|
+
v = variant_by_rsid[rsid]
|
|
166
|
+
pair = {v.allele1, v.allele2}
|
|
167
|
+
for _, _, ref, alt in coords:
|
|
168
|
+
if _orient_genotype(v.allele1, v.allele2, ref, alt) is not None and pair <= {
|
|
169
|
+
ref,
|
|
170
|
+
alt,
|
|
171
|
+
}:
|
|
172
|
+
result[rsid] = (ref, alt)
|
|
173
|
+
break
|
|
174
|
+
else:
|
|
175
|
+
for _, _, ref, alt in coords:
|
|
176
|
+
if _orient_genotype(v.allele1, v.allele2, ref, alt) is not None:
|
|
177
|
+
result[rsid] = (ref, alt)
|
|
178
|
+
break
|
|
179
|
+
return result
|
|
180
|
+
finally:
|
|
181
|
+
gnomad.close()
|
|
182
|
+
|
|
183
|
+
|
|
135
184
|
def _fallback_coding(v: Variant) -> tuple[str, str, int, bool]:
|
|
136
185
|
"""Fallback allele coding when ref/alt is unknown.
|
|
137
186
|
|
|
@@ -17,7 +17,7 @@ from __future__ import annotations
|
|
|
17
17
|
import contextlib
|
|
18
18
|
import logging
|
|
19
19
|
from dataclasses import dataclass, field, replace
|
|
20
|
-
from typing import TYPE_CHECKING
|
|
20
|
+
from typing import TYPE_CHECKING, cast
|
|
21
21
|
|
|
22
22
|
from allelix.utils.build_detect import (
|
|
23
23
|
BUILD_GRCH36,
|
|
@@ -35,6 +35,7 @@ if TYPE_CHECKING:
|
|
|
35
35
|
from allelix.annotators.alphamissense import AlphaMissenseAnnotator
|
|
36
36
|
from allelix.annotators.base import Annotator
|
|
37
37
|
from allelix.annotators.cadd import CaddAnnotator
|
|
38
|
+
from allelix.annotators.clinvar import ClinVarAnnotator
|
|
38
39
|
from allelix.annotators.gnomad import GnomadAnnotator
|
|
39
40
|
from allelix.models import Annotation, Variant
|
|
40
41
|
from allelix.parsers.base import GenotypeParser
|
|
@@ -293,9 +294,9 @@ def _enrich_cadd(
|
|
|
293
294
|
|
|
294
295
|
for a in annotations:
|
|
295
296
|
if a.alt:
|
|
296
|
-
|
|
297
|
-
if
|
|
298
|
-
a.cadd_phred = _lookup_user_allele(a.alt,
|
|
297
|
+
ann_coords = coord_map.get(a.rsid)
|
|
298
|
+
if ann_coords:
|
|
299
|
+
a.cadd_phred = _lookup_user_allele(a.alt, ann_coords, scores)
|
|
299
300
|
elif resolved_coords and a.rsid in resolved_coords:
|
|
300
301
|
# Position fallback: rsID was resolved on the fly via
|
|
301
302
|
# ClinVar; the resolved tuple carries the user's actual
|
|
@@ -399,7 +400,9 @@ def run_analysis(
|
|
|
399
400
|
# rsID-indexed annotators. GH #8.
|
|
400
401
|
rsidless = [v for v in batch_buf if not v.rsid.startswith("rs")]
|
|
401
402
|
if rsidless:
|
|
402
|
-
resolution = clinvar_resolver.bulk_resolve_rsids(
|
|
403
|
+
resolution = cast("ClinVarAnnotator", clinvar_resolver).bulk_resolve_rsids(
|
|
404
|
+
rsidless
|
|
405
|
+
)
|
|
403
406
|
for (chrom, pos, ref, alt), rsid in resolution.items():
|
|
404
407
|
resolved_coords[rsid] = (chrom, pos, ref, alt)
|
|
405
408
|
# ADR-0035 PR 4: populate Variant.ref for array data via gnomAD's
|
|
@@ -600,6 +603,7 @@ class _BuildDetectionState:
|
|
|
600
603
|
if variant.rsid in KNOWN_SNP_POSITIONS:
|
|
601
604
|
result = detect_build(self._buffer)
|
|
602
605
|
if result.is_confident:
|
|
606
|
+
assert result.build is not None # is_confident ⇒ build set
|
|
603
607
|
self.detected = result.build
|
|
604
608
|
self.matched_count = result.matched
|
|
605
609
|
self.inspected_count = result.inspected
|
|
@@ -653,6 +657,7 @@ class _BuildDetectionState:
|
|
|
653
657
|
)
|
|
654
658
|
self.matched_count = result.matched
|
|
655
659
|
self.inspected_count = result.inspected
|
|
660
|
+
assert self.effective is not None # all branches above set it
|
|
656
661
|
out = [replace(v, build=self.effective) for v in self._buffer]
|
|
657
662
|
self._buffer.clear()
|
|
658
663
|
return out
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: allelix
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.2
|
|
4
4
|
Summary: Open-source genotype analysis toolkit. Format-agnostic ingestion, database-agnostic annotation, offline-first.
|
|
5
5
|
Author: Allelix
|
|
6
6
|
Maintainer-email: dial481 <dial481@users.noreply.github.com>
|
|
@@ -29,35 +29,33 @@ Requires-Dist: pysam>=0.22; extra == "cadd"
|
|
|
29
29
|
Provides-Extra: vcf-index
|
|
30
30
|
Requires-Dist: pysam>=0.22; extra == "vcf-index"
|
|
31
31
|
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: mypy>=1.11; extra == "dev"
|
|
32
33
|
Requires-Dist: pre-commit>=3.7; extra == "dev"
|
|
33
34
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
34
35
|
Requires-Dist: pytest-cov<8,>=7; extra == "dev"
|
|
35
36
|
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
37
|
+
Requires-Dist: types-PyYAML>=6.0; extra == "dev"
|
|
36
38
|
Dynamic: license-file
|
|
37
39
|
|
|
40
|
+
<div align="center">
|
|
41
|
+
|
|
42
|
+
<img src="https://allelix.io/apple-touch-icon.png" width="180" alt="Allelix" />
|
|
43
|
+
|
|
44
|
+
**Open-source genotype analysis. Format-agnostic. Offline-first.**
|
|
45
|
+
|
|
46
|
+
[Website](https://allelix.io) · [Quickstart](#quickstart) · [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md)
|
|
47
|
+
|
|
48
|
+
[](https://www.python.org/downloads/)
|
|
49
|
+
[](https://pypi.org/project/allelix/)
|
|
50
|
+
[](LICENSE)
|
|
51
|
+
[](https://github.com/allelix/allelix/actions/workflows/ci.yml)
|
|
52
|
+
[](#privacy)
|
|
53
|
+
|
|
54
|
+
</div>
|
|
55
|
+
|
|
38
56
|
# Allelix
|
|
39
57
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
> **Status:** Production — eight parser formats (including VCF + gVCF),
|
|
43
|
-
> four annotators (ClinVar + ClinPGx + GWAS Catalog + SNPedia), three
|
|
44
|
-
> enrichment sources (gnomAD population frequencies + AlphaMissense
|
|
45
|
-
> pathogenicity + CADD deleteriousness), licensable-source gating for
|
|
46
|
-
> commercial users, dual-build ClinVar caches (GRCh37 + GRCh38),
|
|
47
|
-
> HTML/JSON/terminal reports, methylation + pharmacogenomics focused
|
|
48
|
-
> commands, report diffing, persistent config with commercial-mode
|
|
49
|
-
> safety switch. Build auto-detection from position data (ADR-0021).
|
|
50
|
-
> No regex on prose anywhere in production. **Latest: v2.1.0** —
|
|
51
|
-
> ADR-0035 'Cluster B' coordinated model change: `Variant.ref` carries
|
|
52
|
-
> the forward-strand reference allele, per-Annotation `alt` threading
|
|
53
|
-
> on GWAS / SNPedia / ClinPGx enables allele-specific gnomAD /
|
|
54
|
-
> AlphaMissense / CADD enrichment on array inputs, structured GWAS
|
|
55
|
-
> fields (`trait` / `p_value` / `phecode`) replace prose re-parsing in
|
|
56
|
-
> the rollup, and ClinVar / ClinPGx carrier matching is strand-aware
|
|
57
|
-
> with multi-allelic safety guard. JSON `schema_version` bumped 4 → 5
|
|
58
|
-
> (additive, v4 readers unaffected).
|
|
59
|
-
> Release notes:
|
|
60
|
-
> [`CHANGELOG.md`](https://github.com/allelix/allelix/blob/main/CHANGELOG.md).
|
|
58
|
+
> **Status:** Production. Eight parser formats (including VCF + gVCF), four annotators (ClinVar + ClinPGx + GWAS Catalog + SNPedia), three enrichment sources (gnomAD + AlphaMissense + CADD), licensable-source gating for commercial users, dual-build ClinVar caches (GRCh37 + GRCh38), HTML/JSON/terminal reports, methylation + pharmacogenomics focused commands, report diffing, persistent config with commercial-mode safety switch. Build auto-detection from position data (ADR-0021). No regex on prose anywhere in production. The [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md) tracks every release.
|
|
61
59
|
|
|
62
60
|
## Quickstart
|
|
63
61
|
|
|
@@ -101,16 +99,17 @@ See [Development](#development) for source installs and running tests, [Managing
|
|
|
101
99
|
|
|
102
100
|
Adding a new format means adding one file to `allelix/parsers/` and registering an instance in the `PARSERS` list in `allelix/parsers/__init__.py`.
|
|
103
101
|
|
|
104
|
-
###
|
|
102
|
+
### Roadmap
|
|
105
103
|
|
|
106
|
-
|
|
|
104
|
+
| Release | Theme |
|
|
107
105
|
|---|---|
|
|
108
|
-
|
|
|
109
|
-
|
|
|
110
|
-
|
|
|
111
|
-
|
|
|
112
|
-
|
|
|
113
|
-
|
|
106
|
+
| **v2.2** | Annotation model expansion (cross-source conflict surfacing, risk-allele display, structured MTAG flag), custom-panel UX improvements, methylation panel retune + citations, ClinVar source switch (VCF → `variant_summary.txt`), CLI cleanups (`db clean` / `db path`). |
|
|
107
|
+
| **v2.3** | Per-source magnitude decomposition, Good / Bad / Neutral annotation repute, JSON v3 (AI-legible output contract), plausibility flagging via zygosity × gnomAD MAF, ClinVar review-status weighting, functional-medicine +/− notation. |
|
|
108
|
+
| **v2.4** | ACMG/AMP automated classification engine, variant consequence annotation (VEP-equivalent). |
|
|
109
|
+
| **v2.5+** | CNVs and repeat-expansion support, dbSNP resolution for rsID-less VCFs, gnomAD genomes (non-coding population frequencies), supplemental genotype file merging. |
|
|
110
|
+
| **v3.0** | Ancestry estimation (PCA + 1KG/HGDP reference panels), Polygenic Risk Score (PRS) integration. |
|
|
111
|
+
|
|
112
|
+
Issues and milestones are tracked at [github.com/allelix/allelix/issues](https://github.com/allelix/allelix/issues). Annotator-level strand-aware carrier matching (R-1) shipped in v2.1.0's ADR-0035 cluster.
|
|
114
113
|
|
|
115
114
|
## Supported Databases
|
|
116
115
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "allelix"
|
|
7
|
-
version = "2.1.
|
|
7
|
+
version = "2.1.2"
|
|
8
8
|
description = "Open-source genotype analysis toolkit. Format-agnostic ingestion, database-agnostic annotation, offline-first."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -36,10 +36,12 @@ vcf-index = [
|
|
|
36
36
|
"pysam>=0.22",
|
|
37
37
|
]
|
|
38
38
|
dev = [
|
|
39
|
+
"mypy>=1.11",
|
|
39
40
|
"pre-commit>=3.7",
|
|
40
41
|
"pytest>=8.0",
|
|
41
42
|
"pytest-cov>=7,<8",
|
|
42
43
|
"ruff>=0.6",
|
|
44
|
+
"types-PyYAML>=6.0",
|
|
43
45
|
]
|
|
44
46
|
|
|
45
47
|
[project.scripts]
|
|
@@ -89,7 +91,7 @@ convention = "google"
|
|
|
89
91
|
|
|
90
92
|
[tool.pytest.ini_options]
|
|
91
93
|
testpaths = ["tests"]
|
|
92
|
-
addopts = "-v --strict-markers --tb=short --cov=allelix --cov-report=term-missing --cov-fail-under=92"
|
|
94
|
+
addopts = "-v --strict-markers --tb=short --cov=allelix --cov-branch --cov-report=term-missing --cov-fail-under=92"
|
|
93
95
|
markers = [
|
|
94
96
|
"slow: marks tests that download databases or process large files",
|
|
95
97
|
"integration: marks end-to-end pipeline tests",
|
|
@@ -99,6 +101,39 @@ filterwarnings = [
|
|
|
99
101
|
"error::pytest.PytestUnraisableExceptionWarning",
|
|
100
102
|
]
|
|
101
103
|
|
|
104
|
+
# GH allelix-dev #31: coverage gate now measures BOTH line and branch
|
|
105
|
+
# coverage. Pre-#31 the gate was line-only — a line counted as covered
|
|
106
|
+
# the moment any input crossed it, which is how the v2.0.x carrier-match
|
|
107
|
+
# case/strand and download-truncation correctness bugs shipped green.
|
|
108
|
+
# The 92% floor is calibrated against the branch metric (currently 92.44%
|
|
109
|
+
# with branch coverage enabled); primary annotators sit at 95%+. Raise
|
|
110
|
+
# the floor when new tests close documented missing branches; do not
|
|
111
|
+
# lower it.
|
|
112
|
+
[tool.coverage.run]
|
|
113
|
+
branch = true
|
|
114
|
+
|
|
102
115
|
[tool.coverage.report]
|
|
103
116
|
precision = 2
|
|
104
117
|
fail_under = 92.00
|
|
118
|
+
|
|
119
|
+
# GH allelix-dev #33: backs the py.typed promise with a real type checker.
|
|
120
|
+
# The starting baseline is intentionally permissive (no strict mode, no
|
|
121
|
+
# disallow_untyped_defs) so we get the gate in CI immediately instead of
|
|
122
|
+
# negotiating dozens of remediation PRs. Ratchet later by tightening the
|
|
123
|
+
# global flags one at a time and chasing the new errors.
|
|
124
|
+
[tool.mypy]
|
|
125
|
+
python_version = "3.11"
|
|
126
|
+
check_untyped_defs = true
|
|
127
|
+
warn_unused_ignores = true
|
|
128
|
+
warn_redundant_casts = true
|
|
129
|
+
warn_unreachable = true
|
|
130
|
+
no_implicit_optional = true
|
|
131
|
+
# Don't enforce on tests — ruff's per-file-ignores already exempt tests
|
|
132
|
+
# from ANN, and unit-test fixtures use a lot of pytest magic that mypy
|
|
133
|
+
# would flag without strict-mode benefit.
|
|
134
|
+
exclude = ["tests/", "scripts/"]
|
|
135
|
+
|
|
136
|
+
# Third-party libraries without published type stubs.
|
|
137
|
+
[[tool.mypy.overrides]]
|
|
138
|
+
module = ["pysam", "mwparserfromhell.*"]
|
|
139
|
+
ignore_missing_imports = true
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|