Skip to content

Commit

Permalink
added check for deletion/insertion in range in `StrandBuilder.with_de…
Browse files Browse the repository at this point in the history
…letions` and `StrandBuilder.with_insertions`
  • Loading branch information
dave-doty committed Apr 12, 2022
1 parent f25a28f commit 00451a8
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 12 deletions.
13 changes: 13 additions & 0 deletions scadnano/scadnano.py
Original file line number Diff line number Diff line change
Expand Up @@ -2615,6 +2615,12 @@ def with_deletions(self,
else:
last_domain.deletions = list(deletions)

for deletion in last_domain.deletions:
if not last_domain.start <= deletion < last_domain.end:
raise IllegalDesignError(f'all deletions must be between start={last_domain.start} '
f'and end={last_domain.end}, but deletion={deletion} is outside '
f'that range')

return self

def with_insertions(self, insertions: Union[Tuple[int, int], Iterable[Tuple[int, int]]]) \
Expand Down Expand Up @@ -2660,6 +2666,13 @@ def with_insertions(self, insertions: Union[Tuple[int, int], Iterable[Tuple[int,
raise ValueError(type_msg)
last_domain.insertions = list(insertions)

for insertion in last_domain.insertions:
insertion_offset, _ = insertion
if not last_domain.start <= insertion_offset < last_domain.end:
raise IllegalDesignError(f'all insertions must be between start={last_domain.start} '
f'and end={last_domain.end}, but insertion={insertion} at offset '
f'{insertion_offset} is outside that range')

return self


Expand Down
47 changes: 35 additions & 12 deletions tests/scadnano_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,8 @@ def test_to_json__names_unique_for_modifications_raises_no_error(self) -> None:
helices = [sc.Helix(max_offset=100)]
design: sc.Design = sc.Design(helices=helices, strands=[], grid=sc.square)
name = 'mod_name'
design.draw_strand(0, 0).move(5).with_modification_5p(sc.Modification5Prime(display_text=name, id=name))
design.draw_strand(0, 0).move(5).with_modification_5p(
sc.Modification5Prime(display_text=name, id=name))
design.draw_strand(0, 5).move(5).with_modification_3p(
sc.Modification3Prime(display_text=name, id=name + '3'))
design.to_json(True)
Expand All @@ -389,8 +390,10 @@ def test_to_json__names_not_unique_for_modifications_raises_error(self) -> None:
helices = [sc.Helix(max_offset=100)]
design: sc.Design = sc.Design(helices=helices, strands=[], grid=sc.square)
name = 'mod_name'
design.draw_strand(0, 0).move(5).with_modification_5p(sc.Modification5Prime(display_text=name, id=name))
design.draw_strand(0, 5).move(5).with_modification_3p(sc.Modification3Prime(display_text=name, id=name))
design.draw_strand(0, 0).move(5).with_modification_5p(
sc.Modification5Prime(display_text=name, id=name))
design.draw_strand(0, 5).move(5).with_modification_3p(
sc.Modification3Prime(display_text=name, id=name))
with self.assertRaises(sc.IllegalDesignError):
design.to_json(True)

Expand Down Expand Up @@ -513,7 +516,7 @@ def test_biotin(self) -> None:

def test_to_json_serializable(self) -> None:
biotin5 = mod.biotin_5p
biotin5 = dataclasses.replace(biotin5, connector_length = 6)
biotin5 = dataclasses.replace(biotin5, connector_length=6)
self.assertEqual(r'/5Biosg/', biotin5.idt_text)
self.assertEqual(r'/5Biosg/', biotin5.id)
self.assertEqual('B', biotin5.display_text)
Expand Down Expand Up @@ -694,14 +697,13 @@ def test_paranemic_crossover(self) -> None:
#
design.write_scadnano_file(directory=self.output_path,
filename=f'{file_name}.{sc.default_scadnano_file_extension}')

def test_same_helix_crossover(self) -> None:
file_name = "test_paranemic_crossover"
design = sc.Design.from_cadnano_v2(directory=self.input_path,
filename=file_name + ".json")
self.assertEqual(4, len(design.helices))


def test_2_stape_2_helix_origami_deletions_insertions(self) -> None:
file_name = "test_2_stape_2_helix_origami_deletions_insertions"
design = sc.Design.from_cadnano_v2(directory=self.input_path,
Expand Down Expand Up @@ -1716,7 +1718,6 @@ def setUp(self) -> None:
strands=[],
grid=sc.square)


def test_no_deletion_after_loopout(self) -> None:
# not really a test of inlining, but I added the with_deletions and with_insertions to help these
# tests, so easier just to test this behavior here
Expand All @@ -1729,6 +1730,21 @@ def test_no_insertion_after_loopout(self) -> None:
with self.assertRaises(ValueError):
self.design.draw_strand(0, 0).move(8).loopout(0, 5, 10).with_insertions((4, 2))

def test_deletion_below_range(self) -> None:
with self.assertRaises(ValueError):
self.design.draw_strand(0, 4).move(4).with_deletions(2)

def test_deletion_above_range(self) -> None:
with self.assertRaises(ValueError):
self.design.draw_strand(0, 0).move(4).with_deletions(6)

def test_insertion_below_range(self) -> None:
with self.assertRaises(ValueError):
self.design.draw_strand(0, 4).move(4).with_insertions((2, 1))

def test_insertion_above_range(self) -> None:
with self.assertRaises(ValueError):
self.design.draw_strand(0, 0).move(4).with_insertions((6, 1))

def test_inline_deletions_insertions__one_deletion(self) -> None:
"""
Expand Down Expand Up @@ -2230,9 +2246,15 @@ def test_add_nick__small_design_H0_forward(self) -> None:
self.assertIn(sc.Strand([sc.Domain(0, True, 0, 8, dna_sequence='ACGTACGA')]), design.strands)
self.assertIn(sc.Strand([sc.Domain(0, True, 8, 16, dna_sequence='AACCGGTA')]), design.strands)
# existing Strands
self.assertIn(sc.Strand([sc.Domain(0, False, 0, 16, dna_sequence=remove_whitespace('TACCGGTT TCGTACGT'))]), design.strands)
self.assertIn(sc.Strand([sc.Domain(1, True, 0, 16, dna_sequence=remove_whitespace('AAACCCGG TTTGGGCC'))]), design.strands)
self.assertIn(sc.Strand([sc.Domain(1, False, 0, 16, dna_sequence=remove_whitespace('GGCCCAAA CCGGGTTT'))]), design.strands)
self.assertIn(
sc.Strand([sc.Domain(0, False, 0, 16, dna_sequence=remove_whitespace('TACCGGTT TCGTACGT'))]),
design.strands)
self.assertIn(
sc.Strand([sc.Domain(1, True, 0, 16, dna_sequence=remove_whitespace('AAACCCGG TTTGGGCC'))]),
design.strands)
self.assertIn(
sc.Strand([sc.Domain(1, False, 0, 16, dna_sequence=remove_whitespace('GGCCCAAA CCGGGTTT'))]),
design.strands)
# DNA
strand = strand_matching(design.strands, 0, True, 0, 8)
self.assertEqual(remove_whitespace('ACGTACGA'), strand.dna_sequence)
Expand Down Expand Up @@ -6315,7 +6337,7 @@ def test_helix_groups(self) -> None:
}
design = sc.Design(helices=helices, groups=groups)
design.draw_strand(0, 0).move(7).cross(1).move(-7)
design.draw_strand(2, 7).move(-7).cross(3).move(7) # unlike basic design, put strand on helices 2,3
design.draw_strand(2, 7).move(-7).cross(3).move(7) # unlike basic design, put strand on helices 2,3

# expected values for verification
expected_num_nucleotides = 7 * 4
Expand Down Expand Up @@ -6837,6 +6859,7 @@ def test_loopout_design(self) -> None:

self.assertAlmostEqual(self.EXPECTED_ADJ_NUC_CM_DIST2, sqr_dist2)


class TestPlateMaps(unittest.TestCase):

def setUp(self) -> None:
Expand Down Expand Up @@ -6866,4 +6889,4 @@ def test_plate_map_markdown(self) -> None:
| G | | | | | | | | | | | | |
| H | | | | | | | | | | | | |
""".strip()
self.assertEqual(expected_md, actual_md)
self.assertEqual(expected_md, actual_md)

0 comments on commit 00451a8

Please sign in to comment.