varcode 9.3.1__tar.gz → 9.3.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.
Files changed (164) hide show
  1. {varcode-9.3.1/varcode.egg-info → varcode-9.3.2}/PKG-INFO +1 -1
  2. {varcode-9.3.1 → varcode-9.3.2}/tests/test_sv_sequence_changes.py +209 -0
  3. {varcode-9.3.1 → varcode-9.3.2}/varcode/effects/sequence_change.py +70 -2
  4. varcode-9.3.2/varcode/version.py +1 -0
  5. {varcode-9.3.1 → varcode-9.3.2/varcode.egg-info}/PKG-INFO +1 -1
  6. varcode-9.3.1/varcode/version.py +0 -1
  7. {varcode-9.3.1 → varcode-9.3.2}/LICENSE +0 -0
  8. {varcode-9.3.1 → varcode-9.3.2}/MANIFEST.in +0 -0
  9. {varcode-9.3.1 → varcode-9.3.2}/README.md +0 -0
  10. {varcode-9.3.1 → varcode-9.3.2}/pyproject.toml +0 -0
  11. {varcode-9.3.1 → varcode-9.3.2}/requirements.txt +0 -0
  12. {varcode-9.3.1 → varcode-9.3.2}/setup.cfg +0 -0
  13. {varcode-9.3.1 → varcode-9.3.2}/tests/test_annotator_divergence_scenarios.py +0 -0
  14. {varcode-9.3.1 → varcode-9.3.2}/tests/test_annotator_parity_adversarial.py +0 -0
  15. {varcode-9.3.1 → varcode-9.3.2}/tests/test_annotators.py +0 -0
  16. {varcode-9.3.1 → varcode-9.3.2}/tests/test_bnd_retained_segments.py +0 -0
  17. {varcode-9.3.1 → varcode-9.3.2}/tests/test_cli_effects.py +0 -0
  18. {varcode-9.3.1 → varcode-9.3.2}/tests/test_cli_genes.py +0 -0
  19. {varcode-9.3.1 → varcode-9.3.2}/tests/test_cli_without_pkg_resources.py +0 -0
  20. {varcode-9.3.1 → varcode-9.3.2}/tests/test_collection_filtering.py +0 -0
  21. {varcode-9.3.1 → varcode-9.3.2}/tests/test_collection_variants_attr_consistency.py +0 -0
  22. {varcode-9.3.1 → varcode-9.3.2}/tests/test_common.py +0 -0
  23. {varcode-9.3.1 → varcode-9.3.2}/tests/test_cosmic_mutations.py +0 -0
  24. {varcode-9.3.1 → varcode-9.3.2}/tests/test_cryptic_exons.py +0 -0
  25. {varcode-9.3.1 → varcode-9.3.2}/tests/test_csv_roundtrip.py +0 -0
  26. {varcode-9.3.1 → varcode-9.3.2}/tests/test_dbnsfp_validation.py +0 -0
  27. {varcode-9.3.1 → varcode-9.3.2}/tests/test_default_structural_helpers.py +0 -0
  28. {varcode-9.3.1 → varcode-9.3.2}/tests/test_documentation_examples.py +0 -0
  29. {varcode-9.3.1 → varcode-9.3.2}/tests/test_effect_annotation_errors.py +0 -0
  30. {varcode-9.3.1 → varcode-9.3.2}/tests/test_effect_candidates.py +0 -0
  31. {varcode-9.3.1 → varcode-9.3.2}/tests/test_effect_classes.py +0 -0
  32. {varcode-9.3.1 → varcode-9.3.2}/tests/test_effect_collection.py +0 -0
  33. {varcode-9.3.1 → varcode-9.3.2}/tests/test_effect_collection_serialization.py +0 -0
  34. {varcode-9.3.1 → varcode-9.3.2}/tests/test_effect_collection_sort_order.py +0 -0
  35. {varcode-9.3.1 → varcode-9.3.2}/tests/test_effect_hypotheses.py +0 -0
  36. {varcode-9.3.1 → varcode-9.3.2}/tests/test_effects_from_mutagenix_variants.py +0 -0
  37. {varcode-9.3.1 → varcode-9.3.2}/tests/test_exacto_fusions.py +0 -0
  38. {varcode-9.3.1 → varcode-9.3.2}/tests/test_exonic_splice_site.py +0 -0
  39. {varcode-9.3.1 → varcode-9.3.2}/tests/test_fast_path.py +0 -0
  40. {varcode-9.3.1 → varcode-9.3.2}/tests/test_frameshift_cterminus_regression.py +0 -0
  41. {varcode-9.3.1 → varcode-9.3.2}/tests/test_frameshift_helpers.py +0 -0
  42. {varcode-9.3.1 → varcode-9.3.2}/tests/test_fusion_partner_candidates.py +0 -0
  43. {varcode-9.3.1 → varcode-9.3.2}/tests/test_genome_contig_validation.py +0 -0
  44. {varcode-9.3.1 → varcode-9.3.2}/tests/test_genome_sequence.py +0 -0
  45. {varcode-9.3.1 → varcode-9.3.2}/tests/test_genomic_layout.py +0 -0
  46. {varcode-9.3.1 → varcode-9.3.2}/tests/test_genotype.py +0 -0
  47. {varcode-9.3.1 → varcode-9.3.2}/tests/test_genotype_from_vcf.py +0 -0
  48. {varcode-9.3.1 → varcode-9.3.2}/tests/test_germline_context.py +0 -0
  49. {varcode-9.3.1 → varcode-9.3.2}/tests/test_germline_effects.py +0 -0
  50. {varcode-9.3.1 → varcode-9.3.2}/tests/test_haplotype_effect.py +0 -0
  51. {varcode-9.3.1 → varcode-9.3.2}/tests/test_maf.py +0 -0
  52. {varcode-9.3.1 → varcode-9.3.2}/tests/test_mitochondrial.py +0 -0
  53. {varcode-9.3.1 → varcode-9.3.2}/tests/test_mm10_klf6_frameshift.py +0 -0
  54. {varcode-9.3.1 → varcode-9.3.2}/tests/test_mouse.py +0 -0
  55. {varcode-9.3.1 → varcode-9.3.2}/tests/test_mutant_transcript.py +0 -0
  56. {varcode-9.3.1 → varcode-9.3.2}/tests/test_mutate.py +0 -0
  57. {varcode-9.3.1 → varcode-9.3.2}/tests/test_no_duplicate_variants.py +0 -0
  58. {varcode-9.3.1 → varcode-9.3.2}/tests/test_nonlocal_sv_transcripts.py +0 -0
  59. {varcode-9.3.1 → varcode-9.3.2}/tests/test_osteosarc_fusions.py +0 -0
  60. {varcode-9.3.1 → varcode-9.3.2}/tests/test_partial_annotators.py +0 -0
  61. {varcode-9.3.1 → varcode-9.3.2}/tests/test_premature_stop_short_description.py +0 -0
  62. {varcode-9.3.1 → varcode-9.3.2}/tests/test_problematic_variants.py +0 -0
  63. {varcode-9.3.1 → varcode-9.3.2}/tests/test_protein_diff_parity.py +0 -0
  64. {varcode-9.3.1 → varcode-9.3.2}/tests/test_read_phase_resolver.py +0 -0
  65. {varcode-9.3.1 → varcode-9.3.2}/tests/test_real_world_callers.py +0 -0
  66. {varcode-9.3.1 → varcode-9.3.2}/tests/test_reference.py +0 -0
  67. {varcode-9.3.1 → varcode-9.3.2}/tests/test_reference_mismatch_error.py +0 -0
  68. {varcode-9.3.1 → varcode-9.3.2}/tests/test_reference_segments.py +0 -0
  69. {varcode-9.3.1 → varcode-9.3.2}/tests/test_rna_evidence.py +0 -0
  70. {varcode-9.3.1 → varcode-9.3.2}/tests/test_rna_haplotype_sequences.py +0 -0
  71. {varcode-9.3.1 → varcode-9.3.2}/tests/test_rna_read_phasing_source.py +0 -0
  72. {varcode-9.3.1 → varcode-9.3.2}/tests/test_selenocysteine.py +0 -0
  73. {varcode-9.3.1 → varcode-9.3.2}/tests/test_silent_aa_pos.py +0 -0
  74. {varcode-9.3.1 → varcode-9.3.2}/tests/test_silent_hgvs_description.py +0 -0
  75. {varcode-9.3.1 → varcode-9.3.2}/tests/test_splice_graph.py +0 -0
  76. {varcode-9.3.1 → varcode-9.3.2}/tests/test_splice_outcomes.py +0 -0
  77. {varcode-9.3.1 → varcode-9.3.2}/tests/test_splice_site_effects.py +0 -0
  78. {varcode-9.3.1 → varcode-9.3.2}/tests/test_stop_codon_classification_bugs.py +0 -0
  79. {varcode-9.3.1 → varcode-9.3.2}/tests/test_string_helpers.py +0 -0
  80. {varcode-9.3.1 → varcode-9.3.2}/tests/test_structural_variant_annotator.py +0 -0
  81. {varcode-9.3.1 → varcode-9.3.2}/tests/test_structural_variants.py +0 -0
  82. {varcode-9.3.1 → varcode-9.3.2}/tests/test_sv_tabular_metadata.py +0 -0
  83. {varcode-9.3.1 → varcode-9.3.2}/tests/test_symbolic_alleles.py +0 -0
  84. {varcode-9.3.1 → varcode-9.3.2}/tests/test_symbolic_sv_padding.py +0 -0
  85. {varcode-9.3.1 → varcode-9.3.2}/tests/test_timings.py +0 -0
  86. {varcode-9.3.1 → varcode-9.3.2}/tests/test_transcript_layout.py +0 -0
  87. {varcode-9.3.1 → varcode-9.3.2}/tests/test_transcript_model.py +0 -0
  88. {varcode-9.3.1 → varcode-9.3.2}/tests/test_transforms_left_align.py +0 -0
  89. {varcode-9.3.1 → varcode-9.3.2}/tests/test_transforms_pair_breakends.py +0 -0
  90. {varcode-9.3.1 → varcode-9.3.2}/tests/test_variant.py +0 -0
  91. {varcode-9.3.1 → varcode-9.3.2}/tests/test_variant_collection.py +0 -0
  92. {varcode-9.3.1 → varcode-9.3.2}/tests/test_vcf.py +0 -0
  93. {varcode-9.3.1 → varcode-9.3.2}/tests/test_vcf_output.py +0 -0
  94. {varcode-9.3.1 → varcode-9.3.2}/tests/test_vcf_parsing.py +0 -0
  95. {varcode-9.3.1 → varcode-9.3.2}/tests/test_vcf_phase_resolver.py +0 -0
  96. {varcode-9.3.1 → varcode-9.3.2}/tests/test_vcf_spec_examples.py +0 -0
  97. {varcode-9.3.1 → varcode-9.3.2}/varcode/__init__.py +0 -0
  98. {varcode-9.3.1 → varcode-9.3.2}/varcode/annotators/__init__.py +0 -0
  99. {varcode-9.3.1 → varcode-9.3.2}/varcode/annotators/fast.py +0 -0
  100. {varcode-9.3.1 → varcode-9.3.2}/varcode/annotators/protein_diff.py +0 -0
  101. {varcode-9.3.1 → varcode-9.3.2}/varcode/annotators/registry.py +0 -0
  102. {varcode-9.3.1 → varcode-9.3.2}/varcode/cli/__init__.py +0 -0
  103. {varcode-9.3.1 → varcode-9.3.2}/varcode/cli/effects_script.py +0 -0
  104. {varcode-9.3.1 → varcode-9.3.2}/varcode/cli/genes_script.py +0 -0
  105. {varcode-9.3.1 → varcode-9.3.2}/varcode/cli/logging.conf +0 -0
  106. {varcode-9.3.1 → varcode-9.3.2}/varcode/cli/variant_args.py +0 -0
  107. {varcode-9.3.1 → varcode-9.3.2}/varcode/cli/version_info.py +0 -0
  108. {varcode-9.3.1 → varcode-9.3.2}/varcode/common.py +0 -0
  109. {varcode-9.3.1 → varcode-9.3.2}/varcode/cryptic_exons.py +0 -0
  110. {varcode-9.3.1 → varcode-9.3.2}/varcode/csv_helpers.py +0 -0
  111. {varcode-9.3.1 → varcode-9.3.2}/varcode/effect_candidates.py +0 -0
  112. {varcode-9.3.1 → varcode-9.3.2}/varcode/effect_hypotheses.py +0 -0
  113. {varcode-9.3.1 → varcode-9.3.2}/varcode/effects/__init__.py +0 -0
  114. {varcode-9.3.1 → varcode-9.3.2}/varcode/effects/classify.py +0 -0
  115. {varcode-9.3.1 → varcode-9.3.2}/varcode/effects/codon_tables.py +0 -0
  116. {varcode-9.3.1 → varcode-9.3.2}/varcode/effects/common.py +0 -0
  117. {varcode-9.3.1 → varcode-9.3.2}/varcode/effects/effect_classes.py +0 -0
  118. {varcode-9.3.1 → varcode-9.3.2}/varcode/effects/effect_collection.py +0 -0
  119. {varcode-9.3.1 → varcode-9.3.2}/varcode/effects/effect_helpers.py +0 -0
  120. {varcode-9.3.1 → varcode-9.3.2}/varcode/effects/effect_ordering.py +0 -0
  121. {varcode-9.3.1 → varcode-9.3.2}/varcode/effects/effect_prediction.py +0 -0
  122. {varcode-9.3.1 → varcode-9.3.2}/varcode/effects/effect_prediction_coding.py +0 -0
  123. {varcode-9.3.1 → varcode-9.3.2}/varcode/effects/effect_prediction_coding_frameshift.py +0 -0
  124. {varcode-9.3.1 → varcode-9.3.2}/varcode/effects/effect_prediction_coding_in_frame.py +0 -0
  125. {varcode-9.3.1 → varcode-9.3.2}/varcode/effects/fast_path.py +0 -0
  126. {varcode-9.3.1 → varcode-9.3.2}/varcode/effects/mutate.py +0 -0
  127. {varcode-9.3.1 → varcode-9.3.2}/varcode/effects/structural.py +0 -0
  128. {varcode-9.3.1 → varcode-9.3.2}/varcode/effects/transcript_helpers.py +0 -0
  129. {varcode-9.3.1 → varcode-9.3.2}/varcode/effects/translate.py +0 -0
  130. {varcode-9.3.1 → varcode-9.3.2}/varcode/errors.py +0 -0
  131. {varcode-9.3.1 → varcode-9.3.2}/varcode/exacto.py +0 -0
  132. {varcode-9.3.1 → varcode-9.3.2}/varcode/genome.py +0 -0
  133. {varcode-9.3.1 → varcode-9.3.2}/varcode/genome_sequence.py +0 -0
  134. {varcode-9.3.1 → varcode-9.3.2}/varcode/genomic_layout.py +0 -0
  135. {varcode-9.3.1 → varcode-9.3.2}/varcode/genotype.py +0 -0
  136. {varcode-9.3.1 → varcode-9.3.2}/varcode/germline.py +0 -0
  137. {varcode-9.3.1 → varcode-9.3.2}/varcode/maf.py +0 -0
  138. {varcode-9.3.1 → varcode-9.3.2}/varcode/mutant_transcript.py +0 -0
  139. {varcode-9.3.1 → varcode-9.3.2}/varcode/nucleotides.py +0 -0
  140. {varcode-9.3.1 → varcode-9.3.2}/varcode/phasing.py +0 -0
  141. {varcode-9.3.1 → varcode-9.3.2}/varcode/realized_effects.py +0 -0
  142. {varcode-9.3.1 → varcode-9.3.2}/varcode/reference.py +0 -0
  143. {varcode-9.3.1 → varcode-9.3.2}/varcode/rna_evidence.py +0 -0
  144. {varcode-9.3.1 → varcode-9.3.2}/varcode/rna_read_phasing.py +0 -0
  145. {varcode-9.3.1 → varcode-9.3.2}/varcode/splice_graph.py +0 -0
  146. {varcode-9.3.1 → varcode-9.3.2}/varcode/splice_outcomes.py +0 -0
  147. {varcode-9.3.1 → varcode-9.3.2}/varcode/string_helpers.py +0 -0
  148. {varcode-9.3.1 → varcode-9.3.2}/varcode/structural_variant.py +0 -0
  149. {varcode-9.3.1 → varcode-9.3.2}/varcode/sv_allele_parser.py +0 -0
  150. {varcode-9.3.1 → varcode-9.3.2}/varcode/transcript_layout.py +0 -0
  151. {varcode-9.3.1 → varcode-9.3.2}/varcode/transcript_model.py +0 -0
  152. {varcode-9.3.1 → varcode-9.3.2}/varcode/transforms/__init__.py +0 -0
  153. {varcode-9.3.1 → varcode-9.3.2}/varcode/ucsc_reference_names.py +0 -0
  154. {varcode-9.3.1 → varcode-9.3.2}/varcode/util.py +0 -0
  155. {varcode-9.3.1 → varcode-9.3.2}/varcode/variant.py +0 -0
  156. {varcode-9.3.1 → varcode-9.3.2}/varcode/variant_collection.py +0 -0
  157. {varcode-9.3.1 → varcode-9.3.2}/varcode/vcf.py +0 -0
  158. {varcode-9.3.1 → varcode-9.3.2}/varcode/vcf_output.py +0 -0
  159. {varcode-9.3.1 → varcode-9.3.2}/varcode/vcf_parsing.py +0 -0
  160. {varcode-9.3.1 → varcode-9.3.2}/varcode.egg-info/SOURCES.txt +0 -0
  161. {varcode-9.3.1 → varcode-9.3.2}/varcode.egg-info/dependency_links.txt +0 -0
  162. {varcode-9.3.1 → varcode-9.3.2}/varcode.egg-info/entry_points.txt +0 -0
  163. {varcode-9.3.1 → varcode-9.3.2}/varcode.egg-info/requires.txt +0 -0
  164. {varcode-9.3.1 → varcode-9.3.2}/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.3.1
3
+ Version: 9.3.2
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
@@ -14,6 +14,7 @@ from varcode.effects import (
14
14
  StructuralVariantEffect, TranslocationToIntergenic, Unresolved,
15
15
  )
16
16
  from varcode.effects import structural
17
+ from varcode.effects.codon_tables import codon_table_for_transcript
17
18
 
18
19
 
19
20
  @pytest.fixture
@@ -128,6 +129,214 @@ def test_partial_fragment_and_unmapped_assembly_are_unknown(cftr):
128
129
  assert effect.modifies_coding_sequence is None
129
130
 
130
131
 
132
+ @pytest.mark.parametrize("completeness", [
133
+ "partial_start", "partial_end", "partial_both", "unknown", None,
134
+ ])
135
+ @pytest.mark.parametrize("protein", ["reference_fragment", "different", ""])
136
+ def test_unmapped_partial_proteins_do_not_establish_a_change(cftr, completeness, protein):
137
+ if protein == "reference_fragment":
138
+ protein = cftr.protein_sequence[:100]
139
+ effect = _effect(cftr)
140
+ effect.mutant_transcript = MutantTranscript.from_sequence(
141
+ cftr.sequence[:432], reference_transcript=cftr,
142
+ mutant_protein_sequence=protein,
143
+ evidence={"protein_completeness": completeness})
144
+ assert effect.modifies_protein_sequence is None
145
+ assert effect.modifies_coding_sequence is None
146
+ assert list(EffectCollection([effect]).drop_silent_and_noncoding()) == [effect]
147
+ assert len(EffectCollection([effect]).drop_silent_and_noncoding(False)) == 0
148
+ assert effect.mutant_protein_sequence == protein
149
+
150
+
151
+ @pytest.mark.parametrize("completeness", ["partial_start", "partial_end", "partial_both"])
152
+ def test_partial_label_prevents_complete_orf_fallback(cftr, completeness):
153
+ # A complete-looking ORF must not override the producer's explicit partial
154
+ # interpretation (e.g. an internal alternative start in an observed fragment).
155
+ effect = _effect(cftr, cdna=cftr.sequence, segments=_identity_segments(cftr),
156
+ evidence={"protein_completeness": completeness})
157
+ assert effect.modifies_protein_sequence is None
158
+ assert effect.modifies_coding_sequence is None
159
+ evidence = dict(effect.mutant_transcript.evidence,
160
+ cds_start=min(cftr.start_codon_spliced_offsets),
161
+ cds_end=max(cftr.stop_codon_spliced_offsets) + 1)
162
+ effect.mutant_transcript = replace(effect.mutant_transcript, evidence=evidence)
163
+ assert effect.modifies_protein_sequence is None
164
+ assert effect.modifies_coding_sequence is None
165
+
166
+
167
+ def _mapped_partial(transcript, completeness, change="none"):
168
+ """An observed CDS fragment mapped onto its reference, one codon changed.
169
+
170
+ Flags compare observed codons, so no protein is supplied.
171
+ """
172
+ table = codon_table_for_transcript(transcript)
173
+ start = min(transcript.start_codon_spliced_offsets)
174
+ stop = max(transcript.stop_codon_spliced_offsets) + 1
175
+ left = start if completeness == "partial_end" else start + 300
176
+ right = stop if completeness == "partial_start" else left + 300
177
+ cdna = transcript.sequence[left:right]
178
+ if change == "stop_loss":
179
+ pos, codon = len(cdna) - 3, "TGG"
180
+ else:
181
+ pos = next(i for i in range(3, len(cdna), 3)
182
+ if table.forward_table.get(cdna[i:i + 3], "M") not in "MW")
183
+ original = cdna[pos:pos + 3]
184
+ synonyms = [c for c, aa in sorted(table.forward_table.items())
185
+ if aa == table.forward_table[original] and c != original]
186
+ codon = {"none": original, "synonymous": synonyms[0], "missense": "TGG",
187
+ "nonsense": "TAA", "ambiguous": original[:2] + "N"}[change]
188
+ cdna = cdna[:pos] + codon + cdna[pos + 3:]
189
+ return _effect(
190
+ transcript, cdna=cdna,
191
+ segments=(ReferenceSegment(transcript, left, right),),
192
+ evidence={"protein_completeness": completeness,
193
+ "cds_start": 0, "cds_end": len(cdna)}), pos
194
+
195
+
196
+ @pytest.mark.parametrize("transcript_id", ["ENST00000003084", "ENST00000357654"])
197
+ @pytest.mark.parametrize("completeness", ["partial_start", "partial_end", "partial_both"])
198
+ @pytest.mark.parametrize("change", ["none", "synonymous", "missense", "nonsense", "ambiguous"])
199
+ def test_partial_observation_only_establishes_mapped_local_changes(
200
+ transcript_id, completeness, change):
201
+ transcript = cached_release(81).transcript_by_id(transcript_id)
202
+ effect, _ = _mapped_partial(transcript, completeness, change)
203
+ before = effect.mutant_transcript
204
+ assert effect.modifies_coding_sequence is (
205
+ True if change in ("synonymous", "missense", "nonsense") else None)
206
+ assert effect.modifies_protein_sequence is (
207
+ True if change in ("missense", "nonsense") else None)
208
+ assert effect.mutant_transcript == before
209
+
210
+
211
+ @pytest.mark.parametrize("transcript_id", ["ENST00000003084", "ENST00000357654"])
212
+ def test_mapped_stop_loss_is_a_protein_change(transcript_id):
213
+ transcript = cached_release(81).transcript_by_id(transcript_id)
214
+ effect, _ = _mapped_partial(transcript, "partial_start", "stop_loss")
215
+ assert effect.modifies_coding_sequence is True
216
+ assert effect.modifies_protein_sequence is True
217
+
218
+
219
+ def test_mapped_reference_fragment_is_not_a_truncation(cftr):
220
+ # The #462 report, with reference coordinates: equal observed codons and a
221
+ # shorter supplied protein still leave the unobserved suffix unknown.
222
+ effect, _ = _mapped_partial(cftr, "partial_end")
223
+ effect.mutant_transcript = replace(
224
+ effect.mutant_transcript, mutant_protein_sequence=cftr.protein_sequence[:100])
225
+ assert effect.modifies_coding_sequence is None
226
+ assert effect.modifies_protein_sequence is None
227
+ assert len(EffectCollection([effect]).drop_silent_and_noncoding(False)) == 0
228
+
229
+
230
+ def test_partial_uses_mitochondrial_table():
231
+ # TGA and TGG both encode tryptophan in vertebrate mitochondria.
232
+ transcript = cached_release(81).transcript_by_id("ENST00000361624")
233
+ effect, _ = _mapped_partial(transcript, "partial_both")
234
+ model = effect.mutant_transcript
235
+ cdna = model.cdna_sequence
236
+ pos = next(i for i in range(0, len(cdna), 3) if cdna[i:i + 3] in ("TGA", "TGG"))
237
+ swapped = "TGG" if cdna[pos:pos + 3] == "TGA" else "TGA"
238
+ effect.mutant_transcript = replace(
239
+ model, cdna_sequence=cdna[:pos] + swapped + cdna[pos + 3:])
240
+ assert effect.modifies_coding_sequence is True
241
+ assert effect.modifies_protein_sequence is None
242
+
243
+
244
+ @pytest.mark.parametrize("upstream", [False, True])
245
+ def test_alternative_start_codon_reads_as_met_only_where_the_orf_begins(cftr, upstream):
246
+ # A 3' partner's start codon is internal to a fused ORF, so CTG there is Leu.
247
+ brca1 = cached_release(81).transcript_by_id("ENST00000357654")
248
+ start = min(cftr.start_codon_spliced_offsets)
249
+ other = min(brca1.start_codon_spliced_offsets) + 3
250
+ segments = (ReferenceSegment(cftr, start, start + 300),)
251
+ cdna = "CTG" + cftr.sequence[start + 3:start + 300]
252
+ if upstream:
253
+ segments = (ReferenceSegment(brca1, other, other + 30),) + segments
254
+ cdna = brca1.sequence[other:other + 30] + cdna
255
+ effect = _effect(cftr, cdna=cdna, segments=segments, evidence={
256
+ "protein_completeness": "partial_both", "cds_start": 0, "cds_end": len(cdna)})
257
+ assert effect.modifies_coding_sequence is True
258
+ assert effect.modifies_protein_sequence is (True if upstream else None)
259
+
260
+
261
+ def test_partial_bounds_accept_numpy_integers(cftr):
262
+ import numpy as np
263
+
264
+ effect, _ = _mapped_partial(cftr, "partial_both", "missense")
265
+ evidence = effect.mutant_transcript.evidence
266
+ effect.mutant_transcript = replace(effect.mutant_transcript, evidence=dict(
267
+ evidence, cds_start=np.int64(0), cds_end=np.int64(evidence["cds_end"])))
268
+ assert effect.modifies_coding_sequence is True
269
+ assert effect.modifies_protein_sequence is True
270
+
271
+
272
+ def test_partial_mapped_change_survives_split_reference_segments(cftr):
273
+ effect, pos = _mapped_partial(cftr, "partial_both", "missense")
274
+ segment, = effect.mutant_transcript.reference_segments
275
+ effect.mutant_transcript = replace(effect.mutant_transcript, reference_segments=(
276
+ replace(segment, end=segment.start + pos + 1),
277
+ replace(segment, start=segment.start + pos + 1)))
278
+ assert effect.modifies_coding_sequence is True
279
+ assert effect.modifies_protein_sequence is True
280
+
281
+
282
+ @pytest.mark.parametrize("missing", [
283
+ "frame", "float_frame", "offset_frame", "mapping", "reverse", "length", "edits"])
284
+ def test_partial_local_change_requires_usable_coordinates(cftr, missing):
285
+ from varcode import TranscriptEdit
286
+
287
+ effect, _ = _mapped_partial(cftr, "partial_both", "missense")
288
+ model = effect.mutant_transcript
289
+ if missing == "frame":
290
+ model = replace(model, evidence={"protein_completeness": "partial_both"})
291
+ elif missing == "float_frame":
292
+ model = replace(model, evidence=dict(model.evidence, cds_start=0.0))
293
+ elif missing == "offset_frame":
294
+ # Codons out of frame with the reference CDS have no counterpart.
295
+ model = replace(model, evidence=dict(model.evidence, cds_start=1))
296
+ elif missing == "mapping":
297
+ model = replace(model, reference_segments=None)
298
+ elif missing == "reverse":
299
+ model = replace(model, reference_segments=(replace(model.reference_segments[0], strand="-"),))
300
+ elif missing == "length":
301
+ model = replace(model, cdna_sequence=model.cdna_sequence + "A")
302
+ else:
303
+ model = replace(model, edits=(TranscriptEdit(0, 0, "A"),))
304
+ effect.mutant_transcript = model
305
+ assert effect.modifies_coding_sequence is None
306
+ assert effect.modifies_protein_sequence is None
307
+
308
+
309
+ @pytest.mark.parametrize("completeness", ["partial_start", "partial_end", "partial_both"])
310
+ def test_imported_partial_peptide_without_reference_mapping_is_unknown(cftr, completeness):
311
+ # Exacto peptides carry observed ORF bounds but no reference coordinates.
312
+ variant = StructuralVariant(cftr.contig, cftr.start, "BND", genome=cftr.genome)
313
+ start = min(cftr.start_codon_spliced_offsets)
314
+ cdna = cftr.sequence[:start + 3] + "TGG" + cftr.sequence[start + 6:start + 300]
315
+ candidate = make_fusion_outcome(
316
+ variant, cftr, sequence=cdna, transcript_model_id="observed-model", source="exacto")
317
+ model = candidate.effect.mutant_transcript
318
+ candidate.effect.mutant_transcript = replace(
319
+ model, mutant_protein_sequence="MW" + cftr.protein_sequence[2:100],
320
+ evidence=dict(model.evidence or {}, protein_completeness=completeness,
321
+ cds_start=start, cds_end=start + 300))
322
+ assert candidate.effect.modifies_coding_sequence is None
323
+ assert candidate.effect.modifies_protein_sequence is None
324
+
325
+
326
+ def test_complete_protein_and_partial_alternative_remain_unknown(cftr):
327
+ primary = _effect(cftr, protein=cftr.protein_sequence,
328
+ evidence={"protein_completeness": "start_to_stop"})
329
+ partial, _ = _mapped_partial(cftr, "partial_both", "none")
330
+ primary._extra_candidates = (EffectCandidate(partial, source="rna"),)
331
+ assert primary.modifies_protein_sequence is None
332
+ assert len(EffectCollection([primary]).drop_silent_and_noncoding()) == 1
333
+ assert len(EffectCollection([primary]).drop_silent_and_noncoding(False)) == 0
334
+ changed, _ = _mapped_partial(cftr, "partial_both", "missense")
335
+ primary._extra_candidates += (EffectCandidate(changed, source="rna"),)
336
+ assert primary.modifies_protein_sequence is True
337
+ assert len(EffectCollection([primary]).drop_silent_and_noncoding(False)) == 1
338
+
339
+
131
340
  @pytest.mark.parametrize("primary_status", [False, None])
