Spaces:
Running
Running
Download tests/test_clone_sim.py from WINTER4000/syntheogenesis: direct link, hf CLI and curl.
- Browser
- Download file 12.4 kB
-
https://huggingface.co/spaces/WINTER4000/syntheogenesis/resolve/main/tests/test_clone_sim.py
- Command line
-
hf download hf://spaces/WINTER4000/syntheogenesis/tests/test_clone_sim.py
-
curl -L -o test_clone_sim.py https://huggingface.co/spaces/WINTER4000/syntheogenesis/resolve/main/tests/test_clone_sim.py
12.4 kB
| """Unit tests for the cloning simulator (dee/core/cloning.py).""" | |
| import pytest | |
| from dee.core import cloning as C | |
| # ββ Gibson / overlap assembly βββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_gibson_native_overlaps_circular(): | |
| # three fragments sharing 20-bp overlaps, arranged head-to-tail in a circle. | |
| o1 = "ACGTACGTACGTACGTACGT" # AβB overlap | |
| o2 = "TTTTGGGGCCCCAAAATTTT" # BβC overlap | |
| o3 = "GGGGCCCCAAAATTTTGGGG" # CβA overlap | |
| A = o3 + "AAAAAAAAAAAA" + o1 | |
| B = o1 + "CCCCCCCCCCCC" + o2 | |
| Cc = o2 + "GGGGGGGGGGGG" + o3 | |
| out = C.gibson([{"name": "A", "seq": A}, {"name": "B", "seq": B}, {"name": "C", "seq": Cc}]) | |
| assert out["ok"] is True | |
| assert out["topology"] == "circular" | |
| assert all(j["kind"] == "native" for j in out["junctions"]) | |
| assert out["length"] == len(A) + len(B) + len(Cc) - 60 # minus three 20-bp overlaps | |
| def test_gibson_auto_orders_fragments(): | |
| o1 = "ACGTACGTACGTACGTACGT" | |
| o2 = "TTTTGGGGCCCCAAAATTTT" | |
| A = "AAAAAAAA" + o1 | |
| B = o1 + "CCCCCCCC" + o2 | |
| Cc = o2 + "GGGGGGGG" | |
| out = C.gibson([{"name": "C", "seq": Cc}, {"name": "A", "seq": A}, {"name": "B", "seq": B}], | |
| circular=False) | |
| assert out["ok"] is True | |
| assert out["order"] == ["A", "B", "C"] | |
| assert out["length"] == len(A) + len(B) + len(Cc) - 40 | |
| def test_gibson_designs_overlaps_and_primers_for_bare_fragments(): | |
| A = "ATGGTGAGCAAGGGCGAGGAGCTGTTCACC" | |
| B = "GACGAGCTGTACAAGTAACTCGAGCTAGCAT" | |
| out = C.gibson([{"name": "A", "seq": A}, {"name": "B", "seq": B}], circular=False) | |
| assert out["ok"] is True | |
| assert any(j["kind"] == "designed" for j in out["junctions"]) | |
| assert out["length"] == len(A) + len(B) | |
| prim = {p["fragment"]: p for p in out["primers"]} | |
| assert prim["B"]["forward_tail"] > 0 | |
| assert prim["B"]["forward"].startswith(A[-25:]) | |
| def test_gibson_needs_two_fragments(): | |
| assert C.gibson([{"name": "x", "seq": "ACGT"}])["ok"] is False | |
| # ββ Golden Gate ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _bsa_part(left_oh, body, right_oh): | |
| return "GAATT" + "GGTCTC" + "T" + left_oh + body + right_oh + "T" + "GAGACC" + "TTAAG" | |
| def test_excise_part_reads_overhangs(): | |
| p = C._excise_part(_bsa_part("AAAA", "GCTAGCGGGG", "CCCC"), "BsaI") | |
| assert p["left_oh"] == "AAAA" | |
| assert p["right_oh"] == "CCCC" | |
| assert p["inner"] == "AAAA" + "GCTAGCGGGG" + "CCCC" | |
| def test_golden_gate_assembles_circle(): | |
| p1 = _bsa_part("AAAA", "GCTAGCAAAA", "CCCC") | |
| p2 = _bsa_part("CCCC", "TTGGCCAATT", "AAAA") | |
| out = C.golden_gate([{"name": "P1", "seq": p1}, {"name": "P2", "seq": p2}], enzyme="BsaI") | |
| assert out["ok"] is True | |
| assert out["topology"] == "circular" | |
| assert out["length"] == (4 + len("GCTAGCAAAA")) + (4 + len("TTGGCCAATT")) | |
| assert out["order"] == ["P1", "P2"] | |
| def test_golden_gate_rejects_mismatched_overhangs(): | |
| p1 = _bsa_part("AAAA", "GCTAGCAAAA", "CCCC") | |
| p2 = _bsa_part("GGGG", "TTGGCCAATT", "AAAA") | |
| out = C.golden_gate([{"name": "P1", "seq": p1}, {"name": "P2", "seq": p2}], enzyme="BsaI") | |
| assert out["ok"] is False | |
| def test_golden_gate_rejects_non_cassette(): | |
| out = C.golden_gate([{"name": "P1", "seq": "ACGTACGTACGT"}, {"name": "P2", "seq": "TTTTGGGG"}]) | |
| assert out["ok"] is False | |
| # ββ restriction cut-and-ligate ββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_restriction_clone_single_enzyme(): | |
| vector = "AAAACCCC" + "GAATTC" + "TTTTGGGGCCCCAAAA" | |
| insert = "CC" + "GAATTC" + "ATGCATGCATGCATGC" + "GAATTC" + "GG" | |
| out = C.restriction_clone(vector, insert, "EcoRI", vector_topology="circular") | |
| assert out["ok"] is True | |
| assert out["topology"] == "circular" | |
| assert "ATGCATGCATGCATGC" in out["assembled"] | |
| assert out["length"] > len(vector) | |
| def test_restriction_clone_linear_splices_the_insert_in_at_the_cut(): | |
| # A single cut on a LINEAR vector produces two loose ends, not a seam to | |
| # close β the correct operation is splicing the insert IN at the cut | |
| # (left arm, insert, right arm), not gluing the two arms back together | |
| # unchanged and appending the insert onto the end (the original bug: a | |
| # silent "ok": True carrying the untouched vector plus a dangling tail, | |
| # mislabelled "topology": "circular" on top of that). | |
| left, right = "AAAACCCC", "TTTTGGGGCCCCAAAA" | |
| vector = left + "GAATTC" + right | |
| insert = "CC" + "GAATTC" + "ATGCATGCATGCATGC" + "GAATTC" + "GG" | |
| out = C.restriction_clone(vector, insert, "EcoRI", vector_topology="linear") | |
| assert out["ok"] is True | |
| assert out["topology"] == "linear" | |
| # The insert lands BETWEEN the two original arms β not after them. | |
| assert out["assembled"].startswith(left) | |
| assert out["assembled"].endswith(right) | |
| assert "ATGCATGCATGCATGC" in out["assembled"] | |
| insert_start = out["assembled"].index("ATGCATGCATGCATGC") | |
| assert insert_start < len(out["assembled"]) - len(right) | |
| # Nothing lost, nothing silently kept whole: length is vector + insert | |
| # fragment exactly, not vector + insert (no-op backbone) or vector alone. | |
| i0 = insert.index("GAATTC") | |
| i1 = insert.rindex("GAATTC") | |
| insert_frag_len = i1 - i0 | |
| assert out["length"] == len(vector) + insert_frag_len | |
| assert len(out["features"]) == 3 | |
| def test_restriction_clone_requires_unique_vector_site(): | |
| vector = "GAATTCAAAAGAATTC" | |
| insert = "GAATTCATGCGAATTC" | |
| out = C.restriction_clone(vector, insert, "EcoRI", vector_topology="linear") | |
| assert out["ok"] is False | |
| def test_restriction_clone_requires_flanked_insert(): | |
| vector = "AAAAGAATTCTTTT" | |
| insert = "ATGCATGCATGC" | |
| out = C.restriction_clone(vector, insert, "EcoRI") | |
| assert out["ok"] is False | |
| # ββ Yeast in-vivo (homologous-recombination) assembly βββββββββββββββββββββββ | |
| import random as _random | |
| def _ydna(rng, n): | |
| return "".join(rng.choice("ACGT") for _ in range(n)) | |
| def test_yeast_designs_arms_primers_and_plan(): | |
| rng = _random.Random(11) | |
| vec, gA, gB = _ydna(rng, 240), _ydna(rng, 180), _ydna(rng, 180) | |
| frags = [{"name": "pRS416 URA3 vector", "seq": vec}, | |
| {"name": "geneA", "seq": gA}, {"name": "geneB", "seq": gB}] | |
| r = C.yeast_assembly(frags, circular=True) | |
| assert r["ok"] and r["method"] == "yeast_hr" and r["topology"] == "circular" | |
| assert r["order"] == ["pRS416 URA3 vector", "geneA", "geneB"] | |
| # distinct fragments β every junction is a designed, UNIQUE homology arm | |
| assert all(j["kind"] == "designed" for j in r["junctions"]) | |
| assert all(j["unique"] for j in r["junctions"]) | |
| assert not any("share homology" in w for w in r["warnings"]) | |
| # chimeric primers carry a 40-bp homology tail | |
| assert any(p["forward_tail"] == C.YEAST_ARM for p in r["primers"]) | |
| # marker detected from the vector name; plan + one verification pair per junction | |
| assert r["marker"] == "pRS416 URA3 vector" | |
| assert set(r["plan"]) == {"transform", "selection", "verify"} | |
| assert len(r["verification"]) == len(r["junctions"]) | |
| assert all(v["forward"] and v["reverse"] for v in r["verification"]) | |
| def test_yeast_flags_repeated_terminator_as_misassembly_risk(): | |
| rng = _random.Random(12) | |
| term = _ydna(rng, 45) # a terminator reused on two genes | |
| vec = _ydna(rng, 220) | |
| gA = _ydna(rng, 150) + term | |
| gB = _ydna(rng, 150) + term # SAME terminator at the 3' end | |
| frags = [{"name": "vec LEU2", "seq": vec}, | |
| {"name": "geneA-tCYC1", "seq": gA}, {"name": "geneB-tCYC1", "seq": gB}] | |
| r = C.yeast_assembly(frags, circular=True) | |
| assert r["ok"] | |
| # the two junctions reading off the repeated terminator share homology | |
| assert sum(1 for j in r["junctions"] if not j["unique"]) >= 2 | |
| assert any("share homology" in w for w in r["warnings"]) | |
| def test_yeast_missing_marker_warns_and_can_be_named(): | |
| rng = _random.Random(13) | |
| frags = [{"name": "backbone", "seq": _ydna(rng, 200)}, | |
| {"name": "insert", "seq": _ydna(rng, 200)}] | |
| r = C.yeast_assembly(frags, circular=True) | |
| assert r["marker"] is None | |
| assert any("selectable marker" in w for w in r["warnings"]) | |
| r2 = C.yeast_assembly(frags, circular=True, marker="HIS3") | |
| assert r2["marker"] == "HIS3" | |
| assert not any("selectable marker" in w for w in r2["warnings"]) | |
| # ββ Yeast chromosomal integration (knock-in / knock-out) ββββββββββββββββββββ | |
| def test_yeast_integration_designs_cassette_arms_and_verification(): | |
| rng = _random.Random(21) | |
| up = _ydna(rng, 300) # genomic sequence 5' of the site | |
| dn = _ydna(rng, 300) # genomic sequence 3' of the site | |
| cassette = _ydna(rng, 1400) # a marker cassette to drop in | |
| r = C.yeast_integration(up, dn, cassette, marker="KanMX") | |
| assert r["ok"] and r["method"] == "yeast_integration" and r["topology"] == "linear" | |
| # the integrated allele is flank + cassette + flank, nothing lost | |
| assert r["assembled"] == up + cassette + dn | |
| assert r["length"] == len(up) + len(cassette) + len(dn) | |
| # the cassette is PCR'd with genomic-homology tails: fwd carries up's 3' arm | |
| p = r["cassette_primers"][0] | |
| assert p["forward_tail"] == r["arm"] == C.YEAST_ARM | |
| assert p["forward"].startswith(up[-C.YEAST_ARM:]) | |
| assert p["reverse"].startswith(C.revcomp(dn[:C.YEAST_ARM])) | |
| # two designed junctions, both unique for distinct random flanks | |
| assert len(r["junctions"]) == 2 | |
| assert all(j["kind"] == "designed" and j["unique"] for j in r["junctions"]) | |
| assert r["marker"] == "KanMX" | |
| assert set(r["plan"]) == {"transform", "selection", "verify"} | |
| def test_yeast_integration_verification_primer_sits_outside_the_arm(): | |
| """On-target confirmation: the 5' check primer anneals in the flanking | |
| genome OUTSIDE the homology arm, and the 3' check primer OUTSIDE the 3' | |
| arm. A primer inside the arm (or inside the cassette) would give a band at | |
| any integration site and prove nothing.""" | |
| rng = _random.Random(22) | |
| up, dn, cassette = _ydna(rng, 300), _ydna(rng, 300), _ydna(rng, 1200) | |
| r = C.yeast_integration(up, dn, cassette, marker="HIS3") | |
| v5, v3 = r["verification"] | |
| arm = r["arm"] | |
| # the 5' forward primer is sourced from genome 5' of the arm | |
| assert v5["forward"] and v5["forward"] in up[:-arm] | |
| assert v5["forward"] not in up[-arm:] | |
| # the 3' reverse primer anneals to genome 3' of the arm (bottom strand) | |
| assert v3["reverse"] and C.revcomp(v3["reverse"]) in dn[arm:] | |
| assert "OUTSIDE" in v5["note"] and "OUTSIDE" in v3["note"] | |
| def test_yeast_integration_flags_arms_that_share_homology(): | |
| rng = _random.Random(23) | |
| shared = _ydna(rng, 26) # >= 20 bp present in BOTH arms | |
| up = _ydna(rng, 220) + shared # up[-40:] ends with `shared` | |
| dn = shared + _ydna(rng, 220) # dn[:40] starts with `shared` | |
| r = C.yeast_integration(up, dn, _ydna(rng, 800), marker="URA3") | |
| assert r["ok"] | |
| assert all(j["unique"] is False for j in r["junctions"]) | |
| assert any("share sequence" in w for w in r["warnings"]) | |
| def test_yeast_integration_always_says_to_blast_the_arms(): | |
| """The engine cannot see the genome, so off-target risk is never silently | |
| cleared β it is always surfaced as a thing the user must check.""" | |
| rng = _random.Random(24) | |
| r = C.yeast_integration(_ydna(rng, 250), _ydna(rng, 250), _ydna(rng, 900), marker="LEU2") | |
| assert any("BLAST" in w for w in r["warnings"]) | |
| def test_yeast_integration_rejects_flanks_shorter_than_the_arm(): | |
| rng = _random.Random(25) | |
| r = C.yeast_integration(_ydna(rng, 20), _ydna(rng, 250), _ydna(rng, 400)) | |
| assert r["ok"] is False | |
| assert "homology arm" in r["error"] | |
| def test_yeast_integration_warns_when_no_marker_named(): | |
| rng = _random.Random(26) | |
| r = C.yeast_integration(_ydna(rng, 250), _ydna(rng, 250), _ydna(rng, 400)) | |
| assert r["marker"] is None | |
| assert any("marker" in w for w in r["warnings"]) | |