kleborate 3.0.0__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.
- kleborate/__init__.py +0 -0
- kleborate/__main__.py +497 -0
- kleborate/modules/__init__.py +0 -0
- kleborate/modules/enterobacterales__species/__init__.py +0 -0
- kleborate/modules/enterobacterales__species/data/species_mash_sketches.msh +0 -0
- kleborate/modules/enterobacterales__species/enterobacterales__species.py +110 -0
- kleborate/modules/enterobacterales__species/test_enterobacterales__species.py +223 -0
- kleborate/modules/escherichia__mlst_achtman/__init__.py +0 -0
- kleborate/modules/escherichia__mlst_achtman/data/adk.fasta +3562 -0
- kleborate/modules/escherichia__mlst_achtman/data/download.sh +12 -0
- kleborate/modules/escherichia__mlst_achtman/data/fumC.fasta +4800 -0
- kleborate/modules/escherichia__mlst_achtman/data/gyrB.fasta +3052 -0
- kleborate/modules/escherichia__mlst_achtman/data/icd.fasta +3820 -0
- kleborate/modules/escherichia__mlst_achtman/data/mdh.fasta +3002 -0
- kleborate/modules/escherichia__mlst_achtman/data/profiles.tsv +14984 -0
- kleborate/modules/escherichia__mlst_achtman/data/purA.fasta +2758 -0
- kleborate/modules/escherichia__mlst_achtman/data/recA.fasta +2342 -0
- kleborate/modules/escherichia__mlst_achtman/escherichia__mlst_achtman.py +85 -0
- kleborate/modules/escherichia__mlst_achtman/test_escherichia__mlst_achtman.py +161 -0
- kleborate/modules/escherichia__mlst_pasteur/__init__.py +0 -0
- kleborate/modules/escherichia__mlst_pasteur/data/dinB.fasta +460 -0
- kleborate/modules/escherichia__mlst_pasteur/data/download.sh +13 -0
- kleborate/modules/escherichia__mlst_pasteur/data/icdA.fasta +870 -0
- kleborate/modules/escherichia__mlst_pasteur/data/pabB.fasta +498 -0
- kleborate/modules/escherichia__mlst_pasteur/data/polB.fasta +532 -0
- kleborate/modules/escherichia__mlst_pasteur/data/profiles.tsv +1479 -0
- kleborate/modules/escherichia__mlst_pasteur/data/putP.fasta +554 -0
- kleborate/modules/escherichia__mlst_pasteur/data/trpA.fasta +574 -0
- kleborate/modules/escherichia__mlst_pasteur/data/trpB.fasta +520 -0
- kleborate/modules/escherichia__mlst_pasteur/data/uidA.fasta +596 -0
- kleborate/modules/escherichia__mlst_pasteur/escherichia__mlst_pasteur.py +85 -0
- kleborate/modules/escherichia__mlst_pasteur/test_escherichia__mlst_pasteur.py +161 -0
- kleborate/modules/general__contig_stats/__init__.py +0 -0
- kleborate/modules/general__contig_stats/data/species_specification.txt +9 -0
- kleborate/modules/general__contig_stats/general__contig_stats.py +130 -0
- kleborate/modules/general__contig_stats/test_general__contig_stats.py +191 -0
- kleborate/modules/klebsiella__abst/__init__.py +0 -0
- kleborate/modules/klebsiella__abst/data/download.sh +10 -0
- kleborate/modules/klebsiella__abst/data/iucA.fasta +1650 -0
- kleborate/modules/klebsiella__abst/data/iucB.fasta +629 -0
- kleborate/modules/klebsiella__abst/data/iucC.fasta +1413 -0
- kleborate/modules/klebsiella__abst/data/iucD.fasta +897 -0
- kleborate/modules/klebsiella__abst/data/iutA.fasta +1824 -0
- kleborate/modules/klebsiella__abst/data/profiles.tsv +100 -0
- kleborate/modules/klebsiella__abst/klebsiella__abst.py +106 -0
- kleborate/modules/klebsiella__abst/test_klebsiella__abst.py +149 -0
- kleborate/modules/klebsiella__cbst/__init__.py +0 -0
- kleborate/modules/klebsiella__cbst/data/clbA.fasta +140 -0
- kleborate/modules/klebsiella__cbst/data/clbB.fasta +11502 -0
- kleborate/modules/klebsiella__cbst/data/clbC.fasta +945 -0
- kleborate/modules/klebsiella__cbst/data/clbD.fasta +176 -0
- kleborate/modules/klebsiella__cbst/data/clbE.fasta +54 -0
- kleborate/modules/klebsiella__cbst/data/clbF.fasta +300 -0
- kleborate/modules/klebsiella__cbst/data/clbG.fasta +230 -0
- kleborate/modules/klebsiella__cbst/data/clbH.fasta +2349 -0
- kleborate/modules/klebsiella__cbst/data/clbI.fasta +1248 -0
- kleborate/modules/klebsiella__cbst/data/clbL.fasta +260 -0
- kleborate/modules/klebsiella__cbst/data/clbM.fasta +250 -0
- kleborate/modules/klebsiella__cbst/data/clbN.fasta +2072 -0
- kleborate/modules/klebsiella__cbst/data/clbO.fasta +798 -0
- kleborate/modules/klebsiella__cbst/data/clbP.fasta +378 -0
- kleborate/modules/klebsiella__cbst/data/clbQ.fasta +84 -0
- kleborate/modules/klebsiella__cbst/data/download.sh +20 -0
- kleborate/modules/klebsiella__cbst/data/profiles.tsv +83 -0
- kleborate/modules/klebsiella__cbst/klebsiella__cbst.py +110 -0
- kleborate/modules/klebsiella__cbst/test_klebsiella__cbst.py +144 -0
- kleborate/modules/klebsiella__rmpa2/data/rmpA2.fasta +122 -0
- kleborate/modules/klebsiella__rmpa2/klebsiella__rmpa2.py +84 -0
- kleborate/modules/klebsiella__rmpa2/rmpa2.py +40 -0
- kleborate/modules/klebsiella__rmst/__init__.py +0 -0
- kleborate/modules/klebsiella__rmst/data/download.sh +8 -0
- kleborate/modules/klebsiella__rmst/data/profiles.tsv +172 -0
- kleborate/modules/klebsiella__rmst/data/rmpA.fasta +1116 -0
- kleborate/modules/klebsiella__rmst/data/rmpC.fasta +360 -0
- kleborate/modules/klebsiella__rmst/data/rmpD.fasta +308 -0
- kleborate/modules/klebsiella__rmst/klebsiella__rmst.py +102 -0
- kleborate/modules/klebsiella__rmst/test_klebsiella__rmst.py +195 -0
- kleborate/modules/klebsiella__smst/__init__.py +0 -0
- kleborate/modules/klebsiella__smst/data/download.sh +9 -0
- kleborate/modules/klebsiella__smst/data/iroB.fasta +659 -0
- kleborate/modules/klebsiella__smst/data/iroC.fasta +3889 -0
- kleborate/modules/klebsiella__smst/data/iroD.fasta +594 -0
- kleborate/modules/klebsiella__smst/data/iroN.fasta +1368 -0
- kleborate/modules/klebsiella__smst/data/profiles.tsv +50 -0
- kleborate/modules/klebsiella__smst/klebsiella__smst.py +103 -0
- kleborate/modules/klebsiella__smst/test_klebsiella__smst.py +146 -0
- kleborate/modules/klebsiella__ybst/__init__.py +0 -0
- kleborate/modules/klebsiella__ybst/data/download.sh +17 -0
- kleborate/modules/klebsiella__ybst/data/fyuA.fasta +11060 -0
- kleborate/modules/klebsiella__ybst/data/irp1.fasta +101736 -0
- kleborate/modules/klebsiella__ybst/data/irp2.fasta +54765 -0
- kleborate/modules/klebsiella__ybst/data/profiles.tsv +600 -0
- kleborate/modules/klebsiella__ybst/data/ybtA.fasta +1852 -0
- kleborate/modules/klebsiella__ybst/data/ybtE.fasta +5068 -0
- kleborate/modules/klebsiella__ybst/data/ybtP.fasta +5460 -0
- kleborate/modules/klebsiella__ybst/data/ybtQ.fasta +6942 -0
- kleborate/modules/klebsiella__ybst/data/ybtS.fasta +3427 -0
- kleborate/modules/klebsiella__ybst/data/ybtT.fasta +1796 -0
- kleborate/modules/klebsiella__ybst/data/ybtU.fasta +2840 -0
- kleborate/modules/klebsiella__ybst/data/ybtX.fasta +3633 -0
- kleborate/modules/klebsiella__ybst/klebsiella__ybst.py +106 -0
- kleborate/modules/klebsiella__ybst/test_klebsiella__ybst.py +162 -0
- kleborate/modules/klebsiella_oxytoca_complex__mlst/__init__.py +0 -0
- kleborate/modules/klebsiella_oxytoca_complex__mlst/data/download.sh +12 -0
- kleborate/modules/klebsiella_oxytoca_complex__mlst/data/gapA.fasta +104 -0
- kleborate/modules/klebsiella_oxytoca_complex__mlst/data/infB.fasta +124 -0
- kleborate/modules/klebsiella_oxytoca_complex__mlst/data/mdh.fasta +172 -0
- kleborate/modules/klebsiella_oxytoca_complex__mlst/data/pgi.fasta +174 -0
- kleborate/modules/klebsiella_oxytoca_complex__mlst/data/phoE.fasta +258 -0
- kleborate/modules/klebsiella_oxytoca_complex__mlst/data/profiles.tsv +635 -0
- kleborate/modules/klebsiella_oxytoca_complex__mlst/data/rpoB.fasta +118 -0
- kleborate/modules/klebsiella_oxytoca_complex__mlst/data/tonB.fasta +240 -0
- kleborate/modules/klebsiella_oxytoca_complex__mlst/klebsiella_oxytoca_complex__mlst.py +82 -0
- kleborate/modules/klebsiella_oxytoca_complex__mlst/test_klebsiella_oxytoca_complex__mlst.py +143 -0
- kleborate/modules/klebsiella_pneumo_complex__amr/__init__.py +0 -0
- kleborate/modules/klebsiella_pneumo_complex__amr/col_mutations.py +85 -0
- kleborate/modules/klebsiella_pneumo_complex__amr/data/CARD_AMR_clustered.csv +3136 -0
- kleborate/modules/klebsiella_pneumo_complex__amr/data/CARD_v3.2.9.fasta +12940 -0
- kleborate/modules/klebsiella_pneumo_complex__amr/data/MgrB_and_PmrB.fasta +4 -0
- kleborate/modules/klebsiella_pneumo_complex__amr/data/OmpK.fasta +4 -0
- kleborate/modules/klebsiella_pneumo_complex__amr/data/QRDR_120.fasta +4 -0
- kleborate/modules/klebsiella_pneumo_complex__amr/klebsiella_pneumo_complex__amr.py +105 -0
- kleborate/modules/klebsiella_pneumo_complex__amr/omp_mutations.py +78 -0
- kleborate/modules/klebsiella_pneumo_complex__amr/qrdr_mutations.py +67 -0
- kleborate/modules/klebsiella_pneumo_complex__amr/shv_mutations.py +190 -0
- kleborate/modules/klebsiella_pneumo_complex__amr/test/test_mgrb_pmrb_klebsiella_pneumo_complex__amr.py +72 -0
- kleborate/modules/klebsiella_pneumo_complex__amr/test/test_omp_klebsiella_pneumo_complex__amr.py +78 -0
- kleborate/modules/klebsiella_pneumo_complex__amr/test/test_qrdr_klebsiella_pneumo_complex__amr.py +64 -0
- kleborate/modules/klebsiella_pneumo_complex__amr/test/test_res_aac_klebsiella_pneumo_complex__amr.py +75 -0
- kleborate/modules/klebsiella_pneumo_complex__amr/test/test_shv_klebsiella_pneumo_complex__amr.py +176 -0
- kleborate/modules/klebsiella_pneumo_complex__amr/test/test_tem_klebsiella_pneumo_complex__amr.py +46 -0
- kleborate/modules/klebsiella_pneumo_complex__kaptive/klebsiella_pneumo_complex__kaptive.py +175 -0
- kleborate/modules/klebsiella_pneumo_complex__mlst/__init__.py +0 -0
- kleborate/modules/klebsiella_pneumo_complex__mlst/data/download.sh +12 -0
- kleborate/modules/klebsiella_pneumo_complex__mlst/data/gapA.fasta +710 -0
- kleborate/modules/klebsiella_pneumo_complex__mlst/data/infB.fasta +544 -0
- kleborate/modules/klebsiella_pneumo_complex__mlst/data/mdh.fasta +1004 -0
- kleborate/modules/klebsiella_pneumo_complex__mlst/data/pgi.fasta +852 -0
- kleborate/modules/klebsiella_pneumo_complex__mlst/data/phoE.fasta +1322 -0
- kleborate/modules/klebsiella_pneumo_complex__mlst/data/profiles.tsv +7136 -0
- kleborate/modules/klebsiella_pneumo_complex__mlst/data/rpoB.fasta +768 -0
- kleborate/modules/klebsiella_pneumo_complex__mlst/data/tonB.fasta +1932 -0
- kleborate/modules/klebsiella_pneumo_complex__mlst/klebsiella_pneumo_complex__mlst.py +80 -0
- kleborate/modules/klebsiella_pneumo_complex__mlst/test_klebsiella_pneumo_complex__mlst.py +140 -0
- kleborate/modules/klebsiella_pneumo_complex__resistance_class_count/__init__.py +0 -0
- kleborate/modules/klebsiella_pneumo_complex__resistance_class_count/klebsiella_pneumo_complex__resistance_class_count.py +76 -0
- kleborate/modules/klebsiella_pneumo_complex__resistance_gene_count/__init__.py +0 -0
- kleborate/modules/klebsiella_pneumo_complex__resistance_gene_count/klebsiella_pneumo_complex__resistance_gene_count.py +82 -0
- kleborate/modules/klebsiella_pneumo_complex__resistance_score/__init__.py +0 -0
- kleborate/modules/klebsiella_pneumo_complex__resistance_score/klebsiella_pneumo_complex__resistance_score.py +98 -0
- kleborate/modules/klebsiella_pneumo_complex__virulence_score/__init__.py +0 -0
- kleborate/modules/klebsiella_pneumo_complex__virulence_score/klebsiella_pneumo_complex__virulence_score.py +106 -0
- kleborate/modules/klebsiella_pneumo_complex__virulence_score/test_klebsiella_pneumo_complex__virulence_score.py +154 -0
- kleborate/modules/klebsiella_pneumo_complex__wzi/data/wzi.fasta +4994 -0
- kleborate/modules/klebsiella_pneumo_complex__wzi/data/wzi.txt +556 -0
- kleborate/modules/klebsiella_pneumo_complex__wzi/klebsiella_pneumo_complex__wzi.py +85 -0
- kleborate/modules/klebsiella_pneumo_complex__wzi/wzi.py +163 -0
- kleborate/shared/__init__.py +0 -0
- kleborate/shared/alignment.py +331 -0
- kleborate/shared/help_formatter.py +175 -0
- kleborate/shared/misc.py +88 -0
- kleborate/shared/mlst.py +204 -0
- kleborate/shared/multi_mlst.py +177 -0
- kleborate/shared/resMinimap.py +201 -0
- kleborate/shared/species_defs.py +62 -0
- kleborate/version.py +20 -0
- kleborate-3.0.0.dist-info/LICENSE +674 -0
- kleborate-3.0.0.dist-info/METADATA +731 -0
- kleborate-3.0.0.dist-info/RECORD +172 -0
- kleborate-3.0.0.dist-info/WHEEL +5 -0
- kleborate-3.0.0.dist-info/entry_points.txt +2 -0
- kleborate-3.0.0.dist-info/top_level.txt +1 -0
kleborate/__init__.py
ADDED
|
File without changes
|
kleborate/__main__.py
ADDED
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright 2024 Mary Maranga, Kat Holt, Ryan Wick
|
|
3
|
+
https://github.com/klebgenomics/KleborateModular/
|
|
4
|
+
|
|
5
|
+
This file is part of Kleborate. Kleborate is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU General Public License as published by the Free Software Foundation,
|
|
7
|
+
either version 3 of the License, or (at your option) any later version. Kleborate is distributed in
|
|
8
|
+
the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
9
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
10
|
+
details. You should have received a copy of the GNU General Public License along with Kleborate. If
|
|
11
|
+
not, see <https://www.gnu.org/licenses/>.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import graphlib
|
|
16
|
+
import gzip
|
|
17
|
+
import importlib
|
|
18
|
+
import importlib.metadata
|
|
19
|
+
import os
|
|
20
|
+
import pathlib
|
|
21
|
+
import re
|
|
22
|
+
import shutil
|
|
23
|
+
import subprocess
|
|
24
|
+
import sys
|
|
25
|
+
import tempfile
|
|
26
|
+
import textwrap
|
|
27
|
+
import uuid
|
|
28
|
+
|
|
29
|
+
from .shared.help_formatter import MyParser, MyHelpFormatter
|
|
30
|
+
from .shared.misc import get_compression_type, load_fasta,reverse_complement
|
|
31
|
+
from .shared.species_defs import is_kp_complex, is_ko_complex, is_escherichia
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def parse_arguments(args, all_module_names, modules):
|
|
35
|
+
"""
|
|
36
|
+
This function does the CLI argument parsing for Kleborate. Module-specific arguments are added
|
|
37
|
+
by each module's add_cli_options function.
|
|
38
|
+
"""
|
|
39
|
+
parser = MyParser(description='Kleborate: a tool for characterising virulence and resistance '
|
|
40
|
+
'in pathogen assemblies',
|
|
41
|
+
formatter_class=MyHelpFormatter, add_help=False, epilog=paper_refs())
|
|
42
|
+
|
|
43
|
+
if '--helpall' in args or '--allhelp' in args or '--all_help' in args:
|
|
44
|
+
args.append('--help_all')
|
|
45
|
+
|
|
46
|
+
io_args = parser.add_argument_group('Input/output')
|
|
47
|
+
io_args.add_argument('-a', '--assemblies', nargs='+', type=str,
|
|
48
|
+
help='FASTA file(s) for assemblies')
|
|
49
|
+
io_args.add_argument('-o', '--outfile', type=str, default='Kleborate_results.txt',
|
|
50
|
+
help='File for detailed output (default: Kleborate_results.txt)')
|
|
51
|
+
|
|
52
|
+
module_args = parser.add_argument_group('Modules')
|
|
53
|
+
module_args.add_argument('--list_modules', action='store_true',
|
|
54
|
+
help='Print a list of all available modules and then quit')
|
|
55
|
+
module_args.add_argument('-p', '--preset', type=str,
|
|
56
|
+
help=f'Module presets, choose from: ' + ', '.join(get_presets()))
|
|
57
|
+
module_args.add_argument('-m', '--modules', type=str,
|
|
58
|
+
help='Comma-delimited list of Kleborate modules to use')
|
|
59
|
+
|
|
60
|
+
add_module_cli_arguments(parser, args, all_module_names, modules)
|
|
61
|
+
|
|
62
|
+
help_args = parser.add_argument_group('Help')
|
|
63
|
+
help_args.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS,
|
|
64
|
+
help='Show this help message and exit')
|
|
65
|
+
help_args.add_argument('--help_all', action='help',
|
|
66
|
+
help='Show a help message with all module options')
|
|
67
|
+
help_args.add_argument('--version', action='version', version=f'Kleborate v{get_version()}',
|
|
68
|
+
help="Show program's version number and exit")
|
|
69
|
+
|
|
70
|
+
if not args:
|
|
71
|
+
parser.print_help(file=sys.stderr)
|
|
72
|
+
sys.exit(1)
|
|
73
|
+
|
|
74
|
+
return parser.parse_args(args)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def main():
|
|
78
|
+
all_module_names, modules = import_modules()
|
|
79
|
+
args = parse_arguments(sys.argv[1:], all_module_names, modules)
|
|
80
|
+
print_modules(args, all_module_names, modules)
|
|
81
|
+
|
|
82
|
+
module_names, check_module_list, pass_modules = get_used_module_names(args, all_module_names, get_presets())
|
|
83
|
+
|
|
84
|
+
# Define preset_check_modules
|
|
85
|
+
preset_check_modules = []
|
|
86
|
+
if args.preset:
|
|
87
|
+
presets = get_presets()
|
|
88
|
+
preset_check_modules = [module for module, _ in presets[args.preset]['check']]
|
|
89
|
+
|
|
90
|
+
module_names, module_run_order, external_programs = check_modules(args, modules, module_names, check_module_list, pass_modules)
|
|
91
|
+
check_assemblies(args)
|
|
92
|
+
|
|
93
|
+
full_headers, stdout_headers = get_headers(module_names, modules)
|
|
94
|
+
output_headers(full_headers, stdout_headers, args.outfile)
|
|
95
|
+
|
|
96
|
+
for assembly in args.assemblies:
|
|
97
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
98
|
+
unzipped_assembly = gunzip_assembly_if_necessary(assembly, temp_dir)
|
|
99
|
+
minimap2_index = build_minimap2_index(assembly, unzipped_assembly, external_programs, temp_dir)
|
|
100
|
+
results = {'strain': get_strain_name(assembly)}
|
|
101
|
+
|
|
102
|
+
pass_check = True # default, assume no check and run all modules
|
|
103
|
+
|
|
104
|
+
# if we have 'check' modules in the preset, run these
|
|
105
|
+
if args.preset and len(check_module_list) > 0:
|
|
106
|
+
for module, check in presets[args.preset]['check']:
|
|
107
|
+
try:
|
|
108
|
+
module_results = modules[module].get_results(unzipped_assembly, minimap2_index, args, results)
|
|
109
|
+
|
|
110
|
+
results.update({f'{module}__{header}': result for header, result in module_results.items()})
|
|
111
|
+
check_function = globals()[check]
|
|
112
|
+
|
|
113
|
+
if not check_function(module_results):
|
|
114
|
+
pass_check = False
|
|
115
|
+
print(f"Assembly {assembly} failed in check {check}. Continuing with next assembly.")
|
|
116
|
+
break # Exit the for loop since this assembly failed the check
|
|
117
|
+
|
|
118
|
+
except Exception as e:
|
|
119
|
+
print(f"Error encountered while processing {assembly} with {module}: {e}. Continuing with next assembly.")
|
|
120
|
+
pass_check = False
|
|
121
|
+
break # Exit the for loop since an error occurred
|
|
122
|
+
|
|
123
|
+
# proceed through all other modules
|
|
124
|
+
if pass_check:
|
|
125
|
+
for module in module_run_order:
|
|
126
|
+
if module not in preset_check_modules:
|
|
127
|
+
module_results = modules[module].get_results(unzipped_assembly, minimap2_index, args, results)
|
|
128
|
+
|
|
129
|
+
results.update({f'{module}__{header}': result for header, result in module_results.items()})
|
|
130
|
+
|
|
131
|
+
# write results
|
|
132
|
+
output_results(full_headers, stdout_headers, args.outfile, results)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# def main():
|
|
136
|
+
# all_module_names, modules = import_modules()
|
|
137
|
+
# args = parse_arguments(sys.argv[1:], all_module_names, modules)
|
|
138
|
+
# print_modules(args, all_module_names, modules)
|
|
139
|
+
# module_names, check_module_list, pass_modules = get_used_module_names(args, all_module_names, get_presets())
|
|
140
|
+
# module_names, module_run_order, external_programs = check_modules(args, modules, module_names, check_module_list, pass_modules)
|
|
141
|
+
# check_assemblies(args)
|
|
142
|
+
|
|
143
|
+
# full_headers, stdout_headers = get_headers(module_names, modules)
|
|
144
|
+
# output_headers(full_headers, stdout_headers, args.outfile)
|
|
145
|
+
|
|
146
|
+
# # Define preset_check_modules based on presets
|
|
147
|
+
# presets = get_presets()
|
|
148
|
+
# preset_check_modules = [module for module, _ in presets[args.preset]['check']]
|
|
149
|
+
|
|
150
|
+
# for assembly in args.assemblies:
|
|
151
|
+
# with tempfile.TemporaryDirectory() as temp_dir:
|
|
152
|
+
# unzipped_assembly = gunzip_assembly_if_necessary(assembly, temp_dir)
|
|
153
|
+
# minimap2_index = build_minimap2_index(assembly, unzipped_assembly, external_programs, temp_dir)
|
|
154
|
+
# # results = {'assembly': assembly}
|
|
155
|
+
# results = {'strain': get_strain_name(assembly)}
|
|
156
|
+
|
|
157
|
+
# pass_check = True # default, assume no check and run all modules
|
|
158
|
+
|
|
159
|
+
# # if we have 'check' modules in the preset, run these and see if we pass
|
|
160
|
+
# if len(check_module_list) > 0:
|
|
161
|
+
|
|
162
|
+
# for module, check in presets[args.preset]['check']:
|
|
163
|
+
# try:
|
|
164
|
+
# module_results = modules[module].get_results(unzipped_assembly, minimap2_index, args, results)
|
|
165
|
+
|
|
166
|
+
# results.update({f'{module}__{header}': result for header, result in module_results.items()})
|
|
167
|
+
# check_function = globals()[check]
|
|
168
|
+
|
|
169
|
+
# if not check_function(module_results):
|
|
170
|
+
# pass_check = False
|
|
171
|
+
# print(f"Assembly {assembly} failed in check {check}. Continuing with next assembly.")
|
|
172
|
+
# break # Exit the for loop since this assembly failed the check
|
|
173
|
+
|
|
174
|
+
# except Exception as e:
|
|
175
|
+
# print(f"Error encountered while processing {assembly} with {module}: {e}. Continuing with next assembly.")
|
|
176
|
+
# pass_check = False
|
|
177
|
+
# break # Exit the for loop since an error occurred
|
|
178
|
+
|
|
179
|
+
# # proceed through all other modules
|
|
180
|
+
# if pass_check:
|
|
181
|
+
# for module in module_run_order:
|
|
182
|
+
# if module not in preset_check_modules:
|
|
183
|
+
# module_results = modules[module].get_results(unzipped_assembly, minimap2_index, args, results)
|
|
184
|
+
|
|
185
|
+
# results.update({f'{module}__{header}': result for header, result in module_results.items()})
|
|
186
|
+
|
|
187
|
+
# # write results
|
|
188
|
+
# output_results(full_headers, stdout_headers, args.outfile, results)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def print_modules(args, all_module_names, modules):
|
|
192
|
+
if args.list_modules:
|
|
193
|
+
print()
|
|
194
|
+
print('Available modules for Kleborate')
|
|
195
|
+
print('-------------------------------')
|
|
196
|
+
terminal_width = shutil.get_terminal_size().columns
|
|
197
|
+
end_formatting, bold = '\033[0m', '\033[1m'
|
|
198
|
+
for m in all_module_names:
|
|
199
|
+
description = modules[m].description()
|
|
200
|
+
text = f'{bold}{m}{end_formatting}: {description}'
|
|
201
|
+
print('\n'.join(textwrap.wrap(text, width=terminal_width - 1)))
|
|
202
|
+
print()
|
|
203
|
+
sys.exit(0)
|
|
204
|
+
elif not args.assemblies:
|
|
205
|
+
sys.exit('Error: you must provide one or more assembly files using --assemblies')
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def get_presets():
|
|
210
|
+
kpsc_modules = {
|
|
211
|
+
'check': [('enterobacterales__species', 'is_kp_complex')],
|
|
212
|
+
'pass': [
|
|
213
|
+
'general__contig_stats','klebsiella_pneumo_complex__mlst',
|
|
214
|
+
'klebsiella__ybst', 'klebsiella__cbst', 'klebsiella__abst', 'klebsiella__smst', 'klebsiella__rmst', 'klebsiella_pneumo_complex__virulence_score',
|
|
215
|
+
'klebsiella__rmpa2','klebsiella_pneumo_complex__amr', 'klebsiella_pneumo_complex__resistance_score', 'klebsiella_pneumo_complex__resistance_class_count',
|
|
216
|
+
'klebsiella_pneumo_complex__resistance_gene_count', 'klebsiella_pneumo_complex__wzi','klebsiella_pneumo_complex__kaptive'
|
|
217
|
+
]
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
kosc_modules = {
|
|
221
|
+
'check': [('enterobacterales__species', 'is_ko_complex')],
|
|
222
|
+
'pass': [
|
|
223
|
+
'general__contig_stats',
|
|
224
|
+
'klebsiella_oxytoca_complex__mlst', 'klebsiella__ybst', 'klebsiella__cbst', 'klebsiella__abst', 'klebsiella__smst', 'klebsiella__rmst','klebsiella__rmpa2'
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
escherichia_modules = {
|
|
229
|
+
'check': [('enterobacterales__species', 'is_escherichia')],
|
|
230
|
+
'pass': [
|
|
231
|
+
'general__contig_stats',
|
|
232
|
+
'escherichia__mlst_achtman', 'escherichia__mlst_pasteur'
|
|
233
|
+
]
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
'kpsc': kpsc_modules,
|
|
238
|
+
'kosc': kosc_modules,
|
|
239
|
+
'escherichia': escherichia_modules
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def add_module_cli_arguments(parser, args, all_module_names, modules):
|
|
244
|
+
"""
|
|
245
|
+
This function add CLI argument for modules. Each modules that has options gets its own argument
|
|
246
|
+
group. These are only displayed in the help text if the user used --help_all, otherwise they
|
|
247
|
+
are hidden.
|
|
248
|
+
"""
|
|
249
|
+
for m in all_module_names:
|
|
250
|
+
group = modules[m].add_cli_options(parser)
|
|
251
|
+
if '--help_all' not in args and group is not None:
|
|
252
|
+
for a in group._group_actions:
|
|
253
|
+
a.help = argparse.SUPPRESS
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def get_used_module_names(args, all_module_names, presets):
|
|
257
|
+
if args.preset is None and args.modules is None:
|
|
258
|
+
sys.exit('Error: either --preset or --modules is required')
|
|
259
|
+
|
|
260
|
+
# Initialize empty lists for module names, check modules, and pass modules
|
|
261
|
+
module_names = []
|
|
262
|
+
check_modules = []
|
|
263
|
+
pass_modules = []
|
|
264
|
+
|
|
265
|
+
if args.preset:
|
|
266
|
+
if args.preset not in presets:
|
|
267
|
+
sys.exit(f'Error: {args.preset} is not a valid preset')
|
|
268
|
+
|
|
269
|
+
# Assuming presets[args.preset] is a dictionary with 'check' and 'pass' keys
|
|
270
|
+
check_modules = [module[0] for module in presets[args.preset].get('check', [])] # Extract module names from check modules
|
|
271
|
+
pass_modules = presets[args.preset].get('pass', []) # Directly assign pass modules
|
|
272
|
+
|
|
273
|
+
module_names += check_modules + pass_modules # Combine check and pass modules for the overall list
|
|
274
|
+
|
|
275
|
+
if args.modules:
|
|
276
|
+
for m in args.modules.split(','):
|
|
277
|
+
if m not in all_module_names:
|
|
278
|
+
sys.exit(f'Error: {m} is not a valid module name')
|
|
279
|
+
if m not in module_names:
|
|
280
|
+
module_names.append(m)
|
|
281
|
+
|
|
282
|
+
return module_names, check_modules, pass_modules
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def get_all_module_names():
|
|
286
|
+
"""
|
|
287
|
+
Looks for all Kleborate modules and returns their names. To qualify as a module, it must be in
|
|
288
|
+
the 'modules' directory, in a subdirectory that matches the filename. For example:
|
|
289
|
+
* modules/contig_stats/contig_stats.py <- is a module
|
|
290
|
+
* modules/kpsc_mlst/kpsc_mlst.py <- is a module
|
|
291
|
+
* modules/contig_stats/test_contig_stats.py <- not a module
|
|
292
|
+
|
|
293
|
+
"""
|
|
294
|
+
module_dir = pathlib.Path(__file__).parents[0] / 'modules'
|
|
295
|
+
module_names = []
|
|
296
|
+
for module_file in module_dir.glob('*/*.py'):
|
|
297
|
+
dir_name = module_file.parts[-2]
|
|
298
|
+
if module_file.parts[-1][:-3] == dir_name:
|
|
299
|
+
module_names.append(dir_name)
|
|
300
|
+
if 'template' in module_names:
|
|
301
|
+
module_names.remove('template')
|
|
302
|
+
return sorted(module_names)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def get_strain_name(full_path):
|
|
306
|
+
filename = os.path.split(full_path)[1]
|
|
307
|
+
if filename.endswith('_temp_decompress.fasta'):
|
|
308
|
+
filename = filename[:-22]
|
|
309
|
+
if filename.endswith('.gz'):
|
|
310
|
+
filename = filename[:-3]
|
|
311
|
+
return os.path.splitext(filename)[0]
|
|
312
|
+
|
|
313
|
+
def import_modules():
|
|
314
|
+
"""
|
|
315
|
+
This function imports all Kleborate modules (whether or not they are used in this run).
|
|
316
|
+
"""
|
|
317
|
+
all_module_names = get_all_module_names()
|
|
318
|
+
modules = {}
|
|
319
|
+
for m in all_module_names:
|
|
320
|
+
modules[m] = importlib.import_module(f'..modules.{m}.{m}', __name__)
|
|
321
|
+
return all_module_names, modules
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def check_modules(args, modules, module_names, preset_check_modules, preset_pass_modules):
|
|
325
|
+
"""
|
|
326
|
+
This function checks the options, prerequisites external requirements of the used modules. If
|
|
327
|
+
any fail, the program will quit with an error. It returns:
|
|
328
|
+
* A list of all module names. Probably the same list as given, but if any prerequisite modules
|
|
329
|
+
were missing, they've been added to the end.
|
|
330
|
+
* A topologically sorted list of module names. This is the run order so each module will have
|
|
331
|
+
its prerequisites run first.
|
|
332
|
+
* A list of all external programs used.
|
|
333
|
+
"""
|
|
334
|
+
all_external_programs = set()
|
|
335
|
+
|
|
336
|
+
for m in module_names:
|
|
337
|
+
modules[m].check_cli_options(args)
|
|
338
|
+
all_external_programs.update(modules[m].check_external_programs())
|
|
339
|
+
|
|
340
|
+
new_module_names = module_names.copy()
|
|
341
|
+
for m in module_names:
|
|
342
|
+
for prereq in modules[m].prerequisite_modules():
|
|
343
|
+
if prereq not in new_module_names:
|
|
344
|
+
new_module_names.append(prereq)
|
|
345
|
+
module_names = new_module_names
|
|
346
|
+
|
|
347
|
+
dependency_graph = {m: modules[m].prerequisite_modules() for m in module_names}
|
|
348
|
+
|
|
349
|
+
return module_names, get_run_order(dependency_graph), sorted(all_external_programs)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def get_run_order(dependency_graph):
|
|
353
|
+
try:
|
|
354
|
+
ts = graphlib.TopologicalSorter(dependency_graph)
|
|
355
|
+
return list(ts.static_order())
|
|
356
|
+
except graphlib.CycleError:
|
|
357
|
+
sys.exit('Error: module dependency graph contains a cycle')
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def check_assemblies(args):
|
|
361
|
+
"""
|
|
362
|
+
This function does a quick check to make sure that the input assemblies look good.
|
|
363
|
+
"""
|
|
364
|
+
for assembly in args.assemblies:
|
|
365
|
+
if os.path.isdir(assembly):
|
|
366
|
+
sys.exit('Error: ' + assembly + ' is a directory (please specify assembly files)')
|
|
367
|
+
if not os.path.isfile(assembly):
|
|
368
|
+
sys.exit('Error: could not find ' + assembly)
|
|
369
|
+
fasta = load_fasta(assembly)
|
|
370
|
+
if len(fasta) < 1:
|
|
371
|
+
sys.exit('Error: invalid FASTA file: ' + assembly)
|
|
372
|
+
for _, seq in fasta:
|
|
373
|
+
if len(seq) == 0:
|
|
374
|
+
sys.exit('Error: invalid FASTA file (contains a zero-length sequence): ' + assembly)
|
|
375
|
+
|
|
376
|
+
def get_headers(module_names, modules):
|
|
377
|
+
"""
|
|
378
|
+
This function returns three lists of headers:
|
|
379
|
+
* top_headers: for Kleborate's output file, shows module names
|
|
380
|
+
* full_headers: for Kleborate's output file, show column names
|
|
381
|
+
* stdout_headers: for Kleborate's stdout
|
|
382
|
+
|
|
383
|
+
Each used module contributes headers to these lists. To ensure that there aren't any duplicate
|
|
384
|
+
headers, the module name is added before each header in full_headers and stdout_header,
|
|
385
|
+
separated by a double-underscore.
|
|
386
|
+
"""
|
|
387
|
+
_,full_headers, stdout_headers = [''], ['strain'], ['strain']
|
|
388
|
+
for module_name in module_names:
|
|
389
|
+
module_full, module_stdout = modules[module_name].get_headers()
|
|
390
|
+
#top_headers.append(module_name)
|
|
391
|
+
#top_headers += [''] * (len(module_full) - 1)
|
|
392
|
+
full_headers += [f'{module_name}__{h}' for h in module_full]
|
|
393
|
+
stdout_headers += [f'{module_name}__{h}' for h in module_stdout]
|
|
394
|
+
return full_headers, stdout_headers
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def gunzip_assembly_if_necessary(assembly, temp_dir):
|
|
398
|
+
if get_compression_type(assembly) == 'gz':
|
|
399
|
+
unzipped_assembly = pathlib.Path(temp_dir) / (uuid.uuid4().hex + '.fasta')
|
|
400
|
+
decompress_file(assembly, unzipped_assembly)
|
|
401
|
+
return unzipped_assembly
|
|
402
|
+
else:
|
|
403
|
+
return assembly
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def build_minimap2_index(assembly, unzipped_assembly, external_programs, temp_dir):
|
|
407
|
+
"""
|
|
408
|
+
A lot of the modules use minimap2 alignment, so pre-building the index for this assembly once
|
|
409
|
+
can save a bit of time.
|
|
410
|
+
"""
|
|
411
|
+
if 'minimap2' not in external_programs:
|
|
412
|
+
return None
|
|
413
|
+
minimap2_index = (pathlib.Path(temp_dir) / (uuid.uuid4().hex + '.mmi')).resolve()
|
|
414
|
+
command = ['minimap2', '-d', minimap2_index, unzipped_assembly]
|
|
415
|
+
p = subprocess.run(command, capture_output=True, text=True)
|
|
416
|
+
if p.returncode != 0:
|
|
417
|
+
sys.exit(f'\nError: minimap2 failed to index sample {assembly}:\n{p.stderr}')
|
|
418
|
+
return minimap2_index
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def decompress_file(in_file, out_file):
|
|
422
|
+
with gzip.GzipFile(in_file, 'rb') as i, open(out_file, 'wb') as o:
|
|
423
|
+
s = i.read()
|
|
424
|
+
o.write(s)
|
|
425
|
+
|
|
426
|
+
def output_headers(full_headers, stdout_headers, outfile):
|
|
427
|
+
"""
|
|
428
|
+
This function prints headers to stdout and writes headers to the output file. Module names are
|
|
429
|
+
trimmed off to make the headers shorter and easier to read.
|
|
430
|
+
"""
|
|
431
|
+
trimmed_stdout_headers = [h.split('__')[-1] for h in stdout_headers]
|
|
432
|
+
trimmed_full_headers = [h.split('__')[-1] for h in full_headers]
|
|
433
|
+
print('\t'.join(trimmed_stdout_headers))
|
|
434
|
+
with open(outfile, 'wt') as o:
|
|
435
|
+
o.write('\t'.join(trimmed_full_headers))
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def output_results(full_headers, stdout_headers, outfile, results):
|
|
439
|
+
"""
|
|
440
|
+
This function writes the results to stdout and the output file.
|
|
441
|
+
"""
|
|
442
|
+
print('\t'.join([str(results.get(x, "-")).strip("[] ").replace("assembly", "strain") for x in stdout_headers]))
|
|
443
|
+
with open(outfile, 'at') as o:
|
|
444
|
+
if o.tell() > 0: # Check if the file is not empty
|
|
445
|
+
o.write('\n')
|
|
446
|
+
o.write('\t'.join([str(results.get(x, "-")).strip("[] ").replace("assembly", "strain") for x in full_headers]))
|
|
447
|
+
|
|
448
|
+
for h in results.keys():
|
|
449
|
+
if h not in full_headers:
|
|
450
|
+
sys.exit(f'Error: results contained a value ({h}) that is not covered by the output headers')
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def paper_refs():
|
|
454
|
+
"""
|
|
455
|
+
This function prepares the references for the program's help text, with clean line wrapping.
|
|
456
|
+
"""
|
|
457
|
+
terminal_width = shutil.get_terminal_size().columns
|
|
458
|
+
text = 'If you use Kleborate, please cite the paper:\n' \
|
|
459
|
+
'Lam MMC, et al. A genomic surveillance framework and genotyping tool for Klebsiella ' \
|
|
460
|
+
'pneumoniae and its related species complex. Nature Communications. 2021. ' \
|
|
461
|
+
'doi:10.1038/s41467-021-24448-3.\n\n' \
|
|
462
|
+
'If you turn on the Kaptive option for full K and O typing, please also cite:\n' \
|
|
463
|
+
'Wyres KL, et al. Identification of Klebsiella capsule synthesis loci from whole ' \
|
|
464
|
+
'genome data. Microbial Genomics. 2016. doi:10.1099/mgen.0.000102.'
|
|
465
|
+
wrapped_text = ''
|
|
466
|
+
for line in text.split('\n'):
|
|
467
|
+
wrapped_text += '\n'.join(textwrap.wrap(line, width=terminal_width - 1))
|
|
468
|
+
wrapped_text += '\n'
|
|
469
|
+
return 'R|' + wrapped_text
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def get_version():
|
|
473
|
+
"""
|
|
474
|
+
This function returns the version of Kleborate as a string, without the leading 'v'.
|
|
475
|
+
"""
|
|
476
|
+
# First try to get the version from the pyproject.toml file, in case this is being run directly
|
|
477
|
+
# from the repo using the kleborate-runner.py file.
|
|
478
|
+
pyproject = pathlib.Path(__file__).parents[1] / 'pyproject.toml'
|
|
479
|
+
if pyproject.is_file():
|
|
480
|
+
with open(pyproject, 'rt') as f:
|
|
481
|
+
pyproject_text = f.read()
|
|
482
|
+
if 'name = "kleborate"' in pyproject_text: # make sure it's the right pyproject.toml
|
|
483
|
+
match = re.search(r'version = "(\d+\.\d+\.\d+)"', pyproject_text)
|
|
484
|
+
if match:
|
|
485
|
+
return match.group(1)
|
|
486
|
+
|
|
487
|
+
# If there wasn't a 'pyproject.toml' file, then Kleborate is probably installed, so use
|
|
488
|
+
# importlib to get the version of the installed package.
|
|
489
|
+
try:
|
|
490
|
+
from importlib import metadata
|
|
491
|
+
except ImportError:
|
|
492
|
+
import importlib_metadata as metadata
|
|
493
|
+
return metadata.version(__package__ or __name__)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
if __name__ == '__main__':
|
|
497
|
+
main()
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright 2023 Kat Holt
|
|
3
|
+
Copyright 2023 Ryan Wick (rrwick@gmail.com)
|
|
4
|
+
https://github.com/katholt/Kleborate/
|
|
5
|
+
|
|
6
|
+
This file is part of Kleborate. Kleborate is free software: you can redistribute it and/or modify
|
|
7
|
+
it under the terms of the GNU General Public License as published by the Free Software Foundation,
|
|
8
|
+
either version 3 of the License, or (at your option) any later version. Kleborate is distributed in
|
|
9
|
+
the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
11
|
+
details. You should have received a copy of the GNU General Public License along with Kleborate. If
|
|
12
|
+
not, see <https://www.gnu.org/licenses/>.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import os
|
|
17
|
+
import pathlib
|
|
18
|
+
import shutil
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def description():
|
|
23
|
+
return 'Mash-based species detection for enterobacterales species'
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def prerequisite_modules():
|
|
27
|
+
return []
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_headers():
|
|
31
|
+
full_headers = ['species', 'species_match']
|
|
32
|
+
stdout_headers = ['species']
|
|
33
|
+
return full_headers, stdout_headers
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_add_cli_options():
|
|
37
|
+
parser = argparse.ArgumentParser()
|
|
38
|
+
add_cli_options(parser)
|
|
39
|
+
assert '--enterobacterales__species_strong' in parser.format_help()
|
|
40
|
+
assert '--enterobacterales__species_weak' in parser.format_help()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def add_cli_options(parser):
|
|
44
|
+
module_name = os.path.basename(__file__)[:-3]
|
|
45
|
+
group = parser.add_argument_group(f'{module_name} module')
|
|
46
|
+
group.add_argument('--enterobacterales__species_strong', type=float, default=0.02,
|
|
47
|
+
help='Mash distance threshold for a strong species match')
|
|
48
|
+
group.add_argument('--enterobacterales__species_weak', type=float, default=0.04,
|
|
49
|
+
help='Mash distance threshold for a weak species match')
|
|
50
|
+
return group
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def check_cli_options(args):
|
|
54
|
+
if args.enterobacterales__species_strong <= 0.0 or args.enterobacterales__species_strong >= 1.0:
|
|
55
|
+
sys.exit('Error: --enterobacterales__species_strong must be between 0.0 and 1.0')
|
|
56
|
+
if args.enterobacterales__species_weak <= 0.0 or args.enterobacterales__species_weak >= 1.0:
|
|
57
|
+
sys.exit('Error: --enterobacterales__species_weak must be between 0.0 and 1.0')
|
|
58
|
+
if args.enterobacterales__species_weak < args.enterobacterales__species_strong:
|
|
59
|
+
sys.exit('Error: --enterobacterales__species_weak must be greater than '
|
|
60
|
+
'--enterobacterales__species_strong')
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def check_external_programs():
|
|
64
|
+
if not shutil.which('mash'):
|
|
65
|
+
sys.exit('Error: could not find mash')
|
|
66
|
+
return ['mash']
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_results(assembly, minimap2_index, args, previous_results):
|
|
70
|
+
#print(previous_results)
|
|
71
|
+
sketch_file = pathlib.Path(__file__).parents[0] / 'data' / 'species_mash_sketches.msh'
|
|
72
|
+
species, distance = get_enterobacterales__species(assembly, sketch_file)
|
|
73
|
+
if distance <= args.enterobacterales__species_strong:
|
|
74
|
+
species_hit_strength = 'strong'
|
|
75
|
+
elif distance <= args.enterobacterales__species_weak:
|
|
76
|
+
species_hit_strength = 'weak'
|
|
77
|
+
else:
|
|
78
|
+
species = 'unknown'
|
|
79
|
+
species_hit_strength = ''
|
|
80
|
+
return {'species': species,
|
|
81
|
+
'species_match': species_hit_strength}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def get_enterobacterales__species(assembly, sketch_file):
|
|
85
|
+
f = os.popen('mash dist ' + str(sketch_file) + ' ' + str(assembly))
|
|
86
|
+
best_species, best_distance = None, 1.0
|
|
87
|
+
for line in f:
|
|
88
|
+
line_parts = line.split('\t')
|
|
89
|
+
reference = line_parts[0]
|
|
90
|
+
if len(line_parts) >= 3:
|
|
91
|
+
species = reference.split('/')[0]
|
|
92
|
+
distance = float(line_parts[2])
|
|
93
|
+
if distance < best_distance:
|
|
94
|
+
best_distance = distance
|
|
95
|
+
best_species = species
|
|
96
|
+
f.close()
|
|
97
|
+
best_species = clean_species_name(best_species)
|
|
98
|
+
return best_species, best_distance
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def clean_species_name(species):
|
|
102
|
+
if species is None:
|
|
103
|
+
return None
|
|
104
|
+
species = species.replace('Escherichia_coli', 'Escherichia coli / Shigella')
|
|
105
|
+
species = species.replace('Raoultella', 'Klebsiella (Raoultella)')
|
|
106
|
+
species = species.replace('_', ' ')
|
|
107
|
+
species = species.replace(' subsp ', ' subsp. ')
|
|
108
|
+
if species.endswith(' unknown'):
|
|
109
|
+
species = species.replace(' unknown', ' (unknown species)')
|
|
110
|
+
return species
|