9
9
import multiprocessing
10
10
import os
11
11
import pathlib
12
+ import platform
12
13
import shutil
13
14
import subprocess
14
15
import sys
15
16
import typing
16
17
import uuid
17
18
18
- __version__ = '1.2.7 '
19
+ __version__ = '1.2.8 '
19
20
20
21
DEFAULT_THREAD_COUNT = min (multiprocessing .cpu_count (), 64 )
21
22
@@ -59,7 +60,7 @@ def ranged_float_type(value):
59
60
return f
60
61
61
62
parser = argparse .ArgumentParser (
62
- description = f'%(prog)s v. { __version__ } : calculate ANI and cluster '
63
+ description = f'%(prog)s v{ __version__ } : calculate ANI and cluster '
63
64
'virus (meta)genome sequences' ,
64
65
add_help = False ,
65
66
)
@@ -117,7 +118,7 @@ def ranged_float_type(value):
117
118
'--min-kmers' ,
118
119
metavar = "<int>" ,
119
120
type = int ,
120
- default = 10 ,
121
+ default = 20 ,
121
122
help = 'Filter genome pairs based on minimum number of shared k-mers '
122
123
'[%(default)s]'
123
124
)
@@ -531,7 +532,7 @@ def ranged_float_type(value):
531
532
'--bin' ,
532
533
metavar = '<file>' ,
533
534
type = pathlib .Path ,
534
- dest = "BIN_CLUSTY " ,
535
+ dest = "bin_clusty " ,
535
536
default = f'{ BIN_CLUSTY } ' ,
536
537
help = 'Path to the Clusty binary [%(default)s]'
537
538
)
@@ -603,8 +604,8 @@ def get_uuid() -> str:
603
604
return f'vclust-{ str (uuid .uuid4 ().hex )[:10 ]} '
604
605
605
606
606
- def validate_binary (bin_path : pathlib .Path ) -> pathlib .Path :
607
- """Validates the existence and executability of a binary file.
607
+ def _validate_binary (bin_path : pathlib .Path ) -> pathlib .Path :
608
+ """Validates the presence and executability of a binary file.
608
609
609
610
This function checks if the provided path points to an existing binary file
610
611
and if it is executable. It also attempts to run the binary to ensure it
@@ -618,16 +619,16 @@ def validate_binary(bin_path: pathlib.Path) -> pathlib.Path:
618
619
pathlib.Path: The resolved path to the binary file.
619
620
620
621
Raises:
621
- SystemExit : If the binary file does not exist, is not executable, or
622
- if running the binary encounters an error.
622
+ RuntimeError : If the binary file does not exist, is not executable,
623
+ or if running the binary encounters an error.
623
624
"""
624
625
bin_path = bin_path .resolve ()
625
626
626
627
if not bin_path .exists ():
627
- exit (f'error: Executable not found: { bin_path } ' )
628
+ raise RuntimeError (f'File not found: { bin_path } ' )
628
629
629
630
if not bin_path .is_file () or not os .access (bin_path , os .X_OK ):
630
- exit (f'error: Binary file not executable: { bin_path } ' )
631
+ raise RuntimeError (f'Binary file not executable: { bin_path } ' )
631
632
632
633
try :
633
634
subprocess .run (
@@ -638,14 +639,21 @@ def validate_binary(bin_path: pathlib.Path) -> pathlib.Path:
638
639
check = True
639
640
)
640
641
except subprocess .CalledProcessError as e :
641
- exit (f'error: Running { bin_path } failed with message: { e .stderr } ' )
642
+ raise RuntimeError (f'Running { bin_path } failed with message: { e .stderr } ' )
642
643
except OSError as e :
643
- exit (f'error: OSError in { bin_path } - { e } ' )
644
+ raise RuntimeError (f'OSError in { bin_path } - { e } ' )
644
645
except Exception as e :
645
- exit (f'error: Unexpected error in binary { bin_path } - { e } ' )
646
+ raise RuntimeError (f'Unexpected error in binary { bin_path } - { e } ' )
646
647
return bin_path
647
648
648
649
650
+ def validate_binary (bin_path : pathlib .Path ) -> pathlib .Path :
651
+ try :
652
+ return _validate_binary (bin_path )
653
+ except RuntimeError as e :
654
+ sys .exit (f'error: { e } ' )
655
+
656
+
649
657
def validate_args_fasta_input (args , parser ) -> argparse .Namespace :
650
658
"""Validates the arguments for FASTA input."""
651
659
args .is_multifasta = True
@@ -732,13 +740,13 @@ def run(
732
740
)
733
741
except subprocess .CalledProcessError as e :
734
742
logger .error (f'Process { " " .join (cmd )} failed with message: { e .stderr } ' )
735
- exit (1 )
743
+ sys . exit (1 )
736
744
except OSError as e :
737
745
logger .error (f'OSError: { " " .join (cmd )} failed with message: { e } ' )
738
- exit (1 )
746
+ sys . exit (1 )
739
747
except Exception as e :
740
748
logger .error (f'Unexpected: { " " .join (cmd )} failed with message: { e } ' )
741
- exit (1 )
749
+ sys . exit (1 )
742
750
logger .info (f'Done' )
743
751
return process
744
752
@@ -1145,11 +1153,75 @@ def cmd_clusty(
1145
1153
return cmd
1146
1154
1147
1155
1148
- def vclust_info ():
1149
- print (f'Vclust { __version__ } ' )
1150
- for bin_path in [BIN_KMERDB , BIN_FASTASPLIT , BIN_LZANI , BIN_CLUSTY ]:
1151
- validate_binary (bin_path )
1152
- print (f'{ bin_path .name :<20} ok' )
1156
+ def vclust_info () -> None :
1157
+ """
1158
+ Displays the Vclust version, installation paths, and binary dependencies.
1159
+ Checks for the presence and executable status of required binaries.
1160
+
1161
+ Exits with a non-zero status if any dependencies are missing or
1162
+ not executable.
1163
+
1164
+ Returns:
1165
+ None
1166
+
1167
+ Raises:
1168
+ SystemExit: If any binary dependencies are missing or not executable.
1169
+
1170
+ """
1171
+ # ANSI color codes for terminal output.
1172
+ GREEN = '\033 [92m'
1173
+ RED = '\033 [91m'
1174
+ RESET = '\033 [0m'
1175
+
1176
+ binaries = {
1177
+ 'Kmer-db' : BIN_KMERDB ,
1178
+ 'LZ-ANI' : BIN_LZANI ,
1179
+ 'Clusty' : BIN_CLUSTY ,
1180
+ 'multi-fasta-split' : BIN_FASTASPLIT ,
1181
+ }
1182
+
1183
+ output_lines = [
1184
+ f'Vclust version { __version__ } (Python { platform .python_version ()} )' ,
1185
+ '' ,
1186
+ 'Installed at:' ,
1187
+ f' { pathlib .Path (__file__ ).resolve ()} ' ,
1188
+ f' { BIN_DIR .resolve ()} ' ,
1189
+ '' ,
1190
+ 'Binary dependencies:' ,
1191
+ ]
1192
+
1193
+ errors = [] # List to collect any errors encountered during binary checks.
1194
+
1195
+ # Check each binary's presence and version.
1196
+ for name , path in binaries .items ():
1197
+ try :
1198
+ _validate_binary (path )
1199
+ version = subprocess .run (
1200
+ [str (path ), '-version' if name == 'Kmer-db' else '--version' ],
1201
+ stdout = subprocess .PIPE ,
1202
+ stderr = subprocess .PIPE ,
1203
+ text = True ,
1204
+ check = True
1205
+ ).stderr .strip ()
1206
+ output_lines .append (f' { name :<20} v{ version :<10} ' )
1207
+ except Exception as e :
1208
+ output_lines .append (f' { name :<20} [error]' )
1209
+ errors .append ((name , e ))
1210
+
1211
+ # Append the status summary based on any encountered errors.
1212
+ output_lines .append ('' )
1213
+
1214
+ if errors :
1215
+ output_lines .append (f'{ RED } Status: error{ RESET } ' )
1216
+ output_lines .extend (f" - { name } : { error } " for name , error in errors )
1217
+ else :
1218
+ output_lines .append (f'{ GREEN } Status: ok{ RESET } ' )
1219
+
1220
+ # Output the complete information.
1221
+ print ('\n ' .join (output_lines ))
1222
+
1223
+ if errors :
1224
+ sys .exit (1 )
1153
1225
1154
1226
1155
1227
class CustomHelpFormatter (argparse .HelpFormatter ):
@@ -1324,7 +1396,7 @@ def main():
1324
1396
1325
1397
# Cluster
1326
1398
elif args .command == 'cluster' :
1327
- args .BIN_CLUSTY = validate_binary (args .BIN_CLUSTY )
1399
+ args .bin_clusty = validate_binary (args .bin_clusty )
1328
1400
args = validate_args_cluster (args , parser )
1329
1401
1330
1402
cmd = cmd_clusty (
@@ -1344,7 +1416,7 @@ def main():
1344
1416
leiden_resolution = args .leiden_resolution ,
1345
1417
leiden_beta = args .leiden_beta ,
1346
1418
leiden_iterations = args .leiden_iterations ,
1347
- bin_path = args .BIN_CLUSTY ,
1419
+ bin_path = args .bin_clusty ,
1348
1420
)
1349
1421
p = run (cmd , args .verbose , logger )
1350
1422
0 commit comments