Bug hunting and polishing
Change-Id: I0f05730dd4e01e926ab0c4870c27ed5754f3ccfd
This commit is contained in:
parent
8e3d0da5dd
commit
d5e935788f
@ -38,7 +38,10 @@ def main(argv=None):
|
||||
if argv is None:
|
||||
argv = sys.argv
|
||||
|
||||
loggers.initLogging('gui', 'debug')
|
||||
if '-d' in argv:
|
||||
loggers.initLogging('gui', 'debug')
|
||||
else:
|
||||
loggers.initLogging('gui', 'info')
|
||||
|
||||
app = QApplication(argv)
|
||||
|
||||
|
10
etc/demo.cfg
10
etc/demo.cfg
@ -1,8 +1,12 @@
|
||||
[server]
|
||||
[equipment]
|
||||
id=demonstration
|
||||
|
||||
[interface testing]
|
||||
interface=tcp
|
||||
bindto=0.0.0.0
|
||||
bindport=10767
|
||||
interface = tcp
|
||||
framing=demo
|
||||
# protocol to use for this interface
|
||||
framing=eol
|
||||
encoding=demo
|
||||
|
||||
[device heatswitch]
|
||||
|
@ -35,6 +35,7 @@ from secop.lib.parsing import parse_time, format_time
|
||||
from secop.protocol.encoding import ENCODERS
|
||||
from secop.protocol.framing import FRAMERS
|
||||
from secop.protocol.messages import *
|
||||
from secop.protocol.errors import EXCEPTIONS
|
||||
|
||||
|
||||
class TCPConnection(object):
|
||||
@ -115,9 +116,9 @@ class Value(object):
|
||||
|
||||
def __init__(self, value, qualifiers={}):
|
||||
self.value = value
|
||||
if 't' in qualifiers:
|
||||
self.t = parse_time(qualifiers.pop('t'))
|
||||
self.__dict__.update(qualifiers)
|
||||
if 't' in qualifiers:
|
||||
self.t = parse_time(qualifiers['t'])
|
||||
|
||||
def __repr__(self):
|
||||
r = []
|
||||
@ -153,9 +154,12 @@ class Client(object):
|
||||
port = int(opts.pop('port', 10767))
|
||||
self.contactPoint = "tcp://%s:%d" % (host, port)
|
||||
self.connection = TCPConnection(host, port)
|
||||
# maps an expected reply to an list containing a single Event()
|
||||
# upon rcv of that reply, the event is set and the listitem 0 is
|
||||
# appended with the reply-tuple
|
||||
# maps an expected reply to a list containing a single Event()
|
||||
# upon rcv of that reply, entry is appended with False and
|
||||
# the data of the reply.
|
||||
# if an error is received, the entry is appended with True and an
|
||||
# appropriate Exception.
|
||||
# Then the Event is set.
|
||||
self.expected_replies = {}
|
||||
|
||||
# maps spec to a set of callback functions (or single_shot callbacks)
|
||||
@ -193,47 +197,60 @@ class Client(object):
|
||||
self.log.info('connected to: ' + line.strip())
|
||||
self.secop_id = line
|
||||
continue
|
||||
msgtype, spec, data = self._decode_message(line)
|
||||
msgtype, spec, data = self.decode_message(line)
|
||||
if msgtype in ('update', 'changed'):
|
||||
# handle async stuff
|
||||
self._handle_event(spec, data)
|
||||
if msgtype != 'update':
|
||||
# handle sync stuff
|
||||
if msgtype in self.expected_replies:
|
||||
entry = self.expected_replies[msgtype]
|
||||
entry.extend([msgtype, spec, data])
|
||||
# wake up calling process
|
||||
entry[0].set()
|
||||
elif msgtype == "error":
|
||||
# XXX: hack!
|
||||
if len(self.expected_replies) == 1:
|
||||
entry = self.expected_replies.values()[0]
|
||||
entry.extend([msgtype, spec, data])
|
||||
# wake up calling process
|
||||
entry[0].set()
|
||||
else: # try to find the right request....
|
||||
print data[0] # should be the origin request
|
||||
# XXX: make an assignment of ERROR to an expected reply.
|
||||
self.log.error('TODO: handle ERROR replies!')
|
||||
else:
|
||||
self.log.error('ignoring unexpected reply %r' % line)
|
||||
# handle sync stuff
|
||||
self._handle_sync_reply(msgtype, spec, data)
|
||||
|
||||
def _encode_message(self, requesttype, spec='', data=Ellipsis):
|
||||
def _handle_sync_reply(self, msgtype, spec, data):
|
||||
# handle sync stuff
|
||||
if msgtype == "error":
|
||||
# find originating msgtype and map to expected_reply_type
|
||||
# errormessages carry to offending request as the first
|
||||
# result in the resultist
|
||||
_msgtype, _spec, _data = self.decode_message(data[0])
|
||||
_reply = self._get_reply_from_request(_msgtype)
|
||||
|
||||
entry = self.expected_replies.get((_reply, _spec), None)
|
||||
if entry:
|
||||
self.log.error("request %r resulted in Error %r" %
|
||||
(data[0], spec))
|
||||
entry.extend([True, EXCEPTIONS[spec](data)])
|
||||
entry[0].set()
|
||||
return
|
||||
self.log.error("got an unexpected error %s %r" %
|
||||
(spec, data[0]))
|
||||
return
|
||||
if msgtype == "describing":
|
||||
data = [spec, data]
|
||||
spec = ''
|
||||
entry = self.expected_replies.get((msgtype, spec), None)
|
||||
if entry:
|
||||
self.log.debug("got expected reply '%s %s'" %
|
||||
(msgtype, spec) if spec else
|
||||
"got expected reply '%s'" % msgtype)
|
||||
entry.extend([False, data])
|
||||
entry[0].set()
|
||||
return
|
||||
|
||||
def encode_message(self, requesttype, spec='', data=None):
|
||||
"""encodes the given message to a string
|
||||
"""
|
||||
req = [str(requesttype)]
|
||||
if spec:
|
||||
req.append(str(spec))
|
||||
if data is not Ellipsis:
|
||||
if data is not None:
|
||||
req.append(json.dumps(data))
|
||||
req = ' '.join(req)
|
||||
return req
|
||||
|
||||
def _decode_message(self, msg):
|
||||
def decode_message(self, msg):
|
||||
"""return a decoded message tripel"""
|
||||
msg = msg.strip()
|
||||
if ' ' not in msg:
|
||||
return msg, None, None
|
||||
return msg, '', None
|
||||
msgtype, spec = msg.split(' ', 1)
|
||||
data = None
|
||||
if ' ' in spec:
|
||||
@ -277,8 +294,7 @@ class Client(object):
|
||||
return self._getDescribingModuleData(module)['parameters'][parameter]
|
||||
|
||||
def _issueDescribe(self):
|
||||
_, self.equipment_id, self.describing_data = self.communicate(
|
||||
'describe')
|
||||
self.equipment_id, self.describing_data = self.communicate('describe')
|
||||
|
||||
for module, moduleData in self.describing_data['modules'].items():
|
||||
for parameter, parameterData in moduleData['parameters'].items():
|
||||
@ -299,53 +315,71 @@ class Client(object):
|
||||
self.callbacks.setdefault('%s:%s' %
|
||||
(module, parameter), set()).discard(cb)
|
||||
|
||||
def communicate(self, msgtype, spec='', data=Ellipsis):
|
||||
def _get_reply_from_request(self, requesttype):
|
||||
# maps each (sync) request to the corresponding reply
|
||||
# XXX: should go to the encoder! and be imported here (or make a
|
||||
# translating method)
|
||||
# XXX: should go to the encoder! and be imported here
|
||||
REPLYMAP = {
|
||||
"describe": "describing",
|
||||
"do": "done",
|
||||
"change": "changed",
|
||||
"activate": "active",
|
||||
"deactivate": "inactive",
|
||||
"*IDN?": "SECoP,",
|
||||
"read": "update",
|
||||
#"*IDN?": "SECoP,", # XXX: !!!
|
||||
"ping": "pong",
|
||||
}
|
||||
return REPLYMAP.get(requesttype, requesttype)
|
||||
|
||||
def communicate(self, msgtype, spec='', data=None):
|
||||
self.log.debug('communicate: %r %r %r' % (msgtype, spec, data))
|
||||
if self.stopflag:
|
||||
raise RuntimeError('alreading stopping!')
|
||||
if msgtype == 'read':
|
||||
# send a poll request and then check incoming events
|
||||
if ':' not in spec:
|
||||
spec = spec + ':value'
|
||||
event = threading.Event()
|
||||
result = ['update', spec]
|
||||
self.single_shots.setdefault(spec, set()).add(
|
||||
lambda d: (result.append(d), event.set()))
|
||||
self.connection.writeline(
|
||||
self._encode_message(
|
||||
msgtype, spec, data))
|
||||
if event.wait(10):
|
||||
return tuple(result)
|
||||
raise RuntimeError("timeout upon waiting for reply!")
|
||||
if msgtype == "*IDN?":
|
||||
return self.secop_id
|
||||
|
||||
rply = REPLYMAP[msgtype]
|
||||
if rply in self.expected_replies:
|
||||
if msgtype not in ('*IDN?', 'describe', 'activate',
|
||||
'deactivate', 'do', 'change', 'read', 'ping', 'help'):
|
||||
raise EXCEPTIONS['Protocol'](errorclass='Protocol',
|
||||
errorinfo='%r: No Such Messagetype defined!' %
|
||||
msgtype,
|
||||
origin=self.encode_message(msgtype, spec, data))
|
||||
|
||||
# sanitize input + handle syntactic sugar
|
||||
msgtype = str(msgtype)
|
||||
spec = str(spec)
|
||||
if msgtype == 'change' and ':' not in spec:
|
||||
spec = spec + ':target'
|
||||
if msgtype == 'read' and ':' not in spec:
|
||||
spec = spec + ':value'
|
||||
|
||||
# check if a such a request is already out
|
||||
rply = self._get_reply_from_request(msgtype)
|
||||
if (rply, spec) in self.expected_replies:
|
||||
raise RuntimeError(
|
||||
"can not have more than one requests of the same type at the same time!")
|
||||
|
||||
# prepare sending request
|
||||
event = threading.Event()
|
||||
self.expected_replies[rply] = [event]
|
||||
self.expected_replies[(rply, spec)] = [event]
|
||||
self.log.debug('prepared reception of %r msg' % rply)
|
||||
self.connection.writeline(self._encode_message(msgtype, spec, data))
|
||||
self.log.debug('sent %r msg' % msgtype)
|
||||
if event.wait(10): # wait 10s for reply
|
||||
|
||||
# send request
|
||||
msg = self.encode_message(msgtype, spec, data)
|
||||
self.connection.writeline(msg)
|
||||
self.log.debug('sent msg %r' % msg)
|
||||
|
||||
# wait for reply. timeout after 10s
|
||||
if event.wait(10):
|
||||
self.log.debug('checking reply')
|
||||
result = self.expected_replies[rply][1:4]
|
||||
del self.expected_replies[rply]
|
||||
# if result[0] == "ERROR":
|
||||
# raise RuntimeError('Got %s! %r' % (str(result[1]), repr(result[2])))
|
||||
event, is_error, result = self.expected_replies.pop((rply, spec))
|
||||
if is_error:
|
||||
# if error, result contains the rigth Exception to raise
|
||||
raise result
|
||||
return result
|
||||
del self.expected_replies[rply]
|
||||
|
||||
# timed out
|
||||
del self.expected_replies[(rply, spec)]
|
||||
# XXX: raise a TimedOut ?
|
||||
raise RuntimeError("timeout upon waiting for reply to %r!" % msgtype)
|
||||
|
||||
def quit(self):
|
||||
@ -379,6 +413,9 @@ class Client(object):
|
||||
|
||||
return result
|
||||
|
||||
def getParameter(self, module, parameter):
|
||||
return self.communicate('read', '%s:%s' % (module, parameter))
|
||||
|
||||
def setParameter(self, module, parameter, value):
|
||||
validator = self._getDescribingParameterData(module,
|
||||
parameter)['validator']
|
||||
@ -417,7 +454,11 @@ class Client(object):
|
||||
|
||||
def getProperties(self, module, parameter):
|
||||
return self.describing_data['modules'][
|
||||
module]['parameters'][parameter].items()
|
||||
module]['parameters'][parameter]
|
||||
|
||||
def syncCommunicate(self, *msg):
|
||||
return self.communicate(*msg)
|
||||
|
||||
def ping(self, pingctr=[0]):
|
||||
pingctr[0] = pingctr[0] + 1
|
||||
self.communicate("ping", pingctr[0])
|
||||
|
@ -35,7 +35,7 @@ import threading
|
||||
from secop.lib.parsing import format_time
|
||||
from secop.errors import ConfigError, ProgrammingError
|
||||
from secop.protocol import status
|
||||
from secop.validators import enum, vector, floatrange
|
||||
from secop.validators import enum, vector, floatrange, validator_to_str
|
||||
|
||||
EVENT_ONLY_ON_CHANGED_VALUES = False
|
||||
|
||||
@ -74,9 +74,9 @@ class PARAM(object):
|
||||
unit=self.unit,
|
||||
readonly=self.readonly,
|
||||
value=self.value,
|
||||
timestamp=format_time(self.timestamp) if self.timestamp else None,
|
||||
validator=str(self.validator) if not isinstance(
|
||||
self.validator, type) else self.validator.__name__
|
||||
timestamp=format_time(
|
||||
self.timestamp) if self.timestamp else None,
|
||||
validator=validator_to_str(self.validator),
|
||||
)
|
||||
|
||||
|
||||
@ -260,7 +260,7 @@ class Device(object):
|
||||
# only check if validator given
|
||||
try:
|
||||
v = validator(v)
|
||||
except ValueError as e:
|
||||
except (ValueError, TypeError) as e:
|
||||
raise ConfigError('Device %s: config parameter %r:\n%r'
|
||||
% (self.name, k, e))
|
||||
setattr(self, k, v)
|
||||
@ -285,22 +285,22 @@ class Readable(Device):
|
||||
default="Readable", validator=str),
|
||||
'value': PARAM('current value of the device', readonly=True, default=0.),
|
||||
'pollinterval': PARAM('sleeptime between polls', readonly=False, default=5, validator=floatrange(1, 120),),
|
||||
'status': PARAM('current status of the device', default=status.OK,
|
||||
validator=enum(**{'idle': status.OK,
|
||||
'BUSY': status.BUSY,
|
||||
'WARN': status.WARN,
|
||||
'UNSTABLE': status.UNSTABLE,
|
||||
'ERROR': status.ERROR,
|
||||
'UNKNOWN': status.UNKNOWN}),
|
||||
# 'status': PARAM('current status of the device', default=status.OK,
|
||||
# validator=enum(**{'idle': status.OK,
|
||||
# 'BUSY': status.BUSY,
|
||||
# 'WARN': status.WARN,
|
||||
# 'UNSTABLE': status.UNSTABLE,
|
||||
# 'ERROR': status.ERROR,
|
||||
# 'UNKNOWN': status.UNKNOWN}),
|
||||
# readonly=True),
|
||||
'status': PARAM('current status of the device', default=(status.OK, ''),
|
||||
validator=vector(enum(**{'idle': status.OK,
|
||||
'BUSY': status.BUSY,
|
||||
'WARN': status.WARN,
|
||||
'UNSTABLE': status.UNSTABLE,
|
||||
'ERROR': status.ERROR,
|
||||
'UNKNOWN': status.UNKNOWN}), str),
|
||||
readonly=True),
|
||||
'status2': PARAM('current status of the device', default=(status.OK, ''),
|
||||
validator=vector(enum(**{'idle': status.OK,
|
||||
'BUSY': status.BUSY,
|
||||
'WARN': status.WARN,
|
||||
'UNSTABLE': status.UNSTABLE,
|
||||
'ERROR': status.ERROR,
|
||||
'UNKNOWN': status.UNKNOWN}), str),
|
||||
readonly=True),
|
||||
}
|
||||
|
||||
def init(self):
|
||||
|
@ -68,21 +68,27 @@ class Switch(Driveable):
|
||||
|
||||
def read_status(self, maxage=0):
|
||||
self.log.info("read status")
|
||||
self._update()
|
||||
info = self._update()
|
||||
if self.target == self.value:
|
||||
return status.OK
|
||||
return status.BUSY
|
||||
return status.OK, ''
|
||||
return status.BUSY, info
|
||||
|
||||
def _update(self):
|
||||
started = self.PARAMS['target'].timestamp
|
||||
info = ''
|
||||
if self.target > self.value:
|
||||
info = 'waiting for ON'
|
||||
if time.time() > started + self.switch_on_time:
|
||||
self.log.debug('is switched ON')
|
||||
info = 'is switched ON'
|
||||
self.value = self.target
|
||||
elif self.target < self.value:
|
||||
info = 'waiting for OFF'
|
||||
if time.time() > started + self.switch_off_time:
|
||||
self.log.debug('is switched OFF')
|
||||
info = 'is switched OFF'
|
||||
self.value = self.target
|
||||
if info:
|
||||
self.log.debug(info)
|
||||
return info
|
||||
|
||||
|
||||
class MagneticField(Driveable):
|
||||
@ -101,7 +107,7 @@ class MagneticField(Driveable):
|
||||
|
||||
def init(self):
|
||||
self._state = 'idle'
|
||||
self._heatswitch = self.DISPATCHER.get_device(self.heatswitch)
|
||||
self._heatswitch = self.DISPATCHER.get_module(self.heatswitch)
|
||||
_thread = threading.Thread(target=self._thread)
|
||||
_thread.daemon = True
|
||||
_thread.start()
|
||||
@ -116,7 +122,8 @@ class MagneticField(Driveable):
|
||||
# note: we may also return the read-back value from the hw here
|
||||
|
||||
def read_status(self, maxage=0):
|
||||
return status.OK if self._state == 'idle' else status.BUSY
|
||||
return (status.OK, '') if self._state == 'idle' else (
|
||||
status.BUSY, self._state)
|
||||
|
||||
def _thread(self):
|
||||
loopdelay = 1
|
||||
@ -202,9 +209,9 @@ class SampleTemp(Driveable):
|
||||
ts = time.time()
|
||||
if self.value == self.target:
|
||||
if self.status != status.OK:
|
||||
self.status = status.OK
|
||||
self.status = status.OK, ''
|
||||
else:
|
||||
self.status = status.BUSY
|
||||
self.status = status.BUSY, 'ramping'
|
||||
step = self.ramp * loopdelay / 60.
|
||||
step = max(min(self.target - self.value, step), -step)
|
||||
self.value += step
|
||||
@ -230,14 +237,14 @@ class Label(Readable):
|
||||
def read_value(self, maxage=0):
|
||||
strings = [self.system]
|
||||
|
||||
dev_ts = self.DISPATCHER.get_device(self.subdev_ts)
|
||||
dev_ts = self.DISPATCHER.get_module(self.subdev_ts)
|
||||
if dev_ts:
|
||||
strings.append('at %.3f %s' %
|
||||
(dev_ts.read_value(), dev_ts.PARAMS['value'].unit))
|
||||
else:
|
||||
strings.append('No connection to sample temp!')
|
||||
|
||||
dev_mf = self.DISPATCHER.get_device(self.subdev_mf)
|
||||
dev_mf = self.DISPATCHER.get_module(self.subdev_mf)
|
||||
if dev_mf:
|
||||
mf_stat = dev_mf.read_status()
|
||||
mf_mode = dev_mf.mode
|
||||
@ -262,8 +269,8 @@ class ValidatorTest(Readable):
|
||||
'enum': PARAM('enum', validator=enum('boo', 'faar', z=9), readonly=False, default=1),
|
||||
'vector': PARAM('vector of int, float and str', validator=vector(int, float, str), readonly=False, default=(1, 2.3, 'a')),
|
||||
'array': PARAM('array: 2..3 time oneof(0,1)', validator=array(oneof(2, 3), oneof(0, 1)), readonly=False, default=[1, 0, 1]),
|
||||
'nonnegative': PARAM('nonnegative', validator=nonnegative(), readonly=False, default=0),
|
||||
'positive': PARAM('positive', validator=positive(), readonly=False, default=1),
|
||||
'nonnegative': PARAM('nonnegative', validator=nonnegative, readonly=False, default=0),
|
||||
'positive': PARAM('positive', validator=positive, readonly=False, default=1),
|
||||
'intrange': PARAM('intrange', validator=intrange(2, 9), readonly=False, default=4),
|
||||
'floatrange': PARAM('floatrange', validator=floatrange(-1, 1), readonly=False, default=0,),
|
||||
}
|
||||
|
@ -24,7 +24,7 @@
|
||||
import random
|
||||
|
||||
from secop.devices.core import Readable, Driveable, PARAM
|
||||
from secop.validators import floatrange
|
||||
from secop.validators import floatrange, positive
|
||||
|
||||
|
||||
class LN2(Readable):
|
||||
@ -65,6 +65,8 @@ class Temp(Driveable):
|
||||
PARAMS = {
|
||||
'sensor': PARAM("Sensor number or calibration id",
|
||||
validator=str, readonly=True),
|
||||
'target': PARAM("Target temperature", default=300.0,
|
||||
validator=positive, readonly=False, unit='K'),
|
||||
}
|
||||
|
||||
def read_value(self, maxage=0):
|
||||
|
@ -27,6 +27,7 @@ from PyQt4.QtCore import pyqtSignature as qtsig, QObject, pyqtSignal
|
||||
from secop.gui.util import loadUi
|
||||
from secop.gui.nodectrl import NodeCtrl
|
||||
from secop.gui.modulectrl import ModuleCtrl
|
||||
from secop.gui.paramview import ParameterView
|
||||
from secop.client.baseclient import Client as SECNode
|
||||
|
||||
ITEM_TYPE_NODE = QTreeWidgetItem.UserType + 1
|
||||
@ -35,7 +36,7 @@ ITEM_TYPE_PARAMETER = QTreeWidgetItem.UserType + 3
|
||||
|
||||
|
||||
class QSECNode(SECNode, QObject):
|
||||
newData = pyqtSignal(str, str, object) # module, parameter, data
|
||||
newData = pyqtSignal(str, str, object) # module, parameter, data
|
||||
|
||||
def __init__(self, opts, autoconnect=False, parent=None):
|
||||
SECNode.__init__(self, opts, autoconnect)
|
||||
@ -79,7 +80,7 @@ class MainWindow(QMainWindow):
|
||||
@qtsig('')
|
||||
def on_actionAdd_SEC_node_triggered(self):
|
||||
host, ok = QInputDialog.getText(self, 'Add SEC node',
|
||||
'Enter SEC node hostname:')
|
||||
'Enter SEC node hostname:')
|
||||
|
||||
if not ok:
|
||||
return
|
||||
@ -91,6 +92,10 @@ class MainWindow(QMainWindow):
|
||||
self._displayNode(current.text(0))
|
||||
elif current.type() == ITEM_TYPE_MODULE:
|
||||
self._displayModule(current.parent().text(0), current.text(0))
|
||||
elif current.type() == ITEM_TYPE_PARAMETER:
|
||||
self._displayParameter(current.parent().parent().text(0),
|
||||
current.parent().text(0),
|
||||
current.text(0))
|
||||
|
||||
def _addNode(self, host):
|
||||
|
||||
@ -99,9 +104,10 @@ class MainWindow(QMainWindow):
|
||||
if ':' in host:
|
||||
host, port = host.split(':', 1)
|
||||
port = int(port)
|
||||
node = QSECNode({'connectto':host, 'port':port}, parent=self)
|
||||
node = QSECNode({'connectto': host, 'port': port}, parent=self)
|
||||
host = '%s:%d' % (host, port)
|
||||
|
||||
host = '%s (%s)' % (node.equipment_id, host)
|
||||
self._nodes[host] = node
|
||||
|
||||
# fill tree
|
||||
@ -127,6 +133,13 @@ class MainWindow(QMainWindow):
|
||||
def _displayModule(self, node, module):
|
||||
self._replaceCtrlWidget(ModuleCtrl(self._nodes[node], module))
|
||||
|
||||
def _displayParameter(self, node, module, parameter):
|
||||
self._replaceCtrlWidget(
|
||||
ParameterView(
|
||||
self._nodes[node],
|
||||
module,
|
||||
parameter))
|
||||
|
||||
def _replaceCtrlWidget(self, new):
|
||||
old = self.splitter.widget(1).layout().takeAt(0)
|
||||
if old:
|
||||
|
@ -26,10 +26,12 @@ from PyQt4.QtCore import pyqtSignature as qtsig, Qt, pyqtSignal
|
||||
|
||||
from secop.gui.util import loadUi
|
||||
|
||||
class ParameterButtons(QWidget):
|
||||
setRequested = pyqtSignal(str, str, str) # module, parameter, setpoint
|
||||
|
||||
def __init__(self, module, parameter, initval='', parent=None):
|
||||
class ParameterButtons(QWidget):
|
||||
setRequested = pyqtSignal(str, str, str) # module, parameter, target
|
||||
|
||||
def __init__(self, module, parameter, initval='',
|
||||
readonly=True, parent=None):
|
||||
super(ParameterButtons, self).__init__(parent)
|
||||
loadUi(self, 'parambuttons.ui')
|
||||
|
||||
@ -37,20 +39,24 @@ class ParameterButtons(QWidget):
|
||||
self._parameter = parameter
|
||||
|
||||
self.currentLineEdit.setText(str(initval))
|
||||
if readonly:
|
||||
self.setPushButton.setEnabled(False)
|
||||
self.setLineEdit.setEnabled(False)
|
||||
|
||||
def on_setPushButton_clicked(self):
|
||||
self.setRequested.emit(self._module, self._parameter,
|
||||
self.setLineEdit.text())
|
||||
self.setLineEdit.text())
|
||||
|
||||
|
||||
class ModuleCtrl(QWidget):
|
||||
|
||||
def __init__(self, node, module, parent=None):
|
||||
super(ModuleCtrl, self).__init__(parent)
|
||||
loadUi(self, 'modulectrl.ui')
|
||||
self._node = node
|
||||
self._module = module
|
||||
|
||||
self._paramWidgets = {} # widget cache do avoid garbage collection
|
||||
self._paramWidgets = {} # widget cache do avoid garbage collection
|
||||
|
||||
self.moduleNameLabel.setText(module)
|
||||
self._initModuleWidgets()
|
||||
@ -68,8 +74,12 @@ class ModuleCtrl(QWidget):
|
||||
label = QLabel(param + ':')
|
||||
label.setFont(font)
|
||||
|
||||
props = self._node.getProperties(self._module, param)
|
||||
|
||||
buttons = ParameterButtons(self._module, param,
|
||||
initValues[param].value)
|
||||
initValues[param].value,
|
||||
props['readonly'])
|
||||
|
||||
buttons.setRequested.connect(self._node.setParameter)
|
||||
|
||||
self.paramGroupBox.layout().addWidget(label, row, 0)
|
||||
|
@ -27,8 +27,11 @@ from PyQt4.QtGui import QWidget, QTextCursor, QFont, QFontMetrics
|
||||
from PyQt4.QtCore import pyqtSignature as qtsig, Qt
|
||||
|
||||
from secop.gui.util import loadUi
|
||||
from secop.protocol.errors import SECOPError
|
||||
|
||||
|
||||
class NodeCtrl(QWidget):
|
||||
|
||||
def __init__(self, node, parent=None):
|
||||
super(NodeCtrl, self).__init__(parent)
|
||||
loadUi(self, 'nodectrl.ui')
|
||||
@ -49,9 +52,12 @@ class NodeCtrl(QWidget):
|
||||
|
||||
self._addLogEntry('<span style="font-weight:bold">Request:</span> '
|
||||
'%s:' % msg, raw=True)
|
||||
msg = msg.split(' ', 2)
|
||||
reply = self._node.syncCommunicate(*msg)
|
||||
self._addLogEntry(reply, newline=True, pretty=True)
|
||||
# msg = msg.split(' ', 2)
|
||||
try:
|
||||
reply = self._node.syncCommunicate(*self._node.decode_message(msg))
|
||||
self._addLogEntry(reply, newline=True, pretty=True)
|
||||
except SECOPError as e:
|
||||
self._addLogEntry(e, newline=True, pretty=True, error=True)
|
||||
|
||||
@qtsig('')
|
||||
def on_clearPushButton_clicked(self):
|
||||
@ -64,13 +70,19 @@ class NodeCtrl(QWidget):
|
||||
self._addLogEntry('=========================')
|
||||
self._addLogEntry('', newline=True)
|
||||
|
||||
def _addLogEntry(self, msg, newline=False, pretty=False, raw=False):
|
||||
def _addLogEntry(self, msg, newline=False,
|
||||
pretty=False, raw=False, error=False):
|
||||
|
||||
if pretty:
|
||||
msg = pprint.pformat(msg, width=self._getLogWidth())
|
||||
|
||||
if not raw:
|
||||
msg = '<pre>%s</pre>' % Qt.escape(str(msg)).replace('\n', '<br />')
|
||||
if error:
|
||||
msg = '<div style="color:#FF0000"><b><pre>%s</pre></b></div>' % Qt.escape(
|
||||
str(msg)).replace('\n', '<br />')
|
||||
else:
|
||||
msg = '<pre>%s</pre>' % Qt.escape(str(msg)
|
||||
).replace('\n', '<br />')
|
||||
|
||||
content = ''
|
||||
if self.logTextBrowser.toPlainText():
|
||||
@ -89,4 +101,3 @@ class NodeCtrl(QWidget):
|
||||
# due to monospace)
|
||||
result = self.logTextBrowser.width() / fontMetrics.width('a')
|
||||
return result
|
||||
|
||||
|
81
secop/gui/paramview.py
Normal file
81
secop/gui/paramview.py
Normal file
@ -0,0 +1,81 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# *****************************************************************************
|
||||
# Copyright (c) 2015-2017 by the authors, see LICENSE
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation; either version 2 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Module authors:
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
from PyQt4.QtGui import QWidget, QLabel, QSizePolicy
|
||||
from PyQt4.QtCore import pyqtSignature as qtsig, Qt, pyqtSignal
|
||||
|
||||
from secop.gui.util import loadUi
|
||||
from secop.validators import validator_to_str
|
||||
|
||||
|
||||
class ParameterView(QWidget):
|
||||
|
||||
def __init__(self, node, module, parameter, parent=None):
|
||||
super(ParameterView, self).__init__(parent)
|
||||
loadUi(self, 'paramview.ui')
|
||||
self._node = node
|
||||
self._module = module
|
||||
self._parameter = parameter
|
||||
|
||||
self._propWidgets = {} # widget cache do avoid garbage collection
|
||||
|
||||
self.paramNameLabel.setText("%s:%s" % (module, parameter))
|
||||
self._initParamWidgets()
|
||||
|
||||
# self._node.newData.connect(self._updateValue)
|
||||
|
||||
def _initParamWidgets(self):
|
||||
# initValues = self._node.queryCache(self._module) #? mix live data?
|
||||
row = 0
|
||||
|
||||
font = self.font()
|
||||
font.setBold(True)
|
||||
|
||||
props = self._node._getDescribingParameterData(
|
||||
self._module, self._parameter)
|
||||
for prop in sorted(props):
|
||||
label = QLabel(prop + ':')
|
||||
label.setFont(font)
|
||||
label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||
|
||||
# make 'display' label
|
||||
if prop == 'validator':
|
||||
view = QLabel(validator_to_str(props[prop]))
|
||||
else:
|
||||
view = QLabel(str(props[prop]))
|
||||
view.setFont(self.font())
|
||||
view.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
||||
view.setWordWrap(True)
|
||||
|
||||
self.propertyGroupBox.layout().addWidget(label, row, 0)
|
||||
self.propertyGroupBox.layout().addWidget(view, row, 1)
|
||||
|
||||
self._propWidgets[prop] = (label, view)
|
||||
|
||||
row += 1
|
||||
|
||||
def _updateValue(self, module, parameter, value):
|
||||
if module != self._module:
|
||||
return
|
||||
|
||||
self._paramWidgets[parameter][1].currentLineEdit.setText(str(value[0]))
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>730</width>
|
||||
<height>31</height>
|
||||
<height>33</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -33,7 +33,7 @@
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="currentLineEdit">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
@ -41,6 +41,9 @@
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
|
99
secop/gui/ui/paramview.ui
Normal file
99
secop/gui/ui/paramview.ui
Normal file
@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>230</width>
|
||||
<height>121</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="3" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<italic>false</italic>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Parameter name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="paramNameLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QGroupBox" name="propertyGroupBox">
|
||||
<property name="title">
|
||||
<string>Parameters:</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -45,6 +45,7 @@ from messages import *
|
||||
from errors import *
|
||||
from secop.lib.parsing import format_time
|
||||
|
||||
|
||||
class Dispatcher(object):
|
||||
|
||||
def __init__(self, logger, options):
|
||||
@ -205,7 +206,7 @@ class Dispatcher(object):
|
||||
|
||||
def get_descriptive_data(self):
|
||||
# XXX: be lazy and cache this?
|
||||
result = {'modules':{}}
|
||||
result = {'modules': {}}
|
||||
for modulename in self._export:
|
||||
module = self.get_module(modulename)
|
||||
# some of these need rework !
|
||||
@ -335,9 +336,9 @@ class Dispatcher(object):
|
||||
res = self._setParamValue(msg.module, 'target', msg.value)
|
||||
res.parameter = 'target'
|
||||
# self.broadcast_event(res)
|
||||
if conn in self._active_connections:
|
||||
return None # already send to myself
|
||||
return res # send reply to inactive conns
|
||||
# if conn in self._active_connections:
|
||||
# return None # already send to myself
|
||||
return res
|
||||
|
||||
def handle_Command(self, conn, msg):
|
||||
# notify all by sending CommandReply
|
||||
|
@ -27,7 +27,7 @@
|
||||
|
||||
from secop.protocol.encoding import MessageEncoder
|
||||
from secop.protocol.messages import *
|
||||
from secop.protocol.errors import ProtocollError
|
||||
from secop.protocol.errors import ProtocolError
|
||||
|
||||
import ast
|
||||
import re
|
||||
@ -389,7 +389,7 @@ class DemoEncoder_MZ(MessageEncoder):
|
||||
# errors
|
||||
ErrorReply: lambda msg: "",
|
||||
InternalError: lambda msg: "",
|
||||
ProtocollError: lambda msg: "",
|
||||
ProtocolError: lambda msg: "",
|
||||
CommandFailedError: lambda msg: "error CommandError %s:%s %s" % (msg.device, msg.param, msg.error),
|
||||
NoSuchCommandError: lambda msg: "error NoSuchCommand %s:%s" % (msg.device, msg.param, msg.error),
|
||||
NoSuchDeviceError: lambda msg: "error NoSuchModule %s" % msg.device,
|
||||
|
@ -28,7 +28,7 @@
|
||||
from secop.lib.parsing import format_time
|
||||
from secop.protocol.encoding import MessageEncoder
|
||||
from secop.protocol.messages import *
|
||||
from secop.protocol.errors import ProtocollError
|
||||
#from secop.protocol.errors import ProtocolError
|
||||
|
||||
import ast
|
||||
import re
|
||||
@ -71,7 +71,7 @@ HELPREQUEST = 'help' # literal
|
||||
HELPREPLY = 'helping' # +line number +json_text
|
||||
ERRORCLASSES = ['NoSuchDevice', 'NoSuchParameter', 'NoSuchCommand',
|
||||
'CommandFailed', 'ReadOnly', 'BadValue', 'CommunicationFailed',
|
||||
'IsBusy', 'IsError', 'SyntaxError', 'InternalError',
|
||||
'IsBusy', 'IsError', 'ProtocolError', 'InternalError',
|
||||
'CommandRunning', 'Disabled', ]
|
||||
# note: above strings need to be unique in the sense, that none is/or
|
||||
# starts with another
|
||||
@ -83,15 +83,18 @@ def encode_cmd_result(msgobj):
|
||||
q['t'] = format_time(q['t'])
|
||||
return msgobj.result, q
|
||||
|
||||
|
||||
def encode_value_data(vobj):
|
||||
q = vobj.qualifiers.copy()
|
||||
if 't' in q:
|
||||
q['t'] = format_time(q['t'])
|
||||
return vobj.value, q
|
||||
|
||||
|
||||
def encode_error_msg(emsg):
|
||||
# note: result is JSON-ified....
|
||||
return [emsg.origin, dict( (k,getattr(emsg, k)) for k in emsg.ARGS if k != 'origin')]
|
||||
return [emsg.origin, dict((k, getattr(emsg, k))
|
||||
for k in emsg.ARGS if k != 'origin')]
|
||||
|
||||
|
||||
class DemoEncoder(MessageEncoder):
|
||||
@ -163,6 +166,14 @@ class DemoEncoder(MessageEncoder):
|
||||
ENABLEEVENTSREQUEST, DISABLEEVENTSREQUEST)
|
||||
return '\n'.join('%s %d %s' % (HELPREPLY, i + 1, l.strip())
|
||||
for i, l in enumerate(text.split('\n')[:-1]))
|
||||
if isinstance(msg, HeartbeatRequest):
|
||||
if msg.nonce:
|
||||
return 'ping %s' % msg.nonce
|
||||
return 'ping'
|
||||
if isinstance(msg, HeartbeatReply):
|
||||
if msg.nonce:
|
||||
return 'pong %s' % msg.nonce
|
||||
return 'pong'
|
||||
for msgcls, parts in self.ENCODEMAP.items():
|
||||
if isinstance(msg, msgcls):
|
||||
# resolve lambdas
|
||||
@ -183,12 +194,12 @@ class DemoEncoder(MessageEncoder):
|
||||
return IdentifyReply(version_string=encoded)
|
||||
|
||||
return HelpMessage()
|
||||
return ErrorMessage(errorclass='SyntaxError',
|
||||
errorinfo='Regex did not match!',
|
||||
is_request=True)
|
||||
# return ErrorMessage(errorclass='Protocol',
|
||||
# errorinfo='Regex did not match!',
|
||||
# is_request=True)
|
||||
msgtype, msgspec, data = match.groups()
|
||||
if msgspec is None and data:
|
||||
return ErrorMessage(errorclass='InternalError',
|
||||
return ErrorMessage(errorclass='Internal',
|
||||
errorinfo='Regex matched json, but not spec!',
|
||||
is_request=True,
|
||||
origin=encoded)
|
||||
@ -206,10 +217,10 @@ class DemoEncoder(MessageEncoder):
|
||||
errorinfo=[repr(err), str(encoded)],
|
||||
origin=encoded)
|
||||
msg = self.DECODEMAP[msgtype](msgspec, data)
|
||||
msg.setvalue("origin",encoded)
|
||||
msg.setvalue("origin", encoded)
|
||||
return msg
|
||||
return ErrorMessage(
|
||||
errorclass='SyntaxError',
|
||||
errorclass='Protocol',
|
||||
errorinfo='%r: No Such Messagetype defined!' %
|
||||
encoded,
|
||||
is_request=True,
|
||||
|
@ -30,12 +30,26 @@ class SECOPError(RuntimeError):
|
||||
for k, v in kwds.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
def __repr__(self):
|
||||
args = ', '.join(map(repr, self.args))
|
||||
kwds = ', '.join(['%s=%r' % i for i in self.__dict__.items()])
|
||||
res = []
|
||||
if args:
|
||||
res.append(args)
|
||||
if kwds:
|
||||
res.append(kwds)
|
||||
return '%s(%s)' % (self.name, ', '.join(res))
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.__class__.__name__[:-len('Error')]
|
||||
|
||||
|
||||
class InternalError(SECOPError):
|
||||
pass
|
||||
|
||||
|
||||
class ProtocollError(SECOPError):
|
||||
class ProtocolError(SECOPError):
|
||||
pass
|
||||
|
||||
|
||||
@ -56,6 +70,10 @@ class ReadonlyError(SECOPError):
|
||||
pass
|
||||
|
||||
|
||||
class BadValueError(SECOPError):
|
||||
pass
|
||||
|
||||
|
||||
class CommandFailedError(SECOPError):
|
||||
pass
|
||||
|
||||
@ -64,6 +82,18 @@ class InvalidParamValueError(SECOPError):
|
||||
pass
|
||||
|
||||
|
||||
EXCEPTIONS = dict(
|
||||
Internal=InternalError,
|
||||
Protocol=ProtocolError,
|
||||
NoSuchModule=NoSuchModuleError,
|
||||
NoSuchParam=NoSuchParamError,
|
||||
NoSuchCommand=NoSuchCommandError,
|
||||
BadValue=BadValueError,
|
||||
Readonly=ReadonlyError,
|
||||
CommandFailed=CommandFailedError,
|
||||
InvalidParam=InvalidParamValueError,
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Minimal testing of errors....")
|
||||
|
||||
|
@ -69,11 +69,12 @@ class Value(object):
|
||||
devspec = '%s:%s()' % (devspec, self.command)
|
||||
return '%s:Value(%s)' % (devspec, ', '.join(
|
||||
[repr(self.value)] +
|
||||
['%s=%s' % (k, format_time(v) if k=="timestamp" else repr(v)) for k, v in self.qualifiers.items()]))
|
||||
['%s=%s' % (k, format_time(v) if k == "timestamp" else repr(v)) for k, v in self.qualifiers.items()]))
|
||||
|
||||
|
||||
class Request(Message):
|
||||
is_request = True
|
||||
|
||||
def get_reply(self):
|
||||
"""returns a Reply object prefilled with the attributes from this request."""
|
||||
m = Message()
|
||||
@ -93,12 +94,12 @@ class Request(Message):
|
||||
m.origin = self.origin
|
||||
for k in self.ARGS:
|
||||
m.setvalue(k, self.__dict__[k])
|
||||
m.setvalue("errorclass", errorclass[:-5]
|
||||
if errorclass.endswith('rror')
|
||||
else errorclass)
|
||||
m.setvalue("errorclass", errorclass[:-5]
|
||||
if errorclass.endswith('rror')
|
||||
else errorclass)
|
||||
m.setvalue("errorinfo", errorinfo)
|
||||
return m
|
||||
|
||||
|
||||
|
||||
class IdentifyRequest(Request):
|
||||
pass
|
||||
@ -190,6 +191,4 @@ class ErrorMessage(Message):
|
||||
|
||||
|
||||
class HelpMessage(Request):
|
||||
is_reply = True #!sic!
|
||||
|
||||
|
||||
is_reply = True # !sic!
|
||||
|
@ -30,15 +30,14 @@
|
||||
# if a validator does a mapping, it normally maps to the external representation (used for print/log/protocol/...)
|
||||
# to get the internal representation (for the code), call method convert
|
||||
|
||||
|
||||
class ProgrammingError(Exception):
|
||||
pass
|
||||
from errors import ProgrammingError
|
||||
|
||||
|
||||
class Validator(object):
|
||||
# list of tuples: (name, converter)
|
||||
params = []
|
||||
valuetype = float
|
||||
argstr = ''
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
plist = self.params[:]
|
||||
@ -49,7 +48,7 @@ class Validator(object):
|
||||
for pval in args:
|
||||
pname, pconv = plist.pop(0)
|
||||
if pname in kwds:
|
||||
raise ProgrammingError('%s: positional parameter %s als given '
|
||||
raise ProgrammingError('%s: positional parameter %s is given '
|
||||
'as keyword!' % (
|
||||
self.__class__.__name__,
|
||||
pname))
|
||||
@ -67,18 +66,23 @@ class Validator(object):
|
||||
raise ProgrammingError('%s got unknown arguments: %s' % (
|
||||
self.__class__.__name__,
|
||||
', '.join(list(kwds.keys()))))
|
||||
|
||||
def __repr__(self):
|
||||
params = ['%s=%r' % (pn[0], self.__dict__[pn[0]])
|
||||
for pn in self.params]
|
||||
return ('%s(%s)' % (self.__class__.__name__, ', '.join(params)))
|
||||
params = []
|
||||
for pn, pt in self.params:
|
||||
pv = getattr(self, pn)
|
||||
if callable(pv):
|
||||
params.append('%s=%s' % (pn, validator_to_str(pv)))
|
||||
else:
|
||||
params.append('%s=%r' % (pn, pv))
|
||||
self.argstr = ', '.join(params)
|
||||
|
||||
def __call__(self, value):
|
||||
return self.check(self.valuetype(value))
|
||||
|
||||
def convert(self, value):
|
||||
# transforms the 'internal' representation into the 'external'
|
||||
return self.valuetype(value)
|
||||
def __repr__(self):
|
||||
return self.to_string()
|
||||
|
||||
def to_string(self):
|
||||
return ('%s(%s)' % (self.__class__.__name__, self.argstr))
|
||||
|
||||
|
||||
class floatrange(Validator):
|
||||
@ -102,22 +106,6 @@ class intrange(Validator):
|
||||
(value, self.lower, self.upper))
|
||||
|
||||
|
||||
class positive(Validator):
|
||||
|
||||
def check(self, value):
|
||||
if value > 0:
|
||||
return value
|
||||
raise ValueError('Value %r must be > 0!' % value)
|
||||
|
||||
|
||||
class nonnegative(Validator):
|
||||
|
||||
def check(self, value):
|
||||
if value >= 0:
|
||||
return value
|
||||
raise ValueError('Value %r must be >= 0!' % value)
|
||||
|
||||
|
||||
class array(Validator):
|
||||
"""integral amount of data-elements which are described by the SAME validator
|
||||
|
||||
@ -129,12 +117,15 @@ class array(Validator):
|
||||
|
||||
def check(self, values):
|
||||
requested_size = len(values)
|
||||
try:
|
||||
allowed_size = self.size(requested_size)
|
||||
except ValueError as e:
|
||||
raise ValueError(
|
||||
'illegal number of elements %d, need %r: (%s)' %
|
||||
(requested_size, self.size, e))
|
||||
if callable(self.size):
|
||||
try:
|
||||
allowed_size = self.size(requested_size)
|
||||
except ValueError as e:
|
||||
raise ValueError(
|
||||
'illegal number of elements %d, need %r: (%s)' %
|
||||
(requested_size, self.size, e))
|
||||
else:
|
||||
allowed_size = self.size
|
||||
if requested_size != allowed_size:
|
||||
raise ValueError(
|
||||
'need %d elements (got %d)' %
|
||||
@ -152,8 +143,9 @@ class array(Validator):
|
||||
|
||||
|
||||
# more complicated validator may not be able to use validator base class
|
||||
class vector(object):
|
||||
class vector(Validator):
|
||||
"""fixed length, eache element has its own validator"""
|
||||
valuetype = tuple
|
||||
|
||||
def __init__(self, *args):
|
||||
self.validators = args
|
||||
@ -165,33 +157,30 @@ class vector(object):
|
||||
len(self.validators), len(args))
|
||||
return tuple(v(e) for v, e in zip(self.validators, args))
|
||||
|
||||
def __repr__(self):
|
||||
return ('%s(%s)' % (self.__class__.__name__, self.argstr))
|
||||
|
||||
|
||||
class record(object):
|
||||
# XXX: fixme!
|
||||
class record(Validator):
|
||||
"""fixed length, eache element has its own name and validator"""
|
||||
|
||||
def __init__(self, **kwds):
|
||||
self.validators = args
|
||||
self.argstr = ', '.join([validator_to_str(e) for e in kwds.items()])
|
||||
self.validators = kwds
|
||||
self.argstr = ', '.join(
|
||||
['%s=%s' % (e[0], validator_to_str(e[1])) for e in kwds.items()])
|
||||
|
||||
def __call__(self, arg):
|
||||
def __call__(self, **args):
|
||||
if len(args) != len(self.validators):
|
||||
raise ValueError('Vector: need exactly %d elementes (got %d)' %
|
||||
len(self.validators), len(args))
|
||||
return tuple(v(e) for v, e in zip(self.validators, args))
|
||||
|
||||
def __repr__(self):
|
||||
return ('%s(%s)' % (self.__class__.__name__, self.argstr))
|
||||
|
||||
|
||||
class oneof(object):
|
||||
class oneof(Validator):
|
||||
"""needs to comply with one of the given validators/values"""
|
||||
|
||||
def __init__(self, *args):
|
||||
self.oneof = args
|
||||
self.argstr = ', '.join([validator_to_str(e) for e in args])
|
||||
self.argstr = ', '.join(
|
||||
[validator_to_str(e) if callable(e) else repr(e) for e in args])
|
||||
|
||||
def __call__(self, arg):
|
||||
for v in self.oneof:
|
||||
@ -206,11 +195,8 @@ class oneof(object):
|
||||
return v
|
||||
raise ValueError('Oneof: %r should be one of: %s' % (arg, self.argstr))
|
||||
|
||||
def __repr__(self):
|
||||
return ('%s(%s)' % (self.__class__.__name__, self.argstr))
|
||||
|
||||
|
||||
class enum(object):
|
||||
class enum(Validator):
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
self.mapping = {}
|
||||
@ -226,8 +212,11 @@ class enum(object):
|
||||
self.mapping[args.pop(0)] = i
|
||||
# generate reverse mapping too for use by protocol
|
||||
self.revmapping = {}
|
||||
for k, v in self.mapping.items():
|
||||
params = []
|
||||
for k, v in sorted(self.mapping.items(), key=lambda x: x[1]):
|
||||
self.revmapping[v] = k
|
||||
params.append('%s=%r' % (k, v))
|
||||
self.argstr = ', '.join(params)
|
||||
|
||||
def __call__(self, obj):
|
||||
try:
|
||||
@ -238,22 +227,68 @@ class enum(object):
|
||||
return obj
|
||||
if obj in self.revmapping:
|
||||
return self.revmapping[obj]
|
||||
raise ValueError("%r should be one of %r" %
|
||||
(obj, list(self.mapping.keys())))
|
||||
|
||||
def __repr__(self):
|
||||
params = ['%s=%r' % (mname, mval)
|
||||
for mname, mval in self.mapping.items()]
|
||||
return ('%s(%s)' % (self.__class__.__name__, ', '.join(params)))
|
||||
raise ValueError("%r should be one of %s" %
|
||||
(obj, ', '.join(map(repr, self.mapping.keys()))))
|
||||
|
||||
def convert(self, arg):
|
||||
return self.mapping.get(arg, arg)
|
||||
|
||||
|
||||
# Validators without parameters:
|
||||
def positive(value=Ellipsis):
|
||||
if value != Ellipsis:
|
||||
if value > 0:
|
||||
return value
|
||||
raise ValueError('Value %r must be > 0!' % value)
|
||||
return -1e-38 # small number > 0
|
||||
positive.__repr__ = lambda x: validator_to_str(x)
|
||||
|
||||
|
||||
def nonnegative(value=Ellipsis):
|
||||
if value != Ellipsis:
|
||||
if value >= 0:
|
||||
return value
|
||||
raise ValueError('Value %r must be >= 0!' % value)
|
||||
return 0.0
|
||||
nonnegative.__repr__ = lambda x: validator_to_str(x)
|
||||
|
||||
|
||||
# helpers
|
||||
|
||||
def validator_to_str(validator):
|
||||
return str(validator) if not isinstance(validator, type) \
|
||||
else validator.__name__
|
||||
if isinstance(validator, Validator):
|
||||
return validator.to_string()
|
||||
if hasattr(validator, 'func_name'):
|
||||
return getattr(validator, 'func_name')
|
||||
for s in 'int str float'.split(' '):
|
||||
t = eval(s)
|
||||
if validator == t or isinstance(validator, t):
|
||||
return s
|
||||
print "##########", type(validator), repr(validator)
|
||||
|
||||
|
||||
# XXX: better use a mapping here!
|
||||
def validator_from_str(validator_str):
|
||||
return eval(validator_str)
|
||||
|
||||
if __name__ == '__main__':
|
||||
print "minimal testing: validators"
|
||||
for val, good, bad in [(floatrange(3.09, 5.47), 4.13, 9.27),
|
||||
(intrange(3, 5), 4, 8),
|
||||
(array(size=3, datatype=int), (1, 2, 3), (1, 2, 3, 4)),
|
||||
(vector(int, int), (12, 6), (1.23, 'X')),
|
||||
(oneof('a', 'b', 'c', 1), 'b', 'x'),
|
||||
#(record(a=int, b=float), dict(a=2,b=3.97), dict(c=9,d='X')),
|
||||
(positive, 2, 0),
|
||||
(nonnegative, 0, -1),
|
||||
(enum(a=1, b=20), 1, 12),
|
||||
]:
|
||||
print validator_to_str(val), repr(validator_from_str(validator_to_str(val)))
|
||||
print val(good), 'OK'
|
||||
try:
|
||||
val(bad)
|
||||
print "FAIL"
|
||||
raise ProgrammingError
|
||||
except Exception as e:
|
||||
print bad, e, 'OK'
|
||||
print
|
||||
|
Loading…
x
Reference in New Issue
Block a user