diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a832683..0697e05 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ exclude: '^docs/conf.py' repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: trailing-whitespace - id: check-added-large-files @@ -19,7 +19,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.8.2 + rev: v0.16.0 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] diff --git a/CHANGELOG.md b/CHANGELOG.md index ea495e0..1838022 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Version 0.0.1 +## Version 0.0.1 -- Initial implementation to access OrgDB objects. +- Initial implementation to access OrgDB objects. - This also fetches the annotation hub sqlite file and queries for available org sqlite files instead of a static registry used in the txdb package. diff --git a/docs/requirements.txt b/docs/requirements.txt index a1b9d2b..c20cf60 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,9 +1,9 @@ +furo +myst-nb # Requirements file for ReadTheDocs, check .readthedocs.yml. # To build the module reference correctly, make sure every external package # under `install_requires` in `setup.cfg` is also listed here! # sphinx_rtd_theme myst-parser[linkify] sphinx>=3.2.1 -myst-nb -furo sphinx-autodoc-typehints diff --git a/setup.py b/setup.py index 5876aab..6130f9f 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,10 @@ """ - Setup file for orgdb. - Use setup.cfg to configure your project. +Setup file for orgdb. +Use setup.cfg to configure your project. - This file was generated with PyScaffold 4.6. - PyScaffold helps you to put up the scaffold of your new Python project. - Learn more under: https://pyscaffold.org/ +This file was generated with PyScaffold 4.6. +PyScaffold helps you to put up the scaffold of your new Python project. +Learn more under: https://pyscaffold.org/ """ from setuptools import setup @@ -12,7 +12,7 @@ if __name__ == "__main__": try: setup(use_scm_version={"version_scheme": "no-guess-dev"}) - except: # noqa + except: print( "\n\nAn error occurred while building the project, " "please ensure you have the most updated version of setuptools, " diff --git a/src/orgdb/__init__.py b/src/orgdb/__init__.py index c04f046..fbbae7d 100644 --- a/src/orgdb/__init__.py +++ b/src/orgdb/__init__.py @@ -19,4 +19,4 @@ from .orgdbregistry import OrgDbRegistry from .record import OrgDbRecord -__all__ = ["OrgDb", "OrgDbRegistry", "OrgDbRecord"] +__all__ = ["OrgDb", "OrgDbRecord", "OrgDbRegistry"] diff --git a/src/orgdb/orgdb.py b/src/orgdb/orgdb.py index fa42d55..5ebdfe1 100644 --- a/src/orgdb/orgdb.py +++ b/src/orgdb/orgdb.py @@ -1,5 +1,4 @@ import sqlite3 -from typing import Dict, List, Union from biocframe import BiocFrame from genomicranges import GenomicRanges @@ -68,7 +67,7 @@ def species(self) -> str: return v return "Unknown" - def _define_tables(self) -> Dict[str, tuple]: + def _define_tables(self) -> dict[str, tuple]: """Define the mapping between column names and (table, field). Mirrors .definePossibleTables from R/methods-geneCentricDbs.R @@ -111,8 +110,7 @@ def _define_tables(self) -> Dict[str, tuple]: } if db_class == "OrgDb": - if "ALIAS2PROBE" in mapping: - del mapping["ALIAS2PROBE"] + mapping.pop("ALIAS2PROBE", None) if db_class == "ChipDb": mapping["PROBEID"] = ("c.probes", "probe_id") @@ -379,15 +377,15 @@ def _define_tables(self) -> Dict[str, tuple]: return mapping - def columns(self) -> List[str]: + def columns(self) -> list[str]: """List all available columns/keytypes.""" return list(self._table_map.keys()) - def keytypes(self) -> List[str]: + def keytypes(self) -> list[str]: """List all available keytypes (same as columns).""" return self.columns() - def keys(self, keytype: str) -> List[str]: + def keys(self, keytype: str) -> list[str]: """Return keys for the given keytype.""" if keytype not in self._table_map: raise ValueError(f"Invalid keytype: {keytype}. Use columns() to see valid options.") @@ -404,7 +402,7 @@ def keys(self, keytype: str) -> List[str]: except sqlite3.OperationalError: return [] - def _expand_cols(self, cols: List[str]) -> List[str]: + def _expand_cols(self, cols: list[str]) -> list[str]: """Expand columns like GO into GO, EVIDENCE, ONTOLOGY.""" new_cols = [] for c in cols: @@ -419,7 +417,7 @@ def _expand_cols(self, cols: List[str]) -> List[str]: new_cols.append("CHRLOCCHR") return list(set(new_cols)) # remove duplicates - def select(self, keys: Union[List[str], str], columns: Union[List[str], str], keytype: str) -> BiocFrame: + def select(self, keys: list[str] | str, columns: list[str] | str, keytype: str) -> BiocFrame: """Retrieve data from the database. Args: @@ -482,9 +480,7 @@ def select(self, keys: Union[List[str], str], columns: Union[List[str], str], ke return self._query_as_biocframe(sql, tuple(keys)) - def mapIds( - self, keys: Union[List[str], str], column: str, keytype: str, multiVals: str = "first" - ) -> Union[dict, list]: + def mapIds(self, keys: list[str] | str, column: str, keytype: str, multiVals: str = "first") -> dict | list: """Map keys to a specific column. A wrapper around select. Args: @@ -543,7 +539,7 @@ def genes(self) -> GenomicRanges: return GenomicRanges.empty() query = """ - SELECT + SELECT g.gene_id, c.seqname, c.start_location, diff --git a/src/orgdb/orgdbregistry.py b/src/orgdb/orgdbregistry.py index 5af9b68..dcf4410 100644 --- a/src/orgdb/orgdbregistry.py +++ b/src/orgdb/orgdbregistry.py @@ -1,7 +1,7 @@ import os import sqlite3 from pathlib import Path -from typing import Any, Dict, List, Optional, Union +from typing import Any from pybiocfilecache import BiocFileCache @@ -19,7 +19,7 @@ class OrgDbRegistry: def __init__( self, - cache_dir: Optional[Union[str, Path]] = None, + cache_dir: str | Path | None = None, force: bool = False, ) -> None: """Initialize the OrgDb registry. @@ -39,7 +39,7 @@ def __init__( self._cache_dir.mkdir(parents=True, exist_ok=True) self._bfc = BiocFileCache(self._cache_dir) - self._registry_map: Dict[str, OrgDbRecord] = {} + self._registry_map: dict[str, OrgDbRecord] = {} self._initialize_registry(force=force) @@ -108,7 +108,7 @@ def _initialize_registry(self, force: bool = False): record = OrgDbRecord.from_config_entry(orgdb_id, entry) self._registry_map[orgdb_id] = record - def list_orgdb(self) -> List[str]: + def list_orgdb(self) -> list[str]: """List all available OrgDb IDs (e.g., 'org.Hs.eg.db'). Returns: @@ -205,7 +205,7 @@ def load_db(self, orgdb_id: str, force: bool = False) -> OrgDb: path = self.download(orgdb_id, force=force) return OrgDb(path) - def _get_filepath(self, resource: Any) -> Optional[str]: + def _get_filepath(self, resource: Any) -> str | None: """Helper to extract absolute path from a BiocFileCache resource.""" if hasattr(resource, "rpath"): rel_path = str(resource.rpath) diff --git a/src/orgdb/record.py b/src/orgdb/record.py index 423c76d..458b139 100644 --- a/src/orgdb/record.py +++ b/src/orgdb/record.py @@ -2,7 +2,6 @@ from dataclasses import dataclass from datetime import date, datetime -from typing import Optional __author__ = "Jayaram Kancherla" __copyright__ = "Jayaram Kancherla" @@ -14,16 +13,16 @@ class OrgDbRecord: """Container for a single OrgDb entry.""" orgdb_id: str - release_date: Optional[date] + release_date: date | None url: str - species: Optional[str] = None # e.g. "Hs" or "Hsapiens" - id_type: Optional[str] = None # e.g. "eg" (Entrez Gene) or "tair" + species: str | None = None # e.g. "Hs" or "Hsapiens" + id_type: str | None = None # e.g. "eg" (Entrez Gene) or "tair" - bioc_version: Optional[str] = None + bioc_version: str | None = None @classmethod - def from_config_entry(cls, orgdb_id: str, entry: dict) -> "OrgDbRecord": + def from_config_entry(cls, orgdb_id: str, entry: dict) -> OrgDbRecord: """Build a record from a ORGDB_CONFIG entry: { "release_date": "YYYY-MM-DD", # optional @@ -33,7 +32,7 @@ def from_config_entry(cls, orgdb_id: str, entry: dict) -> "OrgDbRecord": url = entry["url"] date_str = entry.get("release_date") - rel_date: Optional[date] + rel_date: date | None if date_str: rel_date = datetime.strptime(date_str, "%Y-%m-%d").date() else: @@ -59,14 +58,11 @@ def _parse_orgdb_id(orgdb_id: str): into (species, id_type). """ name = orgdb_id - if name.startswith("org."): - name = name[len("org.") :] + name = name.removeprefix("org.") - if name.endswith(".db"): - name = name[: -len(".db")] + name = name.removesuffix(".db") - if name.endswith(".sqlite"): - name = name[: -len(".sqlite")] + name = name.removesuffix(".sqlite") parts = name.split(".") @@ -79,7 +75,7 @@ def _parse_orgdb_id(orgdb_id: str): return species, id_type -def _parse_bioc_version(url: str) -> Optional[str]: +def _parse_bioc_version(url: str) -> str | None: """Extract the Bioconductor/AnnotationHub-like version from URL. Example: diff --git a/tests/conftest.py b/tests/conftest.py index c2998c2..c31d312 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,7 @@ - https://docs.pytest.org/en/stable/writing_plugins.html """ -import sqlite3 +import sqlite3 from orgdb import OrgDb import pytest @@ -102,4 +102,4 @@ def mock_orgdb(mock_orgdb_path): """Return an open OrgDb instance using the mock database.""" db = OrgDb(mock_orgdb_path) yield db - db.close() \ No newline at end of file + db.close() diff --git a/tests/test_orgdb.py b/tests/test_orgdb.py index fe7ca4a..2610a29 100644 --- a/tests/test_orgdb.py +++ b/tests/test_orgdb.py @@ -41,19 +41,19 @@ def test_select_simple(mock_orgdb): def test_select_multikey(mock_orgdb): res = mock_orgdb.select(keys=["1", "10"], columns=["SYMBOL"], keytype="ENTREZID") assert len(res) == 2 - + symbols = res.get_column("SYMBOL") assert "A1BG" in symbols assert "NAT2" in symbols def test_select_go_expansion(mock_orgdb): res = mock_orgdb.select(keys="1", columns=["GO"], keytype="ENTREZID") - + col_names = list(res.column_names) assert "GO" in col_names assert "EVIDENCE" in col_names assert "ONTOLOGY" in col_names - + assert len(res) == 2 go_ids = res.get_column("GO") assert "GO:0000001" in go_ids @@ -66,12 +66,12 @@ def test_select_many_to_one(mock_orgdb): def test_mapIds(mock_orgdb): keys = ["1", "10", "100"] - + res = mock_orgdb.mapIds(keys, column="SYMBOL", keytype="ENTREZID") assert isinstance(res, dict) assert res["1"] == "A1BG" assert res["10"] == "NAT2" - + res_list = mock_orgdb.mapIds(["1"], column="GO", keytype="ENTREZID", multiVals="list") assert isinstance(res_list["1"], list) assert len(res_list["1"]) == 2 @@ -81,12 +81,12 @@ def test_genes_genomicranges(mock_orgdb): gr = mock_orgdb.genes() assert isinstance(gr, GenomicRanges) assert len(gr) == 2 - + names = list(gr.names) idx = names.index("1") assert str(gr.seqnames[idx]) == "chr19" assert gr.start[idx] == 58346806 assert gr.end[idx] == 58353492 - + assert "gene_id" in gr.mcols.column_names - assert gr.mcols.get_column("gene_id")[idx] == "1" \ No newline at end of file + assert gr.mcols.get_column("gene_id")[idx] == "1" diff --git a/tests/test_real.py b/tests/test_real.py index 089b069..9f0e603 100644 --- a/tests/test_real.py +++ b/tests/test_real.py @@ -21,6 +21,6 @@ def test_real_orgdb_workflow(tmp_path): "GO:0048699", "GO:0048143"], columns="SYMBOL") - + assert res.shape == (104, 4) orgdb.close() diff --git a/tests/test_registry.py b/tests/test_registry.py index 57ca98f..bc788d6 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -9,7 +9,7 @@ def registry(tmp_path): def test_registry_init(registry): assert isinstance(registry, OrgDbRegistry) assert "org.Hs.eg.db" in registry.list_orgdb() - + def test_get_record(registry): rec = registry.get_record("org.Hs.eg.db") assert rec.orgdb_id == "org.Hs.eg.db"