varcode 9.2.0__tar.gz → 9.2.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.
- {varcode-9.2.0/varcode.egg-info → varcode-9.2.2}/PKG-INFO +1 -1
- varcode-9.2.2/tests/test_sv_tabular_metadata.py +69 -0
- varcode-9.2.2/tests/test_symbolic_sv_padding.py +81 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/csv_helpers.py +12 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/effects/effect_collection.py +7 -2
- {varcode-9.2.0 → varcode-9.2.2}/varcode/structural_variant.py +19 -7
- {varcode-9.2.0 → varcode-9.2.2}/varcode/sv_allele_parser.py +10 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/variant_collection.py +9 -2
- varcode-9.2.2/varcode/version.py +1 -0
- {varcode-9.2.0 → varcode-9.2.2/varcode.egg-info}/PKG-INFO +1 -1
- {varcode-9.2.0 → varcode-9.2.2}/varcode.egg-info/SOURCES.txt +2 -0
- varcode-9.2.0/varcode/version.py +0 -1
- {varcode-9.2.0 → varcode-9.2.2}/LICENSE +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/MANIFEST.in +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/README.md +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/pyproject.toml +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/requirements.txt +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/setup.cfg +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_annotator_divergence_scenarios.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_annotator_parity_adversarial.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_annotators.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_cli_effects.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_cli_genes.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_cli_without_pkg_resources.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_collection_filtering.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_collection_variants_attr_consistency.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_common.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_cosmic_mutations.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_cryptic_exons.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_csv_roundtrip.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_dbnsfp_validation.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_default_structural_helpers.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_effect_annotation_errors.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_effect_candidates.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_effect_classes.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_effect_collection.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_effect_collection_serialization.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_effect_collection_sort_order.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_effect_hypotheses.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_effects_from_mutagenix_variants.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_exacto_fusions.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_exonic_splice_site.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_fast_path.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_frameshift_cterminus_regression.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_frameshift_helpers.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_genome_contig_validation.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_genome_sequence.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_genomic_layout.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_genotype.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_genotype_from_vcf.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_germline_context.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_germline_effects.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_haplotype_effect.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_maf.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_mitochondrial.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_mm10_klf6_frameshift.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_mouse.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_mutant_transcript.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_mutate.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_no_duplicate_variants.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_osteosarc_fusions.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_partial_annotators.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_premature_stop_short_description.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_problematic_variants.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_protein_diff_parity.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_read_phase_resolver.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_real_world_callers.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_reference.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_reference_mismatch_error.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_reference_segments.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_rna_evidence.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_rna_haplotype_sequences.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_rna_read_phasing_source.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_selenocysteine.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_silent_aa_pos.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_silent_hgvs_description.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_splice_graph.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_splice_outcomes.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_splice_site_effects.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_stop_codon_classification_bugs.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_string_helpers.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_structural_variant_annotator.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_structural_variants.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_symbolic_alleles.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_timings.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_transcript_layout.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_transcript_model.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_transforms_left_align.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_transforms_pair_breakends.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_variant.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_variant_collection.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_vcf.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_vcf_output.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_vcf_parsing.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_vcf_phase_resolver.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/tests/test_vcf_spec_examples.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/__init__.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/annotators/__init__.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/annotators/fast.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/annotators/protein_diff.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/annotators/registry.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/cli/__init__.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/cli/effects_script.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/cli/genes_script.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/cli/logging.conf +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/cli/variant_args.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/cli/version_info.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/common.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/cryptic_exons.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/effect_candidates.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/effect_hypotheses.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/effects/__init__.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/effects/classify.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/effects/codon_tables.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/effects/common.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/effects/effect_classes.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/effects/effect_helpers.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/effects/effect_ordering.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/effects/effect_prediction.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/effects/effect_prediction_coding.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/effects/effect_prediction_coding_frameshift.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/effects/effect_prediction_coding_in_frame.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/effects/fast_path.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/effects/mutate.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/effects/structural.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/effects/transcript_helpers.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/effects/translate.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/errors.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/exacto.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/genome.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/genome_sequence.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/genomic_layout.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/genotype.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/germline.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/maf.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/mutant_transcript.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/nucleotides.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/phasing.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/realized_effects.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/reference.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/rna_evidence.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/rna_read_phasing.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/splice_graph.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/splice_outcomes.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/string_helpers.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/transcript_layout.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/transcript_model.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/transforms/__init__.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/ucsc_reference_names.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/util.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/variant.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/vcf.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/vcf_output.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode/vcf_parsing.py +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode.egg-info/dependency_links.txt +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode.egg-info/entry_points.txt +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode.egg-info/requires.txt +0 -0
- {varcode-9.2.0 → varcode-9.2.2}/varcode.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Structural records must never masquerade as placeholder SNVs (#417)."""
|
|
2
|
+
|
|
3
|
+
import pickle
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from varcode import StructuralVariant, Variant, VariantCollection
|
|
8
|
+
from varcode.csv_helpers import STRUCTURAL_VARIANT_COLUMNS
|
|
9
|
+
from varcode.effects import EffectCollection, Intergenic
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.mark.parametrize("ref", ["A", "C", "G", "T", "N"])
|
|
13
|
+
@pytest.mark.parametrize("kind,alt", [
|
|
14
|
+
("DEL", "<DEL>"), ("DUP", "<DUP>"), ("INV", "<INV>"),
|
|
15
|
+
("CNV", "<CN3>"), ("INS", "<INS:ME:ALU>"),
|
|
16
|
+
("BND", "G[2:200["), ("BND", ".ACGT"), ("INS", "ACGT"),
|
|
17
|
+
])
|
|
18
|
+
def test_actual_alleles_and_no_small_edit_flags(ref, kind, alt):
|
|
19
|
+
sv = StructuralVariant("1", 100, kind, end=200, ref=ref, alt=alt)
|
|
20
|
+
assert sv.start == sv.original_start == 100
|
|
21
|
+
assert sv.ref == sv.original_ref == ref
|
|
22
|
+
assert sv.alt == sv.original_alt == sv.symbolic_alt == alt
|
|
23
|
+
for name in ["is_snv", "is_indel", "is_insertion", "is_deletion",
|
|
24
|
+
"is_transition", "is_transversion"]:
|
|
25
|
+
assert getattr(sv, name) is False
|
|
26
|
+
assert sv.is_structural
|
|
27
|
+
for restored in [StructuralVariant.from_json(sv.to_json()), pickle.loads(pickle.dumps(sv))]:
|
|
28
|
+
assert restored == sv
|
|
29
|
+
assert (restored.ref, restored.alt) == (ref, alt)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def examples():
|
|
33
|
+
snv = Variant("1", 50, "C", "T")
|
|
34
|
+
sv = StructuralVariant("1", 100, "DEL", end=200, ref="T", affected_start=101,
|
|
35
|
+
mate_contig="2", mate_start=300)
|
|
36
|
+
for variant in [snv, sv]:
|
|
37
|
+
variant._gene_names = []
|
|
38
|
+
variant._gene_ids = []
|
|
39
|
+
return snv, sv
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.mark.parametrize("effects", [False, True])
|
|
43
|
+
def test_mixed_tables_retain_structural_coordinates(effects, tmp_path):
|
|
44
|
+
snv, sv = examples()
|
|
45
|
+
collection = (EffectCollection([Intergenic(snv), Intergenic(sv)], sort_key=False)
|
|
46
|
+
if effects else VariantCollection([snv, sv]))
|
|
47
|
+
df = collection.to_dataframe()
|
|
48
|
+
assert list(df.columns) == list(collection._DATAFRAME_COLUMNS + STRUCTURAL_VARIANT_COLUMNS)
|
|
49
|
+
row = df.iloc[1]
|
|
50
|
+
assert (row["ref"], row["alt"], row["sv_type"]) == ("T", "<DEL>", "DEL")
|
|
51
|
+
assert (row["start"], row["end"], row["affected_start"], row["affected_end"]) == (100, 200, 101, 200)
|
|
52
|
+
assert (row["mate_contig"], row["mate_start"]) == ("2", 300)
|
|
53
|
+
if effects:
|
|
54
|
+
assert not row["is_snv"] and not row["is_transition"] and not row["is_transversion"]
|
|
55
|
+
assert df.iloc[0]["is_snv"] and df.iloc[0]["is_transition"]
|
|
56
|
+
path = tmp_path / "mixed.csv"
|
|
57
|
+
collection.to_csv(str(path))
|
|
58
|
+
with pytest.raises(ValueError, match="Structural rows cannot be reconstructed"):
|
|
59
|
+
type(collection).from_csv(str(path))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@pytest.mark.parametrize("effects", [False, True])
|
|
63
|
+
@pytest.mark.parametrize("empty", [False, True])
|
|
64
|
+
def test_point_only_and_empty_schemas_unchanged(effects, empty):
|
|
65
|
+
snv, _ = examples()
|
|
66
|
+
variants = [] if empty else [snv]
|
|
67
|
+
collection = (EffectCollection([Intergenic(v) for v in variants]) if effects
|
|
68
|
+
else VariantCollection(variants))
|
|
69
|
+
assert tuple(collection.to_dataframe().columns) == collection._DATAFRAME_COLUMNS
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Symbolic POS is retained; only POS+1..END changes (#404, VCF 4.3)."""
|
|
2
|
+
|
|
3
|
+
from types import SimpleNamespace
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from pyensembl import cached_release
|
|
7
|
+
|
|
8
|
+
from varcode import StructuralVariant, parse_symbolic_alt, load_vcf
|
|
9
|
+
from varcode.effects.structural import (
|
|
10
|
+
_build_deletion_mutant_transcript, _build_duplication_mutant_transcript,
|
|
11
|
+
_build_inversion_mutant_transcript,
|
|
12
|
+
)
|
|
13
|
+
from varcode.nucleotides import reverse_complement
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.parametrize("kind", ["DEL", "DUP", "INV", "CNV", "CN0", "CN3"])
|
|
17
|
+
def test_parsed_span_excludes_padding_but_keeps_record_coordinates(kind):
|
|
18
|
+
sv = parse_symbolic_alt("1", 100, "T", "<%s>" % kind, info={"END": 105})
|
|
19
|
+
assert (sv.start, sv.end) == (100, 105)
|
|
20
|
+
assert (sv.affected_start, sv.affected_end, sv.length) == (101, 105, 5)
|
|
21
|
+
assert StructuralVariant.from_json(sv.to_json()) == sv
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.mark.parametrize("kind", ["DEL", "DUP", "INV", "CNV"])
|
|
25
|
+
@pytest.mark.parametrize("info", [{}, {"END": 100}, {"END": 99}])
|
|
26
|
+
def test_no_nonempty_span_cannot_delete_or_copy_the_anchor(kind, info):
|
|
27
|
+
with pytest.raises(ValueError, match="END greater than POS"):
|
|
28
|
+
parse_symbolic_alt("1", 100, "T", "<%s>" % kind, info=info)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.mark.parametrize("reverse", [False, True])
|
|
32
|
+
@pytest.mark.parametrize("kind,builder", [
|
|
33
|
+
("DEL", _build_deletion_mutant_transcript),
|
|
34
|
+
("DUP", _build_duplication_mutant_transcript),
|
|
35
|
+
("INV", _build_inversion_mutant_transcript),
|
|
36
|
+
])
|
|
37
|
+
def test_exact_mutant_cdna_on_both_strands(kind, builder, reverse):
|
|
38
|
+
sequence = "ACGTTGCAAGCTTAGGCTAC"
|
|
39
|
+
tx = SimpleNamespace(exons=[SimpleNamespace(start=100, end=119)],
|
|
40
|
+
start=100, end=119, sequence=sequence,
|
|
41
|
+
on_backward_strand=reverse)
|
|
42
|
+
sv = parse_symbolic_alt("1", 109, "G", "<%s>" % kind, info={"END": 114})
|
|
43
|
+
a, b = (5, 10) if reverse else (10, 15)
|
|
44
|
+
if kind == "DEL":
|
|
45
|
+
expected = sequence[:a] + sequence[b:]
|
|
46
|
+
elif kind == "DUP":
|
|
47
|
+
expected = sequence[:b] + sequence[a:b] + sequence[b:]
|
|
48
|
+
else:
|
|
49
|
+
expected = sequence[:a] + reverse_complement(sequence[a:b]) + sequence[b:]
|
|
50
|
+
model = builder(sv, tx)
|
|
51
|
+
if kind == "INV":
|
|
52
|
+
# This builder preserves the layout but intentionally defers sequence.
|
|
53
|
+
observed = "".join(reverse_complement(sequence[s.start:s.end])
|
|
54
|
+
if s.strand == "-" else sequence[s.start:s.end]
|
|
55
|
+
for s in model.reference_segments)
|
|
56
|
+
assert observed == expected
|
|
57
|
+
else:
|
|
58
|
+
assert model.cdna_sequence == expected
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_vcf_deletion_does_not_remove_cftr_exon_terminal_anchor(tmp_path):
|
|
62
|
+
genome = cached_release(81)
|
|
63
|
+
tx = genome.transcript_by_id("ENST00000003084")
|
|
64
|
+
first, second = tx.exons[:2]
|
|
65
|
+
path = tmp_path / "anchor.vcf"
|
|
66
|
+
path.write_text(
|
|
67
|
+
"##fileformat=VCFv4.3\n"
|
|
68
|
+
"##INFO=<ID=END,Number=1,Type=Integer,Description=\"End\">\n"
|
|
69
|
+
"#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO\n"
|
|
70
|
+
"7\t%d\t.\tT\t<DEL>\t.\tPASS\tEND=%d\n" % (first.end, second.end))
|
|
71
|
+
(sv,) = load_vcf(str(path), genome=genome, parse_structural_variants=True)
|
|
72
|
+
effect = sv.effect_on_transcript(tx)
|
|
73
|
+
assert first not in effect.affected_exons
|
|
74
|
+
assert second in effect.affected_exons
|
|
75
|
+
a, b = first.end - first.start + 1, second.end - second.start + 1
|
|
76
|
+
assert effect.mutant_transcript.cdna_sequence == tx.sequence[:a] + tx.sequence[a+b:]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_direct_constructor_span_defaults_remain_explicit():
|
|
80
|
+
sv = StructuralVariant("1", 100, "DEL", end=105)
|
|
81
|
+
assert (sv.affected_start, sv.affected_end, sv.length) == (100, 105, 6)
|
|
@@ -39,6 +39,18 @@ HEADER_PREFIX = "#"
|
|
|
39
39
|
# openvax/varcode#274.
|
|
40
40
|
CONTIG_COLUMN_ALIASES = ("contig", "chr")
|
|
41
41
|
|
|
42
|
+
STRUCTURAL_VARIANT_COLUMNS = (
|
|
43
|
+
"sv_type", "end", "mate_contig", "mate_start", "affected_start", "affected_end",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def reject_structural_csv(df):
|
|
48
|
+
"""CSV summaries do not encode enough state to restore structural effects."""
|
|
49
|
+
if "sv_type" in df and df["sv_type"].fillna("").ne("").any():
|
|
50
|
+
raise ValueError(
|
|
51
|
+
"Structural rows cannot be reconstructed from CSV summaries. "
|
|
52
|
+
"Retain the original VCF/JSON variants and RNA evidence instead.")
|
|
53
|
+
|
|
42
54
|
|
|
43
55
|
def resolve_contig_column(columns):
|
|
44
56
|
"""Return the first of :data:`CONTIG_COLUMN_ALIASES` that appears in
|
|
@@ -17,7 +17,9 @@ from sercol import Collection
|
|
|
17
17
|
|
|
18
18
|
from ..csv_helpers import (
|
|
19
19
|
CONTIG_COLUMN_ALIASES,
|
|
20
|
+
STRUCTURAL_VARIANT_COLUMNS,
|
|
20
21
|
read_metadata_header,
|
|
22
|
+
reject_structural_csv,
|
|
21
23
|
resolve_contig_column,
|
|
22
24
|
warn_on_version_drift,
|
|
23
25
|
write_metadata_header,
|
|
@@ -344,6 +346,8 @@ class EffectCollection(Collection):
|
|
|
344
346
|
|
|
345
347
|
def to_dataframe(self):
|
|
346
348
|
"""Build a dataframe from the effect collection."""
|
|
349
|
+
structural_columns = STRUCTURAL_VARIANT_COLUMNS if any(
|
|
350
|
+
getattr(e.variant, "is_structural", False) for e in self) else ()
|
|
347
351
|
# list of properties to extract from Variant objects if they're
|
|
348
352
|
# not None
|
|
349
353
|
variant_properties = [
|
|
@@ -354,7 +358,7 @@ class EffectCollection(Collection):
|
|
|
354
358
|
"is_snv",
|
|
355
359
|
"is_transversion",
|
|
356
360
|
"is_transition"
|
|
357
|
-
]
|
|
361
|
+
] + list(structural_columns)
|
|
358
362
|
|
|
359
363
|
def row_from_effect(effect):
|
|
360
364
|
row = OrderedDict()
|
|
@@ -375,7 +379,7 @@ class EffectCollection(Collection):
|
|
|
375
379
|
# CSV round-trip doesn't have to special-case len == 0.
|
|
376
380
|
return pd.DataFrame.from_records(
|
|
377
381
|
[row_from_effect(effect) for effect in self],
|
|
378
|
-
columns=self._DATAFRAME_COLUMNS,
|
|
382
|
+
columns=self._DATAFRAME_COLUMNS + structural_columns,
|
|
379
383
|
)
|
|
380
384
|
|
|
381
385
|
def to_csv(self, path, include_header=True):
|
|
@@ -485,6 +489,7 @@ class EffectCollection(Collection):
|
|
|
485
489
|
# dtype is a no-op for whichever column is absent.
|
|
486
490
|
df = pd.read_csv(
|
|
487
491
|
path, comment="#", dtype={"chr": str, "contig": str})
|
|
492
|
+
reject_structural_csv(df)
|
|
488
493
|
contig_col = resolve_contig_column(df.columns)
|
|
489
494
|
if contig_col is None:
|
|
490
495
|
raise ValueError(
|
|
@@ -179,8 +179,8 @@ class StructuralVariant(Variant):
|
|
|
179
179
|
|
|
180
180
|
The SV position model:
|
|
181
181
|
|
|
182
|
-
* :attr:`start` — 1-based
|
|
183
|
-
|
|
182
|
+
* :attr:`start` — 1-based event/record position (matches VCF POS).
|
|
183
|
+
For parsed symbolic spans this is the retained padding base.
|
|
184
184
|
* :attr:`end` — 1-based *inclusive* end. For a DEL/DUP/INV/CNV
|
|
185
185
|
this is the SV endpoint on the same contig. For an INS it
|
|
186
186
|
equals start (insertions are zero-width in reference coords).
|
|
@@ -188,8 +188,8 @@ class StructuralVariant(Variant):
|
|
|
188
188
|
:attr:`mate_contig` / :attr:`mate_start`.
|
|
189
189
|
* :attr:`affected_start` / :attr:`affected_end` — inclusive bases
|
|
190
190
|
changed by a span event. These normally equal ``start`` / ``end``;
|
|
191
|
-
paired breakend events use them to exclude
|
|
192
|
-
bases from exon and mutant-sequence annotation.
|
|
191
|
+
parsed symbolic spans and paired breakend events use them to exclude
|
|
192
|
+
retained VCF padding bases from exon and mutant-sequence annotation.
|
|
193
193
|
|
|
194
194
|
Parameters
|
|
195
195
|
----------
|
|
@@ -242,6 +242,15 @@ class StructuralVariant(Variant):
|
|
|
242
242
|
event coordinates include a retained padding base.
|
|
243
243
|
"""
|
|
244
244
|
|
|
245
|
+
# These flags describe literal small edits, not event classes. Consumers
|
|
246
|
+
# must use is_structural/sv_type instead of interpreting symbolic alleles.
|
|
247
|
+
is_snv = False
|
|
248
|
+
is_indel = False
|
|
249
|
+
is_insertion = False
|
|
250
|
+
is_deletion = False
|
|
251
|
+
is_transition = False
|
|
252
|
+
is_transversion = False
|
|
253
|
+
|
|
245
254
|
__slots__ = (
|
|
246
255
|
"sv_type",
|
|
247
256
|
"mate_contig",
|
|
@@ -288,14 +297,14 @@ class StructuralVariant(Variant):
|
|
|
288
297
|
|
|
289
298
|
# Initialize the base Variant with a placeholder ref/alt so the
|
|
290
299
|
# nucleotide-normalization path doesn't reject <DEL>-style
|
|
291
|
-
# symbolic alleles.
|
|
292
|
-
#
|
|
300
|
+
# symbolic alleles. Restore the actual record fields below; placeholder
|
|
301
|
+
# normalization must not leak into public alleles or exports (#417).
|
|
293
302
|
Variant.__init__(
|
|
294
303
|
self,
|
|
295
304
|
contig=contig,
|
|
296
305
|
start=start,
|
|
297
306
|
ref=ref if ref else "N",
|
|
298
|
-
alt="A"
|
|
307
|
+
alt="A",
|
|
299
308
|
genome=genome,
|
|
300
309
|
ensembl=ensembl,
|
|
301
310
|
allow_extended_nucleotides=True,
|
|
@@ -325,6 +334,9 @@ class StructuralVariant(Variant):
|
|
|
325
334
|
# Preserve the original symbolic ALT string so round-tripping
|
|
326
335
|
# and downstream consumers see what the VCF said.
|
|
327
336
|
self._sv_alt = alt if alt is not None else "<%s>" % sv_type
|
|
337
|
+
self.ref = self.original_ref
|
|
338
|
+
self.alt = self.original_alt = self._sv_alt
|
|
339
|
+
self.start = self.original_start = int(start)
|
|
328
340
|
|
|
329
341
|
# Caches, like Variant's overlapping-gene / transcript caches:
|
|
330
342
|
# the junction list derived from the fields above, and results
|
|
@@ -198,10 +198,20 @@ def parse_symbolic_alt(
|
|
|
198
198
|
if copy_number is not None:
|
|
199
199
|
sv_info["copy_number"] = copy_number
|
|
200
200
|
|
|
201
|
+
# VCF symbolic POS is the retained padding base before the event.
|
|
202
|
+
# Keep record/junction coordinates unchanged; span consumers already
|
|
203
|
+
# use affected_start/end for paired breakends (#404).
|
|
204
|
+
affected_start = None
|
|
205
|
+
if top in {"DEL", "DUP", "INV", "CNV"}:
|
|
206
|
+
if int(end) <= int(start):
|
|
207
|
+
raise ValueError("A symbolic %s requires END greater than POS" % top)
|
|
208
|
+
affected_start = int(start) + 1
|
|
209
|
+
|
|
201
210
|
return StructuralVariant(
|
|
202
211
|
contig=contig,
|
|
203
212
|
start=int(start),
|
|
204
213
|
end=int(end),
|
|
214
|
+
affected_start=affected_start,
|
|
205
215
|
sv_type=top,
|
|
206
216
|
alt=alt,
|
|
207
217
|
ref=ref or "N",
|
|
@@ -19,7 +19,9 @@ from .effects import EffectCollection
|
|
|
19
19
|
from .common import memoize
|
|
20
20
|
from .csv_helpers import (
|
|
21
21
|
CONTIG_COLUMN_ALIASES,
|
|
22
|
+
STRUCTURAL_VARIANT_COLUMNS,
|
|
22
23
|
read_metadata_header,
|
|
24
|
+
reject_structural_csv,
|
|
23
25
|
resolve_contig_column,
|
|
24
26
|
warn_on_version_drift,
|
|
25
27
|
write_metadata_header,
|
|
@@ -434,8 +436,10 @@ class VariantCollection(Collection):
|
|
|
434
436
|
|
|
435
437
|
def to_dataframe(self):
|
|
436
438
|
"""Build a DataFrame from this variant collection."""
|
|
439
|
+
structural_columns = STRUCTURAL_VARIANT_COLUMNS if any(
|
|
440
|
+
getattr(v, "is_structural", False) for v in self) else ()
|
|
437
441
|
def row_from_variant(variant):
|
|
438
|
-
|
|
442
|
+
row = OrderedDict([
|
|
439
443
|
("chr", variant.contig),
|
|
440
444
|
("start", variant.original_start),
|
|
441
445
|
("ref", variant.original_ref),
|
|
@@ -443,11 +447,13 @@ class VariantCollection(Collection):
|
|
|
443
447
|
("gene_name", ";".join(variant.gene_names)),
|
|
444
448
|
("gene_id", ";".join(variant.gene_ids))
|
|
445
449
|
])
|
|
450
|
+
row.update((name, getattr(variant, name, None)) for name in structural_columns)
|
|
451
|
+
return row
|
|
446
452
|
rows = [row_from_variant(v) for v in self]
|
|
447
453
|
# Always return a DataFrame with the expected columns, even
|
|
448
454
|
# when empty, so downstream code (CSV round-trip, joins) doesn't
|
|
449
455
|
# have to special-case len == 0.
|
|
450
|
-
return pd.DataFrame.from_records(rows, columns=self._DATAFRAME_COLUMNS)
|
|
456
|
+
return pd.DataFrame.from_records(rows, columns=self._DATAFRAME_COLUMNS + structural_columns)
|
|
451
457
|
|
|
452
458
|
def to_csv(self, path, include_header=True):
|
|
453
459
|
"""Write this collection to CSV.
|
|
@@ -541,6 +547,7 @@ class VariantCollection(Collection):
|
|
|
541
547
|
# dtype is a no-op for whichever column is absent.
|
|
542
548
|
df = pd.read_csv(
|
|
543
549
|
path, comment="#", dtype={"chr": str, "contig": str})
|
|
550
|
+
reject_structural_csv(df)
|
|
544
551
|
contig_col = resolve_contig_column(df.columns)
|
|
545
552
|
if contig_col is None:
|
|
546
553
|
raise ValueError(
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "9.2.2"
|
|
@@ -68,7 +68,9 @@ tests/test_stop_codon_classification_bugs.py
|
|
|
68
68
|
tests/test_string_helpers.py
|
|
69
69
|
tests/test_structural_variant_annotator.py
|
|
70
70
|
tests/test_structural_variants.py
|
|
71
|
+
tests/test_sv_tabular_metadata.py
|
|
71
72
|
tests/test_symbolic_alleles.py
|
|
73
|
+
tests/test_symbolic_sv_padding.py
|
|
72
74
|
tests/test_timings.py
|
|
73
75
|
tests/test_transcript_layout.py
|
|
74
76
|
tests/test_transcript_model.py
|
varcode-9.2.0/varcode/version.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "9.2.0"
|
|
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
|