132
341
  def test_later_changed_candidate_controls_flags_and_filter(cftr, primary_status):
133
342
  primary = _effect(cftr, protein=cftr.protein_sequence if primary_status is False else None)
@@ -1,5 +1,7 @@
1
1
  """Sequence-change flags for existing SV predictions, not a new SV model."""
2
2
 
3
+ import operator
4
+
3
5
  from .codon_tables import codon_table_for_transcript, translate_sequence
4
6
 
5
7
 
@@ -82,6 +84,67 @@ def _coding_sequence(model, initiator):
82
84
  return None
83
85
 
84
86
 
87
+ def _residue(codon, table, initiator):
88
+ # As for a complete ORF, any start codon at the initiator reads as Met.
89
+ if initiator and codon in table.start_codons:
90
+ return "M"
91
+ return table.forward_table.get(codon, "*")
92
+
93
+
94
+ def _partial_sequence_changes(model, transcript, table):
95
+ """Compare only the mapped, in-frame codons of a partial observation.
96
+
97
+ ``cds_start`` / ``cds_end`` place the producer's reading frame in the
98
+ observed cDNA; transcript segments give each base's reference offset.
99
+ A codon is compared only when its three bases map contiguously onto one
100
+ reference CDS codon. A difference there proves a local change. Equal
101
+ codons cannot show that unobserved sequence is unchanged, and missing
102
+ coverage is not a deletion or truncation, so this never returns False.
103
+ """
104
+ evidence = model.evidence or {}
105
+ try:
106
+ # Any integer type (e.g. numpy); not missing or fractional bounds.
107
+ start = operator.index(evidence.get("cds_start"))
108
+ end = operator.index(evidence.get("cds_end"))
109
+ except TypeError:
110
+ return None, None
111
+ sequence = model.cdna_sequence
112
+ segments = model.reference_segments or ()
113
+ if (sequence is None or model.edits or not transcript.complete
114
+ or not 0 <= start < end <= len(sequence)
115
+ or sum(s.length for s in segments) != len(sequence)):
116
+ # Unmapped edits, or segments that don't render this sequence,
117
+ # leave observed bases without reference coordinates.
118
+ return None, None
119
+ reference_offsets = {}
120
+ offset = 0
121
+ for segment in segments:
122
+ if segment.source == transcript and segment.strand == "+":
123
+ for pos in range(max(start, offset), min(end, offset + segment.length)):
124
+ reference_offsets[pos] = segment.start + pos - offset
125
+ offset += segment.length
126
+ reference_start = min(transcript.start_codon_spliced_offsets)
127
+ reference_coding = transcript.coding_sequence.upper()
128
+ coding_status = None
129
+ for pos in range(start, end - 2, 3):
130
+ codon = sequence[pos:pos + 3].upper()
131
+ ref = reference_offsets.get(pos)
132
+ k = None if ref is None else ref - reference_start
133
+ if (k is not None and k % 3 == 0 and 0 <= k < len(reference_coding)
134
+ and reference_offsets.get(pos + 1) == ref + 1
135
+ and reference_offsets.get(pos + 2) == ref + 2):
136
+ reference_codon = reference_coding[k:k + 3]
137
+ if codon != reference_codon and not set(codon + reference_codon) - set("ACGT"):
138
+ coding_status = True
139
+ # Only an observed ORF that begins here can initiate here.
140
+ observed = _residue(codon, table, k == 0 and pos == start)
141
+ if observed != _residue(reference_codon, table, k == 0):
142
+ return True, True
143
+ if codon in table.stop_codons:
144
+ break # Later bases are not translated in this frame.
145
+ return coding_status, None
146
+
147
+
85
148
  def structural_sequence_changes(effect):
