From 96ac437fd3c497b92135e12962633cb80899fc1a Mon Sep 17 00:00:00 2001 From: Enrico Faulhaber Date: Tue, 12 Sep 2017 16:10:22 +0200 Subject: [PATCH] fix transport and display of node-properties Change-Id: I35a3021768e386a5ce922c8e24128d0bc3a039be --- doc/source/conf.py | 10 +- etc/amagnet.cfg | 5 +- etc/ccr12.cfg | 4 +- etc/cryo.cfg | 7 +- etc/demo.cfg | 4 +- etc/epics.cfg | 3 +- etc/test.cfg | 11 +- secop/client/baseclient.py | 3 +- secop/datatypes.py | 39 +++--- secop/gui/nodectrl.py | 12 +- secop/gui/ui/nodectrl.ui | 212 +++++++++++++++++---------------- secop/lib/__init__.py | 1 - secop/protocol/dispatcher.py | 8 +- secop/server.py | 28 ++++- secop_mlz/amagnet.py | 3 +- setup.py | 22 ++-- test/test_client_baseclient.py | 55 +++++---- test/test_datatypes.py | 90 ++++++++------ 18 files changed, 292 insertions(+), 225 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 3dd36b1..68a5c47 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -37,10 +37,10 @@ from secop.version import get_version # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.mathjax', - 'sphinx.ext.viewcode'] + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.mathjax', + 'sphinx.ext.viewcode'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -179,7 +179,6 @@ texinfo_documents = [ ] - # -- Options for Epub output ---------------------------------------------- # Bibliographic Dublin Core info. @@ -201,6 +200,5 @@ epub_copyright = copyright epub_exclude_files = ['search.html'] - # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'https://docs.python.org/': None} diff --git a/etc/amagnet.cfg b/etc/amagnet.cfg index 97b0406..919680c 100644 --- a/etc/amagnet.cfg +++ b/etc/amagnet.cfg @@ -1,6 +1,5 @@ -[equipment] -id=MLZ_amagnet(Garfield) -.visibility=expert +[equipment MLZ_amagnet(Garfield)] +visibility=expert foo=bar [interface tcp] diff --git a/etc/ccr12.cfg b/etc/ccr12.cfg index 6f35940..1f47651 100644 --- a/etc/ccr12.cfg +++ b/etc/ccr12.cfg @@ -1,5 +1,5 @@ -[equipment] -id=ccr12 +[node ccr12] +description = CCR12 box of MLZ Sample environment group [interface tcp] interface=tcp diff --git a/etc/cryo.cfg b/etc/cryo.cfg index 02912f4..bab3062 100644 --- a/etc/cryo.cfg +++ b/etc/cryo.cfg @@ -1,14 +1,9 @@ -[equipment] +[equipment cryo_7] # set SEC-node properties -id=cryo_7 description = short description This is a very long description providing all the glory details in all the glory details about the stuff we are describing -.description = short description - - This is a very long description providing all the glory details in all the glory details about the stuff we are describing - [interface tcp] interface=tcp diff --git a/etc/demo.cfg b/etc/demo.cfg index 9a2241e..edfd69b 100644 --- a/etc/demo.cfg +++ b/etc/demo.cfg @@ -1,5 +1,5 @@ -[equipment] -id=Equipment_ID_for_demonstration +[equipment Equipment_ID_for_demonstration] +description = virtual modules to play around with [interface tcp] interface=tcp diff --git a/etc/epics.cfg b/etc/epics.cfg index 159b3f0..824b48b 100644 --- a/etc/epics.cfg +++ b/etc/epics.cfg @@ -1,5 +1,4 @@ -[equipment] -id=see_demo_equipment +[equipment see_demo_equipment] [client] connectto=0.0.0.0 diff --git a/etc/test.cfg b/etc/test.cfg index e45829f..f8616de 100644 --- a/etc/test.cfg +++ b/etc/test.cfg @@ -1,5 +1,12 @@ -[equipment] -id=test config +[node test config] +description=description of the testing sec-node + . + Here should be the long description. + It can be very long. + . + a single . on a line gets stripped so you can make paragraphs. + . + These texts are supposed to be possibly very long. [interface tcp] interface=tcp diff --git a/secop/client/baseclient.py b/secop/client/baseclient.py index b2d0a4c..e36e66a 100644 --- a/secop/client/baseclient.py +++ b/secop/client/baseclient.py @@ -317,7 +317,8 @@ class Client(object): if data: self._cache.setdefault(modname, {})[pname] = Value(*data) else: - self.log.warning('got malformed answer! (spec data)' % (spec, data)) + self.log.warning( + 'got malformed answer! (spec data)' % (spec, data)) # self.log.info('cache: %s:%s=%r (was: %s)', modname, pname, data, previous) if spec in self.callbacks: for func in self.callbacks[spec]: diff --git a/secop/datatypes.py b/secop/datatypes.py index b627bab..d9cee0e 100644 --- a/secop/datatypes.py +++ b/secop/datatypes.py @@ -340,25 +340,30 @@ class BoolType(DataType): class ArrayOf(DataType): - def __init__(self, subtype, minsize_or_size=None, maxsize=None): - if maxsize is None: - maxsize = minsize_or_size - self.minsize = minsize_or_size - self.maxsize = maxsize - if self.minsize is not None and self.maxsize is not None and \ - self.minsize > self.maxsize: - raise ValueError('minsize must be less than or equal to maxsize!') + def __init__(self, subtype, minsize=0, maxsize=None): if not isinstance(subtype, DataType): raise ValueError( 'ArrayOf only works with DataType objs as first argument!') + # if only one arg is given, it is maxsize! + if minsize and not maxsize: + maxsize = minsize + minsize = 0 + self.as_json = ['array', subtype.as_json, maxsize] + elif maxsize: + self.as_json = ['array', subtype.as_json, maxsize, minsize] + else: + self.as_json = ['array', subtype.as_json] + self.minsize = minsize or 0 + self.maxsize = maxsize self.subtype = subtype - self.as_json = ['array', self.subtype.as_json, - self.maxsize, self.minsize] - if self.minsize is not None and self.minsize < 0: + if self.maxsize is not None and self.minsize > maxsize: + raise ValueError('minsize must be less than or equal to maxsize!') + + if self.minsize < 0: raise ValueError('Minimum size must be >= 0!') if self.maxsize is not None and self.maxsize < 1: raise ValueError('Maximum size must be >= 1!') - if self.minsize is not None and self.maxsize is not None and self.minsize > self.maxsize: + if self.maxsize is not None and self.minsize > self.maxsize: raise ValueError('Maximum size must be >= Minimum size') def __repr__(self): @@ -534,10 +539,12 @@ DATATYPES = dict( bool=lambda: BoolType(), int=lambda _min=None, _max=None: IntRange(_min, _max), double=lambda _min=None, _max=None: FloatRange(_min, _max), - blob=lambda _min=None, _max=None: BLOBType(_min, _max), - string=lambda _min=None, _max=None: StringType(_min, _max), - array=lambda subtype, _min=None, _max=None: ArrayOf( - get_datatype(subtype), _min, _max), + blob=lambda _max=None, _min=None: BLOBType( + _min, _max) if _min else BLOBType(_max), + string=lambda _max=None, _min=None: StringType( + _min, _max) if _min else StringType(_max), + array=lambda subtype, _max=None, _min=None: ArrayOf( + get_datatype(subtype), _min, _max) if _min else ArrayOf(getdatatype(subtype), _min), tuple=lambda subtypes: TupleOf(*map(get_datatype, subtypes)), enum=lambda kwds: EnumType(**kwds), struct=lambda named_subtypes: StructOf( diff --git a/secop/gui/nodectrl.py b/secop/gui/nodectrl.py index 387683d..725218e 100644 --- a/secop/gui/nodectrl.py +++ b/secop/gui/nodectrl.py @@ -43,7 +43,8 @@ class NodeCtrl(QWidget): self.contactPointLabel.setText(self._node.contactPoint) self.equipmentIdLabel.setText(self._node.equipmentId) self.protocolVersionLabel.setText(self._node.protocolVersion) - self.nodeDescriptionLabel.setText(self._node.describingData.get('description','no description available')) + self.nodeDescriptionLabel.setText(self._node.describingData['properties'].get( + 'description', 'no description available')) self._clearLog() # now populate modules tab @@ -168,7 +169,8 @@ class ReadableWidget(QWidget): self._node = node self._module = module - self._status_type = self._node.getProperties(self._module, 'status').get('datatype') + self._status_type = self._node.getProperties( + self._module, 'status').get('datatype') params = self._node.getProperties(self._module, 'value') datatype = params.get('datatype', StringType()) @@ -198,9 +200,11 @@ class ReadableWidget(QWidget): if pname in params: return params[pname].value try: - # if queried, we get the qualifiers as well, but don't want them here + # if queried, we get the qualifiers as well, but don't want them + # here import mlzlog - mlzlog.getLogger('cached values').warn('no cached value for %s:%s' % (self._module, pname)) + mlzlog.getLogger('cached values').warn( + 'no cached value for %s:%s' % (self._module, pname)) val = self._node.getParameter(self._module, pname)[0] return val except Exception: diff --git a/secop/gui/ui/nodectrl.ui b/secop/gui/ui/nodectrl.ui index 6afb00c..99bee3a 100644 --- a/secop/gui/ui/nodectrl.ui +++ b/secop/gui/ui/nodectrl.ui @@ -82,109 +82,119 @@ p, li { white-space: pre-wrap; } NodeInfo - - - - - - - 75 - true - - - - Contact point: - - - - - - - TextLabel - - - - - - - - 75 - true - - - - Equipment ID: - - - - - - - TextLabel - - - - - - - - 75 - true - - - - Protocol version: - - - - - - - TextLabel - - - - - - - - 75 - true - - - - Description: - - - - - - - Description -long line - - - true - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - Qt::Vertical + + + true - - - 20 - 40 - - - + + + + 0 + 0 + 610 + 413 + + + + + + + + 75 + true + + + + Contact point: + + + + + + + TextLabel + + + + + + + + 75 + true + + + + Equipment ID: + + + + + + + TextLabel + + + + + + + + 75 + true + + + + Protocol version: + + + + + + + TextLabel + + + + + + + + 75 + true + + + + Description: + + + + + + + + 0 + 1 + + + + aaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaa + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + label_2 + gridLayoutWidget + + diff --git a/secop/lib/__init__.py b/secop/lib/__init__.py index c7b88f5..ee0964c 100644 --- a/secop/lib/__init__.py +++ b/secop/lib/__init__.py @@ -49,7 +49,6 @@ CONFIG = { } - class lazy_property(object): """A property that calculates its value only once.""" diff --git a/secop/protocol/dispatcher.py b/secop/protocol/dispatcher.py index 46b5afd..9c53656 100644 --- a/secop/protocol/dispatcher.py +++ b/secop/protocol/dispatcher.py @@ -49,7 +49,12 @@ from secop.lib import formatExtendedStack, formatException class Dispatcher(object): def __init__(self, logger, options): - self.equipment_id = options.pop('equipment_id') + # to avoid errors, we want to eat all options here + self.equipment_id = options['equipment_id'] + self.nodeopts = {} + for k in list(options): + self.nodeopts[k] = options.pop(k) + self.log = logger # map ALL modulename -> moduleobj self._modules = {} @@ -227,6 +232,7 @@ class Dispatcher(object): result['equipment_id'] = self.equipment_id result['firmware'] = 'The SECoP playground' result['version'] = "2017.07" + result.update(self.nodeopts) # XXX: what else? return result diff --git a/secop/server.py b/secop/server.py index fb1d0e7..d87e390 100644 --- a/secop/server.py +++ b/secop/server.py @@ -107,7 +107,8 @@ class Server(object): deviceopts = [] interfaceopts = [] - equipment_id = 'unknown' + equipment_id = None + nodeopts = [] for section in parser.sections(): if section.lower().startswith('device '): # device section @@ -135,11 +136,30 @@ class Server(object): (self._cfgfile, ifname)) # all went well so far interfaceopts.append([ifname, ifopts]) - if parser.has_option('equipment', 'id'): - equipment_id = parser.get('equipment', 'id').replace(' ', '_') + if section.lower().startswith('equipment ') or section.lower().startswith('node '): + if equipment_id is not None: + raise ConfigError('cfgfile %r: only one [node ] section allowed, found another [%s]!' % ( + self._cfgfile, section)) + # equipment/node settings + equipment_id = section.split(' ', 1)[1].replace(' ', '_') + nodeopts = dict(item for item in parser.items(section)) + nodeopts['equipment_id'] = equipment_id + nodeopts['id'] = equipment_id + # MAGIC: transform \n.\n into \n\n which are normally stripped + # by the ini parser + for k in nodeopts: + v = nodeopts[k] + while '\n.\n' in v: + v = v.replace('\n.\n', '\n\n') + nodeopts[k] = v + + if equipment_id is None: + self.log.error('Need a [node ] section, none found!') + raise ConfigError( + 'cfgfile %r: need an [node ] option!' % (self._cfgfile)) self._dispatcher = self._buildObject( - 'Dispatcher', Dispatcher, dict(equipment_id=equipment_id)) + 'Dispatcher', Dispatcher, nodeopts) self._processInterfaceOptions(interfaceopts) self._processModuleOptions(deviceopts) diff --git a/secop_mlz/amagnet.py b/secop_mlz/amagnet.py index 80d0488..1568704 100644 --- a/secop_mlz/amagnet.py +++ b/secop_mlz/amagnet.py @@ -75,7 +75,8 @@ class GarfieldMagnet(SequencerMixin, Drivable): default=(1.0, 0.0, 0.0, 0.0, 0.0)), 'calibrationtable': PARAM('Map of Coefficients for calibration per symmetry setting', datatype=StructOf(symmetric=ArrayOf(FloatRange(), 5, 5), - short=ArrayOf(FloatRange(), 5, 5), + short=ArrayOf( + FloatRange(), 5, 5), asymmetric=ArrayOf(FloatRange(), 5, 5)), export=False), } diff --git a/setup.py b/setup.py index e2e9475..e87c107 100644 --- a/setup.py +++ b/setup.py @@ -35,17 +35,17 @@ uidir = path.join(path.dirname(__file__), 'secop', 'gui', 'ui') uis = [path.join('gui', 'ui', entry) for entry in listdir(uidir)] setup( - name = 'secop-core', - version = secop.version.get_version(), - license = 'GPL', - author = 'Enrico Faulhaber', - author_email = 'enrico.faulhaber@frm2.tum.de', - description = 'SECoP Playground core system', - packages = find_packages(), - package_data = {'secop': ['RELEASE-VERSION'] + uis}, - data_files = [('/etc/init.d', ['etc/secop-server'])], - scripts = scripts, - classifiers = [ + name='secop-core', + version=secop.version.get_version(), + license='GPL', + author='Enrico Faulhaber', + author_email='enrico.faulhaber@frm2.tum.de', + description='SECoP Playground core system', + packages=find_packages(), + package_data={'secop': ['RELEASE-VERSION'] + uis}, + data_files=[('/etc/init.d', ['etc/secop-server'])], + scripts=scripts, + classifiers=[ 'Development Status :: 6 - Mature', 'Intended Audience :: Developers', 'Intended Audience :: Science/Research', diff --git a/test/test_client_baseclient.py b/test/test_client_baseclient.py index 43250c4..b0a8834 100644 --- a/test/test_client_baseclient.py +++ b/test/test_client_baseclient.py @@ -24,14 +24,17 @@ import pytest import sys -sys.path.insert(0, sys.path[0]+'/..') +sys.path.insert(0, sys.path[0] + '/..') from collections import OrderedDict from secop.client.baseclient import Client # define Test-only connection object + + class TestConnect(object): callbacks = [] + def writeline(self, line): pass @@ -49,35 +52,37 @@ def clientobj(request): print (" TEARDOWN ClientObj") -def test_describing_data_decode(clientobj): - assert OrderedDict([('a',1)]) == clientobj._decode_list_to_ordereddict(['a',1]) - assert {'modules':{}, 'properties':{}} == clientobj._decode_substruct(['modules'],{}) - describing_data = {'equipment_id': 'eid', - 'modules': ['LN2', {'commands': [], - 'interfaces': ['Readable', 'Module'], - 'parameters': ['value', {'datatype': ['double'], - 'description': 'current value', +def test_describing_data_decode(clientobj): + assert OrderedDict( + [('a', 1)]) == clientobj._decode_list_to_ordereddict(['a', 1]) + assert {'modules': {}, 'properties': {} + } == clientobj._decode_substruct(['modules'], {}) + describing_data = {'equipment_id': 'eid', + 'modules': ['LN2', {'commands': [], + 'interfaces': ['Readable', 'Module'], + 'parameters': ['value', {'datatype': ['double'], + 'description': 'current value', 'readonly': True, - } - ] - } - ] - } - decoded_data = {'modules': {'LN2': {'commands': {}, - 'parameters': {'value': {'datatype': ['double'], - 'description': 'current value', + } + ] + } + ] + } + decoded_data = {'modules': {'LN2': {'commands': {}, + 'parameters': {'value': {'datatype': ['double'], + 'description': 'current value', 'readonly': True, - } - }, + } + }, 'properties': {'interfaces': ['Readable', 'Module']} - } - }, + } + }, 'properties': {'equipment_id': 'eid', - } - } + } + } a = clientobj._decode_substruct(['modules'], describing_data) for modname, module in a['modules'].items(): - a['modules'][modname] = clientobj._decode_substruct(['parameters', 'commands'], module) + a['modules'][modname] = clientobj._decode_substruct( + ['parameters', 'commands'], module) assert a == decoded_data - diff --git a/test/test_datatypes.py b/test/test_datatypes.py index 3b70389..d868fbe 100644 --- a/test/test_datatypes.py +++ b/test/test_datatypes.py @@ -25,7 +25,7 @@ import pytest import sys -sys.path.insert(0, sys.path[0]+'/..') +sys.path.insert(0, sys.path[0] + '/..') from secop.datatypes import DataType, FloatRange, IntRange, \ EnumType, BLOBType, StringType, BoolType, ArrayOf, TupleOf, StructOf, \ @@ -41,6 +41,7 @@ def test_DataType(): dt.validate('') dt.export() + def test_FloatRange(): dt = FloatRange(-3.14, 3.14) assert dt.as_json == ['double', -3.14, 3.14] @@ -52,16 +53,17 @@ def test_FloatRange(): with pytest.raises(ValueError): dt.validate('XX') with pytest.raises(ValueError): - dt.validate([19,'X']) + dt.validate([19, 'X']) dt.validate(1) dt.validate(0) assert dt.export(-2.718) == -2.718 with pytest.raises(ValueError): - FloatRange('x','Y') + FloatRange('x', 'Y') dt = FloatRange() assert dt.as_json == ['double'] + def test_IntRange(): dt = IntRange(-3, 3) assert dt.as_json == ['int', -3, 3] @@ -73,21 +75,22 @@ def test_IntRange(): with pytest.raises(ValueError): dt.validate('XX') with pytest.raises(ValueError): - dt.validate([19,'X']) + dt.validate([19, 'X']) dt.validate(1) dt.validate(0) with pytest.raises(ValueError): - IntRange('xc','Yx') + IntRange('xc', 'Yx') dt = IntRange() assert dt.as_json == ['int'] + def test_EnumType(): # test constructor catching illegal arguments with pytest.raises(ValueError): EnumType(1) with pytest.raises(ValueError): - EnumType('a',b=0) + EnumType('a', b=0) dt = EnumType(a=3, c=7, stuff=1) assert dt.as_json == ['enum', dict(a=3, c=7, stuff=1)] @@ -99,7 +102,7 @@ def test_EnumType(): with pytest.raises(ValueError): dt.validate('XX') with pytest.raises(TypeError): - dt.validate([19,'X']) + dt.validate([19, 'X']) assert dt.validate('a') == 'a' assert dt.validate('stuff') == 'stuff' @@ -113,8 +116,14 @@ def test_EnumType(): with pytest.raises(ValueError): dt.export(2) + def test_BLOBType(): # test constructor catching illegal arguments + dt = BLOBType() + assert dt.as_json == ['blob'] + dt = BLOBType(10) + assert dt.as_json == ['blob', 10] + dt = BLOBType(3, 10) assert dt.as_json == ['blob', 10, 3] @@ -135,6 +144,11 @@ def test_BLOBType(): def test_StringType(): # test constructor catching illegal arguments + dt = StringType() + assert dt.as_json == ['string'] + dt = StringType(12) + assert dt.as_json == ['string', 12] + dt = StringType(4, 11) assert dt.as_json == ['string', 11, 4] @@ -178,16 +192,21 @@ def test_ArrayOf(): # test constructor catching illegal arguments with pytest.raises(ValueError): ArrayOf(int) - dt = ArrayOf(IntRange(-10,10),1,3) + dt = ArrayOf(IntRange(-10, 10)) + assert dt.as_json == ['array', ['int', -10, 10]] + dt = ArrayOf(IntRange(-10, 10), 5) + assert dt.as_json == ['array', ['int', -10, 10], 5] + + dt = ArrayOf(IntRange(-10, 10), 1, 3) assert dt.as_json == ['array', ['int', -10, 10], 3, 1] with pytest.raises(ValueError): dt.validate(9) with pytest.raises(ValueError): dt.validate('av') - assert dt.validate([1,2,3]) == [1,2,3] + assert dt.validate([1, 2, 3]) == [1, 2, 3] - assert dt.export([1,2,3]) == [1,2,3] + assert dt.export([1, 2, 3]) == [1, 2, 3] def test_TupleOf(): @@ -195,17 +214,17 @@ def test_TupleOf(): with pytest.raises(ValueError): TupleOf(2) - dt = TupleOf(IntRange(-10,10), BoolType()) + dt = TupleOf(IntRange(-10, 10), BoolType()) assert dt.as_json == ['tuple', [['int', -10, 10], ['bool']]] with pytest.raises(ValueError): dt.validate(9) with pytest.raises(ValueError): - dt.validate([99,'X']) + dt.validate([99, 'X']) - assert dt.validate([1,True]) == [1,True] + assert dt.validate([1, True]) == [1, True] - assert dt.export([1,True]) == [1,True] + assert dt.export([1, True]) == [1, True] def test_StructOf(): @@ -218,19 +237,20 @@ def test_StructOf(): dt = StructOf(a_string=StringType(), an_int=IntRange(0, 999)) assert dt.as_json == ['struct', {'a_string': ['string'], 'an_int': ['int', 0, 999], - }] + }] with pytest.raises(ValueError): dt.validate(9) with pytest.raises(ValueError): - dt.validate([99,'X']) + dt.validate([99, 'X']) with pytest.raises(ValueError): dt.validate(dict(a_string='XXX', an_int=1811)) assert dt.validate(dict(a_string='XXX', an_int=8)) == {'a_string': 'XXX', 'an_int': 8} - assert dt.export({'an_int':13, 'a_string':'WFEC'}) == {'a_string': 'WFEC', - 'an_int': 13} + assert dt.export({'an_int': 13, 'a_string': 'WFEC'}) == {'a_string': 'WFEC', + 'an_int': 13} + def test_get_datatype(): with pytest.raises(ValueError): @@ -252,51 +272,46 @@ def test_get_datatype(): assert isinstance(get_datatype(['int', -10, 10]), IntRange) with pytest.raises(ValueError): - get_datatype(['int',10, -10]) + get_datatype(['int', 10, -10]) with pytest.raises(ValueError): get_datatype(['int', 1, 2, 3]) - assert isinstance(get_datatype(['double']), FloatRange) assert isinstance(get_datatype(['double', -2.718]), FloatRange) assert isinstance(get_datatype(['double', None, 3.14]), FloatRange) assert isinstance(get_datatype(['double', -9.9, 11.1]), FloatRange) with pytest.raises(ValueError): - get_datatype(['double',10, -10]) + get_datatype(['double', 10, -10]) with pytest.raises(ValueError): get_datatype(['double', 1, 2, 3]) - with pytest.raises(ValueError): get_datatype(['enum']) assert isinstance(get_datatype(['enum', dict(a=-2.718)]), EnumType) with pytest.raises(ValueError): - get_datatype(['enum',10, -10]) + get_datatype(['enum', 10, -10]) with pytest.raises(ValueError): get_datatype(['enum', [1, 2, 3]]) - assert isinstance(get_datatype(['blob']), BLOBType) assert isinstance(get_datatype(['blob', 1]), BLOBType) assert isinstance(get_datatype(['blob', 1, 10]), BLOBType) with pytest.raises(ValueError): - get_datatype(['blob',10, -10]) + get_datatype(['blob', 10, -10]) with pytest.raises(ValueError): - get_datatype(['blob',10, -10, 1]) - + get_datatype(['blob', 10, -10, 1]) assert isinstance(get_datatype(['string']), StringType) assert isinstance(get_datatype(['string', 1]), StringType) assert isinstance(get_datatype(['string', 1, 10]), StringType) with pytest.raises(ValueError): - get_datatype(['string',10, -10]) + get_datatype(['string', 10, -10]) with pytest.raises(ValueError): - get_datatype(['string',10, -10, 1]) - + get_datatype(['string', 10, -10, 1]) with pytest.raises(ValueError): get_datatype(['array']) @@ -314,7 +329,6 @@ def test_get_datatype(): assert isinstance(get_datatype(['array', ['blob'], 1, 10]), ArrayOf) - with pytest.raises(ValueError): get_datatype(['tuple']) with pytest.raises(ValueError): @@ -322,15 +336,15 @@ def test_get_datatype(): with pytest.raises(ValueError): get_datatype(['tuple', [1], 2, 3]) assert isinstance(get_datatype(['tuple', [['blob']]]), TupleOf) - assert isinstance(get_datatype(['tuple', [['blob']]]).subtypes[0], BLOBType) + assert isinstance(get_datatype( + ['tuple', [['blob']]]).subtypes[0], BLOBType) with pytest.raises(ValueError): get_datatype(['tuple', [['blob']], -10]) with pytest.raises(ValueError): get_datatype(['tuple', [['blob']], -10, 10]) - assert isinstance(get_datatype(['tuple', [['blob'],['int']]]), TupleOf) - + assert isinstance(get_datatype(['tuple', [['blob'], ['int']]]), TupleOf) with pytest.raises(ValueError): get_datatype(['struct']) @@ -338,12 +352,14 @@ def test_get_datatype(): get_datatype(['struct', 1]) with pytest.raises(ValueError): get_datatype(['struct', [1], 2, 3]) - assert isinstance(get_datatype(['struct', {'blob':['blob']}]), StructOf) - assert isinstance(get_datatype(['struct', {'blob':['blob']}]).named_subtypes['blob'], BLOBType) + assert isinstance(get_datatype(['struct', {'blob': ['blob']}]), StructOf) + assert isinstance(get_datatype( + ['struct', {'blob': ['blob']}]).named_subtypes['blob'], BLOBType) with pytest.raises(ValueError): get_datatype(['struct', [['blob']], -10]) with pytest.raises(ValueError): get_datatype(['struct', [['blob']], -10, 10]) - assert isinstance(get_datatype(['struct', {'blob':['blob'], 'int':['int']}]), StructOf) + assert isinstance(get_datatype( + ['struct', {'blob': ['blob'], 'int':['int']}]), StructOf)