seqbank 0.1.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.
seqbank/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ from .seqbank import SeqBank
2
+ from .exceptions import SeqBankError
seqbank/dfam.py ADDED
@@ -0,0 +1,85 @@
1
+ import gzip
2
+ from pathlib import Path
3
+ import h5py
4
+ import tempfile
5
+ import shutil
6
+
7
+ from .io import download_file
8
+ from .seqbank import SeqBank
9
+
10
+ def dfam_url(curated: bool = True, release: str = "current") -> str:
11
+ """
12
+ Constructs the URL for the Dfam database file.
13
+
14
+ Args:
15
+ curated (bool, optional): If True, fetches the curated-only version of the Dfam database. Defaults to True.
16
+ release (str, optional): The Dfam release version. Defaults to "current".
17
+
18
+ Returns:
19
+ str: The URL for the Dfam HDF5 file.
20
+ """
21
+ curated_str = "_curatedonly" if curated else ""
22
+ url = f"https://www.dfam.org/releases/{release}/families/Dfam{curated_str}.h5.gz"
23
+ return url
24
+
25
+
26
+ def add_dfam(seqbank: SeqBank, local_path: Path) -> None:
27
+ """
28
+ Adds sequences from a local Dfam HDF5 file to the SeqBank.
29
+
30
+ Args:
31
+ seqbank (SeqBank): The SeqBank instance to add sequences to.
32
+ local_path (Path): The path to the local Dfam HDF5 file.
33
+ """
34
+ file = h5py.File(local_path, 'r')
35
+
36
+ def visitor_func(name, node):
37
+ if isinstance(node, h5py.Dataset):
38
+ accession = node.attrs['accession']
39
+ seq = node.attrs['consensus']
40
+ seqbank.add(seq=seq, accession=accession)
41
+
42
+ file['Families/DF'].visititems(visitor_func)
43
+
44
+
45
+ def download_dfam(seqbank: SeqBank, curated: bool = True, release: str = "current", force: bool = False) -> bool:
46
+ """
47
+ Downloads the Dfam HDF5 file, decompresses it, and adds sequences to the SeqBank.
48
+
49
+ Args:
50
+ seqbank (SeqBank): The SeqBank instance to which the Dfam sequences will be added.
51
+ curated (bool, optional): If True, fetches the curated-only version of the Dfam database. Defaults to True.
52
+ release (str, optional): The Dfam release version. Defaults to "current".
53
+ force (bool, optional): If True, forces the download even if the URL has been previously processed. Defaults to False.
54
+
55
+ Returns:
56
+ bool: True if the download and addition were successful, False otherwise.
57
+ """
58
+ url = dfam_url(curated, release)
59
+ url_key = seqbank.key_url(url)
60
+
61
+ if url_key in seqbank.file and not force:
62
+ print(f"Already downloaded: {url}")
63
+ return False
64
+
65
+ with tempfile.TemporaryDirectory() as tmpdirname:
66
+ local_gzip_path = Path(tmpdirname) / Path(url).name
67
+ try:
68
+ print(f"Downloading Dfam: {url}")
69
+ download_file(url, local_gzip_path)
70
+
71
+ # Decompress the gzipped file
72
+ local_path = local_gzip_path.with_suffix("")
73
+ with gzip.open(local_gzip_path, 'rb') as infile, open(local_path, 'wb') as outfile:
74
+ shutil.copyfileobj(infile, outfile)
75
+
76
+ # Add sequences to SeqBank
77
+ add_dfam(seqbank, local_path)
78
+
79
+ # Mark URL as processed
80
+ seqbank.save_seen_url(url)
81
+ except Exception as err:
82
+ print(f"Failed to add Dfam: {url}: {err}")
83
+ return False
84
+
85
+ return True
seqbank/exceptions.py ADDED
@@ -0,0 +1,7 @@
1
+ class SeqBankError(Exception):
2
+ """
3
+ Custom exception class for handling errors specific to SeqBank operations.
4
+
5
+ This exception should be raised when any operation related to the SeqBank encounters an error.
6
+ """
7
+ pass
seqbank/io.py ADDED
@@ -0,0 +1,158 @@
1
+ from pathlib import Path
2
+ import gzip
3
+ import bz2
4
+ from Bio import SeqIO
5
+ import requests
6
+ import tempfile
7
+
8
+
9
+ def open_path(path: Path | str):
10
+ """
11
+ Opens a file, optionally decompressing it based on its extension.
12
+
13
+ Args:
14
+ path (Path | str): The path to the file, which can be a compressed (.gz, .bz2) or uncompressed file.
15
+
16
+ Returns:
17
+ file object: A file object opened for reading.
18
+
19
+ Raises:
20
+ ValueError: If the file has an unsupported compression extension.
21
+ """
22
+ path = Path(path)
23
+ suffix = path.suffix.lower()
24
+
25
+ if suffix == ".gz":
26
+ return gzip.open(path, "rt")
27
+
28
+ if suffix == ".bz2":
29
+ return bz2.open(path, "rt")
30
+
31
+ return open(path, "rt")
32
+
33
+
34
+ def get_file_format(path: Path | str) -> str:
35
+ """
36
+ Determines the sequence file format based on the file extension.
37
+
38
+ Supported file formats and their extensions:
39
+ - FASTA: .fa, .fna, .fasta
40
+ - GenBank: .genbank, .gb, .gbk
41
+ - EMBL: .embl
42
+ - Tab-delimited: .tab, .tsv
43
+ - Nexus: .nexus, .nxs
44
+ - FASTQ: .fastq, .fq
45
+
46
+ Args:
47
+ path (Path | str): The path to the file.
48
+
49
+ Returns:
50
+ str: The detected file format (e.g., 'fasta', 'genbank', 'embl', etc.).
51
+
52
+ Raises:
53
+ ValueError: If the file format cannot be determined.
54
+ """
55
+ path = Path(path)
56
+ suffix = path.suffix.lower()
57
+
58
+ if suffix in [".gz", ".bz2"]:
59
+ suffix = path.suffixes[-2].lower()
60
+
61
+ if suffix in [".fa", ".fna", ".fasta"]:
62
+ return "fasta"
63
+
64
+ if suffix in [".genbank", ".gb", ".gbk"]:
65
+ return "genbank"
66
+
67
+ if suffix in [".embl"]:
68
+ return "embl"
69
+
70
+ if suffix in [".tab", ".tsv"]:
71
+ return "tab"
72
+
73
+ if suffix in [".nexus", ".nxs"]:
74
+ return "nexus"
75
+
76
+ if suffix in [".fastq", ".fq"]:
77
+ return "fastq"
78
+
79
+ raise ValueError(f"Cannot determine file format of {path}.")
80
+
81
+
82
+ def seq_count(path: Path | str) -> int:
83
+ """
84
+ Counts the number of sequences in a file.
85
+
86
+ Args:
87
+ path (Path | str): The path to the file.
88
+
89
+ Returns:
90
+ int: The total number of sequences in the file.
91
+ """
92
+ file_format = get_file_format(path)
93
+ with open_path(path) as f:
94
+ total = sum(1 for _ in SeqIO.parse(f, file_format))
95
+ return total
96
+
97
+
98
+ def download_file(url: str, local_path: Path) -> Path:
99
+ """
100
+ Downloads a file from a URL and saves it to a specified local path.
101
+
102
+ Args:
103
+ url (str): The URL of the file to download.
104
+ local_path (Path): The path where the file should be saved.
105
+
106
+ Returns:
107
+ Path: The path to the downloaded file.
108
+
109
+ Raises:
110
+ requests.exceptions.HTTPError: If the download fails.
111
+ """
112
+ with requests.get(url, stream=True) as r:
113
+ r.raise_for_status()
114
+ with open(local_path, 'wb') as f:
115
+ for chunk in r.iter_content(chunk_size=8192):
116
+ f.write(chunk)
117
+ return local_path
118
+
119
+
120
+ class TemporaryDirectory(tempfile.TemporaryDirectory):
121
+ """
122
+ A custom TemporaryDirectory class that can optionally create a parent directory if a prefix is provided.
123
+
124
+ Args:
125
+ prefix (str | Path | None): Optional prefix for the temporary directory.
126
+ """
127
+
128
+ def __init__(self, prefix: str | Path | None = None, *args, **kwargs):
129
+ self._created_dirs = [] # Track directories that were created
130
+ if isinstance(prefix, Path):
131
+ # Resolve path to string and ensure parent directories are created
132
+ prefix.mkdir(parents=True, exist_ok=True)
133
+ self._created_dirs.append(prefix) # Track created parent directories
134
+
135
+ # Ensure prefix ends with a "/"
136
+ prefix = str(prefix.resolve().absolute())
137
+ if not prefix.endswith("/"):
138
+ prefix += "/"
139
+
140
+ super().__init__(prefix=prefix, *args, **kwargs)
141
+
142
+ def cleanup(self):
143
+ """
144
+ Cleans up the temporary directory and removes any created parent directories if they are empty.
145
+ """
146
+ super().cleanup()
147
+ for created_dir in self._created_dirs:
148
+ if not any(created_dir.iterdir()): # Only remove if the directory is empty
149
+ created_dir.rmdir()
150
+
151
+ def __enter__(self) -> Path:
152
+ """
153
+ Enters the context of the temporary directory.
154
+
155
+ Returns:
156
+ Path: The path to the temporary directory.
157
+ """
158
+ return Path(super().__enter__())
seqbank/main.py ADDED
@@ -0,0 +1,166 @@
1
+ import typer
2
+ from pathlib import Path
3
+ from rich.progress import track
4
+
5
+ from .refseq import get_refseq_urls
6
+ from .seqbank import SeqBank
7
+ from .dfam import download_dfam
8
+
9
+ import plotly.graph_objs as go
10
+
11
+ app = typer.Typer()
12
+
13
+ @app.command()
14
+ def add(path: Path, files: list[Path], format: str = "", filter: Path = None) -> None:
15
+ """Add sequences from a file or list of files to a SeqBank.
16
+
17
+ Args:
18
+ path (Path): The path to the SeqBank.
19
+ files (list[Path]): A list of file paths containing sequences.
20
+ format (str, optional): The format of the sequence files. Defaults to "".
21
+ filter (Path, optional): A filter file for sequences. Defaults to None.
22
+ """
23
+ print(f"Opening seqbank '{path}'")
24
+ seqbank = SeqBank(path=path, write=True)
25
+ seqbank.add_files(files, format=format, filter=filter)
26
+
27
+
28
+ @app.command()
29
+ def url(path: Path, urls: list[str], format: str = "", max: int = 0, workers: int = -1, tmp_dir: Path = None) -> None:
30
+ """Add sequences from a list of URLs to a SeqBank.
31
+
32
+ Args:
33
+ path (Path): The path to the SeqBank.
34
+ urls (list[str]): A list of URLs containing sequences.
35
+ format (str, optional): The format of the sequence files. Defaults to "".
36
+ max (int, optional): Maximum number of sequences to add. Defaults to 0 (all).
37
+ workers (int, optional): Number of workers to use for downloading. Defaults to -1.
38
+ tmp_dir (Path, optional): Temporary directory for downloads. Defaults to None.
39
+ """
40
+ print(f"Opening seqbank '{path}'")
41
+ seqbank = SeqBank(path=path, write=True)
42
+
43
+ seqbank.add_urls(urls, format=format, max=max, workers=workers, tmp_dir=tmp_dir)
44
+
45
+
46
+ @app.command()
47
+ def delete(path: Path, accessions: list[str]) -> None:
48
+ """Delete sequences from a SeqBank.
49
+
50
+ Args:
51
+ path (Path): The path to the SeqBank.
52
+ accessions (list[str]): A list of accessions to delete from the SeqBank.
53
+ """
54
+ print(f"Opening seqbank '{path}'")
55
+ seqbank = SeqBank(path=path, write=True)
56
+
57
+ for accession in track(accessions, "Deleting"):
58
+ seqbank.delete(accession)
59
+
60
+
61
+ @app.command()
62
+ def refseq(path: Path, max: int = 0, workers: int = -1, tmp_dir: Path = None) -> None:
63
+ """Download all RefSeq sequences to a SeqBank.
64
+
65
+ Args:
66
+ path (Path): The path to the SeqBank.
67
+ max (int, optional): Maximum number of sequences to add. Defaults to 0 (all).
68
+ workers (int, optional): Number of workers to use for downloading. Defaults to -1.
69
+ tmp_dir (Path, optional): Temporary directory for downloads. Defaults to None.
70
+ """
71
+ print("Getting RefSeq files list")
72
+ return url(path, get_refseq_urls(tmp_dir=tmp_dir), max=max, workers=workers, tmp_dir=tmp_dir)
73
+
74
+
75
+ @app.command()
76
+ def dfam(path: Path, release: str = "current", curated: bool = True) -> bool:
77
+ """Download DFam sequences to a SeqBank.
78
+
79
+ Args:
80
+ path (Path): The path to the SeqBank.
81
+ release (str, optional): The DFam release version to download. Defaults to "current".
82
+ curated (bool, optional): Whether to download curated sequences. Defaults to True.
83
+
84
+ Returns:
85
+ bool: True if the download and addition were successful, False otherwise.
86
+ """
87
+ print("Getting DFam")
88
+ seqbank = SeqBank(path=path, write=True)
89
+ return download_dfam(seqbank, release=release, curated=curated)
90
+
91
+
92
+ @app.command()
93
+ def ls(path: Path) -> None:
94
+ """List accessions in a SeqBank.
95
+
96
+ Args:
97
+ path (Path): The path to the SeqBank.
98
+ """
99
+ seqbank = SeqBank(path=path)
100
+ seqbank.ls()
101
+
102
+
103
+ @app.command()
104
+ def count(path: Path) -> None:
105
+ """Display the number of accessions in a SeqBank.
106
+
107
+ Args:
108
+ path (Path): The path to the SeqBank.
109
+ """
110
+ seqbank = SeqBank(path=path)
111
+ print(len(seqbank))
112
+
113
+
114
+ @app.command()
115
+ def cp(path: Path, new: Path) -> None:
116
+ """Copy each sequence from one SeqBank to another.
117
+
118
+ Args:
119
+ path (Path): The path to the source SeqBank.
120
+ new (Path): The path to the destination SeqBank.
121
+ """
122
+ print(f"Copying seqbank '{path}' to '{new}'")
123
+ seqbank = SeqBank(path=path)
124
+ new = SeqBank(path=new, write=True)
125
+ seqbank.copy(new)
126
+
127
+
128
+ @app.command()
129
+ def export(path: Path, output: Path, format: str = "fasta") -> None:
130
+ """Export a SeqBank to a specified format.
131
+
132
+ Args:
133
+ path (Path): The path to the SeqBank.
134
+ output (Path): The path to save the exported sequences.
135
+ format (str, optional): The format for the exported sequences. Defaults to "fasta".
136
+ """
137
+ print(f"Exporting seqbank '{path}' to '{output}' in {format} format")
138
+ seqbank = SeqBank(path=path)
139
+ return seqbank.export(output, format=format)
140
+
141
+
142
+ @app.command()
143
+ def histogram(path: Path, output_path: Path = None, show: bool = False, nbins: int = 30) -> None:
144
+ """Generate a histogram of sequence lengths from a SeqBank.
145
+
146
+ Args:
147
+ path (Path): The path to the SeqBank.
148
+ output_path (Path, optional): The path to save the histogram. If None, the histogram will be displayed.
149
+ show (bool, optional): Whether to display the histogram. Defaults to False.
150
+ nbins (int, optional): The number of bins for the histogram. Defaults to 30.
151
+ """
152
+ # Load the SeqBank
153
+ seqbank = SeqBank(path=path)
154
+
155
+ # Generate the histogram
156
+ fig: go.Figure = seqbank.histogram(nbins=nbins)
157
+
158
+ # Save the histogram to the specified output path
159
+ if output_path is None:
160
+ show = True
161
+ else:
162
+ fig.write_image(output_path)
163
+ print(f"Histogram saved to {output_path}")
164
+
165
+ if show:
166
+ fig.show()
seqbank/refseq.py ADDED
@@ -0,0 +1,43 @@
1
+ from pathlib import Path
2
+ import re
3
+ from .io import download_file, TemporaryDirectory
4
+
5
+ def get_refseq_filenames(tmp_dir: str | Path | None = None) -> list[str]:
6
+ """
7
+ Retrieves a list of RefSeq genomic filenames from the NCBI FTP site.
8
+
9
+ Args:
10
+ tmp_dir (str | Path | None, optional):
11
+ The directory to create a temporary folder in. If None, a default temporary directory is used.
12
+
13
+ Returns:
14
+ list[str]: A list of filenames sorted numerically by version.
15
+ """
16
+ with TemporaryDirectory(prefix=tmp_dir) as dirname:
17
+ local_path = dirname / 'refseq_complete.html'
18
+
19
+ download_file("https://ftp.ncbi.nlm.nih.gov/refseq/release/complete/", local_path)
20
+ text = local_path.read_text()
21
+
22
+ # Find all .genomic.fna.gz filenames
23
+ filenames = re.findall(r'>(.*?.genomic.fna.gz)<\/a>', text)
24
+
25
+ # Sort filenames numerically by version number (second part of filename)
26
+ filenames = sorted(filenames, key=lambda filename: int(filename.split(".")[1]))
27
+
28
+ return filenames
29
+
30
+
31
+ def get_refseq_urls(tmp_dir: str | Path | None = None) -> list[str]:
32
+ """
33
+ Retrieves a list of URLs for RefSeq genomic files from the NCBI FTP site.
34
+
35
+ Args:
36
+ tmp_dir (str | Path | None, optional):
37
+ The directory to create a temporary folder in. If None, a default temporary directory is used.
38
+
39
+ Returns:
40
+ list[str]: A list of URLs for the RefSeq genomic files.
41
+ """
42
+ filenames = get_refseq_filenames(tmp_dir=tmp_dir)
43
+ return [f"https://ftp.ncbi.nlm.nih.gov/refseq/release/complete/{filename}" for filename in filenames]
seqbank/seqbank.py ADDED
@@ -0,0 +1,537 @@
1
+ from functools import cached_property
2
+ import numpy as np
3
+ import gzip
4
+ import time
5
+ from pathlib import Path
6
+ from joblib import Parallel, delayed
7
+ import plotly.express as px
8
+ import plotly.graph_objs as go
9
+ from Bio.Seq import Seq
10
+ from Bio.SeqRecord import SeqRecord
11
+ from Bio import SeqIO
12
+ from attrs import define
13
+ from rich.progress import track
14
+ import pyfastx
15
+ from rich.progress import Progress, TimeElapsedColumn, MofNCompleteColumn
16
+ from datetime import datetime
17
+ from speedict import Rdict, Options, DBCompressionType, AccessType
18
+ import atexit
19
+
20
+ from .transform import seq_to_bytes, bytes_to_str
21
+ from .io import get_file_format, open_path, download_file, seq_count, TemporaryDirectory
22
+ from .exceptions import SeqBankError
23
+ from .utils import parse_filter
24
+
25
+
26
+
27
+ @define(slots=False)
28
+ class SeqBank():
29
+ path:Path
30
+ write:bool = False
31
+
32
+ def __attrs_post_init__(self) -> None:
33
+ """Initializes the SeqBank object after attributes are set.
34
+
35
+ Expands the user path for the SeqBank file and checks if it exists when not in write mode.
36
+
37
+ Raises:
38
+ FileNotFoundError: If the SeqBank file is not found at the given path when write mode is disabled.
39
+ """
40
+ self.path = Path(self.path).expanduser()
41
+ if not self.write and not self.path.exists():
42
+ raise FileNotFoundError(f"Cannot find SeqBank file at path: {self.path}")
43
+
44
+ def key(self, accession: str) -> str:
45
+ """Generates a key for a given accession.
46
+
47
+ Args:
48
+ accession (str): Accession string used to generate the key.
49
+
50
+ Returns:
51
+ str: A byte-encoded key as a string.
52
+ """
53
+ return bytes(accession, "ascii")
54
+
55
+
56
+ def key_url(self, url: str) -> str:
57
+ """Generates a key for a given URL.
58
+
59
+ Args:
60
+ url (str): The URL for which the key is generated.
61
+
62
+ Returns:
63
+ str: A byte-encoded key string prefixed with '/seqbank/url/'.
64
+ """
65
+ return self.key("/seqbank/url/" + url)
66
+
67
+
68
+ def close(self) -> None:
69
+ """Closes the SeqBank database connection.
70
+
71
+ Attempts to close the database connection, if it's open, and silently handles any exceptions.
72
+ """
73
+ try:
74
+ self._db.close()
75
+ except Exception:
76
+ pass
77
+
78
+ @cached_property
79
+ def file(self) -> Rdict:
80
+ """Initializes and configures the Rdict database for sequence storage.
81
+
82
+ Configures options for the database, such as compression type, optimization, and maximum open files.
83
+ Registers the close method to be executed upon program exit to ensure the database is closed.
84
+
85
+ Returns:
86
+ Rdict: The configured Rdict database object.
87
+ """
88
+ options = Options(raw_mode=True)
89
+ options.set_compression_type(DBCompressionType.none())
90
+ options.set_optimize_filters_for_hits(True)
91
+ options.optimize_for_point_lookup(1024)
92
+ options.set_max_open_files(500)
93
+
94
+ self._db = Rdict(
95
+ path=str(self.path),
96
+ options=options,
97
+ access_type=AccessType.read_write() if self.write else AccessType.read_only()
98
+ )
99
+
100
+ atexit.register(self.close)
101
+ return self._db
102
+
103
+ def __len__(self) -> int:
104
+ """Calculates the total number of items in the SeqBank.
105
+
106
+ Iterates over all the keys in the database and counts them.
107
+
108
+ Returns:
109
+ int: The number of entries in the SeqBank.
110
+ """
111
+ count = 0
112
+ for _ in track(self.file.keys()):
113
+ count += 1
114
+ return count
115
+
116
+ def __getitem__(self, accession: str) -> np.ndarray:
117
+ """Retrieves the sequence data associated with a given accession.
118
+
119
+ Args:
120
+ accession (str): The accession key to look up in the SeqBank.
121
+
122
+ Returns:
123
+ np.ndarray: The sequence data stored in the SeqBank for the given accession.
124
+
125
+ Raises:
126
+ SeqBankError: If the accession cannot be read or an error occurs during retrieval.
127
+ """
128
+ try:
129
+ key = self.key(accession)
130
+ file = self.file
131
+ return file[key]
132
+ except Exception as err:
133
+ raise SeqBankError(f"Failed to read {accession} in SeqBank {self.path}:\n{err}")
134
+
135
+ def items(self):
136
+ """Yields all key-value pairs from the SeqBank.
137
+
138
+ The key is the accession, and the value is the sequence data in NumPy array format.
139
+
140
+ Yields:
141
+ tuple: A tuple containing the accession (key) and the sequence data (value) as a NumPy array.
142
+ """
143
+ for k, v in self.file.items():
144
+ yield k, np.frombuffer(v, dtype="u1")
145
+
146
+ def __contains__(self, accession: str) -> bool:
147
+ """Checks if a given accession exists in the SeqBank.
148
+
149
+ Args:
150
+ accession (str): The accession to check for existence.
151
+
152
+ Returns:
153
+ bool: True if the accession exists in the SeqBank, otherwise False.
154
+ """
155
+ try:
156
+ return self.key(accession) in self.file
157
+ except Exception:
158
+ return False
159
+
160
+ def delete(self, accession: str) -> None:
161
+ """Deletes a sequence entry from the SeqBank by its accession.
162
+
163
+ Args:
164
+ accession (str): The accession of the sequence to delete.
165
+
166
+ Returns:
167
+ None
168
+ """
169
+ key = self.key(accession)
170
+ f = self.file
171
+ if key in f:
172
+ del f[key]
173
+
174
+ def add(self, seq: str | Seq | SeqRecord | np.ndarray, accession: str) -> None:
175
+ """Adds a sequence to the SeqBank with a given accession.
176
+
177
+ This method accepts a sequence in various formats (string, Seq, SeqRecord, or NumPy array)
178
+ and stores it in the SeqBank after appropriate conversion to byte format.
179
+
180
+ Args:
181
+ seq (str | Seq | SeqRecord | np.ndarray): The sequence to add to the SeqBank.
182
+ It can be a string, Bio.Seq object, SeqRecord, or a NumPy array.
183
+ accession (str): The accession key for the sequence to be stored under.
184
+
185
+ Returns:
186
+ None
187
+ """
188
+ key = self.key(accession)
189
+
190
+ if isinstance(seq, SeqRecord):
191
+ seq = seq.seq
192
+ if isinstance(seq, Seq):
193
+ seq = str(seq)
194
+ if isinstance(seq, str):
195
+ seq = seq_to_bytes(seq)
196
+
197
+ self.file[key] = seq
198
+
199
+ def add_file(self, path:Path, format:str="", progress=None, overall_task=None, filter:Path|list|set|None=None) -> None:
200
+ """Adds sequences from a file to the SeqBank.
201
+
202
+ This method processes a sequence file in various formats (e.g., FASTA, FASTQ), optionally filtering specific accessions,
203
+ and adds the sequences to the SeqBank. Progress tracking is available for large file imports.
204
+
205
+ Args:
206
+ path (Path): The path to the sequence file.
207
+ format (str, optional): The format of the sequence file (e.g., "fasta", "fastq"). If not provided, it will be auto-detected.
208
+ progress (Progress, optional): A rich progress bar to display the import progress. Defaults to None.
209
+ overall_task (int | None, optional): An optional task ID for tracking the overall progress. Defaults to None.
210
+ filter (Path | list | set | None, optional): A filter for selecting specific accessions. Defaults to None.
211
+
212
+ Returns:
213
+ None
214
+ """
215
+ filter = parse_filter(filter)
216
+ format = format or get_file_format(path)
217
+ progress = progress or Progress()
218
+
219
+ # If fasta or fastq use pyfastx for speed
220
+ if format in ["fasta", "fastq"]:
221
+ total = sum(1 for _ in pyfastx.Fasta(str(path), build_index=False))
222
+ task = progress.add_task(f"[magenta]{path.name}", total=total)
223
+
224
+ for accession, seq in pyfastx.Fasta(str(path), build_index=False):
225
+ if filter and accession not in filter:
226
+ continue
227
+ self.add(seq, accession)
228
+ progress.update(task, advance=1)
229
+ else:
230
+ total = seq_count(path)
231
+
232
+ with open_path(path) as f:
233
+ task = progress.add_task(f"[magenta]{path.name}", total=total)
234
+ for record in SeqIO.parse(f, format):
235
+ if filter and record.id not in filter:
236
+ continue
237
+
238
+ self.add(record, record.id)
239
+ progress.update(task, advance=1)
240
+
241
+ progress.update(task, visible=False)
242
+ if overall_task is not None:
243
+ progress.update(overall_task, advance=1)
244
+
245
+ print('Added', path.name)
246
+
247
+ def seen_url(self, url: str) -> bool:
248
+ """Checks if a given URL has been seen (i.e., processed) before and present in the SeqBank.
249
+
250
+ Args:
251
+ url (str): The URL to check.
252
+
253
+ Returns:
254
+ bool: True if the URL has been seen (i.e., exists in the SeqBank), otherwise False.
255
+ """
256
+ return self.key_url(url) in self.file
257
+
258
+ def save_seen_url(self, url: str) -> None:
259
+ """Saves a URL as 'seen' by adding it to the SeqBank with a timestamp.
260
+
261
+ Args:
262
+ url (str): The URL to save as seen.
263
+
264
+ Returns:
265
+ None
266
+ """
267
+ url_key = self.key_url(url)
268
+ self.file[url_key] = bytes(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "ascii")
269
+
270
+ def add_url(self, url:str, progress=None, format:str="", force:bool=False, overall_task=None, tmp_dir:str|Path|None=None) -> bool:
271
+ """Downloads and adds sequences from a URL to the SeqBank.
272
+
273
+ This method downloads a file from a given URL, processes it to extract sequences, and adds them to the SeqBank.
274
+ If the URL has already been processed, it can be skipped unless `force=True` is provided.
275
+
276
+ Args:
277
+ url (str): The URL to download the sequence file from.
278
+ progress (Progress, optional): A rich progress bar to display progress of the download and file processing. Defaults to None.
279
+ format (str, optional): The format of the sequence file (e.g., "fasta", "fastq"). If not provided, it will be auto-detected. Defaults to "".
280
+ force (bool, optional): Whether to force downloading and processing the URL even if it has been seen before. Defaults to False.
281
+ overall_task (int | None, optional): An optional task ID for tracking overall progress. Defaults to None.
282
+ tmp_dir (str | Path | None, optional): A temporary directory to store the downloaded file. Defaults to None.
283
+
284
+ Returns:
285
+ bool: True if the URL was successfully processed and added, False otherwise.
286
+ """
287
+ url_key = self.key_url(url)
288
+ if url_key in self.file and not force:
289
+ return False
290
+
291
+ with TemporaryDirectory(prefix=tmp_dir) as tmpdirname:
292
+ local_path = tmpdirname / Path(url).name
293
+ try:
294
+ download_file(url, local_path)
295
+ self.add_file(local_path, format=format, progress=progress, overall_task=overall_task)
296
+ self.save_seen_url(url)
297
+ except Exception as err:
298
+ print(f"Failed to add URL: {url}: {err}")
299
+ return False
300
+
301
+ return True
302
+
303
+ def get_accessions(self) -> set[str]:
304
+ """Retrieves all accessions stored in the SeqBank.
305
+
306
+ This method iterates through the SeqBank database keys and collects all accessions that do not belong
307
+ to the internal '/seqbank/' namespace.
308
+
309
+ Returns:
310
+ set[str]: A set of all accessions present in the SeqBank.
311
+ """
312
+ accessions = set()
313
+ file = self.file
314
+
315
+ for key in file.keys():
316
+ accession = key.decode("ascii")
317
+ if not accession.startswith("/seqbank/"):
318
+ accessions.update([accession])
319
+
320
+ return accessions
321
+
322
+ def missing(self, accessions: list[str] | set[str]) -> set[str]:
323
+ """Finds accessions that are not present in the SeqBank.
324
+
325
+ This method checks a list or set of accessions and returns those that are missing from the SeqBank.
326
+
327
+ Args:
328
+ accessions (list[str] | set[str]): A list or set of accessions to check for presence in the SeqBank.
329
+
330
+ Returns:
331
+ set[str]: A set of accessions that are missing from the SeqBank.
332
+ """
333
+ missing = set()
334
+
335
+ for accession in track(accessions):
336
+ if accession not in self:
337
+ missing.add(accession)
338
+
339
+ return missing
340
+
341
+ def add_urls(self, urls:list[str], max:int=0, format:str="", force:bool=False, workers:int=-1, tmp_dir:str|Path|None=None) -> None:
342
+ """Downloads and adds sequences from a list of URLs to the SeqBank.
343
+
344
+ This method processes a list of URLs, downloads the corresponding sequence files, and adds them to the SeqBank.
345
+ It filters out URLs that have already been processed unless `force=True` is specified, and it can limit the number
346
+ of URLs processed based on the `max` argument. The processing can be parallelized using the `workers` argument.
347
+
348
+ Args:
349
+ urls (list[str]): A list of URLs to download and process.
350
+ max (int, optional): Maximum number of URLs to process. If set to 0, all URLs will be processed. Defaults to 0.
351
+ format (str, optional): The format of the sequence files (e.g., "fasta", "fastq"). If not provided, it will be auto-detected. Defaults to "".
352
+ force (bool, optional): Whether to force re-processing of URLs even if they were processed before. Defaults to False.
353
+ workers (int, optional): Number of workers to use for parallel processing. If set to -1, all available CPU cores will be used. Defaults to -1.
354
+ tmp_dir (str | Path | None, optional): A temporary directory to store downloaded files. Defaults to None.
355
+
356
+ Returns:
357
+ None
358
+ """
359
+ # only add the URLs that haven't been seen before
360
+ urls_to_add = []
361
+ for url in urls:
362
+ if not self.seen_url(url):
363
+ urls_to_add.append(url)
364
+
365
+ # truncate URLs list to `max` if requested
366
+ if max and len(urls_to_add) >= max:
367
+ break
368
+
369
+ with Progress(*Progress.get_default_columns(), TimeElapsedColumn(), MofNCompleteColumn()) as progress:
370
+ parallel = Parallel(n_jobs=workers, prefer="threads")
371
+ add_url = delayed(self.add_url)
372
+ overall_task = progress.add_task(f"[bold red]Adding URLs", total=len(urls_to_add))
373
+ parallel(add_url(url, progress=progress, format=format, force=force, overall_task=overall_task, tmp_dir=tmp_dir) for url in urls_to_add)
374
+
375
+ def ls(self) -> None:
376
+ """
377
+ Lists all accessions in the SeqBank.
378
+
379
+ Iterates through the keys in the SeqBank and prints each one, decoded from bytes to ASCII.
380
+
381
+ Returns:
382
+ None
383
+ """
384
+ for k in self.file.keys():
385
+ print(k.decode("ascii"))
386
+
387
+ def add_files(self, files:list[str], max:int=0, format:str="", workers:int=1, filter:Path|list[str]|set[str]|None=None) -> None:
388
+ """
389
+ Adds sequences from multiple files to the SeqBank.
390
+
391
+ This method processes a list of file paths, downloading and adding sequences from each file to the SeqBank.
392
+ It supports parallel processing and optional filtering of specific accessions. The `max` argument limits the number of files to process.
393
+
394
+ Args:
395
+ files (list[str]): A list of file paths to process.
396
+ max (int, optional): Maximum number of files to process. If set to 0, all files will be processed. Defaults to 0.
397
+ format (str, optional): The format of the sequence files (e.g., "fasta", "fastq"). If not provided, it will be auto-detected. Defaults to "".
398
+ workers (int, optional): Number of workers to use for parallel processing. Defaults to 1.
399
+ filter (Path | list[str] | set[str] | None, None], optional): A filter for selecting specific accessions. Defaults to None.
400
+
401
+ Returns:
402
+ None
403
+ """
404
+ filter = parse_filter(filter)
405
+
406
+ with Progress(*Progress.get_default_columns(), TimeElapsedColumn(), MofNCompleteColumn()) as progress:
407
+ parallel = Parallel(n_jobs=workers, prefer="threads")
408
+ add_file = delayed(self.add_file)
409
+ overall_task = progress.add_task(f"[bold red]Adding files", total=len(files))
410
+ parallel(add_file(file, progress=progress, format=format, overall_task=overall_task, filter=filter) for file in files)
411
+
412
+ def copy(self, other: 'SeqBank') -> None:
413
+ """
414
+ Copies all entries from the current SeqBank to another SeqBank instance.
415
+
416
+ This method iterates over all key-value pairs in the current SeqBank and adds them to the `other` SeqBank instance.
417
+ The `other` SeqBank must be writable.
418
+
419
+ Args:
420
+ other (SeqBank): The target SeqBank instance where entries will be copied.
421
+
422
+ Returns:
423
+ None
424
+ """
425
+ for k, v in track(self.file.items()):
426
+ other.file[k] = v
427
+
428
+ def numpy(self, accession: str) -> np.ndarray:
429
+ """
430
+ Retrieves the sequence data for a given accession and returns it as an unsigned char NumPy array.
431
+
432
+ Args:
433
+ accession (str): The accession key for which the sequence data is retrieved.
434
+
435
+ Returns:
436
+ np.ndarray: The sequence data associated with the given accession, represented as an unsigned char NumPy array.
437
+ """
438
+ return np.frombuffer(self[accession], dtype="u1")
439
+
440
+ def string(self, accession: str) -> str:
441
+ """
442
+ Retrieves the sequence data for a given accession and returns it as a string.
443
+
444
+ Args:
445
+ accession (str): The accession key for which the sequence data is retrieved.
446
+
447
+ Returns:
448
+ str: The sequence data associated with the given accession, represented as a string.
449
+ """
450
+ data = self[accession]
451
+ return bytes_to_str(data)
452
+
453
+ def record(self, accession: str) -> SeqRecord:
454
+ """
455
+ Retrieves the sequence data for a given accession and returns it as a BioPython SeqRecord object.
456
+
457
+ Args:
458
+ accession (str): The accession key for which the sequence data is retrieved.
459
+
460
+ Returns:
461
+ SeqRecord: A BioPython SeqRecord object containing the sequence data, with the given accession as its ID and an empty description.
462
+ """
463
+ record = SeqRecord(
464
+ Seq(self.string(accession)),
465
+ id=accession,
466
+ description="",
467
+ )
468
+ return record
469
+
470
+ def export(self, output: Path | str, format: str = "", accessions: list[str] | str | Path | None = None) -> None:
471
+ """
472
+ Exports the data from the SeqBank to a file using BioPython's SeqIO.
473
+
474
+ Args:
475
+ output (Path | str): The path or filename where the data should be exported.
476
+ format (str, optional): The file format for exporting. If not specified, it will be inferred from the file extension.
477
+ accessions (list[str] | str | Path | None, optional): A list of accessions to export. If a file path or string is provided, it will be read to obtain the list of accessions. If None, all accessions in the SeqBank are exported.
478
+
479
+ Returns:
480
+ None
481
+ """
482
+ accessions = accessions or self.get_accessions()
483
+
484
+ # Read list of accessions if given file
485
+ if isinstance(accessions, (str,Path)):
486
+ accessions = Path(accessions).read_text().strip().split("\n")
487
+
488
+ format = format or get_file_format(output)
489
+ with open(output, "w") as f:
490
+ for accession in accessions:
491
+ SeqIO.write(self.record(accession), f, format)
492
+
493
+ def lengths_dict(self) -> dict[str, int]:
494
+ """
495
+ Returns a dictionary where the keys are the accessions and the values
496
+ are the corresponding lengths of each sequence.
497
+
498
+ Returns:
499
+ dict[str, int]: A dictionary mapping each accession to the length of its corresponding sequence.
500
+ """
501
+ accession_lengths = {}
502
+
503
+ for accession in self.get_accessions():
504
+ # Retrieve the sequence
505
+ sequence = self[accession]
506
+ # Store the length of the sequence in the dictionary
507
+ accession_lengths[accession] = len(sequence)
508
+
509
+ return accession_lengths
510
+
511
+ def histogram(self, nbins: int = 30) -> go.Figure:
512
+ """
513
+ Creates a histogram of the lengths of all sequences and returns the Plotly figure object.
514
+
515
+ Args:
516
+ nbins (int): The number of bins for the histogram. Default is 30.
517
+
518
+ Returns:
519
+ go.Figure: A Plotly figure object representing the histogram of sequence lengths.
520
+ """
521
+ # Get the dictionary of accession lengths
522
+ accession_lengths = self.lengths_dict()
523
+
524
+ # Extract the lengths from the dictionary
525
+ lengths = list(accession_lengths.values())
526
+
527
+ # Create the histogram using Plotly Express
528
+ fig = px.histogram(lengths, nbins=nbins, title="Histogram of Sequence Lengths")
529
+
530
+ # Add labels and customize the layout, removing the legend
531
+ fig.update_layout(
532
+ xaxis_title="Sequence Length",
533
+ yaxis_title="Count",
534
+ showlegend=False # Remove the legend
535
+ )
536
+
537
+ return fig
seqbank/transform.py ADDED
@@ -0,0 +1,63 @@
1
+ import numpy as np
2
+
3
+ vocab_to_int = {"A": 1, "C": 2, "G": 3, "T": 4, "N": 0}
4
+ int_to_vocab = dict(zip(vocab_to_int.values(), vocab_to_int.keys()))
5
+
6
+ DELETE = bytes(range(256)).translate(None, b'ACGTNacgtn')
7
+ TABLE = bytearray(b'\0' * 256)
8
+ REVERSE_TABLE = bytearray(b'\0' * 256)
9
+
10
+ for char, value in vocab_to_int.items():
11
+ TABLE[ord(char)] = value
12
+ TABLE[ord(char.lower())] = value
13
+
14
+ REVERSE_TABLE[value] = ord(char)
15
+
16
+
17
+ def seq_to_bytes(seq: str | bytes, delete: bool = True) -> bytes:
18
+ """
19
+ Convert a DNA sequence to a byte representation using a predefined mapping.
20
+
21
+ Args:
22
+ seq (str | bytes): The DNA sequence to convert. Can be a string or bytes.
23
+ delete (bool, optional): If True, delete characters that are not in 'ACGTN' or 'acgtn'. Defaults to True.
24
+
25
+ Returns:
26
+ bytes: The byte-encoded version of the input sequence.
27
+ """
28
+ if isinstance(seq, str):
29
+ seq = seq.encode("ascii")
30
+
31
+ assert isinstance(seq, bytes), f"Expected bytes, got {type(seq)}"
32
+
33
+ if delete:
34
+ return seq.translate(TABLE, DELETE)
35
+ return seq.translate(TABLE)
36
+
37
+
38
+ def seq_to_numpy(seq: str | bytes, delete: bool = True) -> np.ndarray:
39
+ """
40
+ Convert a DNA sequence to a NumPy array of unsigned 8-bit integers.
41
+
42
+ Args:
43
+ seq (str | bytes): The DNA sequence to convert, in string or byte format.
44
+ delete (bool, optional): If True, remove any characters not in 'ACGTN'. Defaults to True.
45
+
46
+ Returns:
47
+ np.ndarray: A NumPy array representing the byte-encoded DNA sequence.
48
+ """
49
+ b = seq_to_bytes(seq, delete=delete)
50
+ return np.frombuffer(b, dtype="u1")
51
+
52
+
53
+ def bytes_to_str(seq: bytes) -> str:
54
+ """
55
+ Convert byte-encoded DNA sequence back to a string.
56
+
57
+ Args:
58
+ seq (bytes): The byte-encoded DNA sequence.
59
+
60
+ Returns:
61
+ str: The original DNA sequence in string format.
62
+ """
63
+ return seq.translate(REVERSE_TABLE).decode("ascii")
seqbank/utils.py ADDED
@@ -0,0 +1,24 @@
1
+ from typing import Optional
2
+ from pathlib import Path
3
+
4
+ def parse_filter(filter: Path | list[str] | set[str] | None = None) -> Optional[set[str]]:
5
+ """Converts the input into a set of strings to use as a filter.
6
+
7
+ Args:
8
+ filter (Path | list[str] | set[str] | None, optional):
9
+ The filter can be:
10
+ - A `Path` to a file where each line contains a string.
11
+ - A `list` or `set` of strings.
12
+ - `None`, in which case the function will return `None`.
13
+
14
+ Returns:
15
+ Optional[set[str]]:
16
+ A set of strings to use as a filter, or `None` if the input is `None`.
17
+ """
18
+ if not filter:
19
+ return None
20
+
21
+ if isinstance(filter, Path):
22
+ return set(filter.read_text().strip().split("\n"))
23
+
24
+ return set(filter)
@@ -0,0 +1,249 @@
1
+ Metadata-Version: 2.1
2
+ Name: seqbank
3
+ Version: 0.1.2
4
+ Summary: A database to quickly read and write DNA sequence data in numerical form.
5
+ License: Apache-2.0
6
+ Author: Robert Turnbull
7
+ Author-email: robert.turnbull@unimelb.edu.au
8
+ Requires-Python: >=3.10,<3.13
9
+ Classifier: License :: OSI Approved :: Apache Software License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Requires-Dist: attrs (>=23.1.0)
15
+ Requires-Dist: biopython (>=1.81)
16
+ Requires-Dist: h5py (>=3.9.0)
17
+ Requires-Dist: joblib (>=1.3.2)
18
+ Requires-Dist: kaleido (==0.2.1)
19
+ Requires-Dist: numpy (>=1.22.0)
20
+ Requires-Dist: pandas (>=2.0.0)
21
+ Requires-Dist: plotly (>=5.0.0)
22
+ Requires-Dist: pyfastx (>=1.1.0)
23
+ Requires-Dist: requests (>=2.31.0)
24
+ Requires-Dist: rich (>=10.16.1)
25
+ Requires-Dist: speedict (>=0.3.11)
26
+ Requires-Dist: typer (>=0.4.0)
27
+ Description-Content-Type: text/x-rst
28
+
29
+ ================
30
+ Seqbank
31
+ ================
32
+
33
+ .. start-badges
34
+
35
+ |testing badge| |coverage badge| |docs badge| |black badge| |git3moji badge|
36
+
37
+ .. |testing badge| image:: https://github.com/rbturnbull/seqbank/actions/workflows/testing.yml/badge.svg
38
+ :target: https://github.com/rbturnbull/seqbank/actions
39
+
40
+ .. |docs badge| image:: https://github.com/rbturnbull/seqbank/actions/workflows/docs.yml/badge.svg
41
+ :target: https://rbturnbull.github.io/seqbank
42
+
43
+ .. |black badge| image:: https://img.shields.io/badge/code%20style-black-000000.svg
44
+ :target: https://github.com/psf/black
45
+
46
+ .. |coverage badge| image:: https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/rbturnbull/b1625e7f45428007f0982543d9d346d0/raw/coverage-badge.json
47
+ :target: https://rbturnbull.github.io/seqbank/coverage/
48
+
49
+ .. |git3moji badge| image:: https://img.shields.io/badge/git3moji-%E2%9A%A1%EF%B8%8F%F0%9F%90%9B%F0%9F%93%BA%F0%9F%91%AE%F0%9F%94%A4-fffad8.svg
50
+ :target: https://robinpokorny.github.io/git3moji/
51
+
52
+ .. end-badges
53
+
54
+ .. start-quickstart
55
+
56
+ SeqBank is a powerful and flexible command-line application designed to simplify the management and processing of large DNA sequence datasets. Whether you're working with local sequence files,
57
+ retrieving data from remote URLs, or integrating sequences from databases like RefSeq and DFam, SeqBank provides an efficient, user-friendly solution.
58
+
59
+ SeqBank allows users to quickly add, organize, and manipulate sequences using a structured, numerical format optimized for fast retrieval and analysis.
60
+ It's especially useful for bioinformatics professionals who regularly handle vast amounts of genomic data.
61
+
62
+
63
+ Installation
64
+ ============
65
+
66
+ To install the latest version from the repository, you can use this command:
67
+
68
+ .. code-block:: bash
69
+
70
+ pip install git+https://github.com/rbturnbull/seqbank.git
71
+
72
+ .. note ::
73
+
74
+ Soon seqbank will be able to be installed using conda and PyPI
75
+
76
+
77
+ Usage
78
+ ===========
79
+
80
+ SeqBank provides a command-line interface (CLI) for managing DNA sequence data efficiently. Below are the main tools, along with examples of how to use them in practical workflows.
81
+
82
+ Adding Sequences
83
+ ----------------
84
+
85
+ SeqBank allows you to import sequence data from files or URLs into the database. The system supports multiple sequence formats, providing flexibility in handling various datasets.
86
+
87
+ **Example:**
88
+
89
+ To add sequences from one or more local files:
90
+
91
+ .. code-block:: bash
92
+
93
+ seqbank add /path/to/seqbank /path/to/sequence1.fasta /path/to/sequence2.fasta --format fasta
94
+
95
+ To add sequences from a list of URLs:
96
+
97
+ .. code-block:: bash
98
+
99
+ seqbank url /path/to/seqbank https://example.com/sequence1.fasta https://example.com/sequence2.fasta --format fasta --workers 4
100
+
101
+ **Use case:**
102
+ Suppose you have a new set of genome sequences in FASTA format stored locally or accessible via URLs. You can quickly import these sequences into your SeqBank database for centralized storage and further analysis.
103
+
104
+
105
+ Managing Databases
106
+ ------------------
107
+
108
+ SeqBank provides commands to manage and query the sequences in your database. You can list, count, and delete sequences, allowing efficient database management.
109
+
110
+ **Example:**
111
+
112
+ To list all sequences in the database:
113
+
114
+ .. code-block:: bash
115
+
116
+ seqbank ls /path/to/seqbank
117
+
118
+ To count the number of sequences stored:
119
+
120
+ .. code-block:: bash
121
+
122
+ seqbank count /path/to/seqbank
123
+
124
+ To delete a specific sequence by accession number:
125
+
126
+ .. code-block:: bash
127
+
128
+ seqbank delete /path/to/seqbank ABC123DEF456
129
+
130
+ **Use case:**
131
+ If you're managing a growing sequence database, the `ls` command can help you track the sequences, while `delete` can be used to remove outdated or incorrect entries.
132
+
133
+
134
+ Exporting Sequences
135
+ -------------------
136
+
137
+ You can export your stored sequences to common formats like FASTA for easy sharing and use with other bioinformatics tools. This ensures compatibility with external platforms.
138
+
139
+ **Example:**
140
+
141
+ To export sequences in FASTA format to a specific output directory:
142
+
143
+ .. code-block:: bash
144
+
145
+ seqbank export /path/to/seqbank /output/directory --format fasta
146
+
147
+ **Use case:**
148
+ After storing a collection of curated sequences, you may need to export them in FASTA format for downstream analysis using tools like BLAST or multiple sequence alignment software.
149
+
150
+
151
+ Integration with RefSeq and DFam
152
+ --------------------------------
153
+
154
+ SeqBank integrates with popular genomic databases like RefSeq and DFam, allowing users to download and incorporate sequences from these sources.
155
+
156
+ **Example:**
157
+
158
+ To download and add RefSeq sequences with a maximum of 1000 sequences using 4 workers:
159
+
160
+ .. code-block:: bash
161
+
162
+ seqbank refseq /path/to/seqbank --max 1000 --workers 4
163
+
164
+ To download and add DFam sequences from the current release with curated data:
165
+
166
+ .. code-block:: bash
167
+
168
+ seqbank dfam /path/to/seqbank --release current --curated
169
+
170
+ **Use case:**
171
+ If you are studying repetitive elements in a genome, you can easily integrate sequences from DFam into your SeqBank database for comprehensive analysis.
172
+
173
+
174
+ Visualization of Sequence Data
175
+ ------------------------------
176
+
177
+ SeqBank includes built-in functionality for generating histograms of sequence lengths, providing a visual summary of the data.
178
+
179
+ **Example:**
180
+
181
+ To generate and save a histogram of sequence lengths:
182
+
183
+ .. code-block:: bash
184
+
185
+ seqbank histogram /path/to/seqbank --output histogram.png --nbins 50
186
+
187
+ To generate and display the histogram interactively:
188
+
189
+ .. code-block:: bash
190
+
191
+ seqbank histogram /path/to/seqbank --show --nbins 50
192
+
193
+ **Use case:**
194
+ When working with a dataset of varying sequence lengths, generating a histogram can help visualize the distribution and detect outliers or inconsistencies in the data.
195
+
196
+
197
+ Copying Databases
198
+ -----------------
199
+
200
+ SeqBank allows you to copy sequences from one SeqBank database to another, facilitating data migration or backup processes.
201
+
202
+ **Example:**
203
+
204
+ To copy sequences from a source SeqBank to a destination SeqBank:
205
+
206
+ .. code-block:: bash
207
+
208
+ seqbank cp /path/to/source_seqbank /path/to/destination_seqbank
209
+
210
+ **Use case:**
211
+ For maintaining backups of your sequence database or migrating data to a new location, the `cp` command provides a straightforward method to duplicate your SeqBank data.
212
+
213
+
214
+ Filtering Sequences and Custom Workflows
215
+ ----------------------------------------
216
+
217
+ SeqBank supports filtering sequences based on criteria such as sequence length or file format before adding them to the database. Additionally, multi-threaded downloading allows you to download and process sequences more efficiently.
218
+
219
+ **Example:**
220
+
221
+ To filter sequences longer than 1000 bp before adding them:
222
+
223
+ .. code-block:: bash
224
+
225
+ seqbank add /path/to/seqbank /path/to/sequences.fasta --format fasta --filter /path/to/filter_file
226
+
227
+ To enable multi-threaded downloading when adding sequences from URLs:
228
+
229
+ .. code-block:: bash
230
+
231
+ seqbank url /path/to/seqbank https://example.com/sequence1.fasta https://example.com/sequence2.fasta --format fasta --workers 4 --tmp-dir /path/to/tmp
232
+
233
+ **Use case:**
234
+ In projects where only sequences longer than a specific threshold are required, the filtering feature ensures that only relevant sequences are stored. Multi-threaded downloading can be utilized when processing large datasets to save time.
235
+
236
+ .. end-quickstart
237
+
238
+
239
+ Credits
240
+ ==================================
241
+
242
+ .. start-credits
243
+
244
+ * Robert Turnbull <robert.turnbull@unimelb.edu.au>
245
+ * Rafsan Al Mamun <rafsan7238@gmail.com>
246
+
247
+ .. end-credits
248
+
249
+
@@ -0,0 +1,13 @@
1
+ seqbank/__init__.py,sha256=BffCmT3tluD-LpF2MkPss4a_n4guxfB2qTkd7TDge_A,65
2
+ seqbank/dfam.py,sha256=riuYk1NmkIax5414lU2ANXmyiLUIv5y1l7exrDNgtHc,2985
3
+ seqbank/exceptions.py,sha256=gGtjVFsoxP7pfn3LSUSdFoNKOyiFdNv7zFNw9-7f6wg,234
4
+ seqbank/io.py,sha256=N6oFoyyK-xO_F9iGtEwVYgZm6IM5TYLMuE89uNAYLuE,4400
5
+ seqbank/main.py,sha256=uHiysBdUutfIQk-n1ZmLh2m612A-uzsZaZo0XiJLV5o,5555
6
+ seqbank/refseq.py,sha256=iRXHplyv-miEkP6k6gak-Ly3UTqhkvPN0PkrASWbJaM,1637
7
+ seqbank/seqbank.py,sha256=HlFcl29qdevswy4CZ8LZq8xfd79CBqCpPfj1LYsJW34,21368
8
+ seqbank/transform.py,sha256=IFM0S9b5hTx5eSKW3evK6G6gTWzIAm1mp4zKs0tGhnc,1919
9
+ seqbank/utils.py,sha256=DU0qEQ_ec4XQeTlcm6BxzeyWs-fqZYgVGrnziuT_9Ys,797
10
+ seqbank-0.1.2.dist-info/METADATA,sha256=-ypiqPeKIpzr_ekz6L6TwH3p7fwx-Y2ImL9ZQ-SszeQ,8404
11
+ seqbank-0.1.2.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
12
+ seqbank-0.1.2.dist-info/entry_points.txt,sha256=u7kmL0XS-HbARkUTD2FPU-J-fEBYx51iJMrz9tBZeLA,44
13
+ seqbank-0.1.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 1.8.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ seqbank=seqbank.main:app
3
+