opencloning 0.4.7__tar.gz → 0.5__tar.gz
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-0.4.7 → opencloning-0.5}/PKG-INFO +18 -8
- {opencloning-0.4.7 → opencloning-0.5}/README.md +15 -4
- {opencloning-0.4.7 → opencloning-0.5}/pyproject.toml +3 -4
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/app_settings.py +7 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/batch_cloning/pombe/__init__.py +2 -2
- opencloning-0.5/src/opencloning/batch_cloning/pombe/pombe_clone.py +102 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/batch_cloning/pombe/pombe_summary.py +20 -8
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/batch_cloning/ziqiang_et_al2024/__init__.py +8 -8
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/batch_cloning/ziqiang_et_al2024/ziqiang_et_al2024.json +2 -9
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/bug_fixing/backend_v0_3.py +13 -5
- opencloning-0.5/src/opencloning/catalogs/__init__.py +36 -0
- opencloning-0.5/src/opencloning/catalogs/igem2024.yaml +2172 -0
- opencloning-0.5/src/opencloning/catalogs/openDNA_collections.yaml +1161 -0
- opencloning-0.5/src/opencloning/catalogs/readme.txt +1 -0
- opencloning-0.5/src/opencloning/catalogs/seva.tsv +231 -0
- opencloning-0.5/src/opencloning/catalogs/snapgene.yaml +2837 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/dna_functions.py +155 -158
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/dna_utils.py +45 -62
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/ebic/primer_design.py +24 -14
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/endpoints/annotation.py +9 -13
- opencloning-0.5/src/opencloning/endpoints/assembly.py +367 -0
- opencloning-0.5/src/opencloning/endpoints/endpoint_utils.py +52 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/endpoints/external_import.py +169 -124
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/endpoints/no_assembly.py +23 -39
- opencloning-0.5/src/opencloning/endpoints/no_input.py +100 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/endpoints/other.py +1 -1
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/endpoints/primer_design.py +23 -17
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/http_client.py +2 -2
- opencloning-0.5/src/opencloning/ncbi_requests.py +203 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/primer3_functions.py +3 -3
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/primer_design.py +1 -1
- opencloning-0.5/src/opencloning/pydantic_models.py +75 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/request_examples.py +10 -22
- opencloning-0.5/src/opencloning/temp_functions.py +50 -0
- opencloning-0.4.7/src/opencloning/batch_cloning/pombe/pombe_clone.py +0 -183
- opencloning-0.4.7/src/opencloning/cre_lox.py +0 -116
- opencloning-0.4.7/src/opencloning/endpoints/assembly.py +0 -588
- opencloning-0.4.7/src/opencloning/endpoints/no_input.py +0 -115
- opencloning-0.4.7/src/opencloning/gateway.py +0 -154
- opencloning-0.4.7/src/opencloning/ncbi_requests.py +0 -137
- opencloning-0.4.7/src/opencloning/pydantic_models.py +0 -575
- {opencloning-0.4.7 → opencloning-0.5}/LICENSE +0 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/__init__.py +0 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/_version.py +0 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/api_config_utils.py +0 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/batch_cloning/__init__.py +0 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/batch_cloning/index.html +0 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/batch_cloning/pombe/index.html +0 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/batch_cloning/pombe/pombe_gather.py +0 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/batch_cloning/pombe/pombe_get_primers.py +0 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/batch_cloning/ziqiang_et_al2024/index.html +0 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/bug_fixing/README.md +0 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/bug_fixing/__init__.py +0 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/ebic/__init__.py +0 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/ebic/primer_design_settings.py +0 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/get_router.py +0 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/main.py +0 -0
- {opencloning-0.4.7 → opencloning-0.5}/src/opencloning/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opencloning
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5
|
|
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
|
License-File: LICENSE
|
|
@@ -17,14 +17,13 @@ Requires-Dist: beautifulsoup4 (>=4.11.1,<5.0.0)
|
|
|
17
17
|
Requires-Dist: biopython (>=1.85,<2.0)
|
|
18
18
|
Requires-Dist: fastapi
|
|
19
19
|
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
|
20
|
-
Requires-Dist: opencloning-linkml (==0.4.4)
|
|
21
20
|
Requires-Dist: openpyxl (>=3.1.5,<4.0.0)
|
|
22
21
|
Requires-Dist: packaging (>=25.0,<26.0)
|
|
23
22
|
Requires-Dist: pairwise-alignments-to-msa (>=0.1.1,<0.2.0)
|
|
24
23
|
Requires-Dist: pandas (>=2.2.3,<3.0.0)
|
|
25
|
-
Requires-Dist: primer3-py (
|
|
24
|
+
Requires-Dist: primer3-py (>=2.3,<3.0)
|
|
26
25
|
Requires-Dist: pydantic (>=2.7.1,<3.0.0)
|
|
27
|
-
Requires-Dist: pydna (
|
|
26
|
+
Requires-Dist: pydna (>=5.5.5,<6.0.0)
|
|
28
27
|
Requires-Dist: python-multipart
|
|
29
28
|
Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
|
30
29
|
Requires-Dist: regex (>=2024.11.6,<2025.0.0)
|
|
@@ -47,9 +46,9 @@ Read [main project readme](https://github.com/manulera/OpenCloning) first.
|
|
|
47
46
|
|
|
48
47
|
This API provides a series of entry points. The API documentation can be accessed [here](https://api.opencloning.org/docs). You can use the documentation page to try some request directly on the browser. Otherwise, the API is open for you to make requests from a python script or command line at: [https://api.opencloning.org/](https://api.opencloning.org/).
|
|
49
48
|
|
|
50
|
-
## Scripting
|
|
49
|
+
## Scripting with pydna
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
You can write python scripts to automate cloning using the python library [pydna](https://github.com/pydna-group/pydna), which is now integrated with the OpenCloning data model. See [the documentation](https://github.com/pydna-group/pydna/blob/master/docs/notebooks/history.ipynb) for how to get started.
|
|
53
52
|
|
|
54
53
|
## Migrating between model versions and fixing model bugs
|
|
55
54
|
|
|
@@ -117,7 +116,7 @@ docker run -d --name backendcontainer -p 8000:8000 manulera/opencloningbackend
|
|
|
117
116
|
|
|
118
117
|
```
|
|
119
118
|
|
|
120
|
-
If you don't want to download the repository and build the image, you can fetch the latest image from dockerhub
|
|
119
|
+
If you don't want to download the repository and build the image, you can fetch the latest image from dockerhub.
|
|
121
120
|
|
|
122
121
|
```bash
|
|
123
122
|
docker pull manulera/opencloningbackend
|
|
@@ -167,7 +166,7 @@ pytest -v -ks
|
|
|
167
166
|
### Ping a particular library version from github:
|
|
168
167
|
|
|
169
168
|
```
|
|
170
|
-
poetry add git+https://github.com/
|
|
169
|
+
poetry add git+https://github.com/pydna-group/pydna#4fd760d075f77cceeb27969e017e04b42f6d0aa3
|
|
171
170
|
```
|
|
172
171
|
|
|
173
172
|
When installing the last version, sometimes poetry may not be able to access the latest version
|
|
@@ -188,3 +187,14 @@ RECORD_STUBS=1 uvicorn opencloning.main:app --reload --reload-exclude='.venv'
|
|
|
188
187
|
|
|
189
188
|
This will record the stubs (requests and responses) in the `stubs` folder.
|
|
190
189
|
|
|
190
|
+
|
|
191
|
+
### Catalogs
|
|
192
|
+
|
|
193
|
+
Catalogs are used to map ids to urls for several plasmid collections. They are stored in the `src/opencloning/catalogs` folder.
|
|
194
|
+
|
|
195
|
+
To update the catalogs, run the following command:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
poetry run python scripts/update_catalogs.py
|
|
199
|
+
```
|
|
200
|
+
|
|
@@ -13,9 +13,9 @@ Read [main project readme](https://github.com/manulera/OpenCloning) first.
|
|
|
13
13
|
|
|
14
14
|
This API provides a series of entry points. The API documentation can be accessed [here](https://api.opencloning.org/docs). You can use the documentation page to try some request directly on the browser. Otherwise, the API is open for you to make requests from a python script or command line at: [https://api.opencloning.org/](https://api.opencloning.org/).
|
|
15
15
|
|
|
16
|
-
## Scripting
|
|
16
|
+
## Scripting with pydna
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
You can write python scripts to automate cloning using the python library [pydna](https://github.com/pydna-group/pydna), which is now integrated with the OpenCloning data model. See [the documentation](https://github.com/pydna-group/pydna/blob/master/docs/notebooks/history.ipynb) for how to get started.
|
|
19
19
|
|
|
20
20
|
## Migrating between model versions and fixing model bugs
|
|
21
21
|
|
|
@@ -83,7 +83,7 @@ docker run -d --name backendcontainer -p 8000:8000 manulera/opencloningbackend
|
|
|
83
83
|
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
If you don't want to download the repository and build the image, you can fetch the latest image from dockerhub
|
|
86
|
+
If you don't want to download the repository and build the image, you can fetch the latest image from dockerhub.
|
|
87
87
|
|
|
88
88
|
```bash
|
|
89
89
|
docker pull manulera/opencloningbackend
|
|
@@ -133,7 +133,7 @@ pytest -v -ks
|
|
|
133
133
|
### Ping a particular library version from github:
|
|
134
134
|
|
|
135
135
|
```
|
|
136
|
-
poetry add git+https://github.com/
|
|
136
|
+
poetry add git+https://github.com/pydna-group/pydna#4fd760d075f77cceeb27969e017e04b42f6d0aa3
|
|
137
137
|
```
|
|
138
138
|
|
|
139
139
|
When installing the last version, sometimes poetry may not be able to access the latest version
|
|
@@ -153,3 +153,14 @@ RECORD_STUBS=1 uvicorn opencloning.main:app --reload --reload-exclude='.venv'
|
|
|
153
153
|
```
|
|
154
154
|
|
|
155
155
|
This will record the stubs (requests and responses) in the `stubs` folder.
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
### Catalogs
|
|
159
|
+
|
|
160
|
+
Catalogs are used to map ids to urls for several plasmid collections. They are stored in the `src/opencloning/catalogs` folder.
|
|
161
|
+
|
|
162
|
+
To update the catalogs, run the following command:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
poetry run python scripts/update_catalogs.py
|
|
166
|
+
```
|
|
@@ -8,7 +8,7 @@ authors = ["Manuel Lera-Ramirez <manulera14@gmail.com>"]
|
|
|
8
8
|
description = "Backend of OpenCloning, a web application to generate molecular cloning strategies in json format, and share them with others."
|
|
9
9
|
license = "MIT"
|
|
10
10
|
name = "opencloning"
|
|
11
|
-
version = "0.
|
|
11
|
+
version = "0.5"
|
|
12
12
|
package-mode = true
|
|
13
13
|
readme = "README.md"
|
|
14
14
|
repository = "https://github.com/manulera/OpenCloning_backend"
|
|
@@ -20,17 +20,16 @@ httpx = "^0.28.1"
|
|
|
20
20
|
python = "^3.11"
|
|
21
21
|
python-multipart = "*"
|
|
22
22
|
uvicorn = "*"
|
|
23
|
-
pydna = "5.5.2"
|
|
24
23
|
regex = "^2024.11.6"
|
|
25
24
|
pydantic = "^2.7.1"
|
|
26
25
|
pandas = "^2.2.3"
|
|
27
26
|
openpyxl = "^3.1.5"
|
|
28
27
|
pyyaml = "^6.0.2"
|
|
29
|
-
|
|
30
|
-
primer3-py = "2.2.0"
|
|
28
|
+
primer3-py = "^2.3"
|
|
31
29
|
biopython = "^1.85"
|
|
32
30
|
packaging = "^25.0"
|
|
33
31
|
pairwise-alignments-to-msa = "^0.1.1"
|
|
32
|
+
pydna = "^5.5.5"
|
|
34
33
|
|
|
35
34
|
[tool.poetry.group.dev.dependencies]
|
|
36
35
|
autopep8 = "^2.0.4"
|
|
@@ -22,6 +22,11 @@ if os.environ.get('ALLOWED_ORIGINS') is not None:
|
|
|
22
22
|
|
|
23
23
|
# External services settings =================================
|
|
24
24
|
NCBI_API_KEY = os.environ.get('NCBI_API_KEY')
|
|
25
|
+
NCBI_MAX_SEQUENCE_LENGTH = (
|
|
26
|
+
int(os.environ.get('NCBI_MAX_SEQUENCE_LENGTH'))
|
|
27
|
+
if os.environ.get('NCBI_MAX_SEQUENCE_LENGTH') is not None
|
|
28
|
+
else 500000
|
|
29
|
+
)
|
|
25
30
|
PLANNOTATE_URL = os.environ['PLANNOTATE_URL'] if 'PLANNOTATE_URL' in os.environ else None
|
|
26
31
|
PLANNOTATE_TIMEOUT = int(os.environ['PLANNOTATE_TIMEOUT']) if 'PLANNOTATE_TIMEOUT' in os.environ else 20
|
|
27
32
|
# Handle trailing slash:
|
|
@@ -58,6 +63,7 @@ class Settings(BaseModel):
|
|
|
58
63
|
BATCH_CLONING: bool
|
|
59
64
|
RECORD_STUBS: bool
|
|
60
65
|
NCBI_API_KEY: str | None
|
|
66
|
+
NCBI_MAX_SEQUENCE_LENGTH: int
|
|
61
67
|
ALLOWED_ORIGINS: list[str]
|
|
62
68
|
PLANNOTATE_URL: str | None
|
|
63
69
|
PLANNOTATE_TIMEOUT: int
|
|
@@ -73,6 +79,7 @@ settings = Settings(
|
|
|
73
79
|
BATCH_CLONING=BATCH_CLONING,
|
|
74
80
|
RECORD_STUBS=RECORD_STUBS,
|
|
75
81
|
NCBI_API_KEY=NCBI_API_KEY,
|
|
82
|
+
NCBI_MAX_SEQUENCE_LENGTH=NCBI_MAX_SEQUENCE_LENGTH,
|
|
76
83
|
ALLOWED_ORIGINS=ALLOWED_ORIGINS,
|
|
77
84
|
PLANNOTATE_URL=PLANNOTATE_URL,
|
|
78
85
|
PLANNOTATE_TIMEOUT=PLANNOTATE_TIMEOUT,
|
|
@@ -70,8 +70,8 @@ async def post_batch_cloning(
|
|
|
70
70
|
try:
|
|
71
71
|
pombe_summary(temp_dir)
|
|
72
72
|
pombe_gather(temp_dir)
|
|
73
|
-
except Exception:
|
|
74
|
-
raise HTTPException(status_code=400, detail='Summary failed')
|
|
73
|
+
except Exception as e:
|
|
74
|
+
raise HTTPException(status_code=400, detail=f'Summary failed: {e}')
|
|
75
75
|
|
|
76
76
|
# zip the temp dir and return it
|
|
77
77
|
zip_filename = f'{temp_dir}_archive'
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pydna.assembly2 import homologous_recombination_integration, pcr_assembly
|
|
3
|
+
from opencloning.dna_functions import request_from_addgene
|
|
4
|
+
from opencloning.ncbi_requests import get_annotations_from_query, get_genome_region_from_annotation
|
|
5
|
+
import asyncio
|
|
6
|
+
from Bio import SeqIO
|
|
7
|
+
from pydna.primer import Primer
|
|
8
|
+
from pydna.opencloning_models import CloningStrategy
|
|
9
|
+
from fastapi.datastructures import UploadFile
|
|
10
|
+
from pydna.parsers import parse as pydna_parse
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def main(
|
|
14
|
+
gene: str,
|
|
15
|
+
assembly_accession: str,
|
|
16
|
+
output_dir: str,
|
|
17
|
+
plasmid_input: UploadFile | str = '19343',
|
|
18
|
+
padding: int = 1000,
|
|
19
|
+
):
|
|
20
|
+
print(f"\033[92mCloning {gene}\033[0m")
|
|
21
|
+
# Parse primers =================================================================================
|
|
22
|
+
primers = [Primer(p) for p in SeqIO.parse(os.path.join(output_dir, gene, 'primers.fa'), 'fasta')]
|
|
23
|
+
common_primers = [Primer(p) for p in SeqIO.parse(os.path.join(output_dir, 'checking_primers.fa'), 'fasta')]
|
|
24
|
+
|
|
25
|
+
# Get plasmid sequence =================================================================================
|
|
26
|
+
if isinstance(plasmid_input, UploadFile):
|
|
27
|
+
file_content = (await plasmid_input.read()).decode()
|
|
28
|
+
|
|
29
|
+
plasmid = pydna_parse(file_content)[0]
|
|
30
|
+
else:
|
|
31
|
+
plasmid = await request_from_addgene(plasmid_input)
|
|
32
|
+
|
|
33
|
+
# Get genome region =====================================================================
|
|
34
|
+
annotations = await get_annotations_from_query(gene, assembly_accession)
|
|
35
|
+
if len(annotations) == 0:
|
|
36
|
+
raise ValueError(f'No annotations found for {gene}')
|
|
37
|
+
|
|
38
|
+
annotations = [a for a in annotations if gene.upper() in a['locus_tag'].upper()]
|
|
39
|
+
if len(annotations) != 1:
|
|
40
|
+
raise ValueError(f'No right annotation found for {gene}')
|
|
41
|
+
|
|
42
|
+
locus = await get_genome_region_from_annotation(annotations[0], 1000, 1000)
|
|
43
|
+
|
|
44
|
+
# PCR ================================================================================================
|
|
45
|
+
pcr_products = pcr_assembly(plasmid, primers[0], primers[1], limit=14, mismatches=0)
|
|
46
|
+
pcr_products[0].name = 'amplified_marker'
|
|
47
|
+
alleles = homologous_recombination_integration(locus, [pcr_products[0]], 40)
|
|
48
|
+
pcr_check1 = pcr_assembly(alleles[0], primers[2], common_primers[1], limit=14, mismatches=0)[0]
|
|
49
|
+
pcr_check1.name = 'check_pcr_left'
|
|
50
|
+
pcr_check2 = pcr_assembly(alleles[0], primers[3], common_primers[0], limit=14, mismatches=0)[0]
|
|
51
|
+
pcr_check2.name = 'check_pcr_right'
|
|
52
|
+
|
|
53
|
+
cs = CloningStrategy.from_dseqrecords([pcr_check1, pcr_check2])
|
|
54
|
+
|
|
55
|
+
if not os.path.exists(os.path.join(output_dir, gene)):
|
|
56
|
+
os.makedirs(os.path.join(output_dir, gene))
|
|
57
|
+
|
|
58
|
+
with open(os.path.join(output_dir, gene, 'cloning_strategy.json'), 'w') as f:
|
|
59
|
+
f.write(cs.model_dump_json(indent=2))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == '__main__':
|
|
63
|
+
import argparse
|
|
64
|
+
|
|
65
|
+
parser = argparse.ArgumentParser(description='List of genes to delete from S. pombe')
|
|
66
|
+
parser.add_argument(
|
|
67
|
+
'--genes', type=str, required=True, help='Path to a file containing a list of genes, one per line'
|
|
68
|
+
)
|
|
69
|
+
args = parser.parse_args()
|
|
70
|
+
|
|
71
|
+
parser.add_argument(
|
|
72
|
+
'--assembly_accession',
|
|
73
|
+
type=str,
|
|
74
|
+
default='GCF_000002945.2',
|
|
75
|
+
help='Assembly accession for S. pombe genome (default: GCF_000002945.2)',
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
parser.add_argument(
|
|
79
|
+
'--output_dir',
|
|
80
|
+
type=str,
|
|
81
|
+
default='batch_cloning_output',
|
|
82
|
+
help='Directory to save the output files (default: batch_cloning_output)',
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
parser.add_argument(
|
|
86
|
+
'--plasmid',
|
|
87
|
+
type=str,
|
|
88
|
+
default='19343',
|
|
89
|
+
help='Addgene ID for the plasmid (default: 19343)',
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
args = parser.parse_args()
|
|
93
|
+
assembly_accession = args.assembly_accession
|
|
94
|
+
|
|
95
|
+
with open(args.genes, 'r') as f:
|
|
96
|
+
genes = [line.strip() for line in f if line.strip()]
|
|
97
|
+
|
|
98
|
+
if not os.path.exists(args.output_dir):
|
|
99
|
+
os.makedirs(args.output_dir)
|
|
100
|
+
|
|
101
|
+
for gene in genes:
|
|
102
|
+
asyncio.run(main(gene, assembly_accession, args.output_dir, args.plasmid))
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
from
|
|
1
|
+
from pydna.utils import location_boundaries
|
|
2
|
+
from ...pydantic_models import BaseCloningStrategy
|
|
3
|
+
from opencloning_linkml.datamodel import (
|
|
4
|
+
Primer as PrimerModel,
|
|
5
|
+
PCRSource,
|
|
6
|
+
HomologousRecombinationSource,
|
|
7
|
+
)
|
|
2
8
|
from pydna.parsers import parse as pydna_parse
|
|
3
9
|
import os
|
|
4
10
|
import json
|
|
5
11
|
from pydna import tm
|
|
6
12
|
from Bio.Seq import reverse_complement
|
|
7
13
|
import argparse
|
|
14
|
+
from Bio.SeqFeature import Location
|
|
8
15
|
|
|
9
16
|
chromosomes = {
|
|
10
17
|
'NC_003424.3': 'I',
|
|
@@ -17,11 +24,11 @@ chromosomes = {
|
|
|
17
24
|
def find_primer_aligned_sequence(pcr_sources: list[PCRSource], primer: PrimerModel) -> str:
|
|
18
25
|
for source in pcr_sources:
|
|
19
26
|
if source.input[0].sequence == primer.id:
|
|
20
|
-
loc = source.input[0].right_location
|
|
21
|
-
return
|
|
27
|
+
loc = Location.fromstring(source.input[0].right_location)
|
|
28
|
+
return loc.extract(primer.sequence)
|
|
22
29
|
if source.input[-1].sequence == primer.id:
|
|
23
|
-
loc = source.input[-1].left_location
|
|
24
|
-
return
|
|
30
|
+
loc = Location.fromstring(source.input[-1].left_location)
|
|
31
|
+
return loc.extract(reverse_complement(primer.sequence))
|
|
25
32
|
raise ValueError(f"Primer {primer.id} not found in any PCR source")
|
|
26
33
|
|
|
27
34
|
|
|
@@ -33,13 +40,18 @@ def process_folder(working_dir: str):
|
|
|
33
40
|
# We do this to have action to .end and .start
|
|
34
41
|
pcr_sources = [PCRSource.model_validate(s.model_dump()) for s in pcr_sources]
|
|
35
42
|
locus_source = next(s for s in strategy.sources if s.type == 'GenomeCoordinatesSource')
|
|
43
|
+
locus_location = Location.fromstring(locus_source.coordinates)
|
|
36
44
|
hrec_source = next(s for s in strategy.sources if s.type == 'HomologousRecombinationSource')
|
|
37
45
|
# We do this to have action to .end and .start
|
|
38
46
|
hrec_source: HomologousRecombinationSource = HomologousRecombinationSource.model_validate(hrec_source.model_dump())
|
|
39
47
|
|
|
40
|
-
chromosome = chromosomes[locus_source.
|
|
41
|
-
insertion_start =
|
|
42
|
-
|
|
48
|
+
chromosome = chromosomes[locus_source.repository_id]
|
|
49
|
+
insertion_start = (
|
|
50
|
+
locus_location.start + location_boundaries(Location.fromstring(hrec_source.input[0].right_location))[1]
|
|
51
|
+
)
|
|
52
|
+
insertion_end = (
|
|
53
|
+
locus_location.start + location_boundaries(Location.fromstring(hrec_source.input[-1].left_location))[0]
|
|
54
|
+
)
|
|
43
55
|
|
|
44
56
|
# Write out the sequences in genbank format and extract some relevant info
|
|
45
57
|
sequences = [pydna_parse(sequence.file_content)[0] for sequence in strategy.sequences]
|
{opencloning-0.4.7 → opencloning-0.5}/src/opencloning/batch_cloning/ziqiang_et_al2024/__init__.py
RENAMED
|
@@ -6,10 +6,10 @@ from typing import Annotated
|
|
|
6
6
|
from fastapi import Body, Query, HTTPException
|
|
7
7
|
|
|
8
8
|
from ...get_router import get_router
|
|
9
|
-
from ...pydantic_models import
|
|
10
|
-
|
|
11
|
-
PrimerModel,
|
|
9
|
+
from ...pydantic_models import BaseCloningStrategy
|
|
10
|
+
from opencloning_linkml.datamodel import (
|
|
12
11
|
TextFileSequence,
|
|
12
|
+
Primer as PrimerModel,
|
|
13
13
|
PCRSource,
|
|
14
14
|
RestrictionAndLigationSource,
|
|
15
15
|
GatewaySource,
|
|
@@ -96,7 +96,7 @@ async def ziqiang_et_al2024_post(
|
|
|
96
96
|
else:
|
|
97
97
|
name = f'end_ps{i}_start_ps{i + 1}'
|
|
98
98
|
|
|
99
|
-
pcr_source = PCRSource(id=0, output_name=name)
|
|
99
|
+
pcr_source = PCRSource(id=0, input=[], output_name=name)
|
|
100
100
|
fwd_primer = next(p for p in cloning_strategy.primers if p.id == fwd_primer_id)
|
|
101
101
|
rvs_primer = next(p for p in cloning_strategy.primers if p.id == rvs_primer_id)
|
|
102
102
|
|
|
@@ -112,18 +112,18 @@ async def ziqiang_et_al2024_post(
|
|
|
112
112
|
|
|
113
113
|
# Make all input of a Golden gate assembly
|
|
114
114
|
golden_gate_source = RestrictionAndLigationSource(
|
|
115
|
-
id=0, output_name='golden_gate_assembly', restriction_enzymes=['BsaI']
|
|
115
|
+
id=0, input=[], output_name='golden_gate_assembly', restriction_enzymes=['BsaI']
|
|
116
116
|
)
|
|
117
117
|
|
|
118
118
|
# Make them
|
|
119
119
|
input_sequences = [next(s for s in cloning_strategy.sequences if s.id == p) for p in pcr_product_ids]
|
|
120
|
-
resp = await restriction_and_ligation(golden_gate_source, input_sequences, False
|
|
120
|
+
resp = await restriction_and_ligation(golden_gate_source, input_sequences, False)
|
|
121
121
|
golden_gate_product: TextFileSequence = TextFileSequence.model_validate(resp['sequences'][0])
|
|
122
122
|
golden_gate_source: RestrictionAndLigationSource = RestrictionAndLigationSource.model_validate(resp['sources'][0])
|
|
123
123
|
cloning_strategy.add_source_and_sequence(golden_gate_source, golden_gate_product)
|
|
124
124
|
|
|
125
125
|
bp_target = next(s for s in cloning_strategy.sequences if s.id == 6)
|
|
126
|
-
gateway_source = GatewaySource(id=0, output_name='entry_clone', reaction_type='BP', greedy=False)
|
|
126
|
+
gateway_source = GatewaySource(id=0, input=[], output_name='entry_clone', reaction_type='BP', greedy=False)
|
|
127
127
|
resp = await gateway(gateway_source, [golden_gate_product, bp_target], circular_only=True, only_multi_site=True)
|
|
128
128
|
gateway_product: TextFileSequence = TextFileSequence.model_validate(resp['sequences'][0])
|
|
129
129
|
gateway_source: GatewaySource = GatewaySource.model_validate(resp['sources'][0])
|
|
@@ -141,7 +141,7 @@ async def ziqiang_et_al2024_post(
|
|
|
141
141
|
all_input_ids = [s.sequence for s in all_inputs]
|
|
142
142
|
sequences_to_clone = [s for s in cloning_strategy.sequences if s.id not in all_input_ids]
|
|
143
143
|
|
|
144
|
-
gateway_source = GatewaySource(id=0, output_name='expression_clone', reaction_type='LR', greedy=False)
|
|
144
|
+
gateway_source = GatewaySource(id=0, input=[], output_name='expression_clone', reaction_type='LR', greedy=False)
|
|
145
145
|
resp = await gateway(gateway_source, sequences_to_clone, circular_only=True, only_multi_site=True)
|
|
146
146
|
index_of_product = next(i for i, s in enumerate(resp['sequences']) if '/label="Cas9"' in s.file_content)
|
|
147
147
|
expression_clone: TextFileSequence = TextFileSequence.model_validate(resp['sequences'][index_of_product])
|
|
@@ -81,7 +81,6 @@
|
|
|
81
81
|
"database_id": null,
|
|
82
82
|
"input": [],
|
|
83
83
|
"repository_id": "71287",
|
|
84
|
-
"repository_name": "addgene",
|
|
85
84
|
"sequence_file_url": "https://media.addgene.org/snapgene-media/v2.0.0/sequences/352460/6c8b0369-e549-4517-95da-8d07146ca49d/addgene-plasmid-71287-sequence-352460.gbk",
|
|
86
85
|
"addgene_sequence_type": "addgene-full"
|
|
87
86
|
},
|
|
@@ -92,7 +91,6 @@
|
|
|
92
91
|
"database_id": null,
|
|
93
92
|
"input": [],
|
|
94
93
|
"repository_id": "71287",
|
|
95
|
-
"repository_name": "addgene",
|
|
96
94
|
"sequence_file_url": "https://media.addgene.org/snapgene-media/v2.0.0/sequences/352460/6c8b0369-e549-4517-95da-8d07146ca49d/addgene-plasmid-71287-sequence-352460.gbk",
|
|
97
95
|
"addgene_sequence_type": "addgene-full"
|
|
98
96
|
},
|
|
@@ -103,7 +101,6 @@
|
|
|
103
101
|
"database_id": null,
|
|
104
102
|
"input": [],
|
|
105
103
|
"repository_id": "213912",
|
|
106
|
-
"repository_name": "addgene",
|
|
107
104
|
"sequence_file_url": "https://media.addgene.org/snapgene-media/v2.0.0/sequences/437264/8bd82b44-a1c3-4f81-8936-ebcc16d171e9/addgene-plasmid-213912-sequence-437264.gbk",
|
|
108
105
|
"addgene_sequence_type": "addgene-full"
|
|
109
106
|
},
|
|
@@ -114,7 +111,6 @@
|
|
|
114
111
|
"database_id": null,
|
|
115
112
|
"input": [],
|
|
116
113
|
"repository_id": "213913",
|
|
117
|
-
"repository_name": "addgene",
|
|
118
114
|
"sequence_file_url": "https://media.addgene.org/snapgene-media/v2.0.0/sequences/437263/af62d64e-1f22-4dce-aafa-7935d1665700/addgene-plasmid-213913-sequence-437263.gbk",
|
|
119
115
|
"addgene_sequence_type": "addgene-full"
|
|
120
116
|
},
|
|
@@ -125,7 +121,6 @@
|
|
|
125
121
|
"database_id": null,
|
|
126
122
|
"input": [],
|
|
127
123
|
"repository_id": "133748",
|
|
128
|
-
"repository_name": "addgene",
|
|
129
124
|
"sequence_file_url": "https://media.addgene.org/snapgene-media/v2.0.0/sequences/271264/c0ae5f43-46e6-4175-a0c1-9a00cbb92034/addgene-plasmid-133748-sequence-271264.gbk",
|
|
130
125
|
"addgene_sequence_type": "addgene-full"
|
|
131
126
|
},
|
|
@@ -135,8 +130,7 @@
|
|
|
135
130
|
"output_name": "pDONR_P2r-P3",
|
|
136
131
|
"database_id": null,
|
|
137
132
|
"input": [],
|
|
138
|
-
"repository_id": "gateway_cloning_vectors/pDONR_P2r-P3"
|
|
139
|
-
"repository_name": "snapgene"
|
|
133
|
+
"repository_id": "gateway_cloning_vectors/pDONR_P2r-P3"
|
|
140
134
|
},
|
|
141
135
|
{
|
|
142
136
|
"id": 7,
|
|
@@ -207,7 +201,6 @@
|
|
|
207
201
|
"database_id": null,
|
|
208
202
|
"input": [],
|
|
209
203
|
"repository_id": "63143",
|
|
210
|
-
"repository_name": "addgene",
|
|
211
204
|
"sequence_file_url": "https://media.addgene.org/snapgene-media/v2.0.0/sequences/223326/8c81fe9a-fb57-40bc-93d6-e17d83502061/addgene-plasmid-63143-sequence-223326.gbk",
|
|
212
205
|
"addgene_sequence_type": "addgene-full"
|
|
213
206
|
}
|
|
@@ -258,7 +251,7 @@
|
|
|
258
251
|
],
|
|
259
252
|
"description": "",
|
|
260
253
|
"files": null,
|
|
261
|
-
"schema_version": "0.4.
|
|
254
|
+
"schema_version": "0.4.9",
|
|
262
255
|
"backend_version": null,
|
|
263
256
|
"frontend_version": null
|
|
264
257
|
}
|
|
@@ -4,10 +4,12 @@ See info in README.md
|
|
|
4
4
|
|
|
5
5
|
from ..pydantic_models import (
|
|
6
6
|
BaseCloningStrategy as CloningStrategy,
|
|
7
|
+
)
|
|
8
|
+
from pydna.opencloning_models import SequenceLocationStr
|
|
9
|
+
from opencloning_linkml.datamodel import (
|
|
7
10
|
AssemblySource,
|
|
8
11
|
TextFileSequence,
|
|
9
|
-
PrimerModel,
|
|
10
|
-
SequenceLocationStr,
|
|
12
|
+
Primer as PrimerModel,
|
|
11
13
|
)
|
|
12
14
|
from .._version import __version__
|
|
13
15
|
import json
|
|
@@ -50,9 +52,15 @@ def fix_backend_v0_3(input_data: dict) -> CloningStrategy | None:
|
|
|
50
52
|
primers = [PrimerModel.model_validate(p) for p in data['primers'] if p['id'] in primer_ids]
|
|
51
53
|
input_seqs = [primers[0], input_seqs[0], primers[1]]
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
assembly_fragments = [a for a in assembly_source.input if a.type == 'AssemblyFragment']
|
|
56
|
+
|
|
57
|
+
for prev_f, next_f in zip(assembly_fragments, assembly_fragments[1:] + assembly_fragments[:1]):
|
|
58
|
+
left = prev_f.right_location
|
|
59
|
+
right = next_f.left_location
|
|
60
|
+
if (left is not None and right is not None) and (
|
|
61
|
+
len(SequenceLocationStr(left).to_biopython_location())
|
|
62
|
+
!= len(SequenceLocationStr(right).to_biopython_location())
|
|
63
|
+
):
|
|
56
64
|
problematic_source_ids.add(source['id'])
|
|
57
65
|
break
|
|
58
66
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import yaml
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_seva_catalog():
|
|
6
|
+
seva_catalog_path = os.path.join(os.path.dirname(__file__), 'seva.tsv')
|
|
7
|
+
seva_catalog = dict()
|
|
8
|
+
with open(seva_catalog_path, 'r') as f:
|
|
9
|
+
for line in f:
|
|
10
|
+
name, genbank_link = line.strip().split('\t')
|
|
11
|
+
seva_catalog[name] = genbank_link
|
|
12
|
+
return seva_catalog
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_snapgene_catalog():
|
|
16
|
+
snapgene_catalog_path = os.path.join(os.path.dirname(__file__), 'snapgene.yaml')
|
|
17
|
+
with open(snapgene_catalog_path, 'r') as f:
|
|
18
|
+
return yaml.safe_load(f)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_openDNA_collections_catalog():
|
|
22
|
+
catalog_path = os.path.join(os.path.dirname(__file__), 'openDNA_collections.yaml')
|
|
23
|
+
with open(catalog_path, 'r') as f:
|
|
24
|
+
return yaml.safe_load(f)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_iGEM2024_catalog():
|
|
28
|
+
catalog_path = os.path.join(os.path.dirname(__file__), 'igem2024.yaml')
|
|
29
|
+
with open(catalog_path, 'r') as f:
|
|
30
|
+
return yaml.safe_load(f)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
seva_catalog = get_seva_catalog()
|
|
34
|
+
snapgene_catalog = get_snapgene_catalog()
|
|
35
|
+
openDNA_collections_catalog = get_openDNA_collections_catalog()
|
|
36
|
+
iGEM2024_catalog = get_iGEM2024_catalog()
|