diff --git a/frappy/client/__init__.py b/frappy/client/__init__.py index 06535b9..642ba8c 100644 --- a/frappy/client/__init__.py +++ b/frappy/client/__init__.py @@ -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 diff --git a/frappy/datatypes.py b/frappy/datatypes.py index 0e53bb3..00b2ebd 100644 --- a/frappy/datatypes.py +++ b/frappy/datatypes.py @@ -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 diff --git a/frappy/errors.py b/frappy/errors.py index dcb9f75..97cc2f7 100644 --- a/frappy/errors.py +++ b/frappy/errors.py @@ -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, diff --git a/frappy/features.py b/frappy/features.py index d69b832..deffad1 100644 --- a/frappy/features.py +++ b/frappy/features.py @@ -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 --- diff --git a/frappy/params.py b/frappy/params.py index 4279922..2f36fdf 100644 --- a/frappy/params.py +++ b/frappy/params.py @@ -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) diff --git a/frappy_psi/trinamic.py b/frappy_psi/trinamic.py index 0e562c5..c0e706d 100644 --- a/frappy_psi/trinamic.py +++ b/frappy_psi/trinamic.py @@ -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: diff --git a/test/test_datatypes.py b/test/test_datatypes.py index 96cfa95..720b6ce 100644 --- a/test/test_datatypes.py +++ b/test/test_datatypes.py @@ -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) diff --git a/test/test_errors.py b/test/test_errors.py index 21a73e0..5c98d81 100644 --- a/test/test_errors.py +++ b/test/test_errors.py @@ -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 diff --git a/test/test_properties.py b/test/test_properties.py index 47cc717..4bac9f7 100644 --- a/test/test_properties.py +++ b/test/test_properties.py @@ -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