182 lines
5.2 KiB
Python
182 lines
5.2 KiB
Python
from pathlib import Path
|
|||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from gfa_core.id_resolution import (
|
||
|
|
IdResolutionError,
|
||
|
|
ReferenceAliasIndex,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
LEGACY_COLUMNS = (
|
||
|
|
"species_code",
|
||
|
|
"protein_id",
|
||
|
|
"pep_id",
|
||
|
|
"cds_id",
|
||
|
|
"gene_id",
|
||
|
|
"cds_header",
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def write_alias_map(
|
||
|
|
tmp_path: Path,
|
||
|
|
rows: list[dict[str, str]],
|
||
|
|
*,
|
||
|
|
include_ncbi_gene_id: bool = False,
|
||
|
|
) -> Path:
|
||
|
|
path = tmp_path / "reference.pep_cds_idmap.tsv"
|
||
|
|
columns = [*LEGACY_COLUMNS]
|
||
|
|
if include_ncbi_gene_id:
|
||
|
|
columns.append("ncbi_gene_id")
|
||
|
|
lines = ["\t".join(columns)]
|
||
|
|
for row in rows:
|
||
|
|
lines.append("\t".join(row.get(column, "") for column in columns))
|
||
|
|
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||
|
|
return path
|
||
|
|
|
||
|
|
|
||
|
|
def legacy_row(
|
||
|
|
*,
|
||
|
|
species: str = "soe",
|
||
|
|
accession: str = "XP_021867507.1",
|
||
|
|
gene_symbol: str = "LOC110806180",
|
||
|
|
gene_id: str = "110806180",
|
||
|
|
) -> dict[str, str]:
|
||
|
|
return {
|
||
|
|
"species_code": species,
|
||
|
|
"protein_id": accession,
|
||
|
|
"pep_id": accession,
|
||
|
|
"cds_id": accession,
|
||
|
|
"gene_id": gene_symbol,
|
||
|
|
"cds_header": (
|
||
|
|
f"{accession} [gene={gene_symbol}] "
|
||
|
|
f"[db_xref=GeneID:{gene_id}]"
|
||
|
|
),
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def test_legacy_header_returns_versioned_accession_geneid_and_loc_for_versionless_lookup(
|
||
|
|
tmp_path: Path,
|
||
|
|
):
|
||
|
|
path = write_alias_map(tmp_path, [legacy_row()])
|
||
|
|
|
||
|
|
index = ReferenceAliasIndex.from_tsv(path, species="soe")
|
||
|
|
candidates = index.candidates("XP_021867507")
|
||
|
|
|
||
|
|
assert [candidate.value for candidate in candidates] == [
|
||
|
|
"XP_021867507.1",
|
||
|
|
"110806180",
|
||
|
|
"LOC110806180",
|
||
|
|
]
|
||
|
|
assert all(candidate.namespace for candidate in candidates)
|
||
|
|
assert all(candidate.method for candidate in candidates)
|
||
|
|
assert all(candidate.evidence_source == str(path) for candidate in candidates)
|
||
|
|
|
||
|
|
|
||
|
|
def test_declared_species_mismatch_invalidates_file(tmp_path: Path):
|
||
|
|
path = write_alias_map(tmp_path, [legacy_row(species="ath")])
|
||
|
|
|
||
|
|
with pytest.raises(IdResolutionError, match="species") as exc_info:
|
||
|
|
ReferenceAliasIndex.from_tsv(path, species="soe")
|
||
|
|
|
||
|
|
assert exc_info.value.code == "species_mismatch"
|
||
|
|
assert exc_info.value.details["declared_species"] == "soe"
|
||
|
|
assert exc_info.value.details["observed_species"] == "ath"
|
||
|
|
|
||
|
|
|
||
|
|
def test_versionless_collision_with_conflicting_geneids_is_ambiguous(tmp_path: Path):
|
||
|
|
path = write_alias_map(
|
||
|
|
tmp_path,
|
||
|
|
[
|
||
|
|
legacy_row(
|
||
|
|
accession="XP_021867507.1",
|
||
|
|
gene_symbol="LOC110806180",
|
||
|
|
gene_id="110806180",
|
||
|
|
),
|
||
|
|
legacy_row(
|
||
|
|
accession="XP_021867507.2",
|
||
|
|
gene_symbol="LOC999999999",
|
||
|
|
gene_id="999999999",
|
||
|
|
),
|
||
|
|
],
|
||
|
|
)
|
||
|
|
index = ReferenceAliasIndex.from_tsv(path, species="soe")
|
||
|
|
|
||
|
|
assert index.candidates("XP_021867507.1")[0].value == "XP_021867507.1"
|
||
|
|
assert index.candidates("XP_021867507.2")[0].value == "XP_021867507.2"
|
||
|
|
with pytest.raises(IdResolutionError, match="ambiguous") as exc_info:
|
||
|
|
index.candidates("XP_021867507")
|
||
|
|
|
||
|
|
assert exc_info.value.code == "ambiguous_versionless"
|
||
|
|
assert exc_info.value.details["raw_id"] == "XP_021867507"
|
||
|
|
|
||
|
|
|
||
|
|
def test_exact_unversioned_key_does_not_bypass_conflicting_versioned_rows(
|
||
|
|
tmp_path: Path,
|
||
|
|
):
|
||
|
|
path = write_alias_map(
|
||
|
|
tmp_path,
|
||
|
|
[
|
||
|
|
legacy_row(
|
||
|
|
accession="XP_021867507",
|
||
|
|
gene_symbol="LOC110806180",
|
||
|
|
gene_id="110806180",
|
||
|
|
),
|
||
|
|
legacy_row(
|
||
|
|
accession="XP_021867507.1",
|
||
|
|
gene_symbol="LOC110806180",
|
||
|
|
gene_id="110806180",
|
||
|
|
),
|
||
|
|
legacy_row(
|
||
|
|
accession="XP_021867507.2",
|
||
|
|
gene_symbol="LOC999999999",
|
||
|
|
gene_id="999999999",
|
||
|
|
),
|
||
|
|
],
|
||
|
|
)
|
||
|
|
index = ReferenceAliasIndex.from_tsv(path, species="soe")
|
||
|
|
|
||
|
|
with pytest.raises(IdResolutionError, match="ambiguous") as exc_info:
|
||
|
|
index.candidates("XP_021867507")
|
||
|
|
|
||
|
|
assert exc_info.value.code == "ambiguous_versionless"
|
||
|
|
assert exc_info.value.details["matching_accessions"] == [
|
||
|
|
"XP_021867507",
|
||
|
|
"XP_021867507.1",
|
||
|
|
"XP_021867507.2",
|
||
|
|
]
|
||
|
|
|
||
|
|
|
||
|
|
def test_duplicate_accession_rows_with_same_geneid_deduplicate(tmp_path: Path):
|
||
|
|
row = legacy_row()
|
||
|
|
path = write_alias_map(tmp_path, [row, dict(row)])
|
||
|
|
|
||
|
|
index = ReferenceAliasIndex.from_tsv(path, species="soe")
|
||
|
|
|
||
|
|
assert [candidate.value for candidate in index.candidates("XP_021867507.1")] == [
|
||
|
|
"XP_021867507.1",
|
||
|
|
"110806180",
|
||
|
|
"LOC110806180",
|
||
|
|
]
|
||
|
|
assert [candidate.value for candidate in index.candidates("XP_021867507")] == [
|
||
|
|
"XP_021867507.1",
|
||
|
|
"110806180",
|
||
|
|
"LOC110806180",
|
||
|
|
]
|
||
|
|
|
||
|
|
|
||
|
|
def test_explicit_ncbi_gene_id_column_is_consumed(tmp_path: Path):
|
||
|
|
row = legacy_row()
|
||
|
|
row["cds_header"] = "XP_021867507.1 [gene=LOC110806180]"
|
||
|
|
row["ncbi_gene_id"] = "110806180"
|
||
|
|
path = write_alias_map(tmp_path, [row], include_ncbi_gene_id=True)
|
||
|
|
|
||
|
|
index = ReferenceAliasIndex.from_tsv(path, species="soe")
|
||
|
|
|
||
|
|
assert [candidate.value for candidate in index.candidates("XP_021867507.1")] == [
|
||
|
|
"XP_021867507.1",
|
||
|
|
"110806180",
|
||
|
|
"LOC110806180",
|
||
|
|
]
|
||
|
|
|