varcode 9.2.2__tar.gz → 9.2.4__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 (160) hide show
  1. {varcode-9.2.2/varcode.egg-info → varcode-9.2.4}/PKG-INFO +1 -1
  2. varcode-9.2.4/tests/test_bnd_retained_segments.py +111 -0
  3. varcode-9.2.4/tests/test_nonlocal_sv_transcripts.py +83 -0
  4. {varcode-9.2.2 → varcode-9.2.4}/tests/test_osteosarc_fusions.py +9 -1
  5. {varcode-9.2.2 → varcode-9.2.4}/tests/test_symbolic_sv_padding.py +1 -1
  6. {varcode-9.2.2 → varcode-9.2.4}/varcode/effects/structural.py +24 -12
  7. {varcode-9.2.2 → varcode-9.2.4}/varcode/mutant_transcript.py +4 -1
  8. {varcode-9.2.2 → varcode-9.2.4}/varcode/sv_allele_parser.py +11 -0
  9. varcode-9.2.4/varcode/version.py +1 -0
  10. {varcode-9.2.2 → varcode-9.2.4/varcode.egg-info}/PKG-INFO +1 -1
  11. {varcode-9.2.2 → varcode-9.2.4}/varcode.egg-info/SOURCES.txt +2 -0
  12. varcode-9.2.2/varcode/version.py +0 -1
  13. {varcode-9.2.2 → varcode-9.2.4}/LICENSE +0 -0
  14. {varcode-9.2.2 → varcode-9.2.4}/MANIFEST.in +0 -0
  15. {varcode-9.2.2 → varcode-9.2.4}/README.md +0 -0
  16. {varcode-9.2.2 → varcode-9.2.4}/pyproject.toml +0 -0
  17. {varcode-9.2.2 → varcode-9.2.4}/requirements.txt +0 -0
  18. {varcode-9.2.2 → varcode-9.2.4}/setup.cfg +0 -0
  19. {varcode-9.2.2 → varcode-9.2.4}/tests/test_annotator_divergence_scenarios.py +0 -0
  20. {varcode-9.2.2 → varcode-9.2.4}/tests/test_annotator_parity_adversarial.py +0 -0
  21. {varcode-9.2.2 → varcode-9.2.4}/tests/test_annotators.py +0 -0
  22. {varcode-9.2.2 → varcode-9.2.4}/tests/test_cli_effects.py +0 -0
  23. {varcode-9.2.2 → varcode-9.2.4}/tests/test_cli_genes.py +0 -0
  24. {varcode-9.2.2 → varcode-9.2.4}/tests/test_cli_without_pkg_resources.py +0 -0
  25. {varcode-9.2.2 → varcode-9.2.4}/tests/test_collection_filtering.py +0 -0
  26. {varcode-9.2.2 → varcode-9.2.4}/tests/test_collection_variants_attr_consistency.py +0 -0
  27. {varcode-9.2.2 → varcode-9.2.4}/tests/test_common.py +0 -0
  28. {varcode-9.2.2 → varcode-9.2.4}/tests/test_cosmic_mutations.py +0 -0
  29. {varcode-9.2.2 → varcode-9.2.4}/tests/test_cryptic_exons.py +0 -0
  30. {varcode-9.2.2 → varcode-9.2.4}/tests/test_csv_roundtrip.py +0 -0
  31. {varcode-9.2.2 → varcode-9.2.4}/tests/test_dbnsfp_validation.py +0 -0
  32. {varcode-9.2.2 → varcode-9.2.4}/tests/test_default_structural_helpers.py +0 -0
  33. {varcode-9.2.2 → varcode-9.2.4}/tests/test_effect_annotation_errors.py +0 -0
  34. {varcode-9.2.2 → varcode-9.2.4}/tests/test_effect_candidates.py +0 -0
  35. {varcode-9.2.2 → varcode-9.2.4}/tests/test_effect_classes.py +0 -0
  36. {varcode-9.2.2 → varcode-9.2.4}/tests/test_effect_collection.py +0 -0
  37. {varcode-9.2.2 → varcode-9.2.4}/tests/test_effect_collection_serialization.py +0 -0
  38. {varcode-9.2.2 → varcode-9.2.4}/tests/test_effect_collection_sort_order.py +0 -0
  39. {varcode-9.2.2 → varcode-9.2.4}/tests/test_effect_hypotheses.py +0 -0
  40. {varcode-9.2.2 → varcode-9.2.4}/tests/test_effects_from_mutagenix_variants.py +0 -0
  41. {varcode-9.2.2 → varcode-9.2.4}/tests/test_exacto_fusions.py +0 -0
  42. {varcode-9.2.2 → varcode-9.2.4}/tests/test_exonic_splice_site.py +0 -0
  43. {varcode-9.2.2 → varcode-9.2.4}/tests/test_fast_path.py +0 -0
  44. {varcode-9.2.2 → varcode-9.2.4}/tests/test_frameshift_cterminus_regression.py +0 -0
  45. {varcode-9.2.2 → varcode-9.2.4}/tests/test_frameshift_helpers.py +0 -0
  46. {varcode-9.2.2 → varcode-9.2.4}/tests/test_genome_contig_validation.py +0 -0
  47. {varcode-9.2.2 → varcode-9.2.4}/tests/test_genome_sequence.py +0 -0
  48. {varcode-9.2.2 → varcode-9.2.4}/tests/test_genomic_layout.py +0 -0
  49. {varcode-9.2.2 → varcode-9.2.4}/tests/test_genotype.py +0 -0
  50. {varcode-9.2.2 → varcode-9.2.4}/tests/test_genotype_from_vcf.py +0 -0
  51. {varcode-9.2.2 → varcode-9.2.4}/tests/test_germline_context.py +0 -0
  52. {varcode-9.2.2 → varcode-9.2.4}/tests/test_germline_effects.py +0 -0
  53. {varcode-9.2.2 → varcode-9.2.4}/tests/test_haplotype_effect.py +0 -0
  54. {varcode-9.2.2 → varcode-9.2.4}/tests/test_maf.py +0 -0
  55. {varcode-9.2.2 → varcode-9.2.4}/tests/test_mitochondrial.py +0 -0
  56. {varcode-9.2.2 → varcode-9.2.4}/tests/test_mm10_klf6_frameshift.py +0 -0
  57. {varcode-9.2.2 → varcode-9.2.4}/tests/test_mouse.py +0 -0
  58. {varcode-9.2.2 → varcode-9.2.4}/tests/test_mutant_transcript.py +0 -0
  59. {varcode-9.2.2 → varcode-9.2.4}/tests/test_mutate.py +0 -0
  60. {varcode-9.2.2 → varcode-9.2.4}/tests/test_no_duplicate_variants.py +0 -0
  61. {varcode-9.2.2 → varcode-9.2.4}/tests/test_partial_annotators.py +0 -0
  62. {varcode-9.2.2 → varcode-9.2.4}/tests/test_premature_stop_short_description.py +0 -0
  63. {varcode-9.2.2 → varcode-9.2.4}/tests/test_problematic_variants.py +0 -0
  64. {varcode-9.2.2 → varcode-9.2.4}/tests/test_protein_diff_parity.py +0 -0
  65. {varcode-9.2.2 → varcode-9.2.4}/tests/test_read_phase_resolver.py +0 -0
  66. {varcode-9.2.2 → varcode-9.2.4}/tests/test_real_world_callers.py +0 -0
  67. {varcode-9.2.2 → varcode-9.2.4}/tests/test_reference.py +0 -0
  68. {varcode-9.2.2 → varcode-9.2.4}/tests/test_reference_mismatch_error.py +0 -0
  69. {varcode-9.2.2 → varcode-9.2.4}/tests/test_reference_segments.py +0 -0
  70. {varcode-9.2.2 → varcode-9.2.4}/tests/test_rna_evidence.py +0 -0
  71. {varcode-9.2.2 → varcode-9.2.4}/tests/test_rna_haplotype_sequences.py +0 -0
  72. {varcode-9.2.2 → varcode-9.2.4}/tests/test_rna_read_phasing_source.py +0 -0
  73. {varcode-9.2.2 → varcode-9.2.4}/tests/test_selenocysteine.py +0 -0
  74. {varcode-9.2.2 → varcode-9.2.4}/tests/test_silent_aa_pos.py +0 -0
  75. {varcode-9.2.2 → varcode-9.2.4}/tests/test_silent_hgvs_description.py +0 -0
  76. {varcode-9.2.2 → varcode-9.2.4}/tests/test_splice_graph.py +0 -0
  77. {varcode-9.2.2 → varcode-9.2.4}/tests/test_splice_outcomes.py +0 -0
  78. {varcode-9.2.2 → varcode-9.2.4}/tests/test_splice_site_effects.py +0 -0
  79. {varcode-9.2.2 → varcode-9.2.4}/tests/test_stop_codon_classification_bugs.py +0 -0
  80. {varcode-9.2.2 → varcode-9.2.4}/tests/test_string_helpers.py +0 -0
  81. {varcode-9.2.2 → varcode-9.2.4}/tests/test_structural_variant_annotator.py +0 -0
  82. {varcode-9.2.2 → varcode-9.2.4}/tests/test_structural_variants.py +0 -0
  83. {varcode-9.2.2 → varcode-9.2.4}/tests/test_sv_tabular_metadata.py +0 -0
  84. {varcode-9.2.2 → varcode-9.2.4}/tests/test_symbolic_alleles.py +0 -0
  85. {varcode-9.2.2 → varcode-9.2.4}/tests/test_timings.py +0 -0
  86. {varcode-9.2.2 → varcode-9.2.4}/tests/test_transcript_layout.py +0 -0
  87. {varcode-9.2.2 → varcode-9.2.4}/tests/test_transcript_model.py +0 -0
  88. {varcode-9.2.2 → varcode-9.2.4}/tests/test_transforms_left_align.py +0 -0
  89. {varcode-9.2.2 → varcode-9.2.4}/tests/test_transforms_pair_breakends.py +0 -0
  90. {varcode-9.2.2 → varcode-9.2.4}/tests/test_variant.py +0 -0
  91. {varcode-9.2.2 → varcode-9.2.4}/tests/test_variant_collection.py +0 -0
  92. {varcode-9.2.2 → varcode-9.2.4}/tests/test_vcf.py +0 -0
  93. {varcode-9.2.2 → varcode-9.2.4}/tests/test_vcf_output.py +0 -0
  94. {varcode-9.2.2 → varcode-9.2.4}/tests/test_vcf_parsing.py +0 -0
  95. {varcode-9.2.2 → varcode-9.2.4}/tests/test_vcf_phase_resolver.py +0 -0
  96. {varcode-9.2.2 → varcode-9.2.4}/tests/test_vcf_spec_examples.py +0 -0
  97. {varcode-9.2.2 → varcode-9.2.4}/varcode/__init__.py +0 -0
  98. {varcode-9.2.2 → varcode-9.2.4}/varcode/annotators/__init__.py +0 -0
  99. {varcode-9.2.2 → varcode-9.2.4}/varcode/annotators/fast.py +0 -0
  100. {varcode-9.2.2 → varcode-9.2.4}/varcode/annotators/protein_diff.py +0 -0
  101. {varcode-9.2.2 → varcode-9.2.4}/varcode/annotators/registry.py +0 -0
  102. {varcode-9.2.2 → varcode-9.2.4}/varcode/cli/__init__.py +0 -0
  103. {varcode-9.2.2 → varcode-9.2.4}/varcode/cli/effects_script.py +0 -0
  104. {varcode-9.2.2 → varcode-9.2.4}/varcode/cli/genes_script.py +0 -0
  105. {varcode-9.2.2 → varcode-9.2.4}/varcode/cli/logging.conf +0 -0
  106. {varcode-9.2.2 → varcode-9.2.4}/varcode/cli/variant_args.py +0 -0
  107. {varcode-9.2.2 → varcode-9.2.4}/varcode/cli/version_info.py +0 -0
  108. {varcode-9.2.2 → varcode-9.2.4}/varcode/common.py +0 -0
  109. {varcode-9.2.2 → varcode-9.2.4}/varcode/cryptic_exons.py +0 -0
  110. {varcode-9.2.2 → varcode-9.2.4}/varcode/csv_helpers.py +0 -0
  111. {varcode-9.2.2 → varcode-9.2.4}/varcode/effect_candidates.py +0 -0
  112. {varcode-9.2.2 → varcode-9.2.4}/varcode/effect_hypotheses.py +0 -0
  113. {varcode-9.2.2 → varcode-9.2.4}/varcode/effects/__init__.py +0 -0
  114. {varcode-9.2.2 → varcode-9.2.4}/varcode/effects/classify.py +0 -0
  115. {varcode-9.2.2 → varcode-9.2.4}/varcode/effects/codon_tables.py +0 -0
  116. {varcode-9.2.2 → varcode-9.2.4}/varcode/effects/common.py +0 -0
  117. {varcode-9.2.2 → varcode-9.2.4}/varcode/effects/effect_classes.py +0 -0
  118. {varcode-9.2.2 → varcode-9.2.4}/varcode/effects/effect_collection.py +0 -0
  119. {varcode-9.2.2 → varcode-9.2.4}/varcode/effects/effect_helpers.py +0 -0
  120. {varcode-9.2.2 → varcode-9.2.4}/varcode/effects/effect_ordering.py +0 -0
  121. {varcode-9.2.2 → varcode-9.2.4}/varcode/effects/effect_prediction.py +0 -0
  122. {varcode-9.2.2 → varcode-9.2.4}/varcode/effects/effect_prediction_coding.py +0 -0
  123. {varcode-9.2.2 → varcode-9.2.4}/varcode/effects/effect_prediction_coding_frameshift.py +0 -0
  124. {varcode-9.2.2 → varcode-9.2.4}/varcode/effects/effect_prediction_coding_in_frame.py +0 -0
  125. {varcode-9.2.2 → varcode-9.2.4}/varcode/effects/fast_path.py +0 -0
  126. {varcode-9.2.2 → varcode-9.2.4}/varcode/effects/mutate.py +0 -0
  127. {varcode-9.2.2 → varcode-9.2.4}/varcode/effects/transcript_helpers.py +0 -0
  128. {varcode-9.2.2 → varcode-9.2.4}/varcode/effects/translate.py +0 -0
  129. {varcode-9.2.2 → varcode-9.2.4}/varcode/errors.py +0 -0
  130. {varcode-9.2.2 → varcode-9.2.4}/varcode/exacto.py +0 -0
  131. {varcode-9.2.2 → varcode-9.2.4}/varcode/genome.py +0 -0
  132. {varcode-9.2.2 → varcode-9.2.4}/varcode/genome_sequence.py +0 -0
  133. {varcode-9.2.2 → varcode-9.2.4}/varcode/genomic_layout.py +0 -0
  134. {varcode-9.2.2 → varcode-9.2.4}/varcode/genotype.py +0 -0
  135. {varcode-9.2.2 → varcode-9.2.4}/varcode/germline.py +0 -0
  136. {varcode-9.2.2 → varcode-9.2.4}/varcode/maf.py +0 -0
  137. {varcode-9.2.2 → varcode-9.2.4}/varcode/nucleotides.py +0 -0
  138. {varcode-9.2.2 → varcode-9.2.4}/varcode/phasing.py +0 -0
  139. {varcode-9.2.2 → varcode-9.2.4}/varcode/realized_effects.py +0 -0
  140. {varcode-9.2.2 → varcode-9.2.4}/varcode/reference.py +0 -0
  141. {varcode-9.2.2 → varcode-9.2.4}/varcode/rna_evidence.py +0 -0
  142. {varcode-9.2.2 → varcode-9.2.4}/varcode/rna_read_phasing.py +0 -0
  143. {varcode-9.2.2 → varcode-9.2.4}/varcode/splice_graph.py +0 -0
  144. {varcode-9.2.2 → varcode-9.2.4}/varcode/splice_outcomes.py +0 -0
  145. {varcode-9.2.2 → varcode-9.2.4}/varcode/string_helpers.py +0 -0
  146. {varcode-9.2.2 → varcode-9.2.4}/varcode/structural_variant.py +0 -0
  147. {varcode-9.2.2 → varcode-9.2.4}/varcode/transcript_layout.py +0 -0
  148. {varcode-9.2.2 → varcode-9.2.4}/varcode/transcript_model.py +0 -0
  149. {varcode-9.2.2 → varcode-9.2.4}/varcode/transforms/__init__.py +0 -0
  150. {varcode-9.2.2 → varcode-9.2.4}/varcode/ucsc_reference_names.py +0 -0
  151. {varcode-9.2.2 → varcode-9.2.4}/varcode/util.py +0 -0
  152. {varcode-9.2.2 → varcode-9.2.4}/varcode/variant.py +0 -0
  153. {varcode-9.2.2 → varcode-9.2.4}/varcode/variant_collection.py +0 -0
  154. {varcode-9.2.2 → varcode-9.2.4}/varcode/vcf.py +0 -0
  155. {varcode-9.2.2 → varcode-9.2.4}/varcode/vcf_output.py +0 -0
  156. {varcode-9.2.2 → varcode-9.2.4}/varcode/vcf_parsing.py +0 -0
  157. {varcode-9.2.2 → varcode-9.2.4}/varcode.egg-info/dependency_links.txt +0 -0
  158. {varcode-9.2.2 → varcode-9.2.4}/varcode.egg-info/entry_points.txt +0 -0
  159. {varcode-9.2.2 → varcode-9.2.4}/varcode.egg-info/requires.txt +0 -0
  160. {varcode-9.2.2 → varcode-9.2.4}/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.2
