From 715b82a0bf7dcab0e74a95b7e6db11ec267e42de Mon Sep 17 00:00:00 2001 From: Mateusz Praski Date: Wed, 24 Dec 2025 13:45:30 +0100 Subject: [PATCH 1/2] Added CI/CD for tests --- .github/workflows/test.yml | 46 +++++++++++++++++++++++++++++ .gitignore | 3 ++ Makefile | 3 ++ bbttest/__init__.py | 8 ++--- bbttest/utils.py | 4 ++- pyproject.toml | 27 +++++++++++++---- ruff.toml | 5 +--- tests/__init__.py | 0 uv.lock | 60 +++++++++++++++++++++++++++++++++----- 9 files changed, 134 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 tests/__init__.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..7c1ea62 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,46 @@ +name: Tests & checks + +on: + pull_request: + workflow_dispatch: # allow running manually from the Actions tab + +# cancel previous runs on a new commit +concurrency: + group: ${{ github.head_ref }} + cancel-in-progress: true + +jobs: + ci: + strategy: + fail-fast: false + matrix: + python-version: ["3.11", "3.12", "3.13", "3.14"] + os: [macos-latest, ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install uv + uses: astral-sh/setup-uv@v5 + + - uses: actions/cache@v4 + name: Cache venv + with: + path: ./.venv + key: ${{ matrix.os }}-venv-${{ matrix.python-version }}-${{ hashFiles('**/uv.lock') }} + + - name: Install the project dependencies + run: uv sync --group test --python "$(python -c 'import sys; print(sys.executable)')" + shell: bash + + - name: Check pre-commit + run: uv run pre-commit run --all-files + + - name: Run tests with pytest + run: uv run make test diff --git a/.gitignore b/.gitignore index cdf4e4e..f86617a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ dist/ wheels/ *.egg-info +# Dev cache +.ruff_cache/ + # Virtual environments .venv diff --git a/Makefile b/Makefile index 426ce57..06e235b 100644 --- a/Makefile +++ b/Makefile @@ -9,5 +9,8 @@ setup: ## Install development dependencies uv sync --group dev uv run pre-commit install +test: + uv run ruff check + help: @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' diff --git a/bbttest/__init__.py b/bbttest/__init__.py index 06c399d..28989cd 100644 --- a/bbttest/__init__.py +++ b/bbttest/__init__.py @@ -1,13 +1,13 @@ """bbt-test: Bayesian Bradley-Terry model for algorithm comparison.""" +from .const import HyperPrior, ReportedProperty, TieSolver from .py_bbt import PyBBT from .utils import multiple_ropes_control_table -from .const import TieSolver, ReportedProperty, HyperPrior __all__ = [ + "HyperPrior", "PyBBT", - "multiple_ropes_control_table", - "TieSolver", "ReportedProperty", - "HyperPrior", + "TieSolver", + "multiple_ropes_control_table", ] diff --git a/bbttest/utils.py b/bbttest/utils.py index 58841e9..2e47571 100644 --- a/bbttest/utils.py +++ b/bbttest/utils.py @@ -1,7 +1,9 @@ +from typing import Literal + import pandas as pd from .py_bbt import PyBBT -from typing import Literal + def multiple_ropes_control_table( model: PyBBT, diff --git a/pyproject.toml b/pyproject.toml index cd48d91..b8580ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,13 +29,28 @@ dependencies = [ [dependency-groups] dev = [ - "coverage>=7.12.0", - "jupyter>=1.1.1", - "mypy>=1.18.2", - "pre-commit>=4.5.0", - "ruff>=0.14.6", - "seaborn>=0.13.2", + "coverage", + "jupyter", + "mypy", + "pre-commit", + "ruff", + "seaborn", ] +test = [ + "pre-commit", + "pytest", + "ruff", + "mypy", +] + +[tool.mypy] +python_version = "3.11" +check_untyped_defs = true # check all functions, this fixes some tests +allow_redefinition = true # we redefine variables a lot for efficiency +# most libraries used are not properly typed in Python, particularly RDKit +ignore_missing_imports = true +disable_error_code = ["import-untyped"] +no_site_packages = true [tool.uv.build-backend] module-name = "bbttest" diff --git a/ruff.toml b/ruff.toml index 33daa74..e8f7ddc 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,9 +1,6 @@ include = [ "pyproject.toml", - "benchmarking/**/*.ipynb", - "examples/**/*.py", - "skfp/**/*.py", - "tests/**/*.py", + "bbttest/**" ] exclude = [ "docs/**" diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/uv.lock b/uv.lock index f8631b2..8e39870 100644 --- a/uv.lock +++ b/uv.lock @@ -145,7 +145,7 @@ wheels = [ [[package]] name = "bbt-test" -version = "0.1.0" +version = "0.0.0" source = { editable = "." } dependencies = [ { name = "pandas" }, @@ -162,6 +162,12 @@ dev = [ { name = "ruff" }, { name = "seaborn" }, ] +test = [ + { name = "mypy" }, + { name = "pre-commit" }, + { name = "pytest" }, + { name = "ruff" }, +] [package.metadata] requires-dist = [ @@ -172,12 +178,18 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ - { name = "coverage", specifier = ">=7.12.0" }, - { name = "jupyter", specifier = ">=1.1.1" }, - { name = "mypy", specifier = ">=1.18.2" }, - { name = "pre-commit", specifier = ">=4.5.0" }, - { name = "ruff", specifier = ">=0.14.6" }, - { name = "seaborn", specifier = ">=0.13.2" }, + { name = "coverage" }, + { name = "jupyter" }, + { name = "mypy" }, + { name = "pre-commit" }, + { name = "ruff" }, + { name = "seaborn" }, +] +test = [ + { name = "mypy" }, + { name = "pre-commit" }, + { name = "pytest" }, + { name = "ruff" }, ] [[package]] @@ -858,6 +870,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + [[package]] name = "ipykernel" version = "7.1.0" @@ -1979,6 +2000,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, ] +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + [[package]] name = "pre-commit" version = "4.5.0" @@ -2139,6 +2169,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/cd/9bf8773d5e2a62f4218bf45505b9856000df7124d3bf3df6f3f137951f01/pytensor-2.35.1-py2.py3-none-any.whl", hash = "sha256:253fa0ee739d7afa3e009563cf3ccd9c48d9b058e88dee9ee3b8d6fe1ea03ccd", size = 1358179, upload-time = "2025-10-20T15:18:58.661Z" }, ] +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" From 25b99bb65d83ab3204df7a3c5a3cf12c9060a249 Mon Sep 17 00:00:00 2001 From: Mateusz Praski Date: Wed, 24 Dec 2025 13:53:26 +0100 Subject: [PATCH 2/2] Fix pre-commit issues --- bbttest/py_bbt.py | 10 ++++++++-- bbttest/utils.py | 24 +++++++++++++++++------- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/bbttest/py_bbt.py b/bbttest/py_bbt.py index 6ccf469..fd3140f 100644 --- a/bbttest/py_bbt.py +++ b/bbttest/py_bbt.py @@ -1,3 +1,5 @@ +from collections.abc import Sequence + import numpy as np import pandas as pd @@ -139,7 +141,7 @@ def posterior_table( rope_value: tuple[float, float] = (0.45, 0.55), control_model: str | None = None, selected_models: list[str] | None = None, - columns: list[ReportedProperty] = DEFAULT_PROPERTIES, + columns: Sequence[ReportedProperty | str] = DEFAULT_PROPERTIES, hdi_proba: float = 0.89, round_ndigits: int | None = 2, ) -> pd.DataFrame: @@ -196,7 +198,11 @@ def posterior_table( out_table["strong_interpretation_raw"] = np.where( out_table["mean"] > self._STRONG_INTERPRETATION_BETTER_THRESHOLD, out_table["left_model"] + ">", - np.where(out_table["mean"] <= self._STRONG_INTERPRETATION_EQUAL_THRESHOLD, "=", "?"), + np.where( + out_table["mean"] <= self._STRONG_INTERPRETATION_EQUAL_THRESHOLD, + "=", + "?", + ), ) out_table["strong_interpretation"] = np.where( out_table["strong_interpretation_raw"].str.endswith(">"), diff --git a/bbttest/utils.py b/bbttest/utils.py index 2e47571..eba1776 100644 --- a/bbttest/utils.py +++ b/bbttest/utils.py @@ -68,12 +68,22 @@ def multiple_ropes_control_table( else: unknown_models.append(row["left_model"]) - rows.append({ - "rope": rope, - "better_models": better_models if return_as_array else join_char.join(better_models), - "equivalent_models": equivalent_models if return_as_array else join_char.join(equivalent_models), - "worse_models": worse_models if return_as_array else join_char.join(worse_models), - "unknown_models": unknown_models if return_as_array else join_char.join(unknown_models), - }) + rows.append( + { + "rope": rope, + "better_models": better_models + if return_as_array + else join_char.join(better_models), + "equivalent_models": equivalent_models + if return_as_array + else join_char.join(equivalent_models), + "worse_models": worse_models + if return_as_array + else join_char.join(worse_models), + "unknown_models": unknown_models + if return_as_array + else join_char.join(unknown_models), + } + ) return pd.DataFrame(rows)