pydna 5.5.1__py3-none-any.whl → 5.5.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.
- pydna/__init__.py +116 -134
- pydna/_pretty.py +2 -14
- pydna/all.py +10 -20
- pydna/amplicon.py +25 -20
- pydna/amplify.py +46 -26
- pydna/assembly.py +50 -27
- pydna/assembly2.py +1902 -0
- pydna/common_sub_strings.py +2 -12
- pydna/contig.py +39 -22
- pydna/crispr.py +8 -13
- pydna/design.py +89 -59
- pydna/download.py +10 -18
- pydna/dseq.py +119 -59
- pydna/dseqrecord.py +88 -45
- pydna/fakeseq.py +0 -11
- pydna/fusionpcr.py +3 -1
- pydna/gateway.py +2 -2
- pydna/gel.py +8 -13
- pydna/genbank.py +33 -32
- pydna/genbankfile.py +8 -13
- pydna/genbankfixer.py +41 -28
- pydna/genbankrecord.py +11 -14
- pydna/goldengate.py +2 -2
- pydna/ladders.py +4 -11
- pydna/ligate.py +8 -14
- pydna/parsers.py +5 -12
- pydna/primer.py +3 -12
- pydna/readers.py +0 -11
- pydna/seq.py +21 -18
- pydna/seqrecord.py +19 -19
- pydna/sequence_picker.py +3 -12
- pydna/tm.py +13 -15
- pydna/types.py +41 -0
- pydna/utils.py +173 -58
- {pydna-5.5.1.dist-info → pydna-5.5.2.dist-info}/METADATA +17 -3
- pydna-5.5.2.dist-info/RECORD +43 -0
- pydna/editor.py +0 -119
- pydna/myenzymes.py +0 -51
- pydna/myprimers.py +0 -219
- pydna-5.5.1.dist-info/RECORD +0 -44
- {pydna-5.5.1.dist-info → pydna-5.5.2.dist-info}/LICENSE.txt +0 -0
- {pydna-5.5.1.dist-info → pydna-5.5.2.dist-info}/WHEEL +0 -0
pydna/__init__.py
CHANGED
|
@@ -132,111 +132,104 @@ See this repository for a collection of
|
|
|
132
132
|
"""
|
|
133
133
|
|
|
134
134
|
|
|
135
|
-
from pydna.utils import open_folder as _open_folder
|
|
136
|
-
from pathlib import Path as _Path
|
|
135
|
+
# from pydna.utils import open_folder as _open_folder
|
|
136
|
+
# from pathlib import Path as _Path
|
|
137
137
|
import os as _os
|
|
138
|
-
|
|
139
|
-
import logging
|
|
140
|
-
import
|
|
141
|
-
import
|
|
142
|
-
import
|
|
138
|
+
|
|
139
|
+
# import logging as _logging
|
|
140
|
+
# import logging.handlers as _handlers
|
|
141
|
+
# import appdirs as _appdirs
|
|
142
|
+
# import configparser as _configparser
|
|
143
|
+
# import tempfile as _tempfile
|
|
143
144
|
from pydna._pretty import PrettyTable as _PrettyTable
|
|
144
145
|
|
|
145
146
|
|
|
146
147
|
__author__ = "Björn Johansson"
|
|
147
|
-
__copyright__ = "Copyright 2013 -
|
|
148
|
+
__copyright__ = "Copyright 2013 - 2023 Björn Johansson"
|
|
148
149
|
__credits__ = ["Björn Johansson", "Mark Budde"]
|
|
149
150
|
__license__ = "BSD"
|
|
150
151
|
__maintainer__ = "Björn Johansson"
|
|
151
152
|
__email__ = "bjorn_johansson@bio.uminho.pt"
|
|
152
153
|
__status__ = "Development" # "Production" #"Prototype"
|
|
153
|
-
__version__ = "5.5.
|
|
154
|
+
__version__ = "5.5.2"
|
|
154
155
|
|
|
155
156
|
|
|
156
|
-
#
|
|
157
|
-
_os.environ["pydna_config_dir"] = _os.getenv("pydna_config_dir", _appdirs.user_config_dir("pydna"))
|
|
158
|
-
config_dir = _Path(_os.environ["pydna_config_dir"])
|
|
159
|
-
config_dir.mkdir(parents=True, exist_ok=True)
|
|
157
|
+
# obtain config directory from env or appdirs
|
|
158
|
+
# _os.environ["pydna_config_dir"] = _os.getenv("pydna_config_dir", _appdirs.user_config_dir("pydna"))
|
|
159
|
+
# config_dir = _Path(_os.environ["pydna_config_dir"])
|
|
160
|
+
# config_dir.mkdir(parents=True, exist_ok=True)
|
|
160
161
|
|
|
161
162
|
# set path for the pydna.ini file
|
|
162
|
-
_ini_path = config_dir / "pydna.ini"
|
|
163
|
+
# _ini_path = config_dir / "pydna.ini"
|
|
163
164
|
|
|
164
165
|
# define user_data_dir
|
|
165
|
-
user_data_dir = _Path(_appdirs.user_data_dir("pydna"))
|
|
166
|
-
|
|
167
|
-
default_ini = {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
166
|
+
# user_data_dir = _Path(_appdirs.user_data_dir("pydna"))
|
|
167
|
+
|
|
168
|
+
# default_ini = {
|
|
169
|
+
# "ape": "put/path/to/ape/here",
|
|
170
|
+
# "cached_funcs": "pydna.genbank.genbank.nucleotide",
|
|
171
|
+
# "data_dir": str(user_data_dir),
|
|
172
|
+
# "email": "someone@example.com",
|
|
173
|
+
# "enzymes": str(user_data_dir / "enzymes.md"),
|
|
174
|
+
# "log_dir": _appdirs.user_log_dir("pydna"),
|
|
175
|
+
# "loglevel": str(_logging.WARNING),
|
|
176
|
+
# "primers": str(user_data_dir / "primers.md"),
|
|
177
|
+
# "assembly_limit": str(10),
|
|
178
|
+
# }
|
|
179
|
+
|
|
180
|
+
# ini = default_ini.copy()
|
|
178
181
|
|
|
179
182
|
# initiate a config parser instance
|
|
180
|
-
_parser = _configparser.ConfigParser()
|
|
183
|
+
# _parser = _configparser.ConfigParser()
|
|
181
184
|
|
|
182
185
|
# if a pydna.ini exists, it is read
|
|
183
|
-
if _ini_path.exists():
|
|
184
|
-
|
|
185
|
-
else: # otherwise it is created with default settings
|
|
186
|
-
_parser["main"] = default_ini
|
|
187
|
-
_temp_ini_file = _tempfile.NamedTemporaryFile(dir=_ini_path.parent, delete=False)
|
|
188
|
-
_temp_ini_path = _Path(_temp_ini_file.name)
|
|
189
|
-
try:
|
|
190
|
-
_temp_ini_file.close()
|
|
191
|
-
with _temp_ini_path.open("w", encoding="utf-8") as f: # TODO needs encoding?
|
|
192
|
-
_parser.write(f)
|
|
193
|
-
_temp_ini_path.replace(_ini_path)
|
|
194
|
-
finally:
|
|
195
|
-
_temp_ini_path.unlink(missing_ok=True)
|
|
196
|
-
|
|
186
|
+
# if _ini_path.exists():
|
|
187
|
+
# _parser.read(_ini_path)
|
|
197
188
|
# pydna related environmental variables are set
|
|
198
189
|
# from pydna.ini if they are not set already
|
|
199
|
-
_main = _parser["main"]
|
|
190
|
+
# _main = _parser["main"]
|
|
191
|
+
# ini.update(_main)
|
|
200
192
|
|
|
201
|
-
for key in default_ini:
|
|
202
|
-
_os.environ[f"pydna_{key}"] = _os.getenv(f"pydna_{key}", _main.get(key, default_ini[key]))
|
|
203
193
|
|
|
204
|
-
|
|
194
|
+
# for key, value in ini.items():
|
|
195
|
+
# _os.environ[f"pydna_{key}"] = _os.getenv(f"pydna_{key}", value)
|
|
196
|
+
|
|
197
|
+
# logdir = _Path(_os.environ["pydna_log_dir"])
|
|
205
198
|
|
|
206
199
|
# create log directory if not present
|
|
207
|
-
logdir.mkdir(parents=True, exist_ok=True)
|
|
208
|
-
_logmsg = "Log directory {}".format(logdir)
|
|
200
|
+
# logdir.mkdir(parents=True, exist_ok=True)
|
|
201
|
+
# _logmsg = "Log directory {}".format(logdir)
|
|
209
202
|
|
|
210
203
|
# create logger
|
|
211
|
-
_logger = _logging.getLogger("pydna")
|
|
212
|
-
_logger.setLevel(int(_os.environ["pydna_loglevel"]))
|
|
213
|
-
_hdlr = _handlers.RotatingFileHandler(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
_formatter = _logging.Formatter(("%(asctime)s %(levelname)s" " %(funcName)s %(message)s"))
|
|
222
|
-
_hdlr.setFormatter(_formatter)
|
|
223
|
-
_logger.addHandler(_hdlr)
|
|
224
|
-
_logger.info(_logmsg)
|
|
225
|
-
_logger.info("Environmental variable pydna_ape = %s", _os.environ["pydna_ape"])
|
|
226
|
-
_logger.info("Environmental variable pydna_cached_funcs = %s", _os.environ["pydna_cached_funcs"])
|
|
227
|
-
_logger.info("Environmental variable pydna_data_dir = %s", _os.environ["pydna_data_dir"])
|
|
228
|
-
_logger.info("Environmental variable pydna_email = %s", _os.environ["pydna_email"])
|
|
229
|
-
_logger.info("Environmental variable pydna_log_dir = %s", _os.environ["pydna_log_dir"])
|
|
230
|
-
_logger.info("Environmental variable pydna_loglevel = %s", _os.environ["pydna_loglevel"])
|
|
231
|
-
_logger.info("Environmental variable pydna_primers = %s", _os.environ["pydna_primers"])
|
|
232
|
-
_logger.info(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
)
|
|
204
|
+
# _logger = _logging.getLogger("pydna")
|
|
205
|
+
# _logger.setLevel(int(_os.environ["pydna_loglevel"]))
|
|
206
|
+
# _hdlr = _handlers.RotatingFileHandler(
|
|
207
|
+
# logdir / "pydna.log",
|
|
208
|
+
# mode="a",
|
|
209
|
+
# maxBytes=10 * 1024 * 1024,
|
|
210
|
+
# backupCount=10,
|
|
211
|
+
# encoding="utf-8",
|
|
212
|
+
# )
|
|
213
|
+
|
|
214
|
+
# _formatter = _logging.Formatter(("%(asctime)s %(levelname)s" " %(funcName)s %(message)s"))
|
|
215
|
+
# _hdlr.setFormatter(_formatter)
|
|
216
|
+
# _logger.addHandler(_hdlr)
|
|
217
|
+
# _logger.info(_logmsg)
|
|
218
|
+
# _logger.info("Environmental variable pydna_ape = %s", _os.environ["pydna_ape"])
|
|
219
|
+
# _logger.info("Environmental variable pydna_cached_funcs = %s", _os.environ["pydna_cached_funcs"])
|
|
220
|
+
# _logger.info("Environmental variable pydna_data_dir = %s", _os.environ["pydna_data_dir"])
|
|
221
|
+
# _logger.info("Environmental variable pydna_email = %s", _os.environ["pydna_email"])
|
|
222
|
+
# _logger.info("Environmental variable pydna_log_dir = %s", _os.environ["pydna_log_dir"])
|
|
223
|
+
# _logger.info("Environmental variable pydna_loglevel = %s", _os.environ["pydna_loglevel"])
|
|
224
|
+
# _logger.info("Environmental variable pydna_primers = %s", _os.environ["pydna_primers"])
|
|
225
|
+
# _logger.info(
|
|
226
|
+
# "Environmental variable pydna_assembly_limit = %s",
|
|
227
|
+
# _os.environ["pydna_assembly_limit"],
|
|
228
|
+
# )
|
|
236
229
|
|
|
237
230
|
# create cache directory if not present
|
|
238
231
|
|
|
239
|
-
_Path(_os.environ["pydna_data_dir"]).mkdir(parents=True, exist_ok=True)
|
|
232
|
+
# _Path(_os.environ["pydna_data_dir"]).mkdir(parents=True, exist_ok=True)
|
|
240
233
|
|
|
241
234
|
# find out if optional dependecies for gel module are in place
|
|
242
235
|
#
|
|
@@ -264,7 +257,7 @@ _Path(_os.environ["pydna_data_dir"]).mkdir(parents=True, exist_ok=True)
|
|
|
264
257
|
# _logger.info("gel simulation is available," " optional dependencies were found.")
|
|
265
258
|
#
|
|
266
259
|
|
|
267
|
-
_logger.info("__version__ = %s", __version__)
|
|
260
|
+
# _logger.info("__version__ = %s", __version__)
|
|
268
261
|
|
|
269
262
|
|
|
270
263
|
class _PydnaWarning(Warning):
|
|
@@ -304,74 +297,74 @@ class _PydnaDeprecationWarning(_PydnaWarning):
|
|
|
304
297
|
pass
|
|
305
298
|
|
|
306
299
|
|
|
307
|
-
def open_current_folder():
|
|
308
|
-
|
|
300
|
+
# def open_current_folder():
|
|
301
|
+
# """Open the current working directory.
|
|
309
302
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
303
|
+
# Opens in the default file manager. The location for this folder is
|
|
304
|
+
# given by the :func:`os.getcwd` function
|
|
305
|
+
# """
|
|
306
|
+
# return _open_folder(_os.getcwd())
|
|
314
307
|
|
|
315
308
|
|
|
316
|
-
_logger.info("Current working directory = os.getcwd() = %s", _os.getcwd())
|
|
309
|
+
# _logger.info("Current working directory = os.getcwd() = %s", _os.getcwd())
|
|
317
310
|
|
|
318
311
|
|
|
319
|
-
def open_cache_folder():
|
|
320
|
-
|
|
312
|
+
# def open_cache_folder():
|
|
313
|
+
# """Open the pydna cache folder.
|
|
321
314
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
315
|
+
# Opens in the default file manager. The location for this folder is stored
|
|
316
|
+
# in the *pydna_data_dir* environmental variable.
|
|
317
|
+
# """
|
|
318
|
+
# return _open_folder(_os.environ["pydna_data_dir"])
|
|
326
319
|
|
|
327
320
|
|
|
328
|
-
def open_config_folder():
|
|
329
|
-
|
|
321
|
+
# def open_config_folder():
|
|
322
|
+
# """Open the pydna configuration folder.
|
|
330
323
|
|
|
331
|
-
|
|
332
|
-
|
|
324
|
+
# Opens in the default file manager. The location for this folder is stored
|
|
325
|
+
# in the *pydna_config_dir* environmental variable.
|
|
333
326
|
|
|
334
|
-
|
|
335
|
-
|
|
327
|
+
# The `pydna.ini` file can be edited to make pydna quicker to use.
|
|
328
|
+
# See the documentation of the :class:configparser.ConfigParser´ class.
|
|
336
329
|
|
|
337
|
-
|
|
338
|
-
|
|
330
|
+
# Below is the content of a typical `pydna.ini` file on a Linux
|
|
331
|
+
# system.
|
|
339
332
|
|
|
340
|
-
|
|
333
|
+
# ::
|
|
341
334
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
335
|
+
# [main]
|
|
336
|
+
# loglevel=30
|
|
337
|
+
# email=myemail@example.org
|
|
338
|
+
# data_dir=/home/user/.local/share/pydna
|
|
339
|
+
# log_dir=/home/user/.cache/pydna/log
|
|
340
|
+
# ape=tclsh /path/to/ape/AppMain.tcl
|
|
341
|
+
# cached_funcs=Genbank_nucleotide
|
|
342
|
+
# primers=/path/to/primers/PRIMERS.txt
|
|
343
|
+
# enzymes=/path/to/enzymes/RestrictionEnzymes.txt
|
|
351
344
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
345
|
+
# The email address is set to someone@example.com by default. If you change
|
|
346
|
+
# this to you own address, the :func:`pydna.genbank.genbank` function can be
|
|
347
|
+
# used to download sequences from Genbank directly without having to
|
|
348
|
+
# explicitly add the email address.
|
|
356
349
|
|
|
357
|
-
|
|
350
|
+
# Pydna can cache results from the following functions or methods:
|
|
358
351
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
352
|
+
# - :func:`pydna.genbank.Genbank.nucleotide` Genbank_nucleotide
|
|
353
|
+
# - :func:`pydna.amplify.Anneal` amplify_Anneal
|
|
354
|
+
# - :func:`pydna.assembly.Assembly` assembly_Assembly
|
|
355
|
+
# - :func:`pydna.download.download_text` download.download_text
|
|
356
|
+
# - :func:`pydna.dseqrecord.Dseqrecord.synced` Dseqrecord_synced
|
|
364
357
|
|
|
365
|
-
|
|
366
|
-
|
|
358
|
+
# These can be added separated by a comma to the cached_funcs entry
|
|
359
|
+
# in **pydna.ini** file or the pydna_cached_funcs environment variable.
|
|
367
360
|
|
|
368
|
-
|
|
369
|
-
|
|
361
|
+
# """
|
|
362
|
+
# return _open_folder(_os.environ["pydna_config_dir"])
|
|
370
363
|
|
|
371
364
|
|
|
372
|
-
def open_log_folder():
|
|
373
|
-
|
|
374
|
-
|
|
365
|
+
# def open_log_folder():
|
|
366
|
+
# """docstring."""
|
|
367
|
+
# return _open_folder(_os.environ["pydna_log_dir"])
|
|
375
368
|
|
|
376
369
|
|
|
377
370
|
def get_env():
|
|
@@ -403,14 +396,3 @@ def logo():
|
|
|
403
396
|
f = Figlet()
|
|
404
397
|
message = f.renderText(message)
|
|
405
398
|
return _pretty_str(message)
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
if __name__ == "__main__":
|
|
409
|
-
import os as _os
|
|
410
|
-
|
|
411
|
-
cached = _os.getenv("pydna_cached_funcs", "")
|
|
412
|
-
_os.environ["pydna_cached_funcs"] = ""
|
|
413
|
-
import doctest
|
|
414
|
-
|
|
415
|
-
doctest.testmod(verbose=True, optionflags=doctest.ELLIPSIS)
|
|
416
|
-
_os.environ["pydna_cached_funcs"] = cached
|
pydna/_pretty.py
CHANGED
|
@@ -7,8 +7,7 @@ for for nicer string output in the IPython shell and Jupyter notebook.
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from prettytable import PrettyTable as _Pt
|
|
10
|
-
from prettytable import
|
|
11
|
-
|
|
10
|
+
from prettytable import TableStyle as _TableStyle
|
|
12
11
|
from copy import copy as _copy
|
|
13
12
|
from typing import List as _List
|
|
14
13
|
|
|
@@ -33,16 +32,5 @@ class PrettyTable(_Pt):
|
|
|
33
32
|
|
|
34
33
|
def _repr_markdown_(self) -> pretty_str:
|
|
35
34
|
c = _copy(self)
|
|
36
|
-
c.set_style(
|
|
35
|
+
c.set_style(_TableStyle.MARKDOWN)
|
|
37
36
|
return pretty_str(c.get_string())
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if __name__ == "__main__":
|
|
41
|
-
import os as _os
|
|
42
|
-
|
|
43
|
-
cached = _os.getenv("pydna_cached_funcs", "")
|
|
44
|
-
_os.environ["pydna_cached_funcs"] = ""
|
|
45
|
-
import doctest
|
|
46
|
-
|
|
47
|
-
doctest.testmod(verbose=True, optionflags=doctest.ELLIPSIS)
|
|
48
|
-
_os.environ["pydna_cached_funcs"] = cached
|
pydna/all.py
CHANGED
|
@@ -18,10 +18,8 @@ ttt
|
|
|
18
18
|
Dseqrecord(-3)
|
|
19
19
|
>>> from pydna.all import __all__
|
|
20
20
|
>>> __all__
|
|
21
|
-
['Anneal', 'pcr', 'Assembly', 'genbank', 'Genbank', 'download_text
|
|
22
|
-
', '
|
|
23
|
-
', 'ape', 'primer_design', 'assembly_fragments', 'circular_assembly_fragments\
|
|
24
|
-
', 'eq', 'gbtext_clean', 'PrimerList']
|
|
21
|
+
['Anneal', 'pcr', 'Assembly', 'genbank', 'Genbank', 'download_text', 'Dseqrecord',
|
|
22
|
+
'Dseq', 'read', 'read_primer', 'parse', 'parse_primers', 'primer_design', 'assembly_fragments', 'eq', 'gbtext_clean']
|
|
25
23
|
>>>
|
|
26
24
|
"""
|
|
27
25
|
|
|
@@ -39,13 +37,13 @@ __all__ = [
|
|
|
39
37
|
"read_primer",
|
|
40
38
|
"parse",
|
|
41
39
|
"parse_primers",
|
|
42
|
-
"ape",
|
|
40
|
+
# "ape",
|
|
43
41
|
"primer_design",
|
|
44
42
|
"assembly_fragments",
|
|
45
|
-
"circular_assembly_fragments",
|
|
43
|
+
# "circular_assembly_fragments",
|
|
46
44
|
"eq",
|
|
47
45
|
"gbtext_clean",
|
|
48
|
-
"PrimerList",
|
|
46
|
+
# "PrimerList",
|
|
49
47
|
]
|
|
50
48
|
|
|
51
49
|
|
|
@@ -61,21 +59,13 @@ from pydna.readers import read
|
|
|
61
59
|
from pydna.readers import read_primer
|
|
62
60
|
from pydna.parsers import parse
|
|
63
61
|
from pydna.parsers import parse_primers
|
|
64
|
-
|
|
62
|
+
|
|
63
|
+
# from pydna.editor import ape
|
|
65
64
|
from pydna.design import primer_design
|
|
66
65
|
from pydna.design import assembly_fragments
|
|
67
|
-
|
|
66
|
+
|
|
67
|
+
# from pydna.design import circular_assembly_fragments
|
|
68
68
|
from pydna.utils import eq
|
|
69
69
|
from pydna.genbankfixer import gbtext_clean
|
|
70
|
-
from pydna.myprimers import PrimerList
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if __name__ == "__main__":
|
|
74
|
-
import os as _os
|
|
75
|
-
|
|
76
|
-
cached = _os.getenv("pydna_cached_funcs", "")
|
|
77
|
-
_os.environ["pydna_cached_funcs"] = ""
|
|
78
|
-
import doctest
|
|
79
70
|
|
|
80
|
-
|
|
81
|
-
_os.environ["pydna_cached_funcs"] = cached
|
|
71
|
+
# from pydna.myprimers import PrimerList
|
pydna/amplicon.py
CHANGED
|
@@ -18,10 +18,11 @@ from pydna.dseqrecord import Dseqrecord as _Dseqrecord
|
|
|
18
18
|
from pydna.seqrecord import SeqRecord as _SeqRecord
|
|
19
19
|
import textwrap as _textwrap
|
|
20
20
|
import copy as _copy
|
|
21
|
-
import logging as _logging
|
|
22
21
|
|
|
22
|
+
# import logging as _logging
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
|
|
25
|
+
# _module_logger = _logging.getLogger("pydna." + __name__)
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
class Amplicon(_Dseqrecord):
|
|
@@ -43,7 +44,15 @@ class Amplicon(_Dseqrecord):
|
|
|
43
44
|
|
|
44
45
|
"""
|
|
45
46
|
|
|
46
|
-
def __init__(
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
record,
|
|
50
|
+
*args,
|
|
51
|
+
template=None,
|
|
52
|
+
forward_primer=None,
|
|
53
|
+
reverse_primer=None,
|
|
54
|
+
**kwargs,
|
|
55
|
+
):
|
|
47
56
|
super().__init__(record, *args)
|
|
48
57
|
self.template = template
|
|
49
58
|
self.forward_primer = forward_primer
|
|
@@ -83,7 +92,10 @@ class Amplicon(_Dseqrecord):
|
|
|
83
92
|
r.template = self.template.rc()
|
|
84
93
|
r.forward_primer = _copy.copy(self.reverse_primer)
|
|
85
94
|
r.reverse_primer = _copy.copy(self.forward_primer)
|
|
86
|
-
r.forward_primer.position, r.reverse_primer.position =
|
|
95
|
+
r.forward_primer.position, r.reverse_primer.position = (
|
|
96
|
+
r.reverse_primer.position,
|
|
97
|
+
r.forward_primer.position,
|
|
98
|
+
)
|
|
87
99
|
return r
|
|
88
100
|
|
|
89
101
|
rc = reverse_complement
|
|
@@ -123,21 +135,25 @@ class Amplicon(_Dseqrecord):
|
|
|
123
135
|
fzc = tp.seq.crick[::-1][fp.position - fp._fp : fp.position]
|
|
124
136
|
rzc = tp.seq.crick[::-1][rp.position : rp.position + rp._fp]
|
|
125
137
|
f = f"""
|
|
126
|
-
{" " *ft}5{faz}...{raz}3
|
|
138
|
+
{" " * ft}5{faz}...{raz}3
|
|
127
139
|
{sp3}{"|" * rp._fp}
|
|
128
140
|
{sp3}3{rp.seq[::-1]}5
|
|
129
141
|
5{fp.seq}3
|
|
130
|
-
{"|" *fp._fp:>{len(fp)}}
|
|
131
|
-
{" " *ft}3{fzc}...{rzc}5
|
|
142
|
+
{"|" * fp._fp:>{len(fp)}}
|
|
143
|
+
{" " * ft}3{fzc}...{rzc}5
|
|
132
144
|
"""
|
|
133
145
|
# breakpoint()
|
|
134
146
|
return _pretty_str(_textwrap.dedent(f).strip("\n"))
|
|
135
147
|
|
|
136
148
|
def set_forward_primer_footprint(self, length):
|
|
137
|
-
self.forward_primer = _Primer(
|
|
149
|
+
self.forward_primer = _Primer(
|
|
150
|
+
self.forward_primer.tail + self.seq[:length], footprint=length
|
|
151
|
+
)
|
|
138
152
|
|
|
139
153
|
def set_reverse_primer_footprint(self, length):
|
|
140
|
-
self.reverse_primer = _Primer(
|
|
154
|
+
self.reverse_primer = _Primer(
|
|
155
|
+
self.reverse_primer.tail + self.seq[:length], footprint=length
|
|
156
|
+
)
|
|
141
157
|
|
|
142
158
|
def program(self):
|
|
143
159
|
return _program(self)
|
|
@@ -147,14 +163,3 @@ class Amplicon(_Dseqrecord):
|
|
|
147
163
|
|
|
148
164
|
def primers(self):
|
|
149
165
|
return self.forward_primer, self.reverse_primer
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if __name__ == "__main__":
|
|
153
|
-
import os as _os
|
|
154
|
-
|
|
155
|
-
cached = _os.getenv("pydna_cached_funcs", "")
|
|
156
|
-
_os.environ["pydna_cached_funcs"] = ""
|
|
157
|
-
import doctest
|
|
158
|
-
|
|
159
|
-
doctest.testmod(verbose=True, optionflags=doctest.ELLIPSIS)
|
|
160
|
-
_os.environ["pydna_cached_funcs"] = cached
|
pydna/amplify.py
CHANGED
|
@@ -30,10 +30,12 @@ import itertools as _itertools
|
|
|
30
30
|
import re as _re
|
|
31
31
|
import copy as _copy
|
|
32
32
|
import operator as _operator
|
|
33
|
-
import os as _os
|
|
34
|
-
import logging as _logging
|
|
35
33
|
|
|
36
|
-
|
|
34
|
+
# import os as _os
|
|
35
|
+
|
|
36
|
+
# import logging as _logging
|
|
37
|
+
|
|
38
|
+
# _module_logger = _logging.getLogger("pydna." + __name__)
|
|
37
39
|
|
|
38
40
|
_table = { # IUPAC Ambiguity Codes for Nucleotide Degeneracy and U for Uracile
|
|
39
41
|
"A": "A",
|
|
@@ -112,7 +114,9 @@ def _annealing_positions(primer, template, limit):
|
|
|
112
114
|
results = []
|
|
113
115
|
for match_start in positions:
|
|
114
116
|
tm = template[match_start + limit : match_start + limit + length].lower()
|
|
115
|
-
footprint = len(
|
|
117
|
+
footprint = len(
|
|
118
|
+
list(_itertools.takewhile(lambda x: x[0] == x[1], zip(tail, tm)))
|
|
119
|
+
)
|
|
116
120
|
results.append((match_start, footprint + limit))
|
|
117
121
|
return results
|
|
118
122
|
return []
|
|
@@ -343,11 +347,19 @@ class Anneal(object): # ), metaclass=_Memoize):
|
|
|
343
347
|
for rp in self.reverse_primers:
|
|
344
348
|
if self.template.circular:
|
|
345
349
|
shift = fp.position - fp._fp
|
|
346
|
-
tpl = self.template.shifted(
|
|
347
|
-
|
|
348
|
-
|
|
350
|
+
tpl = self.template.shifted(
|
|
351
|
+
shift
|
|
352
|
+
) # shift template so that it starts where the fp starts anneling
|
|
353
|
+
fp.position = (
|
|
354
|
+
fp._fp
|
|
355
|
+
) # New position of fp becomes the footprint length
|
|
356
|
+
rp.position = (rp.position - shift) % len(
|
|
357
|
+
self.template
|
|
358
|
+
) # Shift the rp position as well
|
|
349
359
|
feats = tpl[: rp.position + rp._fp].features
|
|
350
|
-
elif
|
|
360
|
+
elif (
|
|
361
|
+
fp.position <= rp.position
|
|
362
|
+
): # pcr products only formed if fp anneals forward of rp
|
|
351
363
|
feats = self.template[
|
|
352
364
|
fp.position - fp._fp : rp.position + rp._fp
|
|
353
365
|
].features # Save features covered by primers
|
|
@@ -361,10 +373,16 @@ class Anneal(object): # ), metaclass=_Memoize):
|
|
|
361
373
|
if tpl.circular and fp.position == rp.position:
|
|
362
374
|
prd = _Dseqrecord(fp) + _Dseqrecord(rp).reverse_complement()
|
|
363
375
|
else:
|
|
364
|
-
prd =
|
|
376
|
+
prd = (
|
|
377
|
+
_Dseqrecord(fp)
|
|
378
|
+
+ tpl[fp.position : rp.position]
|
|
379
|
+
+ _Dseqrecord(rp).reverse_complement()
|
|
380
|
+
)
|
|
365
381
|
prd.features = feats
|
|
366
382
|
full_tmpl_features = [
|
|
367
|
-
f
|
|
383
|
+
f
|
|
384
|
+
for f in self.template.features
|
|
385
|
+
if f.location.start == 0 and f.location.end == len(self.template)
|
|
368
386
|
]
|
|
369
387
|
new_identifier = ""
|
|
370
388
|
if full_tmpl_features:
|
|
@@ -383,10 +401,14 @@ class Anneal(object): # ), metaclass=_Memoize):
|
|
|
383
401
|
or self.kwargs.get("name")
|
|
384
402
|
or f"{len(prd)}bp_PCR_prod"[:16]
|
|
385
403
|
)
|
|
386
|
-
prd.id =
|
|
387
|
-
|
|
388
|
-
|
|
404
|
+
prd.id = (
|
|
405
|
+
_identifier_from_string(new_identifier)[:16]
|
|
406
|
+
or self.kwargs.get("id")
|
|
407
|
+
or f"{len(prd)}bp"[:16]
|
|
389
408
|
)
|
|
409
|
+
prd.description = self.kwargs.get(
|
|
410
|
+
"description"
|
|
411
|
+
) or "pcr_product_{}_{}".format(fp.description, rp.description)
|
|
390
412
|
|
|
391
413
|
amplicon = _Amplicon(
|
|
392
414
|
prd,
|
|
@@ -405,7 +427,9 @@ class Anneal(object): # ), metaclass=_Memoize):
|
|
|
405
427
|
|
|
406
428
|
def __repr__(self):
|
|
407
429
|
"""returns a short string representation"""
|
|
408
|
-
return "Reaction(products = {})".format(
|
|
430
|
+
return "Reaction(products = {})".format(
|
|
431
|
+
len(self.forward_primers * len(self.reverse_primers))
|
|
432
|
+
)
|
|
409
433
|
|
|
410
434
|
def __str__(self):
|
|
411
435
|
"""returns a short report describing if or where primer
|
|
@@ -419,13 +443,17 @@ class Anneal(object): # ), metaclass=_Memoize):
|
|
|
419
443
|
)
|
|
420
444
|
if self.forward_primers:
|
|
421
445
|
for p in self.forward_primers:
|
|
422
|
-
mystring += "{name} anneals forward (--->) at {pos}\n".format(
|
|
446
|
+
mystring += "{name} anneals forward (--->) at {pos}\n".format(
|
|
447
|
+
name=p.name, pos=p.position
|
|
448
|
+
)
|
|
423
449
|
else:
|
|
424
450
|
mystring += "No forward primers anneal...\n"
|
|
425
451
|
# mystring +="\n"
|
|
426
452
|
if self.reverse_primers:
|
|
427
453
|
for p in self.reverse_primers:
|
|
428
|
-
mystring += "{name} anneals reverse (<---) at {pos}\n".format(
|
|
454
|
+
mystring += "{name} anneals reverse (<---) at {pos}\n".format(
|
|
455
|
+
name=p.name, pos=p.position
|
|
456
|
+
)
|
|
429
457
|
else:
|
|
430
458
|
mystring += "No reverse primers anneal...\n"
|
|
431
459
|
return _pretty_str(mystring.strip())
|
|
@@ -508,7 +536,8 @@ tatcgactgtatcatctgatagcac")
|
|
|
508
536
|
pass
|
|
509
537
|
else:
|
|
510
538
|
raise TypeError(
|
|
511
|
-
"arguments need to be a string, Bio.Seq, SeqRecord"
|
|
539
|
+
"arguments need to be a string, Bio.Seq, SeqRecord"
|
|
540
|
+
", Primer, Dseqrecord or Amplicon object"
|
|
512
541
|
)
|
|
513
542
|
new.append(s)
|
|
514
543
|
|
|
@@ -526,12 +555,3 @@ tatcgactgtatcatctgatagcac")
|
|
|
526
555
|
elif len(anneal_primers.products) == 0:
|
|
527
556
|
raise ValueError(f"No PCR product! {anneal_primers.report()}")
|
|
528
557
|
raise ValueError(f"PCR not specific! {format(anneal_primers.report())}")
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
if __name__ == "__main__":
|
|
532
|
-
cached = _os.getenv("pydna_cached_funcs", "")
|
|
533
|
-
_os.environ["pydna_cached_funcs"] = ""
|
|
534
|
-
import doctest
|
|
535
|
-
|
|
536
|
-
doctest.testmod(verbose=True, optionflags=doctest.ELLIPSIS)
|
|
537
|
-
_os.environ["pydna_cached_funcs"] = cached
|