3
+ Version: 9.2.4
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,111 @@
1
+ """A BND fallback describes only its retained reference fragment (#447)."""
2
+
3
+ import pytest
4
+ from pyensembl import cached_release
5
+
6
+ from varcode import StructuralVariant, parse_symbolic_alt
7
+ from varcode.effects import TranslocationToIntergenic
8
+ from varcode.effects.structural import _build_translocation_mutant_transcript
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
+ def expected_offsets(transcript, position, side):
17
+ """Independent per-base oracle in transcript order, including the anchor."""
18
+ positions = [p for exon in transcript.exons
19
+ for p in (range(exon.start, exon.end + 1) if transcript.strand == "+"
20
+ else range(exon.end, exon.start - 1, -1))]
21
+ kept = [i for i, p in enumerate(positions)
22
+ if (p <= position if side == "left" else p >= position)]
23
+ return min(kept), max(kept) + 1
24
+
25
+
26
+ @pytest.mark.parametrize("alt,side", [
27
+ ("N[22:15500000[", "left"), ("N]22:15500000]", "left"),
28
+ ("[22:15500000[N", "right"), ("]22:15500000]N", "right"),
29
+ ("ACGT.", "left"), (".ACGT", "right"),
30
+ ])
31
+ @pytest.mark.parametrize("location", ["exonic", "exon_start", "exon_end", "intronic"])
32
+ def test_both_strands_and_local_sides(transcript, alt, side, location):
33
+ # An interior exon also permits meaningful suffix/prefix comparisons.
34
+ exon = transcript.exons[2]
35
+ if location == "exonic":
36
+ position = (exon.start + exon.end) // 2
37
+ elif location == "exon_start":
38
+ position = exon.start
39
+ elif location == "exon_end":
40
+ position = exon.end
41
+ else:
42
+ other = transcript.exons[3]
43
+ position = ((exon.end + other.start) // 2 if transcript.strand == "+"
44
+ else (other.end + exon.start) // 2)
45
+ sv = parse_symbolic_alt(transcript.contig, position, "N", alt,
46
+ genome=transcript.genome)
47
+ effect = sv.effect_on_transcript(transcript)
48
+ assert isinstance(effect, TranslocationToIntergenic)
49
+ mt = effect.mutant_transcript
50
+ assert mt is not None
51
+ (segment,) = mt.reference_segments
52
+ assert segment.source is transcript
53
+ assert (segment.start, segment.end) == expected_offsets(transcript, position, side)
54
+ assert segment.strand == "+" # source is already oriented transcript cDNA
55
+ five_prime = (side == "left") == (transcript.strand == "+")
56
+ assert segment.label == ("translocation_5p" if five_prime else "translocation_3p")
57
+ assert segment.length < len(transcript.sequence)
58
+ assert mt.cdna_sequence is None
59
+ assert mt.mutant_protein_sequence is None
60
+ assert mt.evidence["sequence_status"] == "retained_reference_fragment"
61
+
62
+
63
+ def test_cftr_regression_keeps_51_bases_not_entire_transcript():
64
+ tx = cached_release(81).transcript_by_id("ENST00000003084")
65
+ sv = parse_symbolic_alt("7", tx.exons[0].start + 50, "N", "N]22:15500000]",
66
+ genome=tx.genome)
67
+ mt = sv.effect_on_transcript(tx).mutant_transcript
68
+ (segment,) = mt.reference_segments
69
+ assert (segment.start, segment.end) == (0, 51)
70
+ assert segment.source.sequence[segment.start:segment.end] == tx.sequence[:51]
71
+
72
+
73
+ @pytest.mark.parametrize("alt", [None, "<BND>", "N[?:?[", "N[22:15500000]", ".", "N.N"])
74
+ def test_unknown_local_orientation_is_unresolved(transcript, alt):
75
+ sv = StructuralVariant(transcript.contig, transcript.exons[0].start + 5,
76
+ "BND", alt=alt, genome=transcript.genome)
77
+ assert sv.effect_on_transcript(transcript).mutant_transcript is None
78
+
79
+
80
+ @pytest.mark.parametrize("orientation", ["[[", "]]"])
81
+ def test_mate_orientation_does_not_determine_local_side(transcript, orientation):
82
+ sv = StructuralVariant(transcript.contig, transcript.exons[0].start + 5,
83
+ "BND", mate_contig="22", mate_start=15_500_000,
84
+ mate_orientation=orientation, genome=transcript.genome)
85
+ with pytest.warns(UserWarning, match="can't read the breakend orientation"):
86
+ effect = sv.effect_on_transcript(transcript)
87
+ assert isinstance(effect, TranslocationToIntergenic)
88
+ assert effect.mutant_transcript is None
89
+
90
+
91
+ @pytest.mark.parametrize("location", ["before", "after", "wrong_contig"])
92
+ def test_unrelated_transcript_does_not_supply_a_segment(transcript, location):
93
+ position = {"before": transcript.start - 1, "after": transcript.end + 1,
94
+ "wrong_contig": transcript.start + 5}[location]
95
+ contig = "1" if location == "wrong_contig" else transcript.contig
96
+ sv = StructuralVariant(contig, position, "BND", alt="N.", genome=transcript.genome)
97
+ assert _build_translocation_mutant_transcript(sv, transcript) is None
98
+
99
+
100
+ @pytest.mark.parametrize("alt", ["N]22:15500000]", "]22:15500000]N", "N.", ".N", "<BND>"])
101
+ def test_supplied_assembly_is_unchanged(transcript, alt):
102
+ assembly = "ATGGCTTAA"
103
+ sv = StructuralVariant(transcript.contig, transcript.exons[0].start + 5,
104
+ "BND", alt=alt, alt_assembly=assembly, genome=transcript.genome)
105
+ mt = sv.effect_on_transcript(transcript).mutant_transcript
106
+ assert mt.cdna_sequence == assembly
107
+ assert mt.mutant_protein_sequence is None
108
+ assert mt.evidence == {"source": "alt_assembly"}
109
+ (segment,) = mt.reference_segments
110
+ assert segment.source.sequence == assembly
111
+ assert (segment.start, segment.end, segment.label) == (0, len(assembly), "alt_assembly")
@@ -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():
@@ -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)
@@ -35,7 +35,7 @@ from .effect_helpers import exon_length
35
35
  from ..mutant_transcript import MutantTranscript, ReferenceSegment
