13 Commits
0.7.1 ... 0.9.0

Author SHA1 Message Date
bfc286e6b0 Updated test cases to reflect changes in codebase
MLSTProfile will always return a value, even if there were no exact matches.

Removed a test case specifically testing for stopping on failure, which is a removed feature.
2025-02-12 14:57:51 +00:00
a88225fcff Added check to wrap string into list to prevent decomposing string for querying 2025-02-12 14:46:29 +00:00
c18d817cd9 Added test to verify that CSV target columns are ordered 2025-02-12 14:38:12 +00:00
f462e6d5e0 Moved "LazyPersistentCachedBIGSdbMLSTProfiler" to separate branch and deleted from current branch 2025-02-11 19:24:23 +00:00
f75707e4fe CSV output column order is now predictable (sorted) 2025-02-11 17:54:48 +00:00
341ca933a3 Fixed typo in CI script 2025-01-29 17:00:25 +00:00
3e3898334f Began implementing LazyPersistentCachedBIGSdbMLSTProfiler 2025-01-27 22:03:49 +00:00
ba1f0aa318 Fixed potential memory leak 2025-01-27 22:02:52 +00:00
6d0157581f Removed conda environment step for now 2025-01-24 21:43:55 +00:00
4bcbfa0c6a Began adding conda steps for automatic PRs to Bioconda 2025-01-24 19:33:27 +00:00
ca0f9673b0 Upgraded Python version requirement due to use of f-strings 2025-01-24 17:00:57 +00:00
5048fa8057 Deleted uneeded file 2025-01-23 19:23:56 +00:00
39125c848e Added a wildcard patch specifier for aiohttp 2025-01-23 19:23:42 +00:00
7 changed files with 153 additions and 110 deletions

View File

@@ -9,13 +9,14 @@ readme = "README.md"
dependencies = [ dependencies = [
"biopython==1.85", "biopython==1.85",
"aiohttp[speedups]==3.11", "aiohttp[speedups]==3.11.*",
] ]
requires-python = ">=3.11" requires-python = ">=3.12"
description = "A library to rapidly fetch fetch MLST profiles given sequences for various diseases." description = "A library to rapidly fetch fetch MLST profiles given sequences for various diseases."
[project.urls] [project.urls]
Repository = "https://github.com/RealYHD/autoBIGS.engine" Homepage = "https://github.com/RealYHD/autoBIGS.engine"
Source = "https://github.com/RealYHD/autoBIGS.engine"
Issues = "https://github.com/RealYHD/autoBIGS.engine/issues" Issues = "https://github.com/RealYHD/autoBIGS.engine/issues"
[tool.setuptools_scm] [tool.setuptools_scm]

View File

@@ -1,4 +1,4 @@
aiohttp[speedups]==3.11 aiohttp[speedups]==3.11.*
biopython==1.85 biopython==1.85
pytest pytest
pytest-asyncio pytest-asyncio

View File

