diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61bb0a7..7ee5697 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,66 +4,53 @@ on: push: branches: [ master ] pull_request: - branches: [ master ] + branches: [ master, dev ] jobs: - ubuntu_3_11: - runs-on: ubuntu-latest + ubuntu_matrix: + runs-on: ubuntu-22.04 # latest fails testing python 3.7 - https://github.com/actions/setup-python/issues/962 + strategy: + matrix: + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 with: - python-version: '3.11' + python-version: ${{ matrix.python-version }} - name: Install dependencies run: | pip install -e . pip install -e ".[dev]" - name: Run tests - run: python -m unittest discover - - ubuntu_3_7: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Install dependencies - run: | - pip install -e . - pip install -e ".[dev]" - - name: Run tests - run: python -m unittest discover + run: python -m unittest discover tests/ macos: runs-on: macos-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: - python-version: '3.9' + python-version: '3.11' - name: Install dependencies run: | pip install -e . pip install -e ".[dev]" - name: Run tests - run: python -m unittest discover + run: python -m unittest discover tests/ windows: runs-on: windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: - python-version: '3.9' + python-version: '3.11' - name: Install dependencies run: | pip install -e . pip install -e ".[dev]" - name: Run tests - run: python -m unittest discover - + run: python -m unittest discover tests/ \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 8d4855e..fbcfcde 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ classifiers = [ "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.10", diff --git a/src/UniProtMapper/field_base_classes.py b/src/UniProtMapper/field_base_classes.py index 9da426b..c3f51bd 100644 --- a/src/UniProtMapper/field_base_classes.py +++ b/src/UniProtMapper/field_base_classes.py @@ -4,7 +4,7 @@ """ import re -from typing import Union +from typing import List, Union from .utils import read_fields_table @@ -38,7 +38,7 @@ class QueryBuilder: 'NOT reviewed:true OR (organism_id:9606 AND length:[100 TO 200])' """ - def __init__(self, query: Union[list[Union[str, "AnyField"]], "AnyField"]) -> None: + def __init__(self, query: Union[List[Union[str, "AnyField"]], "AnyField"]) -> None: """Initialize QueryBuilder with a query.""" # Convert single field to list for consistency self.query = [query] if hasattr(query, "field_name") else query diff --git a/src/UniProtMapper/idmapping_api.py b/src/UniProtMapper/idmapping_api.py index 1fb1379..5afd476 100644 --- a/src/UniProtMapper/idmapping_api.py +++ b/src/UniProtMapper/idmapping_api.py @@ -40,10 +40,10 @@ class ProtMapper(BaseUniProt): def __init__( self, - pooling_interval=3, - total_retries=5, - backoff_factor=0.25, - api_url="https://rest.uniprot.org", + pooling_interval: int = 3, + total_retries: int = 5, + backoff_factor: float = 0.25, + api_url: str = "https://rest.uniprot.org", ) -> None: """Initialize the class. This will set up the session and retry mechanism. diff --git a/src/UniProtMapper/interface.py b/src/UniProtMapper/interface.py index 89eba16..0c1d5e1 100644 --- a/src/UniProtMapper/interface.py +++ b/src/UniProtMapper/interface.py @@ -24,10 +24,10 @@ class BaseUniProt(ABC): def __init__( self, - pooling_interval=3, - total_retries=5, - backoff_factor=0.25, - api_url="https://rest.uniprot.org", + pooling_interval: int = 3, + total_retries: int = 5, + backoff_factor: float = 0.25, + api_url: str = "https://rest.uniprot.org", ) -> None: """Initialize the class. This will set up the session and retry mechanism. @@ -46,7 +46,7 @@ def __init__( self._re_next_link = re.compile(r'<(.+)>; rel="next"') @property - def fields_table(self): + def fields_table(self) -> None: return read_fields_table() def _setup_retries(self, total_retries, backoff_factor) -> None: @@ -59,14 +59,14 @@ def _setup_retries(self, total_retries, backoff_factor) -> None: def _setup_session(self) -> None: self.session.mount("https://", HTTPAdapter(max_retries=self.retries)) - def check_response(self, response): + def check_response(self, response) -> None: try: response.raise_for_status() except requests.HTTPError: print(response.json()) raise - def get_next_link(self, headers): + def get_next_link(self, headers) -> str: if "Link" in headers: match = self._re_next_link.match(headers["Link"]) if match: diff --git a/src/UniProtMapper/uniprotkb_api.py b/src/UniProtMapper/uniprotkb_api.py index 125fb55..55131d3 100644 --- a/src/UniProtMapper/uniprotkb_api.py +++ b/src/UniProtMapper/uniprotkb_api.py @@ -2,7 +2,7 @@ field classes found in `UniProtMapper.uniprotkb_fields`.""" from logging import info -from typing import Generator, Optional, Union +from typing import Generator, List, Optional, Tuple, Union import pandas as pd import requests @@ -56,7 +56,7 @@ def __init__( def _build_search_url( self, query: str, - fields: list[str], + fields: List[str], format: str = "tsv", include_isoform: bool = False, compressed: bool = False, @@ -111,7 +111,7 @@ def submit_query( def _get_batches( self, initial_response - ) -> Generator[tuple[requests.Response, int], None, None]: + ) -> Generator[Tuple[requests.Response, int], None, None]: """Generator that yields batches of results with pagination. Args: @@ -138,7 +138,7 @@ def _get_batches( def get( self, query: Union[QueryBuilder, str], - fields: Optional[list[str]] = None, + fields: Optional[List[str]] = None, include_isoform: bool = False, compressed: bool = False, size: int = 500, diff --git a/src/UniProtMapper/utils.py b/src/UniProtMapper/utils.py index e1adccd..0ddd9ff 100644 --- a/src/UniProtMapper/utils.py +++ b/src/UniProtMapper/utils.py @@ -7,13 +7,23 @@ from typing import Optional import pandas as pd -import pkg_resources import requests +def get_resource_file(filename: str) -> str: + try: + import pkg_resources + + return pkg_resources.resource_filename("UniProtMapper", filename) + except ImportError: + from importlib import resources + + return str(resources.files("UniProtMapper") / filename) + + def get_resources_root() -> Path: """Returns the path to the resources folder.""" - return Path(pkg_resources.resource_filename("UniProtMapper", "resources")) + return Path(get_resource_file("resources")) def fetch_cross_referenced_db_details( @@ -60,9 +70,7 @@ def read_fields_table(): - `has_full_version`: whether the annotated field contains the full version of the dataset or not (in case of cross-references). - `type`: the type of data. Either "cross_reference" or "uniprot_field".""" - csv_path = pkg_resources.resource_filename( - "UniProtMapper", "resources/uniprot_return_fields.csv" - ) + csv_path = get_resource_file("resources/uniprot_return_fields.csv") return pd.read_csv(csv_path) @@ -70,9 +78,7 @@ def supported_mapping_dbs(): """Return a list of the supported datasets as UniProt cross references. This list is used to validate the arguments `to_db` and `from_db` in the `FieldRetriever.get()` method. """ - _mapping_dbs_path = pkg_resources.resource_filename( - "UniProtMapper", "resources/uniprot_mapping_dbs.json" - ) + _mapping_dbs_path = get_resource_file("resources/uniprot_mapping_dbs.json") with open(_mapping_dbs_path, "r") as f: dbs_dict = json.load(f) return sorted([dbs_dict[k][i] for k in dbs_dict for i in range(len(dbs_dict[k]))]) diff --git a/tests/test_idmapping_api.py b/tests/test_idmapping_api.py index 700407d..d89930d 100644 --- a/tests/test_idmapping_api.py +++ b/tests/test_idmapping_api.py @@ -2,13 +2,13 @@ import pandas as pd -from UniProtMapper.idmapping_api import ProtMapper +from UniProtMapper import ProtMapper # Test data test_ids = ["P30542", "Q16678", "Q02880"] # Initialize the UniProtRetriever object -uni_retriever = ProtMapper() +mapper = ProtMapper() class TestProtMapper(unittest.TestCase): @@ -16,7 +16,7 @@ def setUp(self): self.fields_table = ProtMapper().fields_table def test_supported_dbs(self): - supported_dbs = uni_retriever._supported_dbs + supported_dbs = mapper._supported_dbs self.assertIsInstance(supported_dbs, list) self.assertIn("UniProtKB_AC-ID", supported_dbs) @@ -25,7 +25,7 @@ def test_fields_table(self): self.assertIn("accession", self.fields_table["returned_field"].values) def test_retrieve_fields_default(self): - result_df, failed = uni_retriever(test_ids) + result_df, failed = mapper.get(test_ids) self.assertIsInstance(result_df, pd.DataFrame) self.assertEqual(len(result_df), len(test_ids)) self.assertEqual(len(failed), 0) @@ -45,7 +45,7 @@ def test_retrieve_fields_custom(self): result_columns = self.fields_table[ self.fields_table["returned_field"].isin(custom_fields) ]["label"] - result_df, failed = uni_retriever(test_ids, fields=custom_fields) + result_df, failed = mapper.get(test_ids, fields=custom_fields) self.assertIsInstance(result_df, pd.DataFrame) self.assertEqual(len(result_df), len(test_ids)) self.assertEqual(len(failed), 0) @@ -54,15 +54,15 @@ def test_retrieve_fields_custom(self): def test_retrieve_fields_invalid_field(self): with self.assertRaises(ValueError): - uni_retriever(test_ids, fields=["invalid_field"]) + mapper.get(test_ids, fields=["invalid_field"]) def test_retrieve_fields_invalid_from_db(self): with self.assertRaises(ValueError): - uni_retriever(test_ids, from_db="InvalidDB") + mapper.get(test_ids, from_db="InvalidDB") def test_retrieve_fields_invalid_to_db(self): with self.assertRaises(ValueError): - uni_retriever(test_ids, to_db="InvalidDB") + mapper.get(test_ids, to_db="InvalidDB") if __name__ == "__main__":