diff --git a/.codecov.yaml b/.codecov.yaml new file mode 100644 index 000000000..d0c0e2917 --- /dev/null +++ b/.codecov.yaml @@ -0,0 +1,17 @@ +# Based on pydata/xarray +codecov: + require_ci_to_pass: no + +coverage: + 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 diff --git a/.cruft.json b/.cruft.json new file mode 100644 index 000000000..744ae8c70 --- /dev/null +++ b/.cruft.json @@ -0,0 +1,43 @@ +{ + "template": "https://github.com/scverse/cookiecutter-scverse", + "commit": "d383d94fadff9e4e6fdb59d77c68cb900d7cedec", + "checkout": "v0.6.0", + "context": { + "cookiecutter": { + "project_name": "squidpy", + "package_name": "squidpy", + "project_description": "Spatial Single Cell Analysis in Python", + "author_full_name": "Giovanni Palla", + "author_email": "giovanni.palla@helmholtz-muenchen.de", + "github_user": "giovp", + "github_repo": "squidpy", + "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": "d383d94fadff9e4e6fdb59d77c68cb900d7cedec" + } + }, + "directory": null +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..66678e378 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +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/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..3ca1ccbde --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,94 @@ +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 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: versions + attributes: + label: Versions + description: | + Which version of packages. + + Please install `session-info2`, run the following command in a notebook, + click the “Copy as Markdown” button, then paste the results into the text box below. + + ```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 new file mode 100644 index 000000000..5b62547f9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +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. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..b11e828fe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,11 @@ +name: Feature request +description: Propose a new feature for squidpy +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 diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 000000000..83e01a1ee --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,33 @@ +name: Check Build + +on: + 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@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 000000000..b4bf7da2a --- /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/squidpy + 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 new file mode 100644 index 000000000..0bd76e8cf --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,103 @@ +name: Test + +on: + 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: + # Get the test environment from hatch as defined in pyproject.toml. + # This ensures that the pyproject.toml is the single point of truth for test definitions and the same tests are + # run locally and on continuous integration. + # Check [[tool.hatch.envs.hatch-test.matrix]] in pyproject.toml and https://hatch.pypa.io/latest/environment/ for + # more details. + get-environments: + runs-on: ubuntu-latest + outputs: + envs: ${{ steps.get-envs.outputs.envs }} + steps: + - uses: actions/checkout@v4 + with: + filter: blob:none + fetch-depth: 0 + - name: Install uv + uses: astral-sh/setup-uv@v5 + - name: Get test environments + id: get-envs + run: | + ENVS_JSON=$(uvx hatch env show --json | jq -c 'to_entries + | map( + select(.key | startswith("hatch-test")) + | { + name: .key, + label: (if (.key | contains("pre")) then .key + " (PRE-RELEASE DEPENDENCIES)" else .key end), + python: .value.python + } + )') + echo "envs=${ENVS_JSON}" | tee $GITHUB_OUTPUT + + # Run tests through hatch. Spawns a separate runner for each environment defined in the hatch matrix obtained above. + test: + needs: get-environments + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + env: ${{ fromJSON(needs.get-environments.outputs.envs) }} + + name: ${{ matrix.env.label }} + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + with: + filter: blob:none + fetch-depth: 0 + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + python-version: ${{ matrix.env.python }} + cache-dependency-glob: pyproject.toml + - name: create hatch environment + run: uvx hatch env create ${{ matrix.env.name }} + - name: run tests using hatch + env: + MPLBACKEND: agg + PLATFORM: ${{ matrix.os }} + DISPLAY: :42 + run: uvx hatch run ${{ matrix.env.name }}:run-cov -v --color=yes -n auto + - name: generate coverage report + run: | + # See https://coverage.readthedocs.io/en/latest/config.html#run-patch + test -f .coverage || uvx hatch run ${{ matrix.env.name }}:cov-combine + uvx hatch run ${{ matrix.env.name }}:cov-report # report visibly + uvx hatch run ${{ matrix.env.name }}:coverage xml # create report for upload + - name: Upload coverage + uses: codecov/codecov-action@v5 + + # 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 b6e47617d..bd24e4e00 100644 --- a/.gitignore +++ b/.gitignore @@ -1,129 +1,21 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class +# Temp files +.DS_Store +*~ +buck-out/ -# C extensions -*.so +# Compiled files +.venv/ +__pycache__/ +.*cache/ # Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site +/dist/ -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json +# Tests and coverage +/data/ +/node_modules/ +/.coverage* -# Pyre type checker -.pyre/ +# docs +/docs/generated/ +/docs/_build/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..b9de3fe04 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,38 @@ +fail_fast: false +default_language_version: + python: python3 +default_stages: + - pre-commit + - pre-push +minimum_pre_commit_version: 2.16.0 +repos: + - repo: https://github.com/biomejs/pre-commit + 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.6.0 + hooks: + - id: pyproject-fmt + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.13.2 + hooks: + - id: ruff-check + types_or: [python, pyi, jupyter] + args: [--fix, --exit-non-zero-on-fix] + - id: ruff-format + types_or: [python, pyi, jupyter] + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: detect-private-key + - id: check-ast + - id: end-of-file-fixer + - id: mixed-line-ending + args: [--fix=lf] + - id: trailing-whitespace + - id: check-case-conflict + # Check that there are no merge conflicts (could be generated by template sync) + - id: check-merge-conflict + args: [--assume-in-merge] diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..c3f3f96fb --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,15 @@ +# https://docs.readthedocs.io/en/stable/config-file/v2.html +version: 2 +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 diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..caaeb4f73 --- /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 000000000..36d187461 --- /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 000000000..e034b91f7 --- /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 000000000..9f8f2208c --- /dev/null +++ b/biome.jsonc @@ -0,0 +1,17 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.2.0/schema.json", + "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true }, + "formatter": { "useEditorconfig": true }, + "overrides": [ + { + "includes": ["./.vscode/*.json", "**/*.jsonc"], + "json": { + "formatter": { "trailingCommas": "all" }, + "parser": { + "allowComments": true, + "allowTrailingCommas": true, + }, + }, + }, + ], +} diff --git a/docs/_static/.gitkeep b/docs/_static/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css new file mode 100644 index 000000000..b8c8d47fa --- /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/.gitkeep b/docs/_templates/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/_templates/autosummary/class.rst b/docs/_templates/autosummary/class.rst new file mode 100644 index 000000000..7b4a0cf87 --- /dev/null +++ b/docs/_templates/autosummary/class.rst @@ -0,0 +1,61 @@ +{{ 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/changelog.md b/docs/changelog.md new file mode 100644 index 000000000..d9e79ba64 --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1,3 @@ +```{include} ../CHANGELOG.md + +``` diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 000000000..3625ea241 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,131 @@ +# 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 + +# -- Path setup -------------------------------------------------------------- +import sys +from datetime import datetime +from importlib.metadata import metadata +from pathlib import Path + +HERE = Path(__file__).parent +sys.path.insert(0, str(HERE / "extensions")) + + +# -- 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("squidpy") +project_name = info["Name"] +author = info["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"] + +# The full version, including alpha/beta/rc tags +release = info["Version"] + +bibtex_bibfiles = ["references.bib"] +templates_path = ["_templates"] +nitpicky = True # Warn about broken links +needs_sphinx = "4.0" + +html_context = { + "display_github": True, # Integrate GitHub + "github_user": "giovp", + "github_repo": project_name, + "github_version": "main", + "conf_py_path": "/docs/", +} + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. +# They can be extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [ + "myst_nb", + "sphinx_copybutton", + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.autosummary", + "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")], +] + +autosummary_generate = True +autodoc_member_order = "groupwise" +default_role = "literal" +napoleon_google_docstring = False +napoleon_numpy_docstring = True +napoleon_include_init_with_doc = False +napoleon_use_rtype = True # having a separate entry generally helps readability +napoleon_use_param = True +myst_heading_anchors = 6 # create anchors for h1-h6 +myst_enable_extensions = [ + "amsmath", + "colon_fence", + "deflist", + "dollarmath", + "html_image", + "html_admonition", +] +myst_url_schemes = ("http", "https", "mailto") +nb_output_stderr = "remove" +nb_execution_mode = "off" +nb_merge_streams = True +typehints_defaults = "braces" + +source_suffix = { + ".rst": "restructuredtext", + ".ipynb": "myst-nb", + ".myst": "myst-nb", +} + +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), +} + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "**.ipynb_checkpoints"] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +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" + +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"), +] diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 000000000..699d94291 --- /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.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 + +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/docs/extensions/typed_returns.py b/docs/extensions/typed_returns.py new file mode 100644 index 000000000..0fbffefe3 --- /dev/null +++ b/docs/extensions/typed_returns.py @@ -0,0 +1,32 @@ +# 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 new file mode 100644 index 000000000..9e8740c51 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,154 @@ +[build-system] +build-backend = "hatchling.build" +requires = [ "hatchling" ] + +[project] +name = "squidpy" +version = "0.0.1" +description = "Spatial Single Cell Analysis in Python" +readme = "README.md" +license = { file = "LICENSE" } +maintainers = [ + { name = "Giovanni Palla", email = "giovanni.palla@helmholtz-muenchen.de" }, +] +authors = [ + { name = "Giovanni Palla" }, +] +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", +] +dependencies = [ + "anndata", + # for debug logging (referenced from the issue template) + "session-info2", +] +optional-dependencies.dev = [ + "pre-commit", + "twine>=4.0.2", +] +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>=8.1", + "sphinx-autodoc-typehints", + "sphinx-book-theme>=1", + "sphinx-copybutton", + "sphinx-tabs", + "sphinxcontrib-bibtex>=1", + "sphinxext-opengraph", +] +optional-dependencies.test = [ + "coverage>=7.10", + "pytest", + "pytest-cov", # For VS Code’s coverage functionality +] +# https://docs.pypi.org/project_metadata/#project-urls +urls.Documentation = "https://squidpy.readthedocs.io/" +urls.Homepage = "https://github.com/giovp/squidpy" +urls.Source = "https://github.com/giovp/squidpy" + +[tool.hatch.envs.default] +installer = "uv" +features = [ "dev" ] + +[tool.hatch.envs.docs] +features = [ "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}" + +# 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 = [ "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 +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" ] +xfail_strict = true +addopts = [ + "--import-mode=importlib", # allow using test files with same name +] + +[tool.coverage.run] +source = [ "squidpy" ] +patch = [ "subprocess" ] +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", +]