varcode 9.2.1__tar.gz → 9.2.3__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.
Files changed (159) hide show
  1. {varcode-9.2.1/varcode.egg-info → varcode-9.2.3}/PKG-INFO +1 -1
  2. varcode-9.2.3/tests/test_nonlocal_sv_transcripts.py +83 -0
  3. {varcode-9.2.1 → varcode-9.2.3}/tests/test_osteosarc_fusions.py +9 -1
  4. varcode-9.2.3/tests/test_sv_tabular_metadata.py +69 -0
  5. {varcode-9.2.1 → varcode-9.2.3}/tests/test_symbolic_sv_padding.py +1 -1
  6. {varcode-9.2.1 → varcode-9.2.3}/varcode/csv_helpers.py +12 -0
  7. {varcode-9.2.1 → varcode-9.2.3}/varcode/effects/effect_collection.py +7 -2
  8. {varcode-9.2.1 → varcode-9.2.3}/varcode/effects/structural.py +9 -3
  9. {varcode-9.2.1 → varcode-9.2.3}/varcode/structural_variant.py +15 -3
  10. {varcode-9.2.1 → varcode-9.2.3}/varcode/variant_collection.py +9 -2
  11. varcode-9.2.3/varcode/version.py +1 -0
  12. {varcode-9.2.1 → varcode-9.2.3/varcode.egg-info}/PKG-INFO +1 -1
  13. {varcode-9.2.1 → varcode-9.2.3}/varcode.egg-info/SOURCES.txt +2 -0
  14. varcode-9.2.1/varcode/version.py +0 -1
  15. {varcode-9.2.1 → varcode-9.2.3}/LICENSE +0 -0
  16. {varcode-9.2.1 → varcode-9.2.3}/MANIFEST.in +0 -0
  17. {varcode-9.2.1 → varcode-9.2.3}/README.md +0 -0
  18. {varcode-9.2.1 → varcode-9.2.3}/pyproject.toml +0 -0
  19. {varcode-9.2.1 → varcode-9.2.3}/requirements.txt +0 -0
  20. {varcode-9.2.1 → varcode-9.2.3}/setup.cfg +0 -0
  21. {varcode-9.2.1 → varcode-9.2.3}/tests/test_annotator_divergence_scenarios.py +0 -0
  22. {varcode-9.2.1 → varcode-9.2.3}/tests/test_annotator_parity_adversarial.py +0 -0
  23. {varcode-9.2.1 → varcode-9.2.3}/tests/test_annotators.py +0 -0
  24. {varcode-9.2.1 → varcode-9.2.3}/tests/test_cli_effects.py +0 -0
  25. {varcode-9.2.1 → varcode-9.2.3}/tests/test_cli_genes.py +0 -0
  26. {varcode-9.2.1 → varcode-9.2.3}/tests/test_cli_without_pkg_resources.py +0 -0
  27. {varcode-9.2.1 → varcode-9.2.3}/tests/test_collection_filtering.py +0 -0
  28. {varcode-9.2.1 → varcode-9.2.3}/tests/test_collection_variants_attr_consistency.py +0 -0
  29. {varcode-9.2.1 → varcode-9.2.3}/tests/test_common.py +0 -0
  30. {varcode-9.2.1 → varcode-9.2.3}/tests/test_cosmic_mutations.py +0 -0
  31. {varcode-9.2.1 → varcode-9.2.3}/tests/test_cryptic_exons.py +0 -0
  32. {varcode-9.2.1 → varcode-9.2.3}/tests/test_csv_roundtrip.py +0 -0
  33. {varcode-9.2.1 → varcode-9.2.3}/tests/test_dbnsfp_validation.py +0 -0
  34. {varcode-9.2.1 → varcode-9.2.3}/tests/test_default_structural_helpers.py +0 -0
  35. {varcode-9.2.1 → varcode-9.2.3}/tests/test_effect_annotation_errors.py +0 -0
  36. {varcode-9.2.1 → varcode-9.2.3}/tests/test_effect_candidates.py +0 -0
  37. {varcode-9.2.1 → varcode-9.2.3}/tests/test_effect_classes.py +0 -0
  38. {varcode-9.2.1 → varcode-9.2.3}/tests/test_effect_collection.py +0 -0
  39. {varcode-9.2.1 → varcode-9.2.3}/tests/test_effect_collection_serialization.py +0 -0
  40. {varcode-9.2.1 → varcode-9.2.3}/tests/test_effect_collection_sort_order.py +0 -0
  41. {varcode-9.2.1 → varcode-9.2.3}/tests/test_effect_hypotheses.py +0 -0
  42. {varcode-9.2.1 → varcode-9.2.3}/tests/test_effects_from_mutagenix_variants.py +0 -0
  43. {varcode-9.2.1 → varcode-9.2.3}/tests/test_exacto_fusions.py +0 -0
  44. {varcode-9.2.1 → varcode-9.2.3}/tests/test_exonic_splice_site.py +0 -0
  45. {varcode-9.2.1 → varcode-9.2.3}/tests/test_fast_path.py +0 -0
  46. {varcode-9.2.1 → varcode-9.2.3}/tests/test_frameshift_cterminus_regression.py +0 -0
  47. {varcode-9.2.1 → varcode-9.2.3}/tests/test_frameshift_helpers.py +0 -0
  48. {varcode-9.2.1 → varcode-9.2.3}/tests/test_genome_contig_validation.py +0 -0
  49. {varcode-9.2.1 → varcode-9.2.3}/tests/test_genome_sequence.py +0 -0
  50. {varcode-9.2.1 → varcode-9.2.3}/tests/test_genomic_layout.py +0 -0
  51. {varcode-9.2.1 → varcode-9.2.3}/tests/test_genotype.py +0 -0
  52. {varcode-9.2.1 → varcode-9.2.3}/tests/test_genotype_from_vcf.py +0 -0
  53. {varcode-9.2.1 → varcode-9.2.3}/tests/test_germline_context.py +0 -0
  54. {varcode-9.2.1 → varcode-9.2.3}/tests/test_germline_effects.py +0 -0
  55. {varcode-9.2.1 → varcode-9.2.3}/tests/test_haplotype_effect.py +0 -0
  56. {varcode-9.2.1 → varcode-9.2.3}/tests/test_maf.py +0 -0
  57. {varcode-9.2.1 → varcode-9.2.3}/tests/test_mitochondrial.py +0 -0
  58. {varcode-9.2.1 → varcode-9.2.3}/tests/test_mm10_klf6_frameshift.py +0 -0
  59. {varcode-9.2.1 → varcode-9.2.3}/tests/test_mouse.py +0 -0
  60. {varcode-9.2.1 → varcode-9.2.3}/tests/test_mutant_transcript.py +0 -0
  61. {varcode-9.2.1 → varcode-9.2.3}/tests/test_mutate.py +0 -0
  62. {varcode-9.2.1 → varcode-9.2.3}/tests/test_no_duplicate_variants.py +0 -0
  63. {varcode-9.2.1 → varcode-9.2.3}/tests/test_partial_annotators.py +0 -0
  64. {varcode-9.2.1 → varcode-9.2.3}/tests/test_premature_stop_short_description.py +0 -0
  65. {varcode-9.2.1 → varcode-9.2.3}/tests/test_problematic_variants.py +0 -0
  66. {varcode-9.2.1 → varcode-9.2.3}/tests/test_protein_diff_parity.py +0 -0
  67. {varcode-9.2.1 → varcode-9.2.3}/tests/test_read_phase_resolver.py +0 -0
  68. {varcode-9.2.1 → varcode-9.2.3}/tests/test_real_world_callers.py +0 -0
  69. {varcode-9.2.1 → varcode-9.2.3}/tests/test_reference.py +0 -0
  70. {varcode-9.2.1 → varcode-9.2.3}/tests/test_reference_mismatch_error.py +0 -0
  71. {varcode-9.2.1 → varcode-9.2.3}/tests/test_reference_segments.py +0 -0
  72. {varcode-9.2.1 → varcode-9.2.3}/tests/test_rna_evidence.py +0 -0
  73. {varcode-9.2.1 → varcode-9.2.3}/tests/test_rna_haplotype_sequences.py +0 -0
  74. {varcode-9.2.1 → varcode-9.2.3}/tests/test_rna_read_phasing_source.py +0 -0
  75. {varcode-9.2.1 → varcode-9.2.3}/tests/test_selenocysteine.py +0 -0
  76. {varcode-9.2.1 → varcode-9.2.3}/tests/test_silent_aa_pos.py +0 -0
  77. {varcode-9.2.1 → varcode-9.2.3}/tests/test_silent_hgvs_description.py +0 -0
  78. {varcode-9.2.1 → varcode-9.2.3}/tests/test_splice_graph.py +0 -0
  79. {varcode-9.2.1 → varcode-9.2.3}/tests/test_splice_outcomes.py +0 -0
  80. {varcode-9.2.1 → varcode-9.2.3}/tests/test_splice_site_effects.py +0 -0
  81. {varcode-9.2.1 → varcode-9.2.3}/tests/test_stop_codon_classification_bugs.py +0 -0
  82. {varcode-9.2.1 → varcode-9.2.3}/tests/test_string_helpers.py +0 -0
  83. {varcode-9.2.1 → varcode-9.2.3}/tests/test_structural_variant_annotator.py +0 -0
  84. {varcode-9.2.1 → varcode-9.2.3}/tests/test_structural_variants.py +0 -0
  85. {varcode-9.2.1 → varcode-9.2.3}/tests/test_symbolic_alleles.py +0 -0
  86. {varcode-9.2.1 → varcode-9.2.3}/tests/test_timings.py +0 -0
  87. {varcode-9.2.1 → varcode-9.2.3}/tests/test_transcript_layout.py +0 -0
  88. {varcode-9.2.1 → varcode-9.2.3}/tests/test_transcript_model.py +0 -0
  89. {varcode-9.2.1 → varcode-9.2.3}/tests/test_transforms_left_align.py +0 -0
  90. {varcode-9.2.1 → varcode-9.2.3}/tests/test_transforms_pair_breakends.py +0 -0
  91. {varcode-9.2.1 → varcode-9.2.3}/tests/test_variant.py +0 -0
  92. {varcode-9.2.1 → varcode-9.2.3}/tests/test_variant_collection.py +0 -0
  93. {varcode-9.2.1 → varcode-9.2.3}/tests/test_vcf.py +0 -0
  94. {varcode-9.2.1 → varcode-9.2.3}/tests/test_vcf_output.py +0 -0
  95. {varcode-9.2.1 → varcode-9.2.3}/tests/test_vcf_parsing.py +0 -0
  96. {varcode-9.2.1 → varcode-9.2.3}/tests/test_vcf_phase_resolver.py +0 -0
  97. {varcode-9.2.1 → varcode-9.2.3}/tests/test_vcf_spec_examples.py +0 -0
  98. {varcode-9.2.1 → varcode-9.2.3}/varcode/__init__.py +0 -0
  99. {varcode-9.2.1 → varcode-9.2.3}/varcode/annotators/__init__.py +0 -0
  100. {varcode-9.2.1 → varcode-9.2.3}/varcode/annotators/fast.py +0 -0
  101. {varcode-9.2.1 → varcode-9.2.3}/varcode/annotators/protein_diff.py +0 -0
  102. {varcode-9.2.1 → varcode-9.2.3}/varcode/annotators/registry.py +0 -0
  103. {varcode-9.2.1 → varcode-9.2.3}/varcode/cli/__init__.py +0 -0
  104. {varcode-9.2.1 → varcode-9.2.3}/varcode/cli/effects_script.py +0 -0
  105. {varcode-9.2.1 → varcode-9.2.3}/varcode/cli/genes_script.py +0 -0
  106. {varcode-9.2.1 → varcode-9.2.3}/varcode/cli/logging.conf +0 -0
  107. {varcode-9.2.1 → varcode-9.2.3}/varcode/cli/variant_args.py +0 -0
  108. {varcode-9.2.1 → varcode-9.2.3}/varcode/cli/version_info.py +0 -0
  109. {varcode-9.2.1 → varcode-9.2.3}/varcode/common.py +0 -0
  110. {varcode-9.2.1 → varcode-9.2.3}/varcode/cryptic_exons.py +0 -0
  111. {varcode-9.2.1 → varcode-9.2.3}/varcode/effect_candidates.py +0 -0
  112. {varcode-9.2.1 → varcode-9.2.3}/varcode/effect_hypotheses.py +0 -0
  113. {varcode-9.2.1 → varcode-9.2.3}/varcode/effects/__init__.py +0 -0
  114. {varcode-9.2.1 → varcode-9.2.3}/varcode/effects/classify.py +0 -0
  115. {varcode-9.2.1 → varcode-9.2.3}/varcode/effects/codon_tables.py +0 -0
  116. {varcode-9.2.1 → varcode-9.2.3}/varcode/effects/common.py +0 -0
  117. {varcode-9.2.1 → varcode-9.2.3}/varcode/effects/effect_classes.py +0 -0
  118. {varcode-9.2.1 → varcode-9.2.3}/varcode/effects/effect_helpers.py +0 -0
  119. {varcode-9.2.1 → varcode-9.2.3}/varcode/effects/effect_ordering.py +0 -0
  120. {varcode-9.2.1 → varcode-9.2.3}/varcode/effects/effect_prediction.py +0 -0
  121. {varcode-9.2.1 → varcode-9.2.3}/varcode/effects/effect_prediction_coding.py +0 -0
  122. {varcode-9.2.1 → varcode-9.2.3}/varcode/effects/effect_prediction_coding_frameshift.py +0 -0
  123. {varcode-9.2.1 → varcode-9.2.3}/varcode/effects/effect_prediction_coding_in_frame.py +0 -0
  124. {varcode-9.2.1 → varcode-9.2.3}/varcode/effects/fast_path.py +0 -0
  125. {varcode-9.2.1 → varcode-9.2.3}/varcode/effects/mutate.py +0 -0
  126. {varcode-9.2.1 → varcode-9.2.3}/varcode/effects/transcript_helpers.py +0 -0
  127. {varcode-9.2.1 → varcode-9.2.3}/varcode/effects/translate.py +0 -0
  128. {varcode-9.2.1 → varcode-9.2.3}/varcode/errors.py +0 -0
  129. {varcode-9.2.1 → varcode-9.2.3}/varcode/exacto.py +0 -0
  130. {varcode-9.2.1 → varcode-9.2.3}/varcode/genome.py +0 -0
  131. {varcode-9.2.1 → varcode-9.2.3}/varcode/genome_sequence.py +0 -0
  132. {varcode-9.2.1 → varcode-9.2.3}/varcode/genomic_layout.py +0 -0
  133. {varcode-9.2.1 → varcode-9.2.3}/varcode/genotype.py +0 -0
  134. {varcode-9.2.1 → varcode-9.2.3}/varcode/germline.py +0 -0
  135. {varcode-9.2.1 → varcode-9.2.3}/varcode/maf.py +0 -0
  136. {varcode-9.2.1 → varcode-9.2.3}/varcode/mutant_transcript.py +0 -0
  137. {varcode-9.2.1 → varcode-9.2.3}/varcode/nucleotides.py +0 -0
  138. {varcode-9.2.1 → varcode-9.2.3}/varcode/phasing.py +0 -0
  139. {varcode-9.2.1 → varcode-9.2.3}/varcode/realized_effects.py +0 -0
  140. {varcode-9.2.1 → varcode-9.2.3}/varcode/reference.py +0 -0
  141. {varcode-9.2.1 → varcode-9.2.3}/varcode/rna_evidence.py +0 -0
  142. {varcode-9.2.1 → varcode-9.2.3}/varcode/rna_read_phasing.py +0 -0
  143. {varcode-9.2.1 → varcode-9.2.3}/varcode/splice_graph.py +0 -0
  144. {varcode-9.2.1 → varcode-9.2.3}/varcode/splice_outcomes.py +0 -0
  145. {varcode-9.2.1 → varcode-9.2.3}/varcode/string_helpers.py +0 -0
  146. {varcode-9.2.1 → varcode-9.2.3}/varcode/sv_allele_parser.py +0 -0
  147. {varcode-9.2.1 → varcode-9.2.3}/varcode/transcript_layout.py +0 -0
  148. {varcode-9.2.1 → varcode-9.2.3}/varcode/transcript_model.py +0 -0
  149. {varcode-9.2.1 → varcode-9.2.3}/varcode/transforms/__init__.py +0 -0
  150. {varcode-9.2.1 → varcode-9.2.3}/varcode/ucsc_reference_names.py +0 -0
  151. {varcode-9.2.1 → varcode-9.2.3}/varcode/util.py +0 -0
  152. {varcode-9.2.1 → varcode-9.2.3}/varcode/variant.py +0 -0
  153. {varcode-9.2.1 → varcode-9.2.3}/varcode/vcf.py +0 -0
  154. {varcode-9.2.1 → varcode-9.2.3}/varcode/vcf_output.py +0 -0
  155. {varcode-9.2.1 → varcode-9.2.3}/varcode/vcf_parsing.py +0 -0
  156. {varcode-9.2.1 → varcode-9.2.3}/varcode.egg-info/dependency_links.txt +0 -0
  157. {varcode-9.2.1 → varcode-9.2.3}/varcode.egg-info/entry_points.txt +0 -0
  158. {varcode-9.2.1 → varcode-9.2.3}/varcode.egg-info/requires.txt +0 -0
  159. {varcode-9.2.1 → varcode-9.2.3}/varcode.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: varcode
