opencloning 0.2.7.3__py3-none-any.whl → 0.2.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
opencloning/cre_lox.py ADDED
@@ -0,0 +1,29 @@
1
+ from itertools import product
2
+ from pydna.dseqrecord import Dseqrecord
3
+
4
+ from .dna_utils import compute_regex_site, dseqrecord_finditer
5
+
6
+ # This is the original loxP sequence, here for reference
7
+ LOXP_SEQUENCE = 'ATAACTTCGTATAGCATACATTATACGAAGTTAT'
8
+
9
+ # This is a consensus sequence, from this Addgene blog post: https://blog.addgene.org/plasmids-101-cre-lox
10
+ # IMPORTANT: Because it is palyndromic, we only look for it in the forward direction, if this was changed
11
+ # to a non-palindromic sequence, you would need to look for matches reversing it, like in Gateway cloning
12
+ LOXP_CONSENSUS = 'ATAACTTCGTATANNNTANNNTATACGAAGTTAT'
13
+
14
+
15
+ loxP_regex = compute_regex_site(LOXP_CONSENSUS)
16
+
17
+
18
+ def cre_loxP_overlap(x: Dseqrecord, y: Dseqrecord, _l: None = None) -> list[tuple[int, int, int]]:
19
+ """Find matching loxP sites between two sequences."""
20
+ out = list()
21
+ matches_x = dseqrecord_finditer(loxP_regex, x)
22
+ matches_y = dseqrecord_finditer(loxP_regex, y)
23
+
24
+ for match_x, match_y in product(matches_x, matches_y):
25
+ value_x = match_x.group()
26
+ value_y = match_y.group()
27
+ if value_x == value_y:
28
+ out.append((match_x.start(), match_y.start(), len(value_x)))
29
+ return out
opencloning/dna_utils.py CHANGED
@@ -11,9 +11,15 @@ import os
11
11
  import shutil
12
12
  from pydna.parsers import parse
13
13
  from Bio.Align import PairwiseAligner
14
+ from Bio.Data.IUPACData import ambiguous_dna_values as _ambiguous_dna_values
15
+ import re
14
16
 
15
17
  aligner = PairwiseAligner(scoring='blastn')
16
18
 
19
+ ambiguous_only_dna_values = {**_ambiguous_dna_values}
20
+ for normal_base in 'ACGT':
21
+ del ambiguous_only_dna_values[normal_base]
22
+
17
23
 
18
24
  def sum_is_sticky(three_prime_end: tuple[str, str], five_prime_end: tuple[str, str], partial: bool = False) -> int:
19
25
  """Return the overlap length if the 3' end of seq1 and 5' end of seq2 ends are sticky and compatible for ligation.
@@ -144,3 +150,20 @@ def align_sanger_traces(dseqr: Dseqrecord, sanger_traces: list[str]) -> list[str
144
150
  sanger_traces = traces_oriented
145
151
 
146
152
  return align_with_mafft([query_str, *sanger_traces], True)
153
+
154
+
155
+ def compute_regex_site(site: str) -> str:
156
+ upper_site = site.upper()
157
+ for k, v in ambiguous_only_dna_values.items():
158
+ if len(v) > 1:
159
+ upper_site = upper_site.replace(k, f"[{''.join(v)}]")
160
+
161
+ # Make case insensitive
162
+ upper_site = f'(?i){upper_site}'
163
+ return upper_site
164
+
165
+
166
+ def dseqrecord_finditer(pattern: str, seq: Dseqrecord) -> list[re.Match]:
167
+ query = str(seq.seq) if not seq.circular else str(seq.seq) * 2
168
+ matches = re.finditer(pattern, query)
169
+ return (m for m in matches if m.start() <= len(seq))
@@ -5,7 +5,7 @@ from pydna.primer import Primer as PydnaPrimer
5
5
  from pydna.crispr import cas9
6
6
  from pydantic import conlist, create_model
7
7
  from Bio.Restriction.Restriction import RestrictionBatch
8
-
8
+ from opencloning.cre_lox import cre_loxP_overlap
9
9
  from ..dna_functions import (
10
10
  get_invalid_enzyme_names,
11
11
  format_sequence_genbank,
@@ -24,6 +24,8 @@ from ..pydantic_models import (
24
24
  AssemblySource,
25
25
  OverlapExtensionPCRLigationSource,
26
26
  GatewaySource,
27
+ CreLoxRecombinationSource,
28
+ InVivoAssemblySource,
27
29
  )
28
30
  from ..assembly2 import (
29
31
  Assembly,
@@ -159,6 +161,7 @@ def generate_assemblies(
159
161
  allow_insertion_assemblies: bool,
160
162
  assembly_kwargs: dict | None = None,
161
163
  product_callback: Callable[[Dseqrecord], Dseqrecord] = lambda x: x,
164
+ recombination_mode: bool = False,
162
165
  ) -> dict[Literal['sources', 'sequences'], list[AssemblySource] | list[TextFileSequence]]:
163
166
  if assembly_kwargs is None:
164
167
  assembly_kwargs = {}
@@ -175,10 +178,15 @@ def generate_assemblies(
175
178
  circular_assemblies = asm.get_circular_assemblies()
176
179
  out_sources += [create_source(a, True) for a in circular_assemblies]
177
180
  if not circular_only:
178
- out_sources += [
179
- create_source(a, False)
180
- for a in filter_linear_subassemblies(asm.get_linear_assemblies(), circular_assemblies, fragments)
181
- ]
181
+ if not recombination_mode:
182
+ out_sources += [
183
+ create_source(a, False)
184
+ for a in filter_linear_subassemblies(
185
+ asm.get_linear_assemblies(), circular_assemblies, fragments
186
+ )
187
+ ]
188
+ else:
189
+ out_sources += [create_source(a, False) for a in asm.get_insertion_assemblies()]
182
190
  else:
183
191
  asm = SingleFragmentAssembly(fragments, algorithm=algo, **assembly_kwargs)
184
192
  out_sources.extend(create_source(a, True) for a in asm.get_circular_assemblies())
@@ -385,13 +393,16 @@ async def homologous_recombination(
385
393
  '/gibson_assembly',
386
394
  response_model=create_model(
387
395
  'GibsonAssemblyResponse',
388
- sources=(list[Union[GibsonAssemblySource, OverlapExtensionPCRLigationSource, InFusionSource]], ...),
396
+ sources=(
397
+ list[Union[GibsonAssemblySource, OverlapExtensionPCRLigationSource, InFusionSource, InVivoAssemblySource]],
398
+ ...,
399
+ ),
389
400
  sequences=(list[TextFileSequence], ...),
390
401
  ),
391
402
  )
392
403
  async def gibson_assembly(
393
404
  sequences: conlist(TextFileSequence, min_length=1),
394
- source: Union[GibsonAssemblySource, OverlapExtensionPCRLigationSource, InFusionSource],
405
+ source: Union[GibsonAssemblySource, OverlapExtensionPCRLigationSource, InFusionSource, InVivoAssemblySource],
395
406
  minimal_homology: int = Query(
396
407
  40, description='The minimum homology between consecutive fragments in the assembly.'
397
408
  ),
@@ -522,3 +533,30 @@ async def gateway(
522
533
  return {'sources': sources, 'sequences': sequences}
523
534
 
524
535
  return resp
536
+
537
+
538
+ @router.post(
539
+ '/cre_lox_recombination',
540
+ response_model=create_model(
541
+ 'CreLoxRecombinationResponse',
542
+ sources=(list[CreLoxRecombinationSource], ...),
543
+ sequences=(list[TextFileSequence], ...),
544
+ ),
545
+ )
546
+ async def cre_lox_recombination(source: CreLoxRecombinationSource, sequences: conlist(TextFileSequence, min_length=1)):
547
+ fragments = [read_dsrecord_from_json(seq) for seq in sequences]
548
+
549
+ # Lambda function for code clarity
550
+ def create_source(a, is_circular):
551
+ return CreLoxRecombinationSource.from_assembly(
552
+ assembly=a, circular=is_circular, id=source.id, fragments=fragments
553
+ )
554
+
555
+ resp = generate_assemblies(
556
+ source, create_source, fragments, False, cre_loxP_overlap, True, recombination_mode=True
557
+ )
558
+
559
+ if len(resp['sources']) == 0:
560
+ raise HTTPException(400, 'No compatible Cre/Lox recombination was found.')
561
+
562
+ return resp
opencloning/gateway.py CHANGED
@@ -1,31 +1,10 @@
1
- from Bio.Data.IUPACData import ambiguous_dna_values as _ambiguous_dna_values
2
1
  from Bio.Seq import reverse_complement
3
2
  from pydna.dseqrecord import Dseqrecord as _Dseqrecord
4
3
  import re
5
4
  import itertools as _itertools
6
5
  from Bio.SeqFeature import SimpleLocation, SeqFeature
7
6
  from pydna.utils import shift_location
8
-
9
- ambiguous_only_dna_values = {**_ambiguous_dna_values}
10
- for normal_base in 'ACGT':
11
- del ambiguous_only_dna_values[normal_base]
12
-
13
-
14
- def compute_regex_site(site: str) -> str:
15
- upper_site = site.upper()
16
- for k, v in ambiguous_only_dna_values.items():
17
- if len(v) > 1:
18
- upper_site = upper_site.replace(k, f"[{''.join(v)}]")
19
-
20
- # Make case insensitive
21
- upper_site = f'(?i){upper_site}'
22
- return upper_site
23
-
24
-
25
- def dseqrecord_finditer(pattern: str, seq: _Dseqrecord) -> list[re.Match]:
26
- query = str(seq.seq) if not seq.circular else str(seq.seq) * 2
27
- matches = re.finditer(pattern, query)
28
- return (m for m in matches if m.start() <= len(seq))
7
+ from .dna_utils import compute_regex_site, dseqrecord_finditer
29
8
 
30
9
 
31
10
  raw_gateway_common = {
@@ -45,6 +45,8 @@ from opencloning_linkml.datamodel import (
45
45
  IGEMSource as _IGEMSource,
46
46
  ReverseComplementSource as _ReverseComplementSource,
47
47
  SEVASource as _SEVASource,
48
+ CreLoxRecombinationSource as _CreLoxRecombinationSource,
49
+ InVivoAssemblySource as _InVivoAssemblySource,
48
50
  )
49
51
  from pydna.utils import shift_location as _shift_location
50
52
  from .assembly2 import edge_representation2subfragment_representation, subfragment_representation2edge_representation
@@ -338,6 +340,10 @@ class InFusionSource(AssemblySourceCommonClass, _InFusionSource):
338
340
  pass
339
341
 
340
342
 
343
+ class InVivoAssemblySource(AssemblySourceCommonClass, _InVivoAssemblySource):
344
+ pass
345
+
346
+
341
347
  class CRISPRSource(AssemblySourceCommonClass, _CRISPRSource):
342
348
 
343
349
  # TODO
@@ -384,6 +390,10 @@ class GatewaySource(AssemblySourceCommonClass, _GatewaySource):
384
390
  return super().from_assembly(assembly, id, circular, fragments, reaction_type=reaction_type)
385
391
 
386
392
 
393
+ class CreLoxRecombinationSource(AssemblySourceCommonClass, _CreLoxRecombinationSource):
394
+ pass
395
+
396
+
387
397
  class OligoHybridizationSource(SourceCommonClass, _OligoHybridizationSource):
388
398
  pass
389
399
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: opencloning
3
- Version: 0.2.7.3
3
+ Version: 0.2.8
4
4
  Summary: Backend of OpenCloning, a web application to generate molecular cloning strategies in json format, and share them with others.
5
5
  License: MIT
6
6
  Author: Manuel Lera-Ramirez
@@ -15,7 +15,7 @@ Requires-Dist: beautifulsoup4 (>=4.11.1,<5.0.0)
15
15
  Requires-Dist: biopython (==1.84)
16
16
  Requires-Dist: fastapi
17
17
  Requires-Dist: httpx (>=0.25.0,<0.26.0)
18
- Requires-Dist: opencloning-linkml (==0.2.5.2a0)
18
+ Requires-Dist: opencloning-linkml (==0.2.6a0)
19
19
  Requires-Dist: openpyxl (>=3.1.5,<4.0.0)
20
20
  Requires-Dist: pandas (>=2.2.3,<3.0.0)
21
21
  Requires-Dist: primer3-py (>=2.0.3,<3.0.0)
@@ -19,27 +19,28 @@ opencloning/batch_cloning/pombe/pombe_summary.py,sha256=W9DLpnCuwK7w2DhHLu60N7L6
19
19
  opencloning/batch_cloning/ziqiang_et_al2024/__init__.py,sha256=zZUbj3uMzd9rKMXi5s9LQ1yUg7sccdS0f_4kpw7SQlk,7584
20
20
  opencloning/batch_cloning/ziqiang_et_al2024/index.html,sha256=EDncANDhhQkhi5FjnnAP6liHkG5srf4_Y46IrnMUG5g,4607
21
21
  opencloning/batch_cloning/ziqiang_et_al2024/ziqiang_et_al2024.json,sha256=mB81j2qWam7uRc-980YFjfqq2CiWTXJYfKFAoKuGtRw,157148
22
+ opencloning/cre_lox.py,sha256=ocPx3EVkecoZjHx_ENhk5pEteRXRtiN5z5URmrIcCPw,1194
22
23
  opencloning/dna_functions.py,sha256=W-SxEfvYpN1JVZbTeCNitpQXkazEHvFyqZBUndd-jpY,16329
23
- opencloning/dna_utils.py,sha256=emms1omBhQKuVNEv6YXcHReP69tvUU1iRpLgjXn5p9o,5541
24
+ opencloning/dna_utils.py,sha256=uv97aO04dbk3NnqbN6GlnwOu0MOpK88rl2np2QcEQ4Y,6301
24
25
  opencloning/ebic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
26
  opencloning/ebic/primer_design.py,sha256=gPZTF9w5SV7WGgnefp_HBM831y0z73M1Kb0QUPnbfIM,2270
26
27
  opencloning/ebic/primer_design_settings.py,sha256=OnFsuh0QCvplUEPXLZouzRo9R7rm4nLbcd2LkDCiIDM,1896
27
28
  opencloning/endpoints/annotation.py,sha256=3rlIXeNQzoqPD9lJUEBGLGxvlhUCTcfkqno814A8P0U,2283
28
- opencloning/endpoints/assembly.py,sha256=0kcWchgN5ulj_I7ZOpFhsgq74SBT_7A7xbZz7s5-1C0,19330
29
+ opencloning/endpoints/assembly.py,sha256=H1b7CRx1JZ5pcUGd3uyJG2syYugkXiIo8HRCA11TQfE,20704
29
30
  opencloning/endpoints/external_import.py,sha256=dDG7DiNb8WYE46nLGnkyRbGVVNUDXp3h0_1ixsJAh5o,16242
30
31
  opencloning/endpoints/no_assembly.py,sha256=NY6rhEDCNoZVn6Xk81cen2n-FkMr7ierfxM8G0npbQs,4722
31
32
  opencloning/endpoints/no_input.py,sha256=DuqKD3Ph3a44ZxPMEzZv1nwD5xlxYsN7YyxXcfjSUFc,3844
32
33
  opencloning/endpoints/other.py,sha256=TzfCJLDmZFWeKYxKhEfXOvlQrWWyBIGJ5FR0yA7tuvI,1673
33
34
  opencloning/endpoints/primer_design.py,sha256=ItPUa7bBW9JOOfTuLj0yNnF9UmQ-I_0l3i8wHpnUc6k,12854
34
- opencloning/gateway.py,sha256=qaefWKjfASuVU_nXnCCoDepQq1jhNINNW0VifkCLVC0,9123
35
+ opencloning/gateway.py,sha256=jzpLbB8UCSlks0S6Qe9PXJ7CdzHiH2ko_O7MzYQLR14,8435
35
36
  opencloning/get_router.py,sha256=l2DXaTbeL2tDqlnVMlcewutzt1sjaHlxku1X9HVUwJk,252
36
37
  opencloning/main.py,sha256=l9PrPBMtGMEWxAPiPWR15Qv2oDNnRoNd8H8E3bZW6Do,3750
37
38
  opencloning/ncbi_requests.py,sha256=JrFc-Ugr1r1F4LqsdpJZEiERj7ZemvZSgiIltl2Chx8,5547
38
39
  opencloning/primer_design.py,sha256=nqCmYIZ7UvU4CQwVGJwX7T5LTHwt3-51_ZcTZZAgT_Y,9175
39
- opencloning/pydantic_models.py,sha256=S3ehXeynVlYJQK-G96D0jApMp7dn-eRg1di9d0DbsEc,15045
40
+ opencloning/pydantic_models.py,sha256=gsipVXhjQOXVz2NL-MiNpLuOZYDVo2Pli9F--bp6tjs,15345
40
41
  opencloning/request_examples.py,sha256=QAsJxVaq5tHwlPB404IiJ9WC6SA7iNY7XnJm63BWT_E,2944
41
42
  opencloning/utils.py,sha256=wsdTJYliap-t3oa7yQE3pWDa1CR19mr5lUQfocp4hoM,1875
42
- opencloning-0.2.7.3.dist-info/LICENSE,sha256=VSdVE1f8axjIh6gvo9ZZygJdTVkRFMcwCW_hvjOHC_w,1058
43
- opencloning-0.2.7.3.dist-info/METADATA,sha256=SFtmhPNvDi61Qp9JYNcsiQop9I_zYOPyGfeus-Etbf4,8429
44
- opencloning-0.2.7.3.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
45
- opencloning-0.2.7.3.dist-info/RECORD,,
43
+ opencloning-0.2.8.dist-info/LICENSE,sha256=VSdVE1f8axjIh6gvo9ZZygJdTVkRFMcwCW_hvjOHC_w,1058
44
+ opencloning-0.2.8.dist-info/METADATA,sha256=0kyQ2RhJcsCrkjRR6usNPg4LswxSYq71A61MY0ro0Yw,8425
45
+ opencloning-0.2.8.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
46
+ opencloning-0.2.8.dist-info/RECORD,,