diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f15b866..2f007bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,8 @@ jobs: uses: actions/checkout@v4 - name: Install uv uses: astral-sh/setup-uv@v4 + with: + enable-cache: true - name: Set up Python uses: actions/setup-python@v5 with: @@ -36,6 +38,8 @@ jobs: uses: actions/checkout@v4 - name: Install uv uses: astral-sh/setup-uv@v4 + with: + enable-cache: true - name: Set up Python uses: actions/setup-python@v5 with: @@ -58,6 +62,8 @@ jobs: workspaces: rust - name: Install uv uses: astral-sh/setup-uv@v4 + with: + enable-cache: true - name: Set up Python uses: actions/setup-python@v5 with: @@ -67,7 +73,9 @@ jobs: - name: Build Rust extension run: uv run maturin develop --manifest-path rust/Cargo.toml - name: Run tests - run: uv run pytest + # `-n auto` is also set in pyproject [tool.pytest.ini_options] addopts; kept + # explicit here so the intent is visible. Ubuntu runners have 2-4 cores. + run: uv run pytest -n auto rust: runs-on: ubuntu-latest diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index dde3f4e..068a78f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,7 +17,7 @@ jobs: - name: Build documentation run: uv run --group dev mkdocs build --strict - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./site diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5f11efb..2d0b989 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,6 @@ +# Fast, auto-fixing checks only. Slow, whole-repo gates (pyright, the full pytest +# suite, cargo-clippy, cargo-test) run in CI on every PR instead — running them in a +# pre-commit hook rebuilds the Rust extension and runs the whole suite on every commit. repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.15.12 @@ -9,20 +12,6 @@ repos: files: ^(src|tests)/.*\.py$ - repo: local hooks: - - id: pyright - name: pyright - entry: uv run pyright - language: system - types: [python] - files: ^(src|tests)/.*\.py$ - pass_filenames: false - - id: pytest - name: pytest - entry: bash -c 'uv run maturin develop --manifest-path rust/Cargo.toml && uv run pytest' - language: system - types: [python] - files: ^(src|tests|rust)/ - pass_filenames: false - id: cargo-fmt name: cargo-fmt entry: cargo fmt --check --manifest-path rust/Cargo.toml @@ -30,17 +19,3 @@ repos: types: [rust] files: ^rust/ pass_filenames: false - - id: cargo-clippy - name: cargo-clippy - entry: cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings - language: system - types: [rust] - files: ^rust/ - pass_filenames: false - - id: cargo-test - name: cargo-test - entry: cargo test --manifest-path rust/Cargo.toml - language: system - types: [rust] - files: ^rust/ - pass_filenames: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a6a82ab..1c60d91 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -100,13 +100,13 @@ What the gates cover: - **Ruff linting and formatting.** The current tree enforces core pycodestyle/pyflakes safety checks plus stale-suppression detection. It also enforces an expanded rule set covering common bug patterns (bugbear), simplifications, Python-version upgrades, pytest style, import order, and comprehensions. Treat `uv run ruff check .` and `uv run ruff format --check .` as the stable interface rather than relying on individual rule codes. - **Pyright.** Type checking runs through `uv run pyright`; the project is tightening this as a strict-inference ratchet over time. -- **Python tests.** The suite is offline and currently runs 324 tests in roughly 40-45 seconds, reporting coverage around 94%. +- **Python tests.** The suite is offline and runs in parallel by default via [pytest-xdist](https://pypi.org/project/pytest-xdist/) (`-n auto` in `pyproject.toml`): over 600 tests in ~20-30 seconds, reporting around 90% coverage in the default CI environment (`--extra qc`). Disable parallelism for a single serial run with `pytest -n 0`. - **Rust tests.** `cargo test --manifest-path rust/Cargo.toml` currently runs 46 Rust unit tests for the extension. - **Rust style and lints.** `cargo fmt --check` enforces formatting; clippy runs all targets with warnings denied. ## Pre-commit hooks -Install hooks after setup if you want the same checks to run automatically: +Install hooks after setup to run the fast, auto-fixing checks automatically on every commit. The slower whole-repo gates (pyright, the full pytest suite, cargo-clippy, cargo-test) are intentionally **not** pre-commit hooks — they run in CI on every pull request instead, so committing stays fast. ```bash uv run pre-commit install @@ -116,11 +116,7 @@ Configured hooks: - `ruff`: fixes lint issues in `src/` and `tests/` when possible. - `ruff-format`: formats Python files in `src/` and `tests/`. -- `pyright`: runs `uv run pyright` once per commit attempt. -- `pytest`: rebuilds the extension with `uv run maturin develop --manifest-path rust/Cargo.toml`, then runs `uv run pytest`. - `cargo-fmt`: runs `cargo fmt --check --manifest-path rust/Cargo.toml`. -- `cargo-clippy`: runs `cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings`. -- `cargo-test`: runs `cargo test --manifest-path rust/Cargo.toml`. ## Running subsets diff --git a/docs/installation.md b/docs/installation.md index b7a6193..b1ca98c 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -126,7 +126,7 @@ make check The underlying stable gates are `ruff check` / `ruff format --check`, `pyright`, `pytest`, and `cargo fmt --check` / `cargo test` / `cargo clippy --all-targets -- -D warnings` (see [Development](development.md)). -Install pre-commit hooks if you want the gates to run automatically before commits: +Install pre-commit hooks to run the fast lint/format checks automatically before commits (the full gates run in CI): ```bash uv run pre-commit install diff --git a/pyproject.toml b/pyproject.toml index 38f9fab..b6edd55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,11 +98,14 @@ dev = [ "pytest>=9.0.2", "pytest-cov>=7.1.0", "ruff>=0.15.6", + "pytest-xdist>=3.8.0", ] [tool.pytest.ini_options] testpaths = ["tests"] -addopts = "--cov=tablassert --cov-report=term-missing" +# Parallel by default via pytest-xdist (~7x faster full suite, identical coverage). +# Disable for a single serial run with `-n 0` (e.g. debugging one test). +addopts = "--cov=tablassert --cov-report=term-missing -n auto" markers = ["network: requires internet"] [tool.coverage.run] diff --git a/uv.lock b/uv.lock index 953b40d..9566af1 100644 --- a/uv.lock +++ b/uv.lock @@ -823,6 +823,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, ] +[[package]] +name = "execnet" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, +] + [[package]] name = "fastexcel" version = "0.20.2" @@ -2881,6 +2890,19 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/dc/1e/fb11174c9eaebcec27d36e9e994b90ffa168bc3226925900b9dbbf16c9da/pytest-logging-2015.11.4.tar.gz", hash = "sha256:cec5c85ecf18aab7b2ead5498a31b9f758680ef5a902b9054ab3f2bdbb77c896", size = 3916, upload-time = "2015-11-04T12:15:54.122Z" } +[[package]] +name = "pytest-xdist" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -3900,7 +3922,7 @@ wheels = [ [[package]] name = "tablassert" -version = "8.0.0" +version = "8.0.1" source = { editable = "." } dependencies = [ { name = "biolink-model" }, @@ -3937,6 +3959,7 @@ dev = [ { name = "pyright" }, { name = "pytest" }, { name = "pytest-cov" }, + { name = "pytest-xdist" }, { name = "ruff" }, ] @@ -3969,6 +3992,7 @@ dev = [ { name = "pyright", specifier = ">=1.1.411" }, { name = "pytest", specifier = ">=9.0.2" }, { name = "pytest-cov", specifier = ">=7.1.0" }, + { name = "pytest-xdist", specifier = ">=3.8.0" }, { name = "ruff", specifier = ">=0.15.6" }, ]