3
- Version: 9.2.1
3
+ Version: 9.2.3
4
4
  Summary: Variant annotation in Python
5
5
  Author-email: Alex Rubinsteyn <alex.rubinsteyn@unc.edu>
6
6
  Project-URL: Homepage, https://github.com/openvax/varcode
@@ -0,0 +1,83 @@
1
+ """A partial overlap cannot establish a local DUP/INV transcript (#405)."""
2
+
3
+ import pytest
4
+ from pyensembl import cached_release
5
+
6
+ from varcode import StructuralVariant, parse_symbolic_alt
7
+ from varcode.effects import Inversion, LargeDuplication
8
+ from varcode.effects import structural
9
+
10
+
11
+ @pytest.fixture(params=["ENST00000003084", "ENST00000357654"])
12
+ def transcript(request):
13
+ return cached_release(81).transcript_by_id(request.param)
14
+
15
+
16
+ @pytest.fixture(autouse=True)
17
+ def no_optional_candidates(monkeypatch):
18
+ # Isolate the span fallback; fusion preservation also has real esvee tests.
19
+ monkeypatch.setattr(structural, "_fusion_partner", lambda *args: None)
20
+ monkeypatch.setattr(structural, "_enumerate_and_attach_cryptics", lambda *args: None)
21
+ monkeypatch.setattr(structural, "_enumerate_and_attach_splice_outcomes", lambda *args: None)
22
+
23
+
24
+ def make_sv(transcript, kind, outside, symbolic=False, assembly=None):
25
+ start, end = transcript.start + 50, transcript.end - 50
26
+ if outside in ("left", "both"):
27
+ start = transcript.start - 100
28
+ if outside in ("right", "both"):
29
+ end = transcript.end + 100
30
+ if symbolic:
31
+ return parse_symbolic_alt(
32
+ transcript.contig, start, "N", "<%s>" % kind,
33
+ info={"END": end}, genome=transcript.genome)
34
+ return StructuralVariant(
35
+ transcript.contig, start, kind, end=end,
36
+ genome=transcript.genome, alt_assembly=assembly)
37
+
38
+
39
+ @pytest.mark.parametrize("kind,cls", [("DUP", LargeDuplication), ("INV", Inversion)])
40
+ @pytest.mark.parametrize("outside", ["left", "right", "both"])
41
+ @pytest.mark.parametrize("symbolic", [False, True])
42
+ def test_nonlocal_event_keeps_type_without_inventing_transcript(
43
+ transcript, kind, cls, outside, symbolic):
44
+ sv = make_sv(transcript, kind, outside, symbolic)
45
+ effect = sv.effect_on_transcript(transcript)
46
+ assert isinstance(effect, cls)
47
+ assert effect.mutant_transcript is None
48
+ assert all(c.effect.mutant_transcript is None for c in effect.candidates)
49
+
50
+
51
+ @pytest.mark.parametrize("kind", ["DUP", "INV"])
52
+ @pytest.mark.parametrize("outside", ["left", "right", "both"])
53
+ def test_nonlocal_event_preserves_supplied_assembly(transcript, kind, outside):
54
+ assembly = "ATG" + "GCT" * 10 + "TAA"
55
+ sv = make_sv(transcript, kind, outside, assembly=assembly)
56
+ effect = sv.effect_on_transcript(transcript)
57
+ assert effect.mutant_transcript.cdna_sequence == assembly
58
+
59
+
60
+ @pytest.mark.parametrize("kind", ["DUP", "INV"])
61
+ def test_wholly_internal_event_retains_local_model(transcript, kind):
62
+ sv = make_sv(transcript, kind, outside=None)
63
+ effect = sv.effect_on_transcript(transcript)
64
+ assert effect.mutant_transcript is not None
65
+ if kind == "DUP":
66
+ assert len(effect.mutant_transcript.cdna_sequence) > len(transcript.sequence)
67
+ else:
68
+ assert any(s.strand == "-" for s in effect.mutant_transcript.reference_segments)
69
+
70
+
71
+ @pytest.mark.parametrize("transcript_id", ["ENST00000439541", "ENST00000550411"])
72
+ @pytest.mark.parametrize("kind", ["DUP", "INV"])
73
+ def test_gene_containment_does_not_establish_short_isoform_structure(transcript_id, kind):
74
+ # CBX5's long isoform spans both ends, but these short isoforms do not.
75
+ genome = cached_release(95)
76
+ tx = genome.transcript_by_id(transcript_id)
77
+ sv = StructuralVariant("12", 54_259_010, kind, end=54_262_307, genome=genome)
78
+ ends = [end for junction in sv.junctions for end in junction]
79
+ assert all(structural._contains(tx.gene, end) for end in ends)
80
+ assert any(not structural._contains(tx, end) for end in ends)
81
+ effect = sv.effect_on_transcript(tx)
82
+ assert isinstance(effect, (LargeDuplication, Inversion))
83
+ assert effect.mutant_transcript is None
@@ -48,7 +48,7 @@ from varcode import StructuralVariant, FastEffectAnnotator, load_vcf
48
48
  from varcode.effects.structural import (
49
49
  _build_fusion_mutant_transcript,
50
50
  )
