opencloning 0.2.8__tar.gz → 0.2.8.1__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.2.8 → opencloning-0.2.8.1}/PKG-INFO +2 -2
- {opencloning-0.2.8 → opencloning-0.2.8.1}/pyproject.toml +2 -2
- opencloning-0.2.8.1/src/opencloning/cre_lox.py +58 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/endpoints/external_import.py +59 -13
- opencloning-0.2.8/src/opencloning/cre_lox.py +0 -29
- {opencloning-0.2.8 → opencloning-0.2.8.1}/LICENSE +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/README.md +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/__init__.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/api_config_utils.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/app_settings.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/assembly2.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/EBIC/__init__.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/EBIC/barcode.gb +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/EBIC/common_plasmid.gb +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/EBIC/example.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/EBIC/primer_design_settings.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/__init__.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/index.html +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/pombe/__init__.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/pombe/index.html +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/pombe/pombe_all.sh +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/pombe/pombe_clone.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/pombe/pombe_gather.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/pombe/pombe_get_primers.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/pombe/pombe_summary.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/ziqiang_et_al2024/__init__.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/ziqiang_et_al2024/index.html +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/ziqiang_et_al2024/ziqiang_et_al2024.json +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/dna_functions.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/dna_utils.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/ebic/__init__.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/ebic/primer_design.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/ebic/primer_design_settings.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/endpoints/annotation.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/endpoints/assembly.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/endpoints/no_assembly.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/endpoints/no_input.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/endpoints/other.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/endpoints/primer_design.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/gateway.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/get_router.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/main.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/ncbi_requests.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/primer_design.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/pydantic_models.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/request_examples.py +0 -0
- {opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: opencloning
|
|
3
|
-
Version: 0.2.8
|
|
3
|
+
Version: 0.2.8.1
|
|
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.
|
|
18
|
+
Requires-Dist: opencloning-linkml (==0.2.6.1a0)
|
|
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)
|
|
@@ -3,7 +3,7 @@ authors = ["Manuel Lera-Ramirez <manulera14@gmail.com>"]
|
|
|
3
3
|
description = "Backend of OpenCloning, a web application to generate molecular cloning strategies in json format, and share them with others."
|
|
4
4
|
license = "MIT"
|
|
5
5
|
name = "opencloning"
|
|
6
|
-
version = "v0.2.8"
|
|
6
|
+
version = "v0.2.8.1"
|
|
7
7
|
package-mode = true
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
repository = "https://github.com/manulera/OpenCloning_backend"
|
|
@@ -22,7 +22,7 @@ pydantic = "^2.7.1"
|
|
|
22
22
|
pandas = "^2.2.3"
|
|
23
23
|
openpyxl = "^3.1.5"
|
|
24
24
|
pyyaml = "^6.0.2"
|
|
25
|
-
opencloning-linkml = "0.2.
|
|
25
|
+
opencloning-linkml = "0.2.6.1a0"
|
|
26
26
|
primer3-py = "^2.0.3"
|
|
27
27
|
biopython = "1.84"
|
|
28
28
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from itertools import product
|
|
2
|
+
from pydna.dseqrecord import Dseqrecord
|
|
3
|
+
from Bio.Data.IUPACData import ambiguous_dna_values
|
|
4
|
+
from Bio.Seq import reverse_complement
|
|
5
|
+
from .dna_utils import compute_regex_site, dseqrecord_finditer
|
|
6
|
+
|
|
7
|
+
# We create a dictionary to map ambiguous bases to their consensus base
|
|
8
|
+
# For example, ambigous_base_dict['ACGT'] -> 'N'
|
|
9
|
+
ambiguous_base_dict = {}
|
|
10
|
+
for ambiguous, bases in ambiguous_dna_values.items():
|
|
11
|
+
ambiguous_base_dict[''.join(sorted(bases))] = ambiguous
|
|
12
|
+
|
|
13
|
+
# To handle N values
|
|
14
|
+
ambiguous_base_dict['N'] = 'N'
|
|
15
|
+
|
|
16
|
+
# This is the original loxP sequence, here for reference
|
|
17
|
+
LOXP_SEQUENCE = 'ATAACTTCGTATAGCATACATTATACGAAGTTAT'
|
|
18
|
+
|
|
19
|
+
loxP_sequences = [
|
|
20
|
+
# https://blog.addgene.org/plasmids-101-cre-lox
|
|
21
|
+
# loxP
|
|
22
|
+
'ATAACTTCGTATANNNTANNNTATACGAAGTTAT',
|
|
23
|
+
# PMID:12202778
|
|
24
|
+
# lox66
|
|
25
|
+
'ATAACTTCGTATANNNTANNNTATACGAACGGTA',
|
|
26
|
+
# lox71
|
|
27
|
+
'TACCGTTCGTATANNNTANNNTATACGAAGTTAT',
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
loxP_consensus = ''
|
|
31
|
+
|
|
32
|
+
for pos in range(len(LOXP_SEQUENCE)):
|
|
33
|
+
all_letters = set(seq[pos] for seq in loxP_sequences)
|
|
34
|
+
key = ''.join(sorted(all_letters))
|
|
35
|
+
loxP_consensus += ambiguous_base_dict[key]
|
|
36
|
+
|
|
37
|
+
# We compute the regex for the forward and reverse loxP sequences
|
|
38
|
+
loxP_regex = (compute_regex_site(loxP_consensus), compute_regex_site(reverse_complement(loxP_consensus)))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def cre_loxP_overlap(x: Dseqrecord, y: Dseqrecord, _l: None = None) -> list[tuple[int, int, int]]:
|
|
42
|
+
"""Find matching loxP sites between two sequences."""
|
|
43
|
+
out = list()
|
|
44
|
+
for pattern in loxP_regex:
|
|
45
|
+
matches_x = dseqrecord_finditer(pattern, x)
|
|
46
|
+
matches_y = dseqrecord_finditer(pattern, y)
|
|
47
|
+
|
|
48
|
+
for match_x, match_y in product(matches_x, matches_y):
|
|
49
|
+
value_x = match_x.group()
|
|
50
|
+
value_y = match_y.group()
|
|
51
|
+
if value_x[13:21] == value_y[13:21]:
|
|
52
|
+
out.append((match_x.start() + 13, match_y.start() + 13, 8))
|
|
53
|
+
# Unique values (keeping the order)
|
|
54
|
+
unique_out = []
|
|
55
|
+
for item in out:
|
|
56
|
+
if item not in unique_out:
|
|
57
|
+
unique_out.append(item)
|
|
58
|
+
return unique_out
|
|
@@ -8,6 +8,8 @@ from starlette.responses import RedirectResponse
|
|
|
8
8
|
from Bio import BiopythonParserWarning
|
|
9
9
|
from typing import Annotated
|
|
10
10
|
from urllib.error import HTTPError
|
|
11
|
+
from pydna.utils import location_boundaries
|
|
12
|
+
|
|
11
13
|
from ..get_router import get_router
|
|
12
14
|
from ..pydantic_models import (
|
|
13
15
|
TextFileSequence,
|
|
@@ -22,6 +24,7 @@ from ..pydantic_models import (
|
|
|
22
24
|
GenomeCoordinatesSource,
|
|
23
25
|
SequenceFileFormat,
|
|
24
26
|
SEVASource,
|
|
27
|
+
SimpleSequenceLocation,
|
|
25
28
|
)
|
|
26
29
|
from ..dna_functions import (
|
|
27
30
|
format_sequence_genbank,
|
|
@@ -51,13 +54,13 @@ router = get_router()
|
|
|
51
54
|
'description': 'The sequence was successfully parsed',
|
|
52
55
|
'headers': {
|
|
53
56
|
'x-warning': {
|
|
54
|
-
'description': 'A warning returned if the file can be read but is not in the expected format',
|
|
57
|
+
'description': 'A warning returned if the file can be read but is not in the expected format or if some sequences were not extracted because they are incompatible with the provided coordinates',
|
|
55
58
|
'schema': {'type': 'string'},
|
|
56
59
|
},
|
|
57
60
|
},
|
|
58
61
|
},
|
|
59
62
|
422: {
|
|
60
|
-
'description': 'Biopython cannot process this file.',
|
|
63
|
+
'description': 'Biopython cannot process this file or provided coordinates are invalid.',
|
|
61
64
|
},
|
|
62
65
|
404: {
|
|
63
66
|
'description': 'The index_in_file is out of range.',
|
|
@@ -83,6 +86,12 @@ async def read_from_file(
|
|
|
83
86
|
None,
|
|
84
87
|
description='Name of the output sequence',
|
|
85
88
|
),
|
|
89
|
+
start: int | None = Query(None, description='Start position of the sequence to read (0-based)', ge=0),
|
|
90
|
+
end: int | None = Query(
|
|
91
|
+
None,
|
|
92
|
+
description='End position of the sequence to read (0-based)',
|
|
93
|
+
ge=0,
|
|
94
|
+
),
|
|
86
95
|
):
|
|
87
96
|
"""Return a json sequence from a sequence file"""
|
|
88
97
|
|
|
@@ -107,6 +116,7 @@ async def read_from_file(
|
|
|
107
116
|
sequence_file_format = SequenceFileFormat(extension_dict[extension])
|
|
108
117
|
|
|
109
118
|
dseqs = list()
|
|
119
|
+
warning_messages = list()
|
|
110
120
|
|
|
111
121
|
file_content = await file.read()
|
|
112
122
|
if sequence_file_format == 'snapgene':
|
|
@@ -124,7 +134,6 @@ async def read_from_file(
|
|
|
124
134
|
|
|
125
135
|
if warnings_captured:
|
|
126
136
|
warning_messages = [str(w.message) for w in warnings_captured]
|
|
127
|
-
response.headers['x-warning'] = '; '.join(warning_messages)
|
|
128
137
|
|
|
129
138
|
except ValueError as e:
|
|
130
139
|
raise HTTPException(422, f'Biopython cannot process this file: {e}.')
|
|
@@ -134,25 +143,62 @@ async def read_from_file(
|
|
|
134
143
|
if len(dseqs) == 0:
|
|
135
144
|
raise HTTPException(422, 'Biopython cannot process this file.')
|
|
136
145
|
|
|
146
|
+
if index_in_file is not None:
|
|
147
|
+
if index_in_file >= len(dseqs):
|
|
148
|
+
raise HTTPException(404, 'The index_in_file is out of range.')
|
|
149
|
+
dseqs = [dseqs[index_in_file]]
|
|
150
|
+
|
|
151
|
+
seq_feature = None
|
|
152
|
+
if start is not None and end is not None:
|
|
153
|
+
seq_feature = SimpleSequenceLocation(start=start, end=end)
|
|
154
|
+
extracted_sequences = list()
|
|
155
|
+
for dseq in dseqs:
|
|
156
|
+
try:
|
|
157
|
+
# TODO: We could use extract when this is addressed: https://github.com/biopython/biopython/issues/4989
|
|
158
|
+
location = seq_feature.to_biopython_location(circular=dseq.circular, seq_len=len(dseq))
|
|
159
|
+
i, j = location_boundaries(location)
|
|
160
|
+
extracted_sequence = dseq[i:j]
|
|
161
|
+
# Only add the sequence if the interval is not out of bounds
|
|
162
|
+
if len(extracted_sequence) == len(location):
|
|
163
|
+
extracted_sequences.append(extracted_sequence)
|
|
164
|
+
else:
|
|
165
|
+
extracted_sequences.append(None)
|
|
166
|
+
except Exception:
|
|
167
|
+
extracted_sequences.append(None)
|
|
168
|
+
dseqs = extracted_sequences
|
|
169
|
+
|
|
137
170
|
# The common part
|
|
138
|
-
# TODO: using id=0 is not great
|
|
139
171
|
parent_source = UploadedFileSource(
|
|
140
|
-
id=0,
|
|
172
|
+
id=0,
|
|
173
|
+
sequence_file_format=sequence_file_format,
|
|
174
|
+
file_name=file.filename,
|
|
175
|
+
circularize=circularize,
|
|
176
|
+
coordinates=seq_feature,
|
|
141
177
|
)
|
|
178
|
+
|
|
179
|
+
# If coordinates are provided, we only keep the sequences compatible with those coordinates
|
|
142
180
|
out_sources = list()
|
|
181
|
+
out_sequences = list()
|
|
143
182
|
for i in range(len(dseqs)):
|
|
183
|
+
if dseqs[i] is None:
|
|
184
|
+
continue
|
|
144
185
|
new_source = parent_source.model_copy()
|
|
145
|
-
new_source.index_in_file = i
|
|
186
|
+
new_source.index_in_file = index_in_file if index_in_file is not None else i
|
|
146
187
|
out_sources.append(new_source)
|
|
188
|
+
out_sequences.append(format_sequence_genbank(dseqs[i], output_name))
|
|
147
189
|
|
|
148
|
-
|
|
190
|
+
if len(out_sequences) == 0:
|
|
191
|
+
raise HTTPException(422, 'Provided coordinates are incompatible with sequences in the file.')
|
|
149
192
|
|
|
150
|
-
if
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
193
|
+
if len(out_sequences) < len(dseqs):
|
|
194
|
+
warning_messages.append(
|
|
195
|
+
'Some sequences were not extracted because they are incompatible with the provided coordinates.'
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
if len(warning_messages) > 0:
|
|
199
|
+
response.headers['x-warning'] = '; '.join(warning_messages)
|
|
200
|
+
|
|
201
|
+
return {'sequences': out_sequences, 'sources': out_sources}
|
|
156
202
|
|
|
157
203
|
|
|
158
204
|
# TODO: a bit inconsistent that here you don't put {source: {...}} in the request, but
|
|
@@ -1,29 +0,0 @@
|
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/EBIC/common_plasmid.gb
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/pombe/pombe_clone.py
RENAMED
|
File without changes
|
{opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/pombe/pombe_gather.py
RENAMED
|
File without changes
|
{opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/pombe/pombe_get_primers.py
RENAMED
|
File without changes
|
{opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/pombe/pombe_summary.py
RENAMED
|
File without changes
|
|
File without changes
|
{opencloning-0.2.8 → opencloning-0.2.8.1}/src/opencloning/batch_cloning/ziqiang_et_al2024/index.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|