Skip to content

Commit

Permalink
Add UT for chained methods for DNA extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
UnHumbleBen committed Apr 15, 2022
1 parent d482066 commit 4e60f9b
Show file tree
Hide file tree
Showing 2 changed files with 203 additions and 16 deletions.
22 changes: 19 additions & 3 deletions scadnano/scadnano.py
Original file line number Diff line number Diff line change
Expand Up @@ -2045,6 +2045,16 @@ def get_seq_start_idx(self) -> int:
return self_seq_idx_start


@dataclass
class Extension(_JSONSerializable, Generic[DomainLabel]):
length: int
label: Optional[DomainLabel] = None
name: Optional[str] = None
dna_sequence: Optional[str] = None

def to_json_serializable(self, suppress_indent: bool = True, **kwargs: Any) -> Union[Dict[str, Any], NoIndent]:
raise NotImplementedError()

_wctable = str.maketrans('ACGTacgt', 'TGCAtgca')


Expand Down Expand Up @@ -2225,6 +2235,12 @@ def loopout(self, helix: int, length: int, offset: Optional[int] = None, move: O
self.design.append_domain(self._strand, Loopout(length))
return self

def extension(self, length: int) -> 'StrandBuilder[StrandLabel, DomainLabel]':
"""
TODO: write doc
"""
return self

# remove quotes when Py3.6 support dropped
def move(self, delta: int) -> 'StrandBuilder[StrandLabel, DomainLabel]':
"""
Expand Down Expand Up @@ -2613,7 +2629,7 @@ class Strand(_JSONSerializable, Generic[StrandLabel, DomainLabel]):
uses for the scaffold.
"""

domains: List[Union[Domain[DomainLabel], Loopout[DomainLabel]]]
domains: List[Union[Domain[DomainLabel], Loopout[DomainLabel], Extension[DomainLabel]]]
""":any:`Domain`'s (or :any:`Loopout`'s) composing this Strand.
Each :any:`Domain` is contiguous on a single :any:`Helix`
and could be either single-stranded or double-stranded,
Expand Down Expand Up @@ -2707,7 +2723,7 @@ def dna_sequence(self) -> Optional[str]:
init=False, repr=False, compare=False, default_factory=dict)

def __init__(self,
domains: List[Union[Domain[DomainLabel], Loopout[DomainLabel]]],
domains: List[Union[Domain[DomainLabel], Loopout[DomainLabel], Extension[DomainLabel]]],
circular: bool = False, color: Optional[Color] = None,
idt: Optional[IDTFields] = None,
is_scaffold: bool = False, modification_5p: Optional[Modification5Prime] = None,
Expand Down Expand Up @@ -6027,7 +6043,7 @@ def move_strands_on_helices(self, delta: int) -> None:
self._check_strands_reference_helices_legally()

def assign_dna(self, strand: Strand, sequence: str, assign_complement: bool = True,
domain: Union[Domain, Loopout] = None, check_length: bool = False) -> None:
domain: Union[Domain, Loopout, Extension] = None, check_length: bool = False) -> None:
"""
Assigns `sequence` as DNA sequence of `strand`.
Expand Down
197 changes: 184 additions & 13 deletions tests/scadnano_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,177 @@ def test_strand__loopouts_with_labels_to_json(self) -> None:
self.assertEqual(1, len(design_from_json.strands))
self.assertEqual(expected_strand, design_from_json.strands[0])

def test_strand__extension_3p_from_to(self) -> None:
design: sc.Design = sc.Design(helices=[sc.Helix(max_offset=100)])
sb = design.draw_strand(0, 0)
sb.to(10)

sb.extension(5)

expected_strand: sc.Strand = sc.Strand([
sc.Domain(0, True, 0, 10),
sc.Extension(5),
])
self.assertEqual(1, len(design.strands))
self.assertEqual(expected_strand, design.strands[0])

def test_strand__extension_5p(self) -> None:
design: sc.Design = sc.Design(helices=[sc.Helix(max_offset=100)])
sb = design.draw_strand(0, 0)

sb.extension(5)
sb.to(10)

expected_strand: sc.Strand = sc.Strand([
sc.Extension(5),
sc.Domain(0, True, 0, 10),
])

self.assertEqual(1, len(design.strands))
self.assertEqual(expected_strand, design.strands[0])

def test_strand__update_to_after_extension_5p_ok(self) -> None:
design: sc.Design = sc.Design(helices=[sc.Helix(max_offset=100)])
sb = design.draw_strand(0, 0)

sb.extension(5)
sb.to(10)
sb.update_to(15)

expected_strand: sc.Strand = sc.Strand([
sc.Extension(5),
sc.Domain(0, True, 0, 15),
])

self.assertEqual(1, len(design.strands))
self.assertEqual(expected_strand, design.strands[0])

def test_strand__lone_extension_should_not_add_strand(self) -> None:
design: sc.Design = sc.Design(helices=[sc.Helix(max_offset=100)])
sb = design.draw_strand(0, 0)

sb.extension(5)

self.assertEqual(0, len(design.strands))

def test_strand__to_after_non_5p_extension_should_raise_error(self) -> None:
design: sc.Design = sc.Design(helices=[sc.Helix(max_offset=100)])
sb = design.draw_strand(0, 0)
sb.to(10)
sb.extension(5)

with self.assertRaises(sc.IllegalDesignError):
sb.to(15)

def test_strand__cross_after_extension_should_raise_error(self) -> None:
design: sc.Design = sc.Design(helices=[sc.Helix(max_offset=100)])
sb = design.draw_strand(0, 0)
sb.extension(5)

with self.assertRaises(sc.IllegalDesignError):
sb.cross(1)

def test_strand__extension_after_loopout_should_raise_error(self) -> None:
design: sc.Design = sc.Design(helices=[sc.Helix(max_offset=100)])
sb = design.draw_strand(0, 0)
sb.to(10)
sb.loopout(1, 3)

with self.assertRaises(sc.IllegalDesignError):
sb.extension(5)

def test_strand__extension_after_extension_should_raise_error(self) -> None:
design: sc.Design = sc.Design(helices=[sc.Helix(max_offset=100)])
sb = design.draw_strand(0, 0)
sb.to(10)
sb.extension(4)

with self.assertRaises(sc.IllegalDesignError):
sb.extension(5)

def test_strand__update_to_after_non_5p_extension_should_raise_error(self) -> None:
design: sc.Design = sc.Design(helices=[sc.Helix(max_offset=100)])
sb = design.draw_strand(0, 0)
sb.to(10)
sb.extension(4)

with self.assertRaises(sc.IllegalDesignError):
sb.update_to(15)

def test_strand__as_circular_with_extension_should_raise_error(self) -> None:
design: sc.Design = sc.Design(helices=[sc.Helix(max_offset=100)])
sb = design.draw_strand(0, 0)
sb.to(10)
sb.extension(4)

with self.assertRaises(sc.IllegalDesignError):
sb.as_circular()

def test_strand__extension_on_circular_strand_should_raise_error(self) -> None:
design: sc.Design = sc.Design(helices=[sc.Helix(max_offset=100)])
sb = design.draw_strand(0, 0)
sb.to(10)
sb.as_circular()

with self.assertRaises(sc.IllegalDesignError):
sb.extension(4)

def test_strand__extension_with_label(self) -> None:
design: sc.Design = sc.Design(helices=[sc.Helix(max_offset=100)])
sb = design.draw_strand(0, 0)
sb.to(10)
sb.extension(5)
sb.with_domain_label("ext1")

expected_strand: sc.Strand = sc.Strand([
sc.Domain(0, True, 0, 10),
sc.Extension(5, label="ext1"),
])
self.assertEqual(1, len(design.strands))
self.assertEqual(expected_strand, design.strands[0])

def test_strand__with_sequence_on_extension(self) -> None:
design: sc.Design = sc.Design(helices=[sc.Helix(max_offset=100)])
sb = design.draw_strand(0, 0)
sb.to(10)
sb.extension(5)
sb.with_sequence("A"*10 + "G"*5)

expected_strand: sc.Strand = sc.Strand([
sc.Domain(0, True, 0, 10, dna_sequence="A"*10),
sc.Extension(5, dna_sequence="G"*5),
])
self.assertEqual(1, len(design.strands))
self.assertEqual(expected_strand, design.strands[0])

def test_strand__with_domain_sequence_on_extension(self) -> None:
design: sc.Design = sc.Design(helices=[sc.Helix(max_offset=100)])
sb = design.draw_strand(0, 0)
sb.to(10)
sb.extension(5)
sb.with_domain_sequence("G"*5)

expected_strand: sc.Strand = sc.Strand([
sc.Domain(0, True, 0, 10, dna_sequence="?"*10),
sc.Extension(5, dna_sequence="G"*5),
])
self.assertEqual(1, len(design.strands))
self.assertEqual(expected_strand, design.strands[0])

def test_strand__extension_with_name(self) -> None:
design: sc.Design = sc.Design(helices=[sc.Helix(max_offset=100)])
sb = design.draw_strand(0, 0)
sb.to(10)
sb.extension(5)
sb.with_domain_name("ext1")

expected_strand: sc.Strand = sc.Strand([
sc.Domain(0, True, 0, 10),
sc.Extension(5, name="ext1"),
])
self.assertEqual(1, len(design.strands))
self.assertEqual(expected_strand, design.strands[0])

def test_strand__0_0_to_10_cross_1_to_5(self) -> None:
design = self.design_6helix
sb = design.draw_strand(0, 0)
Expand Down Expand Up @@ -1709,9 +1880,9 @@ def test_inline_deletions_insertions__one_deletion(self) -> None:
strands=[sc.Strand([sc.Domain(0, True, 0, 8, deletions=[4])])],
grid=sc.square)
design.inline_deletions_insertions()
self.helix0_strand0_inlined_test(design, max_offset=23, major_ticks=[0, 7, 15, 23], start=0, end=7)
self.assert_helix0_strand0_inlined(design, max_offset=23, major_ticks=[0, 7, 15, 23], start=0, end=7)