51
- from varcode.effects import GeneFusion, Intronic
51
+ from varcode.effects import GeneFusion, Intronic, Inversion, LargeDuplication
52
52
  from varcode.transforms import pair_breakends
53
53
 
54
54
  from .data import data_path
@@ -267,6 +267,10 @@ def test_esvee_vcf_otx1_kif3c_inversion_is_fusion_on_both_partners():
267
267
  assert fusion.three_prime_transcript.id == kif3c.id
268
268
  assert fusion.mutant_transcript.mutant_protein_sequence == (
269
269
  "MMSYLKQPPYGMNGLGLAGPAMDLLHPSVGYPETS")
270
+ if sv.sv_type == "INV":
271
+ (span,) = [c.effect for c in fusion.candidates
272
+ if isinstance(c.effect, Inversion)]
273
+ assert span.mutant_transcript is None
270
274
 
271
275
 
272
276
  def test_esvee_vcf_cpeb2_fam193a_duplication_is_fusion():
@@ -290,6 +294,10 @@ def test_esvee_vcf_cpeb2_fam193a_duplication_is_fusion():
290
294
  assert isinstance(on_fam193a, GeneFusion)
291
295
  assert on_fam193a.three_prime_transcript.id == fam193a.id
292
296
  assert on_fam193a.five_prime_transcript.gene_name == "CPEB2"
