diff --git a/secop/datatypes.py b/secop/datatypes.py index 9058ff2..857f924 100644 --- a/secop/datatypes.py +++ b/secop/datatypes.py @@ -97,13 +97,24 @@ class DataType(object): if unit is given, use it, else use the unit of the datatype (if any)""" raise NotImplementedError - def setprop(self, key, value, default, func=lambda x:x): - """set a datatype property and store the default""" + def set_prop(self, key, value, default, func=lambda x:x): + """set an optional datatype property and store the default""" self._defaults[key] = default if value is None: value = default setattr(self, key, func(value)) + def get_info(self, **kwds): + """prepare dict for export or repr + + get a dict with all items different from default + plus mandatory keys from kwds""" + for k,v in self._defaults.items(): + value = getattr(self, k) + if value != v: + kwds[k] = value + return kwds + def copy(self): """make a deep copy of the datatype""" @@ -118,12 +129,12 @@ class FloatRange(DataType): absolute_resolution=None, relative_resolution=None,): self.default = 0 if minval <= 0 <= maxval else minval self._defaults = {} - self.setprop('min', minval, float(u'-inf'), float) - self.setprop('max', maxval, float(u'+inf'), float) - self.setprop('unit', unit, u'', unicode) - self.setprop('fmtstr', fmtstr, u'%g', unicode) - self.setprop('absolute_resolution', absolute_resolution, 0.0, float) - self.setprop('relative_resolution', relative_resolution, 1.2e-7, float) + self.set_prop('min', minval, float(u'-inf'), float) + self.set_prop('max', maxval, float(u'+inf'), float) + self.set_prop('unit', unit, u'', unicode) + self.set_prop('fmtstr', fmtstr, u'%g', unicode) + self.set_prop('absolute_resolution', absolute_resolution, 0.0, float) + self.set_prop('relative_resolution', relative_resolution, 1.2e-7, float) # check values if self.min > self.max: @@ -136,8 +147,7 @@ class FloatRange(DataType): raise BadValueError(u'relative_resolution MUST be >=0') def export_datatype(self): - return {u'double': {k: getattr(self, k) for k, v in self._defaults.items() - if v != getattr(self, k)}} + return self.get_info(type='double') def __call__(self, value): try: @@ -151,8 +161,12 @@ class FloatRange(DataType): (value, self.min, self.max)) def __repr__(self): - items = [u'%s=%r' % (k,v) for k,v in self.export_datatype()['double'].items()] - return u'FloatRange(%s)' % (', '.join(items)) + hints = self.get_info() + if 'min' in hints: + hints['minval'] = hints.pop('min') + if 'max' in hints: + hints['maxval'] = hints.pop('max') + return u'FloatRange(%s)' % (', '.join('%s=%r' % (k,v) for k,v in hints.items())) def export_value(self, value): """returns a python object fit for serialisation""" @@ -181,13 +195,15 @@ class IntRange(DataType): self.min = DEFAULT_MIN_INT if minval is None else int(minval) self.max = DEFAULT_MAX_INT if maxval is None else int(maxval) self.default = 0 if minval <= 0 <= maxval else minval + # a unit on an int is now allowed in SECoP, but do we need them in Frappy? + # self.set_prop('unit', unit, u'', unicode) # check values if self.min > self.max: raise BadValueError(u'Max must be larger then min!') def export_datatype(self): - return {u'int': {"min": self.min, "max": self.max}} + return dict(type='int', min=self.min, max=self.max) def __call__(self, value): try: @@ -234,10 +250,10 @@ class ScaledInteger(DataType): self.scale = float(scale) if not self.scale > 0: raise BadValueError(u'Scale MUST be positive!') - self.setprop('unit', unit, u'', unicode) - self.setprop('fmtstr', fmtstr, u'%g', unicode) - self.setprop('absolute_resolution', absolute_resolution, self.scale, float) - self.setprop('relative_resolution', relative_resolution, 1.2e-7, float) + self.set_prop('unit', unit, u'', unicode) + self.set_prop('fmtstr', fmtstr, u'%g', unicode) + self.set_prop('absolute_resolution', absolute_resolution, self.scale, float) + self.set_prop('relative_resolution', relative_resolution, 1.2e-7, float) self.min = DEFAULT_MIN_INT * self.scale if minval is None else float(minval) self.max = DEFAULT_MAX_INT * self.scale if maxval is None else float(maxval) @@ -255,12 +271,9 @@ class ScaledInteger(DataType): # this should be o.k. def export_datatype(self): - info = {k: getattr(self, k) for k, v in self._defaults.items() - if v != getattr(self, k)} - info['scale'] = self.scale - info['min'] = int((self.min + self.scale * 0.5) // self.scale) - info['max'] = int((self.max + self.scale * 0.5) // self.scale) - return {u'scaled': info} + return self.get_info(type='scaled', scale=self.scale, + min = int((self.min + self.scale * 0.5) // self.scale), + max = int((self.max + self.scale * 0.5) // self.scale)) def __call__(self, value): try: @@ -279,12 +292,10 @@ class ScaledInteger(DataType): return value # return 'actual' value (which is more discrete than a float) def __repr__(self): - hints = self.export_datatype()['scaled'] - hints.pop('scale') - items = ['%g' % self.scale] - for k,v in hints.items(): - items.append(u'%s=%r' % (k,v)) - return u'ScaledInteger(%s)' % (', '.join(items)) + hints = self.get_info(scale='%g' % self.scale, + min = int((self.min + self.scale * 0.5) // self.scale), + max = int((self.max + self.scale * 0.5) // self.scale)) + return u'ScaledInteger(%s)' % (', '.join('%s=%r' % kv for kv in hints.items())) def export_value(self, value): """returns a python object fit for serialisation""" @@ -322,7 +333,7 @@ class EnumType(DataType): return EnumType(self._enum) def export_datatype(self): - return {u'enum': {u"members":dict((m.name, m.value) for m in self._enum.members)}} + return {'type': 'enum', 'members':dict((m.name, m.value) for m in self._enum.members)} def __repr__(self): return u"EnumType(%r, %s)" % (self._enum.name, ', '.join(u'%s=%d' %(m.name, m.value) for m in self._enum.members)) @@ -350,39 +361,40 @@ class EnumType(DataType): class BLOBType(DataType): - minsize = None - maxsize = None + minbytes = None + maxbytes = None - def __init__(self, minsize=0, maxsize=None): + def __init__(self, minbytes=0, maxbytes=None): # if only one argument is given, use exactly that many bytes # if nothing is given, default to 255 - if maxsize is None: - maxsize = minsize or 255 - self.minsize = int(minsize) - self.maxsize = int(maxsize) - if self.minsize < 0: + if maxbytes is None: + maxbytes = minbytes or 255 + self._defaults = {} + self.set_prop('minbytes', minbytes, 0, int) + self.maxbytes = int(maxbytes) + if self.minbytes < 0: raise BadValueError(u'sizes must be bigger than or equal to 0!') - elif self.minsize > self.maxsize: - raise BadValueError(u'maxsize must be bigger than or equal to minsize!') - self.default = b'\0' * self.minsize + elif self.minbytes > self.maxbytes: + raise BadValueError(u'maxbytes must be bigger than or equal to minbytes!') + self.default = b'\0' * self.minbytes def export_datatype(self): - return {u'blob': dict(min=self.minsize, max=self.maxsize)} + return self.get_info(type='blob', maxbytes=self.maxbytes) def __repr__(self): - return u'BLOB(%d, %d)' % (self.minsize, self.maxsize) + return u'BLOBType(%d, %d)' % (self.minbytes, self.maxbytes) def __call__(self, value): """return the validated (internal) value or raise""" if type(value) not in [unicode, str]: raise BadValueError(u'%r has the wrong type!' % value) size = len(value) - if size < self.minsize: + if size < self.minbytes: raise BadValueError( - u'%r must be at least %d bytes long!' % (value, self.minsize)) - if size > self.maxsize: + u'%r must be at least %d bytes long!' % (value, self.minbytes)) + if size > self.maxbytes: raise BadValueError( - u'%r must be at most %d bytes long!' % (value, self.maxsize)) + u'%r must be at most %d bytes long!' % (value, self.maxbytes)) return value def export_value(self, value): @@ -403,37 +415,43 @@ class BLOBType(DataType): class StringType(DataType): - minsize = None - maxsize = None + MAXCHARS = 0xffffffff - def __init__(self, minsize=0, maxsize=None): - if maxsize is None: - maxsize = minsize or 100 - self.minsize = int(minsize) - self.maxsize = int(maxsize) - if self.minsize < 0: + def __init__(self, minchars=0, maxchars=None, isUTF8=False): + if maxchars is None: + maxchars = minchars or self.MAXCHARS + self._defaults = {} + self.set_prop('minchars', minchars, 0, int) + self.set_prop('maxchars', maxchars, self.MAXCHARS, int) + self.set_prop('isUTF8', isUTF8, False, bool) + if self.minchars < 0: raise BadValueError(u'sizes must be bigger than or equal to 0!') - elif self.minsize > self.maxsize: - raise BadValueError(u'maxsize must be bigger than or equal to minsize!') - self.default = u' ' * self.minsize + elif self.minchars > self.maxchars: + raise BadValueError(u'maxchars must be bigger than or equal to minchars!') + self.default = u' ' * self.minchars def export_datatype(self): - return {u'string': dict(min=self.minsize, max=self.maxsize)} + return self.get_info(type='string') def __repr__(self): - return u'StringType(%d, %d)' % (self.minsize, self.maxsize) + return u'StringType(%s)' % (', '.join('%s=%r' % kv for kv in self.get_info().items())) def __call__(self, value): """return the validated (internal) value or raise""" if type(value) not in (unicode, str): raise BadValueError(u'%r has the wrong type!' % value) + if not self.isUTF8: + try: + value.encode('ascii') + except UnicodeEncodeError: + raise BadValueError(u'%r contains non-ascii character!' % value) size = len(value) - if size < self.minsize: + if size < self.minchars: raise BadValueError( - u'%r must be at least %d bytes long!' % (value, self.minsize)) - if size > self.maxsize: + u'%r must be at least %d bytes long!' % (value, self.minchars)) + if size > self.maxchars: raise BadValueError( - u'%r must be at most %d bytes long!' % (value, self.maxsize)) + u'%r must be at most %d bytes long!' % (value, self.maxchars)) if u'\0' in value: raise BadValueError( u'Strings are not allowed to embed a \\0! Use a Blob instead!') @@ -461,17 +479,17 @@ class StringType(DataType): # unfortunately, SECoP makes no distinction here.... # note: content is supposed to follow the format of a git commit message, i.e. a line of text, 2 '\n' + a longer explanation class TextType(StringType): - def __init__(self, maxsize=None): - if maxsize is None: - maxsize = 8000 - super(TextType, self).__init__(0, maxsize) + def __init__(self, maxchars=None): + if maxchars is None: + maxchars = self.MAXCHARS + super(TextType, self).__init__(0, maxchars) def __repr__(self): - return u'TextType(%d, %d)' % (self.minsize, self.maxsize) + return u'TextType(%d, %d)' % (self.minchars, self.maxchars) def copy(self): # DataType.copy will not work, because it is exported as 'string' - return TextType(self.maxsize) + return TextType(self.maxchars) # Bool is a special enum @@ -479,7 +497,7 @@ class BoolType(DataType): default = False def export_datatype(self): - return {u'bool': {}} + return {'type': 'bool'} def __repr__(self): return u'BoolType()' @@ -514,51 +532,51 @@ class BoolType(DataType): class ArrayOf(DataType): - minsize = None - maxsize = None + minlen = None + maxlen = None members = None - def __init__(self, members, minsize=0, maxsize=None, unit=None): + def __init__(self, members, minlen=0, maxlen=None, unit=None): if not isinstance(members, DataType): raise BadValueError( u'ArrayOf only works with a DataType as first argument!') # one argument -> exactly that size # argument default to 100 - if maxsize is None: - maxsize = minsize or 100 + if maxlen is None: + maxlen = minlen or 100 self.members = members if unit: self.members.unit = unit - self.minsize = int(minsize) - self.maxsize = int(maxsize) - if self.minsize < 0: + self.minlen = int(minlen) + self.maxlen = int(maxlen) + if self.minlen < 0: raise BadValueError(u'sizes must be > 0') - elif self.maxsize < 1: + elif self.maxlen < 1: raise BadValueError(u'Maximum size must be >= 1!') - elif self.minsize > self.maxsize: - raise BadValueError(u'maxsize must be bigger than or equal to minsize!') - self.default = [members.default] * self.minsize + elif self.minlen > self.maxlen: + raise BadValueError(u'maxlen must be bigger than or equal to minlen!') + self.default = [members.default] * self.minlen def export_datatype(self): - return {u'array': dict(min=self.minsize, max=self.maxsize, - members=self.members.export_datatype())} + return dict(type='array', minlen=self.minlen, maxlen=self.maxlen, + members=self.members.export_datatype()) def __repr__(self): return u'ArrayOf(%s, %s, %s)' % ( - repr(self.members), self.minsize, self.maxsize) + repr(self.members), self.minlen, self.maxlen) def __call__(self, value): """validate an external representation to an internal one""" if isinstance(value, (tuple, list)): # check number of elements - if self.minsize is not None and len(value) < self.minsize: + if self.minlen is not None and len(value) < self.minlen: raise BadValueError( u'Array too small, needs at least %d elements!' % - self.minsize) - if self.maxsize is not None and len(value) > self.maxsize: + self.minlen) + if self.maxlen is not None and len(value) > self.maxlen: raise BadValueError( - u'Array too big, holds at most %d elements!' % self.minsize) + u'Array too big, holds at most %d elements!' % self.minlen) # apply subtype valiation to all elements and return as list return [self.members(elem) for elem in value] raise BadValueError( @@ -600,7 +618,7 @@ class TupleOf(DataType): self.default = tuple(el.default for el in members) def export_datatype(self): - return {u'tuple': dict(members=[subtype.export_datatype() for subtype in self.members])} + return dict(type='tuple', members=[subtype.export_datatype() for subtype in self.members]) def __repr__(self): return u'TupleOf(%s)' % u', '.join([repr(st) for st in self.members]) @@ -659,15 +677,16 @@ class StructOf(DataType): self.default = dict((k,el.default) for k, el in members.items()) def export_datatype(self): - res = {u'struct': dict(members=dict((n, s.export_datatype()) - for n, s in list(self.members.items())))} + res = dict(type='struct', members=dict((n, s.export_datatype()) + for n, s in list(self.members.items()))) if self.optional: - res['struct']['optional'] = self.optional + res['optional'] = self.optional return res def __repr__(self): - return u'StructOf(%s)' % u', '.join( - [u'%s=%s' % (n, repr(st)) for n, st in list(self.members.items())]) + opt = self.optional if self.optional else '' + return u'StructOf(%s%s)' % (u', '.join( + [u'%s=%s' % (n, repr(st)) for n, st in list(self.members.items())]), opt) def __call__(self, value): """return the validated value or raise""" @@ -728,12 +747,12 @@ class CommandType(DataType): def export_datatype(self): a, r = self.argument, self.result - props = {} + props = {'type': 'command'} if a is not None: props['argument'] = a.export_datatype() if r is not None: props['result'] = r.export_datatype() - return {u'command': props} + return props def __repr__(self): argstr = repr(self.argument) if self.argument else '' @@ -876,9 +895,9 @@ DATATYPES = dict( int =lambda min, max, **kwds: IntRange(minval=min, maxval=max, **kwds), scaled =lambda scale, min, max, **kwds: ScaledInteger(scale=scale, minval=min*scale, maxval=max*scale, **kwds), double =lambda min=None, max=None, **kwds: FloatRange(minval=min, maxval=max, **kwds), - blob =lambda max, min=0: BLOBType(minsize=min, maxsize=max), - string =lambda max, min=0: StringType(minsize=min, maxsize=max), - array =lambda max, members, min=0: ArrayOf(get_datatype(members), minsize=min, maxsize=max), + blob =lambda maxbytes, minbytes=0: BLOBType(minbytes=minbytes, maxbytes=maxbytes), + string =lambda minchars=0, maxchars=None: StringType(minchars=minchars, maxchars=maxchars), + array =lambda maxlen, members, minlen=0: ArrayOf(get_datatype(members), minlen=minlen, maxlen=maxlen), tuple =lambda members: TupleOf(*map(get_datatype, members)), enum =lambda members: EnumType('', members=members), struct =lambda members, optional=None: StructOf(optional, @@ -897,10 +916,12 @@ def get_datatype(json): return json if isinstance(json, list) and len(json) == 2: base, args = json # still allow old syntax - elif isinstance(json, dict) and len(json) == 1: - base, args = tuple(json.items())[0] else: - raise BadValueError('a data descriptor must be a dict (len=1), not %r' % json) + try: + args = json.copy() + base = args.pop('type') + except (TypeError, KeyError, AttributeError): + raise BadValueError('a data descriptor must be a dict containing a "type" key, not %r' % json) try: return DATATYPES[base](**args) except (TypeError, AttributeError, KeyError): diff --git a/test/test_datatypes.py b/test/test_datatypes.py index 2315f6b..6c72231 100644 --- a/test/test_datatypes.py +++ b/test/test_datatypes.py @@ -49,7 +49,7 @@ def test_DataType(): def test_FloatRange(): dt = FloatRange(-3.14, 3.14) copytest(dt) - assert dt.export_datatype() == {u'double': {u'min':-3.14, u'max':3.14}} + assert dt.export_datatype() == {'type': 'double', 'min':-3.14, 'max':3.14} with pytest.raises(ValueError): dt(9) @@ -68,18 +68,18 @@ def test_FloatRange(): FloatRange(u'x', u'Y') # check that unit can be changed dt.unit = u'K' - assert dt.export_datatype() == {u'double': {u'min':-3.14, u'max':3.14, u'unit': u'K'}} + assert dt.export_datatype() == {'type': 'double', 'min':-3.14, 'max':3.14, 'unit': u'K'} dt = FloatRange() copytest(dt) - assert dt.export_datatype() == {u'double': {}} + assert dt.export_datatype() == {'type': 'double'} dt = FloatRange(unit=u'X', fmtstr=u'%.2f', absolute_resolution=1, relative_resolution=0.1) copytest(dt) - assert dt.export_datatype() == {u'double': {u'unit':u'X', u'fmtstr':u'%.2f', - u'absolute_resolution':1.0, - u'relative_resolution':0.1}} + assert dt.export_datatype() == {'type': 'double', 'unit':'X', 'fmtstr':'%.2f', + 'absolute_resolution':1.0, + 'relative_resolution':0.1} assert dt(4) == 4 assert dt.format_value(3.14) == u'3.14 X' assert dt.format_value(3.14, u'') == u'3.14' @@ -89,7 +89,7 @@ def test_FloatRange(): def test_IntRange(): dt = IntRange(-3, 3) copytest(dt) - assert dt.export_datatype() == {u'int': {u'min':-3, u'max':3}} + assert dt.export_datatype() == {'type': 'int', 'min':-3, 'max':3} with pytest.raises(ValueError): dt(9) @@ -106,16 +106,16 @@ def test_IntRange(): dt = IntRange() copytest(dt) - assert tuple(dt.export_datatype()) == ('int',) - assert dt.export_datatype()['int'][u'min'] < 0 < dt.export_datatype()['int'][u'max'] - assert dt.export_datatype() == {u'int': {u'max': 16777216, u'min': -16777216}} + assert dt.export_datatype()['type'] == 'int' + assert dt.export_datatype()['min'] < 0 < dt.export_datatype()['max'] + assert dt.export_datatype() == {'type': 'int', 'max': 16777216,u'min': -16777216} assert dt.format_value(42) == u'42' def test_ScaledInteger(): dt = ScaledInteger(0.01, -3, 3) copytest(dt) # serialisation of datatype contains limits on the 'integer' value - assert dt.export_datatype() == {u'scaled': {u'scale':0.01, u'min':-300, u'max':300}} + assert dt.export_datatype() == {'type': 'scaled', 'scale':0.01, 'min':-300, 'max':300} with pytest.raises(ValueError): dt(9) @@ -135,7 +135,7 @@ def test_ScaledInteger(): ScaledInteger(scale=-10, minval=1, maxval=2) # check that unit can be changed dt.unit = u'A' - assert dt.export_datatype() == {u'scaled': {u'scale':0.01, u'min':-300, u'max':300, u'unit': u'A'}} + assert dt.export_datatype() == {'type': 'scaled', 'scale':0.01, 'min':-300, 'max':300, 'unit': 'A'} assert dt.export_value(0.0001) == int(0) assert dt.export_value(2.71819) == int(272) @@ -144,10 +144,10 @@ def test_ScaledInteger(): dt = ScaledInteger(0.003, 0, 1, unit=u'X', fmtstr=u'%.1f', absolute_resolution=0.001, relative_resolution=1e-5) copytest(dt) - assert dt.export_datatype() == {u'scaled': {u'scale':0.003, u'min':0, u'max':333, + assert dt.export_datatype() == {'type': 'scaled', 'scale':0.003, 'min':0, 'max':333, u'unit':u'X', u'fmtstr':u'%.1f', u'absolute_resolution':0.001, - u'relative_resolution':1e-5}} + u'relative_resolution':1e-5} assert dt(0.4) == 0.399 assert dt.format_value(0.4) == u'0.4 X' assert dt.format_value(0.4, u'') == u'0.4' @@ -166,7 +166,7 @@ def test_EnumType(): dt = EnumType(u'dt', a=3, c=7, stuff=1) copytest(dt) - assert dt.export_datatype() == {u'enum': dict(members=dict(a=3, c=7, stuff=1))} + assert dt.export_datatype() == {'type': 'enum', 'members': dict(a=3, c=7, stuff=1)} with pytest.raises(ValueError): dt(9) @@ -201,14 +201,14 @@ def test_BLOBType(): # test constructor catching illegal arguments dt = BLOBType() copytest(dt) - assert dt.export_datatype() == {u'blob': {u'min':0, u'max':255}} + assert dt.export_datatype() == {'type': 'blob', 'maxbytes':255} dt = BLOBType(10) copytest(dt) - assert dt.export_datatype() == {u'blob': {u'min':10, u'max':10}} + assert dt.export_datatype() == {'type': 'blob', 'minbytes':10, 'maxbytes':10} dt = BLOBType(3, 10) copytest(dt) - assert dt.export_datatype() == {u'blob': {u'min':3, u'max':10}} + assert dt.export_datatype() == {'type': 'blob', 'minbytes':3, 'maxbytes':10} with pytest.raises(ValueError): dt(9) @@ -233,13 +233,14 @@ def test_StringType(): # test constructor catching illegal arguments dt = StringType() copytest(dt) + assert dt.export_datatype() == {'type': 'string'} dt = StringType(12) copytest(dt) - assert dt.export_datatype() == {u'string': {u'min':12, u'max':12}} + assert dt.export_datatype() == {'type': 'string', 'minchars':12, 'maxchars':12} dt = StringType(4, 11) copytest(dt) - assert dt.export_datatype() == {u'string': {u'min':4, u'max':11}} + assert dt.export_datatype() == {'type': 'string', 'minchars':4, 'maxchars':11} with pytest.raises(ValueError): dt(9) @@ -265,7 +266,7 @@ def test_TextType(): # test constructor catching illegal arguments dt = TextType(12) copytest(dt) - assert dt.export_datatype() == {u'string': {u'min':0, u'max':12}} + assert dt.export_datatype() == {'type': 'string', 'maxchars':12} with pytest.raises(ValueError): dt(9) @@ -287,7 +288,7 @@ def test_BoolType(): # test constructor catching illegal arguments dt = BoolType() copytest(dt) - assert dt.export_datatype() == {u'bool': {}} + assert dt.export_datatype() == {'type': 'bool'} with pytest.raises(ValueError): dt(9) @@ -319,16 +320,15 @@ def test_ArrayOf(): ArrayOf(-3, IntRange(-10,10)) dt = ArrayOf(IntRange(-10, 10), 5) copytest(dt) - assert dt.export_datatype() == {u'array': {u'min':5, u'max':5, - u'members':{u'int': {u'min':-10, - u'max':10}}}} + assert dt.export_datatype() == {'type': 'array', 'minlen':5, 'maxlen':5, + 'members': {'type': 'int', 'min':-10, + 'max':10}} dt = ArrayOf(FloatRange(-10, 10, unit=u'Z'), 1, 3) copytest(dt) - assert dt.export_datatype() == {u'array': {u'min':1, u'max':3, - u'members':{u'double': {u'min':-10, - u'max':10, - u'unit':u'Z'}}}} + assert dt.export_datatype() == {'type': 'array', 'minlen':1, 'maxlen':3, + 'members':{'type': 'double', 'min':-10, + 'max':10, 'unit': 'Z'}} with pytest.raises(ValueError): dt(9) with pytest.raises(ValueError): @@ -351,10 +351,8 @@ def test_TupleOf(): dt = TupleOf(IntRange(-10, 10), BoolType()) copytest(dt) - assert dt.export_datatype() == {u'tuple': {u'members':[{u'int': {u'min':-10, - u'max':10}}, - {u'bool': {}}]}} - + assert dt.export_datatype() == {'type': 'tuple', + 'members':[{'type': 'int', 'min':-10, 'max':10}, {'type': 'bool'}]} with pytest.raises(ValueError): dt(9) with pytest.raises(ValueError): @@ -378,12 +376,10 @@ def test_StructOf(): dt = StructOf(a_string=StringType(0, 55), an_int=IntRange(0, 999), optional=[u'an_int']) copytest(dt) - assert dt.export_datatype() == {u'struct': {u'members':{u'a_string': - {u'string': {u'min':0, u'max':55}}, - u'an_int': - {u'int': {u'min':0, u'max':999}},}, - u'optional':[u'an_int'], - }} + assert dt.export_datatype() == {'type': 'struct', + 'members':{'a_string': {'type': 'string', 'maxchars':55}, + 'an_int': {'type': 'int', 'min':0, 'max':999}}, + 'optional':['an_int']} with pytest.raises(ValueError): dt(9) @@ -404,14 +400,15 @@ def test_StructOf(): def test_Command(): dt = CommandType() - assert dt.export_datatype() == {u'command': {}} + assert dt.export_datatype() == {'type': 'command'} dt = CommandType(IntRange(-1,1)) - assert dt.export_datatype() == {u'command': {u'argument':{u'int': {u'min':-1, u'max':1}}}} + assert dt.export_datatype() == {'type': 'command', 'argument':{'type': 'int', 'min':-1, 'max':1}} dt = CommandType(IntRange(-1,1), IntRange(-3,3)) - assert dt.export_datatype() == {u'command': {u'argument':{u'int': {u'min':-1, u'max':1}}, - u'result':{u'int': {u'min':-3, u'max':3}}}} + assert dt.export_datatype() == {'type': 'command', + 'argument':{'type': 'int', 'min':-1, 'max':1}, + 'result':{'type': 'int', 'min':-3, 'max':3}} def test_get_datatype(): @@ -424,144 +421,129 @@ def test_get_datatype(): with pytest.raises(ValueError): get_datatype({u'undefined': {}}) - assert isinstance(get_datatype({u'bool': {}}), BoolType) + assert isinstance(get_datatype({'type': 'bool'}), BoolType) with pytest.raises(ValueError): get_datatype([u'bool']) - with pytest.raises(ValueError): - get_datatype({u'bool': 3}) with pytest.raises(ValueError): - get_datatype({u'int': {u'min':-10}}) + get_datatype({'type': 'int', 'min':-10}) # missing max with pytest.raises(ValueError): - get_datatype({u'int': {u'max':10}}) - assert isinstance(get_datatype({u'int': {u'min':-10, u'max':10}}), IntRange) + get_datatype({'type': 'int', 'max':10}) # missing min + assert isinstance(get_datatype({'type': 'int', 'min':-10, 'max':10}), IntRange) with pytest.raises(ValueError): - get_datatype({u'int': {u'min':10, u'max':-10}}) + get_datatype({'type': 'int', 'min':10, 'max':-10}) # min > max with pytest.raises(ValueError): - get_datatype([u'int']) + get_datatype({'type': 'int'}) # missing limits with pytest.raises(ValueError): - get_datatype({u'int': {}}) - with pytest.raises(ValueError): - get_datatype({u'int': 1, u'x': 2}) + get_datatype({'type': 'int', 'x': 2}) - assert isinstance(get_datatype({u'double': {}}), FloatRange) - assert isinstance(get_datatype({u'double': {u'min':-2.718}}), FloatRange) - assert isinstance(get_datatype({u'double': {u'max':3.14}}), FloatRange) - assert isinstance(get_datatype({u'double': {u'min':-9.9, u'max':11.1}}), + assert isinstance(get_datatype({'type': 'double'}), FloatRange) + assert isinstance(get_datatype({'type': 'double', 'min':-2.718}), FloatRange) + assert isinstance(get_datatype({'type': 'double', 'max':3.14}), FloatRange) + assert isinstance(get_datatype({'type': 'double', 'min':-9.9, 'max':11.1}), FloatRange) with pytest.raises(ValueError): get_datatype([u'double']) with pytest.raises(ValueError): - get_datatype({u'double': {u'min':10, u'max':-10}}) + get_datatype({'type': 'double', 'min':10, 'max':-10}) with pytest.raises(ValueError): - get_datatype({u'double': {}, u'x': 2}) + get_datatype(['double', {}, 2]) with pytest.raises(ValueError): - get_datatype({u'scaled': {u'scale':0.01,u'min':-2.718}}) + get_datatype({'type': 'scaled', 'scale':0.01, 'min':-2.718}) with pytest.raises(ValueError): - get_datatype({u'scaled': {u'scale':0.02,u'max':3.14}}) - assert isinstance(get_datatype({u'scaled': {u'scale':0.03, - u'min':-99, - u'max':111}}), ScaledInteger) + get_datatype({'type': 'scaled', 'scale':0.02, 'max':3.14}) + assert isinstance(get_datatype( + {'type': 'scaled', 'scale':0.03, 'min':-99, 'max':111}), ScaledInteger) dt = ScaledInteger(scale=0.03, minval=0, maxval=9.9) - assert dt.export_datatype() == {u'scaled': {u'max':330, u'min':0, u'scale':0.03}} + 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): get_datatype([u'scaled']) # dict missing with pytest.raises(ValueError): - get_datatype({u'scaled': {u'min':-10, u'max':10}}) # no scale + get_datatype({'type': 'scaled', 'min':-10, 'max':10}) # no scale with pytest.raises(ValueError): - get_datatype({u'scaled': {u'min':10, u'max':-10}}) # limits reversed + get_datatype({'type': 'scaled', 'min':10, 'max':-10, 'scale': 1}) # limits reversed with pytest.raises(ValueError): - get_datatype({u'scaled': {u'min':10, u'max':-10, u'x': 2}}) + get_datatype(['scaled', {'min':10, 'max':-10, 'scale': 1}, 2]) with pytest.raises(ValueError): get_datatype([u'enum']) with pytest.raises(ValueError): - get_datatype({u'enum': dict(a=-2)}) - assert isinstance(get_datatype({u'enum': {u'members':dict(a=-2)}}), EnumType) + get_datatype({'type': 'enum', 'a': -2}) + assert isinstance(get_datatype({'type': 'enum', 'members':dict(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): - get_datatype({u'enum': dict(a=-2), u'x': 1}) + get_datatype({'type': 'blob', 'minbytes':10, 'maxbytes':1}) + with pytest.raises(ValueError): + get_datatype({'type': 'blob', 'minbytes':10, 'maxbytes':-10}) + with pytest.raises(ValueError): + get_datatype(['blob', {'maxbytes':10}, 'x']) - assert isinstance(get_datatype({u'blob': {u'max':1}}), BLOBType) - assert isinstance(get_datatype({u'blob': {u'min':1, u'max':10}}), BLOBType) + 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): - get_datatype({u'blob': {u'min':10, u'max':1}}) + get_datatype({'type': 'string', 'minchars':10, 'maxchars':1}) with pytest.raises(ValueError): - get_datatype({u'blob': {u'min':10, u'max':-10}}) + get_datatype({'type': 'string', 'minchars':10, 'maxchars':-10}) with pytest.raises(ValueError): - get_datatype({u'blob': {u'min':10, u'max':-10}, u'x': 0}) - - with pytest.raises(ValueError): - get_datatype([u'string']) - assert isinstance(get_datatype({u'string': {u'max':1}}), StringType) - assert isinstance(get_datatype({u'string': {u'min':1, u'max':10}}), StringType) - - with pytest.raises(ValueError): - get_datatype({u'string': {u'min':10, u'max':1}}) - with pytest.raises(ValueError): - get_datatype({u'string': {u'min':10, u'max':-10}}) - with pytest.raises(ValueError): - get_datatype({u'string': {u'min':10, u'max':-10, u'x': 0}}) + get_datatype(['string', {'maxchars':-0}, 'x']) with pytest.raises(ValueError): get_datatype([u'array']) with pytest.raises(ValueError): - get_datatype({u'array': 1}) - with pytest.raises(ValueError): - get_datatype({u'array': [1]}) - assert isinstance(get_datatype({u'array': {u'min':1, u'max':1, - u'members':{u'blob': {u'max':1}}}} + get_datatype({'type': 'array', 'members': [1]}) + assert isinstance(get_datatype({'type': 'array', 'minlen':1, 'maxlen':1, + 'members':{'type': 'blob', 'maxbytes':1}} ), ArrayOf) - assert isinstance(get_datatype({u'array': {u'min':1, u'max':1, - u'members':{u'blob': {u'max':1}}}} + assert isinstance(get_datatype({'type': 'array', 'minlen':1, u'maxlen':1, + 'members':{'type': 'blob', 'maxbytes':1}} ).members, BLOBType) with pytest.raises(ValueError): - get_datatype({u'array': {u'members':{u'blob': {u'max':1}}, u'min':-10}}) + get_datatype({'type': 'array', 'members':{'type': 'blob', 'maxbytes':1}, 'minbytes':-10}) with pytest.raises(ValueError): - get_datatype({u'array': {u'members':{u'blob': {u'max':1}}, - u'min':10, 'max':1}}) + get_datatype({'type': 'array', 'members':{'type': 'blob', 'maxbytes':1}, + 'min':10, 'max':1}) with pytest.raises(ValueError): - get_datatype({u'array': {u'blob': dict(max=4)}, u'max': 10}) + get_datatype({'type': 'array', 'blob': dict(max=4), 'maxbytes': 10}) with pytest.raises(ValueError): - get_datatype([u'tuple']) + get_datatype(['tuple']) with pytest.raises(ValueError): - get_datatype({u'tuple': 1}) - with pytest.raises(ValueError): - get_datatype([u'tuple', [1], 2, 3]) - assert isinstance(get_datatype({u'tuple': {u'members':[{u'blob': - {u'max':1}}]}}), TupleOf) - assert isinstance(get_datatype({u'tuple': {u'members':[{u'blob': - {u'max':1}}]}}).members[0], BLOBType) + 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): - get_datatype({u'tuple': {}}) + get_datatype({'type': 'tuple', 'members': {}}) with pytest.raises(ValueError): get_datatype([u'tuple', 10, -10]) - assert isinstance(get_datatype({u'tuple': {u'members':[{u'blob': {u'max':1}}, - {u'bool':{}}]}}), TupleOf) + assert isinstance(get_datatype({'type': 'tuple', 'members':[{'type': 'blob', 'maxbytes':1}, + {'type': 'bool'}]}), TupleOf) with pytest.raises(ValueError): - get_datatype([u'struct']) + get_datatype(['struct']) with pytest.raises(ValueError): - get_datatype({u'struct': 1}) - with pytest.raises(ValueError): - get_datatype([u'struct', [1], 2, 3]) - assert isinstance(get_datatype({u'struct': {u'members': - {u'name': {u'blob': {u'max':1}}}}}), StructOf) - assert isinstance(get_datatype({u'struct': {u'members': - {u'name': {u'blob': {u'max':1}}}}}).members[u'name'], BLOBType) + get_datatype(['struct', [1], 2, 3]) + assert isinstance(get_datatype({'type': 'struct', 'members': + {u'name': {'type': 'blob', 'maxbytes':1}}}), StructOf) + assert isinstance(get_datatype({'type': 'struct', 'members': + {u'name': {'type': 'blob', 'maxbytes':1}}}).members[u'name'], BLOBType) with pytest.raises(ValueError): - get_datatype({u'struct': {}}) + get_datatype({'type': 'struct', 'members': {}}) with pytest.raises(ValueError): - get_datatype({u'struct': {u'members':[1,2,3]}}) + get_datatype({'type': 'struct', 'members':[1,2,3]}) diff --git a/test/test_params.py b/test/test_params.py index a80bff9..47f1c12 100644 --- a/test/test_params.py +++ b/test/test_params.py @@ -36,18 +36,18 @@ def test_Command(): assert cmd.ctr assert cmd.argument is None assert cmd.result is None - assert cmd.for_export() == {u'datatype': {u'command': {}}, + assert cmd.for_export() == {u'datatype': {'type': 'command'}, u'description': u'do_something'} cmd = Command(u'do_something', argument=IntRange(-9,9), result=IntRange(-1,1)) assert cmd.description assert isinstance(cmd.argument, IntRange) assert isinstance(cmd.result, IntRange) - assert cmd.for_export() == {u'datatype': {u'command': {u'argument': {u'int': {u'min':-9, u'max':9}}, - u'result': {u'int': {u'min':-1, u'max':1}}}}, + assert cmd.for_export() == {u'datatype': {'type': 'command', 'argument': {'type': 'int', 'min':-9, 'max':9}, + u'result': {'type': 'int', 'min':-1, 'max':1}}, u'description': u'do_something'} - assert cmd.exportProperties() == {u'datatype': {u'command': {u'argument': {u'int': {u'max': 9, u'min': -9}}, - u'result': {u'int': {u'max': 1, u'min': -1}}}}, + assert cmd.exportProperties() == {u'datatype': {'type': 'command', 'argument': {'type': 'int', 'max': 9, 'min': -9}, + 'result': {'type': 'int', 'max': 1, 'min': -1}}, u'description': u'do_something'}