170 lines
6.4 KiB
Python
170 lines
6.4 KiB
Python
from pathlib import Path
|
|||
|
|
|
||
|
|
from openpyxl import Workbook
|
||
|
|
|
||
|
|
from apps.cli import run_identify as identify
|
||
|
|
|
||
|
|
|
||
|
|
def _write_config(path: Path, rows: list[list[str]]) -> None:
|
||
|
|
wb = Workbook()
|
||
|
|
ws = wb.active
|
||
|
|
ws.title = "config"
|
||
|
|
ws.append([
|
||
|
|
"Original_File_Name",
|
||
|
|
"Species_Code",
|
||
|
|
"Species_Name",
|
||
|
|
"Gene_Family",
|
||
|
|
"File_Type",
|
||
|
|
"Ref",
|
||
|
|
"Species_Role",
|
||
|
|
"File_Scope",
|
||
|
|
"Reference_Use",
|
||
|
|
])
|
||
|
|
for row in rows:
|
||
|
|
ws.append(row)
|
||
|
|
wb.save(path)
|
||
|
|
|
||
|
|
|
||
|
|
def test_collect_reference_files_ignores_family_pep_filename_without_config(tmp_path):
|
||
|
|
ref = tmp_path / "At.SPL.pep.fasta"
|
||
|
|
ref.write_text(">AtSPL1\nMAAA\n", encoding="utf-8")
|
||
|
|
|
||
|
|
references = identify.collect_reference_files(tmp_path, gene_family="SPL")
|
||
|
|
|
||
|
|
assert references == []
|
||
|
|
|
||
|
|
|
||
|
|
def test_collect_reference_files_uses_config_family_pep_for_identify_blast(tmp_path):
|
||
|
|
ref = tmp_path / "At.SPL.pep.fasta"
|
||
|
|
ref.write_text(">AtSPL1\nMAAA\n", encoding="utf-8")
|
||
|
|
_write_config(
|
||
|
|
tmp_path / "config.xlsx",
|
||
|
|
[[
|
||
|
|
"At.SPL.pep.fasta",
|
||
|
|
"At",
|
||
|
|
"Arabidopsis thaliana",
|
||
|
|
"SPL",
|
||
|
|
"family_pep",
|
||
|
|
"TRUE",
|
||
|
|
"seed_reference",
|
||
|
|
"known_family",
|
||
|
|
"identify_blast",
|
||
|
|
]],
|
||
|
|
)
|
||
|
|
|
||
|
|
references = identify.collect_reference_files(tmp_path, gene_family="SPL")
|
||
|
|
|
||
|
|
assert len(references) == 1
|
||
|
|
assert references[0]["species"] == "At"
|
||
|
|
assert references[0]["source"] == "config.xlsx"
|
||
|
|
assert Path(references[0]["path"]) == ref.resolve()
|
||
|
|
|
||
|
|
|
||
|
|
def test_parse_blast_outfmt6_uses_sseqid_for_target_candidates(tmp_path):
|
||
|
|
blast_out = tmp_path / "Pm.blast.At.out"
|
||
|
|
blast_out.write_text(
|
||
|
|
"AtNAC001\tPmNAC001\t45.0\t100\t0\t0\t1\t100\t5\t104\t1e-40\t180\t120\t130\n"
|
||
|
|
"AtNAC002\tPmNAC002\t31.0\t90\t0\t0\t1\t90\t10\t99\t1e-06\t90\t110\t120\n"
|
||
|
|
"AtNAC003\tPmNAC003\t30.0\t90\t0\t0\t1\t90\t10\t99\t1e-20\t100\t110\t120\n"
|
||
|
|
"AtNAC004\tPmNAC004\t55.0\t90\t0\t0\t1\t90\t10\t99\t1e-03\t100\t110\t120\n",
|
||
|
|
encoding="utf-8",
|
||
|
|
)
|
||
|
|
|
||
|
|
ids, evidence = identify.parse_blast_outfmt6_target_hits(blast_out, evalue=1e-5)
|
||
|
|
|
||
|
|
assert ids == {"PmNAC001", "PmNAC002"}
|
||
|
|
assert "AtNAC001" not in ids
|
||
|
|
assert evidence["PmNAC001"]["query_ids"] == ["AtNAC001"]
|
||
|
|
assert evidence["PmNAC001"]["best_evalue"] == 1e-40
|
||
|
|
|
||
|
|
|
||
|
|
def test_multi_reference_blast_unions_target_sseqids_and_query_support(monkeypatch, tmp_path):
|
||
|
|
output_dir = tmp_path / "identify" / "Pm"
|
||
|
|
analysis_dir = output_dir / "analysis"
|
||
|
|
analysis_dir.mkdir(parents=True)
|
||
|
|
pep = tmp_path / "Pm.pep.fasta"
|
||
|
|
pep.write_text(">PmNAC001\nMAAA\n>PmNAC002\nMBBB\n", encoding="utf-8")
|
||
|
|
at_ref = tmp_path / "At.NAC.pep.fasta"
|
||
|
|
os_ref = tmp_path / "Os.NAC.pep.fasta"
|
||
|
|
at_ref.write_text(">AtNAC001\nMAAA\n", encoding="utf-8")
|
||
|
|
os_ref.write_text(">OsNAC009\nMBBB\n", encoding="utf-8")
|
||
|
|
|
||
|
|
monkeypatch.setattr(identify, "cleanup_current_blast_intermediates", lambda analysis_dir, prefix: None)
|
||
|
|
monkeypatch.setattr(
|
||
|
|
identify,
|
||
|
|
"build_target_proteome_blast_db",
|
||
|
|
lambda analysis_dir, pep_file: {
|
||
|
|
"status": "success",
|
||
|
|
"target_database_input": str(pep_file.resolve()),
|
||
|
|
"target_database_role": "target_proteome",
|
||
|
|
"target_db_prefix": str((analysis_dir / "blast_db.target_proteome").resolve()),
|
||
|
|
"target_database_files": [str((analysis_dir / "blast_db.target_proteome.pin").resolve())],
|
||
|
|
},
|
||
|
|
)
|
||
|
|
|
||
|
|
def fake_run_blast_analysis(**kwargs):
|
||
|
|
label = kwargs["reference_label"]
|
||
|
|
if label == "At":
|
||
|
|
return {
|
||
|
|
"status": "success",
|
||
|
|
"reference_label": "At",
|
||
|
|
"query_file": str(at_ref.resolve()),
|
||
|
|
"query_role": "reference_family_pep",
|
||
|
|
"target_database_input": str(pep.resolve()),
|
||
|
|
"target_database_role": "target_proteome",
|
||
|
|
"target_db_prefix": str((analysis_dir / "blast_db.target_proteome").resolve()),
|
||
|
|
"target_database_files": [],
|
||
|
|
"candidate_id_column": "sseqid",
|
||
|
|
"blast_output": str((analysis_dir / "Pm.blast.At.out").resolve()),
|
||
|
|
"ids_file": str((analysis_dir / "Pm.blast.At.ids").resolve()),
|
||
|
|
"members": 1,
|
||
|
|
"ids": ["PmNAC001"],
|
||
|
|
"evidence_by_gene": {"PmNAC001": {"query_ids": ["AtNAC001"], "best_evalue": 1e-40, "best_bitscore": 180.0}},
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
"status": "success",
|
||
|
|
"reference_label": "Os",
|
||
|
|
"query_file": str(os_ref.resolve()),
|
||
|
|
"query_role": "reference_family_pep",
|
||
|
|
"target_database_input": str(pep.resolve()),
|
||
|
|
"target_database_role": "target_proteome",
|
||
|
|
"target_db_prefix": str((analysis_dir / "blast_db.target_proteome").resolve()),
|
||
|
|
"target_database_files": [],
|
||
|
|
"candidate_id_column": "sseqid",
|
||
|
|
"blast_output": str((analysis_dir / "Pm.blast.Os.out").resolve()),
|
||
|
|
"ids_file": str((analysis_dir / "Pm.blast.Os.ids").resolve()),
|
||
|
|
"members": 2,
|
||
|
|
"ids": ["PmNAC001", "PmNAC002"],
|
||
|
|
"evidence_by_gene": {
|
||
|
|
"PmNAC001": {"query_ids": ["OsNAC009"], "best_evalue": 1e-20, "best_bitscore": 140.0},
|
||
|
|
"PmNAC002": {"query_ids": ["OsNAC010"], "best_evalue": 1e-25, "best_bitscore": 150.0},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
monkeypatch.setattr(identify, "run_blast_analysis", fake_run_blast_analysis)
|
||
|
|
|
||
|
|
result = identify.run_multi_reference_blast_analysis(
|
||
|
|
input_dir=tmp_path,
|
||
|
|
output_dir=output_dir,
|
||
|
|
prefix="Pm",
|
||
|
|
references=[{"label": "At", "path": str(at_ref)}, {"label": "Os", "path": str(os_ref)}],
|
||
|
|
pep_file=pep,
|
||
|
|
evalue=1e-5,
|
||
|
|
)
|
||
|
|
|
||
|
|
assert Path(result["ids_file"]).read_text(encoding="utf-8").splitlines() == ["PmNAC001", "PmNAC002"]
|
||
|
|
matrix = Path(result["source_matrix"]).read_text(encoding="utf-8").splitlines()
|
||
|
|
assert matrix[0].split("\t") == [
|
||
|
|
"gene_id",
|
||
|
|
"blast_reference_sources",
|
||
|
|
"blast_reference_count",
|
||
|
|
"blast_query_ids",
|
||
|
|
"best_evalue",
|
||
|
|
"best_bitscore",
|
||
|
|
]
|
||
|
|
assert "PmNAC001\tAt;Os\t2\tAtNAC001;OsNAC009\t1e-40\t180.0" in matrix
|
||
|
|
assert "PmNAC002\tOs\t1\tOsNAC010\t1e-25\t150.0" in matrix
|
||
|
|
assert result["blast_database_role"] == "target_proteome"
|
||
|
|
assert result["blast_query_role"] == "reference_family_pep"
|
||
|
|
assert result["blast_candidate_id_column"] == "sseqid"
|