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/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
+ )
@@ -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