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