change again SECoP datatype syntax

- change from single element JSON-object to flattened JSON-object
  with 'type' key.
- rename of some data properties (maxbytes, maxchars, maxlen)
- added isUTF8 to StringType
The rename of the SECoP property 'datatype' to 'datainfo' will
follow in an other change.

Change-Id: I7a75f0d025ff476dd19385db3487f18c4c746bcf
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/21293
Tested-by: JenkinsCodeReview <bjoern_pedersen@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 2019-09-24 08:54:16 +02:00
parent 6acc82d808
commit 596353e09a
3 changed files with 233 additions and 230 deletions

View File

@ -97,13 +97,24 @@ class DataType(object):
if unit is given, use it, else use the unit of the datatype (if any)""" if unit is given, use it, else use the unit of the datatype (if any)"""
raise NotImplementedError raise NotImplementedError
def setprop(self, key, value, default, func=lambda x:x): def set_prop(self, key, value, default, func=lambda x:x):
"""set a datatype property and store the default""" """set an optional datatype property and store the default"""
self._defaults[key] = default self._defaults[key] = default
if value is None: if value is None:
value = default value = default
setattr(self, key, func(value)) 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): def copy(self):
"""make a deep copy of the datatype""" """make a deep copy of the datatype"""
@ -118,12 +129,12 @@ class FloatRange(DataType):
absolute_resolution=None, relative_resolution=None,): absolute_resolution=None, relative_resolution=None,):
self.default = 0 if minval <= 0 <= maxval else minval self.default = 0 if minval <= 0 <= maxval else minval
self._defaults = {} self._defaults = {}
self.setprop('min', minval, float(u'-inf'), float) self.set_prop('min', minval, float(u'-inf'), float)
self.setprop('max', maxval, float(u'+inf'), float) self.set_prop('max', maxval, float(u'+inf'), float)
self.setprop('unit', unit, u'', unicode) self.set_prop('unit', unit, u'', unicode)
self.setprop('fmtstr', fmtstr, u'%g', unicode) self.set_prop('fmtstr', fmtstr, u'%g', unicode)
self.setprop('absolute_resolution', absolute_resolution, 0.0, float) self.set_prop('absolute_resolution', absolute_resolution, 0.0, float)
self.setprop('relative_resolution', relative_resolution, 1.2e-7, float) self.set_prop('relative_resolution', relative_resolution, 1.2e-7, float)
# check values # check values
if self.min > self.max: if self.min > self.max:
@ -136,8 +147,7 @@ class FloatRange(DataType):
raise BadValueError(u'relative_resolution MUST be >=0') raise BadValueError(u'relative_resolution MUST be >=0')
def export_datatype(self): def export_datatype(self):
return {u'double': {k: getattr(self, k) for k, v in self._defaults.items() return self.get_info(type='double')
if v != getattr(self, k)}}
def __call__(self, value): def __call__(self, value):
try: try:
@ -151,8 +161,12 @@ class FloatRange(DataType):
(value, self.min, self.max)) (value, self.min, self.max))
def __repr__(self): def __repr__(self):
items = [u'%s=%r' % (k,v) for k,v in self.export_datatype()['double'].items()] hints = self.get_info()
return u'FloatRange(%s)' % (', '.join(items)) 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): def export_value(self, value):
"""returns a python object fit for serialisation""" """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.min = DEFAULT_MIN_INT if minval is None else int(minval)
self.max = DEFAULT_MAX_INT if maxval is None else int(maxval) self.max = DEFAULT_MAX_INT if maxval is None else int(maxval)
self.default = 0 if minval <= 0 <= maxval else minval 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 # check values
if self.min > self.max: if self.min > self.max:
raise BadValueError(u'Max must be larger then min!') raise BadValueError(u'Max must be larger then min!')
def export_datatype(self): 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): def __call__(self, value):
try: try:
@ -234,10 +250,10 @@ class ScaledInteger(DataType):
self.scale = float(scale) self.scale = float(scale)
if not self.scale > 0: if not self.scale > 0:
raise BadValueError(u'Scale MUST be positive!') raise BadValueError(u'Scale MUST be positive!')
self.setprop('unit', unit, u'', unicode) self.set_prop('unit', unit, u'', unicode)
self.setprop('fmtstr', fmtstr, u'%g', unicode) self.set_prop('fmtstr', fmtstr, u'%g', unicode)
self.setprop('absolute_resolution', absolute_resolution, self.scale, float) self.set_prop('absolute_resolution', absolute_resolution, self.scale, float)
self.setprop('relative_resolution', relative_resolution, 1.2e-7, 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.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) 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. # this should be o.k.
def export_datatype(self): def export_datatype(self):
info = {k: getattr(self, k) for k, v in self._defaults.items() return self.get_info(type='scaled', scale=self.scale,
if v != getattr(self, k)} min = int((self.min + self.scale * 0.5) // self.scale),
info['scale'] = self.scale max = int((self.max + self.scale * 0.5) // 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}
def __call__(self, value): def __call__(self, value):
try: try:
@ -279,12 +292,10 @@ class ScaledInteger(DataType):
return value # return 'actual' value (which is more discrete than a float) return value # return 'actual' value (which is more discrete than a float)
def __repr__(self): def __repr__(self):
hints = self.export_datatype()['scaled'] hints = self.get_info(scale='%g' % self.scale,
hints.pop('scale') min = int((self.min + self.scale * 0.5) // self.scale),
items = ['%g' % self.scale] max = int((self.max + self.scale * 0.5) // self.scale))
for k,v in hints.items(): return u'ScaledInteger(%s)' % (', '.join('%s=%r' % kv for kv in hints.items()))
items.append(u'%s=%r' % (k,v))
return u'ScaledInteger(%s)' % (', '.join(items))
def export_value(self, value): def export_value(self, value):
"""returns a python object fit for serialisation""" """returns a python object fit for serialisation"""
@ -322,7 +333,7 @@ class EnumType(DataType):
return EnumType(self._enum) return EnumType(self._enum)
def export_datatype(self): 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): 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)) 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): class BLOBType(DataType):
minsize = None minbytes = None
maxsize = 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 only one argument is given, use exactly that many bytes
# if nothing is given, default to 255 # if nothing is given, default to 255
if maxsize is None: if maxbytes is None:
maxsize = minsize or 255 maxbytes = minbytes or 255
self.minsize = int(minsize) self._defaults = {}
self.maxsize = int(maxsize) self.set_prop('minbytes', minbytes, 0, int)
if self.minsize < 0: self.maxbytes = int(maxbytes)
if self.minbytes < 0:
raise BadValueError(u'sizes must be bigger than or equal to 0!') raise BadValueError(u'sizes must be bigger than or equal to 0!')
elif self.minsize > self.maxsize: elif self.minbytes > self.maxbytes:
raise BadValueError(u'maxsize must be bigger than or equal to minsize!') raise BadValueError(u'maxbytes must be bigger than or equal to minbytes!')
self.default = b'\0' * self.minsize self.default = b'\0' * self.minbytes
def export_datatype(self): 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): 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): def __call__(self, value):
"""return the validated (internal) value or raise""" """return the validated (internal) value or raise"""
if type(value) not in [unicode, str]: if type(value) not in [unicode, str]:
raise BadValueError(u'%r has the wrong type!' % value) raise BadValueError(u'%r has the wrong type!' % value)
size = len(value) size = len(value)
if size < self.minsize: if size < self.minbytes:
raise BadValueError( raise BadValueError(
u'%r must be at least %d bytes long!' % (value, self.minsize)) u'%r must be at least %d bytes long!' % (value, self.minbytes))
if size > self.maxsize: if size > self.maxbytes:
raise BadValueError( 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 return value
def export_value(self, value): def export_value(self, value):
@ -403,37 +415,43 @@ class BLOBType(DataType):
class StringType(DataType): class StringType(DataType):
minsize = None MAXCHARS = 0xffffffff
maxsize = None
def __init__(self, minsize=0, maxsize=None): def __init__(self, minchars=0, maxchars=None, isUTF8=False):
if maxsize is None: if maxchars is None:
maxsize = minsize or 100 maxchars = minchars or self.MAXCHARS
self.minsize = int(minsize) self._defaults = {}
self.maxsize = int(maxsize) self.set_prop('minchars', minchars, 0, int)
if self.minsize < 0: 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!') raise BadValueError(u'sizes must be bigger than or equal to 0!')
elif self.minsize > self.maxsize: elif self.minchars > self.maxchars:
raise BadValueError(u'maxsize must be bigger than or equal to minsize!') raise BadValueError(u'maxchars must be bigger than or equal to minchars!')
self.default = u' ' * self.minsize self.default = u' ' * self.minchars
def export_datatype(self): def export_datatype(self):
return {u'string': dict(min=self.minsize, max=self.maxsize)} return self.get_info(type='string')
def __repr__(self): 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): def __call__(self, value):
"""return the validated (internal) value or raise""" """return the validated (internal) value or raise"""
if type(value) not in (unicode, str): if type(value) not in (unicode, str):
raise BadValueError(u'%r has the wrong type!' % value) 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) size = len(value)
if size < self.minsize: if size < self.minchars:
raise BadValueError( raise BadValueError(
u'%r must be at least %d bytes long!' % (value, self.minsize)) u'%r must be at least %d bytes long!' % (value, self.minchars))
if size > self.maxsize: if size > self.maxchars:
raise BadValueError( 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: if u'\0' in value:
raise BadValueError( raise BadValueError(
u'Strings are not allowed to embed a \\0! Use a Blob instead!') 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.... # 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 # 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): class TextType(StringType):
def __init__(self, maxsize=None): def __init__(self, maxchars=None):
if maxsize is None: if maxchars is None:
maxsize = 8000 maxchars = self.MAXCHARS
super(TextType, self).__init__(0, maxsize) super(TextType, self).__init__(0, maxchars)
def __repr__(self): def __repr__(self):
return u'TextType(%d, %d)' % (self.minsize, self.maxsize) return u'TextType(%d, %d)' % (self.minchars, self.maxchars)
def copy(self): def copy(self):
# DataType.copy will not work, because it is exported as 'string' # DataType.copy will not work, because it is exported as 'string'
return TextType(self.maxsize) return TextType(self.maxchars)
# Bool is a special enum # Bool is a special enum
@ -479,7 +497,7 @@ class BoolType(DataType):
default = False default = False
def export_datatype(self): def export_datatype(self):
return {u'bool': {}} return {'type': 'bool'}
def __repr__(self): def __repr__(self):
return u'BoolType()' return u'BoolType()'
@ -514,51 +532,51 @@ class BoolType(DataType):
class ArrayOf(DataType): class ArrayOf(DataType):
minsize = None minlen = None
maxsize = None maxlen = None
members = 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): if not isinstance(members, DataType):
raise BadValueError( raise BadValueError(
u'ArrayOf only works with a DataType as first argument!') u'ArrayOf only works with a DataType as first argument!')
# one argument -> exactly that size # one argument -> exactly that size
# argument default to 100 # argument default to 100
if maxsize is None: if maxlen is None:
maxsize = minsize or 100 maxlen = minlen or 100
self.members = members self.members = members
if unit: if unit:
self.members.unit = unit self.members.unit = unit
self.minsize = int(minsize) self.minlen = int(minlen)
self.maxsize = int(maxsize) self.maxlen = int(maxlen)
if self.minsize < 0: if self.minlen < 0:
raise BadValueError(u'sizes must be > 0') raise BadValueError(u'sizes must be > 0')
elif self.maxsize < 1: elif self.maxlen < 1:
raise BadValueError(u'Maximum size must be >= 1!') raise BadValueError(u'Maximum size must be >= 1!')
elif self.minsize > self.maxsize: elif self.minlen > self.maxlen:
raise BadValueError(u'maxsize must be bigger than or equal to minsize!') raise BadValueError(u'maxlen must be bigger than or equal to minlen!')
self.default = [members.default] * self.minsize self.default = [members.default] * self.minlen
def export_datatype(self): def export_datatype(self):
return {u'array': dict(min=self.minsize, max=self.maxsize, return dict(type='array', minlen=self.minlen, maxlen=self.maxlen,
members=self.members.export_datatype())} members=self.members.export_datatype())
def __repr__(self): def __repr__(self):
return u'ArrayOf(%s, %s, %s)' % ( return u'ArrayOf(%s, %s, %s)' % (
repr(self.members), self.minsize, self.maxsize) repr(self.members), self.minlen, self.maxlen)
def __call__(self, value): def __call__(self, value):
"""validate an external representation to an internal one""" """validate an external representation to an internal one"""
if isinstance(value, (tuple, list)): if isinstance(value, (tuple, list)):
# check number of elements # 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( raise BadValueError(
u'Array too small, needs at least %d elements!' % u'Array too small, needs at least %d elements!' %
self.minsize) self.minlen)
if self.maxsize is not None and len(value) > self.maxsize: if self.maxlen is not None and len(value) > self.maxlen:
raise BadValueError( 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 # apply subtype valiation to all elements and return as list
return [self.members(elem) for elem in value] return [self.members(elem) for elem in value]
raise BadValueError( raise BadValueError(
@ -600,7 +618,7 @@ class TupleOf(DataType):
self.default = tuple(el.default for el in members) self.default = tuple(el.default for el in members)
def export_datatype(self): 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): def __repr__(self):
return u'TupleOf(%s)' % u', '.join([repr(st) for st in self.members]) 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()) self.default = dict((k,el.default) for k, el in members.items())
def export_datatype(self): def export_datatype(self):
res = {u'struct': dict(members=dict((n, s.export_datatype()) res = dict(type='struct', members=dict((n, s.export_datatype())
for n, s in list(self.members.items())))} for n, s in list(self.members.items())))
if self.optional: if self.optional:
res['struct']['optional'] = self.optional res['optional'] = self.optional
return res return res
def __repr__(self): def __repr__(self):
return u'StructOf(%s)' % u', '.join( opt = self.optional if self.optional else ''
[u'%s=%s' % (n, repr(st)) for n, st in list(self.members.items())]) 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): def __call__(self, value):
"""return the validated value or raise""" """return the validated value or raise"""
@ -728,12 +747,12 @@ class CommandType(DataType):
def export_datatype(self): def export_datatype(self):
a, r = self.argument, self.result a, r = self.argument, self.result
props = {} props = {'type': 'command'}
if a is not None: if a is not None:
props['argument'] = a.export_datatype() props['argument'] = a.export_datatype()
if r is not None: if r is not None:
props['result'] = r.export_datatype() props['result'] = r.export_datatype()
return {u'command': props} return props
def __repr__(self): def __repr__(self):
argstr = repr(self.argument) if self.argument else '' 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), 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), 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), double =lambda min=None, max=None, **kwds: FloatRange(minval=min, maxval=max, **kwds),
blob =lambda max, min=0: BLOBType(minsize=min, maxsize=max), blob =lambda maxbytes, minbytes=0: BLOBType(minbytes=minbytes, maxbytes=maxbytes),
string =lambda max, min=0: StringType(minsize=min, maxsize=max), string =lambda minchars=0, maxchars=None: StringType(minchars=minchars, maxchars=maxchars),
array =lambda max, members, min=0: ArrayOf(get_datatype(members), minsize=min, maxsize=max), array =lambda maxlen, members, minlen=0: ArrayOf(get_datatype(members), minlen=minlen, maxlen=maxlen),
tuple =lambda members: TupleOf(*map(get_datatype, members)), tuple =lambda members: TupleOf(*map(get_datatype, members)),
enum =lambda members: EnumType('', members=members), enum =lambda members: EnumType('', members=members),
struct =lambda members, optional=None: StructOf(optional, struct =lambda members, optional=None: StructOf(optional,
@ -897,10 +916,12 @@ def get_datatype(json):
return json return json
if isinstance(json, list) and len(json) == 2: if isinstance(json, list) and len(json) == 2:
base, args = json # still allow old syntax base, args = json # still allow old syntax
elif isinstance(json, dict) and len(json) == 1:
base, args = tuple(json.items())[0]
else: 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: try:
return DATATYPES[base](**args) return DATATYPES[base](**args)
except (TypeError, AttributeError, KeyError): except (TypeError, AttributeError, KeyError):

View File

@ -49,7 +49,7 @@ def test_DataType():
def test_FloatRange(): def test_FloatRange():
dt = FloatRange(-3.14, 3.14) dt = FloatRange(-3.14, 3.14)
copytest(dt) 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): with pytest.raises(ValueError):
dt(9) dt(9)
@ -68,18 +68,18 @@ def test_FloatRange():
FloatRange(u'x', u'Y') FloatRange(u'x', u'Y')
# check that unit can be changed # check that unit can be changed
dt.unit = u'K' 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() dt = FloatRange()
copytest(dt) 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, dt = FloatRange(unit=u'X', fmtstr=u'%.2f', absolute_resolution=1,
relative_resolution=0.1) relative_resolution=0.1)
copytest(dt) copytest(dt)
assert dt.export_datatype() == {u'double': {u'unit':u'X', u'fmtstr':u'%.2f', assert dt.export_datatype() == {'type': 'double', 'unit':'X', 'fmtstr':'%.2f',
u'absolute_resolution':1.0, 'absolute_resolution':1.0,
u'relative_resolution':0.1}} 'relative_resolution':0.1}
assert dt(4) == 4 assert dt(4) == 4
assert dt.format_value(3.14) == u'3.14 X' assert dt.format_value(3.14) == u'3.14 X'
assert dt.format_value(3.14, u'') == u'3.14' assert dt.format_value(3.14, u'') == u'3.14'
@ -89,7 +89,7 @@ def test_FloatRange():
def test_IntRange(): def test_IntRange():
dt = IntRange(-3, 3) dt = IntRange(-3, 3)
copytest(dt) 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): with pytest.raises(ValueError):
dt(9) dt(9)
@ -106,16 +106,16 @@ def test_IntRange():
dt = IntRange() dt = IntRange()
copytest(dt) copytest(dt)
assert tuple(dt.export_datatype()) == ('int',) assert dt.export_datatype()['type'] == 'int'
assert dt.export_datatype()['int'][u'min'] < 0 < dt.export_datatype()['int'][u'max'] assert dt.export_datatype()['min'] < 0 < dt.export_datatype()['max']
assert dt.export_datatype() == {u'int': {u'max': 16777216, u'min': -16777216}} assert dt.export_datatype() == {'type': 'int', 'max': 16777216,u'min': -16777216}
assert dt.format_value(42) == u'42' assert dt.format_value(42) == u'42'
def test_ScaledInteger(): def test_ScaledInteger():
dt = ScaledInteger(0.01, -3, 3) dt = ScaledInteger(0.01, -3, 3)
copytest(dt) copytest(dt)
# serialisation of datatype contains limits on the 'integer' value # 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): with pytest.raises(ValueError):
dt(9) dt(9)
@ -135,7 +135,7 @@ def test_ScaledInteger():
ScaledInteger(scale=-10, minval=1, maxval=2) ScaledInteger(scale=-10, minval=1, maxval=2)
# check that unit can be changed # check that unit can be changed
dt.unit = u'A' 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(0.0001) == int(0)
assert dt.export_value(2.71819) == int(272) 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', dt = ScaledInteger(0.003, 0, 1, unit=u'X', fmtstr=u'%.1f',
absolute_resolution=0.001, relative_resolution=1e-5) absolute_resolution=0.001, relative_resolution=1e-5)
copytest(dt) 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'unit':u'X', u'fmtstr':u'%.1f',
u'absolute_resolution':0.001, u'absolute_resolution':0.001,
u'relative_resolution':1e-5}} u'relative_resolution':1e-5}
assert dt(0.4) == 0.399 assert dt(0.4) == 0.399
assert dt.format_value(0.4) == u'0.4 X' assert dt.format_value(0.4) == u'0.4 X'
assert dt.format_value(0.4, u'') == u'0.4' 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) dt = EnumType(u'dt', a=3, c=7, stuff=1)
copytest(dt) 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): with pytest.raises(ValueError):
dt(9) dt(9)
@ -201,14 +201,14 @@ def test_BLOBType():
# test constructor catching illegal arguments # test constructor catching illegal arguments
dt = BLOBType() dt = BLOBType()
copytest(dt) 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) dt = BLOBType(10)
copytest(dt) 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) dt = BLOBType(3, 10)
copytest(dt) 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): with pytest.raises(ValueError):
dt(9) dt(9)
@ -233,13 +233,14 @@ def test_StringType():
# test constructor catching illegal arguments # test constructor catching illegal arguments
dt = StringType() dt = StringType()
copytest(dt) copytest(dt)
assert dt.export_datatype() == {'type': 'string'}
dt = StringType(12) dt = StringType(12)
copytest(dt) 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) dt = StringType(4, 11)
copytest(dt) 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): with pytest.raises(ValueError):
dt(9) dt(9)
@ -265,7 +266,7 @@ def test_TextType():
# test constructor catching illegal arguments # test constructor catching illegal arguments
dt = TextType(12) dt = TextType(12)
copytest(dt) 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): with pytest.raises(ValueError):
dt(9) dt(9)
@ -287,7 +288,7 @@ def test_BoolType():
# test constructor catching illegal arguments # test constructor catching illegal arguments
dt = BoolType() dt = BoolType()
copytest(dt) copytest(dt)
assert dt.export_datatype() == {u'bool': {}} assert dt.export_datatype() == {'type': 'bool'}
with pytest.raises(ValueError): with pytest.raises(ValueError):
dt(9) dt(9)
@ -319,16 +320,15 @@ def test_ArrayOf():
ArrayOf(-3, IntRange(-10,10)) ArrayOf(-3, IntRange(-10,10))
dt = ArrayOf(IntRange(-10, 10), 5) dt = ArrayOf(IntRange(-10, 10), 5)
copytest(dt) copytest(dt)
assert dt.export_datatype() == {u'array': {u'min':5, u'max':5, assert dt.export_datatype() == {'type': 'array', 'minlen':5, 'maxlen':5,
u'members':{u'int': {u'min':-10, 'members': {'type': 'int', 'min':-10,
u'max':10}}}} 'max':10}}
dt = ArrayOf(FloatRange(-10, 10, unit=u'Z'), 1, 3) dt = ArrayOf(FloatRange(-10, 10, unit=u'Z'), 1, 3)
copytest(dt) copytest(dt)
assert dt.export_datatype() == {u'array': {u'min':1, u'max':3, assert dt.export_datatype() == {'type': 'array', 'minlen':1, 'maxlen':3,
u'members':{u'double': {u'min':-10, 'members':{'type': 'double', 'min':-10,
u'max':10, 'max':10, 'unit': 'Z'}}
u'unit':u'Z'}}}}
with pytest.raises(ValueError): with pytest.raises(ValueError):
dt(9) dt(9)
with pytest.raises(ValueError): with pytest.raises(ValueError):
@ -351,10 +351,8 @@ def test_TupleOf():
dt = TupleOf(IntRange(-10, 10), BoolType()) dt = TupleOf(IntRange(-10, 10), BoolType())
copytest(dt) copytest(dt)
assert dt.export_datatype() == {u'tuple': {u'members':[{u'int': {u'min':-10, assert dt.export_datatype() == {'type': 'tuple',
u'max':10}}, 'members':[{'type': 'int', 'min':-10, 'max':10}, {'type': 'bool'}]}
{u'bool': {}}]}}
with pytest.raises(ValueError): with pytest.raises(ValueError):
dt(9) dt(9)
with pytest.raises(ValueError): with pytest.raises(ValueError):
@ -378,12 +376,10 @@ def test_StructOf():
dt = StructOf(a_string=StringType(0, 55), an_int=IntRange(0, 999), dt = StructOf(a_string=StringType(0, 55), an_int=IntRange(0, 999),
optional=[u'an_int']) optional=[u'an_int'])
copytest(dt) copytest(dt)
assert dt.export_datatype() == {u'struct': {u'members':{u'a_string': assert dt.export_datatype() == {'type': 'struct',
{u'string': {u'min':0, u'max':55}}, 'members':{'a_string': {'type': 'string', 'maxchars':55},
u'an_int': 'an_int': {'type': 'int', 'min':0, 'max':999}},
{u'int': {u'min':0, u'max':999}},}, 'optional':['an_int']}
u'optional':[u'an_int'],
}}
with pytest.raises(ValueError): with pytest.raises(ValueError):
dt(9) dt(9)
@ -404,14 +400,15 @@ def test_StructOf():
def test_Command(): def test_Command():
dt = CommandType() dt = CommandType()
assert dt.export_datatype() == {u'command': {}} assert dt.export_datatype() == {'type': 'command'}
dt = CommandType(IntRange(-1,1)) 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)) dt = CommandType(IntRange(-1,1), IntRange(-3,3))
assert dt.export_datatype() == {u'command': {u'argument':{u'int': {u'min':-1, u'max':1}}, assert dt.export_datatype() == {'type': 'command',
u'result':{u'int': {u'min':-3, u'max':3}}}} 'argument':{'type': 'int', 'min':-1, 'max':1},
'result':{'type': 'int', 'min':-3, 'max':3}}
def test_get_datatype(): def test_get_datatype():
@ -424,144 +421,129 @@ def test_get_datatype():
with pytest.raises(ValueError): with pytest.raises(ValueError):
get_datatype({u'undefined': {}}) get_datatype({u'undefined': {}})
assert isinstance(get_datatype({u'bool': {}}), BoolType) assert isinstance(get_datatype({'type': 'bool'}), BoolType)
with pytest.raises(ValueError): with pytest.raises(ValueError):
get_datatype([u'bool']) get_datatype([u'bool'])
with pytest.raises(ValueError):
get_datatype({u'bool': 3})
with pytest.raises(ValueError): with pytest.raises(ValueError):
get_datatype({u'int': {u'min':-10}}) get_datatype({'type': 'int', 'min':-10}) # missing max
with pytest.raises(ValueError): with pytest.raises(ValueError):
get_datatype({u'int': {u'max':10}}) get_datatype({'type': 'int', 'max':10}) # missing min
assert isinstance(get_datatype({u'int': {u'min':-10, u'max':10}}), IntRange) assert isinstance(get_datatype({'type': 'int', 'min':-10, 'max':10}), IntRange)
with pytest.raises(ValueError): 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): with pytest.raises(ValueError):
get_datatype([u'int']) get_datatype({'type': 'int'}) # missing limits
with pytest.raises(ValueError): with pytest.raises(ValueError):
get_datatype({u'int': {}}) get_datatype({'type': 'int', 'x': 2})
with pytest.raises(ValueError):
get_datatype({u'int': 1, u'x': 2})
assert isinstance(get_datatype({u'double': {}}), FloatRange) assert isinstance(get_datatype({'type': 'double'}), FloatRange)
assert isinstance(get_datatype({u'double': {u'min':-2.718}}), FloatRange) assert isinstance(get_datatype({'type': 'double', 'min':-2.718}), FloatRange)
assert isinstance(get_datatype({u'double': {u'max':3.14}}), FloatRange) assert isinstance(get_datatype({'type': 'double', 'max':3.14}), FloatRange)
assert isinstance(get_datatype({u'double': {u'min':-9.9, u'max':11.1}}), assert isinstance(get_datatype({'type': 'double', 'min':-9.9, 'max':11.1}),
FloatRange) FloatRange)
with pytest.raises(ValueError): with pytest.raises(ValueError):
get_datatype([u'double']) get_datatype([u'double'])
with pytest.raises(ValueError): 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): with pytest.raises(ValueError):
get_datatype({u'double': {}, u'x': 2}) get_datatype(['double', {}, 2])
with pytest.raises(ValueError): 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): with pytest.raises(ValueError):
get_datatype({u'scaled': {u'scale':0.02,u'max':3.14}}) get_datatype({'type': 'scaled', 'scale':0.02, 'max':3.14})
assert isinstance(get_datatype({u'scaled': {u'scale':0.03, assert isinstance(get_datatype(
u'min':-99, {'type': 'scaled', 'scale':0.03, 'min':-99, 'max':111}), ScaledInteger)
u'max':111}}), ScaledInteger)
dt = ScaledInteger(scale=0.03, minval=0, maxval=9.9) 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() assert get_datatype(dt.export_datatype()).export_datatype() == dt.export_datatype()
with pytest.raises(ValueError): with pytest.raises(ValueError):
get_datatype([u'scaled']) # dict missing get_datatype([u'scaled']) # dict missing
with pytest.raises(ValueError): 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): 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): 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): with pytest.raises(ValueError):
get_datatype([u'enum']) get_datatype([u'enum'])
with pytest.raises(ValueError): with pytest.raises(ValueError):
get_datatype({u'enum': dict(a=-2)}) get_datatype({'type': 'enum', 'a': -2})
assert isinstance(get_datatype({u'enum': {u'members':dict(a=-2)}}), EnumType) 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): 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({'type': 'string', 'maxchars':1}), StringType)
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', 'minchars':1, 'maxchars':10}), StringType)
with pytest.raises(ValueError): 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): 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): with pytest.raises(ValueError):
get_datatype({u'blob': {u'min':10, u'max':-10}, u'x': 0}) get_datatype(['string', {'maxchars':-0}, 'x'])
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}})
with pytest.raises(ValueError): with pytest.raises(ValueError):
get_datatype([u'array']) get_datatype([u'array'])
with pytest.raises(ValueError): with pytest.raises(ValueError):
get_datatype({u'array': 1}) get_datatype({'type': 'array', 'members': [1]})
with pytest.raises(ValueError): assert isinstance(get_datatype({'type': 'array', 'minlen':1, 'maxlen':1,
get_datatype({u'array': [1]}) 'members':{'type': 'blob', 'maxbytes':1}}
assert isinstance(get_datatype({u'array': {u'min':1, u'max':1,
u'members':{u'blob': {u'max':1}}}}
), ArrayOf) ), ArrayOf)
assert isinstance(get_datatype({u'array': {u'min':1, u'max':1, assert isinstance(get_datatype({'type': 'array', 'minlen':1, u'maxlen':1,
u'members':{u'blob': {u'max':1}}}} 'members':{'type': 'blob', 'maxbytes':1}}
).members, BLOBType) ).members, BLOBType)
with pytest.raises(ValueError): 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): with pytest.raises(ValueError):
get_datatype({u'array': {u'members':{u'blob': {u'max':1}}, get_datatype({'type': 'array', 'members':{'type': 'blob', 'maxbytes':1},
u'min':10, 'max':1}}) 'min':10, 'max':1})
with pytest.raises(ValueError): 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): with pytest.raises(ValueError):
get_datatype([u'tuple']) get_datatype(['tuple'])
with pytest.raises(ValueError): with pytest.raises(ValueError):
get_datatype({u'tuple': 1}) get_datatype(['tuple', [1], 2, 3])
with pytest.raises(ValueError): assert isinstance(get_datatype(
get_datatype([u'tuple', [1], 2, 3]) {'type': 'tuple', 'members':[{'type': 'blob', 'maxbytes':1}]}), TupleOf)
assert isinstance(get_datatype({u'tuple': {u'members':[{u'blob': assert isinstance(get_datatype(
{u'max':1}}]}}), TupleOf) {'type': 'tuple', 'members':[{'type': 'blob', 'maxbytes':1}]}).members[0], BLOBType)
assert isinstance(get_datatype({u'tuple': {u'members':[{u'blob':
{u'max':1}}]}}).members[0], BLOBType)
with pytest.raises(ValueError): with pytest.raises(ValueError):
get_datatype({u'tuple': {}}) get_datatype({'type': 'tuple', 'members': {}})
with pytest.raises(ValueError): with pytest.raises(ValueError):
get_datatype([u'tuple', 10, -10]) get_datatype([u'tuple', 10, -10])
assert isinstance(get_datatype({u'tuple': {u'members':[{u'blob': {u'max':1}}, assert isinstance(get_datatype({'type': 'tuple', 'members':[{'type': 'blob', 'maxbytes':1},
{u'bool':{}}]}}), TupleOf) {'type': 'bool'}]}), TupleOf)
with pytest.raises(ValueError): with pytest.raises(ValueError):
get_datatype([u'struct']) get_datatype(['struct'])
with pytest.raises(ValueError): with pytest.raises(ValueError):
get_datatype({u'struct': 1}) get_datatype(['struct', [1], 2, 3])
with pytest.raises(ValueError): assert isinstance(get_datatype({'type': 'struct', 'members':
get_datatype([u'struct', [1], 2, 3]) {u'name': {'type': 'blob', 'maxbytes':1}}}), StructOf)
assert isinstance(get_datatype({u'struct': {u'members': assert isinstance(get_datatype({'type': 'struct', 'members':
{u'name': {u'blob': {u'max':1}}}}}), StructOf) {u'name': {'type': 'blob', 'maxbytes':1}}}).members[u'name'], BLOBType)
assert isinstance(get_datatype({u'struct': {u'members':
{u'name': {u'blob': {u'max':1}}}}}).members[u'name'], BLOBType)
with pytest.raises(ValueError): with pytest.raises(ValueError):
get_datatype({u'struct': {}}) get_datatype({'type': 'struct', 'members': {}})
with pytest.raises(ValueError): with pytest.raises(ValueError):
get_datatype({u'struct': {u'members':[1,2,3]}}) get_datatype({'type': 'struct', 'members':[1,2,3]})

View File

@ -36,18 +36,18 @@ def test_Command():
assert cmd.ctr assert cmd.ctr
assert cmd.argument is None assert cmd.argument is None
assert cmd.result 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'} u'description': u'do_something'}
cmd = Command(u'do_something', argument=IntRange(-9,9), result=IntRange(-1,1)) cmd = Command(u'do_something', argument=IntRange(-9,9), result=IntRange(-1,1))
assert cmd.description assert cmd.description
assert isinstance(cmd.argument, IntRange) assert isinstance(cmd.argument, IntRange)
assert isinstance(cmd.result, IntRange) assert isinstance(cmd.result, IntRange)
assert cmd.for_export() == {u'datatype': {u'command': {u'argument': {u'int': {u'min':-9, u'max':9}}, assert cmd.for_export() == {u'datatype': {'type': 'command', 'argument': {'type': 'int', 'min':-9, 'max':9},
u'result': {u'int': {u'min':-1, u'max':1}}}}, u'result': {'type': 'int', 'min':-1, 'max':1}},
u'description': u'do_something'} u'description': u'do_something'}
assert cmd.exportProperties() == {u'datatype': {u'command': {u'argument': {u'int': {u'max': 9, u'min': -9}}, assert cmd.exportProperties() == {u'datatype': {'type': 'command', 'argument': {'type': 'int', 'max': 9, 'min': -9},
u'result': {u'int': {u'max': 1, u'min': -1}}}}, 'result': {'type': 'int', 'max': 1, 'min': -1}},
u'description': u'do_something'} u'description': u'do_something'}