def helix0_strand0_inlined_test(self, design, max_offset, major_ticks, start, end):
def assert_helix0_strand0_inlined(self, design, max_offset, major_ticks, start, end):
self.assertEqual(1, len(design.helices))
self.assertEqual(1, len(design.strands))
helix = design.helices[0]
Expand Down Expand Up @@ -1740,7 +1911,7 @@ def test_inline_deletions_insertions__two_deletions(self) -> None:
strands=[sc.Strand([sc.Domain(0, True, 0, 8, deletions=[2, 4])])],
grid=sc.square)
design.inline_deletions_insertions()
self.helix0_strand0_inlined_test(design, max_offset=22, major_ticks=[0, 6, 14, 22], start=0, end=6)
self.assert_helix0_strand0_inlined(design, max_offset=22, major_ticks=[0, 6, 14, 22], start=0, end=6)

def test_inline_deletions_insertions__one_insertion(self) -> None:
"""
Expand All @@ -1759,7 +1930,7 @@ def test_inline_deletions_insertions__one_insertion(self) -> None:
strands=[sc.Strand([sc.Domain(0, True, 0, 8, insertions=[(4, 1)])])],
grid=sc.square)
design.inline_deletions_insertions()
self.helix0_strand0_inlined_test(design, max_offset=25, major_ticks=[0, 9, 17, 25], start=0, end=9)
self.assert_helix0_strand0_inlined(design, max_offset=25, major_ticks=[0, 9, 17, 25], start=0, end=9)

def test_inline_deletions_insertions__two_insertions(self) -> None:
"""
Expand All @@ -1778,7 +1949,7 @@ def test_inline_deletions_insertions__two_insertions(self) -> None:
strands=[sc.Strand([sc.Domain(0, True, 0, 8, insertions=[(2, 3), (4, 1)])])],
grid=sc.square)
design.inline_deletions_insertions()
self.helix0_strand0_inlined_test(design, max_offset=28, major_ticks=[0, 12, 20, 28], start=0, end=12)
self.assert_helix0_strand0_inlined(design, max_offset=28, major_ticks=[0, 12, 20, 28], start=0, end=12)

