From c19d40b3bc3691b956bfff8b0374705a73875575 Mon Sep 17 00:00:00 2001 From: "Mitja M. Zdouc" Date: Sun, 7 Dec 2025 11:01:31 +0100 Subject: [PATCH] Fixed ncbi timeout --- CHANGELOG.md | 3 ++- mite_data/modules/fasta_manager.py | 38 ++++++++++++++++++------------ tests/test_fasta_manager.py | 14 +++++------ 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42f229178..fa83e6021 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - Checks for MIBiG and Rhea IDs (implemented as warnings in mite_validation) -- Timeout for UniProt, NCBI connection +- Timeout for UniProt using requests +- Timeout for NCBI using concurrent.futures (Biopython Entrez lacks timeout, requests blocked by NCBI servers) ### Fixed diff --git a/mite_data/modules/fasta_manager.py b/mite_data/modules/fasta_manager.py index 3c014baef..a0c1ff6ea 100644 --- a/mite_data/modules/fasta_manager.py +++ b/mite_data/modules/fasta_manager.py @@ -123,10 +123,11 @@ this CC0 or use of the Work. """ +import concurrent.futures import json import logging from pathlib import Path -from typing import Self +from typing import Any, Self import requests from Bio import Entrez @@ -201,22 +202,29 @@ def download_ncbi(self: Self, mite_acc: str, genpept_acc: str) -> tuple: Returns: Tuple of path and data for storage + + Notes: + concurrent.futures needed to implement timeout since Biopythons Entrez lacks it and requests is blocked by NCBI """ - url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi" - params = { - "db": "protein", - "id": genpept_acc, - "rettype": "fasta", - "retmode": "text", - } - try: - response = requests.get(url, params, timeout=self.timeout) - except requests.exceptions.ConnectTimeout as e: - raise RuntimeError("Warning: could not connect to NCBI: Timeout") from e - if response is None: - raise RuntimeError(f"{mite_acc}: NCBI download failed on ID {genpept_acc}") - lines = response.text.strip().splitlines() + def fetch_ncbi(acc: str) -> Any: + """Fetch data from NCBI""" + handle = Entrez.efetch( + db="protein", id=genpept_acc, rettype="fasta", retmode="text" + ) + data = handle.read().strip() + handle.close() + return data + + with concurrent.futures.ThreadPoolExecutor() as ex: + future = ex.submit(fetch_ncbi, genpept_acc) + + try: + fasta_data = future.result(timeout=self.timeout) + except concurrent.futures.TimeoutError as e: + raise RuntimeError("Warning: could not connect to NCBI: Timeout") from e + + lines = fasta_data.splitlines() if not lines or len(lines) == 1: raise ValueError( diff --git a/tests/test_fasta_manager.py b/tests/test_fasta_manager.py index 241696d7e..3e5d43cd1 100644 --- a/tests/test_fasta_manager.py +++ b/tests/test_fasta_manager.py @@ -42,13 +42,6 @@ def test_download_ncbi(fasta_manager): assert content -def test_download_ncbi_timeout(fasta_manager): - mngr = copy.deepcopy(fasta_manager) - mngr.timeout = 0.000001 - with pytest.raises(RuntimeError): - mngr.download_ncbi(mite_acc="MITE9999999", genpept_acc="AAD28496.1") - - def test_download_uniprot(fasta_manager): path, content = fasta_manager.download_uniprot( mite_acc="MITE9999999", uniprot_acc="Q9X2V9" @@ -57,6 +50,13 @@ def test_download_uniprot(fasta_manager): assert content +def test_download_ncbi_timeout(fasta_manager): + mngr = copy.deepcopy(fasta_manager) + mngr.timeout = 0.000001 + with pytest.raises(RuntimeError): + mngr.download_ncbi(mite_acc="MITE9999999", genpept_acc="AAD28496.1") + + def test_download_uniprot_timeout(fasta_manager): mngr = copy.deepcopy(fasta_manager) mngr.timeout = 0.000001