split BadValue into WrongType and RangeError

in order to match SECoP specification

fixes #4668

Change-Id: Ica73a8171536ccc324cf8db915347a6263c2d736
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30625
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
2023-03-08 14:13:40 +01:00
parent 20506a8393
commit 0560668b91
9 changed files with 231 additions and 199 deletions

View File

@@ -573,7 +573,7 @@ class SecopClient(ProxyClient):
argument = datatype.export_value(argument)
else:
if argument is not None:
raise frappy.errors.BadValueError('command has no argument')
raise frappy.errors.WrongTypeError('command has no argument')
# pylint: disable=unsubscriptable-object
data, qualifiers = self.request(COMMANDREQUEST, self.identifier[module, command], argument)[2]
datatype = self.modules[module]['commands'][command]['datatype'].result

View File

@@ -28,7 +28,7 @@
import sys
from base64 import b64decode, b64encode
from frappy.errors import BadValueError, \
from frappy.errors import WrongTypeError, RangeError, \
ConfigError, ProgrammingError, ProtocolError
from frappy.lib import clamp, generalConfig
from frappy.lib.enum import Enum
@@ -45,7 +45,7 @@ UNLIMITED = 1 << 64 # internal limit for integers, is probably high enough for
Parser = Parser()
class DiscouragedConversion(BadValueError):
class DiscouragedConversion(WrongTypeError):
"""the discouraged conversion string - > float happened"""
log_message = True
@@ -230,7 +230,7 @@ class FloatRange(HasUnit, DataType):
try:
value = float(value)
except Exception:
raise BadValueError('can not convert %s to a float' % shortrepr(value)) from None
raise WrongTypeError('can not convert %s to a float' % shortrepr(value)) from None
if not generalConfig.lazy_number_validation:
raise DiscouragedConversion('automatic string to float conversion no longer supported') from None
@@ -245,8 +245,8 @@ class FloatRange(HasUnit, DataType):
if self.min - prec <= value <= self.max + prec:
# silently clamp when outside by not more than prec
return clamp(self.min, value, self.max)
raise BadValueError('%.14g must be between %d and %d' %
(value, self.min, self.max))
raise RangeError('%.14g must be between %d and %d' %
(value, self.min, self.max))
def __repr__(self):
hints = self.get_info()
@@ -277,7 +277,7 @@ class FloatRange(HasUnit, DataType):
def compatible(self, other):
if not isinstance(other, (FloatRange, ScaledInteger)):
raise BadValueError('incompatible datatypes')
raise WrongTypeError('incompatible datatypes')
# avoid infinity
other.validate(max(sys.float_info.min, self.min))
other.validate(min(sys.float_info.max, self.max))
@@ -316,11 +316,11 @@ class IntRange(DataType):
fvalue = float(value)
value = int(value)
except Exception:
raise BadValueError('can not convert %s to an int' % shortrepr(value)) from None
raise WrongTypeError('can not convert %s to an int' % shortrepr(value)) from None
if not generalConfig.lazy_number_validation:
raise DiscouragedConversion('automatic string to float conversion no longer supported') from None
if round(fvalue) != fvalue:
raise BadValueError('%r should be an int')
raise WrongTypeError('%r should be an int')
return value
def validate(self, value, previous=None):
@@ -329,8 +329,8 @@ class IntRange(DataType):
# check the limits
if self.min <= value <= self.max:
return value
raise BadValueError('%r must be between %d and %d' %
(value, self.min, self.max))
raise RangeError('%r must be between %d and %d' %
(value, self.min, self.max))
def __repr__(self):
args = (self.min, self.max)
@@ -364,7 +364,7 @@ class IntRange(DataType):
# the following loop will not cycle more than the number of Enum elements
for i in range(self.min, self.max + 1):
other(i)
raise BadValueError('incompatible datatypes')
raise WrongTypeError('incompatible datatypes')
class ScaledInteger(HasUnit, DataType):
@@ -388,7 +388,10 @@ class ScaledInteger(HasUnit, DataType):
def __init__(self, scale, minval=None, maxval=None, absolute_resolution=None, **kwds):
super().__init__()
scale = float(scale)
try:
scale = float(scale)
except (ValueError, TypeError) as e:
raise ProgrammingError(e) from None
if absolute_resolution is None:
absolute_resolution = scale
self.set_properties(
@@ -404,7 +407,7 @@ class ScaledInteger(HasUnit, DataType):
# check values
if '%' not in self.fmtstr:
raise BadValueError('Invalid fmtstr!')
raise ConfigError('Invalid fmtstr!')
# Remark: Datatype.copy() will round min, max to a multiple of self.scale
# this should be o.k.
@@ -434,7 +437,7 @@ class ScaledInteger(HasUnit, DataType):
try:
value = float(value)
except Exception:
raise BadValueError('can not convert %s to float' % shortrepr(value)) from None
raise WrongTypeError('can not convert %s to float' % shortrepr(value)) from None
if not generalConfig.lazy_number_validation:
raise DiscouragedConversion('automatic string to float conversion no longer supported') from None
intval = int(round(value / self.scale))
@@ -446,8 +449,8 @@ class ScaledInteger(HasUnit, DataType):
if self.min - self.scale < value < self.max + self.scale:
# silently clamp when outside by not more than self.scale
return clamp(self(self.min), result, self(self.max))
raise BadValueError('%.14g must be between between %g and %g' %
(value, self.min, self.max))
raise RangeError('%.14g must be between between %g and %g' %
(value, self.min, self.max))
def __repr__(self):
hints = self.get_info(scale=float('%g' % self.scale),
@@ -476,7 +479,7 @@ class ScaledInteger(HasUnit, DataType):
def compatible(self, other):
if not isinstance(other, (FloatRange, ScaledInteger)):
raise BadValueError('incompatible datatypes')
raise WrongTypeError('incompatible datatypes')
other.validate(self.min)
other.validate(self.max)
@@ -522,7 +525,9 @@ class EnumType(DataType):
try:
return self._enum[value]
except (KeyError, TypeError): # TypeError will be raised when value is not hashable
raise BadValueError('%s is not a member of enum %r' % (shortrepr(value), self._enum)) from None
if isinstance(value, (int, str)):
raise RangeError('%s is not a member of enum %r' % (shortrepr(value), self._enum)) from None
raise WrongTypeError('%s must be either int or str for an enum value' % (shortrepr(value))) from None
def from_string(self, text):
return self(text)
@@ -570,13 +575,13 @@ class BLOBType(DataType):
def __call__(self, value):
"""accepts bytes only"""
if not isinstance(value, bytes):
raise BadValueError('%s must be of type bytes' % shortrepr(value))
raise WrongTypeError('%s must be of type bytes' % shortrepr(value))
size = len(value)
if size < self.minbytes:
raise BadValueError(
raise RangeError(
'%r must be at least %d bytes long!' % (value, self.minbytes))
if size > self.maxbytes:
raise BadValueError(
raise RangeError(
'%r must be at most %d bytes long!' % (value, self.maxbytes))
return value
@@ -599,9 +604,9 @@ class BLOBType(DataType):
def compatible(self, other):
try:
if self.minbytes < other.minbytes or self.maxbytes > other.maxbytes:
raise BadValueError('incompatible datatypes')
raise RangeError('incompatible datatypes')
except AttributeError:
raise BadValueError('incompatible datatypes') from None
raise WrongTypeError('incompatible datatypes') from None
class StringType(DataType):
@@ -635,21 +640,21 @@ class StringType(DataType):
def __call__(self, value):
"""accepts strings only"""
if not isinstance(value, str):
raise BadValueError('%s has the wrong type!' % shortrepr(value))
raise WrongTypeError('%s has the wrong type!' % shortrepr(value))
if not self.isUTF8:
try:
value.encode('ascii')
except UnicodeEncodeError:
raise BadValueError('%s contains non-ascii character!' % shortrepr(value)) from None
raise RangeError('%s contains non-ascii character!' % shortrepr(value)) from None
size = len(value)
if size < self.minchars:
raise BadValueError(
raise RangeError(
'%s must be at least %d chars long!' % (shortrepr(value), self.minchars))
if size > self.maxchars:
raise BadValueError(
raise RangeError(
'%s must be at most %d chars long!' % (shortrepr(value), self.maxchars))
if '\0' in value:
raise BadValueError(
raise RangeError(
'Strings are not allowed to embed a \\0! Use a Blob instead!')
return value
@@ -672,9 +677,9 @@ class StringType(DataType):
try:
if self.minchars < other.minchars or self.maxchars > other.maxchars or \
self.isUTF8 > other.isUTF8:
raise BadValueError('incompatible datatypes')
raise RangeError('incompatible datatypes')
except AttributeError:
raise BadValueError('incompatible datatypes') from None
raise WrongTypeError('incompatible datatypes') from None
# TextType is a special StringType intended for longer texts (i.e. embedding \n),
@@ -715,7 +720,7 @@ class BoolType(DataType):
return False
if value in [1, '1', 'True', 'true', 'yes', 'on', True]:
return True
raise BadValueError('%s is not a boolean value!' % shortrepr(value))
raise WrongTypeError('%s is not a boolean value!' % shortrepr(value))
def export_value(self, value):
"""returns a python object fit for serialisation"""
@@ -757,7 +762,7 @@ class ArrayOf(DataType):
def __init__(self, members, minlen=0, maxlen=None):
super().__init__()
if not isinstance(members, DataType):
raise BadValueError(
raise ProgrammingError(
'ArrayOf only works with a DataType as first argument!')
# one argument -> exactly that size
# argument default to 100
@@ -800,15 +805,15 @@ class ArrayOf(DataType):
try:
# check number of elements
if self.minlen is not None and len(value) < self.minlen:
raise BadValueError(
raise RangeError(
'array too small, needs at least %d elements!' %
self.minlen)
if self.maxlen is not None and len(value) > self.maxlen:
raise BadValueError(
raise RangeError(
'array too big, holds at most %d elements!' % self.maxlen)
except TypeError:
raise BadValueError('%s can not be converted to ArrayOf DataType!'
% type(value).__name__) from None
raise WrongTypeError('%s can not be converted to ArrayOf DataType!'
% type(value).__name__) from None
def __call__(self, value):
"""accepts any sequence, converts to tuple (immutable!)"""
@@ -816,7 +821,8 @@ class ArrayOf(DataType):
try:
return tuple(self.members(v) for v in value)
except Exception as e:
raise BadValueError('can not convert some array elements') from e
errcls = RangeError if isinstance(e, RangeError) else WrongTypeError
raise errcls('can not convert some array elements') from e
def validate(self, value, previous=None):
self.check_type(value)
@@ -825,7 +831,8 @@ class ArrayOf(DataType):
return tuple(self.members.validate(v, p) for v, p in zip(value, previous))
return tuple(self.members.validate(v) for v in value)
except Exception as e:
raise BadValueError('some array elements are invalid') from e
errcls = RangeError if isinstance(e, RangeError) else WrongTypeError
raise errcls('some array elements are invalid') from e
def export_value(self, value):
"""returns a python object fit for serialisation"""
@@ -852,10 +859,10 @@ class ArrayOf(DataType):
def compatible(self, other):
try:
if self.minlen < other.minlen or self.maxlen > other.maxlen:
raise BadValueError('incompatible datatypes')
raise RangeError('incompatible datatypes')
self.members.compatible(other.members)
except AttributeError:
raise BadValueError('incompatible datatypes') from None
raise WrongTypeError('incompatible datatypes') from None
def set_main_unit(self, unit):
self.members.set_main_unit(unit)
@@ -869,10 +876,10 @@ class TupleOf(DataType):
def __init__(self, *members):
super().__init__()
if not members:
raise BadValueError('Empty tuples are not allowed!')
raise ProgrammingError('Empty tuples are not allowed!')
for subtype in members:
if not isinstance(subtype, DataType):
raise BadValueError(
raise ProgrammingError(
'TupleOf only works with DataType objs as arguments!')
self.members = members
self.default = tuple(el.default for el in members)
@@ -890,11 +897,11 @@ class TupleOf(DataType):
def check_type(self, value):
try:
if len(value) != len(self.members):
raise BadValueError(
raise WrongTypeError(
'tuple needs %d elements' % len(self.members))
except TypeError:
raise BadValueError('%s can not be converted to TupleOf DataType!'
% type(value).__name__) from None
raise WrongTypeError('%s can not be converted to TupleOf DataType!'
% type(value).__name__) from None
def __call__(self, value):
"""accepts any sequence, converts to tuple"""
@@ -902,7 +909,8 @@ class TupleOf(DataType):
try:
return tuple(sub(elem) for sub, elem in zip(self.members, value))
except Exception as e:
raise BadValueError('can not convert some tuple elements') from e
errcls = RangeError if isinstance(e, RangeError) else WrongTypeError
raise errcls('can not convert some tuple elements') from e
def validate(self, value, previous=None):
self.check_type(value)
@@ -911,7 +919,8 @@ class TupleOf(DataType):
return tuple(sub.validate(elem) for sub, elem in zip(self.members, value))
return tuple(sub.validate(v, p) for sub, v, p in zip(self.members, value, previous))
except Exception as e:
raise BadValueError('some tuple elements are invalid') from e
errcls = RangeError if isinstance(e, RangeError) else WrongTypeError
raise errcls('some tuple elements are invalid') from e
def export_value(self, value):
"""returns a python object fit for serialisation"""
@@ -933,9 +942,9 @@ class TupleOf(DataType):
def compatible(self, other):
if not isinstance(other, TupleOf):
raise BadValueError('incompatible datatypes')
raise WrongTypeError('incompatible datatypes')
if len(self.members) != len(other.members):
raise BadValueError('incompatible datatypes')
raise WrongTypeError('incompatible datatypes')
for a, b in zip(self.members, other.members):
a.compatible(b)
@@ -962,7 +971,7 @@ class StructOf(DataType):
super().__init__()
self.members = members
if not members:
raise BadValueError('Empty structs are not allowed!')
raise ProgrammingError('Empty structs are not allowed!')
self.optional = list(members if optional is None else optional)
for name, subtype in list(members.items()):
if not isinstance(subtype, DataType):
@@ -994,27 +1003,28 @@ class StructOf(DataType):
"""accepts any mapping, returns an immutable dict"""
try:
if set(dict(value)) != set(self.members):
raise BadValueError('member names do not match') from None
raise WrongTypeError('member names do not match') from None
except TypeError:
raise BadValueError('%s can not be converted a StructOf'
% type(value).__name__) from None
raise WrongTypeError('%s can not be converted a StructOf'
% type(value).__name__) from None
try:
return ImmutableDict((str(k), self.members[k](v))
for k, v in list(value.items()))
except Exception as e:
raise BadValueError('can not convert some struct element') from e
errcls = RangeError if isinstance(e, RangeError) else WrongTypeError
raise errcls('can not convert some struct element') from e
def validate(self, value, previous=None):
try:
superfluous = set(dict(value)) - set(self.members)
except TypeError:
raise BadValueError('%s can not be converted a StructOf'
% type(value).__name__) from None
raise WrongTypeError('%s can not be converted a StructOf'
% type(value).__name__) from None
if superfluous - set(self.optional):
raise BadValueError('struct contains superfluous members: %s' % ', '.join(superfluous))
raise WrongTypeError('struct contains superfluous members: %s' % ', '.join(superfluous))
missing = set(self.members) - set(value) - set(self.optional)
if missing:
raise BadValueError('missing struct elements: %s' % ', '.join(missing))
raise WrongTypeError('missing struct elements: %s' % ', '.join(missing))
try:
if previous is None:
return ImmutableDict((str(k), self.members[k].validate(v))
@@ -1023,7 +1033,8 @@ class StructOf(DataType):
result.update(((k, self.members[k].validate(v, previous[k])) for k, v in value.items()))
return ImmutableDict(result)
except Exception as e:
raise BadValueError('some struct elements are invalid') from e
errcls = RangeError if isinstance(e, RangeError) else WrongTypeError
raise errcls('some struct elements are invalid') from e
def export_value(self, value):
"""returns a python object fit for serialisation"""
@@ -1051,9 +1062,9 @@ class StructOf(DataType):
m.compatible(other.members[k])
mandatory.discard(k)
if mandatory:
raise BadValueError('incompatible datatypes')
raise WrongTypeError('incompatible datatypes')
except (AttributeError, TypeError, KeyError):
raise BadValueError('incompatible datatypes') from None
raise WrongTypeError('incompatible datatypes') from None
def set_main_unit(self, unit):
for member in self.members.values():
@@ -1071,10 +1082,10 @@ class CommandType(DataType):
super().__init__()
if argument is not None:
if not isinstance(argument, DataType):
raise BadValueError('CommandType: Argument type must be a DataType!')
raise ProgrammingError('CommandType: Argument type must be a DataType!')
if result is not None:
if not isinstance(result, DataType):
raise BadValueError('CommandType: Result type must be a DataType!')
raise ProgrammingError('CommandType: Result type must be a DataType!')
self.argument = argument
self.result = result
@@ -1118,7 +1129,7 @@ class CommandType(DataType):
if self.result != other.result: # not both are None
other.result.compatible(self.result)
except AttributeError:
raise BadValueError('incompatible datatypes') from None
raise WrongTypeError('incompatible datatypes') from None
# internally used datatypes (i.e. only for programming the SEC-node)
@@ -1204,7 +1215,7 @@ class OrType(DataType):
return t(value)
except Exception:
pass
raise BadValueError("Invalid Value, must conform to one of %s" % (', '.join((str(t) for t in self.types))))
raise WrongTypeError("Invalid Value, must conform to one of %s" % (', '.join((str(t) for t in self.types))))
Int8 = IntRange(-(1 << 7), (1 << 7) - 1)
@@ -1226,7 +1237,7 @@ class LimitsType(TupleOf):
"""accepts an ordered tuple of numeric member types"""
limits = TupleOf.validate(self, value)
if limits[1] < limits[0]:
raise BadValueError('Maximum Value %s must be greater than minimum value %s!' % (limits[1], limits[0]))
raise RangeError('Maximum Value %s must be greater than minimum value %s!' % (limits[1], limits[0]))
return limits
@@ -1294,9 +1305,9 @@ def get_datatype(json, pname=''):
kwargs = json.copy()
base = kwargs.pop('type')
except (TypeError, KeyError, AttributeError):
raise BadValueError('a data descriptor must be a dict containing a "type" key, not %r' % json) from None
raise WrongTypeError('a data descriptor must be a dict containing a "type" key, not %r' % json) from None
try:
return DATATYPES[base](pname=pname, **kwargs)
except Exception as e:
raise BadValueError('invalid data descriptor: %r (%s)' % (json, str(e))) from None
raise WrongTypeError('invalid data descriptor: %r (%s)' % (json, str(e))) from None

View File

@@ -92,7 +92,15 @@ class ReadOnlyError(SECoPError):
pass
class BadValueError(ValueError, SECoPError):
class BadValueError(SECoPError):
"""do not raise, but might used for instance checks (WrongTypeError, RangeError)"""
class RangeError(ValueError, BadValueError):
name = 'RangeError'
class WrongTypeError(TypeError, BadValueError):
pass
@@ -154,23 +162,28 @@ def secop_error(exception):
return InternalError(repr(exception))
EXCEPTIONS = dict(
NoSuchModule=NoSuchModuleError,
NoSuchParameter=NoSuchParameterError,
NoSuchCommand=NoSuchCommandError,
CommandFailed=CommandFailedError,
CommandRunning=CommandRunningError,
ReadOnly=ReadOnlyError,
BadValue=BadValueError,
CommunicationFailed=CommunicationFailedError,
HardwareError=HardwareError,
IsBusy=IsBusyError,
IsError=IsErrorError,
Disabled=DisabledError,
EXCEPTIONS = {e().name: e for e in [
NoSuchModuleError,
NoSuchParameterError,
NoSuchCommandError,
CommandFailedError,
CommandRunningError,
ReadOnlyError,
BadValueError,
RangeError,
WrongTypeError,
CommunicationFailedError,
HardwareError,
IsBusyError,
IsErrorError,
DisabledError,
ProtocolError,
NotImplementedError,
InternalError]}
# TODO: check if these are really needed:
EXCEPTIONS.update(
SyntaxError=ProtocolError,
NotImplemented=NotImplementedError,
ProtocolError=ProtocolError,
InternalError=InternalError,
# internal short versions (candidates for spec)
Protocol=ProtocolError,
Internal=InternalError,

View File

@@ -27,7 +27,7 @@ from frappy.datatypes import ArrayOf, BoolType, EnumType, \
FloatRange, StringType, StructOf, TupleOf
from frappy.core import Command, Done, Drivable, Feature, \
Parameter, Property, PersistentParam, Readable
from frappy.errors import BadValueError, ConfigError
from frappy.errors import RangeError, ConfigError
from frappy.lib import clamp
@@ -96,8 +96,8 @@ class HasLimits(Feature):
min_, max_ = self.apply_offset(-1, *self.abslimits)
if not min_ <= value[0] <= value[1] <= max_:
if value[0] > value[1]:
raise BadValueError('invalid interval: %r' % value)
raise BadValueError('limits not within abs limits [%g, %g]' % (min_, max_))
raise RangeError('invalid interval: %r' % value)
raise RangeError('limits not within abs limits [%g, %g]' % (min_, max_))
self.limits = value
self.saveParameters()
return Done
@@ -106,7 +106,7 @@ class HasLimits(Feature):
"""check if value is valid"""
min_, max_ = self.limits
if not min_ <= value <= max_:
raise BadValueError('limits violation: %g outside [%g, %g]' % (value, min_, max_))
raise RangeError('limits violation: %g outside [%g, %g]' % (value, min_, max_))
# --- not used, not tested yet ---

View File

@@ -28,7 +28,7 @@ import inspect
from frappy.datatypes import BoolType, CommandType, DataType, \
DataTypeType, EnumType, NoneOr, OrType, \
StringType, StructOf, TextType, TupleOf, ValueType
from frappy.errors import BadValueError, ProgrammingError
from frappy.errors import BadValueError, WrongTypeError, ProgrammingError
from frappy.properties import HasProperties, Property
from frappy.lib import generalConfig
@@ -304,7 +304,7 @@ class Parameter(Accessible):
except KeyError:
raise ProgrammingError('cannot set %s on parameter with datatype %s'
% (key, type(self.datatype).__name__)) from None
except ValueError as e:
except BadValueError as e:
raise ProgrammingError('property %s: %s' % (key, str(e))) from None
def checkProperties(self):
@@ -489,7 +489,7 @@ class Command(Accessible):
res = func(argument)
else:
if argument is not None:
raise BadValueError('%s.%s takes no arguments' % (module_obj.__class__.__name__, self.name))
raise WrongTypeError('%s.%s takes no arguments' % (module_obj.__class__.__name__, self.name))
res = func()
if self.result:
return self.result(res)

View File

@@ -29,7 +29,7 @@ from frappy.core import BoolType, Command, EnumType, FloatRange, IntRange, \
HasIO, Parameter, Property, Drivable, PersistentMixin, PersistentParam, Done, \
IDLE, BUSY, ERROR
from frappy.io import BytesIO
from frappy.errors import CommunicationFailedError, HardwareError, BadValueError, IsBusyError
from frappy.errors import CommunicationFailedError, HardwareError, RangeError, IsBusyError
from frappy.rwhandler import ReadHandler, WriteHandler
from frappy.lib import formatStatusBits
@@ -345,8 +345,8 @@ class Motor(PersistentMixin, HasIO, Drivable):
abs(target - self.encoder) > self.move_limit + self.tolerance):
# pylint: disable=bad-string-format-type
# pylint wrongly does not recognise encoder as a descriptor
raise BadValueError('can not move more than %g deg (%g -> %g)' %
(self.move_limit, self.encoder, target))
raise RangeError('can not move more than %g deg (%g -> %g)' %
(self.move_limit, self.encoder, target))
diff = self.encoder - self.steppos
if self._need_reset:
if self.auto_reset:

View File

@@ -31,6 +31,7 @@ from frappy.datatypes import ArrayOf, BLOBType, BoolType, \
StringType, StructOf, TextType, TupleOf, get_datatype, \
DiscouragedConversion
from frappy.lib import generalConfig
from frappy.errors import WrongTypeError, RangeError, BadValueError
def copytest(dt):
@@ -54,15 +55,15 @@ def test_FloatRange():
copytest(dt)
assert dt.export_datatype() == {'type': 'double', 'min':-3.14, 'max':3.14}
with pytest.raises(ValueError):
with pytest.raises(RangeError):
dt.validate(9)
with pytest.raises(ValueError):
with pytest.raises(RangeError):
dt.validate(-9)
dt(9) # convert, but do not check limits
dt(-9) # convert, but do not check limits
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt('XX')
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt([19, 'X'])
dt(1)
dt(0)
@@ -107,19 +108,19 @@ def test_IntRange():
copytest(dt)
assert dt.export_datatype() == {'type': 'int', 'min':-3, 'max':3}
with pytest.raises(ValueError):
with pytest.raises(RangeError):
dt.validate(9)
with pytest.raises(ValueError):
with pytest.raises(RangeError):
dt.validate(-9)
dt(9) # convert, but do not check limits
dt(-9) # convert, but do not check limits
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt('XX')
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt([19, 'X'])
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt(1.3)
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt('1.3')
dt(1)
dt(0)
@@ -145,19 +146,19 @@ def test_ScaledInteger():
# serialisation of datatype contains limits on the 'integer' value
assert dt.export_datatype() == {'type': 'scaled', 'scale':0.01, 'min':-300, 'max':300}
with pytest.raises(ValueError):
with pytest.raises(RangeError):
dt.validate(9)
with pytest.raises(ValueError):
with pytest.raises(RangeError):
dt.validate(-9)
dt(9) # convert, but do not check limits
dt(-9) # convert, but do not check limits
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt('XX')
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt([19, 'X'])
dt(1)
dt(0)
with pytest.raises(ValueError):
with pytest.raises(ProgrammingError):
ScaledInteger('xc', 'Yx')
with pytest.raises(ProgrammingError):
ScaledInteger(scale=0, minval=1, maxval=2)
@@ -189,10 +190,10 @@ def test_ScaledInteger():
assert dt.format_value(0.6, '') == '0.6'
assert dt.format_value(0.6, 'Z') == '0.6 Z'
assert round(dt.validate(1.0004), 5) == 0.999 # rounded value within limit
with pytest.raises(ValueError):
with pytest.raises(RangeError):
dt.validate(1.006) # rounded value outside limit
assert round(dt.validate(0.398), 5) == 0.399 # rounded value within rounded limit
with pytest.raises(ValueError):
with pytest.raises(RangeError):
dt.validate(0.395) # rounded value outside limit
dt.setProperty('min', 1)
@@ -200,7 +201,7 @@ def test_ScaledInteger():
with pytest.raises(ConfigError):
dt.checkProperties()
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt.setProperty('scale', None)
@@ -214,21 +215,21 @@ def test_EnumType():
dt = EnumType('dt', a=3, c=7, stuff=1)
copytest(dt)
assert dt.export_datatype() == {'type': 'enum', 'members': dict(a=3, c=7, stuff=1)}
assert dt.export_datatype() == {'type': 'enum', 'members': {'a': 3, 'c': 7, 'stuff': 1}}
with pytest.raises(ValueError):
with pytest.raises(RangeError):
dt(9)
with pytest.raises(ValueError):
with pytest.raises(RangeError):
dt(-9)
with pytest.raises(ValueError):
with pytest.raises(RangeError):
dt('XX')
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt([19, 'X'])
assert dt('a') == 3
assert dt('stuff') == 1
assert dt(1) == 1
with pytest.raises(ValueError):
with pytest.raises(RangeError):
dt(2)
assert dt.export_value('c') == 7
@@ -237,9 +238,9 @@ def test_EnumType():
assert dt.import_value('c') == 7
assert dt.import_value('a') == 3
assert dt.import_value('stuff') == 1
with pytest.raises(ValueError):
with pytest.raises(RangeError):
dt.export_value(2)
with pytest.raises(ValueError):
with pytest.raises(RangeError):
dt.import_value('A')
assert dt.format_value(3) == 'a<3>'
@@ -258,13 +259,13 @@ def test_BLOBType():
copytest(dt)
assert dt.export_datatype() == {'type': 'blob', 'minbytes':3, 'maxbytes':10}
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt(9)
with pytest.raises(ValueError):
with pytest.raises(RangeError):
dt(b'av')
with pytest.raises(ValueError):
with pytest.raises(RangeError):
dt(b'abcdefghijklmno')
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt('abcd')
assert dt(b'abcd') == b'abcd'
@@ -296,13 +297,13 @@ def test_StringType():
copytest(dt)
assert dt.export_datatype() == {'type': 'string', 'minchars':4, 'maxchars':11}
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt(9)
with pytest.raises(ValueError):
with pytest.raises(RangeError):
dt('av')
with pytest.raises(ValueError):
with pytest.raises(RangeError):
dt('abcdefghijklmno')
with pytest.raises(ValueError):
with pytest.raises(RangeError):
dt('abcdefg\0')
assert dt('abcd') == 'abcd'
# tests with bytes have to be added after migration to py3
@@ -326,11 +327,11 @@ def test_TextType():
copytest(dt)
assert dt.export_datatype() == {'type': 'string', 'maxchars':12}
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt(9)
with pytest.raises(ValueError):
with pytest.raises(RangeError):
dt('abcdefghijklmno')
with pytest.raises(ValueError):
with pytest.raises(RangeError):
dt('abcdefg\0')
assert dt('ab\n\ncd\n') == 'ab\n\ncd\n'
# assert dt(b'ab\n\ncd\n') == 'ab\n\ncd\n'
@@ -347,9 +348,9 @@ def test_BoolType():
copytest(dt)
assert dt.export_datatype() == {'type': 'bool'}
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt(9)
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt('av')
assert dt('true') is True
@@ -362,7 +363,7 @@ def test_BoolType():
assert dt.import_value(False) is False
assert dt.import_value(True) is True
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt.import_value('av')
assert dt.format_value(0) == "False"
@@ -375,9 +376,9 @@ def test_BoolType():
def test_ArrayOf():
# test constructor catching illegal arguments
with pytest.raises(ValueError):
with pytest.raises(ProgrammingError):
ArrayOf(int)
with pytest.raises(ValueError):
with pytest.raises(ProgrammingError):
ArrayOf(-3, IntRange(-10,10))
dt = ArrayOf(IntRange(-10, 10), 5)
copytest(dt)
@@ -390,9 +391,9 @@ def test_ArrayOf():
assert dt.export_datatype() == {'type': 'array', 'minlen':1, 'maxlen':3,
'members':{'type': 'double', 'min':-10,
'max':10, 'unit': 'Z'}}
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt(9)
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt('av')
assert dt([1, 2, 3]) == (1, 2, 3)
@@ -422,16 +423,16 @@ def test_ArrayOf():
def test_TupleOf():
# test constructor catching illegal arguments
with pytest.raises(ValueError):
with pytest.raises(ProgrammingError):
TupleOf(2)
dt = TupleOf(IntRange(-10, 10), BoolType())
copytest(dt)
assert dt.export_datatype() == {'type': 'tuple',
'members':[{'type': 'int', 'min':-10, 'max':10}, {'type': 'bool'}]}
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt(9)
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt([99, 'X'])
assert dt([1, True]) == (1, True)
@@ -447,7 +448,7 @@ def test_TupleOf():
def test_StructOf():
# test constructor catching illegal arguments
with pytest.raises(ValueError):
with pytest.raises(ProgrammingError):
StructOf(IntRange) # pylint: disable=E1121
with pytest.raises(ProgrammingError):
StructOf(IntRange=1)
@@ -460,15 +461,15 @@ def test_StructOf():
'an_int': {'type': 'int', 'min':0, 'max':999}},
'optional':['an_int']}
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt(9)
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
dt([99, 'X'])
with pytest.raises(ValueError):
dt.validate(dict(a_string='XXX', an_int=1811))
with pytest.raises(RangeError):
dt.validate({'a_string': 'XXX', 'an_int': 1811})
assert dt(dict(a_string='XXX', an_int=8)) == {'a_string': 'XXX',
'an_int': 8}
assert dt({'a_string': 'XXX', 'an_int': 8}) == {'a_string': 'XXX',
'an_int': 8}
assert dt.export_value({'an_int': 13, 'a_string': 'WFEC'}) == {
'a_string': 'WFEC', 'an_int': 13}
assert dt.import_value({'an_int': 13, 'a_string': 'WFEC'}) == {
@@ -502,30 +503,30 @@ def test_StatusType():
def test_get_datatype():
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype(1)
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype(True)
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype(str)
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype({'undefined': {}})
assert isinstance(get_datatype({'type': 'bool'}), BoolType)
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype(['bool'])
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype({'type': 'int', 'min':-10}) # missing max
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype({'type': 'int', 'max':10}) # missing min
assert isinstance(get_datatype({'type': 'int', 'min':-10, 'max':10}), IntRange)
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype({'type': 'int', 'min':10, 'max':-10}) # min > max
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype({'type': 'int'}) # missing limits
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype({'type': 'int', 'x': 2})
assert isinstance(get_datatype({'type': 'double'}), FloatRange)
@@ -534,16 +535,16 @@ def test_get_datatype():
assert isinstance(get_datatype({'type': 'double', 'min':-9.9, 'max':11.1}),
FloatRange)
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype(['double'])
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype({'type': 'double', 'min':10, 'max':-10})
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype(['double', {}, 2])
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype({'type': 'scaled', 'scale':0.01, 'min':-2.718})
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype({'type': 'scaled', 'scale':0.02, 'max':3.14})
assert isinstance(get_datatype(
{'type': 'scaled', 'scale':0.03, 'min':-99, 'max':111}), ScaledInteger)
@@ -552,45 +553,45 @@ def test_get_datatype():
assert dt.export_datatype() == {'type': 'scaled', 'max':330, 'min':0, 'scale':0.03}
assert get_datatype(dt.export_datatype()).export_datatype() == dt.export_datatype()
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype(['scaled']) # dict missing
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype({'type': 'scaled', 'min':-10, 'max':10}) # no scale
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype({'type': 'scaled', 'min':10, 'max':-10, 'scale': 1}) # limits reversed
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype(['scaled', {'min':10, 'max':-10, 'scale': 1}, 2])
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype(['enum'])
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype({'type': 'enum', 'a': -2})
assert isinstance(get_datatype({'type': 'enum', 'members':dict(a=-2)}), EnumType)
assert isinstance(get_datatype({'type': 'enum', 'members': {'a': -2}}), EnumType)
assert isinstance(get_datatype({'type': 'blob', 'maxbytes':1}), BLOBType)
assert isinstance(get_datatype({'type': 'blob', 'minbytes':1, 'maxbytes':10}), BLOBType)
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype({'type': 'blob', 'minbytes':10, 'maxbytes':1})
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype({'type': 'blob', 'minbytes':10, 'maxbytes':-10})
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype(['blob', {'maxbytes':10}, 'x'])
assert isinstance(get_datatype({'type': 'string', 'maxchars':1}), StringType)
assert isinstance(get_datatype({'type': 'string', 'maxchars':1}), StringType)
assert isinstance(get_datatype({'type': 'string', 'minchars':1, 'maxchars':10}), StringType)
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype({'type': 'string', 'minchars':10, 'maxchars':1})
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype({'type': 'string', 'minchars':10, 'maxchars':-10})
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype(['string', {'maxchars':-0}, 'x'])
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype(['array'])
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype({'type': 'array', 'members': [1]})
assert isinstance(get_datatype({'type': 'array', 'minlen':1, 'maxlen':1,
'members':{'type': 'blob', 'maxbytes':1}}
@@ -599,43 +600,43 @@ def test_get_datatype():
'members':{'type': 'blob', 'maxbytes':1}}
).members, BLOBType)
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype({'type': 'array', 'members':{'type': 'blob', 'maxbytes':1}, 'minbytes':-10})
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype({'type': 'array', 'members':{'type': 'blob', 'maxbytes':1},
'min':10, 'max':1})
with pytest.raises(ValueError):
get_datatype({'type': 'array', 'blob': dict(max=4), 'maxbytes': 10})
with pytest.raises(WrongTypeError):
get_datatype({'type': 'array', 'blob': {'max': 4}, 'maxbytes': 10})
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype(['tuple'])
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype(['tuple', [1], 2, 3])
assert isinstance(get_datatype(
{'type': 'tuple', 'members':[{'type': 'blob', 'maxbytes':1}]}), TupleOf)
assert isinstance(get_datatype(
{'type': 'tuple', 'members':[{'type': 'blob', 'maxbytes':1}]}).members[0], BLOBType)
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype({'type': 'tuple', 'members': {}})
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype(['tuple', 10, -10])
assert isinstance(get_datatype({'type': 'tuple', 'members':[{'type': 'blob', 'maxbytes':1},
{'type': 'bool'}]}), TupleOf)
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype(['struct'])
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype(['struct', [1], 2, 3])
assert isinstance(get_datatype({'type': 'struct', 'members':
{'name': {'type': 'blob', 'maxbytes':1}}}), StructOf)
assert isinstance(get_datatype({'type': 'struct', 'members':
{'name': {'type': 'blob', 'maxbytes':1}}}).members['name'], BLOBType)
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype({'type': 'struct', 'members': {}})
with pytest.raises(ValueError):
with pytest.raises(WrongTypeError):
get_datatype({'type': 'struct', 'members':[1,2,3]})
@@ -651,7 +652,7 @@ def test_get_datatype():
])
def test_oneway_compatible(dt, contained_in):
dt.compatible(contained_in)
with pytest.raises(ValueError):
with pytest.raises(BadValueError):
contained_in.compatible(dt)
@@ -676,9 +677,9 @@ def test_twoway_compatible(dt1, dt2):
(StructOf(a=FloatRange(-1, 1), b=StringType()), StructOf(a=FloatRange(), b=BoolType())),
])
def test_incompatible(dt1, dt2):
with pytest.raises(ValueError):
with pytest.raises(BadValueError):
dt1.compatible(dt2)
with pytest.raises(ValueError):
with pytest.raises(BadValueError):
dt2.compatible(dt1)

View File

@@ -22,9 +22,15 @@
"""test data types."""
import frappy.errors
from frappy.errors import EXCEPTIONS, SECoPError
def test_errors():
"""check consistence of frappy.errors.EXCEPTIONS"""
for e in frappy.errors.EXCEPTIONS.values():
assert frappy.errors.EXCEPTIONS[e().name] == e
for e in EXCEPTIONS.values():
assert EXCEPTIONS[e().name] == e
# check that all defined secop errors are in EXCEPTIONS
for cls in frappy.errors.__dict__.values():
if isinstance(cls, type) and issubclass(cls, SECoPError):
if cls != SECoPError:
assert cls().name in EXCEPTIONS

View File

@@ -24,7 +24,7 @@
import pytest
from frappy.datatypes import FloatRange, IntRange, StringType, ValueType
from frappy.errors import BadValueError, ConfigError, ProgrammingError
from frappy.errors import RangeError, ConfigError, ProgrammingError
from frappy.properties import HasProperties, Property
from frappy.core import Parameter
@@ -35,6 +35,7 @@ def Prop(*args, name=None, **kwds):
# Property(description, datatype, default, ...)
# pylint: disable=use-dict-literal
V_test_Property = [
[Prop(StringType(), 'default', extname='extname', mandatory=False),
dict(default='default', extname='extname', export=True, mandatory=False)
@@ -99,7 +100,7 @@ def test_Properties():
assert Cls.aa.extname == '_aa'
cc = Cls()
with pytest.raises(BadValueError):
with pytest.raises(RangeError):
cc.aa = 137
assert Cls.bb.default == 0