uht-tooling 0.1.9__py3-none-any.whl → 0.3.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.
- uht_tooling/cli.py +153 -4
- uht_tooling/config.py +137 -0
- uht_tooling/tools.py +143 -0
- uht_tooling/workflows/gui.py +19 -0
- uht_tooling/workflows/mut_rate.py +484 -124
- uht_tooling/workflows/mutation_caller.py +11 -2
- uht_tooling/workflows/umi_hunter.py +9 -4
- {uht_tooling-0.1.9.dist-info → uht_tooling-0.3.0.dist-info}/METADATA +123 -5
- uht_tooling-0.3.0.dist-info/RECORD +20 -0
- uht_tooling-0.1.9.dist-info/RECORD +0 -18
- {uht_tooling-0.1.9.dist-info → uht_tooling-0.3.0.dist-info}/WHEEL +0 -0
- {uht_tooling-0.1.9.dist-info → uht_tooling-0.3.0.dist-info}/entry_points.txt +0 -0
- {uht_tooling-0.1.9.dist-info → uht_tooling-0.3.0.dist-info}/top_level.txt +0 -0
uht_tooling/tools.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""External tool validation utilities."""
|
|
2
|
+
import shutil
|
|
3
|
+
import subprocess
|
|
4
|
+
from typing import Dict, List, Optional, Tuple
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ToolNotFoundError(Exception):
|
|
8
|
+
"""Raised when a required external tool is not found."""
|
|
9
|
+
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def check_tool_available(tool_name: str) -> Tuple[bool, Optional[str]]:
|
|
14
|
+
"""
|
|
15
|
+
Check if a tool is available on PATH.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
tool_name: Name of the executable to check.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Tuple of (available, version_or_error).
|
|
22
|
+
If available is True, version_or_error contains the version string.
|
|
23
|
+
If available is False, version_or_error contains the error message.
|
|
24
|
+
"""
|
|
25
|
+
path = shutil.which(tool_name)
|
|
26
|
+
if not path:
|
|
27
|
+
return False, f"'{tool_name}' not found on PATH"
|
|
28
|
+
|
|
29
|
+
# Try to get version
|
|
30
|
+
try:
|
|
31
|
+
result = subprocess.run(
|
|
32
|
+
[tool_name, "--version"],
|
|
33
|
+
capture_output=True,
|
|
34
|
+
text=True,
|
|
35
|
+
timeout=5,
|
|
36
|
+
)
|
|
37
|
+
version = (result.stdout or result.stderr).strip().split("\n")[0]
|
|
38
|
+
return True, version
|
|
39
|
+
except subprocess.TimeoutExpired:
|
|
40
|
+
return True, "version unknown (timeout)"
|
|
41
|
+
except Exception:
|
|
42
|
+
return True, "version unknown"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def validate_tools(tools: List[str], raise_on_missing: bool = True) -> Dict[str, dict]:
|
|
46
|
+
"""
|
|
47
|
+
Validate multiple tools, optionally raising ToolNotFoundError.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
tools: List of tool names to validate.
|
|
51
|
+
raise_on_missing: If True, raise ToolNotFoundError for missing tools.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Dictionary mapping tool names to their status:
|
|
55
|
+
{
|
|
56
|
+
"tool_name": {
|
|
57
|
+
"available": bool,
|
|
58
|
+
"version": str or None,
|
|
59
|
+
"error": str or None
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
ToolNotFoundError: If raise_on_missing is True and any tool is missing.
|
|
65
|
+
"""
|
|
66
|
+
results: Dict[str, dict] = {}
|
|
67
|
+
missing: List[str] = []
|
|
68
|
+
|
|
69
|
+
for tool in tools:
|
|
70
|
+
available, info = check_tool_available(tool)
|
|
71
|
+
if available:
|
|
72
|
+
results[tool] = {
|
|
73
|
+
"available": True,
|
|
74
|
+
"version": info,
|
|
75
|
+
"error": None,
|
|
76
|
+
}
|
|
77
|
+
else:
|
|
78
|
+
results[tool] = {
|
|
79
|
+
"available": False,
|
|
80
|
+
"version": None,
|
|
81
|
+
"error": info,
|
|
82
|
+
}
|
|
83
|
+
missing.append(tool)
|
|
84
|
+
|
|
85
|
+
if raise_on_missing and missing:
|
|
86
|
+
missing_str = ", ".join(missing)
|
|
87
|
+
raise ToolNotFoundError(
|
|
88
|
+
f"Missing required external tool(s): {missing_str}. "
|
|
89
|
+
f"Install via conda: conda install -c bioconda {' '.join(missing)}"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return results
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# Tool requirements per workflow
|
|
96
|
+
WORKFLOW_TOOLS: Dict[str, List[str]] = {
|
|
97
|
+
"mutation_caller": ["mafft"],
|
|
98
|
+
"umi_hunter": ["mafft"],
|
|
99
|
+
"ep_library_profile": ["minimap2", "NanoFilt"],
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def validate_workflow_tools(workflow: str, raise_on_missing: bool = True) -> Dict[str, dict]:
|
|
104
|
+
"""
|
|
105
|
+
Validate tools required for a specific workflow.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
workflow: Name of the workflow (e.g., "mutation_caller", "umi_hunter").
|
|
109
|
+
raise_on_missing: If True, raise ToolNotFoundError for missing tools.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Dictionary mapping tool names to their status.
|
|
113
|
+
|
|
114
|
+
Raises:
|
|
115
|
+
ValueError: If workflow is not recognized.
|
|
116
|
+
ToolNotFoundError: If raise_on_missing is True and any tool is missing.
|
|
117
|
+
"""
|
|
118
|
+
if workflow not in WORKFLOW_TOOLS:
|
|
119
|
+
# No external tools required for this workflow
|
|
120
|
+
return {}
|
|
121
|
+
|
|
122
|
+
tools = WORKFLOW_TOOLS[workflow]
|
|
123
|
+
return validate_tools(tools, raise_on_missing=raise_on_missing)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def get_tool_requirements_message(workflow: str) -> str:
|
|
127
|
+
"""
|
|
128
|
+
Get a human-readable message about tool requirements for a workflow.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
workflow: Name of the workflow.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
A message describing required tools, or empty string if none required.
|
|
135
|
+
"""
|
|
136
|
+
if workflow not in WORKFLOW_TOOLS:
|
|
137
|
+
return ""
|
|
138
|
+
|
|
139
|
+
tools = WORKFLOW_TOOLS[workflow]
|
|
140
|
+
return (
|
|
141
|
+
f"This workflow requires: {', '.join(tools)}. "
|
|
142
|
+
f"Install via: conda install -c bioconda {' '.join(tools)}"
|
|
143
|
+
)
|
uht_tooling/workflows/gui.py
CHANGED
|
@@ -21,6 +21,7 @@ except ImportError as exc: # pragma: no cover - handled at runtime
|
|
|
21
21
|
f"{exc}. Install optional GUI extras via 'pip install gradio pandas'."
|
|
22
22
|
) from exc
|
|
23
23
|
|
|
24
|
+
from uht_tooling.tools import ToolNotFoundError, validate_workflow_tools
|
|
24
25
|
from uht_tooling.workflows.design_gibson import run_design_gibson
|
|
25
26
|
from uht_tooling.workflows.design_kld import run_design_kld
|
|
26
27
|
from uht_tooling.workflows.design_slim import run_design_slim
|
|
@@ -291,6 +292,12 @@ def run_gui_mutation_caller(
|
|
|
291
292
|
config_dir: Optional[Path] = None
|
|
292
293
|
output_dir: Optional[Path] = None
|
|
293
294
|
try:
|
|
295
|
+
# Validate required external tools
|
|
296
|
+
try:
|
|
297
|
+
validate_workflow_tools("mutation_caller")
|
|
298
|
+
except ToolNotFoundError as e:
|
|
299
|
+
return f"Missing required tools: {e}", None
|
|
300
|
+
|
|
294
301
|
if not fastq_file or not template_file:
|
|
295
302
|
raise ValueError("Upload a FASTQ(.gz) read file and the reference template FASTA.")
|
|
296
303
|
|
|
@@ -367,6 +374,12 @@ def run_gui_umi_hunter(
|
|
|
367
374
|
config_dir: Optional[Path] = None
|
|
368
375
|
output_dir: Optional[Path] = None
|
|
369
376
|
try:
|
|
377
|
+
# Validate required external tools
|
|
378
|
+
try:
|
|
379
|
+
validate_workflow_tools("umi_hunter")
|
|
380
|
+
except ToolNotFoundError as e:
|
|
381
|
+
return f"Missing required tools: {e}", None
|
|
382
|
+
|
|
370
383
|
if not fastq_file or not template_file:
|
|
371
384
|
raise ValueError("Upload a FASTQ(.gz) read file and the template FASTA.")
|
|
372
385
|
|
|
@@ -536,6 +549,12 @@ def run_gui_ep_library_profile(
|
|
|
536
549
|
plasmid_fasta: Optional[str],
|
|
537
550
|
) -> Tuple[str, Optional[str]]:
|
|
538
551
|
try:
|
|
552
|
+
# Validate required external tools
|
|
553
|
+
try:
|
|
554
|
+
validate_workflow_tools("ep_library_profile")
|
|
555
|
+
except ToolNotFoundError as e:
|
|
556
|
+
return f"Missing required tools: {e}", None
|
|
557
|
+
|
|
539
558
|
if not fastq_files or not region_fasta or not plasmid_fasta:
|
|
540
559
|
raise ValueError("Upload FASTQ(.gz) files plus region-of-interest and plasmid FASTA files.")
|
|
541
560
|
|