Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,17 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
cache-dependency-path: requirements.txt
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install flake8 pytest pytest-cov coveralls
python -m pip install flake8 ruff pytest pytest-cov coveralls
pip install -r requirements.txt
pip install .
- name: Lint with flake8
Expand All @@ -53,3 +54,5 @@ jobs:
./test.sh
- name: Publish coverage to Coveralls
uses: coverallsapp/github-action@v2.2.3
with:
parallel: true
3 changes: 3 additions & 0 deletions develop.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
set -e

pip install -e .
11 changes: 2 additions & 9 deletions lint.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
#!/bin/bash
set -o errexit

# getting false positives due to this issue with pylint:
# https://bitbucket.org/logilab/pylint/issues/701/false-positives-with-not-an-iterable-and

find varcode tests -name '*.py' \
| xargs pylint \
--errors-only \
--disable=unsubscriptable-object,not-an-iterable

echo 'Passes pylint check'
ruff check varcode tests
echo 'Passes ruff check'
242 changes: 242 additions & 0 deletions tests/test_effect_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from varcode.effects.effect_helpers import (
changes_exonic_splice_site,
variant_overlaps_interval,
)


class DummyTranscript(object):
def __init__(self, sequence, n_exons=2):
self.sequence = sequence
self.exons = [object()] * n_exons


def test_variant_overlaps_interval_single_base_at_interval_boundaries():
assert variant_overlaps_interval(
variant_start=3,
n_ref_bases=1,
interval_start=3,
interval_end=5)
assert variant_overlaps_interval(
variant_start=5,
n_ref_bases=1,
interval_start=3,
interval_end=5)


def test_variant_overlaps_interval_excludes_adjacent_bases():
assert not variant_overlaps_interval(
variant_start=2,
n_ref_bases=1,
interval_start=3,
interval_end=5)
assert not variant_overlaps_interval(
variant_start=6,
n_ref_bases=1,
interval_start=3,
interval_end=5)


def test_variant_overlaps_interval_spanning_variants():
assert variant_overlaps_interval(
variant_start=2,
n_ref_bases=2,
interval_start=3,
interval_end=5)
assert not variant_overlaps_interval(
variant_start=1,
n_ref_bases=2,
interval_start=3,
interval_end=5)


def test_variant_overlaps_interval_insertions():
assert variant_overlaps_interval(
variant_start=3,
n_ref_bases=0,
interval_start=3,
interval_end=5)
assert variant_overlaps_interval(
variant_start=5,
n_ref_bases=0,
interval_start=3,
interval_end=5)
assert not variant_overlaps_interval(
variant_start=2,
n_ref_bases=0,
interval_start=3,
interval_end=5)


def test_changes_exonic_splice_site_breaking_substitution_in_motif():
transcript = DummyTranscript("AATCAGAA")
result = changes_exonic_splice_site(
transcript_offset=4,
transcript=transcript,
transcript_ref="A",
transcript_alt="C",
exon_start_offset=0,
exon_end_offset=5,
exon_number=1)
assert result is True


def test_changes_exonic_splice_site_preserving_substitution_in_motif():
transcript = DummyTranscript("AATCAGAA")
result = changes_exonic_splice_site(
transcript_offset=3,
transcript=transcript,
transcript_ref="C",
transcript_alt="A",
exon_start_offset=0,
exon_end_offset=5,
exon_number=1)
assert result is None


def test_changes_exonic_splice_site_ignores_noncanonical_reference_motif():
# Exon-end motif is CTG (not MAG), so this path should not trigger.
transcript = DummyTranscript("AATCTGAA")
result = changes_exonic_splice_site(
transcript_offset=4,
transcript=transcript,
transcript_ref="T",
transcript_alt="A",
exon_start_offset=0,
exon_end_offset=5,
exon_number=1)
assert result is None


def test_changes_exonic_splice_site_skips_end_motif_check_for_last_exon():
# Last exon has no downstream splice donor boundary in this model.
transcript = DummyTranscript("AATCAGAA", n_exons=1)
result = changes_exonic_splice_site(
transcript_offset=4,
transcript=transcript,
transcript_ref="A",
transcript_alt="C",
exon_start_offset=0,
exon_end_offset=5,
exon_number=1)
assert result is None


def test_changes_exonic_splice_site_breaking_deletion_in_motif():
transcript = DummyTranscript("AATCAGAA")
result = changes_exonic_splice_site(
transcript_offset=4,
transcript=transcript,
transcript_ref="A",
transcript_alt="",
exon_start_offset=0,
exon_end_offset=5,
exon_number=1)
assert result is True


def test_changes_exonic_splice_site_preserving_insertion_in_motif():
transcript = DummyTranscript("AATCAGAA")
result = changes_exonic_splice_site(
transcript_offset=3,
transcript=transcript,
transcript_ref="",
transcript_alt="A",
exon_start_offset=0,
exon_end_offset=5,
exon_number=1)
assert result is None


def test_changes_exonic_splice_site_breaking_insertion_in_motif():
transcript = DummyTranscript("AATCAGAA")
result = changes_exonic_splice_site(
transcript_offset=3,
transcript=transcript,
transcript_ref="",
transcript_alt="T",
exon_start_offset=0,
exon_end_offset=5,
exon_number=1)
assert result is True


def test_changes_exonic_splice_site_breaking_acceptor_purine_substitution():
# Exon start base A->T for exon_number > 1 should break YAG|R.
transcript = DummyTranscript("CCATGGTT", n_exons=2)
result = changes_exonic_splice_site(
transcript_offset=2,
transcript=transcript,
transcript_ref="A",
transcript_alt="T",
exon_start_offset=2,
exon_end_offset=5,
exon_number=2)
assert result is True


def test_changes_exonic_splice_site_preserving_acceptor_purine_substitution():
transcript = DummyTranscript("CCATGGTT", n_exons=2)
result = changes_exonic_splice_site(
transcript_offset=2,
transcript=transcript,
transcript_ref="A",
transcript_alt="G",
exon_start_offset=2,
exon_end_offset=5,
exon_number=2)
assert result is None


def test_changes_exonic_splice_site_breaking_acceptor_deletion():
# Deleting the first exon base (A) reveals C (non-purine) at exon start.
transcript = DummyTranscript("CCACGGTT", n_exons=2)
result = changes_exonic_splice_site(
transcript_offset=2,
transcript=transcript,
transcript_ref="A",
transcript_alt="",
exon_start_offset=2,
exon_end_offset=5,
exon_number=2)
assert result is True


def test_changes_exonic_splice_site_preserving_acceptor_deletion():
# Deleting first exon base (A) reveals G (purine), preserving YAG|R.
transcript = DummyTranscript("CCAGGGTT", n_exons=2)
result = changes_exonic_splice_site(
transcript_offset=2,
transcript=transcript,
transcript_ref="A",
transcript_alt="",
exon_start_offset=2,
exon_end_offset=5,
exon_number=2)
assert result is None


def test_changes_exonic_splice_site_ignores_variant_before_splice_window():
# The canonical exon-end splice motif is at offsets 3..5: CAG.
# Variant is at offset 2, immediately before that window.
transcript = DummyTranscript("AATCAGAA")
result = changes_exonic_splice_site(
transcript_offset=2,
transcript=transcript,
transcript_ref="T",
transcript_alt="C",
exon_start_offset=0,
exon_end_offset=5,
exon_number=1)
assert result is None
Loading