Spaces:
Running
Running
| """Base-editing design (dee/core/base_editor.py). | |
| Pins the editor library, sequence-only edit prediction (incl. bystander | |
| detection), and β most importantly β the frame-aware codon consequence, | |
| which is what tells a customer "this guide installs a premature stop". | |
| A wrong AA call here would be as damaging as a wrong off-target, so the | |
| codon math is tested against hand-verified examples and is required to | |
| return None rather than guess when the alignment doesn't check out. | |
| """ | |
| import pytest | |
| from dee.core import base_editor as BE | |
| # βββββββββββββββββββββββββββ editor library βββββββββββββββββββββββββββ | |
| def test_library_shape(): | |
| be4 = BE.get_base_editor("be4max") | |
| assert be4.kind == "CBE" and be4.target_base == "C" and be4.result_base == "T" | |
| assert be4.window == (4, 8) | |
| abe = BE.get_base_editor("abe7.10") | |
| assert abe.kind == "ABE" and abe.target_base == "A" and abe.result_base == "G" | |
| assert BE.get_base_editor("nope") is None | |
| def test_list_filters_by_kind(): | |
| assert {e.id for e in BE.list_base_editors("CBE")} == {"be4max", "be3", "evocda_be4max"} | |
| assert {e.id for e in BE.list_base_editors("ABE")} == {"abe8e", "abe7.10"} | |
| def test_activity_profiles_bounded_and_peaked(): | |
| for ed in BE.list_base_editors(): | |
| lo, hi = ed.window | |
| assert set(ed.activity) == set(range(lo, hi + 1)) | |
| assert all(0.0 <= v <= 1.0 for v in ed.activity.values()) | |
| assert max(ed.activity.values()) == pytest.approx(1.0) | |
| # βββββββββββββββββββββββββββ edit prediction βββββββββββββββββββββββββββ | |
| def test_single_cbe_edit_in_window(): | |
| spacer = "AAAC" + "A" * 16 # C at position 4 only | |
| p = BE.predict_base_edits(spacer, "be4max") | |
| assert p.n_targets == 1 | |
| assert p.edits[0][:3] == (4, "C", "T") | |
| assert p.editability == pytest.approx(0.35) # window edge (pos 4) | |
| assert p.has_bystander is False | |
| assert p.summary == "C4βT" | |
| def test_bystander_flagged_and_editability_is_strongest(): | |
| spacer = "AAACA" + "C" + "A" * 14 # C at positions 4 and 6 | |
| p = BE.predict_base_edits(spacer, "be4max") | |
| assert p.n_targets == 2 | |
| assert p.has_bystander is True | |
| assert p.editability == pytest.approx(1.0) # pos 6 is the window peak | |
| assert "bystander" in p.summary | |
| def test_no_target_in_window(): | |
| p = BE.predict_base_edits("A" * 20, "be4max") | |
| assert p.n_targets == 0 and p.editability == 0.0 | |
| assert "no C" in p.summary | |
| def test_abe_targets_adenine(): | |
| spacer = "GGGA" + "G" * 16 # A at position 4 | |
| p = BE.predict_base_edits(spacer, "abe7.10") | |
| assert p.n_targets == 1 | |
| assert p.edits[0][:3] == (4, "A", "G") | |
| def test_unknown_editor_returns_empty(): | |
| p = BE.predict_base_edits("ACGT" * 5, "not_an_editor") | |
| assert p.n_targets == 0 and p.edits == [] | |
| # βββββββββββββββββββββββββββ codon consequence βββββββββββββββββββββββββββ | |
| def test_istop_cga_to_tga(): | |
| # CGA (Arg) --CβT at codon position 1--> TGA (stop). The CRISPR-STOP move. | |
| c = BE.codon_consequence("CGA" + "GGG", 0, "C", "T") | |
| assert c.creates_stop is True | |
| assert c.kind == "nonsense" | |
| assert (c.aa_before, c.aa_after) == ("R", "*") | |
| assert c.label == "R1* (stop gained)" | |
| def test_all_cbe_istop_codons(codon, aa): | |
| # The three codons CBE can turn into a stop by a single CβT. | |
| c = BE.codon_consequence(codon + "TTT", 0, "C", "T") | |
| assert c.creates_stop and c.aa_before == aa and c.aa_after == "*" | |
| def test_silent_edit(): | |
| # GCT (Ala) --TβC at position 3--> GCC (still Ala). | |
| c = BE.codon_consequence("GCT" + "AAA", 2, "T", "C") | |
| assert c.kind == "silent" and c.aa_before == c.aa_after == "A" | |
| assert "silent" in c.label | |
| def test_missense_edit(): | |
| # GCT (Ala) --GβA at position 1--> ACT (Thr). | |
| c = BE.codon_consequence("GCT" + "AAA", 0, "G", "A") | |
| assert c.kind == "missense" and (c.aa_before, c.aa_after) == ("A", "T") | |
| def test_stop_loss_edit(): | |
| # TGA (stop) --AβG at position 3--> TGG (Trp). | |
| c = BE.codon_consequence("TGA" + "GCT", 2, "A", "G") | |
| assert c.kind == "stop_loss" and c.creates_stop is False | |
| assert (c.aa_before, c.aa_after) == ("*", "W") | |
| def test_residue_numbering_uses_codon_index(): | |
| # Second codon β residue 2. | |
| c = BE.codon_consequence("GGG" + "CGA", 3, "C", "T") | |
| assert c.label == "R2* (stop gained)" | |
| def test_refuses_to_guess_on_mismatch_or_out_of_range(): | |
| # ref_base disagrees with the CDS β None (no fabricated consequence). | |
| assert BE.codon_consequence("GCT", 0, "A", "T") is None | |
| # index past the end β None. | |
| assert BE.codon_consequence("GCT", 99, "T", "C") is None | |
| # incomplete trailing codon β None. | |
| assert BE.codon_consequence("GC", 0, "G", "A") is None | |
| # βββββββββββββββ integration: find_guides(mode="base_edit") βββββββββββββββ | |
| # Locks the full path β edit prediction + coordinate mapping (both strands) | |
| # + frame auto-detection + codon translation β against a fixed in-frame CDS. | |
| from dee.core import crispr as C # noqa: E402 | |
| _CDS = "".join([ | |
| "ATG", "GCT", "CGA", "GAT", "CAG", "GGC", "CGT", "CAA", "GCC", "TGG", | |
| "CGC", "GAA", "CGG", "CTG", "CGA", "GCA", "CAG", "GAT", "CGT", "TAA", | |
| ]) # 60 nt, starts ATG, ends TAA, no internal stop β auto-detected frame | |
| def test_base_edit_mode_populates_fields_and_ranks_by_editability(): | |
| guides = C.find_guides(_CDS, enzyme="cas9", max_results=200, | |
| mode="base_edit", base_editor="be4max") | |
| assert guides | |
| for g in guides: | |
| assert g.be_editor == "be4max" | |
| assert 0.0 <= g.be_editability <= 1.0 | |
| eds = [g.be_editability for g in guides] | |
| assert eds == sorted(eds, reverse=True) # editability-first ranking | |
| def test_base_edit_detects_istop_on_forward_strand(): | |
| guides = C.find_guides(_CDS, enzyme="cas9", max_results=200, | |
| mode="base_edit", base_editor="be4max") | |
| stops = {g.be_aa_change for g in guides if g.be_creates_stop} | |
| # CAG (Q) β TAG (*) via a CβT in the window, at residues 5 and 8. | |
| assert "Q5* (stop gained)" in stops | |
| assert "Q8* (stop gained)" in stops | |
| for g in guides: | |
| if g.be_creates_stop: | |
| assert g.be_aa_change.endswith("* (stop gained)") | |
| def test_base_edit_reverse_strand_aa_mapping(): | |
| # A '-' strand guide editing the coding-strand GβA: validates the | |
| # reverse-strand coordinate + complement transform end-to-end. | |
| guides = C.find_guides(_CDS, enzyme="cas9", max_results=200, | |
| mode="base_edit", base_editor="be4max") | |
| changes = {g.be_aa_change for g in guides if g.strand == "-"} | |
| assert "E12K" in changes | |
| def test_base_edit_flags_bystander(): | |
| guides = C.find_guides(_CDS, enzyme="cas9", max_results=200, | |
| mode="base_edit", base_editor="be4max") | |
| assert any(g.be_has_bystander for g in guides) | |
| def test_no_aa_change_without_frame(): | |
| # A random (non-CDS) sequence β edits predicted, but no AA call. | |
| import random | |
| rng = random.Random(11) | |
| seq = "".join(rng.choice("ACGT") for _ in range(400)) | |
| guides = C.find_guides(seq, enzyme="cas9", max_results=50, | |
| mode="base_edit", base_editor="be4max") | |
| assert guides | |
| assert all(g.be_aa_change == "" for g in guides) # frame unknown β no guess | |