Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 46 additions & 55 deletions src/p4p/nt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -147,31 +147,34 @@ 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:
if type[0] == 'a':
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
Expand All @@ -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):

Expand Down
25 changes: 17 additions & 8 deletions src/p4p/test/test_nt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand All @@ -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'),
Expand All @@ -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):

Expand Down