Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Fix numpy version checks #3170

Merged
merged 6 commits into from

3 participants

@rgommers
Owner

Closes gh-2998.

Note that no existing version string parsers in distutils/setuptools/distutils2 work well, so this is the simplest solution I think.

@coveralls

Coverage Status

Coverage remained the same when pulling 7bf2548 on rgommers:version-compare into 34ae412 on scipy:master.

@argriffing
Collaborator

looks like __cmp__ is not the best way to do this with python3 http://docs.python.org/3.0/whatsnew/3.0.html#ordering-comparisons

@rgommers
Owner

Hmm, so have to implement six methods now instead of one. I'm sure there was a good reason, but it's not a very helpful decision here.

@coveralls

Coverage Status

Coverage remained the same when pulling 993aeb8 on rgommers:version-compare into 34ae412 on scipy:master.

@coveralls

Coverage Status

Coverage remained the same when pulling 63c7404 on rgommers:version-compare into 3982a90 on scipy:master.

@rgommers
Owner

OK fixed

@argriffing argriffing merged commit fde1826 into scipy:master
@argriffing
Collaborator

Great! This addition looks like it could also be useful for other packages like sklearn

https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/utils/fixes.py#L82
https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/preprocessing/label.py#L37
https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/utils/extmath.py#L155

or even upstream into numpy, or as a use case for distutils. By the way, I just noticed that NormalizedVersion seems to implement similar logic http://www.python.org/dev/peps/pep-0386/ maybe it could be used even though LooseVersion fails? It seems to know about alpha, beta, release candidate, development versions, etc.

[Edit: I just re-read the comments in the associated scipy issue, and I see that NormalizedVersion does not work for numpy versions]

@rgommers
Owner

Good idea to add this to numpy, we can stick it in numpy.version. Still needed in scipy to support older numpy versions then.

distutils is hibernating. Proposing an addition to NormalizedVersion would make sense I think but I don't have the bandwidth to argue about it, nor the patience to see my patch languish for a year or more.

@argriffing
Collaborator

Still needed in scipy to support older numpy versions then.

Indeed...

I noticed https://github.com/scipy/scipy/blob/master/scipy/__init__.py#L83 should this be updated too?

@rgommers rgommers deleted the rgommers:version-compare branch
@rgommers
Owner

Good catch, missed that one. Will also start printing an incorrect warning for numpy 1.10. Will send a new PR.

@argriffing
Collaborator

Should this also be used to check scipy versions by packages that import scipy?

@rgommers
Owner

