diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3ca5769fe..1effca4d7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -113,31 +113,49 @@ jobs: fail-fast: false matrix: os: ['ubuntu-latest', 'windows-latest', 'macos-13', 'macos-latest'] - python-version: ["3.9", "3.10", "3.11", "3.12"] - architecture: ['x64', 'x86', 'arm64'] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.13t"] + architecture: ['x86', 'x64', 'arm64'] dependencies: ['full', 'pre'] include: # Basic dependencies only - os: ubuntu-latest - python-version: 3.9 + python-version: "3.9" + architecture: 'x64' dependencies: 'none' # Absolute minimum dependencies - os: ubuntu-latest - python-version: 3.9 + python-version: "3.9" + architecture: 'x64' dependencies: 'min' - # NoGIL - - os: ubuntu-latest - python-version: '3.13-dev' - dependencies: 'dev' exclude: - # x86 for Windows + Python<3.12 - - os: ubuntu-latest - architecture: x86 + # Use ubuntu-latest to cover the whole range of Python. For Windows + # and OSX, checking oldest and newest should be sufficient. + - os: windows-latest + python-version: "3.10" + - os: windows-latest + python-version: "3.11" + - os: windows-latest + python-version: "3.12" + - os: macos-13 + python-version: "3.10" - os: macos-13 + python-version: "3.11" + - os: macos-13 + python-version: "3.12" + - os: macos-latest + python-version: "3.10" + - os: macos-latest + python-version: "3.11" + - os: macos-latest + python-version: "3.12" + + ## Unavailable architectures + # x86 is available for Windows + - os: ubuntu-latest architecture: x86 - os: macos-latest architecture: x86 - - python-version: '3.12' + - os: macos-13 architecture: x86 # arm64 is available for macos-14+ - os: ubuntu-latest @@ -149,6 +167,8 @@ jobs: # x64 is not available for macos-14+ - os: macos-latest architecture: x64 + + ## Reduced support # Drop pre tests for macos-13 - os: macos-13 dependencies: pre @@ -167,25 +187,37 @@ jobs: with: submodules: recursive fetch-depth: 0 + - name: Install the latest version of uv + uses: astral-sh/setup-uv@v4 - name: Set up Python ${{ matrix.python-version }} - if: "!endsWith(matrix.python-version, '-dev')" + if: "!endsWith(matrix.python-version, 't')" uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} architecture: ${{ matrix.architecture }} allow-prereleases: true - name: Set up Python ${{ matrix.python-version }} - if: endsWith(matrix.python-version, '-dev') - uses: deadsnakes/action@v3.2.0 - with: - python-version: ${{ matrix.python-version }} - nogil: true + if: endsWith(matrix.python-version, 't') + run: | + uv python install ${IMPL}-${VERSION}-${OS%-*}-${ARCH}-${LIBC} + env: + IMPL: cpython + VERSION: ${{ matrix.python-version }} + # uv expects linux|macos|windows, we can drop the -* but need to rename ubuntu + OS: ${{ matrix.os == 'ubuntu-latest' && 'linux' || matrix.os }} + # uv expects x86, x86_64, aarch64 (among others) + ARCH: ${{ matrix.architecture == 'x64' && 'x86_64' || + matrix.architecture == 'arm64' && 'aarch64' || + matrix.architecture }} + # windows and macos have no options, gnu is the only option for the archs + LIBC: ${{ matrix.os == 'ubuntu-latest' && 'gnu' || 'none' }} - name: Display Python version run: python -c "import sys; print(sys.version)" - name: Install tox run: | - python -m pip install --upgrade pip - python -m pip install tox tox-gh-actions + uv tool install tox --with=git+https://github.com/effigies/tox-gh-actions@abiflags --with=tox-uv + env: + UV_PYTHON: ${{ matrix.python-version }} - name: Show tox config run: tox c - name: Run tox diff --git a/doc-requirements.txt b/doc-requirements.txt index 42400ea57..4136b0f81 100644 --- a/doc-requirements.txt +++ b/doc-requirements.txt @@ -1,7 +1,7 @@ # Auto-generated by tools/update_requirements.py -r requirements.txt sphinx -matplotlib>=1.5.3 +matplotlib>=3.5 numpydoc texext tomli; python_version < '3.11' diff --git a/min-requirements.txt b/min-requirements.txt index 1cdd78bb7..455c6c8c6 100644 --- a/min-requirements.txt +++ b/min-requirements.txt @@ -1,4 +1,16 @@ -# Auto-generated by tools/update_requirements.py -numpy ==1.20 -packaging ==17 -importlib_resources ==1.3; python_version < '3.9' +# This file was autogenerated by uv via the following command: +# uv pip compile --resolution lowest-direct --python 3.9 -o min-requirements.txt pyproject.toml +importlib-resources==5.12.0 + # via nibabel (pyproject.toml) +numpy==1.22.0 + # via nibabel (pyproject.toml) +packaging==20.0 + # via nibabel (pyproject.toml) +pyparsing==3.2.0 + # via packaging +six==1.16.0 + # via packaging +typing-extensions==4.6.0 + # via nibabel (pyproject.toml) +zipp==3.20.2 + # via importlib-resources diff --git a/pyproject.toml b/pyproject.toml index b62c0048a..3b2dfc99b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,13 +51,15 @@ nib-roi = "nibabel.cmdline.roi:main" parrec2nii = "nibabel.cmdline.parrec2nii:main" [project.optional-dependencies] -all = ["nibabel[dicomfs,minc2,spm,zstd]"] +all = ["nibabel[dicomfs,indexed_gzip,minc2,spm,zstd]"] # Features +indexed_gzip = ["indexed_gzip >=1.6"] dicom = ["pydicom >=2.3"] -dicomfs = ["nibabel[dicom]", "pillow"] -minc2 = ["h5py"] -spm = ["scipy"] -zstd = ["pyzstd >= 0.14.3"] +dicomfs = ["nibabel[dicom]", "pillow >=8.4"] +minc2 = ["h5py >=3.5"] +spm = ["scipy >=1.8"] +viewers = ["matplotlib >=3.5"] +zstd = ["pyzstd >=0.15.2"] # For doc and test, make easy to use outside of tox # tox should use these with extras instead of duplicating doc = [ @@ -68,12 +70,12 @@ doc = [ "tomli; python_version < '3.11'", ] test = [ - "pytest", - "pytest-doctestplus", - "pytest-cov", - "pytest-httpserver", - "pytest-xdist", - "coverage>=7.2", + "pytest >=6", + "pytest-doctestplus >=1", + "pytest-cov >=2.11", + "pytest-httpserver >=1.0.7", + "pytest-xdist >=3.5", + "coverage[toml]>=7.2", ] # Remaining: Simpler to centralize in tox dev = ["tox"] @@ -200,3 +202,6 @@ enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] [tool.codespell] skip = "*/data/*,./nibabel-data" ignore-words-list = "ans,te,ue,ist,nin,nd,ccompiler,ser" + +[tool.uv.pip] +only-binary = ["numpy", "scipy", "h5py"] diff --git a/requirements.txt b/requirements.txt index f74ccc085..c65baf5cb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ # Auto-generated by tools/update_requirements.py -numpy >=1.20 -packaging >=17 -importlib_resources >=1.3; python_version < '3.9' +numpy >=1.22 +packaging >=20 +importlib_resources >=5.12; python_version < '3.12' +typing_extensions >=4.6; python_version < '3.13' diff --git a/tools/update_requirements.py b/tools/update_requirements.py index eb0343bd7..13709b22e 100755 --- a/tools/update_requirements.py +++ b/tools/update_requirements.py @@ -2,7 +2,10 @@ import sys from pathlib import Path -import tomli +try: + import tomllib +except ImportError: + import tomli as tomllib if sys.version_info < (3, 6): print('This script requires Python 3.6 to work correctly') @@ -15,7 +18,7 @@ doc_reqs = repo_root / 'doc-requirements.txt' with open(pyproject_toml, 'rb') as fobj: - config = tomli.load(fobj) + config = tomllib.load(fobj) requirements = config['project']['dependencies'] doc_requirements = config['project']['optional-dependencies']['doc'] @@ -27,9 +30,10 @@ lines[1:-1] = requirements reqs.write_text('\n'.join(lines)) -# Write minimum requirements -lines[1:-1] = [req.replace('>=', '==').replace('~=', '==') for req in requirements] -min_reqs.write_text('\n'.join(lines)) +# # Write minimum requirements +# lines[1:-1] = [req.replace('>=', '==').replace('~=', '==') for req in requirements] +# min_reqs.write_text('\n'.join(lines)) +print(f"To update {min_reqs.name}, use `uv pip compile` (see comment at top of file).") # Write documentation requirements lines[1:-1] = ['-r requirements.txt'] + doc_requirements diff --git a/tox.ini b/tox.ini index 82c13debc..cba8b17d6 100644 --- a/tox.ini +++ b/tox.ini @@ -5,18 +5,14 @@ [tox] requires = tox>=4 + tox-uv envlist = # No preinstallations - py3{9,10,11,12,13}-none - # Minimum Python - py39-{min,full} - # x86 support range - py3{9,10,11}-{full,pre}-{x86,x64} - py3{9,10,11}-pre-{x86,x64} - # x64-only range - py3{12,13}-{full,pre}-x64 - # Special environment for numpy 2.0-dev testing - py313-dev-x64 + py3{9,10,11,12,13,13t}-none + # Minimum Python with minimum deps + py39-min + # Run full and pre dependencies against all archs + py3{9,10,11,12,13,13t}-{full,pre}-{x86,x64,arm64} install doctest style @@ -31,12 +27,12 @@ python = 3.11: py311 3.12: py312 3.13: py313 + 3.13t: py313t [gh-actions:env] DEPENDS = - none: none, install + none: none pre: pre - dev: dev full: full, install min: min @@ -48,14 +44,8 @@ ARCH = [testenv] description = Pytest with coverage labels = test -install_command = - python -I -m pip install -v \ - dev: --only-binary numpy,scipy,h5py \ - !dev: --only-binary numpy,scipy,h5py,pillow,matplotlib \ - pre,dev: --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple \ - {opts} {packages} pip_pre = - pre,dev: true + pre: true pass_env = # getpass.getuser() sources for Windows: LOGNAME @@ -71,40 +61,42 @@ pass_env = CLICOLOR CLICOLOR_FORCE set_env = - py313: PYTHON_GIL=0 -extras = test + pre: PIP_EXTRA_INDEX_URL=https://pypi.anaconda.org/scientific-python-nightly-wheels/simple + pre: UV_EXTRA_INDEX_URL=https://pypi.anaconda.org/scientific-python-nightly-wheels/simple + py313t: PYTHONGIL={env:PYTHONGIL:0} +extras = + test + + # Simple, thanks Hugo and Paul + !none: dicomfs + !none: indexed_gzip + + # Minimum dependencies + min: minc2 + min: spm + min: viewers + min: zstd + + # Matplotlib has wheels for everything except win32 (x86) + {full,pre}-{x,arm}64: viewers + + # Nightly, but not released cp313t wheels for: scipy + # When released, remove the py3* line and add full to the pre line + py3{9,10,11,12,13}-full-{x,arm}64: spm + pre-{x,arm}64: spm + + # No cp313t wheels for: h5py, pyzstd + py3{9,10,11,12,13}-{full,pre}-{x,arm}64: minc2 + py3{9,10,11,12,13}-{full,pre}-{x,arm}64: zstd + + # win32 (x86) wheels still exist for scipy+py39 + py39-full-x86: spm + deps = - # General minimum dependencies: pin based on API usage - # matplotlib 3.5 requires packaging 20 - min: packaging ==20 - min: importlib_resources ==5.12; python_version < '3.12' - min: typing_extensions ==4.6; python_version < '3.13' - # 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.22 - min: h5py ==3.5 - min: indexed_gzip ==1.6 - min: matplotlib ==3.5 - min: pillow ==8.4 - min: pydicom ==2.3 - min: pyzstd ==0.15.2 - min: scipy ==1.8 - # Numpy 2.0 is a major breaking release; we cannot put much effort into - # supporting until it's at least RC stable - dev: numpy >=2.1.dev0 - # Scipy stopped producing win32 wheels at py310 - py39-full-x86,x64,arm64: scipy >=1.8 - # Matplotlib depends on scipy, so cannot be built for py310 on x86 - py39-full-x86,x64,arm64: matplotlib >=3.5 - # h5py stopped producing win32 wheels at py39 - {full,pre}-{x64,arm64}: h5py >=3.5 - full,pre,dev: pillow >=8.4 - full,pre: indexed_gzip >=1.6 - full,pre,dev: pyzstd >=0.15.2 - full,pre: pydicom >=2.3 - dev: pydicom @ git+https://github.com/pydicom/pydicom.git@main + pre: pydicom @ git+https://github.com/pydicom/pydicom.git@main + +uv_resolution = + min: lowest-direct commands = pytest --doctest-modules --doctest-plus \ @@ -118,7 +110,6 @@ description = Install and verify import succeeds labels = test deps = extras = -install_command = python -I -m pip install {opts} {packages} commands = python -c "import nibabel; print(nibabel.__version__)"