diff --git a/src/p4p/nt/__init__.py b/src/p4p/nt/__init__.py index e4af0489..dcc68cf6 100644 --- a/src/p4p/nt/__init__.py +++ b/src/p4p/nt/__init__.py @@ -10,7 +10,7 @@ from collections import OrderedDict from ..wrapper import Type, Value from .common import timeStamp, alarm, NTBase -from .scalar import NTScalar +from .scalar import NTScalar, ntwrappercommon, _metaHelper from .ndarray import NTNDArray from .enum import NTEnum @@ -147,23 +147,26 @@ class NTTable(NTBase): Value = Value @staticmethod - def buildType(columns=[], extra=[]): + def buildType(columns=[], extra=[], display=False, control=False, valueAlarm=False): """Build a table :param list columns: List of column names and types. eg [('colA', 'd')] :param list extra: A list of tuples describing additional non-standard fields :returns: A :py:class:`Type` """ - return Type(id="epics:nt/NTTable:1.0", - spec=[ - ('labels', 'as'), - ('value', ('S', None, columns)), - ('descriptor', 's'), - ('alarm', alarm), - ('timeStamp', timeStamp), - ] + extra) - - def __init__(self, columns=[], extra=[]): + F = [ + ('labels', 'as'), + ('value', ('S', None, columns)), + ('descriptor', 's'), + ('alarm', alarm), + ('timeStamp', timeStamp), + ] + # TODO: different metadata options + _metaHelper(F, 'i', display=display, control=control, valueAlarm=valueAlarm) + F.extend(extra) + return Type(id="epics:nt/NTTable:1.0", spec=F) + + def __init__(self, columns=[], **kws): self.labels = [] C = [] for col, type in columns: @@ -171,7 +174,7 @@ def __init__(self, columns=[], extra=[]): raise ValueError("NTTable column types may not be array") C.append((col, 'a' + type)) self.labels.append(col) - self.type = self.buildType(C, extra=extra) + self.type = self.buildType(C, **kws) def wrap(self, values, **kws): """Pack an iterable of dict into a Value @@ -184,55 +187,43 @@ def wrap(self, values, **kws): """ if isinstance(values, Value): return values - else: + elif isinstance(values, (list, dict)): cols = dict([(L, []) for L in self.labels]) - try: - # unzip list of dict - for V in values: - for L in self.labels: - try: - cols[L].append(V[L]) - except (IndexError, KeyError): - pass - # allow omit empty columns + if isinstance(values, list): + update = values + else: + update = values['value'] + # unzip list of dict + for V in update: for L in self.labels: - V = cols[L] - if len(V) == 0: - del cols[L] - - try: - values = self.Value(self.type, { - 'labels': self.labels, - 'value': cols, - }) - except: - _log.error("Failed to encode '%s' with %s", cols, self.labels) - raise - except: - _log.exception("Failed to wrap: %s", values) - raise + try: + cols[L].append(V[L]) + except (IndexError, KeyError): + pass + # allow omit empty columns + for L in self.labels: + V = cols[L] + if len(V) == 0: + del cols[L] + update = {'labels': self.labels, 'value': cols} + if isinstance(values, list): + values = self.Value(self.type, update) + else: + values.update(update) + values = self.Value(self.type, values) + else: + # index or string + V = self.type() + values = V return self._annotate(values, **kws) - @staticmethod - def unwrap(value): - """Iterate an NTTable + def unwrap(self, value): + """Unwrap an NTTable into ntwrappercommon - :returns: An iterator yielding an OrderedDict for each column + :returns: ntwrappercommon object """ - ret = [] - - # build lists of column names, and value - lbl, cols = [], [] - for cname, cval in value.value.items(): - lbl.append(cname) - cols.append(cval) - - # zip together column arrays to iterate over rows - for rval in izip(*cols): - # zip together column names and row values - ret.append(OrderedDict(zip(lbl, rval))) + return ntwrappercommon._store(self, value) - return ret class NTURI(object): diff --git a/src/p4p/test/test_nt.py b/src/p4p/test/test_nt.py index b5a75a10..54503311 100644 --- a/src/p4p/test/test_nt.py +++ b/src/p4p/test/test_nt.py @@ -132,7 +132,7 @@ def test_string_array_wrap(self): class TestTable(RefTestCase): - def test_wrap(self): + def test_dict_of_values_wrap(self): NT = nt.NTTable(columns=[ ('a', 'i'), ('b', 's'), @@ -145,7 +145,21 @@ def test_wrap(self): assert_aequal(V.value.a, [5, 6]) self.assertEqual(V.value.b, ['one', 'two']) + def test_dict_wrap(self): + NT = nt.NTTable(columns=[ + ('a', 'i'), + ('b', 's'), + ]) + V = NT.wrap({'value' : [ + {'a': 5, 'b': 'one'}, + {'a': 6, 'b': 'two'}, + ]}) + + assert_aequal(V.value.a, [5, 6]) + self.assertEqual(V.value.b, ['one', 'two']) + def test_unwrap(self): + NT = nt.NTTable() T = nt.NTTable.buildType(columns=[ ('a', 'ai'), ('b', 'as'), @@ -158,13 +172,8 @@ def test_unwrap(self): }, }) - P = list(nt.NTTable.unwrap(V)) - - self.assertListEqual(P, [ - OrderedDict([('a', 5), ('b', u'one')]), - OrderedDict([('a', 6), ('b', u'two')]), - ]) - + P = NT.unwrap(V) + self.assertEqual(P, nt.ntwrappercommon._store(NT, V)) class TestURI(RefTestCase):