@@ -28,14 +28,14 @@ async def write_mlst_profiles_as_csv(mlst_profiles_iterable: AsyncIterable[tuple
failed.append(name) failed.append(name)
continue continue
if writer is None: if writer is None:
header = ["id", "st", "clonal-complex", *mlst_profile.alleles.keys()] header = ["id", "st", "clonal-complex", *sorted(mlst_profile.alleles.keys())]
writer = csv.DictWriter(filehandle, fieldnames=header) writer = csv.DictWriter(filehandle, fieldnames=header)
writer.writeheader() writer.writeheader()
row_dictionary = { row_dictionary = {
"st": mlst_profile.sequence_type, "st": mlst_profile.sequence_type,
"clonal-complex": mlst_profile.clonal_complex, "clonal-complex": mlst_profile.clonal_complex,
"id": name, "id": name,
**dict_loci_alleles_variants_from_loci(mlst_profile.alleles) **mlst_profile.alleles
} }
writer.writerow(rowdict=row_dictionary) writer.writerow(rowdict=row_dictionary)
return failed return failed

View File

@@ -1,16 +1,43 @@
from abc import abstractmethod
from collections import defaultdict from collections import defaultdict
from contextlib import AbstractAsyncContextManager from contextlib import AbstractAsyncContextManager
from numbers import Number import csv
from typing import Any, AsyncGenerator, AsyncIterable, Collection, Generator, Iterable, Mapping, Sequence, Union from os import path
from typing import Any, AsyncGenerator, AsyncIterable, Iterable, Mapping, Sequence, Union
from aiohttp import ClientSession, ClientTimeout from aiohttp import ClientSession, ClientTimeout
from autobigs.engine.data.local.fasta import read_fasta
from autobigs.engine.data.structures.genomics import NamedString from autobigs.engine.data.structures.genomics import NamedString
from autobigs.engine.data.structures.mlst import Allele, PartialAllelicMatchProfile, MLSTProfile from autobigs.engine.data.structures.mlst import Allele, NamedMLSTProfile, PartialAllelicMatchProfile, MLSTProfile
from autobigs.engine.exceptions.database import NoBIGSdbExactMatchesException, NoBIGSdbMatchesException, NoSuchBIGSdbDatabaseException from autobigs.engine.exceptions.database import NoBIGSdbExactMatchesException, NoBIGSdbMatchesException, NoSuchBIGSdbDatabaseException
from Bio.Align import PairwiseAligner
class BIGSdbMLSTProfiler(AbstractAsyncContextManager): class BIGSdbMLSTProfiler(AbstractAsyncContextManager):
@abstractmethod
def fetch_mlst_allele_variants(self, sequence_strings: Iterable[str]) -> AsyncGenerator[Allele, Any]:
pass
@abstractmethod
async def fetch_mlst_st(self, alleles: Union[AsyncIterable[Allele], Iterable[Allele]]) -> MLSTProfile:
pass
@abstractmethod
async def profile_string(self, sequence_strings: Iterable[str]) -> MLSTProfile:
pass
@abstractmethod
def profile_multiple_strings(self, named_string_groups: AsyncIterable[Iterable[NamedString]], stop_on_fail: bool = False) -> AsyncGenerator[NamedMLSTProfile, Any]:
pass
@abstractmethod
async def close(self):
pass
class OnlineBIGSdbMLSTProfiler(BIGSdbMLSTProfiler):
def __init__(self, database_api: str, database_name: str, schema_id: int): def __init__(self, database_api: str, database_name: str, schema_id: int):
self._database_name = database_name self._database_name = database_name
self._schema_id = schema_id self._schema_id = schema_id
@@ -20,13 +47,18 @@ class BIGSdbMLSTProfiler(AbstractAsyncContextManager):
async def __aenter__(self): async def __aenter__(self):
return self return self
async def fetch_mlst_allele_variants(self, sequence_string: str, exact: bool) -> AsyncGenerator[Allele, Any]: async def fetch_mlst_allele_variants(self, sequence_strings: Union[Iterable[str], str]) -> AsyncGenerator[Allele, Any]:
# See https://bigsdb.pasteur.fr/api/db/pubmlst_bordetella_seqdef/schemes # See https://bigsdb.pasteur.fr/api/db/pubmlst_bordetella_seqdef/schemes
uri_path = "sequence" uri_path = "sequence"
response = await self._http_client.post(uri_path, json={
if isinstance(sequence_strings, str):
sequence_strings = [sequence_strings]
for sequence_string in sequence_strings:
async with self._http_client.post(uri_path, json={
"sequence": sequence_string, "sequence": sequence_string,
"partial_matches": not exact "partial_matches": True
}) }) as response:
sequence_response: dict = await response.json() sequence_response: dict = await response.json()
if "exact_matches" in sequence_response: if "exact_matches" in sequence_response:
@@ -35,10 +67,8 @@ class BIGSdbMLSTProfiler(AbstractAsyncContextManager):
for allele_loci, alleles in exact_matches.items(): for allele_loci, alleles in exact_matches.items():
for allele in alleles: for allele in alleles:
alelle_id = allele["allele_id"] alelle_id = allele["allele_id"]
yield Allele(allele_loci=allele_loci, allele_variant=alelle_id, partial_match_profile=None) yield Allele(allele_locus=allele_loci, allele_variant=alelle_id, partial_match_profile=None)
elif "partial_matches" in sequence_response: elif "partial_matches" in sequence_response:
if exact:
raise NoBIGSdbExactMatchesException(self._database_name, self._schema_id)
partial_matches: dict[str, dict[str, Union[str, float, int]]] = sequence_response["partial_matches"] partial_matches: dict[str, dict[str, Union[str, float, int]]] = sequence_response["partial_matches"]
for allele_loci, partial_match in partial_matches.items(): for allele_loci, partial_match in partial_matches.items():
if len(partial_match) <= 0: if len(partial_match) <= 0:
@@ -46,59 +76,57 @@ class BIGSdbMLSTProfiler(AbstractAsyncContextManager):
partial_match_profile = PartialAllelicMatchProfile( partial_match_profile = PartialAllelicMatchProfile(
percent_identity=float(partial_match["identity"]), percent_identity=float(partial_match["identity"]),
mismatches=int(partial_match["mismatches"]), mismatches=int(partial_match["mismatches"]),
bitscore=float(partial_match["bitscore"]),
gaps=int(partial_match["gaps"]) gaps=int(partial_match["gaps"])
) )
yield Allele( yield Allele(
allele_loci=allele_loci, allele_locus=allele_loci,
allele_variant=str(partial_match["allele"]), allele_variant=str(partial_match["allele"]),
partial_match_profile=partial_match_profile partial_match_profile=partial_match_profile
) )
else: else:
raise NoBIGSdbMatchesException(self._database_name, self._schema_id) raise NoBIGSdbMatchesException(self._database_name, self._schema_id)
async def fetch_mlst_st(self, alleles: Union[AsyncIterable[Allele], Iterable[Allele]]) -> MLSTProfile: async def fetch_mlst_st(self, alleles: Union[AsyncIterable[Allele], Iterable[Allele]]) -> MLSTProfile:
uri_path = "designations" uri_path = "designations"
allele_request_dict: dict[str, list[dict[str, str]]] = defaultdict(list) allele_request_dict: dict[str, list[dict[str, str]]] = defaultdict(list)
if isinstance(alleles, AsyncIterable): if isinstance(alleles, AsyncIterable):
async for allele in alleles: async for allele in alleles:
allele_request_dict[allele.allele_loci].append({"allele": str(allele.allele_variant)}) allele_request_dict[allele.allele_locus].append({"allele": str(allele.allele_variant)})
else: else:
for allele in alleles: for allele in alleles:
allele_request_dict[allele.allele_loci].append({"allele": str(allele.allele_variant)}) allele_request_dict[allele.allele_locus].append({"allele": str(allele.allele_variant)})
request_json = { request_json = {
"designations": allele_request_dict "designations": allele_request_dict
} }
async with self._http_client.post(uri_path, json=request_json) as response: async with self._http_client.post(uri_path, json=request_json) as response:
response_json: dict = await response.json() response_json: dict = await response.json()
allele_map: dict[str, list[Allele]] = defaultdict(list) allele_map: dict[str, Allele] = {}
response_json.setdefault("fields", dict()) response_json.setdefault("fields", dict())
schema_fields_returned: dict[str, str] = response_json["fields"] schema_fields_returned: dict[str, str] = response_json["fields"]
schema_fields_returned.setdefault("ST", "unknown") schema_fields_returned.setdefault("ST", "unknown")
schema_fields_returned.setdefault("clonal_complex", "unknown") schema_fields_returned.setdefault("clonal_complex", "unknown")
schema_exact_matches: dict = response_json["exact_matches"] schema_exact_matches: dict = response_json["exact_matches"]
for exact_match_loci, exact_match_alleles in schema_exact_matches.items(): for exact_match_locus, exact_match_alleles in schema_exact_matches.items():
for exact_match_allele in exact_match_alleles: if len(exact_match_alleles) > 1:
allele_map[exact_match_loci].append(Allele(exact_match_loci, exact_match_allele["allele_id"], None)) raise ValueError(f"Unexpected number of alleles returned for exact match (Expected 1, retrieved {len(exact_match_alleles)})")
allele_map[exact_match_locus] = Allele(exact_match_locus, exact_match_alleles[0]["allele_id"], None)
if len(allele_map) == 0: if len(allele_map) == 0:
raise ValueError("Passed in no alleles.") raise ValueError("Passed in no alleles.")
return MLSTProfile(dict(allele_map), schema_fields_returned["ST"], schema_fields_returned["clonal_complex"]) return MLSTProfile(dict(allele_map), schema_fields_returned["ST"], schema_fields_returned["clonal_complex"])
async def profile_string(self, string: str, exact: bool = False) -> MLSTProfile: async def profile_string(self, sequence_strings: Iterable[str]) -> MLSTProfile:
alleles = self.fetch_mlst_allele_variants(string, exact) alleles = self.fetch_mlst_allele_variants(sequence_strings)
return await self.fetch_mlst_st(alleles) return await self.fetch_mlst_st(alleles)
async def profile_multiple_strings(self, named_string_groups: AsyncIterable[Iterable[NamedString]], stop_on_fail: bool = False) -> AsyncGenerator[NamedMLSTProfile, Any]:
async def profile_multiple_strings(self, namedStrings: AsyncIterable[NamedString], exact: bool = False, stop_on_fail: bool = False) -> AsyncGenerator[tuple[str, Union[MLSTProfile, None]], Any]: async for named_strings in named_string_groups:
async for named_string in namedStrings: for named_string in named_strings:
try: try:
yield (named_string.name, await self.profile_string(named_string.sequence, exact)) yield NamedMLSTProfile(named_string.name, (await self.profile_string([named_string.sequence])))
except NoBIGSdbMatchesException as e: except NoBIGSdbMatchesException as e:
if stop_on_fail: if stop_on_fail:
raise e raise e
yield (named_string.name, None) yield NamedMLSTProfile(named_string.name, None)
async def close(self): async def close(self):
await self._http_client.close() await self._http_client.close()
@@ -155,8 +183,8 @@ class BIGSdbIndex(AbstractAsyncContextManager):
self._seqdefdb_schemas[seqdef_db_name] = schema_descriptions self._seqdefdb_schemas[seqdef_db_name] = schema_descriptions
return self._seqdefdb_schemas[seqdef_db_name] # type: ignore return self._seqdefdb_schemas[seqdef_db_name] # type: ignore
async def build_profiler_from_seqdefdb(self, dbseqdef_name: str, schema_id: int) -> BIGSdbMLSTProfiler: async def build_profiler_from_seqdefdb(self, dbseqdef_name: str, schema_id: int) -> OnlineBIGSdbMLSTProfiler:
return BIGSdbMLSTProfiler(await self.get_bigsdb_api_from_seqdefdb(dbseqdef_name), dbseqdef_name, schema_id) return OnlineBIGSdbMLSTProfiler(await self.get_bigsdb_api_from_seqdefdb(dbseqdef_name), dbseqdef_name, schema_id)
async def close(self): async def close(self):
await self._http_client.close() await self._http_client.close()

View File

@@ -5,17 +5,21 @@ from typing import Mapping, Sequence, Union
class PartialAllelicMatchProfile: class PartialAllelicMatchProfile:
percent_identity: float percent_identity: float
mismatches: int mismatches: int
bitscore: float
gaps: int gaps: int
@dataclass(frozen=True) @dataclass(frozen=True)
class Allele: class Allele:
allele_loci: str allele_locus: str
allele_variant: str allele_variant: str
partial_match_profile: Union[None, PartialAllelicMatchProfile] partial_match_profile: Union[None, PartialAllelicMatchProfile]
@dataclass(frozen=True) @dataclass(frozen=True)
class MLSTProfile: class MLSTProfile:
alleles: Mapping[str, Sequence[Allele]] alleles: Mapping[str, Allele]
sequence_type: str sequence_type: str
clonal_complex: str clonal_complex: str
@dataclass(frozen=True)
class NamedMLSTProfile:
name: str
mlst_profile: Union[None, MLSTProfile]

View File

@@ -1,6 +1,13 @@
from autobigs.engine.data.local.csv import dict_loci_alleles_variants_from_loci from typing import AsyncIterable, Iterable
from autobigs.engine.data.structures.mlst import Allele from autobigs.engine.data.local.csv import dict_loci_alleles_variants_from_loci, write_mlst_profiles_as_csv
from autobigs.engine.data.structures.mlst import Allele, MLSTProfile
import tempfile
from csv import reader
from os import path
async def iterable_to_asynciterable(iterable: Iterable):
for iterated in iterable:
yield iterated
def test_dict_loci_alleles_variants_from_loci_single_loci_not_list(): def test_dict_loci_alleles_variants_from_loci_single_loci_not_list():
alleles_map = { alleles_map = {
@@ -19,3 +26,19 @@ def test_dict_loci_alleles_variants_from_loci_multi_loci_is_list():
for loci, variant in results.items(): for loci, variant in results.items():
assert isinstance(variant, list) assert isinstance(variant, list)
assert len(variant) == 2 assert len(variant) == 2
async def test_column_order_is_same_as_expected_file():
dummy_profiles = [("test_1", MLSTProfile({
"A": Allele("A", "1", None),
"D": Allele("D", "1", None),
"B": Allele("B", "1", None),
"C": Allele("C", "1", None)
}, "mysterious", "very mysterious"))]
with tempfile.TemporaryDirectory() as temp_dir:
output_path = path.join(temp_dir, "out.csv")
await write_mlst_profiles_as_csv(iterable_to_asynciterable(dummy_profiles), output_path)
with open(output_path) as csv_handle:
csv_reader = reader(csv_handle)
lines = list(csv_reader)
target_columns = lines[4:]
assert target_columns == sorted(target_columns)

View File

@@ -6,7 +6,7 @@ import pytest
from autobigs.engine.data.structures.genomics import NamedString from autobigs.engine.data.structures.genomics import NamedString
from autobigs.engine.data.structures.mlst import Allele, MLSTProfile from autobigs.engine.data.structures.mlst import Allele, MLSTProfile
from autobigs.engine.exceptions.database import NoBIGSdbExactMatchesException, NoBIGSdbMatchesException from autobigs.engine.exceptions.database import NoBIGSdbExactMatchesException, NoBIGSdbMatchesException
from autobigs.engine.data.remote.databases.bigsdb import BIGSdbIndex, BIGSdbMLSTProfiler from autobigs.engine.data.remote.databases.bigsdb import BIGSdbIndex, OnlineBIGSdbMLSTProfiler
def gene_scrambler(gene: str, mutation_site_count: Union[int, float], alphabet: Sequence[str] = ["A", "T", "C", "G"]): def gene_scrambler(gene: str, mutation_site_count: Union[int, float], alphabet: Sequence[str] = ["A", "T", "C", "G"]):
rand = random.Random(gene) rand = random.Random(gene)
@@ -20,19 +20,19 @@ def gene_scrambler(gene: str, mutation_site_count: Union[int, float], alphabet:
async def test_institutpasteur_profiling_results_in_exact_matches_when_exact(): async def test_institutpasteur_profiling_results_in_exact_matches_when_exact():
sequence = str(SeqIO.read("tests/resources/tohama_I_bpertussis.fasta", "fasta").seq) sequence = str(SeqIO.read("tests/resources/tohama_I_bpertussis.fasta", "fasta").seq)
async with BIGSdbMLSTProfiler(database_api="https://bigsdb.pasteur.fr/api", database_name="pubmlst_bordetella_seqdef", schema_id=3) as dummy_profiler: async with OnlineBIGSdbMLSTProfiler(database_api="https://bigsdb.pasteur.fr/api", database_name="pubmlst_bordetella_seqdef", schema_id=3) as dummy_profiler:
targets_left = {"adk", "fumC", "glyA", "tyrB", "icd", "pepA", "pgm"} targets_left = {"adk", "fumC", "glyA", "tyrB", "icd", "pepA", "pgm"}
async for exact_match in dummy_profiler.fetch_mlst_allele_variants(sequence_string=sequence, exact=True): async for exact_match in dummy_profiler.fetch_mlst_allele_variants(sequence_strings=[sequence]):
assert isinstance(exact_match, Allele) assert isinstance(exact_match, Allele)
assert exact_match.allele_variant == '1' # All of Tohama I has allele id I assert exact_match.allele_variant == '1' # All of Tohama I has allele id I
targets_left.remove(exact_match.allele_loci) targets_left.remove(exact_match.allele_locus)
assert len(targets_left) == 0 assert len(targets_left) == 0
async def test_institutpasteur_sequence_profiling_non_exact_returns_non_exact(): async def test_institutpasteur_sequence_profiling_non_exact_returns_non_exact():
sequences = list(SeqIO.parse("tests/resources/tohama_I_bpertussis_coding.fasta", "fasta")) sequences = list(SeqIO.parse("tests/resources/tohama_I_bpertussis_coding.fasta", "fasta"))
mlst_targets = {"adk", "fumc", "glya", "tyrb", "icd", "pepa", "pgm"} mlst_targets = {"adk", "fumc", "glya", "tyrb", "icd", "pepa", "pgm"}
async with BIGSdbMLSTProfiler(database_api="https://bigsdb.pasteur.fr/api", database_name="pubmlst_bordetella_seqdef", schema_id=3) as profiler: async with OnlineBIGSdbMLSTProfiler(database_api="https://bigsdb.pasteur.fr/api", database_name="pubmlst_bordetella_seqdef", schema_id=3) as profiler:
for sequence in sequences: for sequence in sequences:
match = re.fullmatch(r".*\[gene=([\w\d]+)\].*", sequence.description) match = re.fullmatch(r".*\[gene=([\w\d]+)\].*", sequence.description)
if match is None: if match is None:
@@ -41,7 +41,7 @@ async def test_institutpasteur_sequence_profiling_non_exact_returns_non_exact():
if gene.lower() not in mlst_targets: if gene.lower() not in mlst_targets:
continue continue
scrambled = gene_scrambler(str(sequence.seq), 0.125) scrambled = gene_scrambler(str(sequence.seq), 0.125)
async for partial_match in profiler.fetch_mlst_allele_variants(scrambled, False): async for partial_match in profiler.fetch_mlst_allele_variants(scrambled):
assert partial_match.partial_match_profile is not None assert partial_match.partial_match_profile is not None
mlst_targets.remove(gene.lower()) mlst_targets.remove(gene.lower())
@@ -60,7 +60,7 @@ async def test_institutpasteur_profiling_results_in_correct_mlst_st():
] ]
for dummy_allele in dummy_alleles: for dummy_allele in dummy_alleles:
yield dummy_allele yield dummy_allele
async with BIGSdbMLSTProfiler(database_api="https://bigsdb.pasteur.fr/api", database_name="pubmlst_bordetella_seqdef", schema_id=3) as dummy_profiler: async with OnlineBIGSdbMLSTProfiler(database_api="https://bigsdb.pasteur.fr/api", database_name="pubmlst_bordetella_seqdef", schema_id=3) as dummy_profiler:
mlst_st_data = await dummy_profiler.fetch_mlst_st(dummy_allele_generator()) mlst_st_data = await dummy_profiler.fetch_mlst_st(dummy_allele_generator())
assert mlst_st_data is not None assert mlst_st_data is not None
assert isinstance(mlst_st_data, MLSTProfile) assert isinstance(mlst_st_data, MLSTProfile)
@@ -77,7 +77,7 @@ async def test_institutpasteur_profiling_non_exact_results_in_list_of_mlsts():
Allele("pepA", "1", None), Allele("pepA", "1", None),
Allele("pgm", "5", None), Allele("pgm", "5", None),
] ]
async with BIGSdbMLSTProfiler(database_api="https://bigsdb.pasteur.fr/api", database_name="pubmlst_bordetella_seqdef", schema_id=3) as dummy_profiler: async with OnlineBIGSdbMLSTProfiler(database_api="https://bigsdb.pasteur.fr/api", database_name="pubmlst_bordetella_seqdef", schema_id=3) as dummy_profiler:
mlst_profile = await dummy_profiler.fetch_mlst_st(dummy_alleles) mlst_profile = await dummy_profiler.fetch_mlst_st(dummy_alleles)
assert mlst_profile.clonal_complex == "unknown" assert mlst_profile.clonal_complex == "unknown"
assert mlst_profile.sequence_type == "unknown" assert mlst_profile.sequence_type == "unknown"
@@ -85,7 +85,7 @@ async def test_institutpasteur_profiling_non_exact_results_in_list_of_mlsts():
async def test_institutpasteur_sequence_profiling_is_correct(): async def test_institutpasteur_sequence_profiling_is_correct():
sequence = str(SeqIO.read("tests/resources/tohama_I_bpertussis.fasta", "fasta").seq) sequence = str(SeqIO.read("tests/resources/tohama_I_bpertussis.fasta", "fasta").seq)
async with BIGSdbMLSTProfiler(database_api="https://bigsdb.pasteur.fr/api", database_name="pubmlst_bordetella_seqdef", schema_id=3) as dummy_profiler: async with OnlineBIGSdbMLSTProfiler(database_api="https://bigsdb.pasteur.fr/api", database_name="pubmlst_bordetella_seqdef", schema_id=3) as dummy_profiler:
profile = await dummy_profiler.profile_string(sequence) profile = await dummy_profiler.profile_string(sequence)
assert profile is not None assert profile is not None
assert isinstance(profile, MLSTProfile) assert isinstance(profile, MLSTProfile)
@@ -104,8 +104,8 @@ async def test_pubmlst_profiling_results_in_exact_matches_when_exact():
Allele("recA", "5", None), Allele("recA", "5", None),
} }
sequence = str(SeqIO.read("tests/resources/FDAARGOS_1560.fasta", "fasta").seq) sequence = str(SeqIO.read("tests/resources/FDAARGOS_1560.fasta", "fasta").seq)
async with BIGSdbMLSTProfiler(database_api="https://rest.pubmlst.org/", database_name="pubmlst_hinfluenzae_seqdef", schema_id=1) as dummy_profiler: async with OnlineBIGSdbMLSTProfiler(database_api="https://rest.pubmlst.org/", database_name="pubmlst_hinfluenzae_seqdef", schema_id=1) as dummy_profiler:
exact_matches = dummy_profiler.fetch_mlst_allele_variants(sequence_string=sequence, exact=True) exact_matches = dummy_profiler.fetch_mlst_allele_variants(sequence_strings=sequence)
async for exact_match in exact_matches: async for exact_match in exact_matches:
assert isinstance(exact_match, Allele) assert isinstance(exact_match, Allele)
dummy_alleles.remove(exact_match) dummy_alleles.remove(exact_match)
@@ -125,7 +125,7 @@ async def test_pubmlst_profiling_results_in_correct_st():
] ]
for dummy_allele in dummy_alleles: for dummy_allele in dummy_alleles:
yield dummy_allele yield dummy_allele
async with BIGSdbMLSTProfiler(database_api="https://rest.pubmlst.org/", database_name="pubmlst_hinfluenzae_seqdef", schema_id=1) as dummy_profiler: async with OnlineBIGSdbMLSTProfiler(database_api="https://rest.pubmlst.org/", database_name="pubmlst_hinfluenzae_seqdef", schema_id=1) as dummy_profiler:
mlst_st_data = await dummy_profiler.fetch_mlst_st(generate_dummy_targets()) mlst_st_data = await dummy_profiler.fetch_mlst_st(generate_dummy_targets())
assert mlst_st_data is not None assert mlst_st_data is not None
assert isinstance(mlst_st_data, MLSTProfile) assert isinstance(mlst_st_data, MLSTProfile)
@@ -134,7 +134,7 @@ async def test_pubmlst_profiling_results_in_correct_st():
async def test_pubmlst_sequence_profiling_is_correct(): async def test_pubmlst_sequence_profiling_is_correct():
sequence = str(SeqIO.read("tests/resources/FDAARGOS_1560.fasta", "fasta").seq) sequence = str(SeqIO.read("tests/resources/FDAARGOS_1560.fasta", "fasta").seq)
async with BIGSdbMLSTProfiler(database_api="https://rest.pubmlst.org/", database_name="pubmlst_hinfluenzae_seqdef", schema_id=1) as dummy_profiler: async with OnlineBIGSdbMLSTProfiler(database_api="https://rest.pubmlst.org/", database_name="pubmlst_hinfluenzae_seqdef", schema_id=1) as dummy_profiler:
profile = await dummy_profiler.profile_string(sequence) profile = await dummy_profiler.profile_string(sequence)
assert profile is not None assert profile is not None
assert isinstance(profile, MLSTProfile) assert isinstance(profile, MLSTProfile)
@@ -167,9 +167,10 @@ async def test_bigsdb_profile_multiple_strings_same_string_twice():
dummy_sequences = [NamedString("seq1", sequence), NamedString("seq2", sequence)] dummy_sequences = [NamedString("seq1", sequence), NamedString("seq2", sequence)]
async def generate_async_iterable_sequences(): async def generate_async_iterable_sequences():
for dummy_sequence in dummy_sequences: for dummy_sequence in dummy_sequences:
yield dummy_sequence yield [dummy_sequence]
async with BIGSdbMLSTProfiler(database_api="https://bigsdb.pasteur.fr/api", database_name="pubmlst_bordetella_seqdef", schema_id=3) as dummy_profiler: async with OnlineBIGSdbMLSTProfiler(database_api="https://bigsdb.pasteur.fr/api", database_name="pubmlst_bordetella_seqdef", schema_id=3) as dummy_profiler:
async for name, profile in dummy_profiler.profile_multiple_strings(generate_async_iterable_sequences()): async for named_profile in dummy_profiler.profile_multiple_strings(generate_async_iterable_sequences()):
name, profile = named_profile.name, named_profile.mlst_profile
assert profile is not None assert profile is not None
assert isinstance(profile, MLSTProfile) assert isinstance(profile, MLSTProfile)
assert profile.clonal_complex == "ST-2 complex" assert profile.clonal_complex == "ST-2 complex"
@@ -180,14 +181,17 @@ async def test_bigsdb_profile_multiple_strings_exactmatch_fail_second_no_stop():
dummy_sequences = [NamedString("seq1", valid_seq), NamedString("should_fail", gene_scrambler(valid_seq, 0.3)), NamedString("seq3", valid_seq)] dummy_sequences = [NamedString("seq1", valid_seq), NamedString("should_fail", gene_scrambler(valid_seq, 0.3)), NamedString("seq3", valid_seq)]
async def generate_async_iterable_sequences(): async def generate_async_iterable_sequences():
for dummy_sequence in dummy_sequences: for dummy_sequence in dummy_sequences:
yield dummy_sequence yield [dummy_sequence]
async with BIGSdbMLSTProfiler(database_api="https://bigsdb.pasteur.fr/api", database_name="pubmlst_bordetella_seqdef", schema_id=3) as dummy_profiler: async with OnlineBIGSdbMLSTProfiler(database_api="https://bigsdb.pasteur.fr/api", database_name="pubmlst_bordetella_seqdef", schema_id=3) as dummy_profiler:
async for name, profile in dummy_profiler.profile_multiple_strings(generate_async_iterable_sequences(), True): async for name_profile in dummy_profiler.profile_multiple_strings(generate_async_iterable_sequences(), True):
if name == "should_fail": name, profile = name_profile.name, name_profile.mlst_profile
assert profile is None
else:
assert profile is not None assert profile is not None
assert isinstance(profile, MLSTProfile) assert isinstance(profile, MLSTProfile)
if name == "should_fail":
assert profile.clonal_complex == "unknown"
assert profile.sequence_type == "unknown"
else:
assert profile.clonal_complex == "ST-2 complex" assert profile.clonal_complex == "ST-2 complex"
assert profile.sequence_type == "1" assert profile.sequence_type == "1"
@@ -196,9 +200,10 @@ async def test_bigsdb_profile_multiple_strings_nonexact_second_no_stop():
dummy_sequences = [NamedString("seq1", valid_seq), NamedString("should_fail", gene_scrambler(valid_seq, 0.3)), NamedString("seq3", valid_seq)] dummy_sequences = [NamedString("seq1", valid_seq), NamedString("should_fail", gene_scrambler(valid_seq, 0.3)), NamedString("seq3", valid_seq)]
async def generate_async_iterable_sequences(): async def generate_async_iterable_sequences():
for dummy_sequence in dummy_sequences: for dummy_sequence in dummy_sequences:
yield dummy_sequence yield [dummy_sequence]
async with BIGSdbMLSTProfiler(database_api="https://bigsdb.pasteur.fr/api", database_name="pubmlst_bordetella_seqdef", schema_id=3) as dummy_profiler: async with OnlineBIGSdbMLSTProfiler(database_api="https://bigsdb.pasteur.fr/api", database_name="pubmlst_bordetella_seqdef", schema_id=3) as dummy_profiler:
async for name, profile in dummy_profiler.profile_multiple_strings(generate_async_iterable_sequences(), False): async for named_profile in dummy_profiler.profile_multiple_strings(generate_async_iterable_sequences(), False):
name, profile = named_profile.name, named_profile.mlst_profile
if name == "should_fail": if name == "should_fail":
assert profile is not None assert profile is not None
assert profile.clonal_complex == "unknown" assert profile.clonal_complex == "unknown"
@@ -210,24 +215,6 @@ async def test_bigsdb_profile_multiple_strings_nonexact_second_no_stop():
assert profile.clonal_complex == "ST-2 complex" assert profile.clonal_complex == "ST-2 complex"
assert profile.sequence_type == "1" assert profile.sequence_type == "1"
async def test_bigsdb_profile_multiple_strings_fail_second_stop():
valid_seq = str(SeqIO.read("tests/resources/tohama_I_bpertussis.fasta", "fasta").seq)
invalid_seq = str(SeqIO.read("tests/resources/FDAARGOS_1560.fasta", "fasta").seq)
dummy_sequences = [NamedString("seq1", valid_seq), NamedString("should_fail", invalid_seq), NamedString("seq3", valid_seq)]
async def generate_async_iterable_sequences():
for dummy_sequence in dummy_sequences:
yield dummy_sequence
async with BIGSdbMLSTProfiler(database_api="https://bigsdb.pasteur.fr/api", database_name="pubmlst_bordetella_seqdef", schema_id=3) as dummy_profiler:
with pytest.raises(NoBIGSdbMatchesException):
async for name, profile in dummy_profiler.profile_multiple_strings(generate_async_iterable_sequences(), exact=True, stop_on_fail=True):
if name == "should_fail":
pytest.fail("Exception should have been thrown, no exception was thrown.")
else:
assert profile is not None
assert isinstance(profile, MLSTProfile)
assert profile.clonal_complex == "ST-2 complex"
assert profile.sequence_type == "1"
async def test_bigsdb_index_get_schemas_for_bordetella(): async def test_bigsdb_index_get_schemas_for_bordetella():
async with BIGSdbIndex() as index: async with BIGSdbIndex() as index:
schemas = await index.get_schemas_for_seqdefdb(seqdef_db_name="pubmlst_bordetella_seqdef") schemas = await index.get_schemas_for_seqdefdb(seqdef_db_name="pubmlst_bordetella_seqdef")