varcode 9.2.8__tar.gz → 9.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-9.2.8/varcode.egg-info → varcode-9.3.0}/PKG-INFO +1 -1
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_documentation_examples.py +8 -0
- varcode-9.3.0/tests/test_sv_sequence_changes.py +296 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/effects/effect_classes.py +24 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/effects/effect_collection.py +17 -3
- varcode-9.3.0/varcode/effects/sequence_change.py +142 -0
- varcode-9.3.0/varcode/version.py +1 -0
- {varcode-9.2.8 → varcode-9.3.0/varcode.egg-info}/PKG-INFO +1 -1
- {varcode-9.2.8 → varcode-9.3.0}/varcode.egg-info/SOURCES.txt +2 -0
- varcode-9.2.8/varcode/version.py +0 -1
- {varcode-9.2.8 → varcode-9.3.0}/LICENSE +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/MANIFEST.in +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/README.md +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/pyproject.toml +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/requirements.txt +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/setup.cfg +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_annotator_divergence_scenarios.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_annotator_parity_adversarial.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_annotators.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_bnd_retained_segments.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_cli_effects.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_cli_genes.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_cli_without_pkg_resources.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_collection_filtering.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_collection_variants_attr_consistency.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_common.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_cosmic_mutations.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_cryptic_exons.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_csv_roundtrip.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_dbnsfp_validation.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_default_structural_helpers.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_effect_annotation_errors.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_effect_candidates.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_effect_classes.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_effect_collection.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_effect_collection_serialization.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_effect_collection_sort_order.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_effect_hypotheses.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_effects_from_mutagenix_variants.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_exacto_fusions.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_exonic_splice_site.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_fast_path.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_frameshift_cterminus_regression.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_frameshift_helpers.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_fusion_partner_candidates.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_genome_contig_validation.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_genome_sequence.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_genomic_layout.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_genotype.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_genotype_from_vcf.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_germline_context.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_germline_effects.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_haplotype_effect.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_maf.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_mitochondrial.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_mm10_klf6_frameshift.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_mouse.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_mutant_transcript.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_mutate.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_no_duplicate_variants.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_nonlocal_sv_transcripts.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_osteosarc_fusions.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_partial_annotators.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_premature_stop_short_description.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_problematic_variants.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_protein_diff_parity.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_read_phase_resolver.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_real_world_callers.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_reference.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_reference_mismatch_error.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_reference_segments.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_rna_evidence.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_rna_haplotype_sequences.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_rna_read_phasing_source.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_selenocysteine.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_silent_aa_pos.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_silent_hgvs_description.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_splice_graph.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_splice_outcomes.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_splice_site_effects.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_stop_codon_classification_bugs.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_string_helpers.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_structural_variant_annotator.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_structural_variants.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_sv_tabular_metadata.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_symbolic_alleles.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_symbolic_sv_padding.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_timings.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_transcript_layout.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_transcript_model.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_transforms_left_align.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_transforms_pair_breakends.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_variant.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_variant_collection.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_vcf.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_vcf_output.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_vcf_parsing.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_vcf_phase_resolver.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/tests/test_vcf_spec_examples.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/__init__.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/annotators/__init__.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/annotators/fast.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/annotators/protein_diff.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/annotators/registry.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/cli/__init__.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/cli/effects_script.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/cli/genes_script.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/cli/logging.conf +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/cli/variant_args.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/cli/version_info.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/common.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/cryptic_exons.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/csv_helpers.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/effect_candidates.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/effect_hypotheses.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/effects/__init__.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/effects/classify.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/effects/codon_tables.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/effects/common.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/effects/effect_helpers.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/effects/effect_ordering.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/effects/effect_prediction.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/effects/effect_prediction_coding.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/effects/effect_prediction_coding_frameshift.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/effects/effect_prediction_coding_in_frame.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/effects/fast_path.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/effects/mutate.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/effects/structural.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/effects/transcript_helpers.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/effects/translate.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/errors.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/exacto.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/genome.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/genome_sequence.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/genomic_layout.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/genotype.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/germline.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/maf.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/mutant_transcript.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/nucleotides.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/phasing.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/realized_effects.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/reference.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/rna_evidence.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/rna_read_phasing.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/splice_graph.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/splice_outcomes.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/string_helpers.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/structural_variant.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/sv_allele_parser.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/transcript_layout.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/transcript_model.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/transforms/__init__.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/ucsc_reference_names.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/util.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/variant.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/variant_collection.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/vcf.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/vcf_output.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode/vcf_parsing.py +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode.egg-info/dependency_links.txt +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode.egg-info/entry_points.txt +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode.egg-info/requires.txt +0 -0
- {varcode-9.2.8 → varcode-9.3.0}/varcode.egg-info/top_level.txt +0 -0
|
@@ -69,6 +69,14 @@ def test_documentation_first_steps(path, count, input_directory):
|
|
|
69
69
|
for effect in namespace["effects"])
|
|
70
70
|
|
|
71
71
|
|
|
72
|
+
def test_documentation_sv_filtering(input_directory):
|
|
73
|
+
namespace = _run(_blocks("docs/structural_variants.md")[:2])
|
|
74
|
+
assert len(namespace["retained"]) > 0
|
|
75
|
+
assert len(namespace["resolved_changes"]) > 0
|
|
76
|
+
assert all(effect.modifies_protein_sequence is True
|
|
77
|
+
for effect in namespace["resolved_changes"])
|
|
78
|
+
|
|
79
|
+
|
|
72
80
|
def test_documentation_germline_scenarios():
|
|
73
81
|
namespace = _run(_blocks(
|
|
74
82
|
"docs/phasing.md", "## Two variants in one codon")[:4])
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"""SV predictions must survive filtering without calling unknowns unchanged."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import replace
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from pyensembl import cached_release
|
|
7
|
+
|
|
8
|
+
from varcode import (
|
|
9
|
+
EffectCandidate, MutantTranscript, ReferenceSegment, StructuralVariant,
|
|
10
|
+
make_fusion_outcome,
|
|
11
|
+
)
|
|
12
|
+
from varcode.effects import (
|
|
13
|
+
CrypticExonCandidate, EffectCollection, GeneFusion, LargeDeletion,
|
|
14
|
+
StructuralVariantEffect, TranslocationToIntergenic, Unresolved,
|
|
15
|
+
)
|
|
16
|
+
from varcode.effects import structural
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def cftr():
|
|
21
|
+
return cached_release(81).transcript_by_id("ENST00000003084")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.fixture
|
|
25
|
+
def isolate_span(monkeypatch):
|
|
26
|
+
monkeypatch.setattr(structural, "_fusion_partners", lambda *args: ())
|
|
27
|
+
monkeypatch.setattr(structural, "_enumerate_and_attach_cryptics", lambda *args: None)
|
|
28
|
+
monkeypatch.setattr(structural, "_enumerate_and_attach_splice_outcomes", lambda *args: None)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _effect(transcript, cdna=None, protein=None, segments=None, evidence=None):
|
|
32
|
+
variant = StructuralVariant(transcript.contig, transcript.start, "BND",
|
|
33
|
+
genome=transcript.genome)
|
|
34
|
+
model = MutantTranscript(
|
|
35
|
+
reference_transcript=transcript, reference_segments=segments,
|
|
36
|
+
cdna_sequence=cdna, mutant_protein_sequence=protein, evidence=evidence)
|
|
37
|
+
return StructuralVariantEffect(variant, transcript, mutant_transcript=model)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _identity_segments(transcript):
|
|
41
|
+
return (ReferenceSegment(transcript, 0, len(transcript.sequence)),)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_seven_audited_fusion_proteins_survive_filter():
|
|
45
|
+
genome = cached_release(95)
|
|
46
|
+
transcript = genome.transcript_by_id("ENST00000538197")
|
|
47
|
+
variant = StructuralVariant("4", 15012987, "BND", alt="N[4:2664478[",
|
|
48
|
+
mate_contig="4", mate_start=2664478, genome=genome)
|
|
49
|
+
effect = variant.effect_on_transcript(transcript)
|
|
50
|
+
fusions = [c.effect for c in effect.candidates if isinstance(c.effect, GeneFusion)]
|
|
51
|
+
assert len(fusions) == 7
|
|
52
|
+
assert all(f.mutant_protein_sequence != transcript.protein_sequence for f in fusions)
|
|
53
|
+
assert all(f.modifies_protein_sequence is True for f in fusions)
|
|
54
|
+
assert all(f.modifies_coding_sequence is True for f in fusions)
|
|
55
|
+
assert len(EffectCollection(fusions).drop_silent_and_noncoding()) == 7
|
|
56
|
+
assert list(EffectCollection([effect]).drop_silent_and_noncoding()) == [effect]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@pytest.mark.parametrize("transcript_id", ["ENST00000003084", "ENST00000357654"])
|
|
60
|
+
@pytest.mark.parametrize("region", ["coding", "start", "whole", "5utr", "3utr"])
|
|
61
|
+
def test_deletion_flags_on_both_strands(transcript_id, region, isolate_span):
|
|
62
|
+
transcript = cached_release(81).transcript_by_id(transcript_id)
|
|
63
|
+
exons = transcript.exons
|
|
64
|
+
if region == "whole":
|
|
65
|
+
start, end = transcript.start, transcript.end
|
|
66
|
+
elif region == "coding":
|
|
67
|
+
start, end = exons[4].start, exons[4].end
|
|
68
|
+
elif region == "start":
|
|
69
|
+
start, end = min(transcript.start_codon_positions), max(transcript.start_codon_positions)
|
|
70
|
+
else:
|
|
71
|
+
exon = exons[0] if region == "5utr" else exons[-1]
|
|
72
|
+
at_low_end = (region == "5utr") == (transcript.strand == "+")
|
|
73
|
+
start, end = ((exon.start, exon.start + 9) if at_low_end
|
|
74
|
+
else (exon.end - 9, exon.end))
|
|
75
|
+
variant = StructuralVariant(transcript.contig, start, "DEL", end=end,
|
|
76
|
+
genome=transcript.genome)
|
|
77
|
+
effect = variant.effect_on_transcript(transcript)
|
|
78
|
+
assert isinstance(effect, LargeDeletion)
|
|
79
|
+
expected = region in ("coding", "start", "whole")
|
|
80
|
+
assert effect.modifies_coding_sequence is expected
|
|
81
|
+
assert effect.modifies_protein_sequence is expected
|
|
82
|
+
assert len(EffectCollection([effect]).drop_silent_and_noncoding()) == int(expected)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@pytest.mark.parametrize("change", ["none", "utr", "synonymous", "missense", "stop"])
|
|
86
|
+
def test_cds_and_protein_are_distinct_comparisons(cftr, change):
|
|
87
|
+
cdna = cftr.sequence
|
|
88
|
+
start = min(cftr.start_codon_spliced_offsets)
|
|
89
|
+
if change == "utr":
|
|
90
|
+
cdna = ("A" if cdna[0] != "A" else "C") + cdna[1:]
|
|
91
|
+
elif change == "synonymous":
|
|
92
|
+
# A GCT alanine codon changed to GCC.
|
|
93
|
+
pos = next(i for i in range(start + 3, start + 300, 3) if cdna[i:i+3] == "GCT")
|
|
94
|
+
cdna = cdna[:pos] + "GCC" + cdna[pos+3:]
|
|
95
|
+
elif change == "missense":
|
|
96
|
+
cdna = cdna[:start+3] + "TGG" + cdna[start+6:]
|
|
97
|
+
elif change == "stop":
|
|
98
|
+
pos = min(cftr.stop_codon_spliced_offsets)
|
|
99
|
+
cdna = cdna[:pos] + "TAA" + cdna[pos+3:]
|
|
100
|
+
effect = _effect(cftr, cdna=cdna, segments=_identity_segments(cftr))
|
|
101
|
+
assert effect.modifies_coding_sequence is (change in ("synonymous", "missense", "stop"))
|
|
102
|
+
assert effect.modifies_protein_sequence is (change == "missense")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@pytest.mark.parametrize("kind", ["DUP", "INV", "BND"])
|
|
106
|
+
def test_unresolved_models_are_not_silent(cftr, kind, isolate_span):
|
|
107
|
+
variant = StructuralVariant(cftr.contig, cftr.start - 100, kind,
|
|
108
|
+
end=cftr.end - 50, genome=cftr.genome)
|
|
109
|
+
if kind == "BND":
|
|
110
|
+
effect = TranslocationToIntergenic(variant, cftr)
|
|
111
|
+
else:
|
|
112
|
+
effect = variant.effect_on_transcript(cftr)
|
|
113
|
+
assert effect.modifies_coding_sequence is None
|
|
114
|
+
assert effect.modifies_protein_sequence is None
|
|
115
|
+
collection = EffectCollection([effect], annotator="fast", annotator_version="test")
|
|
116
|
+
retained = collection.drop_silent_and_noncoding()
|
|
117
|
+
assert list(retained) == [effect]
|
|
118
|
+
assert retained.annotator == "fast"
|
|
119
|
+
assert retained.annotator_version == "test"
|
|
120
|
+
assert len(collection.drop_silent_and_noncoding(keep_unresolved=False)) == 0
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_partial_fragment_and_unmapped_assembly_are_unknown(cftr):
|
|
124
|
+
partial = _effect(cftr, segments=(ReferenceSegment(cftr, 0, 500),))
|
|
125
|
+
assembly = _effect(cftr, cdna=cftr.sequence)
|
|
126
|
+
for effect in (partial, assembly):
|
|
127
|
+
assert effect.modifies_protein_sequence is None
|
|
128
|
+
assert effect.modifies_coding_sequence is None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@pytest.mark.parametrize("primary_status", [False, None])
|
|
132
|
+
def test_later_changed_candidate_controls_flags_and_filter(cftr, primary_status):
|
|
133
|
+
primary = _effect(cftr, protein=cftr.protein_sequence if primary_status is False else None)
|
|
134
|
+
changed = _effect(cftr, protein=cftr.protein_sequence + "A")
|
|
135
|
+
primary._extra_candidates = (EffectCandidate(changed, source="rna", evidence={"reads": 3}),)
|
|
136
|
+
before = primary.candidates
|
|
137
|
+
assert primary.modifies_coding_sequence is True
|
|
138
|
+
assert primary.modifies_protein_sequence is True
|
|
139
|
+
assert list(EffectCollection([primary]).drop_silent_and_noncoding(False)) == [primary]
|
|
140
|
+
assert primary.candidates == before
|
|
141
|
+
assert primary.candidates[1].evidence == {"reads": 3}
|
|
142
|
+
# Self references and back edges must not recurse indefinitely.
|
|
143
|
+
changed._attach_primary_effects([primary])
|
|
144
|
+
assert primary.modifies_protein_sequence is True
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_unchanged_plus_unknown_is_unknown(cftr):
|
|
148
|
+
unchanged = _effect(cftr, protein=cftr.protein_sequence)
|
|
149
|
+
unknown = _effect(cftr)
|
|
150
|
+
unchanged._attach_primary_effects([unknown])
|
|
151
|
+
assert unchanged.modifies_protein_sequence is None
|
|
152
|
+
assert len(EffectCollection([unchanged]).drop_silent_and_noncoding()) == 1
|
|
153
|
+
assert len(EffectCollection([unchanged]).drop_silent_and_noncoding(False)) == 0
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_explicit_protein_without_cds_does_not_prove_cds_unchanged(cftr):
|
|
157
|
+
effect = _effect(cftr, protein=cftr.protein_sequence)
|
|
158
|
+
assert effect.modifies_protein_sequence is False
|
|
159
|
+
assert effect.modifies_coding_sequence is None
|
|
160
|
+
assert len(EffectCollection([effect]).drop_silent_and_noncoding()) == 0
|
|
161
|
+
effect.mutant_transcript = replace(effect.mutant_transcript, mutant_protein_sequence="")
|
|
162
|
+
assert effect.modifies_protein_sequence is True
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_local_duplication_is_changed(cftr, isolate_span):
|
|
166
|
+
exon = cftr.exons[4]
|
|
167
|
+
variant = StructuralVariant(cftr.contig, exon.start, "DUP", end=exon.end,
|
|
168
|
+
genome=cftr.genome)
|
|
169
|
+
effect = variant.effect_on_transcript(cftr)
|
|
170
|
+
assert effect.modifies_coding_sequence is True
|
|
171
|
+
assert effect.modifies_protein_sequence is True
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def test_replacement_primary_effects_do_not_include_local_unknown(cftr):
|
|
175
|
+
wrapper = _effect(cftr)
|
|
176
|
+
unchanged = _effect(cftr, protein=cftr.protein_sequence)
|
|
177
|
+
wrapper._primary_effects = (unchanged,)
|
|
178
|
+
assert wrapper.modifies_protein_sequence is False
|
|
179
|
+
assert len(EffectCollection([wrapper]).drop_silent_and_noncoding()) == 0
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@pytest.mark.parametrize("synonymous", [False, True])
|
|
183
|
+
def test_imported_rna_has_explicit_cds_boundaries(cftr, synonymous):
|
|
184
|
+
variant = StructuralVariant(cftr.contig, cftr.start, "BND", genome=cftr.genome)
|
|
185
|
+
start = min(cftr.start_codon_spliced_offsets)
|
|
186
|
+
cdna = cftr.sequence
|
|
187
|
+
if synonymous:
|
|
188
|
+
pos = next(i for i in range(start + 3, start + 300, 3) if cdna[i:i+3] == "GCT")
|
|
189
|
+
cdna = cdna[:pos] + "GCC" + cdna[pos+3:]
|
|
190
|
+
candidate = make_fusion_outcome(
|
|
191
|
+
variant, cftr, sequence=cdna, cds_start=start,
|
|
192
|
+
transcript_model_id="observed-model")
|
|
193
|
+
assert candidate.effect.modifies_coding_sequence is synonymous
|
|
194
|
+
assert candidate.effect.modifies_protein_sequence is False
|
|
195
|
+
assert candidate.evidence["protein_status"] == "predicted_from_observed_rna"
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def test_fusion_after_stop_is_unchanged_for_five_prime_partner(cftr):
|
|
199
|
+
partner = cached_release(81).transcript_by_id("ENST00000357654")
|
|
200
|
+
model = structural._build_fusion_mutant_transcript(
|
|
201
|
+
cftr, cftr, cftr.end - 10, partner, partner.end - 10)
|
|
202
|
+
assert model.mutant_protein_sequence == cftr.protein_sequence
|
|
203
|
+
variant = StructuralVariant(cftr.contig, cftr.end - 10, "BND", genome=cftr.genome)
|
|
204
|
+
effect = GeneFusion(variant, cftr, partner, mutant_transcript=model)
|
|
205
|
+
assert effect.modifies_coding_sequence is False
|
|
206
|
+
assert effect.modifies_protein_sequence is False
|
|
207
|
+
assert len(EffectCollection([effect]).drop_silent_and_noncoding()) == 0
|
|
208
|
+
# On the opposite end, compare the same allele with that transcript's
|
|
209
|
+
# reference, but still use the 5' partner's annotated start for the CDS.
|
|
210
|
+
other = GeneFusion(variant, partner, cftr, mutant_transcript=model,
|
|
211
|
+
five_prime_transcript=cftr, three_prime_transcript=partner)
|
|
212
|
+
assert other.modifies_coding_sequence is True
|
|
213
|
+
assert other.modifies_protein_sequence is True
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@pytest.mark.parametrize("region", ["5utr", "3utr"])
|
|
217
|
+
def test_local_utr_duplication_does_not_change_cds(cftr, region, isolate_span):
|
|
218
|
+
start = cftr.start + 10 if region == "5utr" else cftr.end - 20
|
|
219
|
+
variant = StructuralVariant(cftr.contig, start, "DUP", end=start + 5,
|
|
220
|
+
genome=cftr.genome)
|
|
221
|
+
effect = variant.effect_on_transcript(cftr)
|
|
222
|
+
assert effect.modifies_coding_sequence is False
|
|
223
|
+
assert effect.modifies_protein_sequence is False
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def test_mitochondrial_translation_uses_correct_table():
|
|
227
|
+
transcript = cached_release(81).transcript_by_id("ENST00000361624")
|
|
228
|
+
effect = _effect(transcript, cdna=transcript.sequence,
|
|
229
|
+
segments=_identity_segments(transcript))
|
|
230
|
+
assert effect.modifies_coding_sequence is False
|
|
231
|
+
assert effect.modifies_protein_sequence is False
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def test_ambiguous_coding_sequence_remains_unknown(cftr):
|
|
235
|
+
start = min(cftr.start_codon_spliced_offsets)
|
|
236
|
+
cdna = cftr.sequence[:start+6] + "N" + cftr.sequence[start+7:]
|
|
237
|
+
effect = _effect(cftr, cdna=cdna, segments=_identity_segments(cftr))
|
|
238
|
+
assert effect.modifies_coding_sequence is None
|
|
239
|
+
assert effect.modifies_protein_sequence is None
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def test_split_start_codon_and_deleted_start_base(cftr):
|
|
243
|
+
start = min(cftr.start_codon_spliced_offsets)
|
|
244
|
+
segments = (ReferenceSegment(cftr, 0, start + 1),
|
|
245
|
+
ReferenceSegment(cftr, start + 1, len(cftr.sequence)))
|
|
246
|
+
effect = _effect(cftr, cdna=cftr.sequence, segments=segments)
|
|
247
|
+
assert effect.modifies_protein_sequence is False
|
|
248
|
+
cut_segments = (segments[0], replace(segments[1], start=start + 2))
|
|
249
|
+
cut_cdna = cftr.sequence[:start+1] + cftr.sequence[start+2:]
|
|
250
|
+
cut = _effect(cftr, cdna=cut_cdna, segments=cut_segments)
|
|
251
|
+
# Do not translate from one retained base of a destroyed initiator.
|
|
252
|
+
assert cut.modifies_protein_sequence is None
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def test_unresolved_and_cryptic_alternatives_are_not_known_unchanged(cftr):
|
|
256
|
+
primary = _effect(cftr, protein=cftr.protein_sequence)
|
|
257
|
+
unknown = Unresolved(primary.variant, cftr, mechanism="missing_sequence")
|
|
258
|
+
cryptic = CrypticExonCandidate(primary.variant, cftr.contig, cftr.start, cftr.start + 20)
|
|
259
|
+
for alternative in (unknown, cryptic):
|
|
260
|
+
assert alternative.modifies_coding_sequence is None
|
|
261
|
+
assert alternative.modifies_protein_sequence is None
|
|
262
|
+
primary._extra_candidates = (EffectCandidate(alternative, source="test"),)
|
|
263
|
+
assert primary.modifies_protein_sequence is None
|
|
264
|
+
assert len(EffectCollection([primary]).drop_silent_and_noncoding()) == 1
|
|
265
|
+
assert len(EffectCollection([primary]).drop_silent_and_noncoding(False)) == 0
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def test_annotation_without_start_codon_remains_unresolved(cftr, monkeypatch):
|
|
269
|
+
exon = cftr.exons[4]
|
|
270
|
+
variant = StructuralVariant(cftr.contig, exon.start, "DEL", end=exon.end,
|
|
271
|
+
genome=cftr.genome)
|
|
272
|
+
monkeypatch.setattr(type(cftr), "contains_start_codon", property(lambda self: False))
|
|
273
|
+
effect = LargeDeletion(variant, cftr, affected_exons=[exon])
|
|
274
|
+
assert effect.modifies_coding_sequence is True
|
|
275
|
+
assert effect.modifies_protein_sequence is None
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def test_annotation_without_cds_features_is_unknown(cftr, monkeypatch):
|
|
279
|
+
def missing_cds(self):
|
|
280
|
+
raise ValueError("No CDS features")
|
|
281
|
+
|
|
282
|
+
monkeypatch.setattr(type(cftr), "coding_sequence_position_ranges", property(missing_cds))
|
|
283
|
+
variant = StructuralVariant(cftr.contig, cftr.start, "DEL", end=cftr.end,
|
|
284
|
+
genome=cftr.genome)
|
|
285
|
+
effect = LargeDeletion(variant, cftr, affected_exons=cftr.exons)
|
|
286
|
+
assert effect.modifies_coding_sequence is None
|
|
287
|
+
assert effect.modifies_protein_sequence is None
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def test_empty_and_cyclic_candidate_sets_are_unknown(cftr):
|
|
291
|
+
first, second = _effect(cftr), _effect(cftr)
|
|
292
|
+
first._primary_effects = ()
|
|
293
|
+
assert first.modifies_protein_sequence is None
|
|
294
|
+
first._primary_effects = (second,)
|
|
295
|
+
second._primary_effects = (first,)
|
|
296
|
+
assert first.modifies_protein_sequence is None
|
|
@@ -306,6 +306,9 @@ class Unresolved(TranscriptMutationEffect):
|
|
|
306
306
|
unresolved does not mean the variant is harmless.
|
|
307
307
|
"""
|
|
308
308
|
|
|
309
|
+
modifies_coding_sequence = None
|
|
310
|
+
modifies_protein_sequence = None
|
|
311
|
+
|
|
309
312
|
def __init__(self, variant, transcript, mechanism, reason=None):
|
|
310
313
|
TranscriptMutationEffect.__init__(self, variant, transcript)
|
|
311
314
|
self.mechanism = mechanism
|
|
@@ -1424,6 +1427,25 @@ class StructuralVariantEffect(TranscriptMutationEffect, MultiOutcomeEffect):
|
|
|
1424
1427
|
return getattr(
|
|
1425
1428
|
self.mutant_transcript, "mutant_protein_sequence", None)
|
|
1426
1429
|
|
|
1430
|
+
@property
|
|
1431
|
+
def modifies_coding_sequence(self):
|
|
1432
|
+
"""True if any candidate changes CDS, None if unresolved, else False.
|
|
1433
|
+
|
|
1434
|
+
Protein equality alone does not establish coding-sequence equality.
|
|
1435
|
+
"""
|
|
1436
|
+
from .sequence_change import modification_status
|
|
1437
|
+
return modification_status(self, "modifies_coding_sequence")
|
|
1438
|
+
|
|
1439
|
+
@property
|
|
1440
|
+
def modifies_protein_sequence(self):
|
|
1441
|
+
"""True if any candidate changes protein, None if unresolved, else False.
|
|
1442
|
+
|
|
1443
|
+
A set with an unchanged primary and a changed alternative returns True.
|
|
1444
|
+
False requires every candidate to be known unchanged; None is not silent.
|
|
1445
|
+
"""
|
|
1446
|
+
from .sequence_change import modification_status
|
|
1447
|
+
return modification_status(self, "modifies_protein_sequence")
|
|
1448
|
+
|
|
1427
1449
|
@property
|
|
1428
1450
|
def candidates(self):
|
|
1429
1451
|
"""Unified :class:`~varcode.EffectCandidate` view over primary
|
|
@@ -1617,6 +1639,8 @@ class CrypticExonCandidate(MutationEffect):
|
|
|
1617
1639
|
"""
|
|
1618
1640
|
|
|
1619
1641
|
short_description = "sv-cryptic-exon-candidate"
|
|
1642
|
+
modifies_coding_sequence = None
|
|
1643
|
+
modifies_protein_sequence = None
|
|
1620
1644
|
|
|
1621
1645
|
def __init__(
|
|
1622
1646
|
self, variant, contig, interval_start, interval_end,
|
|
@@ -220,11 +220,25 @@ class EffectCollection(Collection):
|
|
|
220
220
|
return self.filter(
|
|
221
221
|
lambda effect: effect_priority(effect) >= min_priority)
|
|
222
222
|
|
|
223
|
-
def drop_silent_and_noncoding(self):
|
|
223
|
+
def drop_silent_and_noncoding(self, keep_unresolved=True):
|
|
224
224
|
"""
|
|
225
|
-
|
|
225
|
+
Keep effects with a protein-changing candidate or unresolved SV outcome.
|
|
226
|
+
|
|
227
|
+
Parameters
|
|
228
|
+
----------
|
|
229
|
+
keep_unresolved : bool
|
|
230
|
+
Keep effects whose protein-change status is None (default True).
|
|
231
|
+
False requires a positive prediction of protein change. Candidate
|
|
232
|
+
sets are retained intact if any alternative qualifies; their order
|
|
233
|
+
and provenance are unchanged.
|
|
226
234
|
"""
|
|
227
|
-
|
|
235
|
+
from .sequence_change import modification_status
|
|
236
|
+
|
|
237
|
+
def retain(effect):
|
|
238
|
+
status = modification_status(effect, "modifies_protein_sequence")
|
|
239
|
+
return status is True or (keep_unresolved and status is None)
|
|
240
|
+
|
|
241
|
+
return self.filter(retain)
|
|
228
242
|
|
|
229
243
|
def detailed_string(self):
|
|
230
244
|
"""
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Sequence-change flags for existing SV predictions, not a new SV model."""
|
|
2
|
+
|
|
3
|
+
from .codon_tables import codon_table_for_transcript, translate_sequence
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def modification_status(effect, attribute):
|
|
7
|
+
"""Any changed candidate wins; otherwise unknown wins over unchanged.
|
|
8
|
+
|
|
9
|
+
Walk by identity because SVs include themselves among their candidates.
|
|
10
|
+
Read their local status directly, avoiding recursive property calls.
|
|
11
|
+
"""
|
|
12
|
+
from .effect_classes import MultiOutcomeEffect, StructuralVariantEffect
|
|
13
|
+
|
|
14
|
+
pending, seen = [effect], set()
|
|
15
|
+
unknown = False
|
|
16
|
+
evaluated = False
|
|
17
|
+
while pending:
|
|
18
|
+
current = pending.pop()
|
|
19
|
+
if id(current) in seen:
|
|
20
|
+
continue
|
|
21
|
+
seen.add(id(current))
|
|
22
|
+
candidates = (current.candidates
|
|
23
|
+
if isinstance(current, MultiOutcomeEffect) else ())
|
|
24
|
+
if isinstance(current, StructuralVariantEffect):
|
|
25
|
+
if any(candidate.effect is current for candidate in candidates):
|
|
26
|
+
evaluated = True
|
|
27
|
+
coding, protein = structural_sequence_changes(current)
|
|
28
|
+
status = protein if attribute == "modifies_protein_sequence" else coding
|
|
29
|
+
else:
|
|
30
|
+
# An explicit primary_effects tuple replaces the local result.
|
|
31
|
+
status = False if candidates else None
|
|
32
|
+
else:
|
|
33
|
+
evaluated = True
|
|
34
|
+
status = getattr(current, attribute)
|
|
35
|
+
if status is True:
|
|
36
|
+
return True
|
|
37
|
+
unknown |= status is None
|
|
38
|
+
pending.extend(candidate.effect for candidate in candidates)
|
|
39
|
+
return None if unknown or not evaluated else False
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _retained_start(model, transcript):
|
|
43
|
+
"""Map all three annotated start bases, including split segment boundaries."""
|
|
44
|
+
positions = [set(), set(), set()]
|
|
45
|
+
start = min(transcript.start_codon_spliced_offsets)
|
|
46
|
+
offset = 0
|
|
47
|
+
for segment in model.reference_segments or ():
|
|
48
|
+
if segment.source == transcript and segment.strand == "+":
|
|
49
|
+
for i in range(3):
|
|
50
|
+
if segment.start <= start + i < segment.end:
|
|
51
|
+
positions[i].add(offset + start + i - segment.start)
|
|
52
|
+
offset += segment.length
|
|
53
|
+
return next((p for p in sorted(positions[0])
|
|
54
|
+
if p + 1 in positions[1] and p + 2 in positions[2]), None)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _coding_sequence(model, initiator):
|
|
58
|
+
"""Read a known ORF, or the retained reference ORF; never search for one.
|
|
59
|
+
|
|
60
|
+
Return None for unmapped assemblies, partial fragments, missing sequence,
|
|
61
|
+
overlaid edits whose coordinates haven't been mapped, or an incomplete ORF.
|
|
62
|
+
"""
|
|
63
|
+
if model.cdna_sequence is None:
|
|
64
|
+
return None
|
|
65
|
+
evidence = model.evidence or {}
|
|
66
|
+
start, end = evidence.get("cds_start"), evidence.get("cds_end")
|
|
67
|
+
if start is None or end is None:
|
|
68
|
+
if not initiator.complete or model.edits:
|
|
69
|
+
return None
|
|
70
|
+
start = _retained_start(model, initiator)
|
|
71
|
+
end = None
|
|
72
|
+
if start is None:
|
|
73
|
+
return None
|
|
74
|
+
sequence = model.cdna_sequence[start:end].upper()
|
|
75
|
+
table = codon_table_for_transcript(initiator)
|
|
76
|
+
if sequence[:3] not in table.start_codons:
|
|
77
|
+
return None
|
|
78
|
+
for i in range(3, len(sequence) - 2, 3):
|
|
79
|
+
if sequence[i:i + 3] in table.stop_codons:
|
|
80
|
+
coding = sequence[:i + 3]
|
|
81
|
+
return coding if not set(coding) - set("ACGT") else None
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def structural_sequence_changes(effect):
|
|
86
|
+
"""Return local (coding, protein) changes as True / False / None.
|
|
87
|
+
|
|
88
|
+
Compare against the transcript being annotated, including on a fusion's
|
|
89
|
+
3' side. The 5' partner supplies the initiation site and translation table.
|
|
90
|
+
These are sequence predictions, not evidence of expression or translation.
|
|
91
|
+
"""
|
|
92
|
+
from .effect_classes import LargeDeletion
|
|
93
|
+
|
|
94
|
+
transcript = effect.transcript
|
|
95
|
+
model = effect.mutant_transcript
|
|
96
|
+
coding_status = protein_status = None
|
|
97
|
+
if model is not None:
|
|
98
|
+
protein = model.mutant_protein_sequence
|
|
99
|
+
reference_protein = transcript.protein_sequence
|
|
100
|
+
if protein is not None and reference_protein is not None:
|
|
101
|
+
# An empty protein is a known absence, not missing data.
|
|
102
|
+
if "X" not in protein and "X" not in reference_protein:
|
|
103
|
+
protein_status = protein != reference_protein
|
|
104
|
+
initiator = getattr(effect, "five_prime_transcript", transcript)
|
|
105
|
+
coding = _coding_sequence(model, initiator)
|
|
106
|
+
if coding is not None:
|
|
107
|
+
reference_coding = transcript.coding_sequence
|
|
108
|
+
if reference_coding is not None:
|
|
109
|
+
coding_status = coding != reference_coding.upper()
|
|
110
|
+
if protein_status is None and reference_protein is not None:
|
|
111
|
+
# Initiator methionine also applies to alternative start codons.
|
|
112
|
+
protein = "M" + translate_sequence(
|
|
113
|
+
coding[3:], codon_table=codon_table_for_transcript(initiator))
|
|
114
|
+
if "X" not in reference_protein:
|
|
115
|
+
protein_status = protein != reference_protein
|
|
116
|
+
|
|
117
|
+
# A pure deletion can establish CDS/start loss without a translated model.
|
|
118
|
+
# Do not apply DNA-span inference to a supplied assembly or an RNA outcome.
|
|
119
|
+
if (isinstance(effect, LargeDeletion)
|
|
120
|
+
and not getattr(effect.variant, "alt_assembly", None)
|
|
121
|
+
and (model is None or model.annotator_name == "structural_variant")):
|
|
122
|
+
from .structural import _affected_span
|
|
123
|
+
start, end = _affected_span(effect.variant)
|
|
124
|
+
try:
|
|
125
|
+
coding_ranges = transcript.coding_sequence_position_ranges
|
|
126
|
+
except ValueError:
|
|
127
|
+
# A protein-coding biotype need not have annotated CDS features.
|
|
128
|
+
return coding_status, protein_status
|
|
129
|
+
coding_overlap = any(start <= hi and lo <= end
|
|
130
|
+
for lo, hi in coding_ranges)
|
|
131
|
+
if coding_status is None:
|
|
132
|
+
coding_status = coding_overlap
|
|
133
|
+
if protein_status is None:
|
|
134
|
+
if not coding_overlap:
|
|
135
|
+
protein_status = False
|
|
136
|
+
elif (transcript.contains_start_codon
|
|
137
|
+
and any(start <= pos <= end for pos in transcript.start_codon_positions)):
|
|
138
|
+
protein_status = True
|
|
139
|
+
|
|
140
|
+
if protein_status is True and coding_status is None:
|
|
141
|
+
coding_status = True
|
|
142
|
+
return coding_status, protein_status
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "9.3.0"
|
|
@@ -72,6 +72,7 @@ tests/test_stop_codon_classification_bugs.py
|
|
|
72
72
|
tests/test_string_helpers.py
|
|
73
73
|
tests/test_structural_variant_annotator.py
|
|
74
74
|
tests/test_structural_variants.py
|
|
75
|
+
tests/test_sv_sequence_changes.py
|
|
75
76
|
tests/test_sv_tabular_metadata.py
|
|
76
77
|
tests/test_symbolic_alleles.py
|
|
77
78
|
tests/test_symbolic_sv_padding.py
|
|
@@ -153,6 +154,7 @@ varcode/effects/effect_prediction_coding_frameshift.py
|
|
|
153
154
|
varcode/effects/effect_prediction_coding_in_frame.py
|
|
154
155
|
varcode/effects/fast_path.py
|
|
155
156
|
varcode/effects/mutate.py
|
|
157
|
+
varcode/effects/sequence_change.py
|
|
156
158
|
varcode/effects/structural.py
|
|
157
159
|
varcode/effects/transcript_helpers.py
|
|
158
160
|
varcode/effects/translate.py
|
varcode-9.2.8/varcode/version.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "9.2.8"
|
|
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
|
|
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
|
|
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
|