From 61e7ba76ce394626e21690625815ac7699a1a35d Mon Sep 17 00:00:00 2001 From: scverse-bot <108668866+scverse-bot@users.noreply.github.com> Date: Wed, 2 Apr 2025 18:27:23 +0000 Subject: [PATCH 01/16] Automated template update to v0.5.0 --- .bumpversion.cfg | 8 - .codecov.yaml | 22 +- .cruft.json | 62 ++-- .editorconfig | 3 + .flake8 | 57 ---- .github/ISSUE_TEMPLATE/bug_report.yml | 169 +++++------ .github/ISSUE_TEMPLATE/config.yml | 6 +- .github/ISSUE_TEMPLATE/feature_request.yml | 14 +- .github/workflows/build.yaml | 46 +-- .github/workflows/release.yaml | 34 +++ .github/workflows/test.yaml | 107 ++++--- .gitignore | 12 +- .pre-commit-config.yaml | 116 +++----- .readthedocs.yaml | 22 +- .vscode/extensions.json | 18 ++ .vscode/launch.json | 33 +++ .vscode/settings.json | 18 ++ biome.jsonc | 16 + docs/_static/css/custom.css | 4 + docs/_templates/autosummary/class.rst | 14 +- docs/conf.py | 40 ++- docs/contributing.md | 247 ++++++++-------- docs/extensions/typed_returns.py | 19 +- docs/make.bat | 35 --- docs/template_usage.md | 322 --------------------- pyproject.toml | 183 +++++++----- 26 files changed, 684 insertions(+), 943 deletions(-) delete mode 100644 .bumpversion.cfg delete mode 100644 .flake8 create mode 100644 .github/workflows/release.yaml create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 biome.jsonc create mode 100644 docs/_static/css/custom.css delete mode 100644 docs/make.bat delete mode 100644 docs/template_usage.md diff --git a/.bumpversion.cfg b/.bumpversion.cfg deleted file mode 100644 index fdba9e4f..00000000 --- a/.bumpversion.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[bumpversion] -current_version = 0.0.1 -tag = True -commit = True - -[bumpversion:file:./pyproject.toml] -search = version = "{current_version}" -replace = version = "{new_version}" diff --git a/.codecov.yaml b/.codecov.yaml index 829e56c0..d0c0e291 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/.cruft.json b/.cruft.json index 02453102..02781f21 100644 --- a/.cruft.json +++ b/.cruft.json @@ -1,23 +1,43 @@ { - "template": "/Users/tim.treis/Documents/GitHub/cookiecutter-scverse", - "commit": "7cc5403b05e299d7a4bb169c2bd8c27a2a7676f3", - "checkout": null, - "context": { - "cookiecutter": { - "project_name": "spatialdata-plot", - "package_name": "spatialdata_plot", - "project_description": "A very interesting piece of code", - "author_full_name": "scverse", - "author_email": "scverse", - "github_user": "scverse", - "project_repo": "https://github.com/scverse/spatialdata-plot", - "license": "BSD 3-Clause License", - "_copy_without_render": [ - ".github/workflows/**.yaml", - "docs/_templates/autosummary/**.rst" - ], - "_template": "/Users/tim.treis/Documents/GitHub/cookiecutter-scverse" - } - }, - "directory": null + "template": "https://github.com/scverse/cookiecutter-scverse", + "commit": "94ef9fb6f9ad8cfe65a3d9575679c03c80c49cd1", + "checkout": "v0.5.0", + "context": { + "cookiecutter": { + "project_name": "spatialdata-plot", + "package_name": "spatialdata_plot", + "project_description": "A very interesting piece of code", + "author_full_name": "scverse", + "author_email": "scverse", + "github_user": "scverse", + "github_repo": "spatialdata-plot", + "license": "BSD 3-Clause License", + "ide_integration": true, + "_copy_without_render": [ + ".github/workflows/build.yaml", + ".github/workflows/test.yaml", + "docs/_templates/autosummary/**.rst" + ], + "_exclude_on_template_update": [ + "CHANGELOG.md", + "LICENSE", + "README.md", + "docs/api.md", + "docs/index.md", + "docs/notebooks/example.ipynb", + "docs/references.bib", + "docs/references.md", + "src/**", + "tests/**" + ], + "_render_devdocs": false, + "_jinja2_env_vars": { + "lstrip_blocks": true, + "trim_blocks": true + }, + "_template": "https://github.com/scverse/cookiecutter-scverse", + "_commit": "94ef9fb6f9ad8cfe65a3d9575679c03c80c49cd1" + } + }, + "directory": null } diff --git a/.editorconfig b/.editorconfig index 2fe0ce08..66678e37 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,5 +8,8 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true +[{*.{yml,yaml,toml},.cruft.json}] +indent_size = 2 + [Makefile] indent_style = tab diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 10cfc5a3..00000000 --- a/.flake8 +++ /dev/null @@ -1,57 +0,0 @@ -# Can't yet be moved to the pyproject.toml due to https://github.com/PyCQA/flake8/issues/234 -[flake8] -max-line-length = 120 -ignore = - # line break before a binary operator -> black does not adhere to PEP8 - W503 - # line break occured after a binary operator -> black does not adhere to PEP8 - W504 - # line too long -> we accept long comment lines; black gets rid of long code lines - E501 - # whitespace before : -> black does not adhere to PEP8 - E203 - # line break before binary operator -> black does not adhere to PEP8 - W503 - # missing whitespace after ,', ';', or ':' -> black does not adhere to PEP8 - E231 - # continuation line over-indented for hanging indent -> black does not adhere to PEP8 - E126 - # too many leading '#' for block comment -> this is fine for indicating sections - E262 - # Do not assign a lambda expression, use a def -> lambda expression assignments are convenient - E731 - # allow I, O, l as variable names -> I is the identity matrix - E741 - # Missing docstring in public package - D104 - # Missing docstring in public module - D100 - # Missing docstring in __init__ - D107 - # Errors from function calls in argument defaults. These are fine when the result is immutable. - B008 - # Missing docstring in magic method - D105 - # format string does contain unindexed parameters - P101 - # first line should end with a period [Bug: doesn't work with single-line docstrings] - D400 - # First line should be in imperative mood; try rephrasing - D401 -exclude = .git,__pycache__,build,docs/_build,dist -per-file-ignores = - tests/*: D - */__init__.py: F401 -rst-roles = - class, - func, - ref, - cite:p, - cite:t, -rst-directives = - envvar, - exception, -rst-substitutions = - version, -extend-ignore = - RST307, diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 9dfd4acb..3ca1ccbd 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -2,88 +2,93 @@ name: Bug report description: Report something that is broken or incorrect labels: 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 procecure. - * 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: Version information - description: | - Please paste below the output of + - type: textarea + id: versions + attributes: + label: Versions + description: | + Which version of packages. - ```python - import session_info - session_info.show(html=False, dependencies=True) - ``` - placeholder: | - ----- - anndata 0.8.0rc2.dev27+ge524389 - session_info 1.0.0 - ----- - asttokens NA - awkward 1.8.0 - backcall 0.2.0 - cython_runtime NA - dateutil 2.8.2 - debugpy 1.6.0 - decorator 5.1.1 - entrypoints 0.4 - executing 0.8.3 - h5py 3.7.0 - ipykernel 6.15.0 - jedi 0.18.1 - mpl_toolkits NA - natsort 8.1.0 - numpy 1.22.4 - packaging 21.3 - pandas 1.4.2 - parso 0.8.3 - pexpect 4.8.0 - pickleshare 0.7.5 - pkg_resources NA - prompt_toolkit 3.0.29 - psutil 5.9.1 - ptyprocess 0.7.0 - pure_eval 0.2.2 - pydev_ipython NA - pydevconsole NA - pydevd 2.8.0 - pydevd_file_utils NA - pydevd_plugins NA - pydevd_tracing NA - pygments 2.12.0 - pytz 2022.1 - scipy 1.8.1 - setuptools 62.5.0 - setuptools_scm NA - six 1.16.0 - stack_data 0.3.0 - tornado 6.1 - traitlets 5.3.0 - wcwidth 0.2.5 - zmq 23.1.0 - ----- - IPython 8.4.0 - jupyter_client 7.3.4 - jupyter_core 4.10.0 - ----- - Python 3.9.13 | packaged by conda-forge | (main, May 27 2022, 16:58:50) [GCC 10.3.0] - Linux-5.18.6-arch1-1-x86_64-with-glibc2.35 - ----- - Session information updated at 2022-07-07 17:55 + 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) + ``` + + 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 diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 5cad625d..5b62547f 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 90fe6251..ae9ca05b 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 spatialdata-plot labels: 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 02429433..83e01a1e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,23 +1,33 @@ 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 + +defaults: + run: + # to fail on error in multiline statements (-e), in pipes (-o pipefail), and on unset variables (-u). + shell: bash -euo pipefail {0} jobs: - package: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.10 - uses: actions/setup-python@v2 - with: - python-version: "3.10" - - name: Install build dependencies - run: python -m pip install --upgrade pip wheel twine build - - name: Build package - run: python -m build - - name: Check package - run: twine check --strict dist/*.whl + package: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + filter: blob:none + fetch-depth: 0 + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + cache-dependency-glob: pyproject.toml + - 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 new file mode 100644 index 00000000..6c23760f --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,34 @@ +name: Release + +on: + release: + types: [published] + +defaults: + run: + # to fail on error in multiline statements (-e), in pipes (-o pipefail), and on unset variables (-u). + shell: bash -euo pipefail {0} + +# 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/spatialdata_plot + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + steps: + - uses: actions/checkout@v4 + with: + filter: blob:none + fetch-depth: 0 + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + cache-dependency-glob: pyproject.toml + - 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 2f0ffd7a..d5cfb2a9 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,62 +1,59 @@ name: Test on: - push: - branches: [main] - pull_request: - branches: [main] + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: "0 5 1,15 * *" + +concurrency: + 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} jobs: - test: - runs-on: ${{ matrix.os }} - defaults: - run: - shell: bash -e {0} # -e to fail on error - - strategy: - fail-fast: false - matrix: - python: ["3.8", "3.10"] - os: [ubuntu-latest] + test: + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + python: "3.10" + - os: ubuntu-latest + python: "3.12" + - os: ubuntu-latest + python: "3.12" + pip-flags: "--pre" + name: PRE-RELEASE DEPENDENCIES + + name: ${{ matrix.name }} Python ${{ matrix.python }} + + env: + OS: ${{ matrix.os }} + PYTHON: ${{ matrix.python }} + steps: + - uses: actions/checkout@v4 + with: + filter: blob:none + fetch-depth: 0 + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + cache-dependency-glob: pyproject.toml + - name: run tests using hatch env: - OS: ${{ matrix.os }} - PYTHON: ${{ matrix.python }} - - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python }} - - - name: Get pip cache dir - id: pip-cache-dir - run: | - echo "::set-output name=dir::$(pip cache dir)" - - name: Restore pip cache - uses: actions/cache@v2 - with: - path: ${{ steps.pip-cache-dir.outputs.dir }} - key: pip-${{ runner.os }}-${{ env.pythonLocation }}-${{ hashFiles('**/pyproject.toml') }} - restore-keys: | - pip-${{ runner.os }}-${{ env.pythonLocation }}- - - name: Install test dependencies - run: | - python -m pip install --upgrade pip wheel - pip install codecov - - name: Install dependencies - run: | - pip install ".[dev,test]" - - name: Test - env: - MPLBACKEND: agg - PLATFORM: ${{ matrix.os }} - DISPLAY: :42 - run: | - pytest -v --cov --color=yes - - name: Upload coverage - env: - CODECOV_NAME: ${{ matrix.python }}-${{ matrix.os }} - run: | - codecov --required --flags=unittests + MPLBACKEND: agg + PLATFORM: ${{ matrix.os }} + DISPLAY: :42 + run: uvx hatch test --cover --python ${{ matrix.python }} + - name: Upload coverage + uses: codecov/codecov-action@v4 diff --git a/.gitignore b/.gitignore index 7bb0bd5e..31e10b3e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,20 @@ # Temp files .DS_Store *~ +buck-out/ # Compiled files +.venv/ __pycache__/ +.*cache/ # Distribution / packaging -/build/ /dist/ -/*.egg-info/ # Tests and coverage -/.pytest_cache/ -/.cache/ /data/ +/node_modules/ # docs /docs/generated/ /docs/_build/ - -# IDEs -/.idea/ -/.vscode/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 500ca49a..0fcce11e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,79 +1,47 @@ fail_fast: false default_language_version: - python: python3 + python: python3 default_stages: - - commit - - push + - pre-commit + - pre-push minimum_pre_commit_version: 2.16.0 repos: - - repo: https://github.com/psf/black - rev: 22.12.0 - hooks: - - id: black - - repo: https://github.com/pre-commit/mirrors-prettier - rev: v3.0.0-alpha.4 - hooks: - - id: prettier - - repo: https://github.com/asottile/blacken-docs - rev: v1.12.1 - hooks: - - id: blacken-docs - - repo: https://github.com/PyCQA/isort - rev: 5.11.4 - hooks: - - id: isort - - repo: https://github.com/asottile/yesqa - rev: v1.4.0 - hooks: - - id: yesqa - additional_dependencies: - - flake8-tidy-imports - - flake8-docstrings - - flake8-rst-docstrings - - flake8-comprehensions - - flake8-bugbear - - flake8-blind-except - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.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 - - repo: https://github.com/myint/autoflake - rev: v2.0.0 - hooks: - - id: autoflake - args: - - --in-place - - --remove-all-unused-imports - - --remove-unused-variable - - --ignore-init-module-imports - - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 - hooks: - - id: flake8 - additional_dependencies: - - flake8-tidy-imports - - flake8-docstrings - - flake8-rst-docstrings - - flake8-comprehensions - - flake8-bugbear - - flake8-blind-except - - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 - hooks: - - id: pyupgrade - args: [--py3-plus, --py38-plus, --keep-runtime-typing] - - repo: local - hooks: - - id: forbid-to-commit - name: Don't commit rej files - entry: | - Cannot commit .rej files. These indicate merge conflicts that arise during automated template updates. - Fix the merge conflicts manually and remove the .rej files. - language: fail - files: '.*\.rej$' + - repo: https://github.com/biomejs/pre-commit + rev: v1.9.4 + 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.5.1 + hooks: + - id: pyproject-fmt + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.11.2 + hooks: + - id: ruff + 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: v5.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: local + hooks: + - id: forbid-to-commit + name: Don't commit rej files + entry: | + Cannot commit .rej files. These indicate merge conflicts that arise during automated template updates. + Fix the merge conflicts manually and remove the .rej files. + language: fail + files: '.*\.rej$' diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 9e5d5fa2..69897c3b 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-20.04 - tools: - python: "3.10" + os: ubuntu-20.04 + tools: + python: "3.10" sphinx: - configuration: docs/conf.py - # disable this for more lenient docs builds - fail_on_warning: true + configuration: docs/conf.py + # disable this for more lenient docs builds + fail_on_warning: true python: - install: - - method: pip - path: . - extra_requirements: - - doc + install: + - method: pip + path: . + extra_requirements: + - doc diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..caaeb4f7 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,18 @@ +{ + "recommendations": [ + // GitHub integration + "github.vscode-github-actions", + "github.vscode-pull-request-github", + // Language support + "ms-python.python", + "ms-python.vscode-pylance", + "ms-toolsai.jupyter", + "tamasfe.even-better-toml", + // Dependency management + "ninoseki.vscode-mogami", + // Linting and formatting + "editorconfig.editorconfig", + "charliermarsh.ruff", + "biomejs.biome", + ], +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..36d18746 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,33 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Build Documentation", + "type": "debugpy", + "request": "launch", + "module": "sphinx", + "args": ["-M", "html", ".", "_build"], + "cwd": "${workspaceFolder}/docs", + "console": "internalConsole", + "justMyCode": false, + }, + { + "name": "Python: Debug Test", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "purpose": ["debug-test"], + "console": "internalConsole", + "justMyCode": false, + "env": { + "PYTEST_ADDOPTS": "--color=yes", + }, + "presentation": { + "hidden": true, + }, + }, + ], +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..e034b91f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,18 @@ +{ + "[python][json][jsonc]": { + "editor.formatOnSave": true, + }, + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.codeActionsOnSave": { + "source.fixAll": "always", + "source.organizeImports": "always", + }, + }, + "[json][jsonc]": { + "editor.defaultFormatter": "biomejs.biome", + }, + "python.analysis.typeCheckingMode": "basic", + "python.testing.pytestEnabled": true, + "python.testing.pytestArgs": ["-vv", "--color=yes"], +} diff --git a/biome.jsonc b/biome.jsonc new file mode 100644 index 00000000..2175c16e --- /dev/null +++ b/biome.jsonc @@ -0,0 +1,16 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "formatter": { "useEditorconfig": true }, + "overrides": [ + { + "include": ["./.vscode/*.json", "**/*.jsonc"], + "json": { + "formatter": { "trailingCommas": "all" }, + "parser": { + "allowComments": true, + "allowTrailingCommas": true, + }, + }, + }, + ], +} diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css new file mode 100644 index 00000000..b8c8d47f --- /dev/null +++ b/docs/_static/css/custom.css @@ -0,0 +1,4 @@ +/* Reduce the font size in data frames - See https://github.com/scverse/cookiecutter-scverse/issues/193 */ +div.cell_output table.dataframe { + font-size: 0.8em; +} diff --git a/docs/_templates/autosummary/class.rst b/docs/_templates/autosummary/class.rst index 17dc123c..7b4a0cf8 100644 --- a/docs/_templates/autosummary/class.rst +++ b/docs/_templates/autosummary/class.rst @@ -9,11 +9,11 @@ {% block attributes %} {% if attributes %} Attributes table -~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~ .. autosummary:: {% for item in attributes %} - ~{{ fullname }}.{{ item }} + ~{{ name }}.{{ item }} {%- endfor %} {% endif %} {% endblock %} @@ -26,7 +26,7 @@ Methods table .. autosummary:: {% for item in methods %} {%- if item != '__init__' %} - ~{{ fullname }}.{{ item }} + ~{{ name }}.{{ item }} {%- endif -%} {%- endfor %} {% endif %} @@ -35,13 +35,10 @@ Methods table {% block attributes_documentation %} {% if attributes %} Attributes -~~~~~~~~~~~ +~~~~~~~~~~ {% for item in attributes %} -{{ item }} -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - .. autoattribute:: {{ [objname, item] | join(".") }} {%- endfor %} @@ -56,9 +53,6 @@ Methods {% for item in methods %} {%- if item != '__init__' %} -{{ item }} -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - .. automethod:: {{ [objname, item] | join(".") }} {%- endif -%} {%- endfor %} diff --git a/docs/conf.py b/docs/conf.py index 02831db2..50bc4955 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,5 @@ # Configuration file for the Sphinx documentation builder. -# + # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html @@ -16,12 +16,15 @@ # -- Project information ----------------------------------------------------- +# NOTE: If you installed your project in editable mode, this might be stale. +# If this is the case, reinstall it to refresh the metadata info = metadata("spatialdata-plot") project_name = info["Name"] author = info["Author"] copyright = f"{datetime.now():%Y}, {author}." version = info["Version"] -repository_url = f"https://github.com/scverse/{project_name}" +urls = dict(pu.split(", ") for pu in info.get_all("Project-URL")) +repository_url = urls["Source"] # The full version, including alpha/beta/rc tags release = info["Version"] @@ -33,10 +36,10 @@ html_context = { "display_github": True, # Integrate GitHub - "github_user": "scverse", # Username - "github_repo": project_name, # Repo name - "github_version": "main", # Version - "conf_py_path": "/docs/", # Path in the checkout to the docs root + "github_user": "scverse", + "github_repo": project_name, + "github_version": "main", + "conf_py_path": "/docs/", } # -- General configuration --------------------------------------------------- @@ -52,8 +55,10 @@ "sphinx.ext.napoleon", "sphinxcontrib.bibtex", "sphinx_autodoc_typehints", + "sphinx_tabs.tabs", "sphinx.ext.mathjax", "IPython.sphinxext.ipython_console_highlighting", + "sphinxext.opengraph", *[p.stem for p in (HERE / "extensions").glob("*.py")], ] @@ -65,7 +70,7 @@ napoleon_include_init_with_doc = False napoleon_use_rtype = True # having a separate entry generally helps readability napoleon_use_param = True -myst_heading_anchors = 3 # create anchors for h1-h3 +myst_heading_anchors = 6 # create anchors for h1-h6 myst_enable_extensions = [ "amsmath", "colon_fence", @@ -87,7 +92,9 @@ } intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), "anndata": ("https://anndata.readthedocs.io/en/stable/", None), + "scanpy": ("https://scanpy.readthedocs.io/en/stable/", None), "numpy": ("https://numpy.org/doc/stable/", None), } @@ -104,11 +111,15 @@ # html_theme = "sphinx_book_theme" html_static_path = ["_static"] +html_css_files = ["css/custom.css"] + html_title = project_name html_theme_options = { "repository_url": repository_url, "use_repository_button": True, + "path_to_docs": "docs/", + "navigation_with_keys": False, } pygments_style = "default" @@ -118,18 +129,3 @@ # you can add an exception to this list. # ("py:class", "igraph.Graph"), ] - - -def setup(app): - """App setup hook.""" - app.add_config_value( - "recommonmark_config", - { - "auto_toc_tree_section": "Contents", - "enable_auto_toc_tree": True, - "enable_math": True, - "enable_inline_math": False, - "enable_eval_rst": True, - }, - True, - ) diff --git a/docs/contributing.md b/docs/contributing.md index be9ddee5..330a7d13 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -1,27 +1,49 @@ # Contributing guide -Scanpy provides extensive [developer documentation][scanpy developer guide], most of which applies to this repo, too. -This document will not reproduce the entire content from there. Instead, it aims at summarizing the most important -information to get you started on contributing. +Scanpy provides extensive [developer documentation][scanpy developer guide], most of which applies to this project, too. +This document will not reproduce the entire content from there. +Instead, it aims at summarizing the most important information to get you started on contributing. -We assume that you are already familiar with git and with making pull requests on GitHub. If not, please refer -to the [scanpy developer guide][]. +We assume that you are already familiar with git and with making pull requests on GitHub. +If not, please refer to the [scanpy developer guide][]. + +[scanpy developer guide]: https://scanpy.readthedocs.io/en/latest/dev/index.html ## Installing dev dependencies -In addition to the packages needed to _use_ this package, you need additional python packages to _run tests_ and _build -the documentation_. It's easy to install them using `pip`: +In addition to the packages needed to _use_ this package, +you need additional python packages to [run tests](#writing-tests) and [build the documentation](#docs-building). + +:::::{tabs} +::::{group-tab} Hatch +The easiest way is to get familiar with [hatch environments][], with which these tasks are simply: + +```bash +hatch test # defined in the table [tool.hatch.envs.hatch-test] in pyproject.toml +hatch run docs:build # defined in the table [tool.hatch.envs.docs] +``` + +:::: + +::::{group-tab} Pip +If you prefer managing environments manually, you can use `pip`: ```bash cd spatialdata-plot +python3 -m venv .venv +source .venv/bin/activate pip install -e ".[dev,test,doc]" ``` +:::: +::::: + +[hatch environments]: https://hatch.pypa.io/latest/tutorials/environment/basic-usage/ + ## Code-style -This template uses [pre-commit][] to enforce consistent code-styles. On every commit, pre-commit checks will either -automatically fix issues with the code, or raise an error message. See [pre-commit checks](template_usage.md#pre-commit-checks) for -a full list of checks enabled for this repository. +This package uses [pre-commit][] to enforce consistent code-styles. +On every commit, pre-commit checks will either automatically fix issues with the code, or raise an error message. To enable pre-commit locally, simply run @@ -29,10 +51,11 @@ To enable pre-commit locally, simply run pre-commit install ``` -in the root of the repository. Pre-commit will automatically download all dependencies when it is run for the first time. +in the root of the repository. +Pre-commit will automatically download all dependencies when it is run for the first time. -Alternatively, you can rely on the [pre-commit.ci][] service enabled on GitHub. If you didn't run `pre-commit` before -pushing changes to GitHub it will automatically commit fixes to your pull request, or show an error message. +Alternatively, you can rely on the [pre-commit.ci][] service enabled on GitHub. +If you didn't run `pre-commit` before pushing changes to GitHub it will automatically commit fixes to your pull request, or show an error message. If pre-commit.ci added a commit on a branch you still have been working on locally, simply use @@ -43,155 +66,149 @@ git pull --rebase to integrate the changes into yours. While the [pre-commit.ci][] is useful, we strongly encourage installing and running pre-commit locally first to understand its usage. -Finally, most editors have an _autoformat on save_ feature. Consider enabling this option for [black][black-editors] -and [prettier][prettier-editors]. +Finally, most editors have an _autoformat on save_ feature. +Consider enabling this option for [ruff][ruff-editors] and [biome][biome-editors]. -[black-editors]: https://black.readthedocs.io/en/stable/integrations/editors.html -[prettier-editors]: https://prettier.io/docs/en/editors.html +[pre-commit]: https://pre-commit.com/ +[pre-commit.ci]: https://pre-commit.ci/ +[ruff-editors]: https://docs.astral.sh/ruff/integrations/ +[biome-editors]: https://biomejs.dev/guides/integrate-in-editor/ -## Writing tests +(writing-tests)= -```{note} -Remember to first install the package with `pip install '-e[dev,test]'` -``` +## Writing tests -This package uses the [pytest][] for automated testing. Please [write tests][scanpy-test-docs] for every function added -to the package. +This package uses [pytest][] for automated testing. +Please write {doc}`scanpy:dev/testing` for every function added to the package. -Most IDEs integrate with pytest and provide a GUI to run tests. Alternatively, you can run all tests from the -command line by executing +Most IDEs integrate with pytest and provide a GUI to run tests. +Just point yours to one of the environments returned by ```bash -pytest +hatch env create hatch-test # create test environments for all supported versions +hatch env find hatch-test # list all possible test environment paths ``` -in the root of the repository. Continuous integration will automatically run the tests on all pull requests. - -[scanpy-test-docs]: https://scanpy.readthedocs.io/en/latest/dev/testing.html#writing-tests +Alternatively, you can run all tests from the command line by executing -## Publishing a release - -### Updating the version number - -Before making a release, you need to update the version number. Please adhere to [Semantic Versioning][semver], in brief - -> Given a version number MAJOR.MINOR.PATCH, increment the: -> -> 1. MAJOR version when you make incompatible API changes, -> 2. MINOR version when you add functionality in a backwards compatible manner, and -> 3. PATCH version when you make backwards compatible bug fixes. -> -> Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format. - -We use [bump2version][] to automatically update the version number in all places and automatically create a git tag. -Run one of the following commands in the root of the repository +:::::{tabs} +::::{group-tab} Hatch ```bash -bump2version patch -bump2version minor -bump2version major +hatch test # test with the highest supported Python version +# or +hatch test --all # test with all supported Python versions ``` -Once you are done, run - -``` -git push --tags -``` +:::: -to publish the created tag on GitHub. +::::{group-tab} Pip -[bump2version]: https://github.com/c4urself/bump2version +```bash +source .venv/bin/activate +pytest +``` -### Building and publishing the package on PyPI +:::: +::::: -Python packages are not distributed as source code, but as _distributions_. The most common distribution format is the so-called _wheel_. To build a _wheel_, run +in the root of the repository. -```bash -python -m build -``` +[pytest]: https://docs.pytest.org/ -This command creates a _source archive_ and a _wheel_, which are required for publishing your package to [PyPI][]. These files are created directly in the root of the repository. +### Continuous integration -Before uploading them to [PyPI][] you can check that your _distribution_ is valid by running: +Continuous integration will automatically run the tests on all pull requests and test +against the minimum and maximum supported Python version. -```bash -twine check dist/* -``` +Additionally, there's a CI job that tests against pre-releases of all dependencies (if there are any). +The purpose of this check is to detect incompatibilities of new package versions early on and +gives you time to fix the issue or reach out to the developers of the dependency before the package is released to a wider audience. -and finally publishing it with: +## Publishing a release -```bash -twine upload dist/* -``` +### Updating the version number -Provide your username and password when requested and then go check out your package on [PyPI][]! +Before making a release, you need to update the version number in the `pyproject.toml` file. +Please adhere to [Semantic Versioning][semver], in brief -For more information, follow the [Python packaging tutorial][]. +> Given a version number MAJOR.MINOR.PATCH, increment the: +> +> 1. MAJOR version when you make incompatible API changes, +> 2. MINOR version when you add functionality in a backwards compatible manner, and +> 3. PATCH version when you make backwards compatible bug fixes. +> +> Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format. -It is possible to automate this with GitHub actions, see also [this feature request][pypi-feature-request] -in the cookiecutter-scverse template. +Once you are done, commit and push your changes and navigate to the "Releases" page of this project on GitHub. +Specify `vX.X.X` as a tag name and create a release. +For more information, see [managing GitHub releases][]. +This will automatically create a git tag and trigger a Github workflow that creates a release on [PyPI][]. -[python packaging tutorial]: https://packaging.python.org/en/latest/tutorials/packaging-projects/#generating-distribution-archives -[pypi-feature-request]: https://github.com/scverse/cookiecutter-scverse/issues/88 +[semver]: https://semver.org/ +[managing GitHub releases]: https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository +[pypi]: https://pypi.org/ ## Writing documentation -Please write documentation for new or changed features and use-cases. This project uses [sphinx][] with the following features: +Please write documentation for new or changed features and use-cases. +This project uses [sphinx][] with the following features: -- the [myst][] extension allows to write documentation in markdown/Markedly Structured Text -- [Numpy-style docstrings][numpydoc] (through the [napoloen][numpydoc-napoleon] extension). -- Jupyter notebooks as tutorials through [myst-nb][] (See [Tutorials with myst-nb](#tutorials-with-myst-nb-and-jupyter-notebooks)) -- [Sphinx autodoc typehints][], to automatically reference annotated input and output types +- The [myst][] extension allows to write documentation in markdown/Markedly Structured Text +- [Numpy-style docstrings][numpydoc] (through the [napoloen][numpydoc-napoleon] extension). +- Jupyter notebooks as tutorials through [myst-nb][] (See [Tutorials with myst-nb](#tutorials-with-myst-nb-and-jupyter-notebooks)) +- [sphinx-autodoc-typehints][], to automatically reference annotated input and output types +- Citations (like {cite:p}`Virshup_2023`) can be included with [sphinxcontrib-bibtex](https://sphinxcontrib-bibtex.readthedocs.io/) -See the [scanpy developer docs](https://scanpy.readthedocs.io/en/latest/dev/documentation.html) for more information -on how to write documentation. +See scanpy’s {doc}`scanpy:dev/documentation` for more information on how to write your own. + +[sphinx]: https://www.sphinx-doc.org/en/master/ +[myst]: https://myst-parser.readthedocs.io/en/latest/intro.html +[myst-nb]: https://myst-nb.readthedocs.io/en/latest/ +[numpydoc-napoleon]: https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html +[numpydoc]: https://numpydoc.readthedocs.io/en/latest/format.html +[sphinx-autodoc-typehints]: https://github.com/tox-dev/sphinx-autodoc-typehints ### Tutorials with myst-nb and jupyter notebooks The documentation is set-up to render jupyter notebooks stored in the `docs/notebooks` directory using [myst-nb][]. Currently, only notebooks in `.ipynb` format are supported that will be included with both their input and output cells. -It is your reponsibility to update and re-run the notebook whenever necessary. +It is your responsibility to update and re-run the notebook whenever necessary. -If you are interested in automatically running notebooks as part of the continuous integration, please check -out [this feature request](https://github.com/scverse/cookiecutter-scverse/issues/40) in the `cookiecutter-scverse` -repository. +If you are interested in automatically running notebooks as part of the continuous integration, +please check out [this feature request][issue-render-notebooks] in the `cookiecutter-scverse` repository. + +[issue-render-notebooks]: https://github.com/scverse/cookiecutter-scverse/issues/40 #### Hints -- If you refer to objects from other packages, please add an entry to `intersphinx_mapping` in `docs/conf.py`. Only - if you do so can sphinx automatically create a link to the external documentation. -- If building the documentation fails because of a missing link that is outside your control, you can add an entry to - the `nitpick_ignore` list in `docs/conf.py` +- If you refer to objects from other packages, please add an entry to `intersphinx_mapping` in `docs/conf.py`. + Only if you do so can sphinx automatically create a link to the external documentation. +- If building the documentation fails because of a missing link that is outside your control, + you can add an entry to the `nitpick_ignore` list in `docs/conf.py` + +(docs-building)= #### Building the docs locally +:::::{tabs} +::::{group-tab} Hatch + ```bash +hatch run docs:build +hatch run docs:open +``` + +:::: + +::::{group-tab} Pip + +```bash +source .venv/bin/activate cd docs make html -open _build/html/index.html +(xdg-)open _build/html/index.html ``` - - -[scanpy developer guide]: https://scanpy.readthedocs.io/en/latest/dev/index.html -[cookiecutter-scverse-instance]: https://cookiecutter-scverse-instance.readthedocs.io/en/latest/template_usage.html -[github quickstart guide]: https://docs.github.com/en/get-started/quickstart/create-a-repo?tool=webui -[codecov]: https://about.codecov.io/sign-up/ -[codecov docs]: https://docs.codecov.com/docs -[codecov bot]: https://docs.codecov.com/docs/team-bot -[codecov app]: https://github.com/apps/codecov -[pre-commit.ci]: https://pre-commit.ci/ -[readthedocs.org]: https://readthedocs.org/ -[myst-nb]: https://myst-nb.readthedocs.io/en/latest/ -[jupytext]: https://jupytext.readthedocs.io/en/latest/ -[pre-commit]: https://pre-commit.com/ -[anndata]: https://github.com/scverse/anndata -[mudata]: https://github.com/scverse/mudata -[pytest]: https://docs.pytest.org/ -[semver]: https://semver.org/ -[sphinx]: https://www.sphinx-doc.org/en/master/ -[myst]: https://myst-parser.readthedocs.io/en/latest/intro.html -[numpydoc-napoleon]: https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html -[numpydoc]: https://numpydoc.readthedocs.io/en/latest/format.html -[sphinx autodoc typehints]: https://github.com/tox-dev/sphinx-autodoc-typehints -[pypi]: https://pypi.org/ +:::: +::::: diff --git a/docs/extensions/typed_returns.py b/docs/extensions/typed_returns.py index 94478130..0fbffefe 100644 --- a/docs/extensions/typed_returns.py +++ b/docs/extensions/typed_returns.py @@ -1,24 +1,27 @@ # code from https://github.com/theislab/scanpy/blob/master/docs/extensions/typed_returns.py # with some minor adjustment +from __future__ import annotations + import re +from collections.abc import Generator, Iterable from sphinx.application import Sphinx from sphinx.ext.napoleon import NumpyDocstring -def _process_return(lines): +def _process_return(lines: Iterable[str]) -> Generator[str, None, None]: for line in lines: - m = re.fullmatch(r"(?P\w+)\s+:\s+(?P[\w.]+)", line) - if m: - # Once this is in scanpydoc, we can use the fancy hover stuff - yield f'-{m["param"]} (:class:`~{m["type"]}`)' + if m := re.fullmatch(r"(?P\w+)\s+:\s+(?P[\w.]+)", line): + yield f"-{m['param']} (:class:`~{m['type']}`)" else: yield line -def _parse_returns_section(self, section): - lines_raw = list(_process_return(self._dedent(self._consume_to_next_section()))) - lines = self._format_block(":returns: ", lines_raw) +def _parse_returns_section(self: NumpyDocstring, section: str) -> list[str]: + lines_raw = self._dedent(self._consume_to_next_section()) + if lines_raw[0] == ":": + del lines_raw[0] + lines = self._format_block(":returns: ", list(_process_return(lines_raw))) if lines and lines[-1]: lines.append("") return lines diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 954237b9..00000000 --- a/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=_build - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.https://www.sphinx-doc.org/ - exit /b 1 -) - -if "%1" == "" goto help - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/docs/template_usage.md b/docs/template_usage.md deleted file mode 100644 index 6b39e178..00000000 --- a/docs/template_usage.md +++ /dev/null @@ -1,322 +0,0 @@ -# Using this template - -Welcome to the developer guidelines! This document is split into two parts: - -1. The [repository setup](#setting-up-the-repository). This section is relevant primarily for the repository maintainer and shows how to connect - continuous integration services and documents initial set-up of the repository. -2. The [contributor guide](contributing.md#contributing-guide). It contains information relevant to all developers who want to make a contribution. - -## Setting up the repository - -### First commit - -If you are reading this, you should have just completed the repository creation with : - -```bash -cruft create https://github.com/scverse/cookiecutter-scverse -``` - -and you should have - -``` -cd spatialdata-plot -``` - -into the new project directory. Now that you have created a new repository locally, the first step is to push it to github. To do this, you'd have to create a **new repository** on github. -You can follow the instructions directly on [github quickstart guide][]. -Since `cruft` already populated the local repository of your project with all the necessary files, we suggest to _NOT_ initialize the repository with a `README.md` file or `.gitignore`, because you might encounter git conflicts on your first push. -If you are familiar with git and knows how to handle git conflicts, you can go ahead with your preferred choice. - -:::{note} -If you are looking at this document in the [cookiecutter-scverse-instance][] repository documentation, throughout this document the name of the project is `cookiecutter-scverse-instance`. Otherwise it should be replaced by your new project name: `spatialdata-plot`. -::: - -Now that your new project repository has been created on github at `https://github.com/scverse/spatialdata-plot` you can push your first commit to github. -To do this, simply follow the instructions on your github repository page or a more verbose walkthrough here: - -Assuming you are in `/your/path/to/spatialdata-plot`. Add all files and commit. - -```bash -# stage all files of your new repo -git add --all -# commit -git commit -m "first commit" -``` - -You'll notice that the command `git commit` installed a bunch of packages and triggered their execution: those are pre-commit! To read more about what they are and what they do, you can go to the related section [Pre-commit checks](#pre-commit-checks) in this document. - -:::{note} -There is a chance that `git commit -m "first commit"` fails due to the `prettier` pre-commit formatting the file `.cruft.json`. No problem, you have just experienced what pre-commit checks do in action. Just go ahead and re-add the modified file and try to commit again: - -```bash - git add -u # update all tracked file - git commit -m "first commit" -``` - -::: - -Now that all the files of the newly created project have been committed, go ahead with the remaining steps: - -```bash -# update the `origin` of your local repo with the remote github link -git remote add origin https://github.com/scverse/spatialdata-plot.git -# rename the default branch to main -git branch -M main -# push all your files to remote -git push -u origin main -``` - -Your project should be now available at `https://github.com/scverse/spatialdata-plot`. While the repository at this point can be directly used, there are few remaining steps that needs to be done in order to achieve full functionality. - -### Coverage tests with _Codecov_ - -Coverage tells what fraction of the code is "covered" by unit tests, thereby encouraging contributors to -[write tests](contributing.md#writing-tests). -To enable coverage checks, head over to [codecov][] and sign in with your GitHub account. -You'll find more information in "getting started" section of the [codecov docs][]. - -In the `Actions` tab of your projects' github repository, you can see that the workflows are failing due to the **Upload coverage** step. The error message in the workflow should display something like: - -``` -... - Retrying 5/5 in 2s.. - {'detail': ErrorDetail(string='Could not find a repository, try using repo upload token', code='not_found')} -Error: 404 Client Error: Not Found for url: -... -``` - -While [codecov docs][] has a very extensive documentation on how to get started, _if_ you are using the default settings of this template we can assume that you are using [codecov][] in a github action workflow and hence you can make use of the [codecov bot][]. - -To set it up, simply go to the [codecov app][] page and follow the instructions to activate it for your repository. -Once the activation is completed, go back to the `Actions` tab and re-run the failing workflows. - -The workflows should now succeed and you will be able to find the code coverage at this link: `https://app.codecov.io/gh/scverse/spatialdata-plot`. You might have to wait couple of minutes and the coverage of this repository should be ~60%. - -If your repository is private, you will have to specify an additional token in the repository secrets. In brief, you need to: - -1. Generate a Codecov Token by clicking _setup repo_ in the codecov dashboard. - - If you have already set up codecov in the repository by following the previous steps, you can directly go to the codecov repo webpage. -2. Go to _Settings_ and copy **only** the token `_______-____-...`. -3. Go to _Settings_ of your newly created repository on GitHub. -4. Go to _Security > Secrets > Actions_. -5. Create new repository secret with name `CODECOV_TOKEN` and paste the token generated by codecov. -6. Past these additional lines in `/.github/workflows.test.yaml` under the **Upload coverage** step: - ```bash - - name: Upload coverage - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODECOV_TOKEN }} - ``` -7. Go back to github `Actions` page an re-run previously failed jobs. - -### Documentation on _readthedocs_ - -We recommend using [readthedocs.org][] (RTD) to build and host the documentation for your project. -To enable readthedocs, head over to [their website][readthedocs.org] and sign in with your GitHub account. -On the RTD dashboard choose "Import a Project" and follow the instructions to add your repository. - -- Make sure to choose the correct name of the default branch. On GitHub, the name of the default branch should be `main` (it has - recently changed from `master` to `main`). -- We recommend to enable documentation builds for pull requests (PRs). This ensures that a PR doesn't introduce changes - that break the documentation. To do so, got to `Admin -> Advanced Settings`, check the - `Build pull requests for this projects` option, and click `Save`. For more information, please refer to - the [official RTD documentation](https://docs.readthedocs.io/en/stable/pull-requests.html). -- If you find the RTD builds are failing, you can disable the `fail_on_warning` option in `.readthedocs.yaml`. - -If your project is private, there are ways to enable docs rendering on [readthedocs.org][] but it is more cumbersome and requires a different subscription for read the docs. See a guide [here](https://docs.readthedocs.io/en/stable/guides/importing-private-repositories.html). - -### Pre-commit checks - -[Pre-commit][] checks are fast programs that -check code for errors, inconsistencies and code styles, before the code -is committed. - -We recommend setting up [pre-commit.ci][] to enforce consistency checks on every commit -and pull-request. - -To do so, head over to [pre-commit.ci][] and click "Sign In With GitHub". Follow -the instructions to enable pre-commit.ci for your account or your organization. You -may choose to enable the service for an entire organization or on a per-repository basis. - -Once authorized, pre-commit.ci should automatically be activated. - -#### Overview of pre-commit hooks used by the template - -The following pre-commit checks are for code style and format: - -- [black](https://black.readthedocs.io/en/stable/): standard code - formatter in Python. -- [isort](https://pycqa.github.io/isort/): sort module imports into - sections and types. -- [prettier](https://prettier.io/docs/en/index.html): standard code - formatter for non-Python files (e.g. YAML). -- [blacken-docs](https://github.com/asottile/blacken-docs): black on - python code in docs. - -The following pre-commit checks are for errors and inconsistencies: - -- [flake8](https://flake8.pycqa.org/en/latest/): standard check for errors in Python files. - - [flake8-tidy-imports](https://github.com/adamchainz/flake8-tidy-imports): - tidy module imports. - - [flake8-docstrings](https://github.com/PyCQA/flake8-docstrings): - pydocstyle extension of flake8. - - [flake8-rst-docstrings](https://github.com/peterjc/e8-rst-docstrings): - extension of `flake8-docstrings` for `rst` docs. - - [flake8-comprehensions](https://github.com/adamchainz/e8-comprehensions): - write better list/set/dict comprehensions. - - [flake8-bugbear](https://github.com/PyCQA/flake8-bugbear): - find possible bugs and design issues in program. - - [flake8-blind-except](https://github.com/elijahandrews/flake8-blind-except): - checks for blind, catch-all `except` statements. -- [yesqa](https://github.com/asottile/yesqa): - remove unneccesary `# noqa` comments, follows additional dependencies listed above. -- [autoflake](https://github.com/PyCQA/autoflake): - remove unused imports and variables. -- [pre-commit-hooks](https://github.com/pre-commit/pre-commit-hooks): generic pre-commit hooks. - - **detect-private-key**: checks for the existence of private keys. - - **check-ast**: check whether files parse as valid python. - - **end-of-file-fixer**:check files end in a newline and only a newline. - - **mixed-line-ending**: checks mixed line ending. - - **trailing-whitespace**: trims trailing whitespace. - - **check-case-conflict**: check files that would conflict with case-insensitive file systems. -- [pyupgrade](https://github.com/asottile/pyupgrade): - upgrade syntax for newer versions of the language. -- **forbid-to-commit**: Make sure that `*.rej` files cannot be commited. These files are created by the - [automated template sync](#automated-template-sync) if there's a merge conflict and need to be addressed manually. - -### How to disable or add pre-commit checks - -- To ignore lint warnigs from **flake8**, see [Ignore certain lint warnings](#how-to-ignore-certain-lint-warnings). -- You can add or remove pre-commit checks by simply deleting relevant lines in the `.pre-commit-config.yaml` file. - Some pre-commit checks have additional options that can be specified either in the `pyproject.toml` or tool-specific - config files, such as `.prettierrc.yml` for **prettier** and `.flake8` for **flake8**. - -### How to ignore certain lint warnings - -The [pre-commit checks](#pre-commit-checks) include [flake8](https://flake8.pycqa.org/en/latest/) which checks -for errors in Python files, including stylistic errors. - -In some cases it might overshoot and you may have good reasons to ignore certain warnings. - -To ignore an specific error on a per-case basis, you can add a comment `# noqa` to the offending line. You can also -specify the error ID to ignore, with e.g. `# noqa: E731`. Check the [flake8 guide][] for reference. - -Alternatively, you can disable certain error messages for the entire project. To do so, edit the `.flake8` -file in the root of the repository. Add one line per linting code you wish to ignore and don't forget to add a comment. - -```toml -... -# line break before a binary operator -> black does not adhere to PEP8 -W503 -# line break occured after a binary operator -> black does not adhere to PEP8 -W504 -... -``` - -[flake8 guide]: https://flake8.pycqa.org/en/3.1.1/user/ignoring-errors.html - -### API design - -Scverse ecosystem packages should operate on [AnnData][] and/or [MuData][] data structures and typically use an API -as originally [introduced by scanpy][scanpy-api] with the following submodules: - -- `pp` for preprocessing -- `tl` for tools (that, compared to `pp` generate interpretable output, often associated with a corresponding plotting - function) -- `pl` for plotting functions - -You may add additional submodules as appropriate. While we encourage to follow a scanpy-like API for ecosystem packages, -there may also be good reasons to choose a different approach, e.g. using an object-oriented API. - -[scanpy-api]: https://scanpy.readthedocs.io/en/stable/usage-principles.html - -### Using VCS-based versioning - -By default, the template uses hard-coded version numbers that are set in `pyproject.toml` and [managed with -bump2version](contributing.md#publishing-a-release). If you prefer to have your project automatically infer version numbers from git -tags, it is straightforward to switch to vcs-based versioning using [hatch-vcs][]. - -In `pyproject.toml` add the following changes, and you are good to go! - -```diff ---- a/pyproject.toml -+++ b/pyproject.toml -@@ -1,11 +1,11 @@ - [build-system] - build-backend = "hatchling.build" --requires = ["hatchling"] -+requires = ["hatchling", "hatch-vcs"] - - - [project] - name = "spatialdata-plot" --version = "0.3.1dev" -+dynamic = ["version"] - -@@ -60,6 +60,9 @@ -+[tool.hatch.version] -+source = "vcs" -+ - [tool.coverage.run] - source = ["spatialdata-plot"] - omit = [ -``` - -Don't forget to update the [Making a release section](contributing.md#publishing-a-release) in this document accordingly, after you are done! - -[hatch-vcs]: https://pypi.org/project/hatch-vcs/ - -### Automated template sync - -Automated template sync is enabled by default. This means that every night, a GitHub action runs [cruft][] to check -if a new version of the `scverse-cookiecutter` template got released. If there are any new changes, a pull request -proposing these changes is created automatically. This helps keeping the repository up-to-date with the latest -coding standards. - -It may happen that a template sync results in a merge conflict. If this is the case a `*.ref` file with the -diff is created. You need to manually address these changes and remove the `.rej` file when you are done. -The pull request can only be merged after all `*.rej` files have been removed. - -:::{tip} -The following hints may be useful to work with the template sync: - -- GitHub automatically disables scheduled actions if there has been not activity to the repository for 60 days. - You can re-enable or manually trigger the sync by navigating to `Actions` -> `Sync Template` in your GitHub repository. -- If you want to ignore certain files from the template update, you can add them to the `[tool.cruft]` section in the - `pyproject.toml` file in the root of your repository. More details are described in the - [cruft documentation][cruft-update-project]. -- To disable the sync entirely, simply remove the file `.github/workflows/sync.yaml`. - -::: - -[cruft]: https://cruft.github.io/cruft/ -[cruft-update-project]: https://cruft.github.io/cruft/#updating-a-project - -## Moving forward - -You have reached the end of this document. Congratulations! You have successfully set up your project and are ready to start. -For everything else related to documentation, code style, testing and publishing your project ot pypi, please refer to the [contributing docs](contributing.md#contributing-guide). - - - -[scanpy developer guide]: https://scanpy.readthedocs.io/en/latest/dev/index.html -[cookiecutter-scverse-instance]: https://cookiecutter-scverse-instance.readthedocs.io/en/latest/template_usage.html -[github quickstart guide]: https://docs.github.com/en/get-started/quickstart/create-a-repo?tool=webui -[codecov]: https://about.codecov.io/sign-up/ -[codecov docs]: https://docs.codecov.com/docs -[codecov bot]: https://docs.codecov.com/docs/team-bot -[codecov app]: https://github.com/apps/codecov -[pre-commit.ci]: https://pre-commit.ci/ -[readthedocs.org]: https://readthedocs.org/ -[myst-nb]: https://myst-nb.readthedocs.io/en/latest/ -[jupytext]: https://jupytext.readthedocs.io/en/latest/ -[pre-commit]: https://pre-commit.com/ -[anndata]: https://github.com/scverse/anndata -[mudata]: https://github.com/scverse/mudata -[pytest]: https://docs.pytest.org/ -[semver]: https://semver.org/ -[sphinx]: https://www.sphinx-doc.org/en/master/ -[myst]: https://myst-parser.readthedocs.io/en/latest/intro.html -[numpydoc-napoleon]: https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html -[numpydoc]: https://numpydoc.readthedocs.io/en/latest/format.html -[sphinx autodoc typehints]: https://github.com/tox-dev/sphinx-autodoc-typehints diff --git a/pyproject.toml b/pyproject.toml index fe0c998f..3fe37d08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,104 +1,135 @@ [build-system] build-backend = "hatchling.build" -requires = ["hatchling"] - +requires = [ "hatchling" ] [project] name = "spatialdata-plot" version = "0.0.1" description = "A very interesting piece of code" readme = "README.md" -requires-python = ">=3.8" -license = {file = "LICENSE"} +license = { file = "LICENSE" } +maintainers = [ + { name = "scverse", email = "scverse" }, +] authors = [ - {name = "scverse"}, + { name = "scverse" }, ] -maintainers = [ - {name = "scverse", email = "scverse"}, +requires-python = ">=3.10" +classifiers = [ + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] -urls.Documentation = "https://spatialdata-plot.readthedocs.io/" -urls.Source = "https://github.com/scverse/spatialdata-plot" -urls.Home-page = "https://github.com/scverse/spatialdata-plot" dependencies = [ - "anndata", - # for debug logging (referenced from the issue template) - "session-info" + "anndata", + # for debug logging (referenced from the issue template) + "session-info2", ] - -[project.optional-dependencies] -dev = [ - # CLI for bumping the version number - "bump2version", - "pre-commit", - "twine>=4.0.2" +optional-dependencies.dev = [ + "pre-commit", + "twine>=4.0.2", ] -doc = [ - "sphinx>=4", - "sphinx-book-theme>=0.3.3", - "myst-nb", - "sphinxcontrib-bibtex>=1.0.0", - "sphinx-autodoc-typehints", - # For notebooks - "ipykernel", - "ipython", - "sphinx-copybutton", +optional-dependencies.doc = [ + "docutils>=0.8,!=0.18.*,!=0.19.*", + "ipykernel", + "ipython", + "myst-nb>=1.1", + "pandas", + # Until pybtex >0.24.0 releases: https://bitbucket.org/pybtex-devs/pybtex/issues/169/ + "setuptools", + "sphinx>=4", + "sphinx-autodoc-typehints", + "sphinx-book-theme>=1", + "sphinx-copybutton", + "sphinx-tabs", + "sphinxcontrib-bibtex>=1", + "sphinxext-opengraph", ] -test = [ - "pytest", - "pytest-cov", +optional-dependencies.test = [ + "coverage", + "pytest", ] +# https://docs.pypi.org/project_metadata/#project-urls +urls.Documentation = "https://spatialdata-plot.readthedocs.io/" +urls.Homepage = "https://github.com/scverse/spatialdata-plot" +urls.Source = "https://github.com/scverse/spatialdata-plot" -[tool.coverage.run] -source = ["spatialdata_plot"] -omit = [ - "**/test_*.py", +[tool.hatch.envs.default] +installer = "uv" +features = [ "dev" ] + +[tool.hatch.envs.docs] +features = [ "doc" ] +scripts.build = "sphinx-build -M html docs docs/_build {args}" +scripts.open = "python -m webbrowser -t docs/_build/html/index.html" +scripts.clean = "git clean -fdX -- {args:docs}" + +[tool.hatch.envs.hatch-test] +features = [ "test" ] + +[tool.ruff] +line-length = 120 +src = [ "src" ] +extend-include = [ "*.ipynb" ] + +format.docstring-code-format = true + +lint.select = [ + "B", # flake8-bugbear + "BLE", # flake8-blind-except + "C4", # flake8-comprehensions + "D", # pydocstyle + "E", # Error detected by Pycodestyle + "F", # Errors detected by Pyflakes + "I", # isort + "RUF100", # Report unused noqa directives + "TID", # flake8-tidy-imports + "UP", # pyupgrade + "W", # Warning detected by Pycodestyle +] +lint.ignore = [ + "B008", # Errors from function calls in argument defaults. These are fine when the result is immutable. + "D100", # Missing docstring in public module + "D104", # Missing docstring in public package + "D105", # __magic__ methods are often self-explanatory, allow missing docstrings + "D107", # Missing docstring in __init__ + # Disable one in each pair of mutually incompatible rules + "D203", # We don’t want a blank line before a class docstring + "D213", # <> We want docstrings to start immediately after the opening triple quote + "D400", # first line should end with a period [Bug: doesn’t work with single-line docstrings] + "D401", # First line should be in imperative mood; try rephrasing + "E501", # line too long -> we accept long comment lines; formatter gets rid of long code lines + "E731", # Do not assign a lambda expression, use a def -> lambda expression assignments are convenient + "E741", # allow I, O, l as variable names -> I is the identity matrix ] +lint.per-file-ignores."*/__init__.py" = [ "F401" ] +lint.per-file-ignores."docs/*" = [ "I" ] +lint.per-file-ignores."tests/*" = [ "D" ] +lint.pydocstyle.convention = "numpy" [tool.pytest.ini_options] -testpaths = ["tests"] +testpaths = [ "tests" ] xfail_strict = true addopts = [ - "--import-mode=importlib", # allow using test files with same name + "--import-mode=importlib", # allow using test files with same name ] -[tool.isort] -include_trailing_comma = true -multi_line_output = 3 -profile = "black" -skip_glob = ["docs/*"] - -[tool.black] -line-length = 120 -target-version = ['py38'] -include = '\.pyi?$' -exclude = ''' -( - /( - \.eggs - | \.git - | \.hg - | \.mypy_cache - | \.tox - | \.venv - | _build - | buck-out - | build - | dist - )/ -) -''' - -[tool.jupytext] -formats = "ipynb,md" +[tool.coverage.run] +source = [ "spatialdata_plot" ] +omit = [ + "**/test_*.py", +] [tool.cruft] skip = [ - "tests", - "src/**/__init__.py", - "src/**/basic.py", - "docs/api.md", - "docs/changelog.md", - "docs/references.bib", - "docs/references.md", - "docs/notebooks/example.ipynb" + "tests", + "src/**/__init__.py", + "src/**/basic.py", + "docs/api.md", + "docs/changelog.md", + "docs/references.bib", + "docs/references.md", + "docs/notebooks/example.ipynb", ] From 0dd37e2121f17a79d4a2c6423b6997125be4a011 Mon Sep 17 00:00:00 2001 From: scverse-bot <108668866+scverse-bot@users.noreply.github.com> Date: Tue, 30 Sep 2025 06:34:14 +0000 Subject: [PATCH 02/16] Automated template update to v0.6.0 --- .cruft.json | 6 +- .github/workflows/test.yaml | 80 +++++++++++++++----- .gitignore | 1 + .pre-commit-config.yaml | 19 ++--- .readthedocs.yaml | 23 +++--- biome.jsonc | 5 +- docs/Makefile | 20 ----- docs/contributing.md | 144 ++++++++++++++++++++++++++++++++---- pyproject.toml | 27 ++++++- 9 files changed, 238 insertions(+), 87 deletions(-) delete mode 100644 docs/Makefile diff --git a/.cruft.json b/.cruft.json index 02781f21..7c4e0e5e 100644 --- a/.cruft.json +++ b/.cruft.json @@ -1,7 +1,7 @@ { "template": "https://github.com/scverse/cookiecutter-scverse", - "commit": "94ef9fb6f9ad8cfe65a3d9575679c03c80c49cd1", - "checkout": "v0.5.0", + "commit": "d383d94fadff9e4e6fdb59d77c68cb900d7cedec", + "checkout": "v0.6.0", "context": { "cookiecutter": { "project_name": "spatialdata-plot", @@ -36,7 +36,7 @@ "trim_blocks": true }, "_template": "https://github.com/scverse/cookiecutter-scverse", - "_commit": "94ef9fb6f9ad8cfe65a3d9575679c03c80c49cd1" + "_commit": "d383d94fadff9e4e6fdb59d77c68cb900d7cedec" } }, "directory": null diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d5cfb2a9..0bd76e8c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -18,27 +18,48 @@ defaults: 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 + + # Run tests through hatch. Spawns a separate runner for each environment defined in the hatch matrix obtained above. test: - runs-on: ${{ matrix.os }} + needs: get-environments strategy: fail-fast: false matrix: - include: - - os: ubuntu-latest - python: "3.10" - - os: ubuntu-latest - python: "3.12" - - os: ubuntu-latest - python: "3.12" - pip-flags: "--pre" - name: PRE-RELEASE DEPENDENCIES - - name: ${{ matrix.name }} Python ${{ matrix.python }} - - env: - OS: ${{ matrix.os }} - PYTHON: ${{ matrix.python }} + os: [ubuntu-latest] + env: ${{ fromJSON(needs.get-environments.outputs.envs) }} + + name: ${{ matrix.env.label }} + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -48,12 +69,35 @@ jobs: - 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 test --cover --python ${{ matrix.python }} + 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@v4 + uses: codecov/codecov-action@v5 + + # 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/.gitignore b/.gitignore index 31e10b3e..bd24e4e0 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ __pycache__/ # Tests and coverage /data/ /node_modules/ +/.coverage* # docs /docs/generated/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0fcce11e..b9de3fe0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,24 +7,24 @@ default_stages: minimum_pre_commit_version: 2.16.0 repos: - repo: https://github.com/biomejs/pre-commit - rev: v1.9.4 + rev: v2.2.4 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.5.1 + rev: v2.6.0 hooks: - id: pyproject-fmt - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.2 + rev: v0.13.2 hooks: - - id: ruff + - 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: v5.0.0 + rev: v6.0.0 hooks: - id: detect-private-key - id: check-ast @@ -36,12 +36,3 @@ repos: # Check that there are no merge conflicts (could be generated by template sync) - id: check-merge-conflict args: [--assume-in-merge] - - repo: local - hooks: - - id: forbid-to-commit - name: Don't commit rej files - entry: | - Cannot commit .rej files. These indicate merge conflicts that arise during automated template updates. - Fix the merge conflicts manually and remove the .rej files. - language: fail - files: '.*\.rej$' diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 69897c3b..c3f3f96f 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,16 +1,15 @@ # https://docs.readthedocs.io/en/stable/config-file/v2.html version: 2 build: - os: ubuntu-20.04 + os: ubuntu-24.04 tools: - python: "3.10" -sphinx: - configuration: docs/conf.py - # disable this for more lenient docs builds - fail_on_warning: true -python: - install: - - method: pip - path: . - extra_requirements: - - doc + python: "3.12" + 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/biome.jsonc b/biome.jsonc index 2175c16e..9f8f2208 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -1,9 +1,10 @@ { - "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "$schema": "https://biomejs.dev/schemas/2.2.0/schema.json", + "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true }, "formatter": { "useEditorconfig": true }, "overrides": [ { - "include": ["./.vscode/*.json", "**/*.jsonc"], + "includes": ["./.vscode/*.json", "**/*.jsonc"], "json": { "formatter": { "trailingCommas": "all" }, "parser": { diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index d4bb2cbb..00000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/contributing.md b/docs/contributing.md index 330a7d13..699d9429 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -1,14 +1,33 @@ # Contributing guide -Scanpy provides extensive [developer documentation][scanpy developer guide], most of which applies to this project, too. -This document will not reproduce the entire content from there. -Instead, it aims at summarizing the most important information to get you started on contributing. - +This document aims at summarizing the most important information for getting you started on contributing to this project. We assume that you are already familiar with git and with making pull requests on GitHub. -If not, please refer to the [scanpy developer guide][]. +For more extensive tutorials, that also cover the absolute basics, +please refer to other resources such as the [pyopensci tutorials][], +the [scientific Python tutorials][], or the [scanpy developer guide][]. + +[pyopensci tutorials]: https://www.pyopensci.org/learn.html +[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 + +We highly recommend to familiarize yourself with [`hatch`][hatch]. +Hatch is a Python project manager that + +- manages virtual environments, separately for development, testing and building the documentation. + Separating the environments is useful to avoid dependency conflicts. +- allows to run tests locally in different environments (e.g. different python versions) +- allows to run tasks defined in `pyproject.toml`, e.g. to build documentation. + +While the project is setup with `hatch` in mind, +it is still possible to use different tools to manage dependencies, such as `uv` or `pip`. + +::: + +[hatch]: https://hatch.pypa.io/latest/ + ## Installing dev dependencies In addition to the packages needed to _use_ this package, @@ -16,29 +35,103 @@ you need additional python packages to [run tests](#writing-tests) and [build th :::::{tabs} ::::{group-tab} Hatch -The easiest way is to get familiar with [hatch environments][], with which these tasks are simply: + +On the command line, you typically interact with hatch through its command line interface (CLI). +Running one of the following commands will automatically resolve the environments for testing and +building the documentation in the background: ```bash hatch test # defined in the table [tool.hatch.envs.hatch-test] in pyproject.toml hatch run docs:build # defined in the table [tool.hatch.envs.docs] ``` +When using an IDE such as VS Code, +you’ll have to point the editor at the paths to the virtual environments manually. +The environment you typically want to use as your main development environment is the `hatch-test` +environment with the latest Python version. + +To get a list of all environments for your projects, run + +```bash +hatch env show -i +``` + +This will list “Standalone” environments and a table of “Matrix” environments like the following: + +``` ++------------+---------+--------------------------+----------+---------------------------------+-------------+ +| Name | Type | Envs | Features | Dependencies | Scripts | ++------------+---------+--------------------------+----------+---------------------------------+-------------+ +| hatch-test | virtual | hatch-test.py3.10-stable | dev | coverage-enable-subprocess==1.0 | cov-combine | +| | | hatch-test.py3.13-stable | test | coverage[toml]~=7.4 | cov-report | +| | | hatch-test.py3.13-pre | | pytest-mock~=3.12 | run | +| | | | | pytest-randomly~=3.15 | run-cov | +| | | | | pytest-rerunfailures~=14.0 | | +| | | | | pytest-xdist[psutil]~=3.5 | | +| | | | | pytest~=8.1 | | ++------------+---------+--------------------------+----------+---------------------------------+-------------+ +``` + +From the `Envs` column, select the environment name you want to use for development. +In this example, it would be `hatch-test.py3.13-stable`. + +Next, create the environment with + +```bash +hatch env create hatch-test.py3.13-stable +``` + +Then, obtain the path to the environment using + +```bash +hatch env find hatch-test.py3.13-stable +``` + +In case you are using VScode, now open the command palette (Ctrl+Shift+P) and search for `Python: Select Interpreter`. +Choose `Enter Interpreter Path` and paste the path to the virtual environment from above. + +In this future, this may become easier through a hatch vscode extension. + +:::: + +::::{group-tab} uv + +A popular choice for managing virtual environments is [uv][]. +The main disadvantage compared to hatch is that it supports only a single environment per project at a time, +which requires you to mix the dependencies for running tests and building docs. +This can have undesired side-effects, +such as requiring to install a lower version of a library your project depends on, +only because an outdated sphinx plugin pins an older version. + +To initalize a virtual environment in the `.venv` directory of your project, simply run + +```bash +uv sync --all-extras +``` + +The `.venv` directory is typically automatically discovered by IDEs such as VS Code. + :::: ::::{group-tab} Pip -If you prefer managing environments manually, you can use `pip`: + +Pip is nowadays mostly superseded by environment manager such as [hatch][]. +However, for the sake of completeness, and since it’s ubiquitously available, +we describe how you can manage environments manually using `pip`: ```bash -cd spatialdata-plot python3 -m venv .venv source .venv/bin/activate pip install -e ".[dev,test,doc]" ``` +The `.venv` directory is typically automatically discovered by IDEs such as VS Code. + :::: ::::: [hatch environments]: https://hatch.pypa.io/latest/tutorials/environment/basic-usage/ +[uv]: https://docs.astral.sh/uv/ ## Code-style @@ -55,7 +148,7 @@ in the root of the repository. Pre-commit will automatically download all dependencies when it is run for the first time. Alternatively, you can rely on the [pre-commit.ci][] service enabled on GitHub. -If you didn't run `pre-commit` before pushing changes to GitHub it will automatically commit fixes to your pull request, or show an error message. +If you didn’t run `pre-commit` before pushing changes to GitHub it will automatically commit fixes to your pull request, or show an error message. If pre-commit.ci added a commit on a branch you still have been working on locally, simply use @@ -102,6 +195,14 @@ hatch test --all # test with all supported Python versions :::: +::::{group-tab} uv + +```bash +uv run pytest +``` + +:::: + ::::{group-tab} Pip ```bash @@ -118,12 +219,17 @@ in the root of the repository. ### Continuous integration -Continuous integration will automatically run the tests on all pull requests and test +Continuous integration via GitHub actions will automatically run the tests on all pull requests and test against the minimum and maximum supported Python version. -Additionally, there's a CI job that tests against pre-releases of all dependencies (if there are any). +Additionally, there’s a CI job that tests against pre-releases of all dependencies (if there are any). The purpose of this check is to detect incompatibilities of new package versions early on and -gives you time to fix the issue or reach out to the developers of the dependency before the package is released to a wider audience. +gives you time to fix the issue or reach out to the developers of the dependency before the package +is released to a wider audience. + +The CI job is defined in `.github/workflows/test.yaml`, +however the single point of truth for CI jobs is the Hatch test matrix defined in `pyproject.toml`. +This means that local testing via hatch and remote testing on CI tests against the same python versions and uses the same environments. ## Publishing a release @@ -189,7 +295,7 @@ please check out [this feature request][issue-render-notebooks] in the `cookiecu (docs-building)= -#### Building the docs locally +### Building the docs locally :::::{tabs} ::::{group-tab} Hatch @@ -201,12 +307,22 @@ hatch run docs:open :::: +::::{group-tab} uv + +```bash +cd docs +uv run sphinx-build -M html . _build -W +(xdg-)open _build/html/index.html +``` + +:::: + ::::{group-tab} Pip ```bash source .venv/bin/activate cd docs -make html +sphinx-build -M html . _build -W (xdg-)open _build/html/index.html ``` diff --git a/pyproject.toml b/pyproject.toml index 3fe37d08..81899c37 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ optional-dependencies.doc = [ "pandas", # Until pybtex >0.24.0 releases: https://bitbucket.org/pybtex-devs/pybtex/issues/169/ "setuptools", - "sphinx>=4", + "sphinx>=8.1", "sphinx-autodoc-typehints", "sphinx-book-theme>=1", "sphinx-copybutton", @@ -48,8 +48,9 @@ optional-dependencies.doc = [ "sphinxext-opengraph", ] optional-dependencies.test = [ - "coverage", + "coverage>=7.10", "pytest", + "pytest-cov", # For VS Code’s coverage functionality ] # https://docs.pypi.org/project_metadata/#project-urls urls.Documentation = "https://spatialdata-plot.readthedocs.io/" @@ -62,12 +63,29 @@ features = [ "dev" ] [tool.hatch.envs.docs] features = [ "doc" ] -scripts.build = "sphinx-build -M html docs docs/_build {args}" +scripts.build = "sphinx-build -M html docs docs/_build -W {args}" scripts.open = "python -m webbrowser -t docs/_build/html/index.html" scripts.clean = "git clean -fdX -- {args:docs}" +# Test the lowest and highest supported Python versions with normal deps +[[tool.hatch.envs.hatch-test.matrix]] +deps = [ "stable" ] +python = [ "3.10", "3.13" ] + +# Test the newest supported Python version also with pre-release deps +[[tool.hatch.envs.hatch-test.matrix]] +deps = [ "pre" ] +python = [ "3.13" ] + [tool.hatch.envs.hatch-test] -features = [ "test" ] +features = [ "dev", "test" ] + +[tool.hatch.envs.hatch-test.overrides] +# If the matrix variable `deps` is set to "pre", +# set the environment variable `UV_PRERELEASE` to "allow". +matrix.deps.env-vars = [ + { key = "UV_PRERELEASE", value = "allow", if = [ "pre" ] }, +] [tool.ruff] line-length = 120 @@ -118,6 +136,7 @@ addopts = [ [tool.coverage.run] source = [ "spatialdata_plot" ] +patch = [ "subprocess" ] omit = [ "**/test_*.py", ] From 353a923b9761273b04aaae8a3328c4a469ef55d4 Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Fri, 14 Nov 2025 19:35:09 +0100 Subject: [PATCH 03/16] fixes --- .github/workflows/test.yaml | 21 ++++- .gitignore | 33 ++++++- .pre-commit-config.yaml | 6 ++ .vscode/extensions.json | 18 ---- .vscode/launch.json | 33 ------- .vscode/settings.json | 18 ---- docs/conf.py | 9 +- docs/extensions/typed_returns.py | 3 +- pyproject.toml | 145 ++++++++++++++++++++++++++----- 9 files changed, 189 insertions(+), 97 deletions(-) delete mode 100644 .vscode/extensions.json delete mode 100644 .vscode/launch.json delete mode 100644 .vscode/settings.json diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0bd76e8c..6b17bae9 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -61,6 +61,14 @@ jobs: name: ${{ matrix.env.label }} runs-on: ${{ matrix.os }} + # Configure pytest-xdist and matplotlib for plotting tests + env: + OMP_NUM_THREADS: "1" + OPENBLAS_NUM_THREADS: "1" + MKL_NUM_THREADS: "1" + NUMEXPR_MAX_THREADS: "1" + PYTEST_ADDOPTS: "-n auto --dist=load --durations=10" + steps: - uses: actions/checkout@v4 with: @@ -85,6 +93,13 @@ jobs: 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: Archive figures generated during testing + if: always() + uses: actions/upload-artifact@v4 + with: + name: visual_test_results_${{ matrix.env.name }} + path: tests/figures/* + if-no-files-found: ignore - name: Upload coverage uses: codecov/codecov-action@v5 @@ -98,6 +113,6 @@ jobs: - test runs-on: ubuntu-latest steps: - - uses: re-actors/alls-green@release/v1 - with: - jobs: ${{ toJSON(needs) }} + - uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} diff --git a/.gitignore b/.gitignore index bd24e4e0..8585fcc2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,16 +6,45 @@ buck-out/ # Compiled files .venv/ __pycache__/ +.mypy_cache/ +.ruff_cache/ .*cache/ +/node_modules # Distribution / packaging +/build/ /dist/ +/*.egg-info/ # Tests and coverage +/.pytest_cache/ +/.cache/ /data/ -/node_modules/ -/.coverage* +*failed-diff.png # docs /docs/generated/ /docs/_build/ + +# IDEs +/.idea/ +/.vscode/ + +format.sh + +# test +tests/figures/ + +# jupyter checkpoints +.ipynb_checkpoints + +# sphinx files +*.swp + +# other +_version.py +/temp/ + +# pixi +/pixi/ +pixi.lock diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b9de3fe0..6fddec1f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,6 +23,12 @@ repos: args: [--fix, --exit-non-zero-on-fix] - id: ruff-format types_or: [python, pyi, jupyter] + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.18.2 + hooks: + - id: mypy + additional_dependencies: [numpy, types-requests] + exclude: tests/|docs/ - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index caaeb4f7..00000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "recommendations": [ - // GitHub integration - "github.vscode-github-actions", - "github.vscode-pull-request-github", - // Language support - "ms-python.python", - "ms-python.vscode-pylance", - "ms-toolsai.jupyter", - "tamasfe.even-better-toml", - // Dependency management - "ninoseki.vscode-mogami", - // Linting and formatting - "editorconfig.editorconfig", - "charliermarsh.ruff", - "biomejs.biome", - ], -} diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 36d18746..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Python: Build Documentation", - "type": "debugpy", - "request": "launch", - "module": "sphinx", - "args": ["-M", "html", ".", "_build"], - "cwd": "${workspaceFolder}/docs", - "console": "internalConsole", - "justMyCode": false, - }, - { - "name": "Python: Debug Test", - "type": "debugpy", - "request": "launch", - "program": "${file}", - "purpose": ["debug-test"], - "console": "internalConsole", - "justMyCode": false, - "env": { - "PYTEST_ADDOPTS": "--color=yes", - }, - "presentation": { - "hidden": true, - }, - }, - ], -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index e034b91f..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "[python][json][jsonc]": { - "editor.formatOnSave": true, - }, - "[python]": { - "editor.defaultFormatter": "charliermarsh.ruff", - "editor.codeActionsOnSave": { - "source.fixAll": "always", - "source.organizeImports": "always", - }, - }, - "[json][jsonc]": { - "editor.defaultFormatter": "biomejs.biome", - }, - "python.analysis.typeCheckingMode": "basic", - "python.testing.pytestEnabled": true, - "python.testing.pytestArgs": ["-vv", "--color=yes"], -} diff --git a/docs/conf.py b/docs/conf.py index 50bc4955..78ce0abe 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -94,8 +94,15 @@ intersphinx_mapping = { "python": ("https://docs.python.org/3", None), "anndata": ("https://anndata.readthedocs.io/en/stable/", None), - "scanpy": ("https://scanpy.readthedocs.io/en/stable/", None), "numpy": ("https://numpy.org/doc/stable/", None), + "pandas": ("https://pandas.pydata.org/docs/", None), + "matplotlib": ("https://matplotlib.org/stable/", None), + "geopandas": ("https://geopandas.org/en/stable/", None), + "xarray": ("https://docs.xarray.dev/en/stable/", None), + "datatree": ("https://datatree.readthedocs.io/en/latest/", None), + "dask": ("https://docs.dask.org/en/latest/", None), + "spatialdata": ("https://spatialdata.scverse.org/en/stable/", None), + "scanpy": ("https://scanpy.readthedocs.io/en/stable/", None), } # List of patterns, relative to source directory, that match files and diff --git a/docs/extensions/typed_returns.py b/docs/extensions/typed_returns.py index 0fbffefe..1a3e43ab 100644 --- a/docs/extensions/typed_returns.py +++ b/docs/extensions/typed_returns.py @@ -11,7 +11,8 @@ def _process_return(lines: Iterable[str]) -> Generator[str, None, None]: for line in lines: - if m := re.fullmatch(r"(?P\w+)\s+:\s+(?P[\w.]+)", line): + m = re.fullmatch(r"(?P\w+)\s+:\s+(?P[\w.]+)", line) + if m: yield f"-{m['param']} (:class:`~{m['type']}`)" else: yield line diff --git a/pyproject.toml b/pyproject.toml index 81899c37..6d81b8e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,40 +1,55 @@ [build-system] build-backend = "hatchling.build" -requires = [ "hatchling" ] +requires = [ "hatch-vcs", "hatchling" ] [project] name = "spatialdata-plot" -version = "0.0.1" -description = "A very interesting piece of code" +description = "Static plotting for spatial data." readme = "README.md" license = { file = "LICENSE" } maintainers = [ - { name = "scverse", email = "scverse" }, + { name = "Tim Treis", email = "tim.treis@helmholtz-munich.de" }, ] authors = [ { name = "scverse" }, ] -requires-python = ">=3.10" +requires-python = ">=3.11" classifiers = [ "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", ] +dynamic = [ + "version", # allow version to be set by git tags +] dependencies = [ - "anndata", + "matplotlib", + "matplotlib-scalebar", + "scanpy", + "scikit-learn", # for debug logging (referenced from the issue template) "session-info2", + "spatialdata>=0.3", ] + optional-dependencies.dev = [ + "ipykernel", + "ipywidgets", + "jupyterlab", + "jupytext", + "notebook", + "pooch", "pre-commit", + "pytest", + "pytest-cov", + "ruff", "twine>=4.0.2", ] optional-dependencies.doc = [ "docutils>=0.8,!=0.18.*,!=0.19.*", - "ipykernel", - "ipython", + # For notebooks + "ipython>=8.6", "myst-nb>=1.1", "pandas", # Until pybtex >0.24.0 releases: https://bitbucket.org/pybtex-devs/pybtex/issues/169/ @@ -43,19 +58,34 @@ optional-dependencies.doc = [ "sphinx-autodoc-typehints", "sphinx-book-theme>=1", "sphinx-copybutton", + "sphinx-design", + "sphinx-rtd-theme", "sphinx-tabs", "sphinxcontrib-bibtex>=1", "sphinxext-opengraph", ] optional-dependencies.test = [ "coverage>=7.10", + "pooch", # for scipy.datasets module "pytest", - "pytest-cov", # For VS Code’s coverage functionality + "pytest-cov", + "pytest-xdist", ] -# https://docs.pypi.org/project_metadata/#project-urls -urls.Documentation = "https://spatialdata-plot.readthedocs.io/" -urls.Homepage = "https://github.com/scverse/spatialdata-plot" -urls.Source = "https://github.com/scverse/spatialdata-plot" +urls.Documentation = "https://spatialdata.scverse.org/projects/plot/en/latest/index.html" +urls.Homepage = "https://github.com/scverse/spatialdata-plot.git" +urls.Source = "https://github.com/scverse/spatialdata-plot.git" + +[tool.hatch.build.targets.wheel] +packages = [ 'src/spatialdata_plot' ] + +[tool.hatch.version] +source = "vcs" + +[tool.hatch.build.hooks.vcs] +version-file = "_version.py" + +[tool.hatch.metadata] +allow-direct-references = true [tool.hatch.envs.default] installer = "uv" @@ -70,7 +100,7 @@ scripts.clean = "git clean -fdX -- {args:docs}" # Test the lowest and highest supported Python versions with normal deps [[tool.hatch.envs.hatch-test.matrix]] deps = [ "stable" ] -python = [ "3.10", "3.13" ] +python = [ "3.11", "3.13" ] # Test the newest supported Python version also with pre-release deps [[tool.hatch.envs.hatch-test.matrix]] @@ -79,6 +109,14 @@ python = [ "3.13" ] [tool.hatch.envs.hatch-test] features = [ "dev", "test" ] +scripts = [ + "run = pytest {args:tests}", + "run-cov = pytest --cov --cov-report=term-missing {args:tests}", + "cov = coverage combine", + "cov-combine = coverage combine", + "cov-report = coverage report", + "coverage = coverage {args}", +] [tool.hatch.envs.hatch-test.overrides] # If the matrix variable `deps` is set to "pre", @@ -91,6 +129,15 @@ matrix.deps.env-vars = [ line-length = 120 src = [ "src" ] extend-include = [ "*.ipynb" ] +exclude = [ + ".git", + ".tox", + "__pycache__", + "build", + "dist", + "docs/_build", + "setup.py", +] format.docstring-code-format = true @@ -98,11 +145,18 @@ lint.select = [ "B", # flake8-bugbear "BLE", # flake8-blind-except "C4", # flake8-comprehensions - "D", # pydocstyle + "D", # flake8-docstrings "E", # Error detected by Pycodestyle "F", # Errors detected by Pyflakes "I", # isort + "NPY", # NumPy-specific rules + "PGH", # pygrep-hooks + "PT", # flake8-pytest-style + "Q", # flake8-quotes + "RET", # flake8-raise "RUF100", # Report unused noqa directives + "SIM", # flake8-simplify + "T20", # flake8-print "TID", # flake8-tidy-imports "UP", # pyupgrade "W", # Warning detected by Pycodestyle @@ -114,24 +168,33 @@ lint.ignore = [ "D105", # __magic__ methods are often self-explanatory, allow missing docstrings "D107", # Missing docstring in __init__ # Disable one in each pair of mutually incompatible rules - "D203", # We don’t want a blank line before a class docstring + "D203", # We don't want a blank line before a class docstring "D213", # <> We want docstrings to start immediately after the opening triple quote - "D400", # first line should end with a period [Bug: doesn’t work with single-line docstrings] + "D400", # first line should end with a period [Bug: doesn't work with single-line docstrings] "D401", # First line should be in imperative mood; try rephrasing "E501", # line too long -> we accept long comment lines; formatter gets rid of long code lines "E731", # Do not assign a lambda expression, use a def -> lambda expression assignments are convenient "E741", # allow I, O, l as variable names -> I is the identity matrix ] -lint.per-file-ignores."*/__init__.py" = [ "F401" ] -lint.per-file-ignores."docs/*" = [ "I" ] -lint.per-file-ignores."tests/*" = [ "D" ] +lint.per-file-ignores."*/__init__.py" = [ "D104", "D107", "E402", "F401" ] +lint.per-file-ignores."docs/*" = [ "A", "B", "D", "E", "I" ] +lint.per-file-ignores."src/spatialdata_plot/pl/utils.py" = [ "PGH003" ] +lint.per-file-ignores."tests/*" = [ "B024", "D", "PT" ] +lint.per-file-ignores."tests/conftest.py" = [ "E402", "RET504" ] +lint.unfixable = [ "B", "BLE", "C4", "RET", "T20", "UP" ] lint.pydocstyle.convention = "numpy" [tool.pytest.ini_options] testpaths = [ "tests" ] xfail_strict = true addopts = [ + # "-Werror", # if 3rd party libs raise DeprecationWarnings, just use filterwarnings below "--import-mode=importlib", # allow using test files with same name + "-s", # print output from tests +] +# info on how to use this https://stackoverflow.com/questions/57925071/how-do-i-avoid-getting-deprecationwarning-from-inside-dependencies-with-pytest +filterwarnings = [ + # "ignore:.*U.*mode is deprecated:DeprecationWarning", ] [tool.coverage.run] @@ -141,6 +204,46 @@ omit = [ "**/test_*.py", ] +[tool.jupytext] +formats = "ipynb,md" + +[tool.pixi.workspace] +channels = [ "conda-forge" ] +platforms = [ "osx-arm64", "linux-64" ] + +[tool.pixi.dependencies] +python = ">=3.11" + +[tool.pixi.pypi-dependencies] +spatialdata-plot = { path = ".", editable = true } + +# for gh-actions +[tool.pixi.feature.py311.dependencies] +python = "3.11.*" + +[tool.pixi.feature.py313.dependencies] +python = "3.13.*" + +[tool.pixi.environments] +# 3.11 lane (for gh-actions) +dev-py311 = { features = [ "dev", "test", "py311" ], solve-group = "py311" } +docs-py311 = { features = [ "docs", "py311" ], solve-group = "py311" } + +# 3.13 lane +default = { features = [ "py313" ], solve-group = "py313" } +dev-py313 = { features = [ "dev", "test", "py313" ], solve-group = "py313" } +docs-py313 = { features = [ "docs", "py313" ], solve-group = "py313" } +test-py313 = { features = [ "test", "py313" ], solve-group = "py313" } + +[tool.pixi.tasks] +lab = "jupyter lab" +kernel-install = "python -m ipykernel install --user --name pixi-dev --display-name \"sdata-plot (dev)\"" +test = "pytest -v --color=yes --tb=short --durations=10" +lint = "ruff check ." +format = "ruff format ." +pre-commit-install = "pre-commit install" +pre-commit-run = "pre-commit run --all-files" + [tool.cruft] skip = [ "tests", From d6464f77277106ca250720d05428b93ab2471494 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 14 Nov 2025 18:41:29 +0000 Subject: [PATCH 04/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/conftest.py | 2 +- tests/pl/test_get_extent.py | 2 +- tests/pl/test_render_images.py | 2 +- tests/pl/test_render_labels.py | 2 +- tests/pl/test_render_points.py | 2 +- tests/pl/test_render_shapes.py | 2 +- tests/pl/test_show.py | 2 +- tests/pl/test_upstream_plots.py | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index b44d6d8e..ffe4e786 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -405,7 +405,7 @@ def __new__(cls, clsname, superclasses, attributedict): return super().__new__(cls, clsname, superclasses, attributedict) -class PlotTester(ABC): # noqa: B024 +class PlotTester(ABC): @classmethod def compare(cls, basename: str, tolerance: float | None = None): ACTUAL.mkdir(parents=True, exist_ok=True) diff --git a/tests/pl/test_get_extent.py b/tests/pl/test_get_extent.py index 9d9f1195..14f65b3e 100644 --- a/tests/pl/test_get_extent.py +++ b/tests/pl/test_get_extent.py @@ -10,7 +10,7 @@ from spatialdata.models import PointsModel, ShapesModel from spatialdata.transformations import Affine, set_transformation -import spatialdata_plot # noqa: F401 +import spatialdata_plot from tests.conftest import DPI, PlotTester, PlotTesterMeta sc.pl.set_rcParams_defaults() diff --git a/tests/pl/test_render_images.py b/tests/pl/test_render_images.py index 5cba4e88..8dab039c 100644 --- a/tests/pl/test_render_images.py +++ b/tests/pl/test_render_images.py @@ -5,7 +5,7 @@ from spatial_image import to_spatial_image from spatialdata import SpatialData -import spatialdata_plot # noqa: F401 +import spatialdata_plot from tests.conftest import DPI, PlotTester, PlotTesterMeta, _viridis_with_under_over sc.pl.set_rcParams_defaults() diff --git a/tests/pl/test_render_labels.py b/tests/pl/test_render_labels.py index 8184de0a..aa89d6c5 100644 --- a/tests/pl/test_render_labels.py +++ b/tests/pl/test_render_labels.py @@ -10,7 +10,7 @@ from spatialdata import SpatialData, deepcopy, get_element_instances from spatialdata.models import Labels2DModel, TableModel -import spatialdata_plot # noqa: F401 +import spatialdata_plot from tests.conftest import DPI, PlotTester, PlotTesterMeta, _viridis_with_under_over, get_standard_RNG sc.pl.set_rcParams_defaults() diff --git a/tests/pl/test_render_points.py b/tests/pl/test_render_points.py index 2a60f4c1..fb433ac1 100644 --- a/tests/pl/test_render_points.py +++ b/tests/pl/test_render_points.py @@ -21,7 +21,7 @@ ) from spatialdata.transformations._utils import _set_transformations -import spatialdata_plot # noqa: F401 +import spatialdata_plot from tests.conftest import DPI, PlotTester, PlotTesterMeta, _viridis_with_under_over, get_standard_RNG sc.pl.set_rcParams_defaults() diff --git a/tests/pl/test_render_shapes.py b/tests/pl/test_render_shapes.py index e8b301a1..5c668a97 100644 --- a/tests/pl/test_render_shapes.py +++ b/tests/pl/test_render_shapes.py @@ -16,7 +16,7 @@ from spatialdata.transformations import Affine, Identity, MapAxis, Scale, Sequence, Translation from spatialdata.transformations._utils import _set_transformations -import spatialdata_plot # noqa: F401 +import spatialdata_plot from tests.conftest import DPI, PlotTester, PlotTesterMeta, _viridis_with_under_over, get_standard_RNG sc.pl.set_rcParams_defaults() diff --git a/tests/pl/test_show.py b/tests/pl/test_show.py index 2b7e444d..311dfc0e 100644 --- a/tests/pl/test_show.py +++ b/tests/pl/test_show.py @@ -2,7 +2,7 @@ import scanpy as sc from spatialdata import SpatialData -import spatialdata_plot # noqa: F401 +import spatialdata_plot from tests.conftest import DPI, PlotTester, PlotTesterMeta sc.pl.set_rcParams_defaults() diff --git a/tests/pl/test_upstream_plots.py b/tests/pl/test_upstream_plots.py index 65267bd7..cec48248 100644 --- a/tests/pl/test_upstream_plots.py +++ b/tests/pl/test_upstream_plots.py @@ -13,7 +13,7 @@ set_transformation, ) -import spatialdata_plot # noqa: F401 +import spatialdata_plot from tests.conftest import DPI, PlotTester, PlotTesterMeta # sc.pl.set_rcParams_defaults() From abbc468236f24ac03bbfe40bd76ac5e9d18185a6 Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Fri, 14 Nov 2025 19:44:57 +0100 Subject: [PATCH 05/16] adjusted --- .github/workflows/test.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 6b17bae9..c9127609 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -37,13 +37,14 @@ jobs: - name: Get test environments id: get-envs run: | - ENVS_JSON=$(uvx hatch env show --json | jq -c 'to_entries + ENVS_JSON=$(NO_COLOR=1 uvx --quiet 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 + python: .value.python, + test_type: (if (.key | contains("py3.13-stable")) then "coverage" else null end) } )') echo "envs=${ENVS_JSON}" | tee $GITHUB_OUTPUT From 49e66de23c1775e824fb063ded2d1aeddec02389 Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Fri, 14 Nov 2025 19:51:36 +0100 Subject: [PATCH 06/16] adjusted config discovery --- .github/workflows/test.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index c9127609..afec05ad 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -34,6 +34,9 @@ jobs: fetch-depth: 0 - name: Install uv uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + cache-dependency-glob: pyproject.toml - name: Get test environments id: get-envs run: | From 5f384ed210af2ad00da452ae4d999ce9e89dc998 Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Fri, 14 Nov 2025 19:56:56 +0100 Subject: [PATCH 07/16] adjusted config discovery --- .github/workflows/test.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index afec05ad..d51e9019 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -37,10 +37,12 @@ jobs: with: enable-cache: true cache-dependency-glob: pyproject.toml + - name: Install hatch + run: uv tool install hatch - name: Get test environments id: get-envs run: | - ENVS_JSON=$(NO_COLOR=1 uvx --quiet hatch env show --json | jq -c 'to_entries + ENVS_JSON=$(hatch env show --json | jq -c 'to_entries | map( select(.key | startswith("hatch-test")) | { From ca8e9442aec0e876db874f003046f148fff924c7 Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Fri, 14 Nov 2025 20:01:04 +0100 Subject: [PATCH 08/16] adjusted config discovery --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d51e9019..e03dfd3b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -42,7 +42,7 @@ jobs: - name: Get test environments id: get-envs run: | - ENVS_JSON=$(hatch env show --json | jq -c 'to_entries + ENVS_JSON=$(hatch env show --json 2>/dev/null | jq -c 'to_entries | map( select(.key | startswith("hatch-test")) | { From a994f54d59f70fcce3a83a40cadbab863d192beb Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Fri, 14 Nov 2025 20:04:51 +0100 Subject: [PATCH 09/16] adjusted config discovery --- .github/workflows/test.yaml | 56 ++++++++++--------------------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e03dfd3b..af42063f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -18,51 +18,26 @@ defaults: 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 - with: - enable-cache: true - cache-dependency-glob: pyproject.toml - - name: Install hatch - run: uv tool install hatch - - name: Get test environments - id: get-envs - run: | - ENVS_JSON=$(hatch env show --json 2>/dev/null | 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, - test_type: (if (.key | contains("py3.13-stable")) then "coverage" else null end) - } - )') - 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. + # Run tests through hatch. Spawns a separate runner for each environment defined in the matrix below. + # The matrix is based on [[tool.hatch.envs.hatch-test.matrix]] in pyproject.toml test: - needs: get-environments - strategy: fail-fast: false matrix: os: [ubuntu-latest] - env: ${{ fromJSON(needs.get-environments.outputs.envs) }} + env: + - name: hatch-test.py3.11-stable + label: hatch-test.py3.11-stable + python: "3.11" + test_type: null + - name: hatch-test.py3.13-stable + label: hatch-test.py3.13-stable + python: "3.13" + test_type: coverage + - name: hatch-test.py3.13-pre + label: hatch-test.py3.13-pre (PRE-RELEASE DEPENDENCIES) + python: "3.13" + test_type: null name: ${{ matrix.env.label }} runs-on: ${{ matrix.os }} @@ -115,7 +90,6 @@ jobs: name: Tests pass in all hatch environments if: always() needs: - - get-environments - test runs-on: ubuntu-latest steps: From c653c833c5c3260d97aa03b516d5973e581dfad1 Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Fri, 14 Nov 2025 20:10:03 +0100 Subject: [PATCH 10/16] updated toml --- pyproject.toml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6d81b8e6..c1710c2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -109,14 +109,14 @@ python = [ "3.13" ] [tool.hatch.envs.hatch-test] features = [ "dev", "test" ] -scripts = [ - "run = pytest {args:tests}", - "run-cov = pytest --cov --cov-report=term-missing {args:tests}", - "cov = coverage combine", - "cov-combine = coverage combine", - "cov-report = coverage report", - "coverage = coverage {args}", -] + +[tool.hatch.envs.hatch-test.scripts] +run = "pytest {args:tests}" +run-cov = "pytest --cov --cov-report=term-missing {args:tests}" +cov = "coverage combine" +cov-combine = "coverage combine" +cov-report = "coverage report" +coverage = "coverage {args}" [tool.hatch.envs.hatch-test.overrides] # If the matrix variable `deps` is set to "pre", From cb116360a4272cf3f16d5c8a418fe39083892111 Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Fri, 14 Nov 2025 21:25:20 +0100 Subject: [PATCH 11/16] updated toml --- .github/workflows/test.yaml | 102 ++++++++++++++++++++++++++---------- pyproject.toml | 26 ++++----- 2 files changed, 86 insertions(+), 42 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index af42063f..78859aa0 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -12,84 +12,128 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + FORCE_COLOR: "1" + MPLBACKEND: agg + UV_COMPILE_BYTECODE: "1" + COVERAGE_FILE: ${{ github.workspace }}/.coverage + defaults: run: - # to fail on error in multiline statements (-e), in pipes (-o pipefail), and on unset variables (-u). + # fail on error in multiline statements (-e), in pipes (-o pipefail), and on unset variables (-u) shell: bash -euo pipefail {0} jobs: - # Run tests through hatch. Spawns a separate runner for each environment defined in the matrix below. - # The matrix is based on [[tool.hatch.envs.hatch-test.matrix]] in pyproject.toml + # Discover hatch test environments from pyproject.toml, similar to squidpy. + get-environments: + runs-on: ubuntu-latest + outputs: + envs: ${{ steps.get-envs.outputs.envs }} + steps: + - uses: actions/checkout@v5 + with: + filter: blob:none + fetch-depth: 0 + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + cache-dependency-glob: pyproject.toml + + - name: Get test environments + id: get-envs + run: | + ENVS_JSON=$(NO_COLOR=1 uvx --quiet 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, + test_type: (if (.key | contains("py3.13-stable")) then "coverage" else null end) + } + )') + echo "envs=${ENVS_JSON}" | tee "$GITHUB_OUTPUT" + + # Run tests through hatch for each discovered environment. test: + needs: [get-environments] strategy: fail-fast: false matrix: os: [ubuntu-latest] - env: - - name: hatch-test.py3.11-stable - label: hatch-test.py3.11-stable - python: "3.11" - test_type: null - - name: hatch-test.py3.13-stable - label: hatch-test.py3.13-stable - python: "3.13" - test_type: coverage - - name: hatch-test.py3.13-pre - label: hatch-test.py3.13-pre (PRE-RELEASE DEPENDENCIES) - python: "3.13" - test_type: null - - name: ${{ matrix.env.label }} + env: ${{ fromJSON(needs.get-environments.outputs.envs) }} + name: ${{ matrix.env.label }} (${{ matrix.os }}) runs-on: ${{ matrix.os }} - - # Configure pytest-xdist and matplotlib for plotting tests + # Configure pytest-xdist and BLAS threading as before env: OMP_NUM_THREADS: "1" OPENBLAS_NUM_THREADS: "1" MKL_NUM_THREADS: "1" NUMEXPR_MAX_THREADS: "1" PYTEST_ADDOPTS: "-n auto --dist=load --durations=10" - steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: filter: blob:none fetch-depth: 0 + - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v7 with: python-version: ${{ matrix.env.python }} cache-dependency-glob: pyproject.toml + + - name: Ensure figure directory exists + run: mkdir -p "$GITHUB_WORKSPACE/tests/figures" + - name: create hatch environment run: uvx hatch env create ${{ matrix.env.name }} + - name: run tests using hatch + if: matrix.env.test_type == null + env: + PLATFORM: ${{ matrix.os }} + DISPLAY: :42 + run: uvx hatch run ${{ matrix.env.name }}:run -v --color=yes + + - name: run tests using hatch (coverage) + if: matrix.env.test_type == 'coverage' env: - MPLBACKEND: agg PLATFORM: ${{ matrix.os }} DISPLAY: :42 - run: uvx hatch run ${{ matrix.env.name }}:run-cov -v --color=yes -n auto + run: uvx hatch run ${{ matrix.env.name }}:run-cov -v --color=yes + - name: generate coverage report + if: matrix.env.test_type == 'coverage' 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: Archive figures generated during testing if: always() uses: actions/upload-artifact@v4 with: - name: visual_test_results_${{ matrix.env.name }} - path: tests/figures/* + name: visual_test_results_${{ runner.os }}_${{ matrix.env.name }} + path: ${{ github.workspace }}/tests/figures/* if-no-files-found: ignore + - name: Upload coverage + if: matrix.env.test_type == 'coverage' uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: true - # 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. + # Single “required” check that aggregates all test jobs, as in squidpy. check: name: Tests pass in all hatch environments if: always() needs: + - get-environments - test runs-on: ubuntu-latest steps: diff --git a/pyproject.toml b/pyproject.toml index c1710c2d..50c39cf2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ requires = [ "hatch-vcs", "hatchling" ] name = "spatialdata-plot" description = "Static plotting for spatial data." readme = "README.md" -license = { file = "LICENSE" } +license = "BSD-3-Clause" maintainers = [ { name = "Tim Treis", email = "tim.treis@helmholtz-munich.de" }, ] @@ -46,7 +46,7 @@ optional-dependencies.dev = [ "ruff", "twine>=4.0.2", ] -optional-dependencies.doc = [ +optional-dependencies.docs = [ "docutils>=0.8,!=0.18.*,!=0.19.*", # For notebooks "ipython>=8.6", @@ -71,12 +71,14 @@ optional-dependencies.test = [ "pytest-cov", "pytest-xdist", ] + +urls."Bug Tracker" = "https://github.com/scverse/spatialdata-plot/issues" urls.Documentation = "https://spatialdata.scverse.org/projects/plot/en/latest/index.html" -urls.Homepage = "https://github.com/scverse/spatialdata-plot.git" +urls.Home-page = "https://github.com/scverse/spatialdata-plot.git" urls.Source = "https://github.com/scverse/spatialdata-plot.git" [tool.hatch.build.targets.wheel] -packages = [ 'src/spatialdata_plot' ] +packages = [ "src/spatialdata_plot" ] [tool.hatch.version] source = "vcs" @@ -92,7 +94,7 @@ installer = "uv" features = [ "dev" ] [tool.hatch.envs.docs] -features = [ "doc" ] +features = [ "docs" ] scripts.build = "sphinx-build -M html docs docs/_build -W {args}" scripts.open = "python -m webbrowser -t docs/_build/html/index.html" scripts.clean = "git clean -fdX -- {args:docs}" @@ -109,14 +111,12 @@ python = [ "3.13" ] [tool.hatch.envs.hatch-test] features = [ "dev", "test" ] - -[tool.hatch.envs.hatch-test.scripts] -run = "pytest {args:tests}" -run-cov = "pytest --cov --cov-report=term-missing {args:tests}" -cov = "coverage combine" -cov-combine = "coverage combine" -cov-report = "coverage report" -coverage = "coverage {args}" +scripts.run = "pytest {args:tests}" +scripts.run-cov = "pytest --cov --cov-report=term-missing {args:tests}" +scripts.cov = "coverage combine" +scripts.cov-combine = "coverage combine" +scripts.cov-report = "coverage report" +scripts.coverage = "python -m coverage {args}" [tool.hatch.envs.hatch-test.overrides] # If the matrix variable `deps` is set to "pre", From 850616329a00f29d127680fdc8f363f8b0535bc4 Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Fri, 14 Nov 2025 21:47:11 +0100 Subject: [PATCH 12/16] bump --- .readthedocs.yaml | 13 ++++--------- docs/conf.py | 1 + docs/contributing.md | 2 +- pyproject.toml | 1 - 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index c3f3f96f..b698bd16 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -4,12 +4,7 @@ build: os: ubuntu-24.04 tools: python: "3.12" - 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 + commands: + - python -m pip install uv + - uvx hatch run docs:build + - mv docs/_build $READTHEDOCS_OUTPUT diff --git a/docs/conf.py b/docs/conf.py index 78ce0abe..b7558358 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -55,6 +55,7 @@ "sphinx.ext.napoleon", "sphinxcontrib.bibtex", "sphinx_autodoc_typehints", + "sphinx_design", "sphinx_tabs.tabs", "sphinx.ext.mathjax", "IPython.sphinxext.ipython_console_highlighting", diff --git a/docs/contributing.md b/docs/contributing.md index 699d9429..53aab1f9 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -122,7 +122,7 @@ we describe how you can manage environments manually using `pip`: ```bash python3 -m venv .venv source .venv/bin/activate -pip install -e ".[dev,test,doc]" +pip install -e ".[dev,test,docs]" ``` The `.venv` directory is typically automatically discovered by IDEs such as VS Code. diff --git a/pyproject.toml b/pyproject.toml index 50c39cf2..49ed6e90 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -199,7 +199,6 @@ filterwarnings = [ [tool.coverage.run] source = [ "spatialdata_plot" ] -patch = [ "subprocess" ] omit = [ "**/test_*.py", ] From af0ca3b9381cec784f6010c12df6bde79484bcbd Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Fri, 14 Nov 2025 21:57:03 +0100 Subject: [PATCH 13/16] bump --- docs/conf.py | 12 +++++++++++- src/spatialdata_plot/pl/basic.py | 31 ++++++++++++++++++------------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index b7558358..dc04c192 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -85,6 +85,9 @@ nb_execution_mode = "off" nb_merge_streams = True typehints_defaults = "braces" +autodoc_type_aliases = { + "ColorLike": "spatialdata_plot.pl.basic.ColorLike", +} source_suffix = { ".rst": "restructuredtext", @@ -135,5 +138,12 @@ nitpick_ignore = [ # If building the documentation fails because of a missing link that is outside your control, # you can add an exception to this list. - # ("py:class", "igraph.Graph"), + ("py:class", "Colormap"), + ("py:class", "Normalize"), + ("py:class", "ColorLike"), +] +nitpick_ignore_regex = [ + ("py:class", r"default .+"), + ("py:class", r"gets set to .+"), + ("py:class", r"optional"), ] diff --git a/src/spatialdata_plot/pl/basic.py b/src/spatialdata_plot/pl/basic.py index 883f51ce..6761f471 100644 --- a/src/spatialdata_plot/pl/basic.py +++ b/src/spatialdata_plot/pl/basic.py @@ -187,11 +187,15 @@ def render_shapes( element : str | None, optional The name of the shapes element to render. If `None`, all shapes elements in the `SpatialData` object will be used. - color : ColorLike | None, optional + color : ColorLike | str | None, optional Can either be color-like (name of a color as string, e.g. "red", hex representation, e.g. "#000000" or "#000000ff", or an RGB(A) array as a tuple or list containing 3-4 floats within [0, 1]. If an alpha value is - indicated, the value of `fill_alpha` takes precedence if given) or a string representing a key in - :attr:`sdata.table.obs`. The latter can be used to color by categorical or continuous variables. If + indicated, the value of `fill_alpha` takes precedence if given) or a string referencing stored annotations. + When the provided key matches a column on the shapes element itself, those values are used directly. When + the key references an AnnData table annotating the element, both ``obs`` columns and ``var_names`` entries + (optionally pulled from ``layers``) are supported; use `table_name` to disambiguate which table should be + consulted. The string form can therefore represent categorical or continuous measurements tied to the shapes + element. `element` is `None`, if possible the color will be broadcasted to all elements. For this, the table in which the color key is found must annotate the respective element (region must be set to the specific element). If the color column is found in multiple locations, please provide the table_name to be used for the elements. @@ -241,8 +245,8 @@ def render_shapes( spatial element to be plotted if the table annotates it. If you want to use different tables for particular elements, as specified under element. table_layer: str | None - Layer of the table to use for coloring if `color` is in :attr:`sdata.table.var_names`. If None, the data in - :attr:`sdata.table.X` is used for coloring. + Layer of the table to use for coloring if `color` is present in the ``var_names`` of the table. If None, the + data stored in ``X`` is used for coloring. shape: Literal["circle", "hex", "visium_hex", "square"] | None If None (default), the shapes are rendered as they are. Else, if either of "circle", "hex" or "square" is specified, the shapes are converted to a circle/hexagon/square before rendering. If "visium_hex" is @@ -368,8 +372,9 @@ def render_points( color : str | None, optional Can either be color-like (name of a color as string, e.g. "red", hex representation, e.g. "#000000" or "#000000ff", or an RGB(A) array as a tuple or list containing 3-4 floats within [0, 1]. If an alpha value is - indicated, the value of `fill_alpha` takes precedence if given) or a string representing a key in - :attr:`sdata.table.obs`. The latter can be used to color by categorical or continuous variables. If + indicated, the value of `fill_alpha` takes precedence if given) or a string representing a key in the ``obs`` + dataframe of the table providing annotations. The latter can be used to color by categorical or continuous + variables. If `element` is `None`, if possible the color will be broadcasted to all elements. For this, the table in which the color key is found must annotate the respective element (region must be set to the specific element). If the color column is found in multiple locations, please provide the table_name to be used for the elements. @@ -403,8 +408,8 @@ def render_points( spatial element to be plotted if the table annotates it. If you want to use different tables for particular elements, as specified under element. table_layer: str | None - Layer of the table to use for coloring if `color` is in :attr:`sdata.table.var_names`. If None, the data in - :attr:`sdata.table.X` is used for coloring. + Layer of the table to use for coloring if `color` is present in the ``var_names`` of the table. If None, the + data stored in ``X`` is used for coloring. **kwargs : Any Additional arguments for customization. This can include: @@ -627,8 +632,8 @@ def render_labels( The name of the labels element to render. If `None`, all label elements in the `SpatialData` object will be used and all parameters will be broadcasted if possible. color : str | None - Can either be string representing a color-like or key in :attr:`sdata.table.obs` or in the index of - :attr:`sdata.table.var`. The latter can be used to color by categorical or continuous variables. If the + Can either be string representing a color-like or key in the ``obs`` dataframe or ``var`` index of the + associated table. The latter can be used to color by categorical or continuous variables. If the color column is found in multiple locations, please provide the table_name to be used for the element if you would like a specific table to be used. By default one table will automatically be choosen. groups : list[str] | str | None @@ -664,8 +669,8 @@ def render_labels( table_name: str | None Name of the table containing the color columns. table_layer: str | None - Layer of the AnnData table to use for coloring if `color` is in :attr:`sdata.table.var_names`. If None, - :attr:`sdata.table.X` of the default table is used for coloring. + Layer of the AnnData table to use for coloring if `color` is present in the ``var_names`` of the default + table. If None, the ``X`` matrix of the default table is used for coloring. kwargs Additional arguments to be passed to cmap and norm. From f477376180f935094dc4d009e51e44d60d060027 Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Fri, 14 Nov 2025 22:05:53 +0100 Subject: [PATCH 14/16] safeguarded test --- src/spatialdata_plot/pl/utils.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/spatialdata_plot/pl/utils.py b/src/spatialdata_plot/pl/utils.py index cf21d212..47abb267 100644 --- a/src/spatialdata_plot/pl/utils.py +++ b/src/spatialdata_plot/pl/utils.py @@ -138,6 +138,27 @@ def _extract_scalar_value(value: Any, default: float = 0.0) -> float: return default +def _ensure_region_is_categorical(table: AnnData) -> None: + """Ensure the region column used for annotation remains categorical.""" + attrs = table.uns.get("spatialdata_attrs", {}) + region_key = attrs.get("region_key") + + if not region_key or region_key not in table.obs: + return + + if not isinstance(table.obs[region_key].dtype, CategoricalDtype): + table.obs[region_key] = table.obs[region_key].astype("category") + + +def _ensure_tables_have_categorical_regions(sdata: SpatialData) -> None: + """Cast the region columns of all tables to categorical if necessary.""" + if sdata.tables is None: + return + + for table in sdata.tables.values(): + _ensure_region_is_categorical(table) + + def _verify_plotting_tree(sdata: SpatialData) -> SpatialData: """Verify that the plotting tree exists, and if not, create it.""" if not hasattr(sdata, "plotting_tree"): @@ -2127,6 +2148,8 @@ def _validate_col_for_column_table( table_name: str | None, labels: bool = False, ) -> tuple[str | None, str | None]: + _ensure_tables_have_categorical_regions(sdata) + if not labels and col_for_color in sdata[element_name].columns: table_name = None elif table_name is not None: From 1d62f44c17f86ac3c9371cf7ddb5f6bd813d5e43 Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Fri, 14 Nov 2025 22:13:16 +0100 Subject: [PATCH 15/16] safeguarded test --- tests/pl/test_render_labels.py | 2 +- tests/pl/test_render_points.py | 2 +- tests/pl/test_render_shapes.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/pl/test_render_labels.py b/tests/pl/test_render_labels.py index aa89d6c5..ffa6524f 100644 --- a/tests/pl/test_render_labels.py +++ b/tests/pl/test_render_labels.py @@ -305,7 +305,7 @@ def test_warns_when_table_does_not_annotate_element(sdata_blobs: SpatialData): # Create a table that annotates a DIFFERENT element than the one we will render other_table = sdata_blobs_local["table"].copy() - other_table.obs["region"] = "blobs_multiscale_labels" + other_table.obs["region"] = pd.Categorical(["blobs_multiscale_labels"] * other_table.n_obs) other_table.uns["spatialdata_attrs"]["region"] = "blobs_multiscale_labels" sdata_blobs_local["other_table"] = other_table diff --git a/tests/pl/test_render_points.py b/tests/pl/test_render_points.py index fb433ac1..abd3fcb1 100644 --- a/tests/pl/test_render_points.py +++ b/tests/pl/test_render_points.py @@ -473,7 +473,7 @@ def test_warns_when_table_does_not_annotate_element(sdata_blobs: SpatialData): # Create a table that annotates a DIFFERENT element than the one we will render other_table = sdata_blobs_local["table"].copy() - other_table.obs["region"] = "blobs_labels" # Different from blobs_points + other_table.obs["region"] = pd.Categorical(["blobs_labels"] * other_table.n_obs) other_table.uns["spatialdata_attrs"]["region"] = "blobs_labels" sdata_blobs_local["other_table"] = other_table diff --git a/tests/pl/test_render_shapes.py b/tests/pl/test_render_shapes.py index 5c668a97..989e7160 100644 --- a/tests/pl/test_render_shapes.py +++ b/tests/pl/test_render_shapes.py @@ -670,7 +670,7 @@ def test_warns_when_table_does_not_annotate_element(sdata_blobs: SpatialData): # Create a table that annotates a DIFFERENT element than the one we will render other_table = sdata_blobs_local["table"].copy() - other_table.obs["region"] = "blobs_points" # Different from blobs_circles + other_table.obs["region"] = pd.Categorical(["blobs_points"] * other_table.n_obs) other_table.uns["spatialdata_attrs"]["region"] = "blobs_points" sdata_blobs_local["other_table"] = other_table From e14a2f995efb90ba1701b7d8c40474ec0132c797 Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Sat, 15 Nov 2025 13:26:07 +0100 Subject: [PATCH 16/16] bump --- .pre-commit-config.yaml | 2 +- docs/conf.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6fddec1f..23828b79 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: hooks: - id: pyproject-fmt - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.13.2 + rev: v0.14.0 hooks: - id: ruff-check types_or: [python, pyi, jupyter] diff --git a/docs/conf.py b/docs/conf.py index dc04c192..7d625e46 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -21,7 +21,7 @@ info = metadata("spatialdata-plot") project_name = info["Name"] author = info["Author"] -copyright = f"{datetime.now():%Y}, {author}." +copyright = f"{datetime.now():%Y}, {author}" version = info["Version"] urls = dict(pu.split(", ") for pu in info.get_all("Project-URL")) repository_url = urls["Source"] @@ -37,7 +37,7 @@ html_context = { "display_github": True, # Integrate GitHub "github_user": "scverse", - "github_repo": project_name, + "github_repo": "https://github.com/scverse/spatialdata-plot", "github_version": "main", "conf_py_path": "/docs/", }