297
+ for fusion in (on_cpeb2, on_fam193a):
298
+ (span,) = [c.effect for c in fusion.candidates
299
+ if isinstance(c.effect, LargeDuplication)]
300
+ assert span.mutant_transcript is None
293
301
 
294
302
 
295
303
  def test_esvee_vcf_sema6a_parm1_fusion_from_either_record():
@@ -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
@@ -37,7 +37,7 @@ def test_no_nonempty_span_cannot_delete_or_copy_the_anchor(kind, info):
37
37
  def test_exact_mutant_cdna_on_both_strands(kind, builder, reverse):
38
38
  sequence = "ACGTTGCAAGCTTAGGCTAC"
39
39
  tx = SimpleNamespace(exons=[SimpleNamespace(start=100, end=119)],
40
- start=100, end=119, sequence=sequence,
40
+ contig="1", start=100, end=119, sequence=sequence,
41
41
  on_backward_strand=reverse)
42
42
  sv = parse_symbolic_alt("1", 109, "G", "<%s>" % kind, info={"END": 114})
43
43
  a, b = (5, 10) if reverse else (10, 15)
@@ -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(
@@ -126,9 +126,15 @@ def _cdna_ranges_kept_after_deletion(variant, transcript):
126
126
 
127
127
 
128
128
  def _cdna_ranges_within_sv(variant, transcript):
129
- """cDNA ranges that fall INSIDE the SV span (used for DUP to
130
- derive the duplicated body and for INV to derive the flipped
131
- middle)."""
129
+ """Body ranges for a local DUP/INV model, or [] if not local.
130
+
131
+ Clipping a cross-boundary event to this transcript invents a local
132
+ rearrangement (#405). Every junction end must be in this transcript,
133
+ not merely in its gene. Fusion and supplied-assembly paths are separate.
134
+ """
135
+ if any(not _contains(transcript, end)
136
+ for junction in variant.junctions for end in junction):
137
+ return []
132
138
  sv_start, sv_end = _affected_span(variant)
133
139
  inside = []
134
140
  reverse = transcript.on_backward_strand
@@ -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. The original symbolic ALT is preserved in
292
- # ``_sv_alt``; :attr:`alt` returns it via a property override.
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" if ref == "N" else "A", # placeholder, overridden below
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
@@ -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
- return OrderedDict([
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.3"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: varcode
3
- Version: 9.2.1
3
+ Version: 9.2.3
4
4
  Summary: Variant annotation in Python
5
5
  Author-email: Alex Rubinsteyn <alex.rubinsteyn@unc.edu>
6
6
  Project-URL: Homepage, https://github.com/openvax/varcode
@@ -45,6 +45,7 @@ tests/test_mouse.py
45
45
  tests/test_mutant_transcript.py
46
46
  tests/test_mutate.py
47
47
  tests/test_no_duplicate_variants.py
48
+ tests/test_nonlocal_sv_transcripts.py
48
49
  tests/test_osteosarc_fusions.py
49
50
  tests/test_partial_annotators.py
50
51
  tests/test_premature_stop_short_description.py
@@ -68,6 +69,7 @@ tests/test_stop_codon_classification_bugs.py
68
69
  tests/test_string_helpers.py
69
70
  tests/test_structural_variant_annotator.py
70
71
  tests/test_structural_variants.py
72
+ tests/test_sv_tabular_metadata.py
71
73
  tests/test_symbolic_alleles.py
72
74
  tests/test_symbolic_sv_padding.py
73
75
  tests/test_timings.py
@@ -1 +0,0 @@
1
- __version__ = "9.2.1"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes