diff --git a/.github/workflows/misc.yml b/.github/workflows/misc.yml deleted file mode 100644 index 90645b40eb..0000000000 --- a/.github/workflows/misc.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Miscellaneous checks - -# This file runs doctests on the documentation and style checks - -on: - push: - branches: - - master - - maint/* - pull_request: - branches: - - master - - maint/* - -defaults: - run: - shell: bash - -jobs: - misc: - runs-on: 'ubuntu-latest' - continue-on-error: true - strategy: - matrix: - python-version: ["3.10"] - install: ['pip'] - check: ['style', 'doctest', 'typing'] - pip-flags: [''] - depends: ['REQUIREMENTS'] - env: - DEPENDS: ${{ matrix.depends }} - OPTIONAL_DEPENDS: ${{ matrix.optional-depends }} - INSTALL_TYPE: ${{ matrix.install }} - CHECK_TYPE: ${{ matrix.check }} - EXTRA_PIP_FLAGS: ${{ matrix.pip-flags }} - - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - fetch-depth: 0 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - architecture: ${{ matrix.architecture }} - - name: Display Python version - run: python -c "import sys; print(sys.version)" - - name: Create virtual environment - run: tools/ci/create_venv.sh - - name: Build archive - run: | - source tools/ci/build_archive.sh - echo "ARCHIVE=$ARCHIVE" >> $GITHUB_ENV - - name: Install dependencies - run: tools/ci/install_dependencies.sh - - name: Install NiBabel - run: tools/ci/install.sh - - name: Run tests - run: tools/ci/check.sh - if: ${{ matrix.check != 'skiptests' }} - - name: Upload pytest test results - uses: actions/upload-artifact@v3 - with: - name: pytest-results-${{ matrix.os }}-${{ matrix.python-version }} - path: for_testing/test-results.xml - if: ${{ always() && matrix.check == 'test' }} diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml deleted file mode 100644 index 4431c7135f..0000000000 --- a/.github/workflows/pre-release.yml +++ /dev/null @@ -1,102 +0,0 @@ -name: Pre-release checks - -# This file tests against pre-release wheels for dependencies - -on: - push: - branches: - - master - - maint/* - pull_request: - branches: - - master - - maint/* - schedule: - - cron: '0 0 * * *' - -defaults: - run: - shell: bash - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - pre-release: - # Check pre-releases of dependencies on stable Python - runs-on: ${{ matrix.os }} - continue-on-error: true - strategy: - matrix: - os: ['ubuntu-latest', 'windows-latest', 'macos-latest'] - python-version: ["3.9", "3.10", "3.11", "3.12"] - architecture: ['x64', 'x86'] - install: ['pip'] - check: ['test'] - pip-flags: ['PRE_PIP_FLAGS'] - depends: ['REQUIREMENTS'] - optional-depends: ['DEFAULT_OPT_DEPENDS'] - include: - # Pydicom master - - os: ubuntu-latest - python-version: "3.11" - install: pip - check: test - pip-flags: '' - depends: REQUIREMENTS - optional-depends: PYDICOM_MASTER - exclude: - - os: ubuntu-latest - architecture: x86 - - os: macos-latest - architecture: x86 - - python-version: '3.12' - architecture: x86 - - env: - DEPENDS: ${{ matrix.depends }} - OPTIONAL_DEPENDS: ${{ matrix.optional-depends }} - INSTALL_TYPE: ${{ matrix.install }} - CHECK_TYPE: ${{ matrix.check }} - EXTRA_PIP_FLAGS: ${{ matrix.pip-flags }} - - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - fetch-depth: 0 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - architecture: ${{ matrix.architecture }} - allow-prereleases: true - - name: Display Python version - run: python -c "import sys; print(sys.version)" - - name: Create virtual environment - run: tools/ci/create_venv.sh - - name: Build archive - run: | - source tools/ci/build_archive.sh - echo "ARCHIVE=$ARCHIVE" >> $GITHUB_ENV - - name: Install dependencies - run: tools/ci/install_dependencies.sh - - name: Install NiBabel - run: tools/ci/install.sh - - name: Run tests - run: tools/ci/check.sh - if: ${{ matrix.check != 'skiptests' }} - - uses: codecov/codecov-action@v3 - if: ${{ always() }} - with: - files: cov.xml - - name: Upload pytest test results - uses: actions/upload-artifact@v3 - with: - name: pytest-results-${{ matrix.os }}-${{ matrix.python-version }} - path: for_testing/test-results.xml - if: ${{ always() && matrix.check == 'test' }} diff --git a/.github/workflows/stable.yml b/.github/workflows/test.yml similarity index 68% rename from .github/workflows/stable.yml rename to .github/workflows/test.yml index 90721bc81b..9b12727bda 100644 --- a/.github/workflows/stable.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -name: Stable tests +name: Build and test # This file tests the claimed support range of NiBabel including # @@ -35,7 +35,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-python@v4 @@ -94,70 +94,45 @@ jobs: if: matrix.package == 'archive' run: pip install archive/nibabel-archive.tgz - run: python -c 'import nibabel; print(nibabel.__version__)' - - name: Install test extras + - name: Install minimum test dependencies run: pip install nibabel[test] - name: Run tests - run: pytest --doctest-modules --doctest-plus -v --pyargs nibabel + run: pytest --doctest-modules --doctest-plus -v --pyargs nibabel -n auto - stable: + test: # Check each OS, all supported Python, minimum versions and latest releases runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.dependencies == 'pre' }} strategy: + fail-fast: false matrix: os: ['ubuntu-latest', 'windows-latest', 'macos-latest'] - python-version: [3.8, 3.9, "3.10", "3.11", "3.12"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] architecture: ['x64', 'x86'] - install: ['pip'] - check: ['test'] - pip-flags: [''] - depends: ['REQUIREMENTS'] - optional-depends: ['DEFAULT_OPT_DEPENDS'] + dependencies: ['full', 'pre'] include: # Basic dependencies only - os: ubuntu-latest python-version: 3.8 - install: pip - check: test - pip-flags: '' - depends: REQUIREMENTS - optional-depends: '' + dependencies: 'none' # Absolute minimum dependencies - os: ubuntu-latest python-version: 3.8 - install: pip - check: test - pip-flags: '' - depends: MIN_REQUIREMENTS - optional-depends: '' - # Absolute minimum dependencies plus old MPL, Pydicom, Pillow - - os: ubuntu-latest - python-version: 3.8 - install: pip - check: test - pip-flags: '' - depends: MIN_REQUIREMENTS - optional-depends: MIN_OPT_DEPENDS - # Clean install imports only with package-declared dependencies - - os: ubuntu-latest - python-version: 3.8 - install: pip - check: skiptests - pip-flags: '' - depends: '' + dependencies: 'min' exclude: - os: ubuntu-latest architecture: x86 - os: macos-latest architecture: x86 + - python-version: '3.12' + architecture: x86 + env: - DEPENDS: ${{ matrix.depends }} - OPTIONAL_DEPENDS: ${{ matrix.optional-depends }} - INSTALL_TYPE: ${{ matrix.install }} - CHECK_TYPE: ${{ matrix.check }} - EXTRA_PIP_FLAGS: ${{ matrix.pip-flags }} + DEPENDS: ${{ matrix.dependencies }} + ARCH: ${{ !contains(fromJSON('["none", "min"]'), matrix.dependencies) && matrix.architecture }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive fetch-depth: 0 @@ -166,36 +141,54 @@ jobs: with: python-version: ${{ matrix.python-version }} architecture: ${{ matrix.architecture }} + allow-prereleases: true - name: Display Python version run: python -c "import sys; print(sys.version)" - - name: Create virtual environment - run: tools/ci/create_venv.sh - - name: Build archive + - name: Install tox run: | - source tools/ci/build_archive.sh - echo "ARCHIVE=$ARCHIVE" >> $GITHUB_ENV - - name: Install dependencies - run: tools/ci/install_dependencies.sh - - name: Install NiBabel - run: tools/ci/install.sh - - name: Run tests - if: ${{ matrix.check != 'skiptests' }} - run: tools/ci/check.sh + python -m pip install --upgrade pip + python -m pip install tox tox-gh-actions + - name: Show tox config + run: tox c + - name: Run tox + run: tox -v --exit-and-dump-after 1200 - uses: codecov/codecov-action@v3 if: ${{ always() }} with: files: cov.xml - name: Upload pytest test results - if: ${{ always() && matrix.check == 'test' }} uses: actions/upload-artifact@v3 with: name: pytest-results-${{ matrix.os }}-${{ matrix.python-version }} - path: for_testing/test-results.xml + path: test-results.xml + if: ${{ always() }} + + checks: + runs-on: 'ubuntu-latest' + continue-on-error: true + strategy: + matrix: + check: ['style', 'doctest', 'typecheck'] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: 3 + - name: Display Python version + run: python -c "import sys; print(sys.version)" + - name: Show tox config + run: pipx run tox c + - name: Show tox config (this call) + run: pipx run tox c -e ${{ matrix.check }} + - name: Run check + run: pipx run tox -e ${{ matrix.check }} publish: runs-on: ubuntu-latest environment: "Package deployment" - needs: [stable, test-package] + needs: [test, test-package] if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') steps: - uses: actions/download-artifact@v3 diff --git a/.gitignore b/.gitignore index 4e9cf81029..e413527d13 100644 --- a/.gitignore +++ b/.gitignore @@ -48,7 +48,9 @@ dist/ *.egg-info/ .shelf .tox/ -.coverage +.coverage* +cov.xml +test-results.xml .ropeproject/ htmlcov/ .*_cache/ diff --git a/doc/source/devel/devguide.rst b/doc/source/devel/devguide.rst index 2747564dbf..bce5b64aaa 100644 --- a/doc/source/devel/devguide.rst +++ b/doc/source/devel/devguide.rst @@ -95,6 +95,50 @@ advise that you enable merge summaries within git: See :ref:`configure-git` for more detail. +Pre-commit hooks +---------------- + +NiBabel uses pre-commit_ to help committers validate their changes +before committing. To enable these, you can use pipx_:: + + pipx run pre-commit install + +Or install and run:: + + python -m pip install pre-commit + pre-commit install + + +Testing +======= + +NiBabel uses tox_ to organize our testing and development workflows. +tox runs tests in isolated environments that we specify, +ensuring that we are able to test across many different environments, +and those environments do not depend on our local configurations. + +If you have the pipx_ tool installed, then you may simply:: + + pipx run tox + +Alternatively, you can install tox and run it:: + + python -m pip install tox + tox + +This will run the tests in several configurations, with multiple sets of +optional dependencies. +If you have multiple versions of Python installed in your path, it will +repeat the process for each version of Python iin our supported range. +It may be useful to pick a particular version for rapid development:: + + tox -e py311-full-x64 + +This will run the environment using the Python 3.11 interpreter, with the +full set of optional dependencies that are available for 64-bit +interpreters. If you are using 32-bit Python, replace ``-x64`` with ``-x86``. + + Changelog ========= @@ -123,3 +167,7 @@ Community guidelines Please see `our community guidelines `_. Other projects call these guidelines the "code of conduct". + +.. _tox: https://tox.wiki +.. _pipx: https://pypa.github.io/pipx/ +.. _precommit: https://pre-commit.com/ diff --git a/nibabel/__init__.py b/nibabel/__init__.py index 09be1d2792..db427435ae 100644 --- a/nibabel/__init__.py +++ b/nibabel/__init__.py @@ -39,8 +39,9 @@ # module imports from . import analyze as ana -from . import ecat, imagestats, mriutils, orientations +from . import ecat, imagestats, mriutils from . import nifti1 as ni1 +from . import orientations from . import spm2analyze as spm2 from . import spm99analyze as spm99 from . import streamlines, viewers diff --git a/nibabel/info.py b/nibabel/info.py index 063978444c..33d1b0aa0d 100644 --- a/nibabel/info.py +++ b/nibabel/info.py @@ -62,6 +62,27 @@ .. _release archive: https://github.com/nipy/NiBabel/releases .. _development changelog: https://nipy.org/nibabel/changelog.html +Testing +======= + +During development, we recommend using tox_ to run nibabel tests:: + + git clone https://github.com/nipy/nibabel.git + cd nibabel + tox + +To test an installed version of nibabel, install the test dependencies +and run pytest_:: + + pip install nibabel[test] + pytest --pyargs nibabel + +For more inforation, consult the `developer guidelines`_. + +.. _tox: https://tox.wiki +.. _pytest: https://docs.pytest.org +.. _developer guidelines: https://nipy.org/nibabel/devel/devguide.html + Mailing List ============ diff --git a/nibabel/minc2.py b/nibabel/minc2.py index d02eb6cefc..3096ef9499 100644 --- a/nibabel/minc2.py +++ b/nibabel/minc2.py @@ -26,6 +26,7 @@ mincstats my_funny.mnc """ import warnings + import numpy as np from .minc1 import Minc1File, Minc1Image, MincError, MincHeader diff --git a/nibabel/tests/test_ecat.py b/nibabel/tests/test_ecat.py index c8de98c2d1..6a076cbc38 100644 --- a/nibabel/tests/test_ecat.py +++ b/nibabel/tests/test_ecat.py @@ -8,8 +8,8 @@ ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## import os -import pathlib import warnings +from pathlib import Path from unittest import TestCase import numpy as np @@ -184,8 +184,8 @@ class TestEcatImage(TestCase): img = image_class.load(example_file) def test_file(self): - assert pathlib.Path(self.img.file_map['header'].filename) == pathlib.Path(self.example_file) - assert pathlib.Path(self.img.file_map['image'].filename) == pathlib.Path(self.example_file) + assert Path(self.img.file_map['header'].filename) == Path(self.example_file) + assert Path(self.img.file_map['image'].filename) == Path(self.example_file) def test_save(self): tmp_file = 'tinypet_tmp.v' diff --git a/nibabel/tests/test_filename_parser.py b/nibabel/tests/test_filename_parser.py index 736994e0da..5d352f72dd 100644 --- a/nibabel/tests/test_filename_parser.py +++ b/nibabel/tests/test_filename_parser.py @@ -11,7 +11,13 @@ import pytest -from ..filename_parser import TypesFilenamesError, parse_filename, splitext_addext, types_filenames, _stringify_path +from ..filename_parser import ( + TypesFilenamesError, + _stringify_path, + parse_filename, + splitext_addext, + types_filenames, +) def test_filenames(): diff --git a/pyproject.toml b/pyproject.toml index d399ca7d68..beb81fb0d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,39 +49,34 @@ nib-roi = "nibabel.cmdline.roi:main" parrec2nii = "nibabel.cmdline.parrec2nii:main" [project.optional-dependencies] -all = ["nibabel[dicomfs,dev,doc,minc2,spm,style,test,zstd]"] -dev = ["gitpython", "twine", "nibabel[style]"] +all = ["nibabel[dicomfs,minc2,spm,zstd]"] +# Features dicom = ["pydicom >=1.0.0"] dicomfs = ["nibabel[dicom]", "pillow"] +minc2 = ["h5py"] +spm = ["scipy"] +zstd = ["pyzstd >= 0.14.3"] +# For doc and test, make easy to use outside of tox +# tox should use these with extras instead of duplicating doc = [ - "matplotlib >= 1.5.3", + "sphinx", + "matplotlib>=1.5.3", "numpydoc", - "sphinx ~= 5.3", "texext", - "tomli; python_version < \"3.11\"", + "tomli; python_version < '3.11'", ] -doctest = ["nibabel[doc,test]"] -minc2 = ["h5py"] -spm = ["scipy"] -style = ["flake8", "blue", "isort"] test = [ - "coverage", - "pytest !=5.3.4", - "pytest-cov", + "pytest", "pytest-doctestplus", + "pytest-cov", "pytest-httpserver", "pytest-xdist", ] -typing = [ - "mypy", - "importlib_resources", - "pydicom", - "pytest", - "pyzstd", - "types-setuptools", - "types-Pillow", -] -zstd = ["pyzstd >= 0.14.3"] +# Remaining: Simpler to centralize in tox +dev = ["tox"] +doctest = ["tox"] +style = ["tox"] +typing = ["tox"] [tool.hatch.build.targets.sdist] exclude = [ @@ -99,10 +94,19 @@ exclude = [ [tool.hatch.version] source = "vcs" +tag-pattern = '(?P\d+(?:\.\d+){0,2}[^+]*)(?:\+.*)?$' raw-options = { version_scheme = "release-branch-semver" } [tool.hatch.build.hooks.vcs] version-file = "nibabel/_version.py" +# Old default setuptools_scm template; hatch-vcs currently causes +# a noisy warning if template is missing. +template = ''' +# file generated by setuptools_scm +# don't change, don't track in version control +__version__ = version = {version!r} +__version_tuple__ = version_tuple = {version_tuple!r} +''' [tool.blue] line_length = 99 diff --git a/tools/ci/activate.sh b/tools/ci/activate.sh deleted file mode 100644 index 567e13a67b..0000000000 --- a/tools/ci/activate.sh +++ /dev/null @@ -1,9 +0,0 @@ -if [ -e virtenv/bin/activate ]; then - source virtenv/bin/activate -elif [ -e virtenv/Scripts/activate ]; then - source virtenv/Scripts/activate -else - echo Cannot activate virtual environment - ls -R virtenv - false -fi diff --git a/tools/ci/build_archive.sh b/tools/ci/build_archive.sh deleted file mode 100755 index 3c25012e1b..0000000000 --- a/tools/ci/build_archive.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash - -echo "Building archive" - -source tools/ci/activate.sh - -set -eu - -# Required dependencies -echo "INSTALL_TYPE = $INSTALL_TYPE" - -set -x - -if [ "$INSTALL_TYPE" = "sdist" -o "$INSTALL_TYPE" = "wheel" ]; then - python -m build -elif [ "$INSTALL_TYPE" = "archive" ]; then - ARCHIVE="/tmp/package.tar.gz" - git archive -o $ARCHIVE HEAD -fi - -if [ "$INSTALL_TYPE" = "sdist" ]; then - ARCHIVE=$( ls $PWD/dist/*.tar.gz ) -elif [ "$INSTALL_TYPE" = "wheel" ]; then - ARCHIVE=$( ls $PWD/dist/*.whl ) -elif [ "$INSTALL_TYPE" = "pip" ]; then - ARCHIVE="$PWD" -fi - -export ARCHIVE - -set +eux diff --git a/tools/ci/check.sh b/tools/ci/check.sh deleted file mode 100755 index cd90650722..0000000000 --- a/tools/ci/check.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash - -echo Running tests - -source tools/ci/activate.sh - -set -eu - -# Required variables -echo CHECK_TYPE = $CHECK_TYPE - -set -x - -export NIBABEL_DATA_DIR="$PWD/nibabel-data" - -if [ "${CHECK_TYPE}" == "style" ]; then - # Run styles only on core nibabel code. - flake8 nibabel -elif [ "${CHECK_TYPE}" == "doctest" ]; then - make -C doc html && make -C doc doctest -elif [ "${CHECK_TYPE}" == "test" ]; then - # Change into an innocuous directory and find tests from installation - mkdir for_testing - cd for_testing - cp ../.coveragerc . - pytest --doctest-modules --doctest-plus --cov nibabel --cov-report xml:../cov.xml \ - --junitxml=test-results.xml -v --pyargs nibabel -n auto -elif [ "${CHECK_TYPE}" == "typing" ]; then - mypy nibabel -else - false -fi - -set +eux - -echo Done running tests diff --git a/tools/ci/create_venv.sh b/tools/ci/create_venv.sh deleted file mode 100755 index 7a28767396..0000000000 --- a/tools/ci/create_venv.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -echo Creating isolated virtual environment - -source tools/ci/env.sh - -set -eu - -# Required variables -echo SETUP_REQUIRES = $SETUP_REQUIRES - -set -x - -python -m pip install --upgrade pip virtualenv -virtualenv --python=python virtenv -source tools/ci/activate.sh -python --version -python -m pip install -U $SETUP_REQUIRES -which python -which pip - -set +eux - -echo Done creating isolated virtual environment diff --git a/tools/ci/env.sh b/tools/ci/env.sh deleted file mode 100644 index dd29443126..0000000000 --- a/tools/ci/env.sh +++ /dev/null @@ -1,17 +0,0 @@ -SETUP_REQUIRES="pip build" - -# Minimum requirements -REQUIREMENTS="-r requirements.txt" -# Minimum versions of minimum requirements -MIN_REQUIREMENTS="-r min-requirements.txt" - -DEFAULT_OPT_DEPENDS="scipy matplotlib pillow pydicom h5py indexed_gzip pyzstd" -# pydicom has skipped some important pre-releases, so enable a check against master -PYDICOM_MASTER="git+https://github.com/pydicom/pydicom.git@master" -# Minimum versions of optional requirements -MIN_OPT_DEPENDS="matplotlib==1.5.3 pydicom==1.0.1 pillow==2.6" - -# Numpy and scipy upload nightly/weekly/intermittent wheels -NIGHTLY_WHEELS="https://pypi.anaconda.org/scipy-wheels-nightly/simple" -STAGING_WHEELS="https://pypi.anaconda.org/multibuild-wheels-staging/simple" -PRE_PIP_FLAGS="--pre --extra-index-url $NIGHTLY_WHEELS --extra-index-url $STAGING_WHEELS" diff --git a/tools/ci/install.sh b/tools/ci/install.sh deleted file mode 100755 index c0c3b23e67..0000000000 --- a/tools/ci/install.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash - -echo Installing nibabel - -source tools/ci/activate.sh -source tools/ci/env.sh - -set -eu - -# Required variables -echo INSTALL_TYPE = $INSTALL_TYPE -echo CHECK_TYPE = $CHECK_TYPE -echo EXTRA_PIP_FLAGS = $EXTRA_PIP_FLAGS - -set -x - -if [ -n "$EXTRA_PIP_FLAGS" ]; then - EXTRA_PIP_FLAGS=${!EXTRA_PIP_FLAGS} -fi - -( - # Ensure installation does not depend on being in source tree - mkdir ../unversioned_install_dir - cd ../unversioned_install_dir - pip install $EXTRA_PIP_FLAGS $ARCHIVE - - # Basic import check - python -c 'import nibabel; print(nibabel.__version__)' -) - -if [ "$CHECK_TYPE" == "skiptests" ]; then - exit 0 -fi - -pip install $EXTRA_PIP_FLAGS "nibabel[$CHECK_TYPE]" - -set +eux - -echo Done installing nibabel diff --git a/tools/ci/install_dependencies.sh b/tools/ci/install_dependencies.sh deleted file mode 100755 index 2ea4a524e8..0000000000 --- a/tools/ci/install_dependencies.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -echo Installing dependencies - -source tools/ci/activate.sh -source tools/ci/env.sh - -set -eu - -# Required variables -echo EXTRA_PIP_FLAGS = $EXTRA_PIP_FLAGS -echo DEPENDS = $DEPENDS -echo OPTIONAL_DEPENDS = $OPTIONAL_DEPENDS - -set -x - -if [ -n "$EXTRA_PIP_FLAGS" ]; then - EXTRA_PIP_FLAGS=${!EXTRA_PIP_FLAGS} -fi - -if [ -n "$DEPENDS" ]; then - pip install ${EXTRA_PIP_FLAGS} --only-binary :all: ${!DEPENDS} - if [ -n "$OPTIONAL_DEPENDS" ]; then - for DEP in ${!OPTIONAL_DEPENDS}; do - pip install ${EXTRA_PIP_FLAGS} --only-binary :all: $DEP || true - done - fi -fi - -set +eux - -echo Done installing dependencies diff --git a/tox.ini b/tox.ini index a0002e12b6..dd8e1650b9 100644 --- a/tox.ini +++ b/tox.ini @@ -1,20 +1,197 @@ +# This file encodes a lot of our intended support range, as well as some +# details about dependency availability. +# +# The majority of the information is contained in tox.envlist and testenv.deps. [tox] -# From-scratch tox-default-name virtualenvs -envlist = py25,py26,py27,py32 +requires = + tox>=4 +envlist = + # No preinstallations + py3{8,9,10,11,12}-none + # Minimum Python + py38-{min,full} + # x86 support range + py3{9,10,11}-{full,pre}-{x86,x64} + py3{9,10,11}-pre-{x86,x64} + # x64-only range + py312-{full,pre}-x64 + install + doctest + style + typecheck +skip_missing_interpreters = true + +# Configuration that allows us to split tests across GitHub runners effectively +[gh-actions] +python = + 3.8: py38 + 3.9: py39 + 3.10: py310 + 3.11: py311 + 3.12: py312 + +[gh-actions:env] +DEPENDS = + none: none, install + pre: pre + full: full, install + min: min + +ARCH = + x64: x64 + x86: x86 + [testenv] +description = Pytest with coverage +labels = test +install_command = + python -I -m pip install -v \ + x64: --only-binary numpy,scipy,h5py,pillow \ + x86: --only-binary numpy,scipy,h5py,pillow,matplotlib \ + pre: --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple \ + {opts} {packages} +pip_pre = + pre: true +pass_env = + # getpass.getuser() sources for Windows: + LOGNAME + USER + LNAME + USERNAME + # Environment variables we check for + NIPY_EXTRA_TESTS +extras = test deps = - nose - numpy -commands=nosetests --with-doctest -# MBs virtualenvs; numpy, nose already installed. Run these with: -# tox -e python25,python26,python27,python32,np-1.2.1 -[testenv:python25] + # NEP29/SPEC0 + 1yr: Test on minor release series within the last 3 years + # We're extending this to all optional dependencies + # This only affects the range that we test on; numpy is the only non-optional + # dependency, and will be the only one to affect pip environment resolution. + min: numpy ==1.20 + min: packaging ==17 + min: importlib_resources ==1.3; python_version < '3.9' + min: scipy ==1.6 + min: matplotlib ==3.4 + min: h5py ==2.10 + min: pillow ==8.1 + min: indexed_gzip ==1.4 + min: pyzstd ==0.14.3 + # Numpy 2.0 is a major breaking release; we cannot put much effort into + # supporting until it's at least RC stable + pre: numpy <2.0.dev0 + # Scipy stopped producing win32 wheels at py310 + py3{8,9}-full-x86,x64: scipy >=1.6 + # Matplotlib depends on scipy, so cannot be built for py310 on x86 + py3{8,9}-full-x86,x64: matplotlib >=3.4 + # h5py stopped producing win32 wheels at py39 + py38-full-x86,x64: h5py >=2.10 + full,pre: pillow >=8.1 + # indexed_gzip missing py312 wheels + py3{8,9,10,11}-{full,pre}: indexed_gzip >=1.4 + full,pre: pyzstd >=0.14.3 + min: pydicom ==2.1 + full,pre: pydicom >=2.1 + # pydicom master seems to be breaking things + # pre: pydicom @ git+https://github.com/pydicom/pydicom.git@main + +commands = + pytest --doctest-modules --doctest-plus \ + --cov nibabel --cov-report xml:cov.xml \ + --junitxml test-results.xml \ + --pyargs nibabel {posargs:-n auto} + +[testenv:install] +description = Install and verify import succeeds +labels = test deps = -[testenv:python26] +extras = +install_command = python -I -m pip install {opts} {packages} +commands = + python -c "import nibabel; print(nibabel.__version__)" + +[testenv:docs] +description = Build documentation site +labels = docs +allowlist_externals = make +extras = doc +commands = + make -C doc html + +[testenv:doctest] +description = Run doctests in documentation site +labels = docs +allowlist_externals = make +extras = + doc + test +commands = + make -C doc doctest + +[testenv:style] +description = Check our style guide +labels = check deps = -[testenv:python27] + flake8 + blue + isort[colors] +skip_install = true +commands = + blue --diff --color nibabel + isort --diff --color nibabel + flake8 nibabel + +[testenv:style-fix] +description = Auto-apply style guide to the extent possible +labels = pre-release deps = -[testenv:python32] + blue + isort[colors] +skip_install = true +commands = + blue nibabel + isort nibabel + +[testenv:typecheck] +description = Check type consistency +labels = check deps = -[testenv:np-1.2.1] + mypy + pytest + types-setuptools + types-Pillow + pydicom + numpy + pyzstd + importlib_resources +skip_install = true +commands = + mypy nibabel + +[testenv:build{,-strict}] +labels = + check + pre-release deps = + build + twine +skip_install = true +set_env = + build-strict: PYTHONWARNINGS=error +commands = + python -m build + python -m twine check dist/* + +[testenv:publish] +depends = build +labels = release +deps = + twine +skip_install = true +commands = + python -m twine upload dist/* + +[testenv:zenodo] +deps = gitpython +labels = pre-release +skip_install = true +commands = + python tools/prep_zenodo.py