opencloning 0.2.7.3__py3-none-any.whl → 0.2.8.1__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 +58 -0
- opencloning/dna_utils.py +23 -0
- opencloning/endpoints/assembly.py +45 -7
- opencloning/endpoints/external_import.py +59 -13
- opencloning/gateway.py +1 -22
- opencloning/pydantic_models.py +10 -0
- {opencloning-0.2.7.3.dist-info → opencloning-0.2.8.1.dist-info}/METADATA +2 -2
- {opencloning-0.2.7.3.dist-info → opencloning-0.2.8.1.dist-info}/RECORD +10 -9
- {opencloning-0.2.7.3.dist-info → opencloning-0.2.8.1.dist-info}/LICENSE +0 -0
- {opencloning-0.2.7.3.dist-info → opencloning-0.2.8.1.dist-info}/WHEEL +0 -0
opencloning/cre_lox.py
ADDED
|
@@ -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
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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=(
|
|
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
|
|
@@ -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
|
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 = {
|
opencloning/pydantic_models.py
CHANGED
|
@@ -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.
|
|
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)
|
|
@@ -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=mb2ZddjrPIrUBT3xxMub5-c97WkKZ4Z-HkGFVzuR8pQ,2031
|
|
22
23
|
opencloning/dna_functions.py,sha256=W-SxEfvYpN1JVZbTeCNitpQXkazEHvFyqZBUndd-jpY,16329
|
|
23
|
-
opencloning/dna_utils.py,sha256=
|
|
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=
|
|
29
|
-
opencloning/endpoints/external_import.py,sha256=
|
|
29
|
+
opencloning/endpoints/assembly.py,sha256=H1b7CRx1JZ5pcUGd3uyJG2syYugkXiIo8HRCA11TQfE,20704
|
|
30
|
+
opencloning/endpoints/external_import.py,sha256=DG8WSvyvr-9xy-odEwLHHA4FWiIh8sw4DvTblw5NCYc,18179
|
|
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=
|
|
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=
|
|
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.
|
|
43
|
-
opencloning-0.2.
|
|
44
|
-
opencloning-0.2.
|
|
45
|
-
opencloning-0.2.
|
|
43
|
+
opencloning-0.2.8.1.dist-info/LICENSE,sha256=VSdVE1f8axjIh6gvo9ZZygJdTVkRFMcwCW_hvjOHC_w,1058
|
|
44
|
+
opencloning-0.2.8.1.dist-info/METADATA,sha256=rp3mHAG3x49YfumIjM5teZL6iAtRbhQG1tl64bOjPfI,8429
|
|
45
|
+
opencloning-0.2.8.1.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
|
46
|
+
opencloning-0.2.8.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|