diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cf4ee615..fbb30271 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 diff --git a/.github/workflows/test-spras.yml b/.github/workflows/test-spras.yml index db6d1b0f..b5bd1b74 100644 --- a/.github/workflows/test-spras.yml +++ b/.github/workflows/test-spras.yml @@ -12,13 +12,13 @@ jobs: os: [macos-latest, windows-latest] steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install conda environment - uses: conda-incubator/setup-miniconda@v2 + uses: conda-incubator/setup-miniconda@v4 with: activate-environment: spras environment-file: environment.yml - auto-activate-base: false + auto-activate: false miniconda-version: 'latest' - name: Log conda environment # Log conda environment contents @@ -49,11 +49,11 @@ jobs: sudo docker image prune --all --force sudo docker builder prune -a - name: Install conda environment - uses: conda-incubator/setup-miniconda@v2 + uses: conda-incubator/setup-miniconda@v4 with: activate-environment: spras environment-file: environment.yml - auto-activate-base: false + auto-activate: false miniconda-version: 'latest' - name: Install spras in conda env # Install spras in the environment using pip @@ -85,11 +85,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - name: Install conda environment - uses: conda-incubator/setup-miniconda@v2 + uses: conda-incubator/setup-miniconda@v4 with: activate-environment: spras environment-file: environment.yml - auto-activate-base: false + auto-activate: false miniconda-version: 'latest' # Install spras in the environment using pip - name: Install spras in conda env @@ -110,11 +110,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - name: Install conda environment - uses: conda-incubator/setup-miniconda@v2 + uses: conda-incubator/setup-miniconda@v4 with: activate-environment: spras environment-file: environment.yml - auto-activate-base: false + auto-activate: false miniconda-version: 'latest' - name: Run pre-commit shell: bash --login {0} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fe010814..3b0581af 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for more hooks default_language_version: # Match this to the version specified in environment.yml - python: python3.11 + python: python3.13 repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 # Use the ref you want to point at diff --git a/.readthedocs.yaml b/.readthedocs.yaml index cd6d3883..612c1d92 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -8,7 +8,7 @@ version: 2 build: os: ubuntu-22.04 tools: - python: "3.11" + python: "3.13" # Build documentation in the "docs/" directory with Sphinx sphinx: diff --git a/docker-wrappers/SPRAS/Dockerfile b/docker-wrappers/SPRAS/Dockerfile index 3d69943c..4bb0d660 100644 --- a/docker-wrappers/SPRAS/Dockerfile +++ b/docker-wrappers/SPRAS/Dockerfile @@ -4,7 +4,7 @@ FROM almalinux:9 RUN dnf update -y && \ dnf install -y epel-release && \ dnf install -y gcc gcc-c++ \ - python3.11 python3.11-pip python3.11-devel \ + python3.13 python3.13-pip python3.13-devel \ docker apptainer && \ dnf clean all @@ -13,4 +13,4 @@ RUN chmod -R 777 /spras WORKDIR /spras # Install spras into the container -RUN pip3.11 install . +RUN pip3.13 install . diff --git a/environment.yml b/environment.yml index 4361834a..12dfd2fb 100644 --- a/environment.yml +++ b/environment.yml @@ -3,37 +3,37 @@ channels: - conda-forge dependencies: - adjusttext=1.3.0 - - bioconda::snakemake-minimal=9.6.2 + - bioconda::snakemake-minimal=9.20.0 # Conda refers to pypi/docker as docker-py. - docker-py=7.1.0 - - matplotlib=3.10.3 - - networkx=3.5 - - pandas=2.3.0 - - pydantic=2.11.7 - - numpy=2.3.1 - - requests=2.32.4 - - scikit-learn=1.7.0 + - matplotlib=3.10.9 + - networkx=3.6.1 + - pandas=3.0.2 + - pydantic=2.13.3 + - numpy=2.4.4 + - requests=2.33.1 + - scikit-learn=1.8.0 - seaborn=0.13.2 - spython=0.3.14 # conda-specific for dsub - python-dateutil=2.9.0 - - pytz=2025.2 - - pyyaml=6.0.2 - - tenacity=9.1.2 - - tabulate=0.9.0 + - pytz=2026.1 + - pyyaml=6.0.3 + - tenacity=9.1.4 + - tabulate=0.10.0 # toolchain deps - - pip=25.3 + - pip=26.0.1 # This should be the same as requires-python minus the >=. - - python=3.11 + - python=3.13 # development dependencies - - pre-commit=4.2.0 - - pytest=8.4.1 - - pytest-split=0.10.0 - - sphinx=7.4.7 - - sphinx-rtd-theme=2.0.0 + - pre-commit=4.6.0 + - pytest=9.0.3 + - pytest-split=0.11.0 + - sphinx=9.1.0 + - sphinx-rtd-theme=3.1.0 - pip: - dsub==0.4.13 diff --git a/pyproject.toml b/pyproject.toml index bfc602c6..d28d4cfd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,31 +16,31 @@ classifiers = [ "Programming Language :: Python :: 3", "Topic :: Scientific/Engineering :: Bio-Informatics", ] -requires-python = ">=3.11" +requires-python = ">=3.13" dependencies = [ - "adjusttext==0.7.3", - "snakemake==9.6.2", + "adjusttext==1.3.0", + "snakemake==9.20.0", "docker==7.1.0", - "matplotlib==3.10.3", - "networkx==3.5", - "pandas==2.3.0", - "pydantic==2.11.7", - "numpy==2.3.1", - "requests==2.32.4", - "scikit-learn==1.7.0", + "matplotlib==3.10.9", + "networkx==3.6.1", + "pandas==3.0.2", + "pydantic==2.13.3", + "numpy==2.4.4", + "requests==2.33.1", + "scikit-learn==1.8.0", "seaborn==0.13.2", "spython==0.3.14", # toolchain deps - "pip==25.3", + "pip==26.0.1", ] [project.optional-dependencies] dev = [ # Only required for development - "pre-commit==4.2.0", - "pytest==8.4.1", - "pytest-split==0.10.0", + "pre-commit==4.6.0", + "pytest==9.0.3", + "pytest-split==0.11.0", ] [project.urls] @@ -52,7 +52,7 @@ requires = ["setuptools>=64.0"] build-backend = "setuptools.build_meta" [tool.ruff] -target-version = "py311" +target-version = "py313" # Autofix errors when possible fix = true # Select categories or specific rules from https://beta.ruff.rs/docs/rules/ diff --git a/spras/dataset.py b/spras/dataset.py index ddf74736..3e9975f3 100644 --- a/spras/dataset.py +++ b/spras/dataset.py @@ -1,7 +1,7 @@ import os import pickle as pkl import warnings -from typing import Union +from typing import Self, Union import pandas as pd @@ -60,9 +60,8 @@ def to_file(self, file: LoosePathLike): with open(file, "wb") as f: pkl.dump(self, f) - # NOTE: When we bump to Python 3.13, we can use the reference Dataset instead of the literal "Dataset" for typing. @classmethod - def from_file(cls, file: Union[LoosePathLike, "Dataset"]): + def from_file(cls, file: Union[LoosePathLike, Self]): """ Loads dataset object from a pickle file or another `Dataset` object. Usage: dataset = Dataset.from_file(pickle_file) diff --git a/spras/prm.py b/spras/prm.py index 18f3c8a9..5d595e0c 100644 --- a/spras/prm.py +++ b/spras/prm.py @@ -1,6 +1,7 @@ import os from abc import ABC, abstractmethod from pathlib import Path +from types import get_original_bases from typing import Any, Generic, Mapping, Optional, TypeVar, cast, get_args from pydantic import BaseModel @@ -52,10 +53,22 @@ def get_params_generic(cls) -> type[T]: For example, on `class PathLinker(PRM[PathLinkerParams])`, calling `PathLinker.get_params_generic()` returns `PathLinkerParams`. """ - # TODO: use the type-safe get_original_bases when we bump to >= Python 3.12 - # This is hacky reflection from https://stackoverflow.com/a/71720366/7589775 - # which grabs the class of type T by the definition of `__orig_bases__`. - return get_args(cast(Any, cls).__orig_bases__[0])[0] + # This gives us (PRM[PathLinkerParams], ) + original_bases = get_original_bases(cls) + + # Since we just used reflection, we provide a few mountain-dewey error messages here + # to protect against any developer confusion. + assert len(original_bases) == 1, f"{cls} inherits from several classes, when precisely one is required." + original_bases_args = get_args(original_bases[0]) + assert len(original_bases_args) == 1, "There were several generics passed into PRM, when precisely one is required." + T_class, = original_bases_args + + if not issubclass(T_class, BaseModel): + raise RuntimeError("The generic passed into PRM is not a pydantic.BaseModel.") + + # Finally, we cast, since issubclass overeagerly restricts T_class to type[BaseModel] + # instead of type[T] without imposing the restriction that T inherits from BaseModel + return cast(type[T], T_class) # This is used in `runner.py` to avoid a dependency diamond when trying # to import the actual algorithm schema. @@ -66,11 +79,6 @@ def run_typeless(cls, inputs: dict[str, str | os.PathLike], output_file: str | o """ T_class = cls.get_params_generic() - # Since we just used reflection, we provide a mountain-dewey error message here - # to protect against any developer confusion. - if not issubclass(T_class, BaseModel): - raise RuntimeError("The generic passed into PRM is not a pydantic.BaseModel.") - # Validates our untyped `args` parameter against our parameter class of type T # using BaseModel.model_validate (https://docs.pydantic.dev/latest/api/base_model/#pydantic.BaseModel.model_validate) # (Pydantic already provides nice error messages, so we don't need to worry about catching this.) diff --git a/spras/responsenet.py b/spras/responsenet.py index f53b5984..f8bc3598 100644 --- a/spras/responsenet.py +++ b/spras/responsenet.py @@ -136,7 +136,7 @@ def parse_output(raw_pathway_file, standardized_pathway_file, params): df = raw_pathway_df(raw_pathway_file, sep='\t', header=0) if not df.empty: df.columns = ['Node1', 'Node2', 'Flow'] - df = df.drop(columns=['Flow'], axis=1) + df = df.drop(columns=['Flow']) df = add_rank_column(df) # ResponseNet's outputs should be treated as undirected outputs. df = reinsert_direction_col_undirected(df)