Yes, that would make sense. The next time that scipy version strings will go wrong is 0.20 (which I hope we won't reach). Would be best if NumpyVersion became a public class in numpy, then scipy users can re-use that.

@rgommers
Owner

Updated version check in gh-3177.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
12 scipy/interpolate/tests/test_interpolate.py
@@ -1,15 +1,15 @@
from __future__ import division, print_function, absolute_import
-from distutils.version import LooseVersion
+import warnings
-from numpy.testing import assert_, assert_equal, assert_almost_equal, \
- assert_array_almost_equal, assert_raises, assert_array_equal, \
- dec, TestCase, run_module_suite, assert_allclose
+from numpy.testing import (assert_, assert_equal, assert_almost_equal,
+ assert_array_almost_equal, assert_raises, assert_array_equal,
+ dec, TestCase, run_module_suite, assert_allclose)
from numpy import mgrid, pi, sin, ogrid, poly1d, linspace
import numpy as np
-import warnings
from scipy.lib.six import xrange
+from scipy.lib._version import NumpyVersion
from scipy.interpolate import (interp1d, interp2d, lagrange, PPoly, BPoly,
ppform, splrep, splev, splantider, splint, sproot)
@@ -477,7 +477,7 @@ def test_shape(self):
assert_equal(np.shape(p(0.5)), ())
assert_equal(np.shape(p(np.array(0.5))), ())
- if LooseVersion(np.version.version) >= LooseVersion('1.7'):
+ if NumpyVersion(np.__version__) >= '1.7.0':
# can't use dtype=object (with any numpy; what fails is
# constructing the object array here for old numpy)
assert_raises(ValueError, p, np.array([[0.1, 0.2], [0.4]]))
View
155 scipy/lib/_version.py
@@ -0,0 +1,155 @@
+"""Utility to compare (Numpy) version strings.
+
+The NumpyVersion class allows properly comparing numpy version strings.
+The LooseVersion and StrictVersion classes that distutils provides don't
+work; they don't recognize anything like alpha/beta/rc/dev versions.
+
+"""
+
+import re
+
+from scipy.lib.six import string_types
+
+
+__all__ = ['NumpyVersion']
+
+
+class NumpyVersion():
+ """Parse and compare numpy version strings.
+
+ Numpy has the following versioning scheme (numbers given are examples; they
+ can be >9) in principle):
+
+ - Released version: '1.8.0', '1.8.1', etc.
+ - Alpha: '1.8.0a1', '1.8.0a2', etc.
+ - Beta: '1.8.0b1', '1.8.0b2', etc.
+ - Release candidates: '1.8.0rc1', '1.8.0rc2', etc.
+ - Development versions: '1.8.0.dev-f1234afa' (git commit hash appended)
+ - Development versions after a1: '1.8.0a1.dev-f1234afa',
+ '1.8.0b2.dev-f1234afa',
+ '1.8.1rc1.dev-f1234afa', etc.
+ - Development versions (no git hash available): '1.8.0.dev-Unknown'
+
+ Comparing needs to be done against a valid version string or other
+ `NumpyVersion` instance.
+
+ Parameters
+ ----------
+ vstring : str
+ Numpy version string (``np.__version__``).
+
+ Notes
+ -----
+ All dev versions of the same (pre-)release compare equal.
+
+ Examples
+ --------
+ >>> from scipy.lib._version import NumpyVersion
+ >>> if NumpyVersion(np.__version__) < '1.7.0'):
+ ... print('skip')
+ skip
+
+ >>> NumpyVersion('1.7') # raises ValueError, add ".0"
+
+ """
+ def __init__(self, vstring):
+ self.vstring = vstring
+ ver_main = re.match(r'\d[.]\d+[.]\d+', vstring)
+ if not ver_main:
+ raise ValueError("Not a valid numpy version string")
+
+ self.version = ver_main.group()
+ self.major, self.minor, self.bugfix = [int(x) for x in
+ self.version.split('.')]
+ if len(vstring) == ver_main.end():
+ self.pre_release = 'final'
+ else:
+ alpha = re.match(r'a\d', vstring[ver_main.end():])
+ beta = re.match(r'b\d', vstring[ver_main.end():])
+ rc = re.match(r'rc\d', vstring[ver_main.end():])
+ pre_rel = [m for m in [alpha, beta, rc] if m is not None]
+ if pre_rel:
+ self.pre_release = pre_rel[0].group()
+ else:
+ self.pre_release = ''
+
+ self.is_devversion = bool(re.search(r'.dev-', vstring))
+
+ def _compare_version(self, other):
+ """Compare major.minor.bugfix"""
+ if self.major == other.major:
+ if self.minor == other.minor:
+ if self.bugfix == other.bugfix:
+ vercmp = 0
+ elif self.bugfix > other.bugfix:
+ vercmp = 1
+ else:
+ vercmp = -1
+ elif self.minor > other.minor:
+ vercmp = 1
+ else:
+ vercmp = -1
+ elif self.major > other.major:
+ vercmp = 1
+ else:
+ vercmp = -1
+
+ return vercmp
+
+ def _compare_pre_release(self, other):
+ """Compare alpha/beta/rc/final."""
+ if self.pre_release == other.pre_release:
+ vercmp = 0
+ elif self.pre_release == 'final':
+ vercmp = 1
+ elif other.pre_release == 'final':
+ vercmp = -1
+ elif self.pre_release > other.pre_release:
+ vercmp = 1
+ else:
+ vercmp = -1
+
+ return vercmp
+
+ def _compare(self, other):
+ if not isinstance(other, (string_types, NumpyVersion)):
+ raise ValueError("Invalid object to compare with NumpyVersion.")
+
+ if isinstance(other, string_types):
+ other = NumpyVersion(other)
+
+ vercmp = self._compare_version(other)
+ if vercmp == 0:
+ # Same x.y.z version, check for alpha/beta/rc
+ vercmp = self._compare_pre_release(other)
+ if vercmp == 0:
+ # Same version and same pre-release, check if dev version
+ if self.is_devversion is other.is_devversion:
+ vercmp = 0
+ elif self.is_devversion:
+ vercmp = -1
+ else:
+ vercmp = 1
+
+ return vercmp
+
+ def __lt__(self, other):
+ return self._compare(other) < 0
+
+ def __le__(self, other):
+ return self._compare(other) <= 0
+
+ def __eq__(self, other):
+ return self._compare(other) == 0
+
+ def __ne__(self, other):
+ return self._compare(other) != 0
+
+ def __gt__(self, other):
+ return self._compare(other) > 0
+
+ def __ge__(self, other):
+ return self._compare(other) >= 0
+
+ def __repr(self):
+ return "NumpyVersion(%s)" % self.vstring
View
50 scipy/lib/tests/test__version.py
@@ -0,0 +1,50 @@
+from numpy.testing import assert_, run_module_suite, assert_raises
+from scipy.lib._version import NumpyVersion
+
+
+def test_main_versions():
+ assert_(NumpyVersion('1.8.0') == '1.8.0')
+ for ver in ['1.9.0', '2.0.0', '1.8.1']:
+ assert_(NumpyVersion('1.8.0') < ver)
+
+ for ver in ['1.7.0', '1.7.1', '0.9.9']:
+ assert_(NumpyVersion('1.8.0') > ver)
+
+def test_version_1_point_10():
+ # regression test for gh-2998.
+ assert_(NumpyVersion('1.9.0') < '1.10.0')
+ assert_(NumpyVersion('1.11.0') < '1.11.1')
+ assert_(NumpyVersion('1.11.0') == '1.11.0')
+ assert_(NumpyVersion('1.99.11') < '1.99.12')
+
+def test_alpha_beta_rc():
+ assert_(NumpyVersion('1.8.0rc1') == '1.8.0rc1')
+ for ver in ['1.8.0', '1.8.0rc2']:
+ assert_(NumpyVersion('1.8.0rc1') < ver)
+
+ for ver in ['1.8.0a2', '1.8.0b3', '1.7.2rc4']:
+ assert_(NumpyVersion('1.8.0rc1') > ver)
+
+ assert_(NumpyVersion('1.8.0b1') > '1.8.0a2')
+
+
+def test_dev_version():
+ assert_(NumpyVersion('1.9.0.dev-Unknown') < '1.9.0')
+ for ver in ['1.9.0', '1.9.0a1', '1.9.0b2', '1.9.0b2.dev-ffffffff']:
+ assert_(NumpyVersion('1.9.0.dev-f16acvda') < ver)
+
+ assert_(NumpyVersion('1.9.0.dev-f16acvda') == '1.9.0.dev-11111111')
+
+
+def test_dev_a_b_rc_mixed():
+ assert_(NumpyVersion('1.9.0a2.dev-f16acvda') == '1.9.0a2.dev-11111111')
+ assert_(NumpyVersion('1.9.0a2.dev-6acvda54') < '1.9.0a2')
+
+
+def test_raises():
+ for ver in ['1.9', '1,9.0', '1.7.x']:
+ assert_raises(ValueError, NumpyVersion, ver)
+
+
+if __name__ == "__main__":
+ run_module_suite()
View
4 scipy/sparse/csgraph/tests/test_conversions.py
@@ -2,6 +2,7 @@
import numpy as np
from numpy.testing import assert_array_almost_equal, dec
+from scipy.lib._version import NumpyVersion
from scipy.sparse import csr_matrix
from scipy.sparse.csgraph import csgraph_from_dense, csgraph_to_dense
@@ -36,7 +37,8 @@ def test_csgraph_from_dense():
assert_array_almost_equal(G, G_csr.toarray())
-@dec.skipif(np.version.short_version < '1.6', "Can't test arrays with infs.")
+@dec.skipif(NumpyVersion(np.__version__) < '1.6.0',
+ "Can't test arrays with infs.")
def test_csgraph_to_dense():
np.random.seed(1234)
G = np.random.random((10, 10))
View
19 scipy/sparse/csgraph/tests/test_shortest_path.py
@@ -3,6 +3,7 @@
import numpy as np
from numpy.testing import (assert_array_almost_equal, assert_raises, dec,
run_module_suite)
+from scipy.lib._version import NumpyVersion
from scipy.sparse.csgraph import (shortest_path, dijkstra, johnson,
bellman_ford, construct_dist_matrix, NegativeCycleError)
@@ -57,7 +58,8 @@
methods = ['auto', 'FW', 'D', 'BF', 'J']
-@dec.skipif(np.version.short_version < '1.6', "Can't test arrays with infs.")
+@dec.skipif(NumpyVersion(np.__version__) < '1.6.0',
+ "Can't test arrays with infs.")
def test_dijkstra_limit():
limits = [0, 2, np.inf]
results = [undirected_SP_limit_0,
@@ -72,7 +74,8 @@ def check(limit, result):
yield check, limit, result
-@dec.skipif(np.version.short_version < '1.6', "Can't test arrays with infs.")
+@dec.skipif(NumpyVersion(np.__version__) < '1.6.0',
+ "Can't test arrays with infs.")
def test_directed():
def check(method):
SP = shortest_path(directed_G, method=method, directed=True,
@@ -113,7 +116,8 @@ def check(func, indshape):
yield check, func, indshape
-@dec.skipif(np.version.short_version < '1.6', "Can't test arrays with infs.")
+@dec.skipif(NumpyVersion(np.__version__) < '1.6.0',
+ "Can't test arrays with infs.")
def test_predecessors():
SP_res = {True: directed_SP,
False: undirected_SP}
@@ -132,7 +136,8 @@ def check(method, directed):
yield check, method, directed
-@dec.skipif(np.version.short_version < '1.6', "Can't test arrays with infs.")
+@dec.skipif(NumpyVersion(np.__version__) < '1.6.0',
+ "Can't test arrays with infs.")
def test_construct_shortest_path():
def check(method, directed):
SP1, pred = shortest_path(directed_G,
@@ -147,7 +152,8 @@ def check(method, directed):
yield check, method, directed
-@dec.skipif(np.version.short_version < '1.6', "Can't test arrays with infs.")
+@dec.skipif(NumpyVersion(np.__version__) < '1.6.0',
+ "Can't test arrays with infs.")
def test_unweighted_path():
def check(method, directed):
SP1 = shortest_path(directed_G,
@@ -180,7 +186,8 @@ def check(method, directed):
yield check, method, directed
-@dec.skipif(np.version.short_version < '1.6', "Can't test arrays with infs.")
+@dec.skipif(NumpyVersion(np.__version__) < '1.6.0',
+ "Can't test arrays with infs.")
def test_masked_input():
G = np.ma.masked_equal(directed_G, 0)
View
15 scipy/sparse/tests/test_base.py
@@ -18,7 +18,6 @@ class for generic tests" section.
Run tests if sparse is not installed:
python tests/test_base.py
"""
-from distutils.version import LooseVersion
import warnings
@@ -42,6 +41,8 @@ class for generic tests" section.
from scipy.sparse.sputils import supported_dtypes, isscalarlike
from scipy.sparse.linalg import splu, expm, inv
+from scipy.lib._version import NumpyVersion
+
import nose
warnings.simplefilter('ignore', SparseEfficiencyWarning)
@@ -55,7 +56,7 @@ def _can_cast_samekind(dtype1, dtype2):
and a strict subset of 'same_kind'. So for 1.5.x we just skip the cases
where 'safe' is False and 'same_kind' True.
"""
- if np.__version__[:3] == '1.5':
+ if NumpyVersion(np.__version__) < '1.6.0':
return np.can_cast(dtype1, dtype2)
else:
return np.can_cast(dtype1, dtype2, casting='same_kind')
@@ -1338,7 +1339,7 @@ def test_size_zero_conversions(self):
def test_unary_ufunc_overrides(self):
def check(name):
- if LooseVersion(np.version.version) < LooseVersion('1.9'):
+ if NumpyVersion(np.__version__) < '1.9.0a1':
if name == "sign":
raise nose.SkipTest("sign conflicts with comparison op "
"support on Numpy < 1.9")
@@ -1353,7 +1354,7 @@ def check(name):
X2 = ufunc(X)
assert_array_equal(X2.toarray(), X0)
- if not (LooseVersion(np.version.version) < LooseVersion('1.9')):
+ if not (NumpyVersion(np.__version__) < '1.9.0a1'):
# the out argument doesn't work on Numpy < 1.9
out = np.zeros_like(X0)
X3 = ufunc(X, out=out)
@@ -1389,7 +1390,7 @@ def test_binary_ufunc_overrides(self):
a_items = dict(dense=a, scalar=c, cplx_scalar=d, int_scalar=e, sparse=asp)
b_items = dict(dense=b, scalar=c, cplx_scalar=d, int_scalar=e, sparse=bsp)
- @dec.skipif(LooseVersion(np.version.version) < LooseVersion('1.9'),
+ @dec.skipif(NumpyVersion(np.__version__) < '1.9.0a1',
"feature requires Numpy 1.9")
def check(i, j, dtype):
ax = a_items[i]
@@ -1487,7 +1488,7 @@ def check_one(ufunc, allclose=False):
class _TestInplaceArithmetic:
- @dec.skipif(LooseVersion(np.version.version) < LooseVersion('1.9'),
+ @dec.skipif(NumpyVersion(np.__version__) < '1.9.0a1',
"Not implemented with Numpy < 1.9")
def test_inplace_dense_method(self):
# Check that ndarray inplace ops work
@@ -1528,7 +1529,7 @@ def test_inplace_dense_syntax(self):
y -= b
assert_array_equal(x, y)
- if not (LooseVersion(np.version.version) < LooseVersion('1.9')):
+ if not (NumpyVersion(np.__version__) < '1.9.0a1'):
# These operations don't work properly without __numpy_ufunc__,
# due to missing or incompatible __r*__ implementations
View
10 scipy/spatial/tests/test_qhull.py
@@ -1,16 +1,16 @@
from __future__ import division, print_function, absolute_import
import os
-import sys
+import copy
import numpy as np
from numpy.testing import assert_equal, assert_almost_equal, run_module_suite,\
assert_, dec, assert_allclose, assert_array_equal, assert_raises
from scipy.lib.six import xrange
-import copy
import scipy.spatial.qhull as qhull
from scipy.spatial import cKDTree as KDTree
+from scipy.lib._version import NumpyVersion
def sorted_tuple(x):
@@ -279,7 +279,8 @@ def barycentric_transform(tr, x):
ok = (j != -1) | at_boundary
assert_(ok.all(), "%s %s" % (err_msg, np.where(~ok)))
- @dec.skipif(np.version.short_version < '1.6', "No einsum in numpy 1.5.x")
+ @dec.skipif(NumpyVersion(np.__version__) < '1.6.0',
+ "No einsum in numpy 1.5.x")
def test_degenerate_barycentric_transforms(self):
# The triangulation should not produce invalid barycentric
# transforms that stump the simplex finding
@@ -298,7 +299,8 @@ def test_degenerate_barycentric_transforms(self):
self._check_barycentric_transforms(tri)
@dec.slow
- @dec.skipif(np.version.short_version < '1.6', "No einsum in numpy 1.5.x")
+ @dec.skipif(NumpyVersion(np.__version__) < '1.6.0',
+ "No einsum in numpy 1.5.x")
def test_more_barycentric_transforms(self):
# Triangulate some "nasty" grids
View
3  scipy/special/tests/test_logit.py
@@ -2,6 +2,7 @@
import numpy as np
from numpy.testing import TestCase, assert_equal, assert_almost_equal
+from scipy.lib._version import NumpyVersion
from scipy.special import logit, expit
@@ -15,7 +16,7 @@ def check_logit_out(self, dtype, expected):
finally:
np.seterr(**olderr)
- if np.__version__ >= '1.6':
+ if NumpyVersion(np.__version__) >= '1.6.0':
assert_almost_equal(actual, expected)
else:
assert_almost_equal(actual[1:-1], expected[1:-1])
View
6 scipy/special/tests/test_orthogonal_eval.py
@@ -1,18 +1,18 @@
from __future__ import division, print_function, absolute_import
-from distutils.version import LooseVersion
import sys
import numpy as np
from numpy.testing import assert_, assert_allclose, dec
import scipy.special.orthogonal as orth
+from scipy.lib._version import NumpyVersion
from scipy.special._testutils import FuncData
+
# Early Numpy versions have bugs in ufunc keyword argument parsing
numpy_version_requirement = dec.skipif(
- LooseVersion(np.version.version) < LooseVersion('1.6')
- and sys.version_info[0] >= 3,
+ NumpyVersion(np.__version__) < '1.6.0' and sys.version_info[0] >= 3,
"Bug in Numpy < 1.6 on Python 3")
View
6 scipy/stats/tests/common_tests.py
@@ -5,10 +5,12 @@
import numpy as np
import numpy.testing as npt
-from distutils.version import LooseVersion
+
+from scipy.lib._version import NumpyVersion
from scipy import stats
-NUMPY_BELOW_1_7 = LooseVersion(np.version.version) < LooseVersion('1.7')
+
+NUMPY_BELOW_1_7 = NumpyVersion(np.__version__) < '1.7.0'
def check_normalization(distfn, args, distname):
View
7 scipy/stats/tests/test_distributions.py
@@ -15,6 +15,7 @@
import numpy
import numpy as np
from numpy import typecodes, array
+from scipy.lib._version import NumpyVersion
from scipy import special
import scipy.stats as stats
from scipy.stats.distributions import argsreduce
@@ -553,7 +554,8 @@ def test_stats2(self):
class TestInvGamma(TestCase):
- @dec.skipif(np.__version__ < '1.7', "assert_* funcs broken with inf/nan")
+ @dec.skipif(NumpyVersion(np.__version__) < '1.7.0',
+ "assert_* funcs broken with inf/nan")
def test_invgamma_inf_gh_1866(self):
# invgamma's moments are only finite for a>n
# specific numbers checked w/ boost 1.54
@@ -808,7 +810,8 @@ def test_entropy_2d(self):
assert_array_almost_equal(stats.entropy(pk, qk),
[0.1933259, 0.18609809])
- @dec.skipif(np.__version__ < '1.7', "assert_* funcs broken with inf/nan")
+ @dec.skipif(NumpyVersion(np.__version__) < '1.7.0',
+ "assert_* funcs broken with inf/nan")
def test_entropy_2d_zero(self):
pk = [[0.1, 0.2], [0.6, 0.3], [0.3, 0.5]]
qk = [[0.0, 0.1], [0.3, 0.6], [0.5, 0.3]]
Something went wrong with that request. Please try again.