opencloning 0.3.7__py3-none-any.whl → 0.4.2__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.
@@ -1,5 +1,5 @@
1
- from pydantic import BaseModel, Field, model_validator, field_validator
2
- from typing import Optional, List
1
+ from pydantic import BaseModel, Field, model_validator, field_validator, Discriminator, Tag
2
+ from typing import Optional, List, Union, Annotated
3
3
  from pydantic_core import core_schema
4
4
  from ._version import __version__
5
5
 
@@ -49,8 +49,9 @@ from opencloning_linkml.datamodel import (
49
49
  SEVASource as _SEVASource,
50
50
  CreLoxRecombinationSource as _CreLoxRecombinationSource,
51
51
  InVivoAssemblySource as _InVivoAssemblySource,
52
+ SourceInput as _SourceInput,
52
53
  )
53
- from .assembly2 import (
54
+ from pydna.assembly2 import (
54
55
  edge_representation2subfragment_representation,
55
56
  subfragment_representation2edge_representation,
56
57
  )
@@ -64,6 +65,10 @@ class TextFileSequence(_TextFileSequence):
64
65
  pass
65
66
 
66
67
 
68
+ class SourceInput(_SourceInput):
69
+ pass
70
+
71
+
67
72
  class PrimerModel(_Primer):
68
73
  """Called PrimerModel not to be confused with the class from pydna."""
69
74
 
@@ -94,8 +99,23 @@ class SeqFeatureModel(BaseModel):
94
99
  # Sources =========================================
95
100
 
96
101
 
97
- class SourceCommonClass:
98
- input: Optional[List[int]] = Field(
102
+ def input_discriminator(v) -> str | None:
103
+ """
104
+ Discriminator that yields SourceInput by default
105
+ """
106
+ if isinstance(v, dict):
107
+ input_type = v.get('type', None)
108
+ if input_type is None:
109
+ return 'SourceInput'
110
+ else:
111
+ return input_type
112
+ elif isinstance(v, SourceInput):
113
+ return v.type
114
+ return None
115
+
116
+
117
+ class SourceCommonClass(BaseModel):
118
+ input: Optional[List[SourceInput]] = Field(
99
119
  default_factory=list,
100
120
  description="""The sequences that are an input to this source. If the source represents external import of a sequence, it's empty.""",
101
121
  json_schema_extra={'linkml_meta': {'alias': 'input', 'domain_of': ['Source']}},
@@ -292,7 +312,7 @@ class SequenceLocationStr(str):
292
312
  return cls.field_validator(value)
293
313
 
294
314
 
295
- class AssemblyFragment(_AssemblyFragment):
315
+ class AssemblyFragment(_AssemblyFragment, SourceInput):
296
316
  left_location: Optional[SequenceLocationStr] = None
297
317
  right_location: Optional[SequenceLocationStr] = None
298
318
 
@@ -322,14 +342,26 @@ class AssemblyFragment(_AssemblyFragment):
322
342
  class AssemblySourceCommonClass(SourceCommonClass):
323
343
  # TODO: This is different in the LinkML model, because there it is not required,
324
344
  # and here we make it default to list.
325
- assembly: List[AssemblyFragment] = Field(
326
- default_factory=list, description="""The joins between the fragments in the assembly"""
345
+ input: Optional[
346
+ List[
347
+ Annotated[
348
+ Union[
349
+ Annotated[SourceInput, Tag('SourceInput')],
350
+ Annotated['AssemblyFragment', Tag('AssemblyFragment')],
351
+ ],
352
+ Discriminator(input_discriminator),
353
+ ]
354
+ ]
355
+ ] = Field(
356
+ default_factory=list,
357
+ description="""The inputs to this source. If the source represents external import of a sequence, it's empty.""",
358
+ json_schema_extra={'linkml_meta': {'alias': 'input', 'domain_of': ['Source'], 'slot_uri': 'schema:object'}},
327
359
  )
328
360
 
329
361
  def minimal_overlap(self):
330
362
  """Returns the minimal overlap between the fragments in the assembly"""
331
363
  all_overlaps = list()
332
- for f in self.assembly:
364
+ for f in self.input:
333
365
  if f.left_location is not None:
334
366
  all_overlaps.append(f.left_location.end - f.left_location.start)
335
367
  if f.right_location is not None:
@@ -338,9 +370,13 @@ class AssemblySourceCommonClass(SourceCommonClass):
338
370
 
339
371
  def get_assembly_plan(self, fragments: list[_SeqRecord]) -> tuple:
340
372
  """Returns the assembly plan"""
341
- subf = [f.to_fragment_tuple(fragments) for f in self.assembly]
373
+ subf = [f.to_fragment_tuple(fragments) for f in self.input if f.type == 'AssemblyFragment']
342
374
  return subfragment_representation2edge_representation(subf, self.circular)
343
375
 
376
+ def is_assembly_complete(self) -> bool:
377
+ """Returns True if the assembly is complete"""
378
+ return any(f.type == 'AssemblyFragment' for f in self.input)
379
+
344
380
  @classmethod
345
381
  def from_assembly(
346
382
  cls,
@@ -353,7 +389,6 @@ class AssemblySourceCommonClass(SourceCommonClass):
353
389
 
354
390
  # Replace the positions with the actual ids
355
391
  fragment_ids = [int(f.id) for f in fragments]
356
- input_ids = [int(f.id) for f in fragments if not isinstance(f, _PydnaPrimer)]
357
392
 
358
393
  # Here the ids are still the positions in the fragments list
359
394
  fragment_assembly_positions = edge_representation2subfragment_representation(assembly, circular)
@@ -368,8 +403,7 @@ class AssemblySourceCommonClass(SourceCommonClass):
368
403
  ]
369
404
  return cls(
370
405
  id=id,
371
- input=input_ids,
372
- assembly=assembly_fragments,
406
+ input=assembly_fragments,
373
407
  circular=circular,
374
408
  **kwargs,
375
409
  )
@@ -428,7 +462,9 @@ class CRISPRSource(AssemblySourceCommonClass, _CRISPRSource):
428
462
  fragments: list[_SeqRecord],
429
463
  guides: list[int],
430
464
  ):
431
- return super().from_assembly(assembly, id, False, fragments, guides=guides)
465
+ source = super().from_assembly(assembly, id, False, fragments)
466
+ source.input += [SourceInput(sequence=guide) for guide in guides]
467
+ return source
432
468
 
433
469
 
434
470
  class RestrictionAndLigationSource(AssemblySourceCommonClass, _RestrictionAndLigationSource):
@@ -486,17 +522,14 @@ class BaseCloningStrategy(_CloningStrategy):
486
522
  json_schema_extra={'linkml_meta': {'alias': 'backend_version', 'domain_of': ['CloningStrategy']}},
487
523
  )
488
524
 
489
- def next_primer_id(self):
490
- return max([p.id for p in self.primers], default=0) + 1
491
-
492
525
  def add_primer(self, primer: PrimerModel):
493
526
  if primer in self.primers:
494
527
  return
495
- primer.id = self.next_primer_id()
528
+ primer.id = self.next_id()
496
529
  self.primers.append(primer)
497
530
 
498
- def next_node_id(self):
499
- return max([s.id for s in self.sources + self.sequences], default=0) + 1
531
+ def next_id(self):
532
+ return max([s.id for s in self.sources + self.sequences + self.primers], default=0) + 1
500
533
 
501
534
  def add_source_and_sequence(self, source: SourceCommonClass, sequence: TextFileSequence):
502
535
  if source in self.sources:
@@ -505,11 +538,11 @@ class BaseCloningStrategy(_CloningStrategy):
505
538
  f"Source {source.id} already exists in the cloning strategy, but sequence {sequence.id} it's not its output."
506
539
  )
507
540
  return
508
- source.id = self.next_node_id()
541
+ new_id = self.next_id()
542
+ source.id = new_id
509
543
  self.sources.append(source)
510
- sequence.id = self.next_node_id()
544
+ sequence.id = new_id
511
545
  self.sequences.append(sequence)
512
- source.output = sequence.id
513
546
 
514
547
  def all_children_source_ids(self, source_id: int, source_children: list | None = None) -> list[int]:
515
548
  """Returns the ids of all source children ids of a source"""
@@ -517,7 +550,7 @@ class BaseCloningStrategy(_CloningStrategy):
517
550
  if source_children is None:
518
551
  source_children = []
519
552
 
520
- sources_that_take_output_as_input = [s for s in self.sources if source.output in s.input]
553
+ sources_that_take_output_as_input = [s for s in self.sources if source.id in [inp.sequence for inp in s.input]]
521
554
  new_source_ids = [s.id for s in sources_that_take_output_as_input]
522
555
 
523
556
  source_children.extend(new_source_ids)
@@ -66,10 +66,10 @@ oligonucleotide_hybridization_examples = {
66
66
  'value': {
67
67
  'source': {
68
68
  'id': 1,
69
- 'input': [],
70
- 'output': 0,
71
- 'forward_oligo': 2,
72
- 'reverse_oligo': 3,
69
+ 'input': [
70
+ {'sequence': 2},
71
+ {'sequence': 3},
72
+ ],
73
73
  },
74
74
  'primers': [
75
75
  {'id': 2, 'name': 'primer1', 'sequence': 'aaGCGGCCGCgtagaactttatgtgcttccttacattggt'},
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: opencloning
3
- Version: 0.3.7
3
+ Version: 0.4.2
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
@@ -12,19 +12,20 @@ Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
13
  Classifier: Programming Language :: Python :: 3.13
14
14
  Requires-Dist: beautifulsoup4 (>=4.11.1,<5.0.0)
15
- Requires-Dist: biopython (==1.84)
15
+ Requires-Dist: biopython (>=1.85,<2.0)
16
16
  Requires-Dist: fastapi
17
17
  Requires-Dist: httpx (>=0.28.1,<0.29.0)
18
- Requires-Dist: opencloning-linkml (==0.3.0a0)
18
+ Requires-Dist: opencloning-linkml (==0.4.3)
19
19
  Requires-Dist: openpyxl (>=3.1.5,<4.0.0)
20
20
  Requires-Dist: packaging (>=25.0,<26.0)
21
+ Requires-Dist: pairwise-alignments-to-msa (>=0.1.1,<0.2.0)
21
22
  Requires-Dist: pandas (>=2.2.3,<3.0.0)
22
- Requires-Dist: primer3-py (==2.0.3)
23
+ Requires-Dist: primer3-py (==2.2.0)
23
24
  Requires-Dist: pydantic (>=2.7.1,<3.0.0)
24
- Requires-Dist: pydna (==5.5.0)
25
+ Requires-Dist: pydna (==5.5.2)
25
26
  Requires-Dist: python-multipart
26
27
  Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
27
- Requires-Dist: regex (>=2023.10.3,<2024.0.0)
28
+ Requires-Dist: regex (>=2024.11.6,<2025.0.0)
28
29
  Requires-Dist: uvicorn
29
30
  Project-URL: Repository, https://github.com/manulera/OpenCloning_backend
30
31
  Description-Content-Type: text/markdown
@@ -86,8 +87,8 @@ poetry install
86
87
  # Install the pre-commit hooks
87
88
  pre-commit install
88
89
 
89
- # Activate the virtual environment
90
- poetry shell
90
+ # Activate the virtual environment (used to be `poetry shell`)
91
+ poetry env activate
91
92
 
92
93
  ```
93
94
 
@@ -1,51 +1,49 @@
1
1
  opencloning/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  opencloning/_version.py,sha256=6QbWXLSZypjtWL_CwJFHH4dzMRK3AUH4B0YudzvGz9s,200
3
3
  opencloning/api_config_utils.py,sha256=inAXPGYNDz-DuEoSqitImj0Vv5TpQSbMZH9D3dQb5P0,4319
4
- opencloning/app_settings.py,sha256=MScy0le1Dn00rxqDkpeoBLWwjWQTyzHBQ-MqSDdmoe4,2054
5
- opencloning/assembly2.py,sha256=M-Als7mrEdHc8Ee3Zr_pgQ5WiN7Uqd37BMxr9Q-1myw,61743
4
+ opencloning/app_settings.py,sha256=FJFayBMLtE9gRC1e0ns5sVQRUb9DVXJ8o-ix4bTlPmI,2911
6
5
  opencloning/batch_cloning/EBIC/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
6
  opencloning/batch_cloning/EBIC/barcode.gb,sha256=G6kP6MuY23S-n3xg16LQaTasFtYFqik5eEgcooZ9ATM,815
8
7
  opencloning/batch_cloning/EBIC/common_plasmid.gb,sha256=At1HJjqJ2MsLMEx6W3MihJy7tgdtDu3fhwF4YGuw8Dk,13068
9
- opencloning/batch_cloning/EBIC/example.py,sha256=FWjROXWsgM-gz2oYnKiywUVKa9uoEe964FlFRle_hYI,6870
8
+ opencloning/batch_cloning/EBIC/example.py,sha256=1OENMoPVn5lhTall_DvD7qx7t021a2HMV11kpStVs88,6851
10
9
  opencloning/batch_cloning/EBIC/primer_design_settings.py,sha256=MVML1r1ciJYMFUJoqZVcGLoPM-f28oBN1wSDzlD0y64,1896
11
10
  opencloning/batch_cloning/__init__.py,sha256=uDxAa45g30_S6dJScNMlIxubQXlLRUsWoLX4S4y-l88,244
12
11
  opencloning/batch_cloning/index.html,sha256=HDqPHrJxrrKfGmy_dwYHhOsdUgZHHIch7Z0ey8qyvZI,1332
13
12
  opencloning/batch_cloning/pombe/__init__.py,sha256=Fq7SroO0Fer5CtFBRWdduIvzp1_dTUZwBb8IjBtRQO0,3332
14
13
  opencloning/batch_cloning/pombe/index.html,sha256=3YchoKGpcKDfvTOW1Rdih4PkbZIkMjKIQ0PaVXfV3e8,8348
15
- opencloning/batch_cloning/pombe/pombe_all.sh,sha256=0yvDdBaIdt2RsIrvnjgn5L3KtYBToq3Rl8-X8RFHibE,364
16
- opencloning/batch_cloning/pombe/pombe_clone.py,sha256=OY6yOlBK-9OAmHu3HUhP50mIXvyR0HJg1_2OjBFifV8,8123
14
+ opencloning/batch_cloning/pombe/pombe_clone.py,sha256=3Z51NA0rhU-e4SxuLnlV5iDWeQrqECJTfe8IfiDoiow,7996
17
15
  opencloning/batch_cloning/pombe/pombe_gather.py,sha256=qI-biMZGLxVDV-vOf3sT6bS1BawtTihZNcSYNlnnEi8,2432
18
16
  opencloning/batch_cloning/pombe/pombe_get_primers.py,sha256=nrn6V4_l8FWWfqS4WU3qPt95KwzvCXxETh8E-zWbRhM,3968
19
- opencloning/batch_cloning/pombe/pombe_summary.py,sha256=W9DLpnCuwK7w2DhHLu60N7L6jquuYubD3ZRFwdhNPVw,4033
20
- opencloning/batch_cloning/ziqiang_et_al2024/__init__.py,sha256=zZUbj3uMzd9rKMXi5s9LQ1yUg7sccdS0f_4kpw7SQlk,7584
17
+ opencloning/batch_cloning/pombe/pombe_summary.py,sha256=DBpoSEV1xPFm8TFDhrxS4tjqxJVrfH-ttM_0ELjQqok,4350
18
+ opencloning/batch_cloning/ziqiang_et_al2024/__init__.py,sha256=9-aG8JfZx8Y91qUAchTqRL2Rq5lSrZ34O_VkD8WNyiY,7083
21
19
  opencloning/batch_cloning/ziqiang_et_al2024/index.html,sha256=EDncANDhhQkhi5FjnnAP6liHkG5srf4_Y46IrnMUG5g,4607
22
- opencloning/batch_cloning/ziqiang_et_al2024/ziqiang_et_al2024.json,sha256=d-7oXbxoKhMKLz4FJ2OGDdedWTisoaRqLAh1NZnScBg,157189
23
- opencloning/bug_fixing/README.md,sha256=fGf_4HPTZ2TjGfIOZzw8IAD78-N-RnBrX8mz14wjXEQ,4237
20
+ opencloning/batch_cloning/ziqiang_et_al2024/ziqiang_et_al2024.json,sha256=JAOe35VPeG3QF1CnDzZxOzqpBTSpeCCtAo2W1mVOFEo,157159
21
+ opencloning/bug_fixing/README.md,sha256=eak5x1NTyTxsklaZ1BzB3RYH26MC_2gNuH-gHvuNpKY,4441
24
22
  opencloning/bug_fixing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- opencloning/bug_fixing/backend_v0_3.py,sha256=BTnqCXYIcOShut0WfwH6tDjJ1fecAHFHVSDTOKJgUZw,4119
23
+ opencloning/bug_fixing/backend_v0_3.py,sha256=GfuDRvlmcSBv9emXZSWqW-SktOeI1xH-tl2UET-BSoY,4145
26
24
  opencloning/cre_lox.py,sha256=x_OVYzfaLJH5eVyp05_I9YNycT606UL683AswhQ-gjU,4294
27
- opencloning/dna_functions.py,sha256=WvEiKuZEzy3mXjIkOMv4Ymej6xQ3gm4mymM8JCgvE_o,16611
28
- opencloning/dna_utils.py,sha256=LmkIScIlogpWxprQfsIdjqtLDpqDmTZp45pcZwWFpZM,6683
25
+ opencloning/dna_functions.py,sha256=O5H5DilNUOIzH8gVFiOuG8SVlrenrbwyo9lKwfxIHnY,16663
26
+ opencloning/dna_utils.py,sha256=NSS_b5q8u8XNzVqCwzCQaxj4I2xHf_Inmw7iBWS9gc8,6746
29
27
  opencloning/ebic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
28
  opencloning/ebic/primer_design.py,sha256=gPZTF9w5SV7WGgnefp_HBM831y0z73M1Kb0QUPnbfIM,2270
31
29
  opencloning/ebic/primer_design_settings.py,sha256=OnFsuh0QCvplUEPXLZouzRo9R7rm4nLbcd2LkDCiIDM,1896
32
30
  opencloning/endpoints/annotation.py,sha256=3rlIXeNQzoqPD9lJUEBGLGxvlhUCTcfkqno814A8P0U,2283
33
- opencloning/endpoints/assembly.py,sha256=XBv2Vg0MrBl4WjKlclyJ4QTgydqda9P3LRl_25y3TOo,21307
34
- opencloning/endpoints/external_import.py,sha256=i2hAKSYi2IIdglmDhDhWmBzHbhn6A9A8ybbj4nmhROM,18188
35
- opencloning/endpoints/no_assembly.py,sha256=NY6rhEDCNoZVn6Xk81cen2n-FkMr7ierfxM8G0npbQs,4722
36
- opencloning/endpoints/no_input.py,sha256=DuqKD3Ph3a44ZxPMEzZv1nwD5xlxYsN7YyxXcfjSUFc,3844
31
+ opencloning/endpoints/assembly.py,sha256=KdVi63Bjmb7qwCQrwMUkRK6lAhlAJvBcHLqj1VJtuAQ,21584
32
+ opencloning/endpoints/external_import.py,sha256=Z9zd94kzmnbLG_yEAjI-1HK7sIAMcLz3FSZZR66BV-U,18386
33
+ opencloning/endpoints/no_assembly.py,sha256=F6mRZFGUAASZ0v7EhPyBgmn_O-YLySMR1a_0aUH2U70,4806
34
+ opencloning/endpoints/no_input.py,sha256=0wc5sTD6nL0SrsXmT__x0g-m6m45ID5hq-D8KDKy98Y,4170
37
35
  opencloning/endpoints/other.py,sha256=7YBXU5UrVCjEjOjdYWw-0sASXn3MhWVZYwDYSZD4C9E,3452
38
36
  opencloning/endpoints/primer_design.py,sha256=3eiQ7MwgeLoAuXFUMNF-DzjzwH_eJGCjd4s32CjxIic,12717
39
37
  opencloning/gateway.py,sha256=pFB3gsCQL715kOFOP1NQOOsQqrkWuQe5qXk4IunF5SA,8486
40
38
  opencloning/get_router.py,sha256=l2DXaTbeL2tDqlnVMlcewutzt1sjaHlxku1X9HVUwJk,252
41
- opencloning/http_client.py,sha256=Ndxuoknrit5glKehvWLfKjqp0miLAfVsfLh4q57gEM4,1612
39
+ opencloning/http_client.py,sha256=coVesi_qJWea-VHBOd-V-Th6e4P9rzl-8ZILPGq2ne0,1262
42
40
  opencloning/main.py,sha256=l9PrPBMtGMEWxAPiPWR15Qv2oDNnRoNd8H8E3bZW6Do,3750
43
41
  opencloning/ncbi_requests.py,sha256=b8ow9TDpXbyYk_0HeK-7RXWwwZGrhH-MylSNc3_tH0I,5557
44
42
  opencloning/primer_design.py,sha256=nqCmYIZ7UvU4CQwVGJwX7T5LTHwt3-51_ZcTZZAgT_Y,9175
45
- opencloning/pydantic_models.py,sha256=lMO78M4MwDgzTEGz9qzsaADwAFXagWK4qGsF1K1hLZw,18865
46
- opencloning/request_examples.py,sha256=QAsJxVaq5tHwlPB404IiJ9WC6SA7iNY7XnJm63BWT_E,2944
43
+ opencloning/pydantic_models.py,sha256=N18tl8r6owDxTBRtrvB7DU3kmp59sBc8gYxujT9q6jE,19900
44
+ opencloning/request_examples.py,sha256=_VvY4lNpgqhLTM6O_OUs1TujhL7RUbRFZ9ptyssbxLk,2934
47
45
  opencloning/utils.py,sha256=0Lvw1h1AsUJTK2b9mNzYVi_DBeWmWCFA5dIPl_gERcI,1479
48
- opencloning-0.3.7.dist-info/LICENSE,sha256=VSdVE1f8axjIh6gvo9ZZygJdTVkRFMcwCW_hvjOHC_w,1058
49
- opencloning-0.3.7.dist-info/METADATA,sha256=yuwNExxPXtTWO-Xiur4esQhroyB85O6oo_BtQUQvnzw,9041
50
- opencloning-0.3.7.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
51
- opencloning-0.3.7.dist-info/RECORD,,
46
+ opencloning-0.4.2.dist-info/LICENSE,sha256=VSdVE1f8axjIh6gvo9ZZygJdTVkRFMcwCW_hvjOHC_w,1058
47
+ opencloning-0.4.2.dist-info/METADATA,sha256=jdAjLsb539U782FhTf2O7jV3UdQ-07mlCvu4LnFyd-c,9138
48
+ opencloning-0.4.2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
49
+ opencloning-0.4.2.dist-info/RECORD,,