From ce3c191687543108937a92cfaa3bff41314c7ff3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 20:54:15 +0000 Subject: [PATCH 1/2] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/biomejs/pre-commit: v2.5.2 → v2.6.3](https://github.com/biomejs/pre-commit/compare/v2.5.2...v2.6.3) - [github.com/tox-dev/pyproject-fmt: v2.25.1 → v2.30.3](https://github.com/tox-dev/pyproject-fmt/compare/v2.25.1...v2.30.3) - [github.com/astral-sh/ruff-pre-commit: v0.15.20 → v0.17.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.20...v0.17.2) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c7982e6..6cc9931 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,16 +7,16 @@ default_stages: minimum_pre_commit_version: 2.16.0 repos: - repo: https://github.com/biomejs/pre-commit - rev: v2.5.2 + rev: v2.6.3 hooks: - id: biome-format exclude: ^\.cruft\.json$ # inconsistent indentation with cruft - file never to be modified manually. - repo: https://github.com/tox-dev/pyproject-fmt - rev: v2.25.1 + rev: v2.30.3 hooks: - id: pyproject-fmt - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.20 + rev: v0.17.2 hooks: - id: ruff-check types_or: [python, pyi, jupyter] From 6d1dd4358d330c67c0f9498672504a61cf44143e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 20:54:26 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .codecov.yaml | 22 +-- .github/ISSUE_TEMPLATE/bug_report.yml | 168 +++++++++--------- .github/ISSUE_TEMPLATE/config.yml | 6 +- .github/ISSUE_TEMPLATE/feature_request.yml | 14 +- .github/workflows/build.yaml | 38 ++-- .github/workflows/release.yaml | 42 ++--- .github/workflows/test.yaml | 194 ++++++++++----------- .pre-commit-config.yaml | 66 +++---- .readthedocs.yaml | 26 +-- AGENTS.md | 26 +-- README.md | 9 + REVIEW_GUIDE.md | 18 +- docs/contributing.md | 2 +- pyproject.toml | 3 +- 14 files changed, 322 insertions(+), 312 deletions(-) diff --git a/.codecov.yaml b/.codecov.yaml index d0c0e29..829e56c 100644 --- a/.codecov.yaml +++ b/.codecov.yaml @@ -1,17 +1,17 @@ # Based on pydata/xarray codecov: - require_ci_to_pass: no + require_ci_to_pass: no coverage: - status: - project: - default: - # Require 1% coverage, i.e., always succeed - target: 1 - patch: false - changes: false + status: + project: + default: + # Require 1% coverage, i.e., always succeed + target: 1 + patch: false + changes: false comment: - layout: diff, flags, files - behavior: once - require_base: no + layout: diff, flags, files + behavior: once + require_base: no diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index c91d8e9..c6247a8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -2,93 +2,93 @@ name: Bug report description: Report something that is broken or incorrect type: Bug body: - - type: markdown - attributes: - value: | - **Note**: Please read [this guide](https://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports) - detailing how to provide the necessary information for us to reproduce your bug. In brief: - * Please provide exact steps how to reproduce the bug in a clean Python environment. - * In case it's not clear what's causing this bug, please provide the data or the data generation procedure. - * Sometimes it is not possible to share the data, but usually it is possible to replicate problems on publicly - available datasets or to share a subset of your data. + - type: markdown + attributes: + value: | + **Note**: Please read [this guide](https://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports) + detailing how to provide the necessary information for us to reproduce your bug. In brief: + * Please provide exact steps how to reproduce the bug in a clean Python environment. + * In case it's not clear what's causing this bug, please provide the data or the data generation procedure. + * Sometimes it is not possible to share the data, but usually it is possible to replicate problems on publicly + available datasets or to share a subset of your data. - - type: textarea - id: report - attributes: - label: Report - description: A clear and concise description of what the bug is. - validations: - required: true + - type: textarea + id: report + attributes: + label: Report + description: A clear and concise description of what the bug is. + validations: + required: true - - type: textarea - id: versions - attributes: - label: Versions - description: | - Which version of packages. + - type: textarea + id: versions + attributes: + label: Versions + description: | + Which version of packages. - Please install `session-info2`, run the following command in a notebook, - click the “Copy as Markdown” button, then paste the results into the text box below. + Please install `session-info2`, run the following command in a notebook, + click the “Copy as Markdown” button, then paste the results into the text box below. - ```python - In[1]: import session_info2; session_info2.session_info(dependencies=True) - ``` + ```python + In[1]: import session_info2; session_info2.session_info(dependencies=True) + ``` - Alternatively, run this in a console: + Alternatively, run this in a console: - ```python - >>> import session_info2; print(session_info2.session_info(dependencies=True)._repr_mimebundle_()["text/markdown"]) - ``` - render: python - placeholder: | - anndata 0.11.3 - ---- ---- - charset-normalizer 3.4.1 - coverage 7.7.0 - psutil 7.0.0 - dask 2024.7.1 - jaraco.context 5.3.0 - numcodecs 0.15.1 - jaraco.functools 4.0.1 - Jinja2 3.1.6 - sphinxcontrib-jsmath 1.0.1 - sphinxcontrib-htmlhelp 2.1.0 - toolz 1.0.0 - session-info2 0.1.2 - PyYAML 6.0.2 - llvmlite 0.44.0 - scipy 1.15.2 - pandas 2.2.3 - sphinxcontrib-devhelp 2.0.0 - h5py 3.13.0 - tblib 3.0.0 - setuptools-scm 8.2.0 - more-itertools 10.3.0 - msgpack 1.1.0 - sparse 0.15.5 - wrapt 1.17.2 - jaraco.collections 5.1.0 - numba 0.61.0 - pyarrow 19.0.1 - pytz 2025.1 - MarkupSafe 3.0.2 - crc32c 2.7.1 - sphinxcontrib-qthelp 2.0.0 - sphinxcontrib-serializinghtml 2.0.0 - zarr 2.18.4 - asciitree 0.3.3 - six 1.17.0 - sphinxcontrib-applehelp 2.0.0 - numpy 2.1.3 - cloudpickle 3.1.1 - sphinxcontrib-bibtex 2.6.3 - natsort 8.4.0 - jaraco.text 3.12.1 - setuptools 76.1.0 - Deprecated 1.2.18 - packaging 24.2 - python-dateutil 2.9.0.post0 - ---- ---- - Python 3.13.2 | packaged by conda-forge | (main, Feb 17 2025, 14:10:22) [GCC 13.3.0] - OS Linux-6.11.0-109019-tuxedo-x86_64-with-glibc2.39 - Updated 2025-03-18 15:47 + ```python + >>> import session_info2; print(session_info2.session_info(dependencies=True)._repr_mimebundle_()["text/markdown"]) + ``` + render: python + placeholder: | + anndata 0.11.3 + ---- ---- + charset-normalizer 3.4.1 + coverage 7.7.0 + psutil 7.0.0 + dask 2024.7.1 + jaraco.context 5.3.0 + numcodecs 0.15.1 + jaraco.functools 4.0.1 + Jinja2 3.1.6 + sphinxcontrib-jsmath 1.0.1 + sphinxcontrib-htmlhelp 2.1.0 + toolz 1.0.0 + session-info2 0.1.2 + PyYAML 6.0.2 + llvmlite 0.44.0 + scipy 1.15.2 + pandas 2.2.3 + sphinxcontrib-devhelp 2.0.0 + h5py 3.13.0 + tblib 3.0.0 + setuptools-scm 8.2.0 + more-itertools 10.3.0 + msgpack 1.1.0 + sparse 0.15.5 + wrapt 1.17.2 + jaraco.collections 5.1.0 + numba 0.61.0 + pyarrow 19.0.1 + pytz 2025.1 + MarkupSafe 3.0.2 + crc32c 2.7.1 + sphinxcontrib-qthelp 2.0.0 + sphinxcontrib-serializinghtml 2.0.0 + zarr 2.18.4 + asciitree 0.3.3 + six 1.17.0 + sphinxcontrib-applehelp 2.0.0 + numpy 2.1.3 + cloudpickle 3.1.1 + sphinxcontrib-bibtex 2.6.3 + natsort 8.4.0 + jaraco.text 3.12.1 + setuptools 76.1.0 + Deprecated 1.2.18 + packaging 24.2 + python-dateutil 2.9.0.post0 + ---- ---- + Python 3.13.2 | packaged by conda-forge | (main, Feb 17 2025, 14:10:22) [GCC 13.3.0] + OS Linux-6.11.0-109019-tuxedo-x86_64-with-glibc2.39 + Updated 2025-03-18 15:47 diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 5b62547..5cad625 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: false contact_links: - - name: Scverse Community Forum - url: https://discourse.scverse.org/ - about: If you have questions about “How to do X”, please ask them here. + - name: Scverse Community Forum + url: https://discourse.scverse.org/ + about: If you have questions about “How to do X”, please ask them here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 6f13514..2675fb8 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -2,10 +2,10 @@ name: Feature request description: Propose a new feature for cell-annotator type: Enhancement body: - - type: textarea - id: description - attributes: - label: Description of feature - description: Please describe your suggestion for a new feature. It might help to describe a problem or use case, plus any alternatives that you have considered. - validations: - required: true + - type: textarea + id: description + attributes: + label: Description of feature + description: Please describe your suggestion for a new feature. It might help to describe a problem or use case, plus any alternatives that you have considered. + validations: + required: true diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index c6ecc2f..07c7963 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,26 +1,26 @@ name: Check Build on: - push: - branches: [main] - pull_request: - branches: [main] + push: + branches: [main] + pull_request: + branches: [main] concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: - package: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - with: - filter: blob:none - fetch-depth: 0 - - name: Install uv - uses: astral-sh/setup-uv@v7 - - name: Build package - run: uv build - - name: Check package - run: uvx twine check --strict dist/*.whl + package: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + filter: blob:none + fetch-depth: 0 + - name: Install uv + uses: astral-sh/setup-uv@v7 + - name: Build package + run: uv build + - name: Check package + run: uvx twine check --strict dist/*.whl diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 0bc2162..000cd22 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,27 +1,27 @@ name: Release on: - release: - types: [published] + release: + types: [published] # Use "trusted publishing", see https://docs.pypi.org/trusted-publishers/ jobs: - release: - name: Upload release to PyPI - runs-on: ubuntu-latest - environment: - name: pypi - url: https://pypi.org/p/cell_annotator - permissions: - id-token: write # IMPORTANT: this permission is mandatory for trusted publishing - steps: - - uses: actions/checkout@v5 - with: - filter: blob:none - fetch-depth: 0 - - name: Install uv - uses: astral-sh/setup-uv@v7 - - name: Build package - run: uv build - - name: Publish package distributions to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + release: + name: Upload release to PyPI + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/cell_annotator + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + steps: + - uses: actions/checkout@v5 + with: + filter: blob:none + fetch-depth: 0 + - name: Install uv + uses: astral-sh/setup-uv@v7 + - name: Build package + run: uv build + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 969e3ec..1c743d3 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,112 +1,112 @@ name: Test on: - push: - branches: [main] - pull_request: - branches: [main] - schedule: - - cron: "0 5 1,15 * *" + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: "0 5 1,15 * *" concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true defaults: - run: - # to fail on error in multiline statements (-e), in pipes (-o pipefail), and on unset variables (-u). - shell: bash -euo pipefail {0} + run: + # to fail on error in multiline statements (-e), in pipes (-o pipefail), and on unset variables (-u). + shell: bash -euo pipefail {0} jobs: - # Get the test environment from hatch as defined in pyproject.toml. - # This ensures that the pyproject.toml is the single point of truth for test definitions and the same tests are - # run locally and on continuous integration. - # Check [[tool.hatch.envs.hatch-test.matrix]] in pyproject.toml and https://hatch.pypa.io/latest/environment/ for - # more details. - get-environments: - runs-on: ubuntu-latest - outputs: - envs: ${{ steps.get-envs.outputs.envs }} - steps: - - uses: actions/checkout@v4 - with: - filter: blob:none - fetch-depth: 0 - - name: Install uv - uses: astral-sh/setup-uv@v5 - - name: Get test environments - id: get-envs - run: | - ENVS_JSON=$(uvx hatch env show --json | jq -c 'to_entries - | map( - select(.key | startswith("hatch-test")) - | { - name: .key, - label: (if (.key | contains("pre")) then .key + " (PRE-RELEASE DEPENDENCIES)" else .key end), - python: .value.python - } - )') - echo "envs=${ENVS_JSON}" | tee $GITHUB_OUTPUT + # Get the test environment from hatch as defined in pyproject.toml. + # This ensures that the pyproject.toml is the single point of truth for test definitions and the same tests are + # run locally and on continuous integration. + # Check [[tool.hatch.envs.hatch-test.matrix]] in pyproject.toml and https://hatch.pypa.io/latest/environment/ for + # more details. + get-environments: + runs-on: ubuntu-latest + outputs: + envs: ${{ steps.get-envs.outputs.envs }} + steps: + - uses: actions/checkout@v4 + with: + filter: blob:none + fetch-depth: 0 + - name: Install uv + uses: astral-sh/setup-uv@v5 + - name: Get test environments + id: get-envs + run: | + ENVS_JSON=$(uvx hatch env show --json | jq -c 'to_entries + | map( + select(.key | startswith("hatch-test")) + | { + name: .key, + label: (if (.key | contains("pre")) then .key + " (PRE-RELEASE DEPENDENCIES)" else .key end), + python: .value.python + } + )') + echo "envs=${ENVS_JSON}" | tee $GITHUB_OUTPUT - # Run tests through hatch. Spawns a separate runner for each environment defined in the hatch matrix obtained above. - test: - needs: get-environments + # Run tests through hatch. Spawns a separate runner for each environment defined in the hatch matrix obtained above. + test: + needs: get-environments - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest] - env: ${{ fromJSON(needs.get-environments.outputs.envs) }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + env: ${{ fromJSON(needs.get-environments.outputs.envs) }} - name: ${{ matrix.env.label }} - runs-on: ${{ matrix.os }} + name: ${{ matrix.env.label }} + runs-on: ${{ matrix.os }} - env: - OS: ${{ matrix.os }} - PYTHON: ${{ matrix.python }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - - steps: - - uses: actions/checkout@v4 - with: - filter: blob:none - fetch-depth: 0 - - name: Install uv - uses: astral-sh/setup-uv@v5 - with: - python-version: ${{ matrix.env.python }} - cache-dependency-glob: pyproject.toml - - name: create hatch environment - run: uvx hatch env create ${{ matrix.env.name }} - - name: run tests using hatch env: - MPLBACKEND: agg - PLATFORM: ${{ matrix.os }} - DISPLAY: :42 - run: uvx hatch run ${{ matrix.env.name }}:run-cov -v --color=yes -n auto - - name: generate coverage report - run: | - # See https://coverage.readthedocs.io/en/latest/config.html#run-patch - test -f .coverage || uvx hatch run ${{ matrix.env.name }}:cov-combine - uvx hatch run ${{ matrix.env.name }}:cov-report # report visibly - uvx hatch run ${{ matrix.env.name }}:coverage xml # create report for upload - - name: Upload coverage - uses: codecov/codecov-action@v5 - with: - token: ${{ secrets.CODECOV_TOKEN }} + OS: ${{ matrix.os }} + PYTHON: ${{ matrix.python }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + + steps: + - uses: actions/checkout@v4 + with: + filter: blob:none + fetch-depth: 0 + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + python-version: ${{ matrix.env.python }} + cache-dependency-glob: pyproject.toml + - name: create hatch environment + run: uvx hatch env create ${{ matrix.env.name }} + - name: run tests using hatch + env: + MPLBACKEND: agg + PLATFORM: ${{ matrix.os }} + DISPLAY: :42 + run: uvx hatch run ${{ matrix.env.name }}:run-cov -v --color=yes -n auto + - name: generate coverage report + run: | + # See https://coverage.readthedocs.io/en/latest/config.html#run-patch + test -f .coverage || uvx hatch run ${{ matrix.env.name }}:cov-combine + uvx hatch run ${{ matrix.env.name }}:cov-report # report visibly + uvx hatch run ${{ matrix.env.name }}:coverage xml # create report for upload + - name: Upload coverage + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} - # Check that all tests defined above pass. This makes it easy to set a single "required" test in branch - # protection instead of having to update it frequently. See https://github.com/re-actors/alls-green#why. - check: - name: Tests pass in all hatch environments - if: always() - needs: - - get-environments - - test - runs-on: ubuntu-latest - steps: - - uses: re-actors/alls-green@release/v1 - with: - jobs: ${{ toJSON(needs) }} + # Check that all tests defined above pass. This makes it easy to set a single "required" test in branch + # protection instead of having to update it frequently. See https://github.com/re-actors/alls-green#why. + check: + name: Tests pass in all hatch environments + if: always() + needs: + - get-environments + - test + runs-on: ubuntu-latest + steps: + - uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6cc9931..cc2a617 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,38 +1,38 @@ fail_fast: false default_language_version: - python: python3 + python: python3 default_stages: - - pre-commit - - pre-push + - pre-commit + - pre-push minimum_pre_commit_version: 2.16.0 repos: - - repo: https://github.com/biomejs/pre-commit - rev: v2.6.3 - hooks: - - id: biome-format - exclude: ^\.cruft\.json$ # inconsistent indentation with cruft - file never to be modified manually. - - repo: https://github.com/tox-dev/pyproject-fmt - rev: v2.30.3 - hooks: - - id: pyproject-fmt - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.17.2 - hooks: - - id: ruff-check - types_or: [python, pyi, jupyter] - args: [--fix, --exit-non-zero-on-fix] - - id: ruff-format - types_or: [python, pyi, jupyter] - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v6.0.0 - hooks: - - id: detect-private-key - - id: check-ast - - id: end-of-file-fixer - - id: mixed-line-ending - args: [--fix=lf] - - id: trailing-whitespace - - id: check-case-conflict - # Check that there are no merge conflicts (could be generated by template sync) - - id: check-merge-conflict - args: [--assume-in-merge] + - repo: https://github.com/biomejs/pre-commit + rev: v2.6.3 + hooks: + - id: biome-format + exclude: ^\.cruft\.json$ # inconsistent indentation with cruft - file never to be modified manually. + - repo: https://github.com/tox-dev/pyproject-fmt + rev: v2.30.3 + hooks: + - id: pyproject-fmt + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.17.2 + hooks: + - id: ruff-check + types_or: [python, pyi, jupyter] + args: [--fix, --exit-non-zero-on-fix] + - id: ruff-format + types_or: [python, pyi, jupyter] + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: detect-private-key + - id: check-ast + - id: end-of-file-fixer + - id: mixed-line-ending + args: [--fix=lf] + - id: trailing-whitespace + - id: check-case-conflict + # Check that there are no merge conflicts (could be generated by template sync) + - id: check-merge-conflict + args: [--assume-in-merge] diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 6c28477..7f029f3 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,16 +1,16 @@ # https://docs.readthedocs.io/en/stable/config-file/v2.html version: 2 build: - os: ubuntu-24.04 - tools: - python: "3.13" - nodejs: latest - jobs: - create_environment: - - asdf plugin add uv - - asdf install uv latest - - asdf global uv latest - build: - html: - - uvx hatch run docs:build - - mv docs/_build $READTHEDOCS_OUTPUT + os: ubuntu-24.04 + tools: + python: "3.13" + nodejs: latest + jobs: + create_environment: + - asdf plugin add uv + - asdf install uv latest + - asdf global uv latest + build: + html: + - uvx hatch run docs:build + - mv docs/_build $READTHEDOCS_OUTPUT diff --git a/AGENTS.md b/AGENTS.md index aa1b06c..6270465 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,18 +16,18 @@ Every fact should have one owner. This file owns invariants and the reference ta ## Where To Find What -| Topic | Source of truth | -|-------|----------------| -| User-facing overview, installation, quickstart | `README.md` | -| Contributor setup, environments, docs build, release workflow | `docs/contributing.md` | -| Public API reference | `docs/api.md` and autosummary under `docs/generated/` | -| Tutorials and example notebooks | `docs/notebooks/tutorials/` | -| PR review workflow and risk areas | `REVIEW_GUIDE.md` | -| Test fixtures (providers, example AnnData) | `tests/conftest.py`, `tests/utils.py` | -| LLM prompt templates | `src/cell_annotator/_prompts.py` | -| Structured LLM response schemas | `src/cell_annotator/_response_formats.py` | -| Provider wiring (OpenAI / Anthropic / Gemini) | `src/cell_annotator/model/_providers.py`, `llm_interface.py`, `base_annotator.py` | -| Release notes | GitHub Releases () | +| Topic | Source of truth | +| ------------------------------------------------------------- | --------------------------------------------------------------------------------- | +| User-facing overview, installation, quickstart | `README.md` | +| Contributor setup, environments, docs build, release workflow | `docs/contributing.md` | +| Public API reference | `docs/api.md` and autosummary under `docs/generated/` | +| Tutorials and example notebooks | `docs/notebooks/tutorials/` | +| PR review workflow and risk areas | `REVIEW_GUIDE.md` | +| Test fixtures (providers, example AnnData) | `tests/conftest.py`, `tests/utils.py` | +| LLM prompt templates | `src/cell_annotator/_prompts.py` | +| Structured LLM response schemas | `src/cell_annotator/_response_formats.py` | +| Provider wiring (OpenAI / Anthropic / Gemini) | `src/cell_annotator/model/_providers.py`, `llm_interface.py`, `base_annotator.py` | +| Release notes | GitHub Releases () | ## Review Guidelines @@ -37,7 +37,7 @@ This file only owns the project invariants and source-of-truth map below. ## Critical Invariants -- **Data privacy.** Only cluster marker gene *names*, plus `species`, `tissue`, and (optionally) `stage`, are sent to LLM providers. Raw expression values, cell-level data, and user metadata must never cross the provider boundary. Any PR that broadens what is sent is a hard red flag. +- **Data privacy.** Only cluster marker gene _names_, plus `species`, `tissue`, and (optionally) `stage`, are sent to LLM providers. Raw expression values, cell-level data, and user metadata must never cross the provider boundary. Any PR that broadens what is sent is a hard red flag. - **Structured outputs only.** All LLM calls round-trip through Pydantic models in `_response_formats.py` (e.g. `CellTypeListOutput`, `ExpectedMarkerGeneOutput`, `CellTypeMappingOutput`). Never parse free-form LLM text in production paths; renaming or removing fields breaks user-visible contracts. - **Optional provider extras.** `openai`, `anthropic`, and `google-genai` are optional extras. Provider-specific imports must be guarded and must fail with a clear message when the extra is missing; they must not leak into import-time code paths of the top-level package. - **API keys stay in the environment.** Keys are loaded from env vars or a local `.env` via python-dotenv (`APIKeyManager`). They must never be logged, serialized, or included in error messages / exceptions. diff --git a/README.md b/README.md index 293135d..a9271af 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,13 @@ ## 📦 Installation + You need to have 🐍 Python 3.11 or newer installed on your system. If you don't have Python installed, we recommend installing [Mambaforge][]. ### 🚀 PyPI + Install by running: ```bash @@ -47,6 +49,7 @@ pip install cell-annotator ### 🛠️ Development version + To install the latest development version from [GitHub](https://github.com/quadbio/cell-annotator), run ```bash @@ -55,6 +58,7 @@ pip install git+https://github.com/quadbio/cell-annotator.git@main ## 🏁 Getting started + After installation, head over to the LLM provider of your choice to generate an API key 🔑. For example: - OpenAI: [API key](https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key) @@ -107,6 +111,7 @@ cell_ann.annotate_clusters(key_added="cell_type_predicted") ## 💸 Costs and models + CellAnnotator is LLM-agnostic and works with multiple providers: - **OpenAI (GPT models):** The default model is currently `gpt-4o-mini`, which is included in [OpenAI's Free Usage Tier](https://platform.openai.com/docs/guides/rate-limits). You can get started for free and experiment with our 📚 [tutorials](https://cell-annotator.readthedocs.io/en/latest/notebooks/tutorials/index.html) and your own data. For more accurate cell type labels in complex tissues, we recommend more powerful models like `gpt-4o`, `gpt-4.1`, or reasoning models like `o3-mini` (these may incur a small fee; e.g., running both tutorials with `o3-mini` costs around 1 USD). See the [OpenAI API docs](https://platform.openai.com/docs/models) for details. @@ -122,6 +127,7 @@ You can select your provider and model by setting the appropriate parameters. Mo ## 🔐 Data privacy + This package sends cluster marker genes, and the `species` and `tissue` you define, to the selected LLM provider (e.g., OpenAI, Google, Anthropic, or OpenRouter routes). **No actual gene expression values are sent.** Please ensure your usage of this package aligns with your institution's guidelines on data privacy and the use of external AI models. Each provider has its own privacy policy and terms of service. Review these carefully before using CellAnnotator with sensitive or regulated data. @@ -130,14 +136,17 @@ When using OpenRouter, requests are forwarded to the upstream provider implied b ## 🙏 Credits + This tool was inspired by [Hou et al., Nature Methods 2024](https://www.nature.com/articles/s41592-024-02235-4) and [https://github.com/VPetukhov/GPTCellAnnotator](https://github.com/VPetukhov/GPTCellAnnotator). ## 📬 Contact + If you found a bug, please use the [issue tracker][]. ## 📖 Citation + Please use our [zenodo][] entry to cite this software. [mambaforge]: https://github.com/conda-forge/miniforge#mambaforge diff --git a/REVIEW_GUIDE.md b/REVIEW_GUIDE.md index ae19a24..972d07d 100644 --- a/REVIEW_GUIDE.md +++ b/REVIEW_GUIDE.md @@ -18,7 +18,7 @@ Use `AGENTS.md` for architecture, invariants, and commands. Use this guide for r ## High-Risk Areas -- **Data privacy boundary.** Any change that broadens what is sent to an LLM provider beyond marker gene *names* + `species` / `tissue` / `stage` is a hard red flag. Audit new prompt construction code in `_prompts.py` and anything that builds payloads in `base_annotator.py` / `llm_interface.py`. +- **Data privacy boundary.** Any change that broadens what is sent to an LLM provider beyond marker gene _names_ + `species` / `tissue` / `stage` is a hard red flag. Audit new prompt construction code in `_prompts.py` and anything that builds payloads in `base_annotator.py` / `llm_interface.py`. - **Prompt template wording** (`src/cell_annotator/_prompts.py`): silent wording changes measurably shift annotation outputs. Treat these like behavior changes; they warrant a GitHub-release note. - **Pydantic response schemas** (`src/cell_annotator/_response_formats.py`): renaming, removing, or retyping fields breaks parsing of structured LLM outputs and is a serialized-contract change. - **Provider SDK integration** (`src/cell_annotator/model/_providers.py`, `llm_interface.py`, `base_annotator.py`): auth, retry, structured-output parsing, and error surfacing differ per provider; regressions are easy to miss with only one provider mocked. @@ -32,13 +32,13 @@ Use `AGENTS.md` for architecture, invariants, and commands. Use this guide for r Test directories mirror `src/cell_annotator/` — changes to `src/cell_annotator/X/` have matching tests under `tests/`. -| Changed path | Look at | -|---|---| -| `src/cell_annotator/model/` | `tests/model/` (per-class files: `test_cell_annotator.py`, `test_sample_annotator.py`, `test_base_annotator.py`, `test_llm_interface.py`, `test_obs_beautifier.py`) | -| `src/cell_annotator/utils.py` | `tests/test_utils.py` | -| `src/cell_annotator/check.py` | `tests/test_check.py` | -| `src/cell_annotator/_response_formats.py` or `_prompts.py` | downstream: `tests/model/test_base_annotator.py`, `tests/model/test_llm_interface.py` | -| Cross-cutting fixture changes | `tests/conftest.py`, `tests/utils.py` | +| Changed path | Look at | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `src/cell_annotator/model/` | `tests/model/` (per-class files: `test_cell_annotator.py`, `test_sample_annotator.py`, `test_base_annotator.py`, `test_llm_interface.py`, `test_obs_beautifier.py`) | +| `src/cell_annotator/utils.py` | `tests/test_utils.py` | +| `src/cell_annotator/check.py` | `tests/test_check.py` | +| `src/cell_annotator/_response_formats.py` or `_prompts.py` | downstream: `tests/model/test_base_annotator.py`, `tests/model/test_llm_interface.py` | +| Cross-cutting fixture changes | `tests/conftest.py`, `tests/utils.py` | ## Testing @@ -59,7 +59,7 @@ Apply these checks whenever the PR touches code or tests. - Distinguish critical regressions (data-privacy, provider-contract, AnnData-key, structured-output) from trivial or flaky failures. - Surface critical failures back to the author and ask them to fix before merge. -**Modified tests.** Scrutinize *how* existing tests were changed. +**Modified tests.** Scrutinize _how_ existing tests were changed. - PRs that only relax thresholds, remove assertions, delete cases, or loosen `parametrize` matrices are a red flag — tests-working-around-tests defeats the purpose. - Require an explicit justification in the PR body for any weakened assertion; do not accept silently. diff --git a/docs/contributing.md b/docs/contributing.md index 153c69f..496c2ce 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -11,7 +11,7 @@ the [scientific Python tutorials][], or the [scanpy developer guide][]. [scientific Python tutorials]: https://learn.scientific-python.org/development/tutorials/ [scanpy developer guide]: https://scanpy.readthedocs.io/en/latest/dev/index.html -:::{tip} The *hatch* project manager +:::{tip} The _hatch_ project manager We highly recommend to familiarize yourself with [`hatch`][hatch]. Hatch is a Python project manager that diff --git a/pyproject.toml b/pyproject.toml index 3fefbfe..2d927d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", ] dynamic = [ "version" ] dependencies = [ @@ -174,11 +175,11 @@ ini_options.markers = [ ini_options.xfail_strict = true [tool.coverage] +run.source = [ "cell_annotator" ] run.omit = [ "**/test_*.py", ] run.patch = [ "subprocess" ] -run.source = [ "cell_annotator" ] [tool.cruft] skip = [