def test_inline_deletions_insertions__one_deletion_one_insertion(self) -> None:
"""
Expand All @@ -1797,7 +1968,7 @@ def test_inline_deletions_insertions__one_deletion_one_insertion(self) -> None:
strands=[sc.Strand([sc.Domain(0, True, 0, 8, deletions=[4], insertions=[(2, 3)])])],
grid=sc.square)
design.inline_deletions_insertions()
self.helix0_strand0_inlined_test(design, max_offset=26, major_ticks=[0, 10, 18, 26], start=0, end=10)
self.assert_helix0_strand0_inlined(design, max_offset=26, major_ticks=[0, 10, 18, 26], start=0, end=10)

def test_inline_deletions_insertions__one_deletion_right_of_major_tick(self) -> None:
"""
Expand All @@ -1816,7 +1987,7 @@ def test_inline_deletions_insertions__one_deletion_right_of_major_tick(self) ->
strands=[sc.Strand([sc.Domain(0, True, 0, 12, deletions=[9])])],
grid=sc.square)
design.inline_deletions_insertions()
self.helix0_strand0_inlined_test(design, max_offset=23, major_ticks=[0, 8, 15, 23], start=0, end=11)
self.assert_helix0_strand0_inlined(design, max_offset=23, major_ticks=[0, 8, 15, 23], start=0, end=11)

