From 0df3e7cdf4f1ddf01efa1cae118838bb29d1fc96 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Wed, 11 May 2022 09:47:05 -0400 Subject: [PATCH 1/7] MNT: 1.8.1.dev0 --- doc/interfaces.rst | 2 +- nipype/info.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/interfaces.rst b/doc/interfaces.rst index 7f8bbf1e35..c5edd658f9 100644 --- a/doc/interfaces.rst +++ b/doc/interfaces.rst @@ -8,7 +8,7 @@ Interfaces and Workflows :Release: |version| :Date: |today| -Previous versions: `1.7.1 `_ `1.7.0 `_ +Previous versions: `1.8.0 `_ `1.7.1 `_ Workflows --------- diff --git a/nipype/info.py b/nipype/info.py index 4fe067b9b6..3794d3ef98 100644 --- a/nipype/info.py +++ b/nipype/info.py @@ -5,7 +5,7 @@ # nipype version information # Remove -dev for release -__version__ = "1.8.0" +__version__ = "1.8.1.dev0" def get_nipype_gitversion(): From bf67449e8385e1fb0e4e68ef546eb1daec3d3c80 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Wed, 11 May 2022 09:54:31 -0400 Subject: [PATCH 2/7] TEST: Check compatibility of vendored LooseVersion with distutils version --- nipype/external/tests/test_version.py | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 nipype/external/tests/test_version.py diff --git a/nipype/external/tests/test_version.py b/nipype/external/tests/test_version.py new file mode 100644 index 0000000000..24fa2e0321 --- /dev/null +++ b/nipype/external/tests/test_version.py @@ -0,0 +1,33 @@ +import warnings + +import pytest + +from nipype.external.version import LooseVersion as Vendored + +with warnings.catch_warnings(): + warnings.simplefilter("ignore") + try: + from distutils.version import LooseVersion as Original + except ImportError: + pytest.skip() + + +@pytest.mark.parametrize("v1, v2", [("0.0.0", "0.0.0"), ("0.0.0", "1.0.0")]) +def test_LooseVersion_compat(v1, v2): + vend1, vend2 = Vendored(v1), Vendored(v2) + orig1, orig2 = Original(v1), Original(v2) + + assert vend1 == orig1 + assert orig1 == vend1 + assert vend2 == orig2 + assert orig2 == vend2 + assert (vend1 == orig2) == (v1 == v2) + assert (vend1 < orig2) == (v1 < v2) + assert (vend1 > orig2) == (v1 > v2) + assert (vend1 <= orig2) == (v1 <= v2) + assert (vend1 >= orig2) == (v1 >= v2) + assert (orig1 == vend2) == (v1 == v2) + assert (orig1 < vend2) == (v1 < v2) + assert (orig1 > vend2) == (v1 > v2) + assert (orig1 <= vend2) == (v1 <= v2) + assert (orig1 >= vend2) == (v1 >= v2) From aa99263caa2b2eac097c1437ec62f3bb0cc7bb0d Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Wed, 11 May 2022 10:35:23 -0400 Subject: [PATCH 3/7] FIX: Make vendored LooseVersion comparable to distutils.version.LooseVersion --- nipype/external/version.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/nipype/external/version.py b/nipype/external/version.py index 0a2fbf167e..141443da96 100644 --- a/nipype/external/version.py +++ b/nipype/external/version.py @@ -12,6 +12,8 @@ # 2022.04.27 - Minor changes are made to the comments, # - The StrictVersion class was removed # - Black styling was applied +# 2022.05.11 - Refactor LooseVersion._cmp to permit comparisons with +# distutils.version.LooseVersion # # distutils/version.py @@ -38,6 +40,7 @@ of the same class, thus must follow the same rules) """ +import sys import re @@ -211,10 +214,7 @@ def __repr__(self): return "LooseVersion ('%s')" % str(self) def _cmp(self, other): - if isinstance(other, str): - other = LooseVersion(other) - elif not isinstance(other, LooseVersion): - return NotImplemented + other = self._coerce(other) if self.version == other.version: return 0 @@ -222,3 +222,19 @@ def _cmp(self, other): return -1 if self.version > other.version: return 1 + + @staticmethod + def _coerce(other): + if isinstance(other, LooseVersion): + return other + elif isinstance(other, str): + return LooseVersion(other) + elif "distutils" in sys.modules: + # Using this check to avoid importing distutils and suppressing the warning + try: + from distutils.version import LooseVersion as deprecated + except ImportError: + return NotImplemented + if isinstance(other, deprecated): + return LooseVersion(str(other)) + return NotImplemented From f0fb21d6a871d44236ba4cd7e1dbedb28569e4b7 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Wed, 11 May 2022 12:57:38 -0400 Subject: [PATCH 4/7] TEST: Catch warnings... apparently they double dip --- nipype/external/tests/test_version.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nipype/external/tests/test_version.py b/nipype/external/tests/test_version.py index 24fa2e0321..3c24475d94 100644 --- a/nipype/external/tests/test_version.py +++ b/nipype/external/tests/test_version.py @@ -15,7 +15,9 @@ @pytest.mark.parametrize("v1, v2", [("0.0.0", "0.0.0"), ("0.0.0", "1.0.0")]) def test_LooseVersion_compat(v1, v2): vend1, vend2 = Vendored(v1), Vendored(v2) - orig1, orig2 = Original(v1), Original(v2) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + orig1, orig2 = Original(v1), Original(v2) assert vend1 == orig1 assert orig1 == vend1 From 8557831c838a1484213474e91d5e1e1dd93d0d6b Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Mon, 16 May 2022 11:16:35 -0400 Subject: [PATCH 5/7] RF: Use looseversion package instead of vendoring --- nipype/__init__.py | 2 +- nipype/external/tests/test_version.py | 35 ---- nipype/external/version.py | 240 ----------------------- nipype/info.py | 1 + nipype/interfaces/dipy/preprocess.py | 2 +- nipype/interfaces/dipy/reconstruction.py | 2 +- nipype/interfaces/dipy/registration.py | 2 +- nipype/interfaces/dipy/stats.py | 2 +- nipype/interfaces/dipy/tracks.py | 2 +- nipype/utils/config.py | 2 +- nipype/utils/misc.py | 2 +- 11 files changed, 9 insertions(+), 283 deletions(-) delete mode 100644 nipype/external/tests/test_version.py delete mode 100644 nipype/external/version.py diff --git a/nipype/__init__.py b/nipype/__init__.py index bfc1e16a5b..bf6968a95a 100644 --- a/nipype/__init__.py +++ b/nipype/__init__.py @@ -14,7 +14,7 @@ import os # XXX Deprecate this import -from .external.version import LooseVersion +from looseversion import LooseVersion from .info import URL as __url__, STATUS as __status__, __version__ from .utils.config import NipypeConfig diff --git a/nipype/external/tests/test_version.py b/nipype/external/tests/test_version.py deleted file mode 100644 index 3c24475d94..0000000000 --- a/nipype/external/tests/test_version.py +++ /dev/null @@ -1,35 +0,0 @@ -import warnings - -import pytest - -from nipype.external.version import LooseVersion as Vendored - -with warnings.catch_warnings(): - warnings.simplefilter("ignore") - try: - from distutils.version import LooseVersion as Original - except ImportError: - pytest.skip() - - -@pytest.mark.parametrize("v1, v2", [("0.0.0", "0.0.0"), ("0.0.0", "1.0.0")]) -def test_LooseVersion_compat(v1, v2): - vend1, vend2 = Vendored(v1), Vendored(v2) - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - orig1, orig2 = Original(v1), Original(v2) - - assert vend1 == orig1 - assert orig1 == vend1 - assert vend2 == orig2 - assert orig2 == vend2 - assert (vend1 == orig2) == (v1 == v2) - assert (vend1 < orig2) == (v1 < v2) - assert (vend1 > orig2) == (v1 > v2) - assert (vend1 <= orig2) == (v1 <= v2) - assert (vend1 >= orig2) == (v1 >= v2) - assert (orig1 == vend2) == (v1 == v2) - assert (orig1 < vend2) == (v1 < v2) - assert (orig1 > vend2) == (v1 > v2) - assert (orig1 <= vend2) == (v1 <= v2) - assert (orig1 >= vend2) == (v1 >= v2) diff --git a/nipype/external/version.py b/nipype/external/version.py deleted file mode 100644 index 141443da96..0000000000 --- a/nipype/external/version.py +++ /dev/null @@ -1,240 +0,0 @@ -# This module has been vendored from CPython distutils/version.py -# last updated in 662db125cddbca1db68116c547c290eb3943d98e -# -# It is licensed according to the Python Software Foundation License Version 2 -# which may be found in full in the following (hopefully persistent) locations: -# -# https://github.com/python/cpython/blob/main/LICENSE -# https://spdx.org/licenses/Python-2.0.html -# -# The following changes have been made: -# -# 2022.04.27 - Minor changes are made to the comments, -# - The StrictVersion class was removed -# - Black styling was applied -# 2022.05.11 - Refactor LooseVersion._cmp to permit comparisons with -# distutils.version.LooseVersion -# - -# distutils/version.py -# -# Implements multiple version numbering conventions for the -# Python Module Distribution Utilities. - -"""Provides classes to represent module version numbers (one class for -each style of version numbering). There are currently two such classes -implemented: StrictVersion and LooseVersion. - -Every version number class implements the following interface: - * the 'parse' method takes a string and parses it to some internal - representation; if the string is an invalid version number, - 'parse' raises a ValueError exception - * the class constructor takes an optional string argument which, - if supplied, is passed to 'parse' - * __str__ reconstructs the string that was passed to 'parse' (or - an equivalent string -- ie. one that will generate an equivalent - version number instance) - * __repr__ generates Python code to recreate the version number instance - * _cmp compares the current instance with either another instance - of the same class or a string (which will be parsed to an instance - of the same class, thus must follow the same rules) -""" - -import sys -import re - - -class Version: - """Abstract base class for version numbering classes. Just provides - constructor (__init__) and reproducer (__repr__), because those - seem to be the same for all version numbering classes; and route - rich comparisons to _cmp. - """ - - def __init__(self, vstring=None): - if vstring: - self.parse(vstring) - - def __repr__(self): - return "%s ('%s')" % (self.__class__.__name__, str(self)) - - def __eq__(self, other): - c = self._cmp(other) - if c is NotImplemented: - return c - return c == 0 - - def __lt__(self, other): - c = self._cmp(other) - if c is NotImplemented: - return c - return c < 0 - - def __le__(self, other): - c = self._cmp(other) - if c is NotImplemented: - return c - return c <= 0 - - def __gt__(self, other): - c = self._cmp(other) - if c is NotImplemented: - return c - return c > 0 - - def __ge__(self, other): - c = self._cmp(other) - if c is NotImplemented: - return c - return c >= 0 - - -# The rules according to Greg Stein: -# 1) a version number has 1 or more numbers separated by a period or by -# sequences of letters. If only periods, then these are compared -# left-to-right to determine an ordering. -# 2) sequences of letters are part of the tuple for comparison and are -# compared lexicographically -# 3) recognize the numeric components may have leading zeroes -# -# The LooseVersion class below implements these rules: a version number -# string is split up into a tuple of integer and string components, and -# comparison is a simple tuple comparison. This means that version -# numbers behave in a predictable and obvious way, but a way that might -# not necessarily be how people *want* version numbers to behave. There -# wouldn't be a problem if people could stick to purely numeric version -# numbers: just split on period and compare the numbers as tuples. -# However, people insist on putting letters into their version numbers; -# the most common purpose seems to be: -# - indicating a "pre-release" version -# ('alpha', 'beta', 'a', 'b', 'pre', 'p') -# - indicating a post-release patch ('p', 'pl', 'patch') -# but of course this can't cover all version number schemes, and there's -# no way to know what a programmer means without asking him. -# -# The problem is what to do with letters (and other non-numeric -# characters) in a version number. The current implementation does the -# obvious and predictable thing: keep them as strings and compare -# lexically within a tuple comparison. This has the desired effect if -# an appended letter sequence implies something "post-release": -# eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002". -# -# However, if letters in a version number imply a pre-release version, -# the "obvious" thing isn't correct. Eg. you would expect that -# "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison -# implemented here, this just isn't so. -# -# Two possible solutions come to mind. The first is to tie the -# comparison algorithm to a particular set of semantic rules, as has -# been done in the StrictVersion class above. This works great as long -# as everyone can go along with bondage and discipline. Hopefully a -# (large) subset of Python module programmers will agree that the -# particular flavour of bondage and discipline provided by StrictVersion -# provides enough benefit to be worth using, and will submit their -# version numbering scheme to its domination. The free-thinking -# anarchists in the lot will never give in, though, and something needs -# to be done to accommodate them. -# -# Perhaps a "moderately strict" version class could be implemented that -# lets almost anything slide (syntactically), and makes some heuristic -# assumptions about non-digits in version number strings. This could -# sink into special-case-hell, though; if I was as talented and -# idiosyncratic as Larry Wall, I'd go ahead and implement a class that -# somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is -# just as happy dealing with things like "2g6" and "1.13++". I don't -# think I'm smart enough to do it right though. -# -# In any case, I've coded the test suite for this module (see -# ../test/test_version.py) specifically to fail on things like comparing -# "1.2a2" and "1.2". That's not because the *code* is doing anything -# wrong, it's because the simple, obvious design doesn't match my -# complicated, hairy expectations for real-world version numbers. It -# would be a snap to fix the test suite to say, "Yep, LooseVersion does -# the Right Thing" (ie. the code matches the conception). But I'd rather -# have a conception that matches common notions about version numbers. - - -class LooseVersion(Version): - - """Version numbering for anarchists and software realists. - Implements the standard interface for version number classes as - described above. A version number consists of a series of numbers, - separated by either periods or strings of letters. When comparing - version numbers, the numeric components will be compared - numerically, and the alphabetic components lexically. The following - are all valid version numbers, in no particular order: - - 1.5.1 - 1.5.2b2 - 161 - 3.10a - 8.02 - 3.4j - 1996.07.12 - 3.2.pl0 - 3.1.1.6 - 2g6 - 11g - 0.960923 - 2.2beta29 - 1.13++ - 5.5.kw - 2.0b1pl0 - - In fact, there is no such thing as an invalid version number under - this scheme; the rules for comparison are simple and predictable, - but may not always give the results you want (for some definition - of "want"). - """ - - component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE) - - def __init__(self, vstring=None): - if vstring: - self.parse(vstring) - - def parse(self, vstring): - # I've given up on thinking I can reconstruct the version string - # from the parsed tuple -- so I just store the string here for - # use by __str__ - self.vstring = vstring - components = [x for x in self.component_re.split(vstring) if x and x != '.'] - for i, obj in enumerate(components): - try: - components[i] = int(obj) - except ValueError: - pass - - self.version = components - - def __str__(self): - return self.vstring - - def __repr__(self): - return "LooseVersion ('%s')" % str(self) - - def _cmp(self, other): - other = self._coerce(other) - - if self.version == other.version: - return 0 - if self.version < other.version: - return -1 - if self.version > other.version: - return 1 - - @staticmethod - def _coerce(other): - if isinstance(other, LooseVersion): - return other - elif isinstance(other, str): - return LooseVersion(other) - elif "distutils" in sys.modules: - # Using this check to avoid importing distutils and suppressing the warning - try: - from distutils.version import LooseVersion as deprecated - except ImportError: - return NotImplemented - if isinstance(other, deprecated): - return LooseVersion(str(other)) - return NotImplemented diff --git a/nipype/info.py b/nipype/info.py index 3794d3ef98..045ff3a9b7 100644 --- a/nipype/info.py +++ b/nipype/info.py @@ -146,6 +146,7 @@ def get_nipype_gitversion(): "traits>=%s,!=5.0" % TRAITS_MIN_VERSION, "filelock>=3.0.0", "etelemetry>=0.2.0", + "looseversion", ] TESTS_REQUIRES = [ diff --git a/nipype/interfaces/dipy/preprocess.py b/nipype/interfaces/dipy/preprocess.py index d4271b6159..867ba79d81 100644 --- a/nipype/interfaces/dipy/preprocess.py +++ b/nipype/interfaces/dipy/preprocess.py @@ -4,7 +4,7 @@ import nibabel as nb import numpy as np -from nipype.external.version import LooseVersion +from looseversion import LooseVersion from ... import logging from ..base import traits, TraitedSpec, File, isdefined from .base import ( diff --git a/nipype/interfaces/dipy/reconstruction.py b/nipype/interfaces/dipy/reconstruction.py index cef7579772..14a2dff462 100644 --- a/nipype/interfaces/dipy/reconstruction.py +++ b/nipype/interfaces/dipy/reconstruction.py @@ -7,7 +7,7 @@ import numpy as np import nibabel as nb -from nipype.external.version import LooseVersion +from looseversion import LooseVersion from ... import logging from ..base import TraitedSpec, File, traits, isdefined diff --git a/nipype/interfaces/dipy/registration.py b/nipype/interfaces/dipy/registration.py index e07859560d..b9b818a66a 100644 --- a/nipype/interfaces/dipy/registration.py +++ b/nipype/interfaces/dipy/registration.py @@ -1,4 +1,4 @@ -from nipype.external.version import LooseVersion +from looseversion import LooseVersion from ... import logging from .base import HAVE_DIPY, dipy_version, dipy_to_nipype_interface, get_dipy_workflows diff --git a/nipype/interfaces/dipy/stats.py b/nipype/interfaces/dipy/stats.py index 971857b64e..f2de24ca33 100644 --- a/nipype/interfaces/dipy/stats.py +++ b/nipype/interfaces/dipy/stats.py @@ -1,4 +1,4 @@ -from nipype.external.version import LooseVersion +from looseversion import LooseVersion from ... import logging from .base import HAVE_DIPY, dipy_version, dipy_to_nipype_interface, get_dipy_workflows diff --git a/nipype/interfaces/dipy/tracks.py b/nipype/interfaces/dipy/tracks.py index 6b1da93a95..e97250dd26 100644 --- a/nipype/interfaces/dipy/tracks.py +++ b/nipype/interfaces/dipy/tracks.py @@ -3,7 +3,7 @@ import os.path as op import numpy as np import nibabel as nb -from nipype.external.version import LooseVersion +from looseversion import LooseVersion from ... import logging from ..base import TraitedSpec, BaseInterfaceInputSpec, File, isdefined, traits diff --git a/nipype/utils/config.py b/nipype/utils/config.py index 3106bd4c8c..9c7505455d 100644 --- a/nipype/utils/config.py +++ b/nipype/utils/config.py @@ -14,7 +14,7 @@ import errno import atexit from warnings import warn -from nipype.external.version import LooseVersion +from looseversion import LooseVersion import configparser import numpy as np diff --git a/nipype/utils/misc.py b/nipype/utils/misc.py index ba8687110c..11aa9ea859 100644 --- a/nipype/utils/misc.py +++ b/nipype/utils/misc.py @@ -9,7 +9,7 @@ from collections.abc import Iterator from warnings import warn -from nipype.external.version import LooseVersion +from looseversion import LooseVersion import numpy as np From 0a958a19a93892f4c06243c683c94593314ce45d Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Mon, 16 May 2022 16:40:52 -0400 Subject: [PATCH 6/7] DOC: 1.8.1 changelog --- doc/changelog/1.X.X-changelog.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/changelog/1.X.X-changelog.rst b/doc/changelog/1.X.X-changelog.rst index 03caebaa27..00695e64f1 100644 --- a/doc/changelog/1.X.X-changelog.rst +++ b/doc/changelog/1.X.X-changelog.rst @@ -1,3 +1,13 @@ +1.8.1 (May 16, 2022) +==================== + +Bug-fix release in the 1.8.x series. + +The previous release vendored ``distutils.version.LooseVersion``, and the vendored objects did not +preserve compatiblity with the ``distutils`` objects. This release switches to the +``looseversion`` package that ensures compatiblity. + + 1.8.0 (May 10, 2022) ==================== From 8b21e4ba77fcb883864e2eca8498f3abd0efe453 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Mon, 16 May 2022 16:41:12 -0400 Subject: [PATCH 7/7] REL: 1.8.1 --- nipype/info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nipype/info.py b/nipype/info.py index 045ff3a9b7..f60f774e4a 100644 --- a/nipype/info.py +++ b/nipype/info.py @@ -5,7 +5,7 @@ # nipype version information # Remove -dev for release -__version__ = "1.8.1.dev0" +__version__ = "1.8.1" def get_nipype_gitversion():