docs: update README and add pixi-based tests

- Add property-based tests for PixiRunner
- Add HAN055.fna test data file
- Update README with pixi installation and usage guide
- Update .gitignore for pixi and test artifacts
- Update CLI to remove Docker-related arguments
This commit is contained in:
2026-01-08 16:59:17 +08:00
parent ae4c6351d9
commit 8d11216481
6 changed files with 76125 additions and 181 deletions

View File

@@ -1,4 +1,10 @@
#!/usr/bin/env python3
"""Bttoxin Pipeline API (pixi-based).
This module provides the API for running the BtToxin pipeline using pixi environments:
- digger environment: BtToxin_Digger with bioconda dependencies
- pipeline environment: Python analysis with pandas/matplotlib/seaborn
"""
from __future__ import annotations
import logging
@@ -10,16 +16,15 @@ from types import SimpleNamespace
from typing import Dict, Any, Optional
import sys as _sys
# Ensure repo-relative imports for backend and scripts when running from installed package
# Ensure repo-relative imports for scripts when running from installed package
_REPO_ROOT = Path(__file__).resolve().parents[1]
_BACKEND_DIR = _REPO_ROOT / "backend"
_SCRIPTS_DIR = _REPO_ROOT / "scripts"
for _p in (str(_BACKEND_DIR), str(_SCRIPTS_DIR)):
for _p in (str(_SCRIPTS_DIR),):
if _p not in _sys.path:
_sys.path.append(_p)
# Import DockerContainerManager from backend
from app.utils.docker_client import DockerContainerManager # type: ignore
# Import PixiRunner from scripts
from pixi_runner import PixiRunner # type: ignore
logger = logging.getLogger(__name__)
@@ -45,21 +50,19 @@ def _lazy_import_plotter():
class BtToxinRunner:
"""Wrap BtToxin_Digger docker invocation for a single FNA."""
"""Wrap BtToxin_Digger pixi invocation for a single FNA."""
def __init__(
self,
image: str = "quay.io/biocontainers/bttoxin_digger:1.0.10--hdfd78af_0",
platform: str = "linux/amd64",
base_workdir: Optional[Path] = None,
bttoxin_db_dir: Optional[Path] = None,
) -> None:
self.image = image
self.platform = platform
if base_workdir is None:
base_workdir = _REPO_ROOT / "runs" / "bttoxin"
self.base_workdir = base_workdir
self.base_workdir.mkdir(parents=True, exist_ok=True)
self.mgr = DockerContainerManager(image=self.image, platform=self.platform)
self.bttoxin_db_dir = bttoxin_db_dir
self.runner = PixiRunner(pixi_project_dir=_REPO_ROOT, env_name="digger")
def _prepare_layout(self, fna_path: Path) -> tuple[Path, Path, Path, Path, str]:
if not fna_path.exists():
@@ -86,13 +89,14 @@ class BtToxinRunner:
fna_path = Path(fna_path)
input_dir, digger_out, log_dir, run_root, sample_name = self._prepare_layout(fna_path)
logger.info("Start BtToxin_Digger: %s (sample=%s)", fna_path, sample_name)
result = self.mgr.run_bttoxin_digger(
result = self.runner.run_bttoxin_digger(
input_dir=input_dir,
output_dir=digger_out,
log_dir=log_dir,
sequence_type=sequence_type,
scaf_suffix=fna_path.suffix or ".fna",
threads=threads,
bttoxin_db_dir=self.bttoxin_db_dir,
)
toxins_dir = digger_out / "Results" / "Toxins"
files = {
@@ -234,15 +238,13 @@ class PlotAPI:
class BtSingleFnaPipeline:
"""End-to-end single-FNA pipeline: Digger → Shotter → Plot → Bundle."""
"""End-to-end single-FNA pipeline: Digger → Shotter → Plot → Bundle (pixi-based)."""
def __init__(
self,
image: str = "quay.io/biocontainers/bttoxin_digger:1.0.10--hdfd78af_0",
platform: str = "linux/amd64",
base_workdir: Optional[Path] = None,
) -> None:
self.digger = BtToxinRunner(image=image, platform=platform, base_workdir=base_workdir)
self.base_workdir = base_workdir
self.shotter = ShotterAPI()
self.plotter = PlotAPI()
@@ -256,8 +258,11 @@ class BtSingleFnaPipeline:
require_index_hit: bool = False,
lang: str = "zh",
threads: int = 4,
bttoxin_db_dir: Optional[Path] = None,
) -> Dict[str, Any]:
dig = self.digger.run_single_fna(fna_path=fna, sequence_type="nucl", threads=threads)
# Create digger runner with optional external database
digger = BtToxinRunner(base_workdir=self.base_workdir, bttoxin_db_dir=bttoxin_db_dir)
dig = digger.run_single_fna(fna_path=fna, sequence_type="nucl", threads=threads)
if not dig.get("success"):
return {"ok": False, "stage": "digger", "detail": dig}
run_root: Path = dig["run_root"]