def test_inline_deletions_insertions__one_deletion_on_major_tick(self) -> None:
"""
Expand All @@ -1836,7 +2007,7 @@ def test_inline_deletions_insertions__one_deletion_on_major_tick(self) -> None:
strands=[sc.Strand([sc.Domain(0, True, 0, 12, deletions=[8])])],
grid=sc.square)
design.inline_deletions_insertions()
self.helix0_strand0_inlined_test(design, max_offset=23, major_ticks=[0, 8, 15, 23], start=0, end=11)
self.assert_helix0_strand0_inlined(design, max_offset=23, major_ticks=[0, 8, 15, 23], start=0, end=11)

def test_inline_deletions_insertions__one_deletion_left_of_major_tick(self) -> None:
"""
Expand All @@ -1855,7 +2026,7 @@ def test_inline_deletions_insertions__one_deletion_left_of_major_tick(self) -> N
strands=[sc.Strand([sc.Domain(0, True, 0, 12, deletions=[7])])],
grid=sc.square)
design.inline_deletions_insertions()
self.helix0_strand0_inlined_test(design, max_offset=23, major_ticks=[0, 7, 15, 23], start=0, end=11)
self.assert_helix0_strand0_inlined(design, max_offset=23, major_ticks=[0, 7, 15, 23], start=0, end=11)

def test_inline_deletions_insertions__one_insertion_right_of_major_tick(self) -> None:
"""
Expand All @@ -1874,7 +2045,7 @@ def test_inline_deletions_insertions__one_insertion_right_of_major_tick(self) ->
strands=[sc.Strand([sc.Domain(0, True, 0, 12, insertions=[(9, 1)])])],
grid=sc.square)
design.inline_deletions_insertions()
self.helix0_strand0_inlined_test(design, max_offset=25, major_ticks=[0, 8, 17, 25], start=0, end=13)
self.assert_helix0_strand0_inlined(design, max_offset=25, major_ticks=[0, 8, 17, 25], start=0, end=13)

def test_inline_deletions_insertions__one_insertion_on_major_tick(self) -> None:
"""
Expand All @@ -1893,7 +2064,7 @@ def test_inline_deletions_insertions__one_insertion_on_major_tick(self) -> None:
strands=[sc.Strand([sc.Domain(0, True, 0, 12, insertions=[(8, 1)])])],
grid=sc.square)
design.inline_deletions_insertions()
self.helix0_strand0_inlined_test(design, max_offset=25, major_ticks=[0, 8, 17, 25], start=0, end=13)
self.assert_helix0_strand0_inlined(design, max_offset=25, major_ticks=[0, 8, 17, 25], start=0, end=13)

def test_inline_deletions_insertions__one_insertion_left_of_major_tick(self) -> None:
"""
Expand All @@ -1912,7 +2083,7 @@ def test_inline_deletions_insertions__one_insertion_left_of_major_tick(self) ->
strands=[sc.Strand([sc.Domain(0, True, 0, 12, insertions=[(7, 1)])])],
grid=sc.square)
design.inline_deletions_insertions()
self.helix0_strand0_inlined_test(design, max_offset=25, major_ticks=[0, 9, 17, 25], start=0, end=13)
self.assert_helix0_strand0_inlined(design, max_offset=25, major_ticks=[0, 9, 17, 25], start=0, end=13)

def test_inline_deletions_insertions__deletions_insertions_in_multiple_domains(self) -> None:
"""
Expand All @@ -1931,7 +2102,7 @@ def test_inline_deletions_insertions__deletions_insertions_in_multiple_domains(s
strands=[sc.Strand([sc.Domain(0, True, 0, 24, deletions=[19], insertions=[(5, 2), (11, 1)])])],
grid=sc.square)
design.inline_deletions_insertions()
self.helix0_strand0_inlined_test(design, max_offset=26, major_ticks=[0, 10, 19, 26], start=0, end=26)
self.assert_helix0_strand0_inlined(design, max_offset=26, major_ticks=[0, 10, 19, 26], start=0, end=26)

def test_inline_deletions_insertions__deletions_insertions_in_multiple_domains_two_strands(self) -> None:
"""
Expand Down

0 comments on commit 4e60f9b

Please sign in to comment.