Skip to content

Commit 2c58e55

Browse files
authored
Gateway primer design (#234)
* first version * remove unused code
1 parent 9522172 commit 2c58e55

File tree

5 files changed

+162
-26
lines changed

5 files changed

+162
-26
lines changed

gateway.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,15 @@ def dseqrecord_finditer(pattern: str, seq: _Dseqrecord) -> list[re.Match]:
8888
for k, v in raw_gateway_sites_conservative.items()
8989
}
9090

91+
# From snapgene - ask Valerie
92+
primer_design_attB = {
93+
'attB1': 'ACAAGTTTGTACAAAAAAGCAGGCT',
94+
'attB2': 'ACCACTTTGTACAAGAAAGCTGGGT',
95+
'attB3': 'ACAACTTTGTATAATAAAGTTGTA',
96+
'attB4': 'ACAACTTTGTATAGAAAAGTTGTA',
97+
'attB5': 'ACAACTTTGTATACAAAAGTTGTA',
98+
}
99+
91100

92101
def gateway_overlap(seqx: _Dseqrecord, seqy: _Dseqrecord, reaction: str, greedy: bool) -> list[tuple[int, int, int]]:
93102
"""Find gateway overlaps"""

main.py

Lines changed: 60 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,11 @@
7373
import os
7474
from record_stub_route import RecordStubRoute
7575
from starlette.responses import RedirectResponse
76-
from primer_design import homologous_recombination_primers, gibson_assembly_primers, simple_pair_primers
76+
from primer_design import (
77+
homologous_recombination_primers,
78+
gibson_assembly_primers,
79+
simple_pair_primers,
80+
)
7781
import asyncio
7882
import re
7983
import warnings
@@ -1171,10 +1175,10 @@ def annotate(x):
11711175
return resp
11721176

11731177

1174-
@router.post(
1175-
'/primer_design/homologous_recombination',
1176-
response_model=create_model('HomologousRecombinationPrimerDesignResponse', primers=(list[PrimerModel], ...)),
1177-
)
1178+
PrimerDesignResponse = create_model('PrimerDesignResponse', primers=(list[PrimerModel], ...))
1179+
1180+
1181+
@router.post('/primer_design/homologous_recombination', response_model=PrimerDesignResponse)
11781182
async def primer_design_homologous_recombination(
11791183
pcr_template: PrimerDesignQuery,
11801184
homologous_recombination_target: PrimerDesignQuery,
@@ -1223,21 +1227,7 @@ async def primer_design_homologous_recombination(
12231227
return {'primers': [forward_primer, reverse_primer]}
12241228

12251229

1226-
# A primer design endpoint for Gibson assembly
1227-
# This is how the request data will look like from javascript code:
1228-
# const requestData = {
1229-
# queries: templateEntities.map((e, index) => ({
1230-
# sequence: e,
1231-
# location: selectedRegion2SequenceLocation(amplifyRegions[index]),
1232-
# orientation: fragmentOrientations[index],
1233-
# })),
1234-
# };
1235-
1236-
1237-
@router.post(
1238-
'/primer_design/gibson_assembly',
1239-
response_model=create_model('GibsonAssemblyPrimerDesignResponse', primers=(list[PrimerModel], ...)),
1240-
)
1230+
@router.post('/primer_design/gibson_assembly', response_model=PrimerDesignResponse)
12411231
async def primer_design_gibson_assembly(
12421232
pcr_templates: list[PrimerDesignQuery],
12431233
spacers: list[str] | None = Body(
@@ -1277,11 +1267,7 @@ async def primer_design_gibson_assembly(
12771267
return {'primers': primers}
12781268

12791269

1280-
@router.post(
1281-
'/primer_design/simple_pair',
1282-
response_model=create_model('SimplePairPrimerDesignResponse', primers=(list[PrimerModel], ...)),
1283-
summary='Design primers for PCR, you can also include restriction enzyme sites with filler bases.',
1284-
)
1270+
@router.post('/primer_design/simple_pair', response_model=PrimerDesignResponse)
12851271
async def primer_design_simple_pair(
12861272
pcr_template: PrimerDesignQuery,
12871273
spacers: list[str] | None = Body(
@@ -1372,6 +1358,41 @@ async def annotate_with_plannotate(
13721358
return {'sources': [source], 'sequences': [format_sequence_genbank(seqr, source.output_name)]}
13731359

13741360

1361+
# @router.post('/primer_design/gateway_attB', response_model=PrimerDesignResponse)
1362+
# async def primer_design_gateway_attB(
1363+
# template: PrimerDesignQuery,
1364+
# left_site: str = Query(..., description='The left attB site to recombine.', regex=r'^attB[1-5]$'),
1365+
# right_site: str = Query(..., description='The right attB site to recombine.', regex=r'^attB[1-5]$'),
1366+
# spacers: list[str] | None = Body(None, description='Spacers to add between the attB site and the primer.'),
1367+
# filler_bases: str = Query(
1368+
# 'GGGG',
1369+
# description='These bases are added to the 5\' end of the primer to ensure proper restriction enzyme digestion.',
1370+
# ),
1371+
# minimal_hybridization_length: int = Query(
1372+
# ..., description='The minimal length of the hybridization region in bps.'
1373+
# ),
1374+
# target_tm: float = Query(
1375+
# ..., description='The desired melting temperature for the hybridization part of the primer.'
1376+
# ),
1377+
# ):
1378+
# """Design primers for Gateway attB"""
1379+
# dseqr = read_dsrecord_from_json(template.sequence)
1380+
# location = template.location.to_biopython_location(dseqr.circular, len(dseqr))
1381+
# template = location.extract(dseqr)
1382+
# if not template.forward_orientation:
1383+
# template = template.reverse_complement()
1384+
# template.name = dseqr.name
1385+
# template.id = dseqr.id
1386+
# try:
1387+
# primers = gateway_attB_primers(
1388+
# template, minimal_hybridization_length, target_tm, (left_site, right_site), spacers, filler_bases
1389+
# )
1390+
# except ValueError as e:
1391+
# raise HTTPException(400, *e.args)
1392+
1393+
# return {'primers': primers}
1394+
1395+
13751396
@router.post(
13761397
'/validate',
13771398
summary='Validate a cloning strategy',
@@ -1393,6 +1414,20 @@ async def rename_sequence(
13931414
return format_sequence_genbank(dseqr, name)
13941415

13951416

1417+
@router.post('/annotation/get_gateway_sites', response_model=dict[str, list[str]])
1418+
async def get_gateway_sites(
1419+
sequence: TextFileSequence, greedy: bool = Query(False, description='Whether to use the greedy algorithm.')
1420+
) -> dict[str, list[str]]:
1421+
"""
1422+
Get a dictionary with the names of the gateway sites present in the sequence and their locations as strings.
1423+
"""
1424+
dseqr = read_dsrecord_from_json(sequence)
1425+
sites_dict = find_gateway_sites(dseqr, greedy)
1426+
for site in sites_dict:
1427+
sites_dict[site] = [str(loc) for loc in sites_dict[site]]
1428+
return sites_dict
1429+
1430+
13961431
if not SERVE_FRONTEND:
13971432

13981433
@router.get('/')

primer_design.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,40 @@ def simple_pair_primers(
164164
PrimerModel(id=0, name=fwd_primer_name, sequence=str(fwd_primer_seq)),
165165
PrimerModel(id=0, name=rvs_primer_name, sequence=str(rvs_primer_seq)),
166166
)
167+
168+
169+
# def gateway_attB_primers(
170+
# template: Dseqrecord,
171+
# minimal_hybridization_length: int,
172+
# target_tm: float,
173+
# sites: tuple[str, str],
174+
# spacers: tuple[str, str],
175+
# filler_bases: str = 'GGGG',
176+
# ) -> tuple[PrimerModel, PrimerModel]:
177+
# if spacers is None:
178+
# spacers = ['', '']
179+
180+
# if len(spacers) != 2:
181+
# raise ValueError("The 'spacers' list must contain exactly two elements.")
182+
183+
# if sites[0] not in primer_design_attB or sites[1] not in primer_design_attB:
184+
# raise ValueError('Invalid attB site.')
185+
186+
# amplicon = primer_design(template, limit=minimal_hybridization_length, target_tm=target_tm)
187+
# fwd_primer, rvs_primer = amplicon.primers()
188+
189+
# if fwd_primer is None or rvs_primer is None:
190+
# raise ValueError('Primers could not be designed, try changing settings.')
191+
192+
# template_name = template.name if template.name != 'name' else f'seq_{template.id}'
193+
194+
# left_site = primer_design_attB[sites[0]]
195+
# right_site = primer_design_attB[sites[1]]
196+
197+
# fwd_primer_seq = filler_bases + left_site + spacers[0] + fwd_primer.seq
198+
# rvs_primer_seq = filler_bases + right_site + reverse_complement(spacers[1]) + rvs_primer.seq
199+
200+
# return (
201+
# PrimerModel(id=0, name=f'{template_name}_{sites[0]}_fwd', sequence=str(fwd_primer_seq)),
202+
# PrimerModel(id=0, name=f'{template_name}_{sites[1]}_rvs', sequence=str(rvs_primer_seq)),
203+
# )

test_endpoints.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2687,5 +2687,32 @@ async def test_plannotate_other_error(self):
26872687
self.assertEqual(e.code, 400)
26882688

26892689

2690+
class AnnotationTest(unittest.TestCase):
2691+
2692+
attB1 = 'ACAACTTTGTACAAAAAAGCAGAAG'
2693+
attB2 = 'ACAACTTTGTACAAGAAAGCTGGGC'
2694+
greedy_attP1 = 'CAAATAATGATTTTATTTTGACTGATAGTGACCTGTTCGTTGCAACAAATTGATAAGCAATGCTTTTTTATAATGCCAACTTTGTACAAAAAAGCTGAACGAGAAACGTAAAATGATATAAATATCAATATATTAAATTAGATTTTGCATAAAAAACAGACTACATAATACTGTAAAACACAACATATCCAGTCA'
2695+
2696+
def test_get_gateway_sites(self):
2697+
seq = Dseqrecord('aaa' + self.attB1 + 'ccc' + self.attB2 + 'ccc' + self.greedy_attP1)
2698+
response = client.post('/annotation/get_gateway_sites', json=format_sequence_genbank(seq).model_dump())
2699+
self.assertEqual(response.status_code, 200)
2700+
payload = response.json()
2701+
self.assertIn('attB1', payload)
2702+
self.assertIn('attB2', payload)
2703+
self.assertNotIn('attP1', payload)
2704+
2705+
response = client.post(
2706+
'/annotation/get_gateway_sites',
2707+
json=format_sequence_genbank(seq).model_dump(),
2708+
params={'greedy': True},
2709+
)
2710+
self.assertEqual(response.status_code, 200)
2711+
payload = response.json()
2712+
self.assertIn('attB1', payload)
2713+
self.assertIn('attB2', payload)
2714+
self.assertIn('attP1', payload)
2715+
2716+
26902717
if __name__ == '__main__':
26912718
unittest.main()

test_primer_design.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
from primer_design import homologous_recombination_primers, gibson_assembly_primers, simple_pair_primers
1+
from primer_design import (
2+
homologous_recombination_primers,
3+
gibson_assembly_primers,
4+
simple_pair_primers,
5+
)
26
from Bio.SeqFeature import SimpleLocation, SeqFeature
37
from unittest import TestCase
48
from pydna.dseqrecord import Dseqrecord
@@ -511,3 +515,27 @@ def test_without_restriction_enzymes(self):
511515
# Check that primers are correct
512516
self.assertTrue(fwd.sequence.startswith('ATGCA'))
513517
self.assertTrue(rvs.sequence.startswith('GTCAT'))
518+
519+
520+
# class TestGatewayAttBPrimers(TestCase):
521+
# def test_normal_examples(self):
522+
# """
523+
# Test the gateway_attB_primers function.
524+
# """
525+
# template = Dseqrecord('ATGCAAACAGTGAACAGATGGAGACAATAATGATGGATGAC')
526+
# template.id = '0'
527+
# minimal_hybridization_length = 10
528+
# target_tm = 55
529+
# left_site = 'attB1'
530+
# right_site = 'attB5'
531+
# spacers = None
532+
533+
# primers = gateway_attB_primers(
534+
# template, minimal_hybridization_length, target_tm, (left_site, right_site), spacers
535+
# )
536+
537+
# self.assertEqual(len(primers), 2)
538+
# self.assertEqual(primers[0].name, 'seq_0_attB1_fwd')
539+
# self.assertEqual(primers[1].name, 'seq_0_attB5_rvs')
540+
# self.assertTrue(primers[0].sequence.startswith('GGGG' + primer_design_attB['attB1']))
541+
# self.assertTrue(primers[1].sequence.startswith('GGGG' + primer_design_attB['attB5']))

0 commit comments

Comments
 (0)