86
149
  """Return local (coding, protein) changes as True / False / None.
87
150
 
@@ -93,15 +156,20 @@ def structural_sequence_changes(effect):
93
156
 
94
157
  transcript = effect.transcript
95
158
  model = effect.mutant_transcript
159
+ initiator = getattr(effect, "five_prime_transcript", transcript)
96
160
  coding_status = protein_status = None
97
- if model is not None:
161
+ if model is not None and (model.evidence or {}).get(
162
+ "protein_completeness", "start_to_stop") != "start_to_stop":
163
+ # A fragment is not a full-length protein; a shorter one isn't truncated.
164
+ coding_status, protein_status = _partial_sequence_changes(
165
+ model, transcript, codon_table_for_transcript(initiator))
166
+ elif model is not None:
98
167
  protein = model.mutant_protein_sequence
99
168
  reference_protein = transcript.protein_sequence
100
169
  if protein is not None and reference_protein is not None:
101
170
  # An empty protein is a known absence, not missing data.
102
171
  if "X" not in protein and "X" not in reference_protein:
103
172
  protein_status = protein != reference_protein
104
- initiator = getattr(effect, "five_prime_transcript", transcript)
105
173
  coding = _coding_sequence(model, initiator)
106
174
  if coding is not None:
107
175
  reference_coding = transcript.coding_sequence
@@ -0,0 +1 @@
1
+ __version__ = "9.3.2"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: varcode
3
- Version: 9.3.1
3
+ Version: 9.3.2
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
@@ -1 +0,0 @@
1
- __version__ = "9.3.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
File without changes