diff --git a/.cruft.json b/.cruft.json index 192a336..5bc242b 100644 --- a/.cruft.json +++ b/.cruft.json @@ -1,7 +1,7 @@ { "template": "https://github.com/scverse/cookiecutter-scverse", - "commit": "d383d94fadff9e4e6fdb59d77c68cb900d7cedec", - "checkout": "v0.6.0", + "commit": "6ff5b92b5d44ea6d8a88e47538475718d467db95", + "checkout": "v0.7.0", "context": { "cookiecutter": { "project_name": "decoupler", @@ -36,7 +36,7 @@ "trim_blocks": true }, "_template": "https://github.com/scverse/cookiecutter-scverse", - "_commit": "d383d94fadff9e4e6fdb59d77c68cb900d7cedec" + "_commit": "6ff5b92b5d44ea6d8a88e47538475718d467db95" } }, "directory": null diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3ca1ccb..6104b9e 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 -labels: bug +type: Bug body: - type: markdown attributes: @@ -9,8 +9,7 @@ body: 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. + * Replicate problems on public datasets or share data subsets when full sharing isn't possible. - type: textarea id: report diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index c1636a7..db1e164 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,6 +1,6 @@ name: Feature request description: Propose a new feature for decoupler -labels: enhancement +type: Enhancement body: - type: textarea id: description diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 83e01a1..c6ecc2f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -10,23 +10,16 @@ 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@v4 + - uses: actions/checkout@v5 with: filter: blob:none fetch-depth: 0 - name: Install uv - uses: astral-sh/setup-uv@v5 - with: - cache-dependency-glob: pyproject.toml + uses: astral-sh/setup-uv@v7 - name: Build package run: uv build - name: Check package diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 80cd575..ba00a6f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -4,11 +4,6 @@ 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: @@ -20,14 +15,12 @@ jobs: permissions: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing 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 - with: - cache-dependency-glob: pyproject.toml + uses: astral-sh/setup-uv@v7 - name: Build package run: uv build - name: Publish package distributions to PyPI diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0bd76e8..6bf473b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -12,11 +12,6 @@ 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: # 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 @@ -28,12 +23,12 @@ jobs: outputs: envs: ${{ steps.get-envs.outputs.envs }} 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 - name: Get test environments id: get-envs run: | @@ -51,6 +46,8 @@ jobs: # 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 strategy: fail-fast: false @@ -60,17 +57,17 @@ jobs: name: ${{ matrix.env.label }} runs-on: ${{ matrix.os }} + continue-on-error: ${{ contains(matrix.env.name, 'pre') }} # make "all-green" pass even if pre-release job fails 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: create hatch environment run: uvx hatch env create ${{ matrix.env.name }} - name: run tests using hatch @@ -87,6 +84,9 @@ jobs: uvx hatch run ${{ matrix.env.name }}:coverage xml # create report for upload - name: Upload coverage uses: codecov/codecov-action@v5 + with: + fail_ci_if_error: true + use_oidc: 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. diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 96d19cb..ca96c01 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -3,7 +3,8 @@ version: 2 build: os: ubuntu-24.04 tools: - python: "3.12" + python: "3.13" + nodejs: latest jobs: create_environment: - asdf plugin add uv diff --git a/docs/conf.py b/docs/conf.py index 9c5b751..1790bc5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -5,11 +5,14 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- +import shutil import sys from datetime import datetime from importlib.metadata import metadata from pathlib import Path +from sphinxcontrib import katex + HERE = Path(__file__).parent sys.path.insert(0, str(HERE / "extensions")) @@ -19,7 +22,7 @@ # 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("decoupler") -project_name = info["Name"] +project = info["Name"] author = info["Author"] copyright = f"{datetime.now():%Y}, scverse." version = info["Version"] @@ -37,7 +40,7 @@ html_context = { "display_github": True, # Integrate GitHub "github_user": "scverse", - "github_repo": project_name, + "github_repo": project, "github_version": "main", "conf_py_path": "/docs/", } @@ -54,9 +57,9 @@ "sphinx.ext.autosummary", "sphinx.ext.napoleon", "sphinxcontrib.bibtex", + "sphinxcontrib.katex", "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")], @@ -92,7 +95,8 @@ } intersphinx_mapping = { - "python": ("https://docs.python.org/3", None), + # TODO: replace `3.13` with `3` once ReadTheDocs supports building with Python 3.14 + "python": ("https://docs.python.org/3.13", None), "anndata": ("https://anndata.readthedocs.io/en/stable/", None), "scanpy": ("https://scanpy.readthedocs.io/en/stable/", None), "numpy": ("https://numpy.org/doc/stable/", None), @@ -115,7 +119,7 @@ html_static_path = ["_static"] html_css_files = ["css/custom.css"] -html_title = project_name +html_title = project html_logo = "_static/images/logo.png" html_favicon = "_static/images/logo.png" @@ -127,6 +131,7 @@ } pygments_style = "default" +katex_prerender = shutil.which(katex.NODEJS_BINARY) is not None nitpick_ignore = [ # If building the documentation fails because of a missing link that is outside your control, diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..b172fd1 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,330 @@ +# Contributing guide + +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. + +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, +you need additional python packages to [run tests](#writing-tests) and [build the documentation](#docs-building). + +:::::{tabs} +::::{group-tab} 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 +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.11-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. +In this example, it would be `hatch-test.py3.14-stable`. + +Next, create the environment with + +```bash +hatch env create hatch-test.py3.14-stable +``` + +Then, obtain the path to the environment using + +```bash +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. + +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 + +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 +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 + +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 + +```bash +pre-commit install +``` + +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 pre-commit.ci added a commit on a branch you still have been working on locally, simply use + +```bash +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 [ruff][ruff-editors] and [biome][biome-editors]. + +[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 + +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 +``` + +Alternatively, you can run all tests from the command line by executing + +:::::{tabs} +::::{group-tab} Hatch + +```bash +hatch test # test with the highest supported Python version +# or +hatch test --all # test with all supported Python versions +``` + +:::: + +::::{group-tab} uv + +```bash +uv run pytest +``` + +:::: + +::::{group-tab} Pip + +```bash +source .venv/bin/activate +pytest +``` + +:::: +::::: + +in the root of the repository. + +[pytest]: https://docs.pytest.org/ + +### 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). +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. + +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 + +### Updating the version number + +Before making a release, you need to update the version number in the `pyproject.toml` file. +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. + +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][]. + +[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: + +- 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 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 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][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` + +(docs-building)= + +### Building the docs locally + +:::::{tabs} +::::{group-tab} Hatch + +```bash +hatch run docs:build +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 +sphinx-build -M html . _build -W +(xdg-)open _build/html/index.html +``` + +:::: +::::: diff --git a/pyproject.toml b/pyproject.toml index 3c2f2a4..196fd05 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,10 +14,9 @@ maintainers = [ authors = [ { name = "Pau Badia i Mompel" }, ] -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", @@ -35,26 +34,36 @@ dependencies = [ "session-info2", "tqdm", ] -optional-dependencies.dev = [ +# https://docs.pypi.org/project_metadata/#project-urls +urls.Documentation = "https://decoupler.readthedocs.io/" +urls.Homepage = "https://github.com/PauBadiaM/decoupler" +urls.Source = "https://github.com/PauBadiaM/decoupler" + +[dependency-groups] +dev = [ "pre-commit", "twine>=4.0.2", ] -optional-dependencies.doc = [ - "docutils>=0.8,!=0.18.*,!=0.19.*", +test = [ + "coverage>=7.10", + "pytest", + "pytest-cov", # For VS Code’s coverage functionality +] +doc = [ "ipykernel", "ipython", "myst-nb>=1.1", "pandas", - # Until pybtex >0.24.0 releases: https://bitbucket.org/pybtex-devs/pybtex/issues/169/ - "setuptools", "sphinx>=8.1", "sphinx-autodoc-typehints", "sphinx-book-theme>=1", "sphinx-copybutton", "sphinx-tabs", "sphinxcontrib-bibtex>=1", + "sphinxcontrib-katex", "sphinxext-opengraph", ] + optional-dependencies.full = [ "dcor", "igraph", @@ -84,10 +93,10 @@ urls.Source = "https://github.com/scverse/decoupler" [tool.hatch.envs.default] installer = "uv" -features = [ "dev" ] +dependency-groups = [ "dev" ] [tool.hatch.envs.docs] -features = [ "doc" ] +dependency-groups = [ "doc" ] 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}" @@ -95,15 +104,15 @@ 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.14" ] # Test the newest supported Python version also with pre-release deps [[tool.hatch.envs.hatch-test.matrix]] deps = [ "pre" ] -python = [ "3.13" ] +python = [ "3.14" ] [tool.hatch.envs.hatch-test] -features = [ "dev", "test" ] +dependency-groups = [ "dev", "test" ] [tool.hatch.envs.hatch-test.overrides] # If the matrix variable `deps` is set to "pre", @@ -152,9 +161,9 @@ lint.per-file-ignores."docs/*" = [ "I" ] lint.per-file-ignores."tests/*" = [ "D" ] lint.pydocstyle.convention = "numpy" -[tool.pytest.ini_options] +[tool.pytest] +strict = true testpaths = [ "tests" ] -xfail_strict = true addopts = [ "--import-mode=importlib", # allow using test files with same name ]