36
36
  # Existing assembled-SV pickles use this private module path.
37
37
  from ..mutant_transcript import _AssembledAllele # noqa: F401
38
- from ..sv_allele_parser import breakend_sides
38
+ from ..sv_allele_parser import breakend_sides, _breakend_local_side
39
39
 
40
40
 
41
41
  # --------------------------------------------------------------------
@@ -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
@@ -495,23 +501,29 @@ def _build_fusion_mutant_transcript(
495
501
 
496
502
 
497
503
  def _build_translocation_mutant_transcript(variant, transcript):
498
- """Build a :class:`MutantTranscript` for a BND classified as
499
- :class:`TranslocationToIntergenic`.
504
+ """Describe only the retained local reference fragment of a BND.
500
505
 
501
- One segment covering the (this-side) transcript up to the
502
- breakpoint. Intergenic space beyond the breakpoint isn't
503
- representable as a transcript source; #338 and #341 extend this
504
- with caller-supplied genomic intervals / long-read assemblies.
506
+ The other side and full mutant cDNA/protein remain unknown. Without
507
+ local orientation or a breakpoint on this transcript, no fragment can
508
+ be established. Supplied assemblies are handled by the caller.
505
509
  """
510
+ side = _breakend_local_side(variant.symbolic_alt)
511
+ if (side is None or str(variant.contig) != str(transcript.contig)
512
+ or not transcript.start <= variant.start <= transcript.end):
513
+ return None
514
+ keep_five_prime = _retains_five_prime_end(transcript, side)
515
+ cut = _cdna_cut(transcript, variant.start, keep_five_prime)
506
516
  full_len = len(str(transcript.sequence))
517
+ start, end = (0, cut) if keep_five_prime else (cut, full_len)
507
518
  segments = (
508
519
  ReferenceSegment(
509
- source=transcript, start=0, end=full_len,
510
- strand="+", label="translocation_5p"),)
520
+ source=transcript, start=start, end=end, strand="+",
521
+ label="translocation_5p" if keep_five_prime else "translocation_3p"),)
511
522
  return MutantTranscript(
512
523
  reference_transcript=transcript,
513
524
  reference_segments=segments,
514
525
  cdna_sequence=None,
526
+ evidence={"sequence_status": "retained_reference_fragment"},
515
527
  annotator_name="structural_variant")
516
528
 
517
529
 
@@ -232,7 +232,10 @@ class MutantTranscript(DataclassSerializable):
232
232
  strand segments), produce the mutant cDNA. ``None`` for the
233
233
  point-variant shape; a fusion's segments would be
234
234
  ``(5p_partner_segment, 3p_partner_segment)``. Coordinates are
235
- in each segment's own reference system."""
235
+ in each segment's own reference system. A partial structural model may
236
+ describe only a retained reference fragment: its producer identifies this
237
+ in ``evidence`` and leaves the full ``cdna_sequence`` unknown. Concatenating
238
+ those partial segments does not establish a full allele."""
236
239
 
237
240
  cdna_sequence: Optional[str] = None
238
241
  """The mutated spliced mRNA, when computed. ``None`` if the
@@ -78,6 +78,17 @@ def breakend_sides(alt):
78
78
  return this_side, mate_side
79
79
 
80
80
 
81
+ def _breakend_local_side(alt):
82
+ """The retained local side, including single breakends with no mate."""
83
+ sides = breakend_sides(alt)
84
+ if sides is not None:
85
+ return sides[0]
86
+ single = _SINGLE_BREAKEND_RE.match(alt or "")
87
+ if single is not None:
88
+ return "right" if single.group("joined_before") else "left"
89
+ return None
90
+
91
+
81
92
  def _extract_info(info, key):
82
93
  """Pull an INFO field value, handling list-of-ints from CIPOS /
83
94
  CIEND and plain ints/strings otherwise. Returns ``None`` if
@@ -0,0 +1 @@
1
+ __version__ = "9.2.4"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: varcode
3
- Version: 9.2.2
3
+ Version: 9.2.4
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
@@ -6,6 +6,7 @@ requirements.txt
6
6
  tests/test_annotator_divergence_scenarios.py
7
7
  tests/test_annotator_parity_adversarial.py
8
8
  tests/test_annotators.py
9
+ tests/test_bnd_retained_segments.py
9
10
  tests/test_cli_effects.py
10
11
  tests/test_cli_genes.py
11
12
  tests/test_cli_without_pkg_resources.py
@@ -45,6 +46,7 @@ tests/test_mouse.py
45
46
  tests/test_mutant_transcript.py
46
47
  tests/test_mutate.py
47
48
  tests/test_no_duplicate_variants.py
49
+ tests/test_nonlocal_sv_transcripts.py
48
50
  tests/test_osteosarc_fusions.py
49
51
  tests/test_partial_annotators.py
50
52
  tests/test_premature_stop_short_description.py
@@ -1 +0,0 @@
1
- __version__ = "9.2.2"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes