meta_eukaryome_detect 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- meta_eukaryome_detect-0.1.0/LICENSE +21 -0
- meta_eukaryome_detect-0.1.0/PKG-INFO +62 -0
- meta_eukaryome_detect-0.1.0/README.md +14 -0
- meta_eukaryome_detect-0.1.0/meta_eukaryome_detect/__init__.py +5 -0
- meta_eukaryome_detect-0.1.0/meta_eukaryome_detect/create_ngless_template.py +61 -0
- meta_eukaryome_detect-0.1.0/meta_eukaryome_detect/depth_summary.py +117 -0
- meta_eukaryome_detect-0.1.0/meta_eukaryome_detect/external_tools.py +90 -0
- meta_eukaryome_detect-0.1.0/meta_eukaryome_detect/meta_eukaryome_database.py +133 -0
- meta_eukaryome_detect-0.1.0/meta_eukaryome_detect/meta_eukaryome_detect.py +292 -0
- meta_eukaryome_detect-0.1.0/pyproject.toml +110 -0
- meta_eukaryome_detect-0.1.0/tests/__init__.py +1 -0
- meta_eukaryome_detect-0.1.0/tests/test_meta_eukaryome_detect.py +2 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023, Anthony Fullam
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: meta_eukaryome_detect
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Pathogen, Parasite, Eukaryote and Virus detection in metagenomes..
|
|
5
|
+
Home-page: https://git.embl.de/grp-bork/meta-eukaryome-detect
|
|
6
|
+
License: MIT
|
|
7
|
+
Author: Anthony Fullam
|
|
8
|
+
Author-email: anthony.fullam@embl.de
|
|
9
|
+
Requires-Python: >=3.11,<4.0
|
|
10
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Natural Language :: English
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Provides-Extra: doc
|
|
19
|
+
Provides-Extra: test
|
|
20
|
+
Requires-Dist: black (>=23.10.1,<24.0.0) ; extra == "test"
|
|
21
|
+
Requires-Dist: bump2version (>=1.0.1,<2.0.0) ; extra == "dev"
|
|
22
|
+
Requires-Dist: flake8 (>=3.9.2,<4.0.0) ; extra == "test"
|
|
23
|
+
Requires-Dist: flake8-docstrings (>=1.6.0,<2.0.0) ; extra == "test"
|
|
24
|
+
Requires-Dist: flask (>=3.0.0,<4.0.0)
|
|
25
|
+
Requires-Dist: isort (>=5.8.0,<6.0.0) ; extra == "test"
|
|
26
|
+
Requires-Dist: jinja2 (>=3.1.2,<4.0.0)
|
|
27
|
+
Requires-Dist: mkdocs (>=1.5.3,<2.0.0) ; extra == "doc"
|
|
28
|
+
Requires-Dist: mkdocs-autorefs (>=0.5.0,<0.6.0) ; extra == "doc"
|
|
29
|
+
Requires-Dist: mkdocs-include-markdown-plugin (>=6.0.3,<7.0.0) ; extra == "doc"
|
|
30
|
+
Requires-Dist: mkdocs-material (>=6.1.7,<7.0.0) ; extra == "doc"
|
|
31
|
+
Requires-Dist: mkdocs-material-extensions (>=1.0.1,<2.0.0)
|
|
32
|
+
Requires-Dist: mkdocstrings (>=0.23.0,<0.24.0) ; extra == "doc"
|
|
33
|
+
Requires-Dist: mkdocstrings-python (>=1.7.3,<2.0.0)
|
|
34
|
+
Requires-Dist: mypy (>=0.900,<0.901) ; extra == "test"
|
|
35
|
+
Requires-Dist: pandas (>=2.2.3,<3.0.0)
|
|
36
|
+
Requires-Dist: pip (>=20.3.1,<21.0.0) ; extra == "dev"
|
|
37
|
+
Requires-Dist: pre-commit (>=2.12.0,<3.0.0) ; extra == "dev"
|
|
38
|
+
Requires-Dist: pytest (>=6.2.4,<7.0.0) ; extra == "test"
|
|
39
|
+
Requires-Dist: pytest-cov (>=2.12.0,<3.0.0) ; extra == "test"
|
|
40
|
+
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
41
|
+
Requires-Dist: toml (>=0.10.2,<0.11.0) ; extra == "dev"
|
|
42
|
+
Requires-Dist: tox (>=3.20.1,<4.0.0) ; extra == "dev"
|
|
43
|
+
Requires-Dist: twine (>=3.3.0,<4.0.0) ; extra == "dev"
|
|
44
|
+
Requires-Dist: types-requests (>=2.31.0.10,<3.0.0.0)
|
|
45
|
+
Requires-Dist: virtualenv (>=20.2.2,<21.0.0) ; extra == "dev"
|
|
46
|
+
Description-Content-Type: text/markdown
|
|
47
|
+
|
|
48
|
+
# meta-eukaryome-detect
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
[](https://pypi.org/project/meta-eukaryome-detect/)
|
|
52
|
+
[](https://opensource.org/licenses/MIT)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
Pathogen, Parasite, Eukaryote and Virus detection in metagenomes.
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
* Documentation: <https://grp-bork.embl-community.io/grp-microbiome-astrology/meta_eukaryome_detect/>
|
|
59
|
+
* GitHub: <https://git.embl.de/grp-bork/meta_eukaryome_detect>
|
|
60
|
+
* PyPI: <https://pypi.org/project/meta-eukaryome-detect/>
|
|
61
|
+
* Free software: MIT
|
|
62
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# meta-eukaryome-detect
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
[](https://pypi.org/project/meta-eukaryome-detect/)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Pathogen, Parasite, Eukaryote and Virus detection in metagenomes.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
* Documentation: <https://grp-bork.embl-community.io/grp-microbiome-astrology/meta_eukaryome_detect/>
|
|
12
|
+
* GitHub: <https://git.embl.de/grp-bork/meta_eukaryome_detect>
|
|
13
|
+
* PyPI: <https://pypi.org/project/meta-eukaryome-detect/>
|
|
14
|
+
* Free software: MIT
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_min_identity_pc(reference_type):
|
|
6
|
+
match reference_type:
|
|
7
|
+
case 'plastid':
|
|
8
|
+
return '98'
|
|
9
|
+
case 'mito':
|
|
10
|
+
return '96'
|
|
11
|
+
case 'pr2':
|
|
12
|
+
return '97'
|
|
13
|
+
case 'virulence':
|
|
14
|
+
return '95'
|
|
15
|
+
case 'viruses':
|
|
16
|
+
return '95'
|
|
17
|
+
case _:
|
|
18
|
+
sys.exit(f'{reference_type} not a valid reference type!')
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_ngless_filtering_template(reference_type: str, temp_dir: Path, result_dir: Path):
|
|
22
|
+
if reference_type == 'preprocess':
|
|
23
|
+
return f"""
|
|
24
|
+
ngless "1.1"
|
|
25
|
+
import "mocat" version "0.0"
|
|
26
|
+
input = load_mocat_sample(ARGV[1])
|
|
27
|
+
|
|
28
|
+
input = preprocess(input, keep_singles=True) using |read|:
|
|
29
|
+
read = smoothtrim(read, min_quality=20, window=4)
|
|
30
|
+
if len(read) < 70:
|
|
31
|
+
discard
|
|
32
|
+
|
|
33
|
+
write(input, ofile='{temp_dir}/preprocessed/preprocessed.fq' )
|
|
34
|
+
"""
|
|
35
|
+
else:
|
|
36
|
+
min_identity_pc = get_min_identity_pc(reference_type)
|
|
37
|
+
return f"""
|
|
38
|
+
ngless "1.1"
|
|
39
|
+
import "parallel" version "1.0"
|
|
40
|
+
import "mocat" version "0.0"
|
|
41
|
+
import "samtools" version "0.0"
|
|
42
|
+
|
|
43
|
+
input = load_mocat_sample(ARGV[1])
|
|
44
|
+
|
|
45
|
+
mapped = map(input, fafile=ARGV[2])
|
|
46
|
+
write(samtools_sort(mapped), ofile='{temp_dir}/{reference_type}_unfiltered.bam' )
|
|
47
|
+
|
|
48
|
+
mapped = select(mapped) using |mr|:
|
|
49
|
+
mr = mr.filter(min_match_size=70, min_identity_pc={min_identity_pc}, action={{drop}})
|
|
50
|
+
write(samtools_sort(mapped), ofile='{result_dir}/{reference_type}_filtered.bam' )
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def write_templates(out_dir, temp_dir, result_dir):
|
|
55
|
+
templates = {}
|
|
56
|
+
for template in ['preprocess', 'plastid', 'mito', 'pr2', 'virulence', 'viruses']:
|
|
57
|
+
out_file = Path(out_dir) / f'{template}.ngless'
|
|
58
|
+
templates[template] = out_file
|
|
59
|
+
with open(out_file, 'w') as f:
|
|
60
|
+
f.write(get_ngless_filtering_template(template, temp_dir, result_dir))
|
|
61
|
+
return templates
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import gzip
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
output_col_order = {
|
|
8
|
+
'plastid': [
|
|
9
|
+
"sample_name",
|
|
10
|
+
"ref_id",
|
|
11
|
+
"ref_length",
|
|
12
|
+
"avgdepth_cov",
|
|
13
|
+
"breadth_cov",
|
|
14
|
+
"breadth_fraction",
|
|
15
|
+
"ani_cluster_id",
|
|
16
|
+
"full_taxonomy",
|
|
17
|
+
"bin_names_in_cluster",
|
|
18
|
+
"organism_groups_all",
|
|
19
|
+
'eukaryote_type1',
|
|
20
|
+
'eukaryote_type2',
|
|
21
|
+
'short_name',
|
|
22
|
+
'possible_contaminant_in_human_gut',
|
|
23
|
+
'other_notes',
|
|
24
|
+
'eukaryote_type3',
|
|
25
|
+
],
|
|
26
|
+
'mito': [
|
|
27
|
+
"sample_name",
|
|
28
|
+
"ref_id",
|
|
29
|
+
"ref_length",
|
|
30
|
+
"avgdepth_cov",
|
|
31
|
+
"breadth_cov",
|
|
32
|
+
"breadth_fraction",
|
|
33
|
+
"ani_cluster_id",
|
|
34
|
+
"full_taxonomy",
|
|
35
|
+
"bin_names_in_cluster",
|
|
36
|
+
"organism_groups_all",
|
|
37
|
+
'eukaryote_type1',
|
|
38
|
+
'eukaryote_type2',
|
|
39
|
+
'short_name',
|
|
40
|
+
'possible_contaminant_in_human_gut',
|
|
41
|
+
'other_notes',
|
|
42
|
+
'eukaryote_type3',
|
|
43
|
+
],
|
|
44
|
+
'pr2': [
|
|
45
|
+
"sample_name",
|
|
46
|
+
"ref_id",
|
|
47
|
+
"ref_length",
|
|
48
|
+
"avgdepth_cov",
|
|
49
|
+
"breadth_cov",
|
|
50
|
+
"breadth_fraction",
|
|
51
|
+
"reference_type",
|
|
52
|
+
"gene_cluster_id",
|
|
53
|
+
],
|
|
54
|
+
'virulence': [
|
|
55
|
+
"sample_name",
|
|
56
|
+
"ref_id",
|
|
57
|
+
"ref_length",
|
|
58
|
+
"avgdepth_cov",
|
|
59
|
+
"breadth_cov",
|
|
60
|
+
"breadth_fraction",
|
|
61
|
+
"gene_cluster_id",
|
|
62
|
+
"SpName_x",
|
|
63
|
+
],
|
|
64
|
+
'viruses': [
|
|
65
|
+
"sample_name",
|
|
66
|
+
"ref_id",
|
|
67
|
+
"ref_length",
|
|
68
|
+
"avgdepth_cov",
|
|
69
|
+
"breadth_cov",
|
|
70
|
+
"breadth_fraction",
|
|
71
|
+
"Species",
|
|
72
|
+
"Genus",
|
|
73
|
+
"Family",
|
|
74
|
+
"Molecule_type",
|
|
75
|
+
"Host",
|
|
76
|
+
"Isolation_Source",
|
|
77
|
+
"Collection_Date",
|
|
78
|
+
"BioSample",
|
|
79
|
+
"GenBank_Title",
|
|
80
|
+
],
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
breadth_coverages = {'plastid': 500, 'mito': 500, 'pr2': 100, 'virulence': 50, 'viruses': 100}
|
|
84
|
+
|
|
85
|
+
metadata_files = {
|
|
86
|
+
'plastid': 'mito_plastid_metadata',
|
|
87
|
+
'mito': 'mito_plastid_metadata',
|
|
88
|
+
'pr2': 'pr2_metadata',
|
|
89
|
+
'virulence': 'virulence_metadata',
|
|
90
|
+
'viruses': 'viruses_metadata',
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def get_depth_summary(out_dir: Path, reference: str, depth_file: Path, DB: Path, sample_name: str):
|
|
95
|
+
depths = pd.read_csv(depth_file, sep="\t", header=None, usecols=[0, 2], names=["ref_id", "depth_coverage"])
|
|
96
|
+
out_file = out_dir / f"{reference}.depth_summary.tsv.gz"
|
|
97
|
+
if len(depths) != 0:
|
|
98
|
+
output = depths.groupby("ref_id").mean().rename(columns={"depth_coverage": "avgdepth_cov"})
|
|
99
|
+
output["breadth_cov"] = depths.groupby("ref_id").count()
|
|
100
|
+
output = output[output["breadth_cov"] > breadth_coverages[reference]].sort_values(
|
|
101
|
+
by="breadth_cov", ascending=False
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
contigLen = pd.read_csv(
|
|
105
|
+
DB / metadata_files[reference],
|
|
106
|
+
sep="\t",
|
|
107
|
+
header=0,
|
|
108
|
+
)
|
|
109
|
+
output = pd.merge(output, contigLen, how="left", left_index=True, right_on=["ref_id"])
|
|
110
|
+
|
|
111
|
+
output["breadth_fraction"] = output["breadth_cov"] / output["ref_length"]
|
|
112
|
+
output["sample_name"] = sample_name
|
|
113
|
+
output[output_col_order[reference]].round(4).to_csv(out_file, sep="\t", index=False, compression="gzip")
|
|
114
|
+
|
|
115
|
+
else:
|
|
116
|
+
with gzip.open(out_file, "wb") as f:
|
|
117
|
+
f.write("\t".join(output_col_order[reference]).encode())
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import shutil
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def check_if_tool_exists(tool_name):
|
|
9
|
+
"""Check if tool is available."""
|
|
10
|
+
return shutil.which(tool_name) is not None
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def ngless(template: Path, cpu_count: str, temp_dir: Path, sample_folder: Path, verbose: bool, DB_path='') -> None:
|
|
14
|
+
"""Run ngless.
|
|
15
|
+
|
|
16
|
+
Arguments:
|
|
17
|
+
template (Path): full path of gene calls
|
|
18
|
+
cpu_count (str): number of threads to use
|
|
19
|
+
temp_dir (Path): path of directory to use for tmp files
|
|
20
|
+
sample_folder (Path): full path ti sample directory
|
|
21
|
+
verbose (bool): Verbose output
|
|
22
|
+
DB_path (str): Path to directory containing metaeuk db
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
if verbose:
|
|
26
|
+
ngless_verbosity = '--trace'
|
|
27
|
+
else:
|
|
28
|
+
ngless_verbosity = ''
|
|
29
|
+
try:
|
|
30
|
+
subprocess.check_output(
|
|
31
|
+
[
|
|
32
|
+
"ngless",
|
|
33
|
+
"--temporary-directory",
|
|
34
|
+
temp_dir,
|
|
35
|
+
template,
|
|
36
|
+
"--jobs",
|
|
37
|
+
cpu_count,
|
|
38
|
+
sample_folder,
|
|
39
|
+
DB_path,
|
|
40
|
+
ngless_verbosity,
|
|
41
|
+
],
|
|
42
|
+
universal_newlines=True,
|
|
43
|
+
)
|
|
44
|
+
except subprocess.CalledProcessError as e:
|
|
45
|
+
sys.exit(f"[ERROR] Failed to run ngless on {sample_folder} \n {e.output}")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def samtools_unique(filtered_bam: Path, unique_bam: Path, min_map_qual: str, verbose: bool) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Run samtools unique.
|
|
51
|
+
|
|
52
|
+
Arguments:
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
subprocess.check_output(
|
|
57
|
+
[
|
|
58
|
+
"samtools",
|
|
59
|
+
"view",
|
|
60
|
+
"-bq",
|
|
61
|
+
min_map_qual,
|
|
62
|
+
filtered_bam,
|
|
63
|
+
"-o",
|
|
64
|
+
unique_bam,
|
|
65
|
+
],
|
|
66
|
+
universal_newlines=True,
|
|
67
|
+
)
|
|
68
|
+
except subprocess.CalledProcessError as e:
|
|
69
|
+
sys.exit(f"[ERROR] Failed to run samtools view on {filtered_bam} \n {e.output}")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def samtools_depth(unique_bam: Path, unique_depth: Path, verbose: bool) -> None:
|
|
73
|
+
"""Run samtools.
|
|
74
|
+
|
|
75
|
+
Arguments:
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
try:
|
|
79
|
+
subprocess.check_output(
|
|
80
|
+
[
|
|
81
|
+
"samtools",
|
|
82
|
+
"depth",
|
|
83
|
+
unique_bam,
|
|
84
|
+
"-o",
|
|
85
|
+
unique_depth,
|
|
86
|
+
],
|
|
87
|
+
universal_newlines=True,
|
|
88
|
+
)
|
|
89
|
+
except subprocess.CalledProcessError as e:
|
|
90
|
+
sys.exit(f"[ERROR] Failed to run samtools view on {unique_bam} \n {e.output}")
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import gzip
|
|
3
|
+
import hashlib
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
import requests
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def md5sum_file(file):
|
|
13
|
+
"""Computes MD5 sum of file.
|
|
14
|
+
|
|
15
|
+
Arguments:
|
|
16
|
+
file (str): Path of file to md5sum
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
str: md5sum
|
|
20
|
+
"""
|
|
21
|
+
block_size = 8192
|
|
22
|
+
m = hashlib.md5()
|
|
23
|
+
with open(file, "rb") as f:
|
|
24
|
+
while True:
|
|
25
|
+
data = f.read(block_size)
|
|
26
|
+
if not data:
|
|
27
|
+
return m.hexdigest()
|
|
28
|
+
m.update(data)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def download_file(file_url, out_file):
|
|
32
|
+
"""Download a file to disk
|
|
33
|
+
|
|
34
|
+
Streams a file from URL to disk.
|
|
35
|
+
|
|
36
|
+
Arguments:
|
|
37
|
+
file_url (str): URL of file to download
|
|
38
|
+
out_file (str): Target file path
|
|
39
|
+
"""
|
|
40
|
+
with requests.get(file_url, stream=True) as r:
|
|
41
|
+
with open(out_file, "wb") as f:
|
|
42
|
+
shutil.copyfileobj(r.raw, f)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def decompress_gzip_file(gz_file, out_file):
|
|
46
|
+
"""Decompress a gzip file.
|
|
47
|
+
|
|
48
|
+
Uncompressed given gzip file to out_file.
|
|
49
|
+
|
|
50
|
+
Arguments:
|
|
51
|
+
gz_file (str): Path of gzip file
|
|
52
|
+
out_file (str): Path of target uncompressed out file
|
|
53
|
+
"""
|
|
54
|
+
with gzip.open(gz_file, "rb") as f_in:
|
|
55
|
+
with open(out_file, "wb") as f_out:
|
|
56
|
+
shutil.copyfileobj(f_in, f_out)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_md5_file(file_url):
|
|
60
|
+
"""Read md5sums from url
|
|
61
|
+
|
|
62
|
+
Arguments:
|
|
63
|
+
file_url (str): URL of file
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
dict: {file: md5sum}
|
|
67
|
+
"""
|
|
68
|
+
md5s = requests.get(f"{file_url}").text.split("\n")
|
|
69
|
+
return {x.split(' ')[1]: x.split(' ')[0] for x in md5s if x}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def check_md5(expected_md5, file_path):
|
|
73
|
+
"""Check md5 and remove file if incorect.
|
|
74
|
+
|
|
75
|
+
Arguments:
|
|
76
|
+
expected_md5 (str): Expected md5 of file
|
|
77
|
+
file_path (str): Path of file
|
|
78
|
+
"""
|
|
79
|
+
downloaded_md5 = md5sum_file(file_path)
|
|
80
|
+
if downloaded_md5 != expected_md5:
|
|
81
|
+
os.unlink(file_path)
|
|
82
|
+
return False
|
|
83
|
+
else:
|
|
84
|
+
return True
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_db(base_dir):
|
|
88
|
+
"""Download GUNC DB.
|
|
89
|
+
|
|
90
|
+
Arguments:
|
|
91
|
+
base_dir (str): Path of output directory
|
|
92
|
+
"""
|
|
93
|
+
logging.basicConfig(
|
|
94
|
+
format="%(asctime)s : %(message)s",
|
|
95
|
+
datefmt="%H:%M:%S",
|
|
96
|
+
level=logging.INFO,
|
|
97
|
+
)
|
|
98
|
+
logger = logging.getLogger(__name__)
|
|
99
|
+
if not os.path.isdir(base_dir):
|
|
100
|
+
sys.exit(f"[ERROR] Output Directory {base_dir} doesnt exist.")
|
|
101
|
+
base_url = "https://swifter.embl.de/~fullam/meta_eukaryome_database/"
|
|
102
|
+
files_to_download = get_md5_file(f"{base_url}MD5SUM_gzipped")
|
|
103
|
+
uncompressed_md5s = get_md5_file(f"{base_url}MD5SUM")
|
|
104
|
+
count_files_to_download = len(files_to_download)
|
|
105
|
+
logger.info("[INFO] DB downloading...")
|
|
106
|
+
for i, file_name in enumerate(files_to_download, 1):
|
|
107
|
+
logger.info(f"[INFO] Downloading {i}/{count_files_to_download} {file_name}")
|
|
108
|
+
gz_file_url = f"{base_url}{file_name}"
|
|
109
|
+
gz_file_path = os.path.join(base_dir, file_name)
|
|
110
|
+
out_file = gz_file_path.replace(".gz", "")
|
|
111
|
+
out_file_name = file_name.replace(".gz", "")
|
|
112
|
+
|
|
113
|
+
retry = 3
|
|
114
|
+
while retry:
|
|
115
|
+
download_file(gz_file_url, gz_file_path)
|
|
116
|
+
if not check_md5(files_to_download[file_name], gz_file_path):
|
|
117
|
+
logger.error(f"[ERROR] MD5 check failed, removing {gz_file_path}")
|
|
118
|
+
retry -= 1
|
|
119
|
+
continue
|
|
120
|
+
decompress_gzip_file(gz_file_path, out_file)
|
|
121
|
+
if not check_md5(uncompressed_md5s[out_file_name], out_file):
|
|
122
|
+
logger.error(f"[ERROR] MD5 check failed, removing {out_file}")
|
|
123
|
+
retry -= 1
|
|
124
|
+
continue
|
|
125
|
+
else:
|
|
126
|
+
break
|
|
127
|
+
else:
|
|
128
|
+
if retry == 0:
|
|
129
|
+
sys.exit('Downloading database files failed too many times')
|
|
130
|
+
os.unlink(gz_file_path)
|
|
131
|
+
|
|
132
|
+
logger.info("[INFO] DB download successful.")
|
|
133
|
+
logger.info(f"[INFO] DB path: {os.path.abspath(base_dir)}")
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import argparse
|
|
3
|
+
import logging
|
|
4
|
+
import multiprocessing
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import sys
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from . import create_ngless_template, depth_summary, external_tools, meta_eukaryome_database
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger("meta_eukaryome_detect.py")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def parse_args(args):
|
|
17
|
+
"""Parse Arguments
|
|
18
|
+
|
|
19
|
+
Arguments:
|
|
20
|
+
args: List of args supplied to script.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Namespace: assigned args
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
description = "Pathogen, Parasite, Eukaryote and Virus detection in metagenomes.\n"
|
|
27
|
+
formatter = lambda prog: argparse.HelpFormatter(prog, max_help_position=155)
|
|
28
|
+
parser = argparse.ArgumentParser(description=description, formatter_class=formatter)
|
|
29
|
+
subparsers = parser.add_subparsers(title="meta_eukaryome_detect subcommands", metavar="", dest="cmd")
|
|
30
|
+
run = subparsers.add_parser("run", help="Run Meta-Eukaryome-Detection.", formatter_class=formatter)
|
|
31
|
+
run_group = run.add_mutually_exclusive_group(required=True)
|
|
32
|
+
advanced_run = run.add_argument_group('Advanced Options')
|
|
33
|
+
download_db = subparsers.add_parser(
|
|
34
|
+
"download_db", help="Download meta_eukaryome_detect db.", formatter_class=formatter
|
|
35
|
+
)
|
|
36
|
+
run.add_argument(
|
|
37
|
+
"-d",
|
|
38
|
+
"--db_dir",
|
|
39
|
+
help="meta_eukaryome_database directory. Default: META_EUKARYOME_DETECT_DB_DIR envvar",
|
|
40
|
+
default=os.environ.get("META_EUKARYOME_DETECT_DB_DIR"),
|
|
41
|
+
metavar="",
|
|
42
|
+
)
|
|
43
|
+
run_group.add_argument(
|
|
44
|
+
"-i",
|
|
45
|
+
"--input_sample_dir",
|
|
46
|
+
help="""Input directory contining fasta files for a sample
|
|
47
|
+
(suffixes of the form _1.fq.gz .2.fq.gz single.fg.gz allowed)""",
|
|
48
|
+
metavar="",
|
|
49
|
+
)
|
|
50
|
+
run.add_argument(
|
|
51
|
+
"-s",
|
|
52
|
+
"--sample_name",
|
|
53
|
+
help="Name to give to output files, Default: Name of input dir",
|
|
54
|
+
default=None,
|
|
55
|
+
metavar="",
|
|
56
|
+
)
|
|
57
|
+
advanced_run.add_argument(
|
|
58
|
+
"--min_map_qual_plastid",
|
|
59
|
+
help="Minimum Mapping quality to use for plastid reference.",
|
|
60
|
+
default='1',
|
|
61
|
+
metavar="",
|
|
62
|
+
)
|
|
63
|
+
advanced_run.add_argument(
|
|
64
|
+
"--min_map_qual_mito",
|
|
65
|
+
help="Minimum Mapping quality to use for mitochondria reference.",
|
|
66
|
+
default='1',
|
|
67
|
+
metavar="",
|
|
68
|
+
)
|
|
69
|
+
advanced_run.add_argument(
|
|
70
|
+
"--min_map_qual_pr2",
|
|
71
|
+
help="Minimum Mapping quality to use for pr2 reference.",
|
|
72
|
+
default='20',
|
|
73
|
+
metavar="",
|
|
74
|
+
)
|
|
75
|
+
advanced_run.add_argument(
|
|
76
|
+
"--min_map_qual_virulence",
|
|
77
|
+
help="Minimum Mapping quality to use for virulence reference.",
|
|
78
|
+
default='20',
|
|
79
|
+
metavar="",
|
|
80
|
+
)
|
|
81
|
+
advanced_run.add_argument(
|
|
82
|
+
"--min_map_qual_virus",
|
|
83
|
+
help="Minimum Mapping quality to use for virus reference.",
|
|
84
|
+
default='0',
|
|
85
|
+
metavar="",
|
|
86
|
+
)
|
|
87
|
+
run.add_argument(
|
|
88
|
+
"-t",
|
|
89
|
+
"--threads",
|
|
90
|
+
help="number of CPU threads. Default: 4",
|
|
91
|
+
default="4",
|
|
92
|
+
metavar="",
|
|
93
|
+
)
|
|
94
|
+
run.add_argument(
|
|
95
|
+
"-p",
|
|
96
|
+
"--parallel",
|
|
97
|
+
help="Run all mappings in parallel (threads will be split between each of the 5 references)",
|
|
98
|
+
action="store_true",
|
|
99
|
+
default=False,
|
|
100
|
+
)
|
|
101
|
+
run.add_argument(
|
|
102
|
+
"-o",
|
|
103
|
+
"--out_dir",
|
|
104
|
+
help="Output dir. Default: cwd",
|
|
105
|
+
default=os.getcwd(),
|
|
106
|
+
metavar="",
|
|
107
|
+
)
|
|
108
|
+
run.add_argument(
|
|
109
|
+
"--temp_dir",
|
|
110
|
+
help="Directory to store temp files. Default: cwd",
|
|
111
|
+
default=None,
|
|
112
|
+
metavar="",
|
|
113
|
+
)
|
|
114
|
+
run.add_argument(
|
|
115
|
+
"--keep_temp_files",
|
|
116
|
+
help="Do not remove temproary files",
|
|
117
|
+
action="store_true",
|
|
118
|
+
default=False,
|
|
119
|
+
)
|
|
120
|
+
run.add_argument(
|
|
121
|
+
"-v",
|
|
122
|
+
"--verbose",
|
|
123
|
+
help="Verbose output for debugging",
|
|
124
|
+
action="store_true",
|
|
125
|
+
default=False,
|
|
126
|
+
)
|
|
127
|
+
download_db.add_argument("path", help="Download database to given directory.", metavar="dest_path")
|
|
128
|
+
if not args:
|
|
129
|
+
parser.print_help(sys.stderr)
|
|
130
|
+
sys.exit(1)
|
|
131
|
+
download_db.add_argument(
|
|
132
|
+
"-v",
|
|
133
|
+
"--verbose",
|
|
134
|
+
help="Verbose output for debugging",
|
|
135
|
+
action="store_true",
|
|
136
|
+
default=False,
|
|
137
|
+
)
|
|
138
|
+
args = parser.parse_args(args)
|
|
139
|
+
return args
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def create_dir(path):
|
|
143
|
+
"""Create a directory
|
|
144
|
+
|
|
145
|
+
Will create a directory if it doesnt already exist.
|
|
146
|
+
|
|
147
|
+
Arguments:
|
|
148
|
+
path (str): directory path
|
|
149
|
+
"""
|
|
150
|
+
if not os.path.exists(path):
|
|
151
|
+
logger.debug(f'Creating dir: {path}')
|
|
152
|
+
os.makedirs(path)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def start_checks(args):
|
|
156
|
+
"""Checks if tool dependencies are available."""
|
|
157
|
+
if not external_tools.check_if_tool_exists("ngless"):
|
|
158
|
+
logger.error("Ngless not found.")
|
|
159
|
+
sys.exit(1)
|
|
160
|
+
if not external_tools.check_if_tool_exists("samtools"):
|
|
161
|
+
logger.error("Samtools not found.")
|
|
162
|
+
sys.exit(1)
|
|
163
|
+
if not os.path.isdir(args.out_dir):
|
|
164
|
+
logger.error(f"Output Directory {args.out_dir} doesnt exist.")
|
|
165
|
+
sys.exit(1)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def run(args):
|
|
169
|
+
input_dir = Path(args.input_sample_dir)
|
|
170
|
+
if not args.sample_name:
|
|
171
|
+
sample_name = input_dir.name
|
|
172
|
+
else:
|
|
173
|
+
sample_name = args.sample_name
|
|
174
|
+
|
|
175
|
+
if args.temp_dir:
|
|
176
|
+
ngless_temp_dir = Path(args.temp_dir) / f'{sample_name}_ngless_temp_dir'
|
|
177
|
+
else:
|
|
178
|
+
ngless_temp_dir = Path(args.out_dir) / f'{sample_name}_ngless_temp_dir'
|
|
179
|
+
|
|
180
|
+
create_dir(ngless_temp_dir)
|
|
181
|
+
ngless_template_dir = ngless_temp_dir / 'ngless_templates'
|
|
182
|
+
create_dir(ngless_template_dir)
|
|
183
|
+
result_dir = Path(args.out_dir) / f'{sample_name}_metaeuk_output'
|
|
184
|
+
create_dir(result_dir)
|
|
185
|
+
templates = create_ngless_template.write_templates(ngless_template_dir, ngless_temp_dir, result_dir)
|
|
186
|
+
logger.info('Running Preprocessing of input files.')
|
|
187
|
+
logger.debug(f'Running template: {templates["preprocess"]}')
|
|
188
|
+
preprocessed_temp_dir = ngless_temp_dir / 'preprocessed'
|
|
189
|
+
create_dir(preprocessed_temp_dir)
|
|
190
|
+
external_tools.ngless(templates['preprocess'], args.threads, preprocessed_temp_dir, input_dir, args.verbose)
|
|
191
|
+
read_count_file = ngless_template_dir / 'preprocess.ngless.output_ngless' / 'fq.tsv'
|
|
192
|
+
shutil.copyfile(read_count_file, result_dir / 'readcounts.tsv')
|
|
193
|
+
mapping_qualities = {
|
|
194
|
+
'plastid': args.min_map_qual_plastid,
|
|
195
|
+
'mito': args.min_map_qual_mito,
|
|
196
|
+
'pr2': args.min_map_qual_pr2,
|
|
197
|
+
'virulence': args.min_map_qual_virulence,
|
|
198
|
+
'viruses': args.min_map_qual_virus,
|
|
199
|
+
}
|
|
200
|
+
meta = []
|
|
201
|
+
threads = int(args.threads)
|
|
202
|
+
if threads > 7 and args.parallel:
|
|
203
|
+
if threads < 21:
|
|
204
|
+
jobs = int(threads / 4)
|
|
205
|
+
threads = int(threads / jobs)
|
|
206
|
+
else:
|
|
207
|
+
jobs = 5
|
|
208
|
+
threads = int(threads / jobs)
|
|
209
|
+
else:
|
|
210
|
+
jobs = 1
|
|
211
|
+
for template in ['plastid', 'mito', 'pr2', 'virulence', 'viruses']:
|
|
212
|
+
meta.append(
|
|
213
|
+
(
|
|
214
|
+
template,
|
|
215
|
+
templates[template],
|
|
216
|
+
mapping_qualities[template],
|
|
217
|
+
args,
|
|
218
|
+
str(threads),
|
|
219
|
+
result_dir,
|
|
220
|
+
ngless_temp_dir,
|
|
221
|
+
sample_name,
|
|
222
|
+
)
|
|
223
|
+
)
|
|
224
|
+
p = multiprocessing.Pool(jobs)
|
|
225
|
+
p.map(run_detection, meta)
|
|
226
|
+
if not args.keep_temp_files:
|
|
227
|
+
shutil.rmtree(ngless_temp_dir)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def run_detection(meta):
|
|
231
|
+
template, template_path, mapping_quality, args, threads, result_dir, ngless_temp_dir, sample_name = meta
|
|
232
|
+
logger.debug(f'Running reference: {template} with args: {meta}')
|
|
233
|
+
DB_path = Path(args.db_dir) / f'{template}_reference'
|
|
234
|
+
logger.debug(f'DB Path: {DB_path}')
|
|
235
|
+
external_tools.ngless(
|
|
236
|
+
template_path,
|
|
237
|
+
threads,
|
|
238
|
+
ngless_temp_dir,
|
|
239
|
+
ngless_temp_dir / 'preprocessed',
|
|
240
|
+
args.verbose,
|
|
241
|
+
DB_path,
|
|
242
|
+
)
|
|
243
|
+
unfiltered_bam = ngless_temp_dir / f"{template}_unfiltered.bam"
|
|
244
|
+
filtered_bam = result_dir / f"{template}_filtered.bam"
|
|
245
|
+
unique_bam = ngless_temp_dir / f"{template}_unique.bam"
|
|
246
|
+
unique_depth = ngless_temp_dir / f"{template}_unique.depth"
|
|
247
|
+
external_tools.samtools_unique(filtered_bam, unique_bam, mapping_quality, args.verbose)
|
|
248
|
+
external_tools.samtools_depth(unique_bam, unique_depth, args.verbose)
|
|
249
|
+
depth_summary.get_depth_summary(result_dir, template, unique_depth, Path(args.db_dir), sample_name)
|
|
250
|
+
if not args.keep_temp_files:
|
|
251
|
+
unfiltered_bam.unlink()
|
|
252
|
+
# filtered_bam.unlink()
|
|
253
|
+
unique_depth.unlink()
|
|
254
|
+
logger.info(f'Finished running reference: {template}')
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def main():
|
|
258
|
+
args = parse_args(sys.argv[1:])
|
|
259
|
+
if args.verbose:
|
|
260
|
+
logging.basicConfig(
|
|
261
|
+
format="%(asctime)s : [%(levelname)7s] : %(name)s:%(lineno)s %(funcName)20s() : %(message)s",
|
|
262
|
+
datefmt="%H:%M:%S",
|
|
263
|
+
level=logging.DEBUG,
|
|
264
|
+
)
|
|
265
|
+
else:
|
|
266
|
+
logging.basicConfig(
|
|
267
|
+
format="%(asctime)s : %(message)s",
|
|
268
|
+
datefmt="%H:%M:%S",
|
|
269
|
+
level=logging.INFO,
|
|
270
|
+
)
|
|
271
|
+
logger = logging.getLogger("meta_eukaryome_detect.py")
|
|
272
|
+
logger.debug(args)
|
|
273
|
+
start_time = datetime.now()
|
|
274
|
+
logger.info(f'START {start_time.strftime("%Y-%m-%d")}')
|
|
275
|
+
if args.cmd == "download_db":
|
|
276
|
+
meta_eukaryome_database.get_db(args.path)
|
|
277
|
+
if args.cmd == "run":
|
|
278
|
+
start_checks(args)
|
|
279
|
+
if not args.db_dir:
|
|
280
|
+
logger.error("Database_dir argument missing.")
|
|
281
|
+
sys.exit(1)
|
|
282
|
+
else:
|
|
283
|
+
if not os.path.isdir(args.db_dir):
|
|
284
|
+
logger.error("Database_dir not found.")
|
|
285
|
+
sys.exit(1)
|
|
286
|
+
run(args)
|
|
287
|
+
end_time = datetime.now()
|
|
288
|
+
logger.info(f'END {end_time.strftime("%Y-%m-%d")}')
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
if __name__ == "__main__":
|
|
292
|
+
main()
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
[tool]
|
|
2
|
+
[tool.poetry]
|
|
3
|
+
name = "meta_eukaryome_detect"
|
|
4
|
+
version = "0.1.0"
|
|
5
|
+
homepage = "https://git.embl.de/grp-bork/meta-eukaryome-detect"
|
|
6
|
+
description = "Pathogen, Parasite, Eukaryote and Virus detection in metagenomes.."
|
|
7
|
+
authors = ["Anthony Fullam <anthony.fullam@embl.de>"]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
license = "MIT"
|
|
10
|
+
classifiers=[
|
|
11
|
+
'Development Status :: 2 - Pre-Alpha',
|
|
12
|
+
'Intended Audience :: Developers',
|
|
13
|
+
'License :: OSI Approved :: MIT License',
|
|
14
|
+
'Natural Language :: English',
|
|
15
|
+
'Programming Language :: Python :: 3',
|
|
16
|
+
'Programming Language :: Python :: 3.11',
|
|
17
|
+
]
|
|
18
|
+
packages = [
|
|
19
|
+
{ include = "meta_eukaryome_detect" },
|
|
20
|
+
{ include = "tests", format = "sdist" },
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[tool.poetry.dependencies]
|
|
24
|
+
python = ">=3.11,<4.0"
|
|
25
|
+
black = "^23.10.1"
|
|
26
|
+
isort = { version = "^5.8.0", optional = true}
|
|
27
|
+
flake8 = { version = "^3.9.2", optional = true}
|
|
28
|
+
flake8-docstrings = { version = "^1.6.0", optional = true }
|
|
29
|
+
mypy = {version = "^0.900", optional = true}
|
|
30
|
+
pytest = { version = "^6.2.4", optional = true}
|
|
31
|
+
pytest-cov = { version = "^2.12.0", optional = true}
|
|
32
|
+
tox = { version = "^3.20.1", optional = true}
|
|
33
|
+
virtualenv = { version = "^20.2.2", optional = true}
|
|
34
|
+
pip = { version = "^20.3.1", optional = true}
|
|
35
|
+
mkdocs = "^1.5.3"
|
|
36
|
+
mkdocs-include-markdown-plugin = "^6.0.3"
|
|
37
|
+
mkdocs-material = { version = "^6.1.7", optional = true}
|
|
38
|
+
mkdocstrings = "^0.23.0"
|
|
39
|
+
mkdocs-material-extensions = { version = "^1.0.1", optional = true}
|
|
40
|
+
twine = { version = "^3.3.0", optional = true}
|
|
41
|
+
mkdocs-autorefs = "^0.5.0"
|
|
42
|
+
pre-commit = {version = "^2.12.0", optional = true}
|
|
43
|
+
toml = {version = "^0.10.2", optional = true}
|
|
44
|
+
bump2version = {version = "^1.0.1", optional = true}
|
|
45
|
+
flask = "^3.0.0"
|
|
46
|
+
jinja2 = "^3.1.2"
|
|
47
|
+
mkdocstrings-python = "^1.7.3"
|
|
48
|
+
requests = "^2.31.0"
|
|
49
|
+
types-requests = "^2.31.0.10"
|
|
50
|
+
pandas = "^2.2.3"
|
|
51
|
+
|
|
52
|
+
[tool.poetry.extras]
|
|
53
|
+
test = [
|
|
54
|
+
"pytest",
|
|
55
|
+
"black",
|
|
56
|
+
"isort",
|
|
57
|
+
"mypy",
|
|
58
|
+
"flake8",
|
|
59
|
+
"flake8-docstrings",
|
|
60
|
+
"pytest-cov"
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
dev = ["tox", "pre-commit", "virtualenv", "pip", "twine", "toml", "bump2version"]
|
|
64
|
+
|
|
65
|
+
doc = [
|
|
66
|
+
"mkdocs",
|
|
67
|
+
"mkdocs-include-markdown-plugin",
|
|
68
|
+
"mkdocs-material",
|
|
69
|
+
"mkdocstrings",
|
|
70
|
+
"mkdocs-material-extension",
|
|
71
|
+
"mkdocs-autorefs"
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
[tool.poetry.scripts]
|
|
75
|
+
meta-eukaryome-detect = 'meta_eukaryome_detect.meta_eukaryome_detect:main'
|
|
76
|
+
|
|
77
|
+
[tool.black]
|
|
78
|
+
line-length = 120
|
|
79
|
+
skip-string-normalization = true
|
|
80
|
+
target-version = ['py311']
|
|
81
|
+
include = '\.pyi?$'
|
|
82
|
+
exclude = '''
|
|
83
|
+
/(
|
|
84
|
+
\.eggs
|
|
85
|
+
| \.git
|
|
86
|
+
| \.hg
|
|
87
|
+
| \.mypy_cache
|
|
88
|
+
| \.tox
|
|
89
|
+
| \.venv
|
|
90
|
+
| _build
|
|
91
|
+
| buck-out
|
|
92
|
+
| build
|
|
93
|
+
| dist
|
|
94
|
+
)/
|
|
95
|
+
'''
|
|
96
|
+
|
|
97
|
+
[tool.isort]
|
|
98
|
+
multi_line_output = 3
|
|
99
|
+
include_trailing_comma = true
|
|
100
|
+
force_grid_wrap = 0
|
|
101
|
+
use_parentheses = true
|
|
102
|
+
ensure_newline_before_comments = true
|
|
103
|
+
line_length = 120
|
|
104
|
+
skip_gitignore = true
|
|
105
|
+
# you can skip files as below
|
|
106
|
+
#skip_glob = docs/conf.py
|
|
107
|
+
|
|
108
|
+
[build-system]
|
|
109
|
+
requires = ["poetry-core>=1.0.0"]
|
|
110
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Unit test package for meta-eukaryome-detect."""
|