diff --git a/.coveragerc b/.coveragerc index f65ec5e..dd05c95 100644 --- a/.coveragerc +++ b/.coveragerc @@ -2,4 +2,8 @@ branch = True omit = */_version.py - */tests/* + +[paths] +source = + pydra/tasks + /**/pydra/tasks diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..00699be --- /dev/null +++ b/.editorconfig @@ -0,0 +1,24 @@ +# EditorConfig + +# This is the top-most EditorConfig file. +root = true + +# Use Unix-style newlines with a newline ending for all files. +[*] +end_of_line = lf +insert_final_newline = true + +# Use 4-space indentation for Python. +[*.py] +indent_size = 4 +indent_style = space + +# Use 4-space indentation for TOML. +[*.toml] +indent_size = 4 +indent_style = space + +# Use 2-space indentation for YAML. +[*.{yml,yaml}] +indent_size = 2 +indent_style = space diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index f26133f..47984d1 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -6,114 +6,113 @@ name: Python package # Set once env: SUBPACKAGE: nipype1 + FSLCONDA: https://fsl.fmrib.ox.ac.uk/fsldownloads/fslconda/public/ + NO_ET: 1 # etelemetry causes order-of-magnitude slowdowns on: push: - branches: [ main ] - tags: [ '*' ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] + release: + types: + - published + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash -el {0} jobs: devcheck: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.7', '3.11'] # Check oldest and newest versions - pip-flags: ['', '--editable'] + python-version: ["3.8", "3.12"] # Check oldest and newest versions + pip-flags: ["", "--editable"] pydra: - - 'pydra' - - '--editable git+https://github.com/nipype/pydra.git#egg=pydra' + - "pydra" + - "--editable git+https://github.com/nipype/pydra.git#egg=pydra" steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install build dependencies - run: | - python -m pip install --upgrade pip - - name: Install Pydra - run: | - pip install ${{ matrix.pydra }} - python -c "import pydra as m; print(f'{m.__name__} {m.__version__} @ {m.__file__}')" - - name: Install task package - run: | - pip install ${{ matrix.pip-flags }} ".[dev]" - python -c "import pydra.tasks.$SUBPACKAGE as m; print(f'{m.__name__} {m.__version__} @ {m.__file__}')" - python -c "import pydra as m; print(f'{m.__name__} {m.__version__} @ {m.__file__}')" + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install build dependencies + run: | + python -m pip install --upgrade pip + - name: Install Pydra + run: | + pip install ${{ matrix.pydra }} + python -c "import pydra as m; print(f'{m.__name__} {m.__version__} @ {m.__file__}')" + - name: Install task package + run: | + pip install ${{ matrix.pip-flags }} ".[dev]" + python -c "import pydra.tasks.$SUBPACKAGE as m; print(f'{m.__name__} {m.__version__} @ {m.__file__}')" + python -c "import pydra as m; print(f'{m.__name__} {m.__version__} @ {m.__file__}')" test: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Set up NeuroDebian - run: bash <(wget -q -O- http://neuro.debian.net/_files/neurodebian-travis.sh) - - name: Install FSL - run: sudo apt-get install -y fsl - - name: Install build dependencies - run: | - python -m pip install --upgrade pip - - name: Install task package - run: | - pip install ".[test]" - python -c "import pydra.tasks.$SUBPACKAGE as m; print(f'{m.__name__} {m.__version__} @ {m.__file__}')" - python -c "import pydra as m; print(f'{m.__name__} {m.__version__} @ {m.__file__}')" - - name: Test with pytest - run: | - source /etc/fsl/fsl.sh - pytest -sv --doctest-modules pydra/tasks/$SUBPACKAGE \ - --cov pydra.tasks.$SUBPACKAGE --cov-report xml - - uses: codecov/codecov-action@v1 - if: ${{ always() }} + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: conda-incubator/setup-miniconda@v3 + with: + python-version: ${{ matrix.python-version }} + mamba-version: "*" + channels: ${{ env.FSLCONDA }},conda-forge,defaults + channel-priority: true + - name: Install FSL + run: | + mamba install fsl-avwutils + mamba env config vars set FSLDIR="$CONDA_PREFIX" FSLOUTPUTTYPE="NIFTI_GZ" + # Hack because we're not doing a full FSL install + echo "6.0.7.9" > $CONDA_PREFIX/etc/fslversion + - name: Upgrade pip + run: | + python -m pip install --upgrade pip + - name: Install task package + run: | + pip install ".[test]" + python -c "import pydra.tasks.$SUBPACKAGE as m; print(f'{m.__name__} {m.__version__} @ {m.__file__}')" + python -c "import pydra as m; print(f'{m.__name__} {m.__version__} @ {m.__file__}')" + - name: Test with pytest + run: | + pytest -sv --doctest-modules --pyargs pydra.tasks.$SUBPACKAGE \ + --cov pydra.tasks.$SUBPACKAGE --cov-report xml --cov-report term-missing + - uses: codecov/codecov-action@v4 + if: ${{ always() }} + with: + token: ${{ secrets.CODECOV_TOKEN }} + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + - uses: hynek/build-and-inspect-python-package@v2 deploy: - needs: [devcheck, test] + needs: [build, devcheck, test] runs-on: ubuntu-latest - strategy: - matrix: - python-version: [3.9] + permissions: + id-token: write + if: github.repository_owner == 'nipype' && github.event.action == 'published' + environment: Publish steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - fetch-depth: 0 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install build tools - run: python -m pip install build twine - - name: Build source and wheel distributions - run: python -m build - - name: Check distributions - run: twine check dist/* - - uses: actions/upload-artifact@v2 - with: - name: distributions - path: dist/ - # Deploy on tags if PYPI_API_TOKEN is defined in the repository secrets. - # Secrets are not accessible in the if: condition [0], so set an output variable [1] - # [0] https://github.community/t/16928 - # [1] https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-output-parameter - - name: Check for PyPI token on tag - id: deployable - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') - env: - PYPI_API_TOKEN: "${{ secrets.PYPI_API_TOKEN }}" - run: if [ -n "$PYPI_API_TOKEN" ]; then echo ::set-output name=DEPLOY::true; fi - - name: Upload to PyPI - if: steps.deployable.outputs.DEPLOY - uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 # v1.4.2 - with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} + - uses: actions/download-artifact@v4 + with: + name: Packages + path: dist + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.gitignore b/.gitignore index ef50f11..4dc2815 100644 --- a/.gitignore +++ b/.gitignore @@ -129,4 +129,4 @@ dmypy.json .pyre/ # Generated by flit_scm -_versions.py +_version.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1061012..7a0fcb1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,13 +2,13 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.5.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: check-added-large-files - repo: https://github.com/psf/black - rev: 22.10.0 + rev: 24.3.0 hooks: - id: black diff --git a/CHANGELOG.md b/CHANGELOG.md index df7ef20..b8e31e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,30 @@ All notable changes to this project will be documented in this file. +## [0.3.0] - 2024-03-22 + +### Changed + +* Pydra 0.23+ compatibility +* Drop Python 3.7 compatibility, test Python 3.12 +* Updates to CI workflows + +## [0.2.2] – 2022-11-22 + +### Changed + +- Set Nipype developers as maintainers. + +## [0.2.1] – 2022-11-22 + +### Added + +- Publish package automatically on release. + +### Fixed + +- Update project metadata. + ## [0.2.0] – 2022-11-21 ### Added @@ -21,5 +45,8 @@ All notable changes to this project will be documented in this file. - Initial release for PyPI +[0.3.0]: https://github.com/nipype/pydra-nipype1/compare/0.2.2...0.3.0 +[0.2.2]: https://github.com/nipype/pydra-nipype1/compare/0.2.1...0.2.2 +[0.2.1]: https://github.com/nipype/pydra-nipype1/compare/0.2.0...0.2.1 [0.2.0]: https://github.com/nipype/pydra-nipype1/compare/0.1.0...0.2.0 [0.1.0]: https://github.com/nipype/pydra-nipype1/releases/tag/0.1.0 diff --git a/codecov.yml b/codecov.yml index bbf806c..f45bbe4 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,10 +1,7 @@ coverage: range: "50...100" ignore: # files and folders that will be removed during processing - - "**/tests" - "**/_version.py" - - "setup.py" - - "versioneer.py" status: project: default: diff --git a/pydra/tasks/nipype1/__init__.py b/pydra/tasks/nipype1/__init__.py index d786431..94ee228 100644 --- a/pydra/tasks/nipype1/__init__.py +++ b/pydra/tasks/nipype1/__init__.py @@ -2,10 +2,11 @@ >>> from pydra import ShellCommandTask >>> import pydra.tasks.nipype1 """ + try: from ._version import __version__ -except ImportError: - pass +except ImportError: # pragma: no cover + __version__ = "0+unknown" from .utils import Nipype1Task diff --git a/pydra/tasks/nipype1/tests/__init__.py b/pydra/tasks/nipype1/tests/__init__.py index e69de29..6e98041 100644 --- a/pydra/tasks/nipype1/tests/__init__.py +++ b/pydra/tasks/nipype1/tests/__init__.py @@ -0,0 +1,18 @@ +import sys +from atexit import register +from contextlib import ExitStack +from functools import lru_cache + + +if sys.version_info < (3, 9): + from importlib_resources import as_file, files +else: + from importlib.resources import as_file, files + +_stack = ExitStack() +register(_stack.close) + + +@lru_cache +def load_resource(anchor, *parts) -> str: + return str(_stack.enter_context(as_file(files(anchor).joinpath(*parts)))) diff --git a/pydra/tasks/nipype1/tests/test_nipype1task.py b/pydra/tasks/nipype1/tests/test_nipype1task.py index 9a17c6e..5ceeb16 100644 --- a/pydra/tasks/nipype1/tests/test_nipype1task.py +++ b/pydra/tasks/nipype1/tests/test_nipype1task.py @@ -1,6 +1,6 @@ import pytest import shutil -from pkg_resources import resource_filename +from . import load_resource from nipype.interfaces import fsl import nipype.interfaces.utility as nutil @@ -12,7 +12,7 @@ def test_isolation(tmp_path): in_file = tmp_path / "orig/tpms_msk.nii.gz" in_file.parent.mkdir() - shutil.copyfile(resource_filename("nipype", "testing/data/tpms_msk.nii.gz"), in_file) + shutil.copyfile(load_resource("nipype", "testing/data/tpms_msk.nii.gz"), in_file) out_dir = tmp_path / "output" out_dir.mkdir() diff --git a/pydra/tasks/nipype1/utils.py b/pydra/tasks/nipype1/utils.py index 0be31c4..913403b 100644 --- a/pydra/tasks/nipype1/utils.py +++ b/pydra/tasks/nipype1/utils.py @@ -11,7 +11,7 @@ def traitedspec_to_specinfo(traitedspec): return pydra.specs.SpecInfo( name="Inputs", fields=[ - (name, attrs.field(metadata={"help_string": trait.desc})) + (name, attrs.field(metadata={"help_string": trait.desc}, type=ty.Any)) for name, trait in traitedspec.traits().items() if name in trait_names ], @@ -27,11 +27,11 @@ class Nipype1Task(pydra.engine.task.TaskBase): in Pydra Task outputs. >>> import pytest - >>> from pkg_resources import resource_filename + >>> from pydra.tasks.nipype1.tests import load_resource >>> from nipype.interfaces import fsl >>> if fsl.Info.version() is None: ... pytest.skip() - >>> img = resource_filename('nipype', 'testing/data/tpms_msk.nii.gz') + >>> img = load_resource('nipype', 'testing/data/tpms_msk.nii.gz') >>> from pydra.tasks.nipype1.utils import Nipype1Task >>> thresh = Nipype1Task(fsl.Threshold()) @@ -68,7 +68,7 @@ def __init__( ) self.output_spec = traitedspec_to_specinfo(interface._outputs()) - def _run_task(self): + def _run_task(self, environment=None): inputs = attrs.asdict(self.inputs, filter=lambda a, v: v is not attrs.NOTHING) node = nipype.Node(self._interface, base_dir=self.output_dir, name=self.name) node.inputs.trait_set(**inputs) diff --git a/pyproject.toml b/pyproject.toml index b381503..7fe2461 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "flit_scm:buildapi" name = "pydra-nipype1" description = "Tools for importing nipype 1.x interfaces into Pydra" readme = "README.md" -requires-python = "~=3.7" +requires-python = ">=3.8" dependencies = [ "pydra >=0.6.2", "nipype", @@ -16,6 +16,9 @@ license = {file = "LICENSE"} authors = [ {name = "Chris Markiewicz", email = "markiewicz@stanford.edu"}, ] +maintainers = [ + {name = "Nipype developers", email = "neuroimaging@python.org"}, +] keywords = [ "pydra", "nipype", @@ -27,6 +30,7 @@ classifiers = [ "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS :: MacOS X", "Operating System :: POSIX :: Linux", + "Programming Language :: Python :: 3", "Topic :: Scientific/Engineering", ] dynamic = ["version"] @@ -36,14 +40,6 @@ dev = [ "black", "pre-commit", ] -doc = [ - "packaging", - "sphinx >=2.1.2", - "sphinx_rtd_theme", - "sphinxcontrib-apidoc ~=0.3.0", - "sphinxcontrib-napoleon", - "sphinxcontrib-versioning", -] test = [ "pytest>=4.4.0", "pytest-cov", @@ -64,5 +60,5 @@ write_to = "pydra/tasks/nipype1/_version.py" [tool.black] line-length = 99 -target-version = ["py37", "py38"] +target-version = ["py37"] exclude = "_version.py"