Skip to content

Commit e8d98a2

Browse files
Merge pull request #10 from refresh-bio/binaries_validation
Binaries validation
2 parents 042478a + 5ef76fc commit e8d98a2

File tree

3 files changed

+85
-22
lines changed

3 files changed

+85
-22
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ For datasets containing up to 1000 viral genomes, Vclust is available at [http:/
6868

6969
## 2. Installation
7070

71-
To install Vclust you can download statically compiled binaries or compile dependencies from source.
71+
To install Vclust you can download statically compiled binaries or compile dependencies from source. Vclust requires Python 3.7 or higher.
7272

7373
### Option 1: Download precompiled binaries
7474

test.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,23 @@ def test_dir():
3131
shutil.rmtree(TMP_DIR)
3232

3333

34+
@pytest.mark.parametrize('subcommand',[
35+
'prefilter', 'align', 'cluster',
36+
])
37+
def test_subcommands(subcommand):
38+
cmd = [
39+
f'{VCLUST.resolve()}',
40+
f'{subcommand}',
41+
]
42+
p = subprocess.run(cmd,
43+
stdout=subprocess.PIPE,
44+
stderr=subprocess.PIPE,
45+
text=True)
46+
assert p.returncode == 0
47+
assert not p.stderr
48+
assert p.stdout
49+
50+
3451
@pytest.mark.parametrize('input,params,error_msg',[
3552
(FASTA_DIR, ['--batch-size', '4'], 'error: --batch-size'),
3653
(FASTA_DIR, ['--min-ident', '95'], 'between 0 and 1'),

vclust.py

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@
99
import multiprocessing
1010
import pathlib
1111
import shutil
12+
import os
1213
import subprocess
1314
import sys
1415
import typing
1516
import uuid
1617

17-
__version__ = '1.0.1'
18+
__version__ = '1.0.2'
1819

1920
DEFAULT_THREAD_COUNT = min(multiprocessing.cpu_count(), 64)
2021

@@ -524,26 +525,49 @@ def create_logger(name: str, log_level: int = logging.INFO) -> logging.Logger:
524525

525526
def get_uuid() -> str:
526527
"""Returns a unique string identifier."""
527-
return f'vclust-{str(uuid.uuid4().hex)[:16]}'
528+
return f'vclust-{str(uuid.uuid4().hex)[:10]}'
528529

529530

530531
def validate_binary(bin_path: pathlib.Path) -> pathlib.Path:
531-
"""Verifies if a binary file exists and is executable.
532+
"""Validates the existence and executability of a binary file.
533+
534+
This function checks if the provided path points to an existing binary file
535+
and if it is executable. It also attempts to run the binary to ensure it
536+
operates without errors.
532537
533538
Args:
534-
bin_path (Path): Path to the executable binary file.
539+
bin_path:
540+
The path to the executable binary file.
535541
536542
Returns:
537-
Path to the binary file.
543+
pathlib.Path: The resolved path to the binary file.
538544
539545
Raises:
540-
SystemExit: If the binary file is not found or not executable.
541-
546+
SystemExit: If the binary file does not exist, is not executable, or
547+
if running the binary encounters an error.
542548
"""
549+
bin_path = bin_path.resolve()
550+
543551
if not bin_path.exists():
544-
exit(f'error: executable not found: {bin_path.resolve()}')
545-
if not shutil.which(bin_path):
546-
exit(f'error: file not executable: {bin_path.resolve()}')
552+
exit(f'error: Executable not found: {bin_path}')
553+
554+
if not bin_path.is_file() or not os.access(bin_path, os.X_OK):
555+
exit(f'error: Binary file not executable: {bin_path}')
556+
557+
try:
558+
process = subprocess.run(
559+
[str(bin_path)],
560+
stdout=subprocess.DEVNULL,
561+
stderr=subprocess.PIPE,
562+
text=True,
563+
check=True
564+
)
565+
except subprocess.CalledProcessError as e:
566+
exit(f'error: Running {bin_path} failed with message: {e.stderr}')
567+
except OSError as e:
568+
exit(f'error: OSError in {bin_path} - {e}')
569+
except Exception as e:
570+
exit(f'error: Unexpected error in binary {bin_path} - {e}')
547571
return bin_path
548572

549573

@@ -601,23 +625,45 @@ def run(
601625
verbose: bool,
602626
logger: logging.Logger
603627
) -> subprocess.CompletedProcess:
604-
"""Runs a given command as a subprocess and handle logging.
628+
"""Executes a given command as a subprocess and handles logging.
629+
630+
This function runs the specified command, logs the execution details,
631+
and manages errors. If verbose mode is enabled, the command's standard
632+
error output is not suppressed. Otherwise, the standard error is piped
633+
and logged in case of failure.
634+
635+
Args:
636+
cmd:
637+
The command to run as a list of strings.
638+
verbose:
639+
Flag indicating whether to run the command in verbose mode.
640+
logger:
641+
The logger instance for logging information and errors.
605642
606643
Returns:
607-
subprocess.CompletedProcess: Completed process information.
644+
subprocess.CompletedProcess: The completed process information.
608645
646+
Raises:
647+
SystemExit: If the command fails to execute or an error occurs.
609648
"""
610649
logger.info(f'Running: {" ".join(cmd)}')
611-
process = subprocess.run(
612-
cmd,
613-
stdout=subprocess.DEVNULL,
614-
stderr=None if verbose else subprocess.PIPE,
615-
text=True,
616-
)
617-
if process.returncode:
618-
logger.error(f'While running: {" ".join(process.args)}')
619-
logger.error(f'Error message: {process.stderr}')
650+
try:
651+
process = subprocess.run(
652+
cmd,
653+
stdout=subprocess.DEVNULL,
654+
stderr=None if verbose else subprocess.PIPE,
655+
text=True,
656+
check=True
657+
)
658+
except subprocess.CalledProcessError as e:
659+
logger.error(f'Process {" ".join(cmd)} failed with message: {e.stderr}')
660+
exit(1)
661+
except OSError as e:
662+
logger.error(f'OSError: {" ".join(cmd)} failed with message: {e}')
620663
exit(1)
664+
except Exception as e:
665+
logger.error(f'Unexpected: {" ".join(cmd)} failed with message: {e}')
666+
exit(1)
621667
logger.info(f'Done')
622668
return process
623669

0 commit comments

Comments
 (0)