diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 9bd6c545..da841af0 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -8,23 +8,26 @@ jobs: package_and_release: runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/v') + defaults: + run: + shell: bash -l {0} steps: - - uses: actions/checkout@v3 - - name: Set up Python 3.12 - uses: actions/setup-python@v5 + - uses: actions/checkout@v4 + + - name: Setup Pixi + uses: prefix-dev/setup-pixi@v0.8.1 with: - python-version: "3.12" - cache: pip - - name: Install build dependencies - run: python -m pip install --upgrade pip wheel twine build + pixi-version: v0.34.0 + cache: true + - name: Build package - run: python -m build + run: | + pixi run -e dev-py313 python -m build + - name: Check package - run: twine check --strict dist/*.whl - - name: Install hatch - run: pip install hatch - - name: Build project for distribution - run: hatch build + run: | + pixi run -e dev-py313 twine check --strict dist/*.whl + - name: Publish a Python distribution to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e1517cde..cea67dfa 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,13 +13,13 @@ jobs: runs-on: ${{ matrix.os }} defaults: run: - shell: bash -e {0} # -e to fail on error + shell: bash -l {0} strategy: fail-fast: false matrix: - python: ["3.10", "3.12"] - os: [ubuntu-latest] + python: ["3.11", "3.13"] + os: [ubuntu-latest, macos-latest] env: OS: ${{ matrix.os }} @@ -27,47 +27,32 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python }} - - name: Get pip cache dir - id: pip-cache-dir - run: | - echo "::set-output name=dir::$(pip cache dir)" - - name: Restore pip cache - uses: actions/cache@v3 + - name: Setup Pixi + uses: prefix-dev/setup-pixi@v0.8.1 with: - path: ${{ steps.pip-cache-dir.outputs.dir }} - key: pip-${{ runner.os }}-${{ env.pythonLocation }}-${{ hashFiles('**/pyproject.toml') }} - restore-keys: | - pip-${{ runner.os }}-${{ env.pythonLocation }}- - - name: Install test dependencies - run: | - python -m pip install --upgrade pip wheel - pip install pytest-cov - - name: Install dependencies - run: | - pip install numpy - pip install --pre -e ".[dev,test,pre]" - - name: Test + pixi-version: v0.34.0 + cache: true + + - name: Install dependencies and run tests env: MPLBACKEND: agg PLATFORM: ${{ matrix.os }} DISPLAY: :42 run: | - pytest -v --cov --color=yes --cov-report=xml + pixi run -e dev-py${{ matrix.python }} test - name: Archive figures generated during testing if: always() uses: actions/upload-artifact@v4 with: name: visual_test_results_${{ matrix.os }}-python${{ matrix.python }} - path: /home/runner/work/spatialdata-plot/spatialdata-plot/tests/figures/* + path: tests/figures/* + - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 with: name: coverage verbose: true + file: ./coverage.xml env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/docs/conf.py b/docs/conf.py index d1573a31..21f20fe2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -5,6 +5,8 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- +from __future__ import annotations + import sys from datetime import datetime from importlib.metadata import metadata diff --git a/pyproject.toml b/pyproject.toml index d491c024..5e67904d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,83 +1,147 @@ [build-system] build-backend = "hatchling.build" -requires = ["hatchling", "hatch-vcs"] +requires = ["hatchling", "hatch-vcs", "hatch-fancy-pypi-readme"] [project] name = "spatialdata-plot" +dynamic = ["readme", "version"] description = "Static plotting for spatial data." +requires-python = ">=3.11" +license = "BSD-3-Clause" +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Science/Research", + "Natural Language :: English", + "Operating System :: POSIX :: Linux", + "Operating System :: MacOS :: MacOS X", + "Typing :: Typed", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Environment :: Console", + "Framework :: Jupyter", + "Topic :: Scientific/Engineering :: Bio-Informatics", + "Topic :: Scientific/Engineering :: Visualization", +] +keywords = [ + "spatial omics", + "spatial data analysis", + "visualization", + "plotting", + "bio-informatics", + "single-cell", +] authors = [ - {name = "scverse"}, + {name = "Tim Treis"}, + {name = "Wouter-Michiel Vierdag"}, + {name = "Sonja Stockhaus"}, ] maintainers = [ - {name = "scverse", email = "tim.treis@helmholtz-munich.de"}, -] -urls.Documentation = "https://spatialdata.scverse.org/projects/plot/en/latest/index.html" -urls.Source = "https://github.com/scverse/spatialdata-plot.git" -urls.Home-page = "https://github.com/scverse/spatialdata-plot.git" -requires-python = ">=3.10" -dynamic= [ - "version" # allow version to be set by git tags + {name = "Tim Treis", email = "tim.treis@helmholtz-munich.de"}, ] -license = {file = "LICENSE"} -readme = "README.md" + dependencies = [ "spatialdata>=0.3.0", - "matplotlib", - "scikit-learn", - "scanpy", - "matplotlib_scalebar", + "matplotlib>=3.3", + "matplotlib-scalebar>=0.8.0", + "scikit-learn>=0.24.0", + "scanpy>=1.9.3", + "numpy>=1.23.0", + "pandas>=2.1.0", ] [project.optional-dependencies] dev = [ - "bump2version", + "pre-commit>=3.0.0", + "hatch>=1.9.0", + "build", + "twine", + "jupyterlab", + "notebook", + "ipykernel", + "ipywidgets", + "jupytext", + "pytest", + "pytest-cov", + "ruff", +] +test = [ + "pytest>=7", + "pytest-xdist>=3", + "pytest-mock>=3.5.0", + "pytest-cov>=4", + "coverage[toml]>=7", + "pytest-timeout>=2.1.0", + "pooch", # for scipy.datasets module ] docs = [ - "sphinx>=4.5", + "ipython>=8.6.0", + "ipywidgets>=8.0.0", + "sphinx>=5.3", + "sphinx-autodoc-annotation", + "sphinx-autodoc-typehints>=1.10.3", "sphinx-book-theme>=1.0.0", "sphinx_rtd_theme", - "myst-nb", - "sphinxcontrib-bibtex>=1.0.0", - "sphinx-autodoc-typehints", + "sphinxcontrib-bibtex>=2.3.0", + "myst-nb>=0.17.1", "sphinx-design", - # For notebooks - "ipython>=8.6.0", - "sphinx-copybutton", -] -test = [ - "pytest", - "pytest-cov", - "pooch", # for scipy.datasets module + "sphinx-copybutton>=0.5.0", ] # this will be used by readthedocs and will make pip also look for pre-releases, generally installing the latest available version pre = [ "spatialdata>=0.1.0-pre0" ] +[project.urls] +Homepage = "https://github.com/scverse/spatialdata-plot" +"Bug Tracker" = "https://github.com/scverse/spatialdata-plot/issues" +Documentation = "https://spatialdata.scverse.org/projects/plot/en/latest/index.html" +"Source Code" = "https://github.com/scverse/spatialdata-plot" + [tool.coverage.run] +branch = true +parallel = true source = ["spatialdata_plot"] omit = [ + "*/__init__.py", + "*/_version.py", "**/test_*.py", ] +[tool.coverage.paths] +source = [ + "spatialdata_plot", + "*/site-packages/spatialdata_plot", +] + +[tool.coverage.report] +exclude_lines = [ + "\\#.*pragma:\\s*no.?cover", + "^if __name__ == .__main__.:$", + "^\\s*raise AssertionError\\b", + "^\\s*raise NotImplementedError\\b", + "^\\s*return NotImplemented\\b", +] +show_missing = true +precision = 2 +skip_empty = true +sort = "Miss" + [tool.pytest.ini_options] testpaths = ["tests"] xfail_strict = true addopts = [ -# "-Werror", # if 3rd party libs raise DeprecationWarnings, just use filterwarnings below "--import-mode=importlib", # allow using test files with same name "-s" # print output from tests ] -# info on how to use this https://stackoverflow.com/questions/57925071/how-do-i-avoid-getting-deprecationwarning-from-inside-dependencies-with-pytest filterwarnings = [ - # "ignore:.*U.*mode is deprecated:DeprecationWarning", + "ignore::UserWarning", + "ignore::DeprecationWarning", ] -[tool.jupytext] -formats = "ipynb,md" - [tool.hatch.build.targets.wheel] -packages = ['src/spatialdata_plot'] +packages = ["src/spatialdata_plot"] [tool.hatch.version] source = "vcs" @@ -88,6 +152,11 @@ version-file = "_version.py" [tool.hatch.metadata] allow-direct-references = true +[tool.hatch.metadata.hooks.fancy-pypi-readme] +content-type = "text/markdown" +[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] +path = "README.md" + [tool.ruff] line-length = 120 exclude = [ @@ -97,76 +166,97 @@ exclude = [ "build", "docs/_build", "dist", - "setup.py", + "setup.py" ] + +[tool.ruff.format] +docstring-code-format = true + [tool.ruff.lint] ignore = [ + # line too long -> we accept long comment lines; formatter gets rid of long code lines + "E501", # Do not assign a lambda expression, use a def -> lambda expression assignments are convenient "E731", # allow I, O, l as variable names -> I is the identity matrix, i, j, k, l is reasonable indexing notation "E741", # Missing docstring in public package "D104", + # ... imported but unused + "F401", # Missing docstring in public module "D100", # Missing docstring in __init__ "D107", - # Missing docstring in magic method - "D105", # Do not perform function calls in argument defaults. "B008", # Missing docstring in magic method "D105", + # Missing blank line before section + "D411", + # B024 Do not use `__class__` for string comparisons. + "B024", ] select = [ - "D", # flake8-docstrings "I", # isort "E", # pycodestyle "F", # pyflakes "W", # pycodestyle - "Q", # flake8-quotes - "SIM", # flake8-simplify - "TID", # flake-8-tidy-imports - "NPY", # NumPy-specific rules - "PT", # flake8-pytest-style - "B", # flake8-bugbear + # below are not autofixed "UP", # pyupgrade "C4", # flake8-comprehensions + "B", # flake8-bugbear "BLE", # flake8-blind-except - "T20", # flake8-print - "RET", # flake8-raise - "PGH", # pygrep-hooks ] -unfixable = ["B", "UP", "C4", "BLE", "T20", "RET"] +unfixable = ["B", "C4", "BLE"] + +[tool.ruff.lint.isort] +required-imports = ["from __future__ import annotations"] [tool.ruff.lint.per-file-ignores] - "tests/*" = ["D", "PT", "B024"] - "*/__init__.py" = ["F401", "D104", "D107", "E402"] - "docs/*" = ["D","B","E","A"] - # "src/spatialdata/transformations/transformations.py" = ["D101","D102", "D106", "B024", "T201", "RET504"] - "tests/conftest.py"= ["E402", "RET504"] - "src/spatialdata_plot/pl/utils.py"= ["PGH003"] - -[tool.ruff.lint.pydocstyle] -convention = "numpy" - -[tool.bumpver] -current_version = "0.0.2" -version_pattern = "MAJOR.MINOR.PATCH" -commit_message = "bump version {old_version} -> {new_version}" -tag_message = "{new_version}" -tag_scope = "default" -pre_commit_hook = "" -post_commit_hook = "" -commit = true -tag = true -push = false - -[tool.bumpver.file_patterns] -"pyproject.toml" = [ - 'current_version = "{version}"', -] -"README.md" = [ - "{version}", - "{pep440_version}", -] +"*/__init__.py" = ["D104", "F401"] +"tests/*" = ["D"] +"docs/*" = ["D", "B"] +"src/spatialdata_plot/pl/utils.py" = ["PGH003"] + +[tool.ruff.lint.flake8-tidy-imports] +# Disallow all relative imports. +ban-relative-imports = "all" + +[tool.pixi.workspace] +channels = ["conda-forge"] +platforms = ["osx-arm64", "linux-64"] + +[tool.pixi.dependencies] +python = ">=3.11" + +[tool.pixi.pypi-dependencies] +spatialdata-plot = { path = ".", editable = true } + +# for gh-actions +[tool.pixi.feature.py311.dependencies] +python = "3.11.*" + +[tool.pixi.feature.py313.dependencies] +python = "3.13.*" + +[tool.pixi.environments] +# 3.11 lane +dev-py311 = { features = ["dev", "test", "py311"], solve-group = "py311" } +docs-py311 = { features = ["docs", "py311"], solve-group = "py311" } +test-py311 = { features = ["test", "py311"], solve-group = "py311" } + +# 3.13 lane +default = { features = ["py313"], solve-group = "py313" } +dev-py313 = { features = ["dev", "test", "py313"], solve-group = "py313" } +docs-py313 = { features = ["docs", "py313"], solve-group = "py313" } +test-py313 = { features = ["test", "py313"], solve-group = "py313" } + +[tool.pixi.tasks] +lab = "jupyter lab" +kernel-install = "python -m ipykernel install --user --name pixi-dev --display-name \"spatialdata-plot (dev)\"" +test = "pytest -v --cov --color=yes --cov-report=xml --tb=short --durations=10" +lint = "ruff check ." +format = "ruff format ." +pre-commit-install = "pre-commit install" +pre-commit-run = "pre-commit run --all-files" diff --git a/src/spatialdata_plot/__init__.py b/src/spatialdata_plot/__init__.py index fd8c82c0..517ee3da 100644 --- a/src/spatialdata_plot/__init__.py +++ b/src/spatialdata_plot/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from importlib.metadata import version from . import pl diff --git a/src/spatialdata_plot/_accessor.py b/src/spatialdata_plot/_accessor.py index a35e52dc..982ee96b 100644 --- a/src/spatialdata_plot/_accessor.py +++ b/src/spatialdata_plot/_accessor.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import spatialdata as sd from xarray.core.extensions import _register_accessor diff --git a/src/spatialdata_plot/_logging.py b/src/spatialdata_plot/_logging.py index be1cf5f7..26f9c61c 100644 --- a/src/spatialdata_plot/_logging.py +++ b/src/spatialdata_plot/_logging.py @@ -1,9 +1,10 @@ # from https://github.com/scverse/spatialdata/blob/main/src/spatialdata/_logging.py +from __future__ import annotations import logging -def _setup_logger() -> "logging.Logger": +def _setup_logger() -> logging.Logger: from rich.console import Console from rich.logging import RichHandler diff --git a/src/spatialdata_plot/pl/__init__.py b/src/spatialdata_plot/pl/__init__.py index 8bf47aa9..dca0660a 100644 --- a/src/spatialdata_plot/pl/__init__.py +++ b/src/spatialdata_plot/pl/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .basic import PlotAccessor __all__ = [ diff --git a/tests/conftest.py b/tests/conftest.py index a4fdb789..26c0e825 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from abc import ABC, ABCMeta from collections.abc import Callable from functools import wraps diff --git a/tests/pl/test_get_extent.py b/tests/pl/test_get_extent.py index 338a17b7..23140c08 100644 --- a/tests/pl/test_get_extent.py +++ b/tests/pl/test_get_extent.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import math import matplotlib diff --git a/tests/pl/test_render.py b/tests/pl/test_render.py index 4ada7268..7705a423 100644 --- a/tests/pl/test_render.py +++ b/tests/pl/test_render.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import matplotlib.pyplot as plt import pytest diff --git a/tests/pl/test_render_images.py b/tests/pl/test_render_images.py index 5484ac4e..097de37f 100644 --- a/tests/pl/test_render_images.py +++ b/tests/pl/test_render_images.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import dask.array as da import matplotlib import numpy as np diff --git a/tests/pl/test_render_labels.py b/tests/pl/test_render_labels.py index 69c2da42..ed8f2e90 100644 --- a/tests/pl/test_render_labels.py +++ b/tests/pl/test_render_labels.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import dask.array as da import matplotlib import matplotlib.pyplot as plt diff --git a/tests/pl/test_render_points.py b/tests/pl/test_render_points.py index aa9d8ff7..2dbd124a 100644 --- a/tests/pl/test_render_points.py +++ b/tests/pl/test_render_points.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import math import dask.dataframe diff --git a/tests/pl/test_render_shapes.py b/tests/pl/test_render_shapes.py index 67bc70f9..13a320be 100644 --- a/tests/pl/test_render_shapes.py +++ b/tests/pl/test_render_shapes.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import math import anndata diff --git a/tests/pl/test_show.py b/tests/pl/test_show.py index cd775fb5..4c903468 100644 --- a/tests/pl/test_show.py +++ b/tests/pl/test_show.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import matplotlib import numpy as np import scanpy as sc diff --git a/tests/pl/test_upstream_plots.py b/tests/pl/test_upstream_plots.py index d052a8a5..3b5fca79 100644 --- a/tests/pl/test_upstream_plots.py +++ b/tests/pl/test_upstream_plots.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import math import matplotlib diff --git a/tests/pl/test_utils.py b/tests/pl/test_utils.py index 75f4cf29..0fb2fc7f 100644 --- a/tests/pl/test_utils.py +++ b/tests/pl/test_utils.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import matplotlib import matplotlib.pyplot as plt import numpy as np diff --git a/tests/test_image.py b/tests/test_image.py index 64d0599f..899a791f 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest