diff --git a/.cruft.json b/.cruft.json index 6c79e228..edf03f69 100644 --- a/.cruft.json +++ b/.cruft.json @@ -1,7 +1,7 @@ { "template": "https://github.com/scverse/cookiecutter-scverse", - "commit": "6ff5b92b5d44ea6d8a88e47538475718d467db95", - "checkout": "v0.7.0", + "commit": "6518dfa1abde7379ea7255daf0ce09c23f2b4c94", + "checkout": "v0.8.0", "context": { "cookiecutter": { "project_name": "spatialdata-plot", @@ -13,6 +13,7 @@ "github_repo": "spatialdata-plot", "license": "BSD 3-Clause License", "ide_integration": false, + "issue_categorization": "", "_copy_without_render": [ ".github/workflows/build.yaml", ".github/workflows/test.yaml", @@ -36,7 +37,7 @@ "trim_blocks": true }, "_template": "https://github.com/scverse/cookiecutter-scverse", - "_commit": "6ff5b92b5d44ea6d8a88e47538475718d467db95" + "_commit": "6518dfa1abde7379ea7255daf0ce09c23f2b4c94" } }, "directory": null diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 33720e0b..cc32eda8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,6 +1,6 @@ name: Bug report description: Report something that is broken or incorrect -type: Bug +labels: bug body: - type: markdown attributes: @@ -27,7 +27,7 @@ body: Which version of packages. Please install `session-info2`, run the following command in a notebook, - click the "Copy as Markdown" button, then paste the results into the text box below. + 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) @@ -39,3 +39,55 @@ body: >>> 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 fed9c64f..5b62547f 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,4 +2,4 @@ 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. + about: If you have questions about “How to do X”, please ask them here. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..914ad69d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + cooldown: + default-days: 7 + groups: + actions-deps: + patterns: + - "*" diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9f3eafb7..f9a98c54 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -10,16 +10,20 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +permissions: + contents: read + jobs: package: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: filter: blob:none fetch-depth: 0 + persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 - name: Build package run: uv build - name: Check package @@ -28,14 +32,15 @@ jobs: docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: submodules: true fetch-depth: 0 + persist-credentials: false - name: Pull latest notebooks # Match RTD: render whatever is on notebooks-repo main, not the pinned SHA. run: git submodule update --init --remote --recursive - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 - name: Build docs - run: uvx --with="virtualenv<21" hatch run docs:build + run: uvx hatch run docs:build diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b55b308a..a56eb1d2 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -4,43 +4,30 @@ on: release: types: [published] -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true +# Use "trusted publishing", see https://docs.pypi.org/trusted-publishers/ +permissions: {} jobs: - build: + release: + name: Upload release to PyPI runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/spatialdata_plot + permissions: + contents: read + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: filter: blob:none fetch-depth: 0 + persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 + with: + enable-cache: false - name: Build package run: uv build - - name: Check package - run: uvx twine check --strict dist/* - - name: Upload build artifacts - uses: actions/upload-artifact@v4 - with: - name: dist - path: dist/ - - publish: - needs: build - runs-on: ubuntu-latest - permissions: - id-token: write # for PyPI trusted publishing - environment: - name: pypi - url: https://pypi.org/p/spatialdata-plot - steps: - - name: Download build artifacts - uses: actions/download-artifact@v4 - with: - name: dist - path: dist/ - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 1db7c86c..ab116f23 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -3,7 +3,6 @@ name: Test on: push: branches: [main] - tags: ["v*"] pull_request: branches: [main] schedule: @@ -13,23 +12,27 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -env: - MPLBACKEND: agg +permissions: + contents: read jobs: - # Dynamically extract the test matrix from hatch so pyproject.toml is the single source of truth. - # See [[tool.hatch.envs.hatch-test.matrix]] in pyproject.toml. + # 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 + runs-on: ubuntu-slim outputs: envs: ${{ steps.get-envs.outputs.envs }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: filter: blob:none fetch-depth: 0 + persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 - name: Get test environments id: get-envs run: | @@ -44,10 +47,12 @@ jobs: )') echo "envs=${ENVS_JSON}" | tee $GITHUB_OUTPUT + # Run tests through hatch. Spawns a separate runner for each environment defined in the hatch matrix obtained above. test: needs: get-environments permissions: id-token: write # for codecov OIDC + contents: read strategy: fail-fast: false @@ -57,23 +62,27 @@ jobs: name: ${{ matrix.env.label }} runs-on: ${{ matrix.os }} - continue-on-error: ${{ contains(matrix.env.name, 'pre') }} + continue-on-error: ${{ contains(matrix.env.name, 'pre') }} # make "all-green" pass even if pre-release job fails steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: filter: blob:none fetch-depth: 0 + persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 with: python-version: ${{ matrix.env.python }} - name: Ensure figure directory exists run: mkdir -p tests/figures - name: Create hatch environment run: uvx hatch env create ${{ matrix.env.name }} + - name: List all installed package versions + run: uvx hatch run ${{ matrix.env.name }}:uv pip list - name: Run tests env: + MPLBACKEND: agg DISPLAY: ":42" run: >- uvx hatch run ${{ matrix.env.name }}:${{ @@ -88,19 +97,19 @@ jobs: uvx hatch run ${{ matrix.env.name }}:cov-report - name: Archive visual test figures if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: visual_test_results_${{ matrix.env.name }} path: tests/figures/* - name: Upload coverage if: matrix.env.name == 'hatch-test.py3.14-stable' - uses: codecov/codecov-action@v6 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: fail_ci_if_error: true use_oidc: true - # Single required check for branch protection. - # See https://github.com/re-actors/alls-green#why + # 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() @@ -109,6 +118,6 @@ jobs: - test runs-on: ubuntu-latest steps: - - uses: re-actors/alls-green@release/v1 + - uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 with: jobs: ${{ toJSON(needs) }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1f52f820..368303a4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,16 +7,16 @@ default_stages: minimum_pre_commit_version: 2.16.0 repos: - repo: https://github.com/biomejs/pre-commit - rev: v2.4.14 + rev: v2.5.3 hooks: - id: biome-format - exclude: ^\.cruft\.json$ # inconsistent indentation with cruft + exclude: ^\.cruft\.json$ # inconsistent indentation with cruft - file never to be modified manually. - repo: https://github.com/tox-dev/pyproject-fmt - rev: v2.21.1 + rev: v2.25.3 hooks: - id: pyproject-fmt - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.12 + rev: v0.15.21 hooks: - id: ruff-check types_or: [python, pyi, jupyter] @@ -33,8 +33,16 @@ repos: args: [--fix=lf] - id: trailing-whitespace - id: check-case-conflict + # Check that there are no merge conflicts (could be generated by template sync) - id: check-merge-conflict args: [--assume-in-merge] + + - repo: https://github.com/zizmorcore/zizmor-pre-commit + rev: v1.24.1 + hooks: + - id: zizmor + args: [--no-progress, --fix] + - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.20.2 hooks: diff --git a/.readthedocs.yaml b/.readthedocs.yaml index c5deab19..6ebbae81 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,9 +1,9 @@ -# https://docs.readthedocs.io/en/stable/config-file/v2.html +# https://docs.readthedocs.io/page/config-file/v2.html version: 2 build: os: ubuntu-24.04 tools: - python: "3.13" + python: "3.14" nodejs: latest jobs: post_checkout: @@ -18,8 +18,7 @@ build: - asdf global uv latest build: html: - # TODO: remove "--with=virtualenv<21" once hatch is compatible with virtualenv 21+ (pypa/hatch#2193) - - uvx "--with=virtualenv<21" hatch run docs:build + - uvx hatch run docs:build - mv docs/_build $READTHEDOCS_OUTPUT submodules: diff --git a/docs/_templates/autosummary/class.rst b/docs/_templates/autosummary/class.rst deleted file mode 100644 index 7b4a0cf8..00000000 --- a/docs/_templates/autosummary/class.rst +++ /dev/null @@ -1,61 +0,0 @@ -{{ fullname | escape | underline}} - -.. currentmodule:: {{ module }} - -.. add toctree option to make autodoc generate the pages - -.. autoclass:: {{ objname }} - -{% block attributes %} -{% if attributes %} -Attributes table -~~~~~~~~~~~~~~~~ - -.. autosummary:: -{% for item in attributes %} - ~{{ name }}.{{ item }} -{%- endfor %} -{% endif %} -{% endblock %} - -{% block methods %} -{% if methods %} -Methods table -~~~~~~~~~~~~~ - -.. autosummary:: -{% for item in methods %} - {%- if item != '__init__' %} - ~{{ name }}.{{ item }} - {%- endif -%} -{%- endfor %} -{% endif %} -{% endblock %} - -{% block attributes_documentation %} -{% if attributes %} -Attributes -~~~~~~~~~~ - -{% for item in attributes %} - -.. autoattribute:: {{ [objname, item] | join(".") }} -{%- endfor %} - -{% endif %} -{% endblock %} - -{% block methods_documentation %} -{% if methods %} -Methods -~~~~~~~ - -{% for item in methods %} -{%- if item != '__init__' %} - -.. automethod:: {{ [objname, item] | join(".") }} -{%- endif -%} -{%- endfor %} - -{% endif %} -{% endblock %} diff --git a/docs/conf.py b/docs/conf.py index fd5c69ce..b927ef31 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,7 +2,7 @@ # 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 +# https://www.sphinx-doc.org/page/usage/configuration.html # -- Path setup -------------------------------------------------------------- import shutil @@ -60,9 +60,9 @@ "sphinxcontrib.bibtex", "sphinxcontrib.katex", "sphinx_autodoc_typehints", - "sphinx_tabs.tabs", "IPython.sphinxext.ipython_console_highlighting", "sphinxext.opengraph", + "scverse_misc.sphinx_ext", *[p.stem for p in (HERE / "extensions").glob("*.py")], ] @@ -88,6 +88,7 @@ nb_execution_mode = "off" nb_merge_streams = True typehints_defaults = "braces" +always_use_bars_union = True # use `|` instead of `Union` in types even when building with Python ≤3.14 source_suffix = { ".rst": "restructuredtext", @@ -96,9 +97,9 @@ } intersphinx_mapping = { - "python": ("https://docs.python.org/3.13", None), - "anndata": ("https://anndata.readthedocs.io/en/stable/", None), - "scanpy": ("https://scanpy.readthedocs.io/en/stable/", None), + "python": ("https://docs.python.org/3", None), + "anndata": ("https://anndata.scverse.org/en/stable/", None), + "scanpy": ("https://scanpy.scverse.org/en/stable/", None), "numpy": ("https://numpy.org/doc/stable/", None), "geopandas": ("https://geopandas.org/en/stable/", None), "xarray": ("https://docs.xarray.dev/en/stable/", None), diff --git a/docs/contributing.md b/docs/contributing.md index 76e42673..33afd7d0 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -9,7 +9,7 @@ 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 +[scanpy developer guide]: https://scanpy.scverse.org/page/dev/ :::{tip} The *hatch* project manager @@ -33,8 +33,9 @@ it is still possible to use different tools to manage dependencies, such as `uv` 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 +:::::{tab-set} +::::{tab-item} Hatch +:sync: hatch 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 @@ -45,43 +46,75 @@ hatch test # defined in the table [tool.hatch.envs.hatch-test] in pyproject.tom 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. +### VS Code +If you are using VS code, install the [hatch-code][] extension. +Additionally, make sure that the `vscode-python-environments` extension is installed (should be by default) +and `"python.useEnvironmentsExtension": true` is activated in your `settings.json`. + +Next, open the "Python Environment Managers" sidebar. +You can do so by opening the command palette (Ctrl+Shift+P) and searching for `Python: Focus on Environment Managers View`. +It will show a collapsible list where you can expand "Hatch" +and activate an environment by clicking on the checkmark next to it. +As the main development environment, we recommend to use `hatch-test` with the latest supported Python version. + +### Other IDEs + +For other IDEs, you’ll have to point the editor at the paths to the virtual environments manually. 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.12-stable | dev | coverage-enable-subprocess==1.0 | cov-combine | +| | | hatch-test.py3.14-stable | test | coverage[toml]~=7.4 | cov-report | +| | | hatch-test.py3.14-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. +As the main development environment, we recommend to use `hatch-test` with the latest supported Python version. +In this example, it would be `hatch-test.py3.14-stable`. Next, create the environment with ```bash -hatch env create +hatch env create hatch-test.py3.14-stable ``` Then, obtain the path to the environment using ```bash -hatch env find +hatch env find hatch-test.py3.14-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. +and manually point it to the python binary. + :::: -::::{group-tab} uv +::::{tab-item} uv +:sync: 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 +To initialize a virtual environment in the `.venv` directory of your project, simply run ```bash uv sync --all-extras @@ -91,10 +124,11 @@ The `.venv` directory is typically automatically discovered by IDEs such as VS C :::: -::::{group-tab} Pip +::::{tab-item} Pip +:sync: pip -Pip is nowadays mostly superseded by environment managers such as [hatch][]. -However, for the sake of completeness, and since it's ubiquitously available, +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 @@ -109,24 +143,26 @@ The `.venv` directory is typically automatically discovered by IDEs such as VS C ::::: [hatch environments]: https://hatch.pypa.io/latest/tutorials/environment/basic-usage/ +[hatch-code]: https://marketplace.visualstudio.com/items?itemName=PyPA.hatch [uv]: https://docs.astral.sh/uv/ ## Code-style -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. +This package uses [pre-commit][]-style hooks to enforce consistent code-styles. +We recommend running them with [prek][], a fast, drop-in replacement for `pre-commit` that reads the same `.pre-commit-config.yaml`. +On every commit, the checks will either automatically fix issues with the code, or raise an error message. -To enable pre-commit locally, simply run +To enable the checks locally, install [prek][] (e.g. with `uv tool install prek`) and run ```bash -pre-commit install +prek install ``` in the root of the repository. -Pre-commit will automatically download all dependencies when it is run for the first time. +prek 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 the checks 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 @@ -135,12 +171,13 @@ 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. +While the [pre-commit.ci][] is useful, we strongly encourage installing and running the checks locally first to understand their usage. Finally, most editors have an _autoformat on save_ feature. Consider enabling this option for [ruff][ruff-editors] and [biome][biome-editors]. [pre-commit]: https://pre-commit.com/ +[prek]: https://prek.j178.dev/ [pre-commit.ci]: https://pre-commit.ci/ [ruff-editors]: https://docs.astral.sh/ruff/integrations/ [biome-editors]: https://biomejs.dev/guides/integrate-in-editor/ @@ -153,17 +190,14 @@ 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. -Just point yours to one of the environments returned by - -```bash -hatch env create hatch-test # create test environments for all supported versions -hatch env find hatch-test # list all possible test environment paths -``` +If you set up your virtual environments as described in [installing dev dependencies](#installing-dev-dependencies), +test cases should be automatically discovered by your IDE. Alternatively, you can run all tests from the command line by executing -:::::{tabs} -::::{group-tab} Hatch +:::::{tab-set} +::::{tab-item} Hatch +:sync: hatch ```bash hatch test # test with the highest supported Python version @@ -173,7 +207,8 @@ hatch test --all # test with all supported Python versions :::: -::::{group-tab} uv +::::{tab-item} uv +:sync: uv ```bash uv run pytest @@ -181,7 +216,8 @@ uv run pytest :::: -::::{group-tab} Pip +::::{tab-item} Pip +:sync: pip ```bash source .venv/bin/activate @@ -195,23 +231,12 @@ in the root of the repository. [pytest]: https://docs.pytest.org/ -### Testing the correctness of the plots - -Many tests will produce plots and check that they are correct by comparing them with a previously saved and serialized version of the same plots. The ground truth images are located in `tests/_images`. Different OS/versions may produce similar but not identical plots (for instance the ticks/padding could vary). To take into account for this please consider the following: - -- you should not use locally generated plots as ground truth images, but you should commit images that have been generated by a GitHub Action. The recommended workflow is to go to the ["actions" page for the repo](https://github.com/scverse/spatialdata-plot/actions/workflows/test.yaml), download the artifacts, and upload them as ground truth (after having reviewed them). -- the ground truth images need to be updated when a new test is passing, or when a test starts producing a slightly different (but consistent) plot. -- please never replace the ground truth images without having manually reviewed them. -- if you run the tests locally in macOS or Windows they will likely fail because the ground truth images are generated using Ubuntu. To overcome this you can use `act`, which will generate a Docker reproducing the environment used in the GitHub Action. After the Docker container is generated you can use it within IDEs to run tests and debug code. -- in the case of PyCharm, it is easier to create a container from a `Dockerfile` instead of using `act`. Please in such case use the `Dockerfile` made available in the repository. In this [thread](https://github.com/scverse/spatialdata-plot/pull/397) you can find extra details, and the process is shown [in this Loom recording](https://www.loom.com/share/172e309e5803419bb3b3107eee1f3a4e). -- If you are encountering problems with `act` or `docker`, please [get in touch with the developers via Zulip](https://scverse.zulipchat.com/#narrow/channel/443514-spatialdata-dev) and we will help troubleshoot the issue. - ### Continuous integration 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. @@ -250,62 +275,30 @@ 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 [napoleon][numpydoc-napoleon] extension). -- Jupyter notebooks as tutorials through [myst-nb][] (See [Gallery notebooks (submodule)](#gallery-notebooks-submodule)) +- [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 scanpy's {doc}`scanpy:dev/documentation` for more information on how to write your own. +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]: https://www.sphinx-doc.org/ +[myst]: https://myst-parser.readthedocs.io/page/intro.html +[myst-nb]: https://myst-nb.readthedocs.io/ +[numpydoc-napoleon]: https://www.sphinx-doc.org/page/usage/extensions/napoleon.html +[numpydoc]: https://numpydoc.readthedocs.io/page/format.html [sphinx-autodoc-typehints]: https://github.com/tox-dev/sphinx-autodoc-typehints -### Gallery notebooks (submodule) - -The gallery rendered into the docs lives in a separate repository, -[`scverse/spatialdata-plot-notebooks`][notebooks-repo], mounted here as a git -submodule at `docs/notebooks/`. This follows the scverse convention used by -`anndata`, `spatialdata`, `scvi-tools`, and `squidpy`. - -Notebooks are pre-executed by humans and committed with their outputs; the -docs build performs no execution and pulls no data. A scheduled CI job in the -notebooks repo re-executes every notebook against the latest -`spatialdata-plot` release and fails on output drift. +### Tutorials with myst-nb and jupyter notebooks -#### Working with the gallery locally +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 responsibility to update and re-run the notebook whenever necessary. -```bash -# First-time clone — pull this repo with submodules -git clone --recurse-submodules https://github.com/scverse/spatialdata-plot.git - -# Already cloned without submodules — initialise after the fact -git submodule update --init --recursive - -# Pull the latest gallery content from the notebooks repo's main branch -git submodule update --remote docs/notebooks -``` - -ReadTheDocs is configured (`submodules: include: all` in `.readthedocs.yaml`) -to fetch the submodule on every build, so PR previews always render the -current pinned gallery content. - -#### Adding or editing a notebook - -Notebook changes are made in the [notebooks repo][notebooks-repo], not here. -See its `CONTRIBUTING.md` for the workflow. After your notebook PR merges -there, open a small follow-up PR in this repo bumping the submodule pin: - -```bash -git submodule update --remote docs/notebooks -git add docs/notebooks -git commit -m "Bump notebooks submodule" -``` +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. -[notebooks-repo]: https://github.com/scverse/spatialdata-plot-notebooks +[issue-render-notebooks]: https://github.com/scverse/cookiecutter-scverse/issues/40 #### Hints @@ -318,19 +311,9 @@ git commit -m "Bump notebooks submodule" ### Building the docs locally -:::{important} -The docs include a git submodule (`docs/notebooks` → `spatialdata-plot-notebooks`). -If you cloned this repo without `--recurse-submodules`, initialise it once before building: - -```bash -git submodule update --init --recursive -``` - -Without the submodule, the gallery pages will be missing and sphinx will warn about broken toctree entries. -::: - -:::::{tabs} -::::{group-tab} Hatch +:::::{tab-set} +::::{tab-item} Hatch +:sync: hatch ```bash hatch run docs:build @@ -339,7 +322,8 @@ hatch run docs:open :::: -::::{group-tab} uv +::::{tab-item} uv +:sync: uv ```bash cd docs @@ -349,7 +333,8 @@ uv run sphinx-build -M html . _build -W :::: -::::{group-tab} Pip +::::{tab-item} Pip +:sync: pip ```bash source .venv/bin/activate diff --git a/docs/extensions/typed_returns.py b/docs/extensions/typed_returns.py deleted file mode 100644 index 0fbffefe..00000000 --- a/docs/extensions/typed_returns.py +++ /dev/null @@ -1,32 +0,0 @@ -# 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: Iterable[str]) -> Generator[str, None, None]: - for line in lines: - 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: 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 - - -def setup(app: Sphinx): - """Set app.""" - NumpyDocstring._parse_returns_section = _parse_returns_section diff --git a/pyproject.toml b/pyproject.toml index ee0cf8ba..f522b60c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ urls.Source = "https://github.com/scverse/spatialdata-plot.git" [dependency-groups] dev = [ - "pre-commit", + "prek", "twine>=4.0.2", ] test = [ @@ -60,83 +60,71 @@ doc = [ "ipykernel", "ipython>=8.6", "myst-nb>=1.1", + "pandas", + "scverse-misc[sphinx]>=0.1.2", "sphinx>=8.1", "sphinx-autodoc-typehints", "sphinx-book-theme>=1", "sphinx-copybutton", "sphinx-design", - "sphinx-tabs", "sphinxcontrib-bibtex>=1", "sphinxcontrib-katex", "sphinxext-opengraph", ] + [tool.hatch] +version.source = "vcs" +metadata.allow-direct-references = true build.hooks.vcs.version-file = "_version.py" build.targets.wheel.packages = [ "src/spatialdata_plot" ] envs.default.installer = "uv" envs.default.dependency-groups = [ "dev" ] -envs.docs.dependency-groups = [ "doc" ] envs.docs.scripts.build = "sphinx-build -M html docs docs/_build -W {args}" -envs.docs.scripts.open = "python -m webbrowser -t docs/_build/html/index.html" envs.docs.scripts.clean = "git clean -fdX -- {args:docs}" -envs.hatch-test.dependency-groups = [ "test" ] +envs.docs.scripts.open = "python -m webbrowser -t docs/_build/html/index.html" +envs.docs.dependency-groups = [ "doc" ] +envs.hatch-test.scripts.cov-combine = "coverage combine" +envs.hatch-test.scripts.cov-report = [ "coverage report", "coverage xml -o coverage.xml" ] +envs.hatch-test.scripts.run = "pytest{env:HATCH_TEST_ARGS:} -p no:cov {args}" +envs.hatch-test.scripts.run-cov = "coverage run -m pytest{env:HATCH_TEST_ARGS:} -p no:cov {args}" envs.hatch-test.matrix = [ { deps = [ "stable" ], python = [ "3.12", "3.14" ] }, { deps = [ "pre" ], python = [ "3.14" ] } ] envs.hatch-test.overrides.matrix.deps.env-vars = [ - { key = "UV_PRERELEASE", value = "allow", if = [ "pre" ] }, + { value = "allow", key = "UV_PRERELEASE", if = [ "pre" ] }, ] -envs.hatch-test.scripts.run = "pytest{env:HATCH_TEST_ARGS:} -p no:cov {args}" -envs.hatch-test.scripts.run-cov = "coverage run -m pytest{env:HATCH_TEST_ARGS:} -p no:cov {args}" -envs.hatch-test.scripts.cov-combine = "coverage combine" -envs.hatch-test.scripts.cov-report = [ "coverage report", "coverage xml -o coverage.xml" ] -metadata.allow-direct-references = true -version.source = "vcs" - -[tool.pixi.workspace] -channels = [ "conda-forge" ] -platforms = [ "linux-64", "osx-arm64" ] - -[tool.pixi.dependencies] -python = ">=3.12" - -[tool.pixi.pypi-dependencies] -spatialdata-plot = { path = ".", editable = true } +envs.hatch-test.dependency-groups = [ "test" ] +[tool.pixi] +workspace.channels = [ "conda-forge" ] +workspace.platforms = [ "linux-64", "osx-arm64" ] +dependencies.python = ">=3.12" +pypi-dependencies.spatialdata-plot = { path = ".", editable = true } +tasks.format = "ruff format ." +tasks.kernel-install = 'python -m ipykernel install --user --name pixi-dev --display-name "sdata-plot (dev)"' +tasks.kernel-install-interactive = "python -m ipykernel install --user --name sdata-plot-interactive --display-name \"sdata-plot (interactive)\"" +tasks.lab = "jupyter lab" +tasks.lint = "ruff check ." +tasks.pre-commit-install = "pre-commit install" +tasks.pre-commit-run = "pre-commit run --all-files" +tasks.test = "pytest -v --color=yes --tb=short --durations=10" # When the `interactive` feature is active, install the package with the # `interactive` PyPI extra (anywidget, ipykernel, ipywidgets) so the pixi # env mirrors what `pip install spatialdata-plot[interactive]` would give. -[tool.pixi.feature.interactive.pypi-dependencies] -spatialdata-plot = { path = ".", editable = true, extras = [ "interactive" ] } - -[tool.pixi.tasks] -format = "ruff format ." -kernel-install = 'python -m ipykernel install --user --name pixi-dev --display-name "sdata-plot (dev)"' -kernel-install-interactive = 'python -m ipykernel install --user --name sdata-plot-interactive --display-name "sdata-plot (interactive)"' -lab = "jupyter lab" -lint = "ruff check ." -pre-commit-install = "pre-commit install" -pre-commit-run = "pre-commit run --all-files" -test = "pytest -v --color=yes --tb=short --durations=10" - +feature.interactive.pypi-dependencies.spatialdata-plot = { path = ".", extras = [ "interactive" ], editable = true } # for gh-actions -[tool.pixi.feature.py312.dependencies] -python = "3.12.*" - -[tool.pixi.feature.py313.dependencies] -python = "3.13.*" - -[tool.pixi.environments] +feature.py312.dependencies.python = "3.12.*" +feature.py313.dependencies.python = "3.13.*" # 3.13 lane -default = { features = [ "py313" ], solve-group = "py313" } +environments.default = { features = [ "py313" ], solve-group = "py313" } +environments.dev-interactive-py313 = { features = [ "dev", "test", "interactive", "py313" ], solve-group = "py313" } # 3.12 lane (for gh-actions) -dev-py312 = { features = [ "dev", "test", "py312" ], solve-group = "py312" } -dev-py313 = { features = [ "dev", "test", "py313" ], solve-group = "py313" } -dev-interactive-py313 = { features = [ "dev", "test", "interactive", "py313" ], solve-group = "py313" } -docs-py312 = { features = [ "doc", "py312" ], solve-group = "py312" } -docs-py313 = { features = [ "doc", "py313" ], solve-group = "py313" } -test-py313 = { features = [ "test", "py313" ], solve-group = "py313" } +environments.dev-py312 = { features = [ "dev", "test", "py312" ], solve-group = "py312" } +environments.dev-py313 = { features = [ "dev", "test", "py313" ], solve-group = "py313" } +environments.docs-py312 = { features = [ "doc", "py312" ], solve-group = "py312" } +environments.docs-py313 = { features = [ "doc", "py313" ], solve-group = "py313" } +environments.test-py313 = { features = [ "test", "py313" ], solve-group = "py313" } [tool.ruff] line-length = 120 @@ -194,12 +182,12 @@ lint.pydocstyle.convention = "numpy" [tool.pytest] ini_options.testpaths = [ "tests" ] -ini_options.xfail_strict = true ini_options.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 ] +ini_options.xfail_strict = true # info on how to use this https://stackoverflow.com/questions/57925071/how-do-i-avoid-getting-deprecationwarning-from-inside-dependencies-with-pytest ini_options.filterwarnings = [ # "ignore:.*U.*mode is deprecated:DeprecationWarning", @@ -217,11 +205,9 @@ paths.source = [ "*/site-packages/spatialdata_plot", ] -[tool.jupytext] -formats = "ipynb,md" - [tool.cruft] skip = [ + ".git", "tests", "src/**/__init__.py", "src/**/basic.py",