Merge pull request #521 from campagnola/fix-qstring
Relax type checking on string parameters
This commit is contained in:
commit
dd672c41b6
@ -200,7 +200,7 @@ def mkColor(*args):
|
||||
try:
|
||||
return Colors[c]
|
||||
except KeyError:
|
||||
raise Exception('No color named "%s"' % c)
|
||||
raise ValueError('No color named "%s"' % c)
|
||||
if len(c) == 3:
|
||||
r = int(c[0]*2, 16)
|
||||
g = int(c[1]*2, 16)
|
||||
@ -235,18 +235,18 @@ def mkColor(*args):
|
||||
elif len(args[0]) == 2:
|
||||
return intColor(*args[0])
|
||||
else:
|
||||
raise Exception(err)
|
||||
raise TypeError(err)
|
||||
elif type(args[0]) == int:
|
||||
return intColor(args[0])
|
||||
else:
|
||||
raise Exception(err)
|
||||
raise TypeError(err)
|
||||
elif len(args) == 3:
|
||||
(r, g, b) = args
|
||||
a = 255
|
||||
elif len(args) == 4:
|
||||
(r, g, b, a) = args
|
||||
else:
|
||||
raise Exception(err)
|
||||
raise TypeError(err)
|
||||
|
||||
args = [r,g,b,a]
|
||||
args = [0 if np.isnan(a) or np.isinf(a) else a for a in args]
|
||||
@ -404,22 +404,39 @@ def makeArrowPath(headLen=20, tipAngle=20, tailLen=20, tailWidth=3, baseAngle=0)
|
||||
|
||||
|
||||
def eq(a, b):
|
||||
"""The great missing equivalence function: Guaranteed evaluation to a single bool value."""
|
||||
"""The great missing equivalence function: Guaranteed evaluation to a single bool value.
|
||||
|
||||
This function has some important differences from the == operator:
|
||||
|
||||
1. Returns True if a IS b, even if a==b still evaluates to False, such as with nan values.
|
||||
2. Tests for equivalence using ==, but silently ignores some common exceptions that can occur
|
||||
(AtrtibuteError, ValueError).
|
||||
3. When comparing arrays, returns False if the array shapes are not the same.
|
||||
4. When comparing arrays of the same shape, returns True only if all elements are equal (whereas
|
||||
the == operator would return a boolean array).
|
||||
"""
|
||||
if a is b:
|
||||
return True
|
||||
|
||||
try:
|
||||
with warnings.catch_warnings(module=np): # ignore numpy futurewarning (numpy v. 1.10)
|
||||
e = a==b
|
||||
except ValueError:
|
||||
return False
|
||||
except AttributeError:
|
||||
try:
|
||||
# Sometimes running catch_warnings(module=np) generates AttributeError ???
|
||||
catcher = warnings.catch_warnings(module=np) # ignore numpy futurewarning (numpy v. 1.10)
|
||||
catcher.__enter__()
|
||||
except Exception:
|
||||
catcher = None
|
||||
e = a==b
|
||||
except (ValueError, AttributeError):
|
||||
return False
|
||||
except:
|
||||
print('failed to evaluate equivalence for:')
|
||||
print(" a:", str(type(a)), str(a))
|
||||
print(" b:", str(type(b)), str(b))
|
||||
raise
|
||||
finally:
|
||||
if catcher is not None:
|
||||
catcher.__exit__(None, None, None)
|
||||
|
||||
t = type(e)
|
||||
if t is bool:
|
||||
return e
|
||||
|
@ -326,25 +326,12 @@ class SimpleParameter(Parameter):
|
||||
'int': int,
|
||||
'float': float,
|
||||
'bool': bool,
|
||||
'str': self._interpStr,
|
||||
'str': asUnicode,
|
||||
'color': self._interpColor,
|
||||
'colormap': self._interpColormap,
|
||||
}[self.opts['type']]
|
||||
return fn(v)
|
||||
|
||||
def _interpStr(self, v):
|
||||
if sys.version[0] == '2':
|
||||
if isinstance(v, QtCore.QString):
|
||||
v = unicode(v)
|
||||
elif not isinstance(v, basestring):
|
||||
raise TypeError("Cannot set str parmeter from object %r" % v)
|
||||
else:
|
||||
if isinstance(v, QtCore.QString):
|
||||
v = str(v)
|
||||
elif not isinstance(v, str):
|
||||
raise TypeError("Cannot set str parmeter from object %r" % v)
|
||||
return v
|
||||
|
||||
def _interpColor(self, v):
|
||||
return fn.mkColor(v)
|
||||
|
||||
|
@ -1,7 +1,19 @@
|
||||
# ~*~ coding: utf8 ~*~
|
||||
import sys
|
||||
import pytest
|
||||
from pyqtgraph.Qt import QtGui, QtCore
|
||||
import pyqtgraph.parametertree as pt
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.python2_3 import asUnicode
|
||||
from pyqtgraph.functions import eq
|
||||
import numpy as np
|
||||
|
||||
app = pg.mkQApp()
|
||||
|
||||
def _getWidget(param):
|
||||
return list(param.items.keys())[0].widget
|
||||
|
||||
|
||||
def test_opts():
|
||||
paramSpec = [
|
||||
dict(name='bool', type='bool', readonly=True),
|
||||
@ -12,7 +24,111 @@ def test_opts():
|
||||
tree = pt.ParameterTree()
|
||||
tree.setParameters(param)
|
||||
|
||||
assert list(param.param('bool').items.keys())[0].widget.isEnabled() is False
|
||||
assert list(param.param('color').items.keys())[0].widget.isEnabled() is False
|
||||
assert _getWidget(param.param('bool')).isEnabled() is False
|
||||
assert _getWidget(param.param('bool')).isEnabled() is False
|
||||
|
||||
|
||||
def test_types():
|
||||
paramSpec = [
|
||||
dict(name='float', type='float'),
|
||||
dict(name='int', type='int'),
|
||||
dict(name='str', type='str'),
|
||||
dict(name='list', type='list', values=['x','y','z']),
|
||||
dict(name='dict', type='list', values={'x':1, 'y':3, 'z':7}),
|
||||
dict(name='bool', type='bool'),
|
||||
dict(name='color', type='color'),
|
||||
]
|
||||
|
||||
param = pt.Parameter.create(name='params', type='group', children=paramSpec)
|
||||
tree = pt.ParameterTree()
|
||||
tree.setParameters(param)
|
||||
|
||||
all_objs = {
|
||||
'int0': 0, 'int':7, 'float': -0.35, 'bigfloat': 1e129, 'npfloat': np.float(5),
|
||||
'npint': np.int(5),'npinf': np.inf, 'npnan': np.nan, 'bool': True,
|
||||
'complex': 5+3j, 'str': 'xxx', 'unicode': asUnicode('µ'),
|
||||
'list': [1,2,3], 'dict': {'1': 2}, 'color': pg.mkColor('k'),
|
||||
'brush': pg.mkBrush('k'), 'pen': pg.mkPen('k'), 'none': None
|
||||
}
|
||||
if hasattr(QtCore, 'QString'):
|
||||
all_objs['qstring'] = QtCore.QString('xxxµ')
|
||||
|
||||
# float
|
||||
types = ['int0', 'int', 'float', 'bigfloat', 'npfloat', 'npint', 'npinf', 'npnan', 'bool']
|
||||
check_param_types(param.child('float'), float, float, 0.0, all_objs, types)
|
||||
|
||||
# int
|
||||
types = ['int0', 'int', 'float', 'bigfloat', 'npfloat', 'npint', 'bool']
|
||||
inttyps = int if sys.version[0] >= '3' else (int, long)
|
||||
check_param_types(param.child('int'), inttyps, int, 0, all_objs, types)
|
||||
|
||||
# str (should be able to make a string out of any type)
|
||||
types = all_objs.keys()
|
||||
strtyp = str if sys.version[0] >= '3' else unicode
|
||||
check_param_types(param.child('str'), strtyp, asUnicode, '', all_objs, types)
|
||||
|
||||
# bool (should be able to make a boolean out of any type?)
|
||||
types = all_objs.keys()
|
||||
check_param_types(param.child('bool'), bool, bool, False, all_objs, types)
|
||||
|
||||
# color
|
||||
types = ['color', 'int0', 'int', 'float', 'npfloat', 'npint', 'list']
|
||||
init = QtGui.QColor(128, 128, 128, 255)
|
||||
check_param_types(param.child('color'), QtGui.QColor, pg.mkColor, init, all_objs, types)
|
||||
|
||||
|
||||
def check_param_types(param, types, map_func, init, objs, keys):
|
||||
"""Check that parameter setValue() accepts or rejects the correct types and
|
||||
that value() returns the correct type.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
param : Parameter instance
|
||||
types : type or tuple of types
|
||||
The allowed types for this parameter to return from value().
|
||||
map_func : function
|
||||
Converts an input value to the expected output value.
|
||||
init : object
|
||||
The expected initial value of the parameter
|
||||
objs : dict
|
||||
Contains a variety of objects that will be tested as arguments to
|
||||
param.setValue().
|
||||
keys : list
|
||||
The list of keys indicating the valid objects in *objs*. When
|
||||
param.setValue() is teasted with each value from *objs*, we expect
|
||||
an exception to be raised if the associated key is not in *keys*.
|
||||
"""
|
||||
val = param.value()
|
||||
if not isinstance(types, tuple):
|
||||
types = (types,)
|
||||
assert val == init and type(val) in types
|
||||
|
||||
# test valid input types
|
||||
good_inputs = [objs[k] for k in keys if k in objs]
|
||||
good_outputs = map(map_func, good_inputs)
|
||||
for x,y in zip(good_inputs, good_outputs):
|
||||
param.setValue(x)
|
||||
val = param.value()
|
||||
if not (eq(val, y) and type(val) in types):
|
||||
raise Exception("Setting parameter %s with value %r should have resulted in %r (types: %r), "
|
||||
"but resulted in %r (type: %r) instead." % (param, x, y, types, val, type(val)))
|
||||
|
||||
# test invalid input types
|
||||
for k,v in objs.items():
|
||||
if k in keys:
|
||||
continue
|
||||
try:
|
||||
param.setValue(v)
|
||||
except (TypeError, ValueError, OverflowError):
|
||||
continue
|
||||
except Exception as exc:
|
||||
raise Exception("Setting %s parameter value to %r raised %r." % (param, v, exc))
|
||||
|
||||
raise Exception("Setting %s parameter value to %r should have raised an exception." % (param, v))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import pyqtgraph as pg
|
||||
import numpy as np
|
||||
import sys
|
||||
from numpy.testing import assert_array_almost_equal, assert_almost_equal
|
||||
import pytest
|
||||
|
||||
@ -294,5 +295,67 @@ def test_makeARGB():
|
||||
pg.makeARGB(np.zeros((2,2,3), dtype='float'), levels=np.zeros([3, 2, 2]))
|
||||
|
||||
|
||||
def test_eq():
|
||||
eq = pg.functions.eq
|
||||
|
||||
zeros = [0, 0.0, np.float(0), np.int(0)]
|
||||
if sys.version[0] < '3':
|
||||
zeros.append(long(0))
|
||||
for i,x in enumerate(zeros):
|
||||
for y in zeros[i:]:
|
||||
assert eq(x, y)
|
||||
assert eq(y, x)
|
||||
|
||||
assert eq(np.nan, np.nan)
|
||||
|
||||
# test
|
||||
class NotEq(object):
|
||||
def __eq__(self, x):
|
||||
return False
|
||||
|
||||
noteq = NotEq()
|
||||
assert eq(noteq, noteq) # passes because they are the same object
|
||||
assert not eq(noteq, NotEq())
|
||||
|
||||
|
||||
# Should be able to test for equivalence even if the test raises certain
|
||||
# exceptions
|
||||
class NoEq(object):
|
||||
def __init__(self, err):
|
||||
self.err = err
|
||||
def __eq__(self, x):
|
||||
raise self.err
|
||||
|
||||
noeq1 = NoEq(AttributeError())
|
||||
noeq2 = NoEq(ValueError())
|
||||
noeq3 = NoEq(Exception())
|
||||
|
||||
assert eq(noeq1, noeq1)
|
||||
assert not eq(noeq1, noeq2)
|
||||
assert not eq(noeq2, noeq1)
|
||||
with pytest.raises(Exception):
|
||||
eq(noeq3, noeq2)
|
||||
|
||||
# test array equivalence
|
||||
# note that numpy has a weird behavior here--np.all() always returns True
|
||||
# if one of the arrays has size=0; eq() will only return True if both arrays
|
||||
# have the same shape.
|
||||
a1 = np.zeros((10, 20)).astype('float')
|
||||
a2 = a1 + 1
|
||||
a3 = a2.astype('int')
|
||||
a4 = np.empty((0, 20))
|
||||
assert not eq(a1, a2)
|
||||
assert not eq(a1, a3)
|
||||
assert not eq(a1, a4)
|
||||
|
||||
assert eq(a2, a3)
|
||||
assert not eq(a2, a4)
|
||||
|
||||
assert not eq(a3, a4)
|
||||
|
||||
assert eq(a4, a4.copy())
|
||||
assert not eq(a4, a4.T)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_interpolateArray()
|
Loading…
Reference in New Issue
Block a user