varcode 7.2.0__tar.gz → 7.3.0__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.
- {varcode-7.2.0/varcode.egg-info → varcode-7.3.0}/PKG-INFO +1 -1
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_effect_hypotheses.py +20 -0
- varcode-7.3.0/tests/test_realized_effects.py +262 -0
- varcode-7.3.0/tests/test_splice_graph.py +257 -0
- varcode-7.3.0/tests/test_transcript_layout.py +164 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/__init__.py +6 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/annotators/__init__.py +2 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/annotators/registry.py +2 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/effect_hypotheses.py +28 -1
- {varcode-7.2.0 → varcode-7.3.0}/varcode/effects/__init__.py +2 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/effects/effect_classes.py +25 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/effects/effect_ordering.py +2 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/effects/effect_prediction.py +12 -5
- {varcode-7.2.0 → varcode-7.3.0}/varcode/genomic_layout.py +76 -6
- varcode-7.3.0/varcode/realized_effects.py +334 -0
- varcode-7.3.0/varcode/splice_graph.py +231 -0
- varcode-7.3.0/varcode/transcript_layout.py +502 -0
- varcode-7.3.0/varcode/version.py +1 -0
- {varcode-7.2.0 → varcode-7.3.0/varcode.egg-info}/PKG-INFO +1 -1
- {varcode-7.2.0 → varcode-7.3.0}/varcode.egg-info/SOURCES.txt +6 -0
- varcode-7.2.0/varcode/version.py +0 -1
- {varcode-7.2.0 → varcode-7.3.0}/LICENSE +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/MANIFEST.in +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/README.md +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/pyproject.toml +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/requirements.txt +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/setup.cfg +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_annotator_divergence_scenarios.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_annotator_parity_adversarial.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_annotators.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_cli_effects.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_cli_genes.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_collection_filtering.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_collection_variants_attr_consistency.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_common.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_cosmic_mutations.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_cryptic_exons.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_csv_roundtrip.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_dbnsfp_validation.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_effect_annotation_errors.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_effect_candidates.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_effect_classes.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_effect_collection.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_effect_collection_serialization.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_effect_collection_sort_order.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_effects_from_mutagenix_variants.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_exonic_splice_site.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_fast_path.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_frameshift_cterminus_regression.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_frameshift_helpers.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_genome_sequence.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_genomic_layout.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_genotype.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_genotype_from_vcf.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_germline_context.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_germline_effects.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_haplotype_effect.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_maf.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_mitochondrial.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_mm10_klf6_frameshift.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_mouse.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_mutant_transcript.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_mutate.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_no_duplicate_variants.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_osteosarc_fusions.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_premature_stop_short_description.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_problematic_variants.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_protein_diff_parity.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_read_phase_resolver.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_real_world_callers.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_reference.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_reference_mismatch_error.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_reference_segments.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_rna_evidence.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_rna_read_phasing_source.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_selenocysteine.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_silent_aa_pos.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_silent_hgvs_description.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_splice_outcomes.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_splice_site_effects.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_stop_codon_classification_bugs.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_string_helpers.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_structural_variant_annotator.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_structural_variants.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_symbolic_alleles.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_timings.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_transforms_left_align.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_transforms_pair_breakends.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_variant.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_variant_collection.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_vcf.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_vcf_output.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_vcf_parsing.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_vcf_phase_resolver.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/tests/test_vcf_spec_examples.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/annotators/fast.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/annotators/protein_diff.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/annotators/structural_variant.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/cli/__init__.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/cli/effects_script.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/cli/genes_script.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/cli/logging.conf +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/cli/variant_args.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/cli/version_info.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/common.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/cryptic_exons.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/csv_helpers.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/effect_candidates.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/effects/classify.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/effects/codon_tables.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/effects/common.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/effects/effect_collection.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/effects/effect_helpers.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/effects/effect_prediction_coding.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/effects/effect_prediction_coding_frameshift.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/effects/effect_prediction_coding_in_frame.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/effects/fast_path.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/effects/mutate.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/effects/transcript_helpers.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/effects/translate.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/errors.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/genome.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/genome_sequence.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/genotype.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/germline.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/maf.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/mutant_transcript.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/nucleotides.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/phasing.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/reference.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/rna_evidence.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/rna_read_phasing.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/splice_outcomes.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/string_helpers.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/structural_variant.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/sv_allele_parser.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/transforms/__init__.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/ucsc_reference_names.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/util.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/variant.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/variant_collection.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/vcf.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/vcf_output.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode/vcf_parsing.py +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode.egg-info/dependency_links.txt +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode.egg-info/entry_points.txt +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode.egg-info/requires.txt +0 -0
- {varcode-7.2.0 → varcode-7.3.0}/varcode.egg-info/top_level.txt +0 -0
|
@@ -181,3 +181,23 @@ def test_ordering_uses_ordinal_rank_when_scores_are_uncalibrated():
|
|
|
181
181
|
|
|
182
182
|
assert ordered[0].effect is low_priority
|
|
183
183
|
assert ordered[0].probability is None
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def test_ordering_prefers_fewer_cis_variants_after_rank_and_priority():
|
|
187
|
+
baseline = product("ABCDEFG")
|
|
188
|
+
outcomes = (
|
|
189
|
+
ClassifiedOutcome(
|
|
190
|
+
EffectHypothesis(
|
|
191
|
+
phase=(("g", "cis"),), enumeration_index=0),
|
|
192
|
+
effect("cis"), baseline, product("ABCDEX"), 3),
|
|
193
|
+
ClassifiedOutcome(
|
|
194
|
+
EffectHypothesis(
|
|
195
|
+
phase=(("g", "trans"),), enumeration_index=1),
|
|
196
|
+
effect("reference-like"), baseline, product("ABCDEY"), 3),
|
|
197
|
+
)
|
|
198
|
+
candidates = merge_classified_outcomes(outcomes)
|
|
199
|
+
|
|
200
|
+
ordered = order_realized_candidates(candidates, lambda value: 1)
|
|
201
|
+
|
|
202
|
+
assert [type(candidate.effect).__name__ for candidate in ordered] == [
|
|
203
|
+
"reference-like", "cis"]
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""End-to-end tests for the opt-in realized effect engine."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from pyensembl import cached_release
|
|
5
|
+
|
|
6
|
+
from varcode import (
|
|
7
|
+
Completeness,
|
|
8
|
+
GermlineContext,
|
|
9
|
+
StructuralVariant,
|
|
10
|
+
Variant,
|
|
11
|
+
get_annotator,
|
|
12
|
+
predict_realized_effect,
|
|
13
|
+
)
|
|
14
|
+
from varcode.effects.effect_classes import (
|
|
15
|
+
Deletion,
|
|
16
|
+
GeneFusion,
|
|
17
|
+
Insertion,
|
|
18
|
+
Substitution,
|
|
19
|
+
Unresolved,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from .test_splice_graph import (
|
|
23
|
+
LongIntronTranscript,
|
|
24
|
+
Transcript,
|
|
25
|
+
deletion,
|
|
26
|
+
long_provider,
|
|
27
|
+
point,
|
|
28
|
+
provider,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
ensembl_grch38 = cached_release(81)
|
|
33
|
+
CFTR_TRANSCRIPT_ID = "ENST00000003084"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_realized_annotator_is_registered_but_not_default():
|
|
37
|
+
annotator = get_annotator("realized")
|
|
38
|
+
|
|
39
|
+
assert annotator.name == "realized"
|
|
40
|
+
assert get_annotator("fast").name == "fast"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_weak_exonic_variant_keeps_normal_coding_effect_first():
|
|
44
|
+
transcript = Transcript()
|
|
45
|
+
variant = point(9, "A", "G")
|
|
46
|
+
|
|
47
|
+
result = predict_realized_effect(
|
|
48
|
+
(variant,), transcript, sequence_provider=provider())
|
|
49
|
+
|
|
50
|
+
assert type(result) is Substitution
|
|
51
|
+
assert result.aa_ref == "Q"
|
|
52
|
+
assert result.aa_alt == "R"
|
|
53
|
+
assert all(rank == 0 for rank in result.mechanism_rank)
|
|
54
|
+
assert result.probability is None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_strong_donor_variant_returns_ordinary_deletion_as_top_effect():
|
|
58
|
+
transcript = Transcript()
|
|
59
|
+
variant = point(11, "G", "A")
|
|
60
|
+
|
|
61
|
+
result = predict_realized_effect(
|
|
62
|
+
(variant,), transcript, sequence_provider=provider())
|
|
63
|
+
|
|
64
|
+
assert type(result) is Deletion
|
|
65
|
+
assert result.aa_ref == "Q"
|
|
66
|
+
assert [
|
|
67
|
+
type(candidate.effect).__name__ for candidate in result.candidates
|
|
68
|
+
] == ["Deletion", "FrameShift", "Unresolved", "Intronic"]
|
|
69
|
+
unresolved = [
|
|
70
|
+
candidate.effect for candidate in result.candidates
|
|
71
|
+
if isinstance(candidate.effect, Unresolved)]
|
|
72
|
+
assert [effect.mechanism for effect in unresolved] == ["cryptic"]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_same_mutant_with_different_phase_baselines_does_not_merge():
|
|
76
|
+
transcript = Transcript()
|
|
77
|
+
somatic = point(11, "G", "A")
|
|
78
|
+
germline = point(9, "A", "G")
|
|
79
|
+
|
|
80
|
+
result = predict_realized_effect(
|
|
81
|
+
(somatic,), transcript, germline_variants=(germline,),
|
|
82
|
+
sequence_provider=provider())
|
|
83
|
+
|
|
84
|
+
deletion_candidates = [
|
|
85
|
+
candidate for candidate in result.candidates
|
|
86
|
+
if type(candidate.effect) is Deletion]
|
|
87
|
+
assert len(deletion_candidates) == 2
|
|
88
|
+
assert {
|
|
89
|
+
candidate.outcomes[0].baseline.protein_sequence
|
|
90
|
+
for candidate in deletion_candidates
|
|
91
|
+
} == {"MQA", "MRA"}
|
|
92
|
+
assert {
|
|
93
|
+
candidate.outcomes[0].mutant.protein_sequence
|
|
94
|
+
for candidate in deletion_candidates
|
|
95
|
+
} == {"MA"}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_sv_deletion_does_not_merge_away_patient_specific_baseline():
|
|
99
|
+
transcript = Transcript()
|
|
100
|
+
somatic = deletion(8, 10)
|
|
101
|
+
germline = point(9, "A", "G")
|
|
102
|
+
|
|
103
|
+
result = predict_realized_effect(
|
|
104
|
+
(somatic,), transcript, germline_variants=(germline,),
|
|
105
|
+
sequence_provider=provider())
|
|
106
|
+
|
|
107
|
+
assert type(result) is Deletion
|
|
108
|
+
deletion_candidates = [
|
|
109
|
+
candidate for candidate in result.candidates
|
|
110
|
+
if type(candidate.effect) is Deletion]
|
|
111
|
+
assert len(deletion_candidates) == 2
|
|
112
|
+
assert {
|
|
113
|
+
candidate.outcomes[0].baseline.protein_sequence
|
|
114
|
+
for candidate in deletion_candidates
|
|
115
|
+
} == {"MQA", "MRA"}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_multiple_somatic_variants_are_realized_on_one_product():
|
|
119
|
+
transcript = Transcript()
|
|
120
|
+
variants = (point(8, "C", "A"), point(9, "A", "G"))
|
|
121
|
+
|
|
122
|
+
result = predict_realized_effect(
|
|
123
|
+
variants, transcript, sequence_provider=provider())
|
|
124
|
+
|
|
125
|
+
assert type(result) is Substitution
|
|
126
|
+
assert result.aa_ref == "Q"
|
|
127
|
+
assert result.aa_alt == "R"
|
|
128
|
+
assert result.variants == variants
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_combined_phase_and_splice_hypotheses_obey_global_cap():
|
|
132
|
+
transcript = Transcript()
|
|
133
|
+
somatic = point(11, "G", "A")
|
|
134
|
+
germline = point(9, "A", "G")
|
|
135
|
+
|
|
136
|
+
with pytest.raises(ValueError, match="Combined phase/splice"):
|
|
137
|
+
predict_realized_effect(
|
|
138
|
+
(somatic,), transcript, germline_variants=(germline,),
|
|
139
|
+
sequence_provider=provider(), max_hypotheses=7)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_real_cftr_missense_matches_existing_varcode_result():
|
|
143
|
+
transcript = ensembl_grch38.transcript_by_id(CFTR_TRANSCRIPT_ID)
|
|
144
|
+
variant = Variant("7", 117531100, "T", "A", ensembl_grch38)
|
|
145
|
+
|
|
146
|
+
result = predict_realized_effect((variant,), transcript)
|
|
147
|
+
|
|
148
|
+
assert type(result) is Substitution
|
|
149
|
+
assert result.short_description == "p.L159M"
|
|
150
|
+
assert len(result.candidates) == 1
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_effects_dispatches_realized_annotator_with_germline_context():
|
|
154
|
+
somatic = Variant("7", 117531100, "T", "A", ensembl_grch38)
|
|
155
|
+
germline = Variant("7", 117531101, "T", "C", ensembl_grch38)
|
|
156
|
+
context = GermlineContext.from_variants(
|
|
157
|
+
[germline], reference_name="GRCh38")
|
|
158
|
+
|
|
159
|
+
effects = somatic.effects(annotator="realized", germline=context)
|
|
160
|
+
result = next(
|
|
161
|
+
effect for effect in effects
|
|
162
|
+
if effect.transcript.id == CFTR_TRANSCRIPT_ID)
|
|
163
|
+
|
|
164
|
+
assert type(result) is Substitution
|
|
165
|
+
assert len(result.candidates) == 2
|
|
166
|
+
assert {
|
|
167
|
+
candidate.outcomes[0].baseline.protein_sequence[158]
|
|
168
|
+
for candidate in result.candidates
|
|
169
|
+
} == {"L", "S"}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def test_realized_dispatch_preserves_sparse_germline_uncertainty_flag():
|
|
173
|
+
somatic = Variant("7", 117531100, "T", "A", ensembl_grch38)
|
|
174
|
+
distant = Variant("7", 117587799, "C", "T", ensembl_grch38)
|
|
175
|
+
context = GermlineContext.from_variants(
|
|
176
|
+
[distant], reference_name="GRCh38",
|
|
177
|
+
completeness=Completeness.SPARSE)
|
|
178
|
+
|
|
179
|
+
effects = somatic.effects(annotator="realized", germline=context)
|
|
180
|
+
result = next(
|
|
181
|
+
effect for effect in effects
|
|
182
|
+
if effect.transcript.id == CFTR_TRANSCRIPT_ID)
|
|
183
|
+
|
|
184
|
+
assert result.germline_unknown is True
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def test_real_cftr_donor_plus_one_enumerates_splice_mechanisms():
|
|
188
|
+
transcript = ensembl_grch38.transcript_by_id(CFTR_TRANSCRIPT_ID)
|
|
189
|
+
variant = Variant("7", 117531115, "G", "A", ensembl_grch38)
|
|
190
|
+
|
|
191
|
+
result = predict_realized_effect((variant,), transcript)
|
|
192
|
+
|
|
193
|
+
assert type(result) is Deletion
|
|
194
|
+
assert [
|
|
195
|
+
candidate.effect.mechanism
|
|
196
|
+
for candidate in result.candidates
|
|
197
|
+
if isinstance(candidate.effect, Unresolved)
|
|
198
|
+
] == ["intron_retention", "cryptic"]
|
|
199
|
+
assert result.probability is None
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@pytest.mark.parametrize(
|
|
203
|
+
"sv_type,expected_class",
|
|
204
|
+
[("DEL", Deletion), ("DUP", Insertion)])
|
|
205
|
+
def test_real_cftr_whole_exon_sv_is_classified_from_realized_product(
|
|
206
|
+
sv_type, expected_class):
|
|
207
|
+
transcript = ensembl_grch38.transcript_by_id(CFTR_TRANSCRIPT_ID)
|
|
208
|
+
exon = transcript.exons[4]
|
|
209
|
+
variant = StructuralVariant(
|
|
210
|
+
contig="7",
|
|
211
|
+
start=exon.start - 300,
|
|
212
|
+
end=exon.end + 300,
|
|
213
|
+
sv_type=sv_type,
|
|
214
|
+
genome=ensembl_grch38)
|
|
215
|
+
|
|
216
|
+
result = predict_realized_effect((variant,), transcript)
|
|
217
|
+
|
|
218
|
+
assert type(result) is expected_class
|
|
219
|
+
assert len(result.candidates) == 1
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@pytest.mark.parametrize(
|
|
223
|
+
"reverse,position,ref,alt",
|
|
224
|
+
[(False, 23, "G", "A"), (True, 178, "C", "T")])
|
|
225
|
+
def test_cryptic_donor_is_realized_on_mutated_haplotype_both_strands(
|
|
226
|
+
reverse, position, ref, alt):
|
|
227
|
+
transcript = LongIntronTranscript(reverse=reverse)
|
|
228
|
+
variant = point(position, ref, alt)
|
|
229
|
+
|
|
230
|
+
result = predict_realized_effect(
|
|
231
|
+
(variant,), transcript, sequence_provider=long_provider(reverse))
|
|
232
|
+
|
|
233
|
+
cryptic = next(
|
|
234
|
+
candidate for candidate in result.candidates
|
|
235
|
+
if candidate.ordinal_key == (2,))
|
|
236
|
+
assert not isinstance(cryptic.effect, Unresolved)
|
|
237
|
+
evidence = cryptic.outcomes[0].mutant.evidence
|
|
238
|
+
resolved = evidence["resolved_splice_choices"]
|
|
239
|
+
assert len(resolved) == 1
|
|
240
|
+
assert resolved[0][1] == "cryptic"
|
|
241
|
+
assert resolved[0][3] > 0
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def test_realized_annotator_routes_bnd_through_verified_fusion_builder():
|
|
245
|
+
genome = cached_release(95)
|
|
246
|
+
otx1 = genome.transcript_by_id("ENST00000282549")
|
|
247
|
+
variant = StructuralVariant(
|
|
248
|
+
contig="2",
|
|
249
|
+
start=63_053_516,
|
|
250
|
+
sv_type="BND",
|
|
251
|
+
alt="N]2:25955847]",
|
|
252
|
+
mate_contig="2",
|
|
253
|
+
mate_start=25_955_847,
|
|
254
|
+
mate_orientation="]]",
|
|
255
|
+
genome=genome)
|
|
256
|
+
|
|
257
|
+
result = predict_realized_effect((variant,), otx1)
|
|
258
|
+
|
|
259
|
+
assert isinstance(result, GeneFusion)
|
|
260
|
+
assert result.partner_transcript.id == "ENST00000264712"
|
|
261
|
+
assert result.mutant_protein_sequence == (
|
|
262
|
+
"MMSYLKQPPYGMNGLGLAGPAMDLLHPSVGYPETS")
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""Tests for splice-site detection on realized forward/reverse layouts."""
|
|
2
|
+
|
|
3
|
+
from types import SimpleNamespace
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from varcode.genomic_layout import GenomicLayout
|
|
8
|
+
from varcode.nucleotides import reverse_complement
|
|
9
|
+
from varcode.splice_graph import (
|
|
10
|
+
disrupted_splice_sites,
|
|
11
|
+
enumerate_layout_splice_plans,
|
|
12
|
+
)
|
|
13
|
+
from varcode.transcript_layout import build_exon_runs, realize_exon_path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
FORWARD = "ATGGTAGCAAGTAGGCTGTAGTAG"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _long_forward_sequence():
|
|
20
|
+
bases = list("A" * 200)
|
|
21
|
+
for start, sequence in (
|
|
22
|
+
(1, "ATG"), (4, "GT"),
|
|
23
|
+
(18, "AG"), (20, "CAA"), (23, "GT"),
|
|
24
|
+
(29, "CAGGTAAGT"),
|
|
25
|
+
(118, "AG"), (120, "GCT"), (123, "GT"),
|
|
26
|
+
(138, "AG"), (140, "TAG")):
|
|
27
|
+
bases[start - 1:start - 1 + len(sequence)] = sequence
|
|
28
|
+
return "".join(bases)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
LONG_FORWARD = _long_forward_sequence()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Exon:
|
|
35
|
+
def __init__(self, start, end):
|
|
36
|
+
self.start = start
|
|
37
|
+
self.end = end
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Transcript:
|
|
41
|
+
contig = "x"
|
|
42
|
+
start = 1
|
|
43
|
+
end = 24
|
|
44
|
+
is_protein_coding = True
|
|
45
|
+
complete = True
|
|
46
|
+
protein_sequence = "MQA"
|
|
47
|
+
stop_codon_spliced_offsets = (9, 10, 11)
|
|
48
|
+
id = "synthetic-splice"
|
|
49
|
+
name = "SYN-001"
|
|
50
|
+
|
|
51
|
+
def __init__(self, reverse=False):
|
|
52
|
+
self.gene = SimpleNamespace(id="synthetic-gene", name="SYN")
|
|
53
|
+
self.on_backward_strand = reverse
|
|
54
|
+
intervals = ((1, 3), (8, 10), (15, 17), (22, 24))
|
|
55
|
+
if reverse:
|
|
56
|
+
intervals = tuple(
|
|
57
|
+
(25 - end, 25 - start)
|
|
58
|
+
for start, end in reversed(intervals))
|
|
59
|
+
self.start_codon_positions = (22, 23, 24)
|
|
60
|
+
else:
|
|
61
|
+
self.start_codon_positions = (1, 2, 3)
|
|
62
|
+
self.start_codon_spliced_offsets = (0, 1, 2)
|
|
63
|
+
self.exons = tuple(Exon(*interval) for interval in intervals)
|
|
64
|
+
|
|
65
|
+
def spliced_offset(self, position):
|
|
66
|
+
offset = 0
|
|
67
|
+
exons = sorted(
|
|
68
|
+
self.exons, key=lambda exon: exon.start,
|
|
69
|
+
reverse=self.on_backward_strand)
|
|
70
|
+
for exon in exons:
|
|
71
|
+
if exon.start <= position <= exon.end:
|
|
72
|
+
if self.on_backward_strand:
|
|
73
|
+
return offset + exon.end - position
|
|
74
|
+
return offset + position - exon.start
|
|
75
|
+
offset += exon.end - exon.start + 1
|
|
76
|
+
raise ValueError(position)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class LongIntronTranscript(Transcript):
|
|
80
|
+
end = 142
|
|
81
|
+
id = "synthetic-long-intron"
|
|
82
|
+
|
|
83
|
+
def __init__(self, reverse=False):
|
|
84
|
+
self.gene = SimpleNamespace(id="synthetic-gene", name="SYN")
|
|
85
|
+
self.on_backward_strand = reverse
|
|
86
|
+
intervals = ((1, 3), (20, 22), (120, 122), (140, 142))
|
|
87
|
+
if reverse:
|
|
88
|
+
intervals = tuple(
|
|
89
|
+
(201 - end, 201 - start)
|
|
90
|
+
for start, end in reversed(intervals))
|
|
91
|
+
self.start_codon_positions = (198, 199, 200)
|
|
92
|
+
else:
|
|
93
|
+
self.start_codon_positions = (1, 2, 3)
|
|
94
|
+
self.start = min(start for start, _ in intervals)
|
|
95
|
+
self.end = max(end for _, end in intervals)
|
|
96
|
+
self.start_codon_spliced_offsets = (0, 1, 2)
|
|
97
|
+
self.exons = tuple(Exon(*interval) for interval in intervals)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def long_provider(reverse=False):
|
|
101
|
+
chromosome = reverse_complement(LONG_FORWARD) if reverse else LONG_FORWARD
|
|
102
|
+
|
|
103
|
+
def get_sequence(contig, start, end):
|
|
104
|
+
assert contig == "x"
|
|
105
|
+
sequence = chromosome[start - 1:end]
|
|
106
|
+
return sequence + "A" * (end - start + 1 - len(sequence))
|
|
107
|
+
|
|
108
|
+
return get_sequence
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def provider(reverse=False):
|
|
112
|
+
chromosome = reverse_complement(FORWARD) if reverse else FORWARD
|
|
113
|
+
|
|
114
|
+
def get_sequence(contig, start, end):
|
|
115
|
+
assert contig == "x"
|
|
116
|
+
return chromosome[start - 1:end]
|
|
117
|
+
|
|
118
|
+
return get_sequence
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def point(start, ref, alt):
|
|
122
|
+
return SimpleNamespace(
|
|
123
|
+
contig="x", trimmed_base1_start=start,
|
|
124
|
+
trimmed_base1_end=start + len(ref) - 1,
|
|
125
|
+
trimmed_ref=ref, trimmed_alt=alt, is_structural=False)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def deletion(start, end):
|
|
129
|
+
return SimpleNamespace(
|
|
130
|
+
contig="x", start=start, end=end,
|
|
131
|
+
affected_start=start, affected_end=end,
|
|
132
|
+
sv_type="DEL", alt_assembly=None, is_structural=True)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def statuses_for(variant=None, reverse=False, with_sequence=True):
|
|
136
|
+
transcript = Transcript(reverse)
|
|
137
|
+
layout = GenomicLayout.from_transcript(
|
|
138
|
+
transcript,
|
|
139
|
+
flank=0,
|
|
140
|
+
sequence_provider=provider(reverse) if with_sequence else None)
|
|
141
|
+
if variant is not None:
|
|
142
|
+
layout = layout.apply_point_variant(
|
|
143
|
+
variant, validate_reference=with_sequence)
|
|
144
|
+
runs = build_exon_runs(transcript, layout)
|
|
145
|
+
return disrupted_splice_sites(transcript, layout, runs)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@pytest.mark.parametrize("reverse", [False, True])
|
|
149
|
+
def test_reference_splice_sites_are_intact(reverse):
|
|
150
|
+
assert statuses_for(reverse=reverse) == ()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_donor_canonical_and_weak_windows_are_distinct():
|
|
154
|
+
strong = statuses_for(point(11, "G", "A"))
|
|
155
|
+
weak_intronic = statuses_for(point(14, "G", "A"))
|
|
156
|
+
weak_exonic = statuses_for(point(10, "A", "C"))
|
|
157
|
+
|
|
158
|
+
assert [(s.key.side, s.key.exon_number, s.strength) for s in strong] == [
|
|
159
|
+
("donor", 2, "lost")]
|
|
160
|
+
donor = next(
|
|
161
|
+
status for status in weak_intronic
|
|
162
|
+
if status.key.side == "donor" and status.key.exon_number == 2)
|
|
163
|
+
assert donor.strength == "weak_intronic"
|
|
164
|
+
assert weak_exonic[0].strength == "weak_exonic"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_reverse_strand_donor_uses_transcript_direction():
|
|
168
|
+
# Forward position 11 maps to reverse-complement position 14.
|
|
169
|
+
statuses = statuses_for(point(14, "C", "T"), reverse=True)
|
|
170
|
+
|
|
171
|
+
assert [(s.key.side, s.key.exon_number, s.strength) for s in statuses] == [
|
|
172
|
+
("donor", 2, "lost")]
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def test_tier_zero_detects_explicit_canonical_site_edit_without_fasta():
|
|
176
|
+
statuses = statuses_for(
|
|
177
|
+
point(11, "G", "A"), with_sequence=False)
|
|
178
|
+
|
|
179
|
+
assert statuses[0].strength == "lost"
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def test_exon_truncation_is_a_lost_splice_site():
|
|
183
|
+
transcript = Transcript()
|
|
184
|
+
layout = GenomicLayout.from_transcript(
|
|
185
|
+
transcript, flank=0, sequence_provider=provider())
|
|
186
|
+
layout = layout.apply_structural_variant(deletion(10, 14))
|
|
187
|
+
|
|
188
|
+
statuses = disrupted_splice_sites(
|
|
189
|
+
transcript, layout, build_exon_runs(transcript, layout))
|
|
190
|
+
|
|
191
|
+
assert any(
|
|
192
|
+
status.key.side == "donor"
|
|
193
|
+
and status.key.exon_number == 2
|
|
194
|
+
and status.strength == "lost"
|
|
195
|
+
for status in statuses)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def test_strong_site_options_are_ordinal_not_probabilities():
|
|
199
|
+
transcript = Transcript()
|
|
200
|
+
layout = GenomicLayout.from_transcript(
|
|
201
|
+
transcript, flank=0, sequence_provider=provider())
|
|
202
|
+
layout = layout.apply_point_variant(point(11, "G", "A"))
|
|
203
|
+
|
|
204
|
+
plans = enumerate_layout_splice_plans(transcript, layout)
|
|
205
|
+
mechanisms = [
|
|
206
|
+
tuple(option.mechanism for _, option in plan.choices)
|
|
207
|
+
for plan in plans]
|
|
208
|
+
|
|
209
|
+
assert mechanisms == [
|
|
210
|
+
("exon_skip",),
|
|
211
|
+
("intron_retention",),
|
|
212
|
+
("cryptic",),
|
|
213
|
+
("normal",),
|
|
214
|
+
]
|
|
215
|
+
assert all(plan.probability is None for plan in plans)
|
|
216
|
+
skipped = realize_exon_path(
|
|
217
|
+
transcript, layout, kept_run_keys=plans[0].kept_runs)
|
|
218
|
+
assert skipped.cdna_sequence == "ATGGCTTAG"
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def test_two_sites_on_skipped_exon_do_not_form_impossible_cartesian_pair():
|
|
222
|
+
transcript = Transcript()
|
|
223
|
+
layout = GenomicLayout.from_transcript(
|
|
224
|
+
transcript, flank=0, sequence_provider=provider())
|
|
225
|
+
# Destroy exon 2's acceptor and donor.
|
|
226
|
+
layout = layout.apply_variants((
|
|
227
|
+
point(6, "A", "C"),
|
|
228
|
+
point(11, "G", "A"),
|
|
229
|
+
))
|
|
230
|
+
|
|
231
|
+
plans = enumerate_layout_splice_plans(transcript, layout)
|
|
232
|
+
|
|
233
|
+
assert plans
|
|
234
|
+
assert all(not (
|
|
235
|
+
len(plan.choices) == 2
|
|
236
|
+
and all(option.mechanism == "exon_skip"
|
|
237
|
+
for _, option in plan.choices))
|
|
238
|
+
for plan in plans)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def test_retained_intron_cannot_coexist_with_skipped_downstream_exon():
|
|
242
|
+
transcript = Transcript()
|
|
243
|
+
layout = GenomicLayout.from_transcript(
|
|
244
|
+
transcript, flank=0, sequence_provider=provider())
|
|
245
|
+
# Destroy the donors of exon 2 and exon 3. Retaining intron 2 requires
|
|
246
|
+
# exon 3, so it cannot coexist with exon 3 skipping.
|
|
247
|
+
layout = layout.apply_variants((
|
|
248
|
+
point(11, "G", "A"),
|
|
249
|
+
point(18, "G", "A"),
|
|
250
|
+
))
|
|
251
|
+
|
|
252
|
+
plans = enumerate_layout_splice_plans(transcript, layout)
|
|
253
|
+
|
|
254
|
+
assert all(not (
|
|
255
|
+
dict((key.exon_number, option.mechanism) for key, option in plan.choices)
|
|
256
|
+
== {2: "intron_retention", 3: "exon_skip"})
|
|
257
|
+
for plan in plans)
|