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:
zolliker 2023-03-08 14:13:40 +01:00
parent 58ff438f46
commit c0704b3d4f
9 changed files with 270 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

@ -22,9 +22,12 @@
"""Define Mixin Features for real Modules implemented in the server"""
from frappy.datatypes import FloatRange, TupleOf
from frappy.core import Feature, Parameter, PersistentParam
from frappy.errors import BadValueError
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 RangeError, ConfigError
from frappy.lib import clamp
class HasSimpleOffset(Feature):
@ -45,12 +48,48 @@ class HasTargetLimits(Feature):
"""
target_limits = PersistentParam('user limits', readonly=False, default=(-9e99, 9e99),
datatype=TupleOf(FloatRange(unit='deg'), FloatRange(unit='deg')))
_limits = None
def apply_offset(self, sign, *values):
if isinstance(self, HasOffset):
return tuple(v + sign * self.offset for v in values)
return values
def earlyInit(self):
super().earlyInit()
# make limits valid
_limits = self.apply_offset(1, *self.limits)
self._limits = tuple(clamp(self.abslimits[0], v, self.abslimits[1]) for v in _limits)
self.read_limits()
def checkProperties(self):
pname = 'target' if isinstance(self, Drivable) else 'value'
dt = self.parameters[pname].datatype
min_, max_ = self.abslimits
t_min, t_max = self.apply_offset(1, dt.min, dt.max)
if t_min > max_ or t_max < min_:
raise ConfigError('abslimits not within %s range' % pname)
self.abslimits = clamp(t_min, min_, t_max), clamp(t_min, max_, t_max)
super().checkProperties()
def read_limits(self):
return self.apply_offset(-1, *self._limits)
def write_limits(self, value):
min_, max_ = self.apply_offset(-1, *self.abslimits)
if not min_ <= value[0] <= value[1] <= max_:
if value[0] > value[1]:
raise RangeError('invalid interval: %r' % value)
raise RangeError('limits not within abs limits [%g, %g]' % (min_, max_))
self.limits = value
self.saveParameters()
return Done
def check_limits(self, value):
"""check if value is valid"""
min_, max_ = self.target_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_))
# --- legacy mixins, not agreed as standard ---

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