From 2670b1266ef09458e654fafa86eef00016db6db2 Mon Sep 17 00:00:00 2001 From: oesteban Date: Thu, 26 Mar 2020 15:08:56 -0700 Subject: [PATCH] chore: Use setuptools_scm to manage versioning Summary ------- Pilots whether we can abandon versioneer in all projects, as setuptools_scm has minimal boilerplate. Changes ------- The setuptools_scm module is added to the pyproject.toml file, as recommended in the documentation. A tiny hack is added to the root ``__init__.py`` file, so that the version is read from ``nitransforms/_version.py`` when that file exists or it is interpolated via ``setuptools_scm.get_version``. To make the version traverse into container images, before building, ``python setup.py --version`` must be invoked first (and it will create the ``nitransforms/_version.py`` file. ``nitransforms/_version.py`` has been added to ``.gitignore`` to avoid adding it by mistake. Finally, the CircleCI workflow is also modified, so that the packaging is always tested and installed versions are checked both for python packages and docker distributions. Resolves: #82 --- .circleci/config.yml | 101 +++++++++++++++++++++++++++-- .gitignore | 3 + nitransforms/__init__.py | 18 ++++- nitransforms/tests/test_version.py | 41 ++++++++++++ pyproject.toml | 10 +++ setup.cfg | 21 +++++- setup.py | 7 ++ 7 files changed, 190 insertions(+), 11 deletions(-) create mode 100644 nitransforms/tests/test_version.py create mode 100644 pyproject.toml diff --git a/.circleci/config.yml b/.circleci/config.yml index b7db4924..7d3f0765 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -63,6 +63,19 @@ jobs: key: build-v1-{{ .Branch }}-{{ epoch }} paths: - /tmp/docker + - run: + name: Check version packaged in Docker image + command: | + THISVERSION=$(python3 setup.py --version) + THISVERSION=${CIRCLE_TAG:-$THISVERSION} + INSTALLED_VERSION=$(\ + docker run -it --rm --entrypoint=python nitransforms \ + -c 'import nitransforms as nit; print(nit.__version__)') + INSTALLED_VERSION=${INSTALLED_VERSION%$'\r'} + echo "VERSION: \"$THISVERSION\"" + echo "INSTALLED: \"$INSTALLED_VERSION\"" + test "$INSTALLED_VERSION" = "$THISVERSION" + - run: name: Store FreeSurfer license file command: | @@ -125,16 +138,81 @@ jobs: - store_test_results: path: /tmp/tests/summaries/ - test_packaging_and_deploy: - machine: - image: circleci/classic:201808-01 + test_package: + docker: + - image: circleci/python:3.7.4 + working_directory: /tmp/src/nitransforms + steps: + - checkout + - run: + name: Setup Python environment with virtualenvs + command: | + python -m pip install --user --upgrade virtualenv pip + - run: + name: Prepare build environment + command: | + virtualenv --python=python3 /tmp/build + source /tmp/build/bin/activate + python3 -m pip install "setuptools ~= 42.0" "setuptools_scm[toml] >= 3.4" "pip>=10.0.1" \ + twine docutils + - run: + name: Build NiTransforms in build environment + command: | + source /tmp/build/bin/activate + python setup.py sdist bdist_wheel + - run: + name: Check sdist package in build environment + command: | + source /tmp/build/bin/activate + twine check dist/nitransforms* + - store_artifacts: + path: /tmp/src/nitransforms/dist + - run: + name: Prepare sdist install environment + command: | + virtualenv --python=python3 /tmp/install_sdist + source /tmp/install_sdist/bin/activate + python3 -m pip install "setuptools ~= 42.0" "setuptools_scm[toml] >= 3.4" "pip>=10.0.1" + - run: + name: Install sdist package into install environment and check version + command: | + source /tmp/install_sdist/bin/activate + THISVERSION=$( python setup.py --version ) + THISVERSION=${CIRCLE_TAG:-$THISVERSION} + pip install dist/nitransforms*.tar.gz + INSTALLED_VERSION=$(python -c 'import nitransforms as nit; print(nit.__version__)') + INSTALLED_VERSION=${INSTALLED_VERSION%$'\r'} + echo "VERSION: \"$THISVERSION\"" + echo "INSTALLED: \"$INSTALLED_VERSION\"" + test "$INSTALLED_VERSION" = "$THISVERSION" + - run: + name: Prepare wheel install environment + command: | + virtualenv --python=python3 /tmp/install_wheel + source /tmp/install_wheel/bin/activate + python3 -m pip install "setuptools ~= 42.0" "setuptools_scm[toml] >= 3.4" "pip>=10.0.1" + - run: + name: Install wheel into install environment and check version + command: | + source /tmp/install_wheel/bin/activate + THISVERSION=$( python setup.py --version ) + THISVERSION=${CIRCLE_TAG:-$THISVERSION} + pip install dist/nitransforms*.whl + INSTALLED_VERSION=$(python -c 'import nitransforms as nit; print(nit.__version__)') + INSTALLED_VERSION=${INSTALLED_VERSION%$'\r'} + echo "VERSION: \"$THISVERSION\"" + echo "INSTALLED: \"$INSTALLED_VERSION\"" + test "$INSTALLED_VERSION" = "$THISVERSION" + + deploy_pypi: + docker: + - image: circleci/python:3.7.4 working_directory: /tmp/src/nitransforms steps: - checkout - - run: pyenv local 3.7.0 - run: name: Install build depends - command: python3 -m pip install "setuptools>=30.4.0" "pip>=10.0.1" "twine<2.0" docutils + command: python3 -m pip install "setuptools ~= 42.0" "setuptools_scm[toml] >= 3.4" "pip>=10.0.1" "twine<2.0" docutils - run: name: Build and check command: | @@ -144,7 +222,7 @@ jobs: - run: name: Validate version command: | - THISVERSION=$( python3 get_version.py ) + THISVERSION=$( python setup.py --version ) python3 -m pip install dist/*.tar.gz mkdir empty cd empty @@ -167,9 +245,18 @@ workflows: tags: only: /.*/ - - test_packaging_and_deploy: + - test_package: + filters: + branches: + ignore: + - /docs?\/.*/ + tags: + only: /.*/ + + - deploy_pypi: requires: - build_pytest + - test_package filters: branches: ignore: /.*/ diff --git a/.gitignore b/.gitignore index fccbf479..8681c41b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# setuptools-scm +nitransforms/_version.py + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/nitransforms/__init__.py b/nitransforms/__init__.py index 34868b55..c35b9a18 100644 --- a/nitransforms/__init__.py +++ b/nitransforms/__init__.py @@ -18,7 +18,21 @@ """ from .linear import Affine from .nonlinear import DisplacementsFieldTransform -from .io import afni, fsl, itk +try: + from ._version import __version__ +except ModuleNotFoundError: + from pkg_resources import get_distribution, DistributionNotFound + try: + __version__ = get_distribution('nitransforms').version + except DistributionNotFound: + __version__ = 'unknown' + del get_distribution + del DistributionNotFound -__all__ = ['afni', 'fsl', 'itk', 'Affine', 'DisplacementsFieldTransform'] + +__all__ = [ + 'Affine', + 'DisplacementsFieldTransform', + '__version__', +] diff --git a/nitransforms/tests/test_version.py b/nitransforms/tests/test_version.py new file mode 100644 index 00000000..0e2e7fd6 --- /dev/null +++ b/nitransforms/tests/test_version.py @@ -0,0 +1,41 @@ +"""Test _version.py.""" +import sys +from collections import namedtuple +from pkg_resources import DistributionNotFound +from importlib import reload +import nitransforms + + +def test_version_scm0(monkeypatch): + """Retrieve the version via setuptools_scm.""" + class _version: + __version__ = "10.0.0" + + monkeypatch.setitem(sys.modules, 'nitransforms._version', _version) + reload(nitransforms) + assert nitransforms.__version__ == "10.0.0" + + +def test_version_scm1(monkeypatch): + """Retrieve the version via pkg_resources.""" + monkeypatch.setitem(sys.modules, 'nitransforms._version', None) + + def _dist(name): + Distribution = namedtuple("Distribution", ["name", "version"]) + return Distribution(name, "success") + + monkeypatch.setattr('pkg_resources.get_distribution', _dist) + reload(nitransforms) + assert nitransforms.__version__ == "success" + + +def test_version_scm2(monkeypatch): + """Check version could not be interpolated.""" + monkeypatch.setitem(sys.modules, 'nitransforms._version', None) + + def _raise(name): + raise DistributionNotFound("No get_distribution mock") + + monkeypatch.setattr('pkg_resources.get_distribution', _raise) + reload(nitransforms) + assert nitransforms.__version__ == "unknown" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..132dd49d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,10 @@ +[build-system] +requires = ["setuptools ~= 42.0", "wheel", "setuptools_scm[toml] >= 3.4"] + +[tool.setuptools_scm] +write_to = "nitransforms/_version.py" +write_to_template = """\ +\"\"\"Version file, automatically generated by setuptools_scm.\"\"\" +__version__ = "{version}" +""" +fallback_version = "0.0" diff --git a/setup.cfg b/setup.cfg index 5d8b63a1..122eee65 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,20 @@ [metadata] -provides = - nitransforms +author = The NiPreps developers +author_email = nipreps@gmail.com +classifiers = + Development Status :: 2 - Pre-Alpha + Intended Audience :: Science/Research + Topic :: Scientific/Engineering :: Image Recognition + License :: OSI Approved :: BSD License + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 +description = NiTransforms -- Neuroimaging spatial transforms in Python. +license = MIT +long_description = file:README.md +long_description_content_type = text/markdown; charset=UTF-8 +provides = nitransforms +url = https://github.com/poldracklab/nitransforms [options] python_requires = >= 3.6 @@ -14,6 +28,9 @@ test_requires = pytest-cov nose codecov +setup_requires = + setuptools_scm + toml packages = find: include_package_data = True diff --git a/setup.py b/setup.py index 8b25856b..5f2a1c89 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,13 @@ +"""Prepare package for distribution.""" +from pathlib import Path from setuptools import setup +from toml import loads if __name__ == "__main__": + scm_cfg = loads( + (Path(__file__).parent / "pyproject.toml").read_text() + )["tool"]["setuptools_scm"] setup( name="nitransforms", + use_scm_version=scm_cfg, )