#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2013-2023 by Björn Johansson. All rights reserved.
# This code is part of the Python-dna distribution and governed by its
# license. Please see the LICENSE.txt file that should have been included
# as part of this package.
"""This module provides a class for downloading sequences from genbank
called Genbank and an function that does the same thing called genbank.
The function can be used if the environmental variable **pydna_email** has
been set to a valid email address. The easiest way to do this permanantly is to edit the
`pydna.ini` file. See the documentation of :func:`pydna.open_config_folder`"""
from pydna.utils import memorize as _memorize
from pydna.genbankrecord import GenbankRecord as _GenbankRecord
from pydna.readers import read as _read
from Bio import Entrez as _Entrez
from typing import Literal as _Literal, Optional as _Optional
import re as _re
import os as _os
import logging as _logging
_module_logger = _logging.getLogger("pydna." + __name__)
# TODO http://httpbin.org/ use for testing?
[docs]class Genbank:
"""Class to facilitate download from genbank. It is easier and
quicker to use the :func:`pydna.genbank.genbank` function directly.
Parameters
----------
users_email : string
Has to be a valid email address. You should always tell
Genbanks who you are, so that they can contact you.
Examples
--------
>>> from pydna.genbank import Genbank
>>> gb=Genbank("bjornjobb@gmail.com")
>>> rec = gb.nucleotide("LP002422.1") # <- entry from genbank
>>> print(len(rec))
1
"""
def __init__(
self,
users_email: str,
*,
tool: str = "pydna",
) -> None:
if not _re.match(r"[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}", users_email, _re.IGNORECASE):
raise ValueError("email address {} is not valid.".format(users_email))
_module_logger.info("#### Genbank ititiation ####")
_module_logger.info("Genbank initiated with email: %s", users_email)
_module_logger.info("Genbank initiated with tool : %s", tool)
if users_email == "someone@example.com":
raise ValueError("you have to set your email address in order to download from Genbank")
self.email = users_email
self.tool = tool
def __repr__(self) -> str:
"""This method returns a short representation containing the email used to initiate."""
return "GenbankConnection({})".format(self.email)
@_memorize("pydna.genbank.Genbank.nucleotide")
def nucleotide(
self,
item: str,
seq_start: _Optional[int] = None,
seq_stop: _Optional[int] = None,
strand: _Literal[1, 2] = 1,
) -> _GenbankRecord:
"""This method downloads a genbank nuclotide record from genbank. This method is
cached by default. This can be controlled by editing the **pydna_cached_funcs** environment
variable. The best way to do this permanently is to edit the edit the
`pydna.ini` file. See the documentation of :func:`pydna.open_config_folder`
Item is a string containing one genbank accession number
for a nucleotide file. Genbank nucleotide accession numbers have this format:
| A12345 = 1 letter + 5 numerals
| AB123456 = 2 letters + 6 numerals
The accession number is sometimes followed by a point and version number
| BK006936.2
Item can also contain optional interval information in the following formats:
| BK006936.2 REGION: complement(613900..615202)
| NM_005546 REGION: 1..100
| NM_005546 REGION: complement(1..100)
| 21614549:1-100
| 21614549:c100-1
| 21614549 1-100
| 21614549 c100-1
It is useful to set an interval for large genbank records to limit the download time.
The items above containing interval information and can be obtained directly by
looking up an entry in Genbank and setting the `Change region shown` on the
upper right side of the page. The `ACCESSION` line of the displayed Genbank
file will have the formatting shown.
Alternatively, seq_start and seq_stop can be set explicitly to the sequence intervals to be
downloaded.
If strand is 2. "c", "C", "crick", "Crick", "antisense","Antisense",
"2", 2, "-" or "-1", the antisense (Crick) strand is returned, otherwise
the sense (Watson) strand is returned.
Result is returned as a :class:`pydna.genbankrecord.GenbankRecord` object.
References
----------
.. [#] http://www.dsimb.inserm.fr/~fuchs/M2BI/AnalSeq/Annexes/Sequences/Accession_Numbers.htm
.. [#] http://www.ncbi.nlm.nih.gov/books/NBK25499/#chapter4.EFetch
"""
matches = (
(1, _re.search(r"(REGION:\s(?P<start>\d+)\.\.(?P<stop>\d+))", item)),
(
2,
_re.search(r"(REGION: complement\((?P<start>\d+)\.\.(?P<stop>\d+)\))", item),
),
(1, _re.search(r"(:|\s)(?P<start>\d+)-(?P<stop>\d+)", item)),
(2, _re.search(r"(:|\s)c(?P<start>\d+)-(?P<stop>\d+)", item)),
)
for strand_, match in matches:
if match:
seq_start = match.group("start")
seq_stop = match.group("stop")
item = item[: match.start()]
strand = strand_
break
if strand not in [1, 2]:
try:
strand = {"c": 2, "crick": 2, "antisense": 2, "2": 2, "-": 2, "-1": 2}[strand.lower()]
except (KeyError, AttributeError):
strand = 1
_module_logger.info("#### Genbank download ####")
_module_logger.info("item %s", item)
_module_logger.info("start %s", seq_start)
_module_logger.info("stop %s", seq_stop)
_module_logger.info("strand %s", str(strand))
_Entrez.email = self.email
_Entrez.tool = self.tool
_module_logger.info("Entrez.email %s", self.email)
text = _Entrez.efetch(
db="nuccore",
id=item,
rettype="gbwithparts",
seq_start=seq_start,
seq_stop=seq_stop,
strand=strand,
retmode="text",
).read()
_module_logger.info("text[:160] %s", text[:160])
return _GenbankRecord(_read(text), item=item, start=seq_start, stop=seq_stop, strand=strand)
[docs]def genbank(accession: str = "CS570233.1", *args, **kwargs) -> _GenbankRecord:
"""
Download a genbank nuclotide record.
This function takes the same paramenters as the
:func:pydna.genbank.Genbank.nucleotide method. The email address stored
in the `pydna_email` environment variable is used. The easiest way set
this permanantly is to edit the `pydna.ini` file.
See the documentation of :func:`pydna.open_config_folder`
if no accession is given, a `very short Genbank
entry <https://www.ncbi.nlm.nih.gov/nuccore/CS570233.1>`_
is used as an example (see below). This can be useful for testing the
connection to Genbank.
Please note that this result is also cached by default by settings in
the pydna.ini file.
See the documentation of :func:`pydna.open_config_folder`
::
LOCUS CS570233 14 bp DNA linear PAT 18-MAY-2007
DEFINITION Sequence 6 from Patent WO2007025016.
ACCESSION CS570233
VERSION CS570233.1
KEYWORDS .
SOURCE synthetic construct
ORGANISM synthetic construct
other sequences; artificial sequences.
REFERENCE 1
AUTHORS Shaw,R.W. and Cottenoir,M.
TITLE Inhibition of metallo-beta-lactamase by double-stranded dna
JOURNAL Patent: WO 2007025016-A1 6 01-MAR-2007;
Texas Tech University System (US)
FEATURES Location/Qualifiers
source 1..14
/organism="synthetic construct"
/mol_type="unassigned DNA"
/db_xref="taxon:32630"
/note="This is a 14bp aptamer inhibitor."
ORIGIN
1 atgttcctac atga
//
"""
email = _os.getenv("pydna_email")
_module_logger.info("#### genbank function called ####")
_module_logger.info("email %s", email)
_module_logger.info("accession %s", email)
gb = Genbank(email)
return gb.nucleotide(accession, *args, **kwargs)
if __name__ == "__main__":
cached = _os.getenv("pydna_cached_funcs", "")
_os.environ["pydna_cached_funcs"] = ""
import doctest
doctest.testmod(verbose=True, optionflags=doctest.ELLIPSIS)
_os.environ["pydna_cached_funcs"] = cached
pass