replace validators with datatypes
Change-Id: I446c4e14e24afa3f65e79c8b6e07eec3271532b0
This commit is contained in:
parent
a87e568b55
commit
bc3253a01a
@ -30,8 +30,8 @@ import Queue
|
|||||||
|
|
||||||
import mlzlog
|
import mlzlog
|
||||||
|
|
||||||
from secop.validators import validator_from_str
|
from secop.datatypes import get_datatype
|
||||||
from secop.lib import mkthread
|
from secop.lib import mkthread, formatException
|
||||||
from secop.lib.parsing import parse_time, format_time
|
from secop.lib.parsing import parse_time, format_time
|
||||||
from secop.protocol.encoding import ENCODERS
|
from secop.protocol.encoding import ENCODERS
|
||||||
from secop.protocol.framing import FRAMERS
|
from secop.protocol.framing import FRAMERS
|
||||||
@ -271,6 +271,7 @@ class Client(object):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
# keep as string
|
# keep as string
|
||||||
data = json_data
|
data = json_data
|
||||||
|
# print formatException()
|
||||||
return msgtype, spec, data
|
return msgtype, spec, data
|
||||||
|
|
||||||
def _handle_event(self, spec, data):
|
def _handle_event(self, spec, data):
|
||||||
@ -310,9 +311,9 @@ class Client(object):
|
|||||||
|
|
||||||
for module, moduleData in self.describing_data['modules'].items():
|
for module, moduleData in self.describing_data['modules'].items():
|
||||||
for parameter, parameterData in moduleData['parameters'].items():
|
for parameter, parameterData in moduleData['parameters'].items():
|
||||||
validator = validator_from_str(parameterData['validator'])
|
datatype = get_datatype(parameterData['datatype'])
|
||||||
self.describing_data['modules'][module]['parameters'] \
|
self.describing_data['modules'][module]['parameters'] \
|
||||||
[parameter]['validator'] = validator
|
[parameter]['datatype'] = datatype
|
||||||
|
|
||||||
def register_callback(self, module, parameter, cb):
|
def register_callback(self, module, parameter, cb):
|
||||||
self.log.debug('registering callback %r for %s:%s' %
|
self.log.debug('registering callback %r for %s:%s' %
|
||||||
@ -440,10 +441,10 @@ class Client(object):
|
|||||||
return self.communicate('read', '%s:%s' % (module, parameter))
|
return self.communicate('read', '%s:%s' % (module, parameter))
|
||||||
|
|
||||||
def setParameter(self, module, parameter, value):
|
def setParameter(self, module, parameter, value):
|
||||||
validator = self._getDescribingParameterData(module,
|
datatype = self._getDescribingParameterData(module,
|
||||||
parameter)['validator']
|
parameter)['datatype']
|
||||||
|
|
||||||
value = validator(value)
|
value = datatype.export(datatype.validate(value))
|
||||||
self.communicate('change', '%s:%s' % (module, parameter), value)
|
self.communicate('change', '%s:%s' % (module, parameter), value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -21,14 +21,6 @@
|
|||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
"""Define validated data types."""
|
"""Define validated data types."""
|
||||||
|
|
||||||
# a Validator returns a validated object or raises an ValueError
|
|
||||||
# also validators should have a __repr__ returning a 'python' string
|
|
||||||
# which recreates them
|
|
||||||
|
|
||||||
# if a validator does a mapping, it normally maps to the
|
|
||||||
# internal representation with method :meth:`validate`
|
|
||||||
# to get the external representation (aöso for logging),
|
|
||||||
# call method :meth:`export`
|
|
||||||
|
|
||||||
|
|
||||||
from .errors import ProgrammingError
|
from .errors import ProgrammingError
|
||||||
@ -56,10 +48,10 @@ class FloatRange(DataType):
|
|||||||
"""Restricted float type"""
|
"""Restricted float type"""
|
||||||
|
|
||||||
def __init__(self, min=None, max=None):
|
def __init__(self, min=None, max=None):
|
||||||
self.min = float('-Inf') if min is None else float(min)
|
self.min = None if min is None else float(min)
|
||||||
self.max = float('+Inf') if max is None else float(max)
|
self.max = None if max is None else float(max)
|
||||||
# note: as we may compare to Inf all comparisons would be false
|
# note: as we may compare to Inf all comparisons would be false
|
||||||
if self.min <= self.max:
|
if (self.min or float('-inf')) <= (self.max or float('+inf')):
|
||||||
self.as_json = ['double', min, max]
|
self.as_json = ['double', min, max]
|
||||||
else:
|
else:
|
||||||
raise ValueError('Max must be larger then min!')
|
raise ValueError('Max must be larger then min!')
|
||||||
@ -67,15 +59,25 @@ class FloatRange(DataType):
|
|||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
try:
|
try:
|
||||||
value = float(value)
|
value = float(value)
|
||||||
|
except:
|
||||||
|
raise ValueError('Can not validate %r to float' % value)
|
||||||
|
if self.min is not None and value < self.min:
|
||||||
|
raise ValueError('%r should not be less then %s' % (value, self.min))
|
||||||
|
if self.max is not None and value > self.max:
|
||||||
|
raise ValueError('%r should not be greater than %s' % (value, self.max))
|
||||||
|
if None in (self.min, self.max):
|
||||||
|
return value
|
||||||
if self.min <= value <= self.max:
|
if self.min <= value <= self.max:
|
||||||
return value
|
return value
|
||||||
raise ValueError('%r should be an float between %.3f and %.3f' %
|
raise ValueError('%r should be an float between %.3f and %.3f' %
|
||||||
(value, self.min, self.max))
|
(value, self.min, self.max))
|
||||||
except:
|
|
||||||
raise ValueError('Can not validate %r to float' % value)
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "FloatRange(%f, %f)" % (self.min, self.max)
|
if self.max != None:
|
||||||
|
return "FloatRange(%r, %r)" % (float('-inf') if self.min is None else self.min, self.max)
|
||||||
|
if self.min != None:
|
||||||
|
return "FloatRange(%r)" % self.min
|
||||||
|
return "FloatRange()"
|
||||||
|
|
||||||
def export(self, value):
|
def export(self, value):
|
||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
@ -105,7 +107,11 @@ class IntRange(DataType):
|
|||||||
raise ValueError('Can not validate %r to int' % value)
|
raise ValueError('Can not validate %r to int' % value)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
if self.max is not None:
|
||||||
return "IntRange(%d, %d)" % (self.min, self.max)
|
return "IntRange(%d, %d)" % (self.min, self.max)
|
||||||
|
if self.min is not None:
|
||||||
|
return "IntRange(%d)" % self.min
|
||||||
|
return "IntRange(%d)" % self.min
|
||||||
|
|
||||||
def export(self, value):
|
def export(self, value):
|
||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
@ -119,7 +125,8 @@ class EnumType(DataType):
|
|||||||
self.entries = {}
|
self.entries = {}
|
||||||
num = 0
|
num = 0
|
||||||
for arg in args:
|
for arg in args:
|
||||||
if type(args) != str:
|
if type(arg) != str:
|
||||||
|
print arg, type(arg)
|
||||||
raise ValueError('EnumType entries MUST be strings!')
|
raise ValueError('EnumType entries MUST be strings!')
|
||||||
self.entries[num] = arg
|
self.entries[num] = arg
|
||||||
num += 1
|
num += 1
|
||||||
@ -138,7 +145,7 @@ class EnumType(DataType):
|
|||||||
self.as_json = ['enum', self.reversed.copy()]
|
self.as_json = ['enum', self.reversed.copy()]
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "EnumType(%s)" % ', '.join(['%r=%d' % (v,k) for k,v in self.entries.items()])
|
return "EnumType(%s)" % ', '.join(['%s=%d' % (v,k) for k,v in self.entries.items()])
|
||||||
|
|
||||||
def export(self, value):
|
def export(self, value):
|
||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
@ -169,8 +176,10 @@ class BLOBType(DataType):
|
|||||||
raise ValueError('maxsize must be bigger than minsize!')
|
raise ValueError('maxsize must be bigger than minsize!')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if self.minsize or self.maxsize:
|
if self.maxsize:
|
||||||
return 'BLOB(%d, %s)' % (self.minsize, self.maxsize)
|
return 'BLOB(%s, %s)' % (str(self.minsize), str(self.maxsize))
|
||||||
|
if self.minsize:
|
||||||
|
return 'BLOB(%d)' % self.minsize
|
||||||
return 'BLOB()'
|
return 'BLOB()'
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
@ -203,7 +212,11 @@ class StringType(DataType):
|
|||||||
raise ValueError('maxsize must be bigger than minsize!')
|
raise ValueError('maxsize must be bigger than minsize!')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'StringType(%d, %s)' % (self.minsize, self.maxsize)
|
if self.maxsize:
|
||||||
|
return 'StringType(%s, %s)' % (str(self.minsize), str(self.maxsize))
|
||||||
|
if self.minsize:
|
||||||
|
return 'StringType(%d)' % str(self.minsize)
|
||||||
|
return 'StringType()'
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
"""return the validated (internal) value or raise"""
|
"""return the validated (internal) value or raise"""
|
||||||
@ -223,6 +236,7 @@ class StringType(DataType):
|
|||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
return '%s' % value
|
return '%s' % value
|
||||||
|
|
||||||
|
|
||||||
# Bool is a special enum
|
# Bool is a special enum
|
||||||
class BoolType(DataType):
|
class BoolType(DataType):
|
||||||
as_json = ['bool']
|
as_json = ['bool']
|
||||||
@ -265,6 +279,9 @@ class ArrayOf(DataType):
|
|||||||
if self.minsize is not None and self.maxsize is not None and self.minsize > self.maxsize:
|
if self.minsize is not None and self.maxsize is not None and self.minsize > self.maxsize:
|
||||||
raise ValueError('Maximum size must be >= Minimum size')
|
raise ValueError('Maximum size must be >= Minimum size')
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'ArrayOf(%s, %s, %s)' % (repr(self.subtype), self.minsize, self.maxsize)
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
"""validate a external representation to an internal one"""
|
"""validate a external representation to an internal one"""
|
||||||
if isinstance(value, (tuple, list)):
|
if isinstance(value, (tuple, list)):
|
||||||
@ -292,6 +309,9 @@ class TupleOf(DataType):
|
|||||||
self.subtypes = subtypes
|
self.subtypes = subtypes
|
||||||
self.as_json = ['tuple', [subtype.as_json for subtype in subtypes]]
|
self.as_json = ['tuple', [subtype.as_json for subtype in subtypes]]
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'TupleOf(%s)' % ', '.join([repr(st) for st in self.subtypes])
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
"""return the validated value or raise"""
|
"""return the validated value or raise"""
|
||||||
# keep the ordering!
|
# keep the ordering!
|
||||||
@ -320,6 +340,9 @@ class StructOf(DataType):
|
|||||||
self.named_subtypes = named_subtypes
|
self.named_subtypes = named_subtypes
|
||||||
self.as_json = ['struct', dict((n,s.as_json) for n,s in named_subtypes.items())]
|
self.as_json = ['struct', dict((n,s.as_json) for n,s in named_subtypes.items())]
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'StructOf(%s)' % ', '.join(['%s=%s'%(n,repr(st)) for n,st in self.named_subtypes.iteritems()])
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
"""return the validated value or raise"""
|
"""return the validated value or raise"""
|
||||||
try:
|
try:
|
||||||
@ -358,10 +381,14 @@ DATATYPES = dict(
|
|||||||
|
|
||||||
# probably not needed...
|
# probably not needed...
|
||||||
def export_datatype(datatype):
|
def export_datatype(datatype):
|
||||||
|
if datatype is None:
|
||||||
|
return datatype
|
||||||
return datatype.as_json
|
return datatype.as_json
|
||||||
|
|
||||||
# important for getting the right datatype from formerly jsonified descr.
|
# important for getting the right datatype from formerly jsonified descr.
|
||||||
def get_datatype(json):
|
def get_datatype(json):
|
||||||
|
if json is None:
|
||||||
|
return json
|
||||||
if not isinstance(json, list):
|
if not isinstance(json, list):
|
||||||
raise ValueError('Argument must be a properly formatted list!')
|
raise ValueError('Argument must be a properly formatted list!')
|
||||||
if len(json)<1:
|
if len(json)<1:
|
||||||
|
@ -34,7 +34,8 @@ import threading
|
|||||||
from secop.lib.parsing import format_time
|
from secop.lib.parsing import format_time
|
||||||
from secop.errors import ConfigError, ProgrammingError
|
from secop.errors import ConfigError, ProgrammingError
|
||||||
from secop.protocol import status
|
from secop.protocol import status
|
||||||
from secop.validators import enum, vector, floatrange, validator_to_str
|
from secop.datatypes import DataType, EnumType, TupleOf, StringType, FloatRange, export_datatype
|
||||||
|
|
||||||
|
|
||||||
EVENT_ONLY_ON_CHANGED_VALUES = False
|
EVENT_ONLY_ON_CHANGED_VALUES = False
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ class PARAM(object):
|
|||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
description,
|
description,
|
||||||
validator=float,
|
datatype=None,
|
||||||
default=Ellipsis,
|
default=Ellipsis,
|
||||||
unit=None,
|
unit=None,
|
||||||
readonly=True,
|
readonly=True,
|
||||||
@ -59,8 +60,14 @@ class PARAM(object):
|
|||||||
# make a copy of a PARAM object
|
# make a copy of a PARAM object
|
||||||
self.__dict__.update(description.__dict__)
|
self.__dict__.update(description.__dict__)
|
||||||
return
|
return
|
||||||
|
if not isinstance(datatype, DataType):
|
||||||
|
if issubclass(datatype, DataType):
|
||||||
|
# goodie: make an instance from a class (forgotten ()???)
|
||||||
|
datatype = datatype()
|
||||||
|
else:
|
||||||
|
raise ValueError('Datatype MUST be from datatypes!')
|
||||||
self.description = description
|
self.description = description
|
||||||
self.validator = validator
|
self.datatype = datatype
|
||||||
self.default = default
|
self.default = default
|
||||||
self.unit = unit
|
self.unit = unit
|
||||||
self.readonly = readonly
|
self.readonly = readonly
|
||||||
@ -79,7 +86,7 @@ class PARAM(object):
|
|||||||
res = dict(
|
res = dict(
|
||||||
description=self.description,
|
description=self.description,
|
||||||
readonly=self.readonly,
|
readonly=self.readonly,
|
||||||
validator=validator_to_str(self.validator),
|
datatype=export_datatype(self.datatype),
|
||||||
)
|
)
|
||||||
if self.unit:
|
if self.unit:
|
||||||
res['unit'] = self.unit
|
res['unit'] = self.unit
|
||||||
@ -98,9 +105,9 @@ class CMD(object):
|
|||||||
def __init__(self, description, arguments, result):
|
def __init__(self, description, arguments, result):
|
||||||
# descriptive text for humans
|
# descriptive text for humans
|
||||||
self.description = description
|
self.description = description
|
||||||
# list of validators for arguments
|
# list of datatypes for arguments
|
||||||
self.arguments = arguments
|
self.arguments = arguments
|
||||||
# validator for result
|
# datatype for result
|
||||||
self.resulttype = result
|
self.resulttype = result
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@ -111,8 +118,8 @@ class CMD(object):
|
|||||||
# used for serialisation only
|
# used for serialisation only
|
||||||
return dict(
|
return dict(
|
||||||
description=self.description,
|
description=self.description,
|
||||||
arguments=repr(self.arguments),
|
arguments=map(export_datatype, self.arguments),
|
||||||
resulttype=repr(self.resulttype), )
|
resulttype=export_datatype(self.resulttype), )
|
||||||
|
|
||||||
# Meta class
|
# Meta class
|
||||||
# warning: MAGIC!
|
# warning: MAGIC!
|
||||||
@ -163,7 +170,7 @@ class DeviceMeta(type):
|
|||||||
def wrapped_wfunc(self, value, pname=pname, wfunc=wfunc):
|
def wrapped_wfunc(self, value, pname=pname, wfunc=wfunc):
|
||||||
self.log.debug("wfunc: set %s to %r" % (pname, value))
|
self.log.debug("wfunc: set %s to %r" % (pname, value))
|
||||||
pobj = self.PARAMS[pname]
|
pobj = self.PARAMS[pname]
|
||||||
value = pobj.validator(value) if pobj.validator else value
|
value = pobj.datatype.validate(value) if pobj.datatype else value
|
||||||
if wfunc:
|
if wfunc:
|
||||||
value = wfunc(self, value) or value
|
value = wfunc(self, value) or value
|
||||||
# XXX: use setattr or direct manipulation
|
# XXX: use setattr or direct manipulation
|
||||||
@ -180,7 +187,7 @@ class DeviceMeta(type):
|
|||||||
|
|
||||||
def setter(self, value, pname=pname):
|
def setter(self, value, pname=pname):
|
||||||
pobj = self.PARAMS[pname]
|
pobj = self.PARAMS[pname]
|
||||||
value = pobj.validator(value) if pobj.validator else value
|
value = pobj.datatype.validate(value) if pobj.datatype else value
|
||||||
pobj.timestamp = time.time()
|
pobj.timestamp = time.time()
|
||||||
if not EVENT_ONLY_ON_CHANGED_VALUES or (value != pobj.value):
|
if not EVENT_ONLY_ON_CHANGED_VALUES or (value != pobj.value):
|
||||||
pobj.value = value
|
pobj.value = value
|
||||||
@ -309,14 +316,14 @@ class Device(object):
|
|||||||
self.PARAMS[k] = param_type(self.PARAMS[k])
|
self.PARAMS[k] = param_type(self.PARAMS[k])
|
||||||
|
|
||||||
# now 'apply' config:
|
# now 'apply' config:
|
||||||
# pass values through the validators and store as attributes
|
# pass values through the datatypes and store as attributes
|
||||||
for k, v in cfgdict.items():
|
for k, v in cfgdict.items():
|
||||||
# apply validator, complain if type does not fit
|
# apply datatype, complain if type does not fit
|
||||||
validator = self.PARAMS[k].validator
|
datatype = self.PARAMS[k].datatype
|
||||||
if validator is not None:
|
if datatype is not None:
|
||||||
# only check if validator given
|
# only check if datatype given
|
||||||
try:
|
try:
|
||||||
v = validator(v)
|
v = datatype.validate(v)
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
raise ConfigError('Device %s: config parameter %r:\n%r' %
|
raise ConfigError('Device %s: config parameter %r:\n%r' %
|
||||||
(self.name, k, e))
|
(self.name, k, e))
|
||||||
@ -338,19 +345,20 @@ class Readable(Device):
|
|||||||
providing the readonly parameter 'value' and 'status'
|
providing the readonly parameter 'value' and 'status'
|
||||||
"""
|
"""
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
'value': PARAM('current value of the device', readonly=True, default=0.),
|
'value': PARAM('current value of the device', readonly=True, default=0.,
|
||||||
|
datatype=FloatRange()),
|
||||||
'pollinterval': PARAM('sleeptime between polls', default=5,
|
'pollinterval': PARAM('sleeptime between polls', default=5,
|
||||||
readonly=False, validator=floatrange(0.1, 120), ),
|
readonly=False, datatype=FloatRange(0.1, 120), ),
|
||||||
'status': PARAM('current status of the device', default=(status.OK, ''),
|
'status': PARAM('current status of the device', default=(status.OK, ''),
|
||||||
validator=vector(
|
datatype=TupleOf(
|
||||||
enum(**{
|
EnumType(**{
|
||||||
'IDLE': status.OK,
|
'IDLE': status.OK,
|
||||||
'BUSY': status.BUSY,
|
'BUSY': status.BUSY,
|
||||||
'WARN': status.WARN,
|
'WARN': status.WARN,
|
||||||
'UNSTABLE': status.UNSTABLE,
|
'UNSTABLE': status.UNSTABLE,
|
||||||
'ERROR': status.ERROR,
|
'ERROR': status.ERROR,
|
||||||
'UNKNOWN': status.UNKNOWN
|
'UNKNOWN': status.UNKNOWN
|
||||||
}), str),
|
}), StringType() ),
|
||||||
readonly=True),
|
readonly=True),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,6 +386,7 @@ class Driveable(Readable):
|
|||||||
"""
|
"""
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
'target': PARAM('target value of the device', default=0., readonly=False,
|
'target': PARAM('target value of the device', default=0., readonly=False,
|
||||||
|
datatype=FloatRange(),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
# XXX: CMDS ???? auto deriving working well enough?
|
# XXX: CMDS ???? auto deriving working well enough?
|
||||||
|
@ -27,7 +27,7 @@ import threading
|
|||||||
|
|
||||||
from secop.devices.core import Driveable, CMD, PARAM
|
from secop.devices.core import Driveable, CMD, PARAM
|
||||||
from secop.protocol import status
|
from secop.protocol import status
|
||||||
from secop.validators import floatrange, positive, enum, nonnegative, vector
|
from secop.datatypes import FloatRange, EnumType, TupleOf
|
||||||
from secop.lib import clamp, mkthread
|
from secop.lib import clamp, mkthread
|
||||||
|
|
||||||
|
|
||||||
@ -44,81 +44,82 @@ class Cryostat(CryoBase):
|
|||||||
"""
|
"""
|
||||||
PARAMS = dict(
|
PARAMS = dict(
|
||||||
jitter=PARAM("amount of random noise on readout values",
|
jitter=PARAM("amount of random noise on readout values",
|
||||||
validator=floatrange(0, 1), unit="K",
|
datatype=FloatRange(0, 1), unit="K",
|
||||||
default=0.1, readonly=False, export=False,
|
default=0.1, readonly=False, export=False,
|
||||||
),
|
),
|
||||||
T_start=PARAM("starting temperature for simulation",
|
T_start=PARAM("starting temperature for simulation",
|
||||||
validator=positive, default=10,
|
datatype=FloatRange(0), default=10,
|
||||||
export=False,
|
export=False,
|
||||||
),
|
),
|
||||||
looptime=PARAM("timestep for simulation",
|
looptime=PARAM("timestep for simulation",
|
||||||
validator=floatrange(0.01, 10), unit="s", default=1,
|
datatype=FloatRange(0.01, 10), unit="s", default=1,
|
||||||
readonly=False, export=False,
|
readonly=False, export=False,
|
||||||
),
|
),
|
||||||
ramp=PARAM("ramping speed of the setpoint",
|
ramp=PARAM("ramping speed of the setpoint",
|
||||||
validator=floatrange(0, 1e3), unit="K/min", default=1,
|
datatype=FloatRange(0, 1e3), unit="K/min", default=1,
|
||||||
readonly=False,
|
readonly=False,
|
||||||
),
|
),
|
||||||
setpoint=PARAM("current setpoint during ramping else target",
|
setpoint=PARAM("current setpoint during ramping else target",
|
||||||
validator=float, default=1, unit='K',
|
datatype=FloatRange(), default=1, unit='K',
|
||||||
),
|
),
|
||||||
maxpower=PARAM("Maximum heater power",
|
maxpower=PARAM("Maximum heater power",
|
||||||
validator=nonnegative, default=1, unit="W",
|
datatype=FloatRange(0), default=1, unit="W",
|
||||||
readonly=False,
|
readonly=False,
|
||||||
group='heater_settings',
|
group='heater_settings',
|
||||||
),
|
),
|
||||||
heater=PARAM("current heater setting",
|
heater=PARAM("current heater setting",
|
||||||
validator=floatrange(0, 100), default=0, unit="%",
|
datatype=FloatRange(0, 100), default=0, unit="%",
|
||||||
group='heater_settings',
|
group='heater_settings',
|
||||||
),
|
),
|
||||||
heaterpower=PARAM("current heater power",
|
heaterpower=PARAM("current heater power",
|
||||||
validator=nonnegative, default=0, unit="W",
|
datatype=FloatRange(0), default=0, unit="W",
|
||||||
group='heater_settings',
|
group='heater_settings',
|
||||||
),
|
),
|
||||||
target=PARAM("target temperature",
|
target=PARAM("target temperature",
|
||||||
validator=nonnegative, default=0, unit="K",
|
datatype=FloatRange(0), default=0, unit="K",
|
||||||
readonly=False,
|
readonly=False,
|
||||||
),
|
),
|
||||||
value=PARAM("regulation temperature",
|
value=PARAM("regulation temperature",
|
||||||
validator=nonnegative, default=0, unit="K",
|
datatype=FloatRange(0), default=0, unit="K",
|
||||||
),
|
),
|
||||||
pid=PARAM("regulation coefficients",
|
pid=PARAM("regulation coefficients",
|
||||||
validator=vector(nonnegative, floatrange(
|
datatype=TupleOf(FloatRange(0), FloatRange(0, 100),
|
||||||
0, 100), floatrange(0, 100)),
|
FloatRange(0, 100)),
|
||||||
default=(40, 10, 2), readonly=False,
|
default=(40, 10, 2), readonly=False,
|
||||||
group='pid',
|
group='pid',
|
||||||
),
|
),
|
||||||
p=PARAM("regulation coefficient 'p'",
|
p=PARAM("regulation coefficient 'p'",
|
||||||
validator=nonnegative, default=40, unit="%/K", readonly=False,
|
datatype=FloatRange(0), default=40, unit="%/K", readonly=False,
|
||||||
group='pid',
|
group='pid',
|
||||||
),
|
),
|
||||||
i=PARAM("regulation coefficient 'i'",
|
i=PARAM("regulation coefficient 'i'",
|
||||||
validator=floatrange(0, 100), default=10, readonly=False,
|
datatype=FloatRange(0, 100), default=10, readonly=False,
|
||||||
group='pid',
|
group='pid',
|
||||||
),
|
),
|
||||||
d=PARAM("regulation coefficient 'd'",
|
d=PARAM("regulation coefficient 'd'",
|
||||||
validator=floatrange(0, 100), default=2, readonly=False,
|
datatype=FloatRange(0, 100), default=2, readonly=False,
|
||||||
group='pid',
|
group='pid',
|
||||||
),
|
),
|
||||||
mode=PARAM("mode of regulation",
|
mode=PARAM("mode of regulation",
|
||||||
validator=enum('ramp', 'pid', 'openloop'), default='ramp',
|
datatype=EnumType('ramp', 'pid', 'openloop'),
|
||||||
|
default='ramp',
|
||||||
readonly=False,
|
readonly=False,
|
||||||
),
|
),
|
||||||
pollinterval=PARAM("polling interval",
|
pollinterval=PARAM("polling interval",
|
||||||
validator=positive, default=5,
|
datatype=FloatRange(0), default=5,
|
||||||
),
|
),
|
||||||
tolerance=PARAM("temperature range for stability checking",
|
tolerance=PARAM("temperature range for stability checking",
|
||||||
validator=floatrange(0, 100), default=0.1, unit='K',
|
datatype=FloatRange(0, 100), default=0.1, unit='K',
|
||||||
readonly=False,
|
readonly=False,
|
||||||
group='stability',
|
group='stability',
|
||||||
),
|
),
|
||||||
window=PARAM("time window for stability checking",
|
window=PARAM("time window for stability checking",
|
||||||
validator=floatrange(1, 900), default=30, unit='s',
|
datatype=FloatRange(1, 900), default=30, unit='s',
|
||||||
readonly=False,
|
readonly=False,
|
||||||
group='stability',
|
group='stability',
|
||||||
),
|
),
|
||||||
timeout=PARAM("max waiting time for stabilisation check",
|
timeout=PARAM("max waiting time for stabilisation check",
|
||||||
validator=floatrange(1, 36000), default=900, unit='s',
|
datatype=FloatRange(1, 36000), default=900, unit='s',
|
||||||
readonly=False,
|
readonly=False,
|
||||||
group='stability',
|
group='stability',
|
||||||
),
|
),
|
||||||
|
@ -25,7 +25,7 @@ import random
|
|||||||
import threading
|
import threading
|
||||||
|
|
||||||
from secop.devices.core import Readable, Driveable, PARAM
|
from secop.devices.core import Readable, Driveable, PARAM
|
||||||
from secop.validators import *
|
from secop.datatypes import EnumType, FloatRange, IntRange, ArrayOf, StringType, TupleOf, StructOf, BoolType
|
||||||
from secop.protocol import status
|
from secop.protocol import status
|
||||||
|
|
||||||
|
|
||||||
@ -34,18 +34,18 @@ class Switch(Driveable):
|
|||||||
"""
|
"""
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
'value': PARAM('current state (on or off)',
|
'value': PARAM('current state (on or off)',
|
||||||
validator=enum(on=1, off=0), default=0,
|
datatype=EnumType(on=1, off=0), default=0,
|
||||||
),
|
),
|
||||||
'target': PARAM('wanted state (on or off)',
|
'target': PARAM('wanted state (on or off)',
|
||||||
validator=enum(on=1, off=0), default=0,
|
datatype=EnumType(on=1, off=0), default=0,
|
||||||
readonly=False,
|
readonly=False,
|
||||||
),
|
),
|
||||||
'switch_on_time': PARAM('seconds to wait after activating the switch',
|
'switch_on_time': PARAM('seconds to wait after activating the switch',
|
||||||
validator=floatrange(0, 60), unit='s',
|
datatype=FloatRange(0, 60), unit='s',
|
||||||
default=10, export=False,
|
default=10, export=False,
|
||||||
),
|
),
|
||||||
'switch_off_time': PARAM('cool-down time in seconds',
|
'switch_off_time': PARAM('cool-down time in seconds',
|
||||||
validator=floatrange(0, 60), unit='s',
|
datatype=FloatRange(0, 60), unit='s',
|
||||||
default=10, export=False,
|
default=10, export=False,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@ -99,22 +99,22 @@ class MagneticField(Driveable):
|
|||||||
"""
|
"""
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
'value': PARAM('current field in T',
|
'value': PARAM('current field in T',
|
||||||
unit='T', validator=floatrange(-15, 15), default=0,
|
unit='T', datatype=FloatRange(-15, 15), default=0,
|
||||||
),
|
),
|
||||||
'target': PARAM('target field in T',
|
'target': PARAM('target field in T',
|
||||||
unit='T', validator=floatrange(-15, 15), default=0,
|
unit='T', datatype=FloatRange(-15, 15), default=0,
|
||||||
readonly=False,
|
readonly=False,
|
||||||
),
|
),
|
||||||
'ramp': PARAM('ramping speed',
|
'ramp': PARAM('ramping speed',
|
||||||
unit='T/min', validator=floatrange(0, 1), default=0.1,
|
unit='T/min', datatype=FloatRange(0, 1), default=0.1,
|
||||||
readonly=False,
|
readonly=False,
|
||||||
),
|
),
|
||||||
'mode': PARAM('what to do after changing field',
|
'mode': PARAM('what to do after changing field',
|
||||||
default=1, validator=enum(persistent=1, hold=0),
|
default=1, datatype=EnumType(persistent=1, hold=0),
|
||||||
readonly=False,
|
readonly=False,
|
||||||
),
|
),
|
||||||
'heatswitch': PARAM('name of heat switch device',
|
'heatswitch': PARAM('name of heat switch device',
|
||||||
validator=str, export=False,
|
datatype=StringType(), export=False,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,10 +183,10 @@ class CoilTemp(Readable):
|
|||||||
"""
|
"""
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
'value': PARAM('Coil temperatur',
|
'value': PARAM('Coil temperatur',
|
||||||
unit='K', validator=float, default=0,
|
unit='K', datatype=FloatRange(), default=0,
|
||||||
),
|
),
|
||||||
'sensor': PARAM("Sensor number or calibration id",
|
'sensor': PARAM("Sensor number or calibration id",
|
||||||
validator=str, readonly=True,
|
datatype=StringType(), readonly=True,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,13 +199,13 @@ class SampleTemp(Driveable):
|
|||||||
"""
|
"""
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
'value': PARAM('Sample temperature',
|
'value': PARAM('Sample temperature',
|
||||||
unit='K', validator=float, default=10,
|
unit='K', datatype=FloatRange(), default=10,
|
||||||
),
|
),
|
||||||
'sensor': PARAM("Sensor number or calibration id",
|
'sensor': PARAM("Sensor number or calibration id",
|
||||||
validator=str, readonly=True,
|
datatype=StringType(), readonly=True,
|
||||||
),
|
),
|
||||||
'ramp': PARAM('moving speed in K/min',
|
'ramp': PARAM('moving speed in K/min',
|
||||||
validator=floatrange(0, 100), unit='K/min', default=0.1,
|
datatype=FloatRange(0, 100), unit='K/min', default=0.1,
|
||||||
readonly=False,
|
readonly=False,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@ -243,16 +243,16 @@ class Label(Readable):
|
|||||||
"""
|
"""
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
'system': PARAM("Name of the magnet system",
|
'system': PARAM("Name of the magnet system",
|
||||||
validator=str, export=False,
|
datatype=StringType, export=False,
|
||||||
),
|
),
|
||||||
'subdev_mf': PARAM("name of subdevice for magnet status",
|
'subdev_mf': PARAM("name of subdevice for magnet status",
|
||||||
validator=str, export=False,
|
datatype=StringType, export=False,
|
||||||
),
|
),
|
||||||
'subdev_ts': PARAM("name of subdevice for sample temp",
|
'subdev_ts': PARAM("name of subdevice for sample temp",
|
||||||
validator=str, export=False,
|
datatype=StringType, export=False,
|
||||||
),
|
),
|
||||||
'value': PARAM("final value of label string",
|
'value': PARAM("final value of label string",
|
||||||
validator=str,
|
datatype=StringType,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,25 +283,22 @@ class Label(Readable):
|
|||||||
return '; '.join(strings)
|
return '; '.join(strings)
|
||||||
|
|
||||||
|
|
||||||
class ValidatorTest(Readable):
|
class DatatypesTest(Readable):
|
||||||
"""
|
"""
|
||||||
"""
|
"""
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
'oneof': PARAM('oneof',
|
|
||||||
validator=oneof(int, 'X', 2.718), readonly=False, default=4.0),
|
|
||||||
'enum': PARAM('enum',
|
'enum': PARAM('enum',
|
||||||
validator=enum('boo', 'faar', z=9), readonly=False, default=1),
|
datatype=EnumType('boo', 'faar', z=9), readonly=False, default=1),
|
||||||
'vector': PARAM('vector of int, float and str',
|
'tupleof': PARAM('tuple of int, float and str',
|
||||||
validator=vector(int, float, str), readonly=False, default=(1, 2.3, 'a')),
|
datatype=TupleOf(IntRange(), FloatRange(), StringType()), readonly=False, default=(1, 2.3, 'a')),
|
||||||
'array': PARAM('array: 2..3 times oneof(0,1)',
|
'arrayof': PARAM('array: 2..3 times bool',
|
||||||
validator=array(oneof(2, 3), oneof(0, 1)), readonly=False, default=[1, 0, 1]),
|
datatype=ArrayOf(BoolType(), 2, 3), readonly=False, default=[1, 0, 1]),
|
||||||
'nonnegative': PARAM('nonnegative',
|
|
||||||
validator=nonnegative, readonly=False, default=0),
|
|
||||||
'positive': PARAM('positive',
|
|
||||||
validator=positive, readonly=False, default=1),
|
|
||||||
'intrange': PARAM('intrange',
|
'intrange': PARAM('intrange',
|
||||||
validator=intrange(2, 9), readonly=False, default=4),
|
datatype=IntRange(2, 9), readonly=False, default=4),
|
||||||
'floatrange': PARAM('floatrange',
|
'floatrange': PARAM('floatrange',
|
||||||
validator=floatrange(-1, 1), readonly=False, default=0,
|
datatype=FloatRange(-1, 1), readonly=False, default=0,
|
||||||
|
),
|
||||||
|
'struct': PARAM('struct(a=str, b=int, c=bool)',
|
||||||
|
datatype=StructOf(a=StringType(), b=IntRange(), c=BoolType()),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
import random
|
import random
|
||||||
|
|
||||||
from secop.lib.parsing import format_time
|
from secop.lib.parsing import format_time
|
||||||
from secop.validators import enum, vector, floatrange, validator_to_str
|
from secop.datatypes import EnumType, TupleOf, FloatRange, get_datatype, StringType
|
||||||
from secop.devices.core import Readable, Device, Driveable, PARAM
|
from secop.devices.core import Readable, Device, Driveable, PARAM
|
||||||
from secop.protocol import status
|
from secop.protocol import status
|
||||||
|
|
||||||
@ -62,14 +62,17 @@ class EpicsReadable(Readable):
|
|||||||
"""EpicsDriveable handles a Driveable interfacing to EPICS v4"""
|
"""EpicsDriveable handles a Driveable interfacing to EPICS v4"""
|
||||||
# Commmon PARAMS for all EPICS devices
|
# Commmon PARAMS for all EPICS devices
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
'value': PARAM('EPICS generic value', validator=float,
|
'value': PARAM('EPICS generic value',
|
||||||
|
datatype=FloatRange(),
|
||||||
default=300.0,),
|
default=300.0,),
|
||||||
'epics_version': PARAM("EPICS version used, v3 or v4",
|
'epics_version': PARAM("EPICS version used, v3 or v4",
|
||||||
validator=str,),
|
datatype=EnumType(v3=3, v4=4),),
|
||||||
# 'private' parameters: not remotely accessible
|
# 'private' parameters: not remotely accessible
|
||||||
'value_pv': PARAM('EPICS pv_name of value', validator=str,
|
'value_pv': PARAM('EPICS pv_name of value',
|
||||||
|
datatype=StringType(),
|
||||||
default="unset", export=False),
|
default="unset", export=False),
|
||||||
'status_pv': PARAM('EPICS pv_name of status', validator=str,
|
'status_pv': PARAM('EPICS pv_name of status',
|
||||||
|
datatype=StringType(),
|
||||||
default="unset", export=False),
|
default="unset", export=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,18 +122,18 @@ class EpicsDriveable(Driveable):
|
|||||||
"""EpicsDriveable handles a Driveable interfacing to EPICS v4"""
|
"""EpicsDriveable handles a Driveable interfacing to EPICS v4"""
|
||||||
# Commmon PARAMS for all EPICS devices
|
# Commmon PARAMS for all EPICS devices
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
'target': PARAM('EPICS generic target', validator=float,
|
'target': PARAM('EPICS generic target', datatype=FloatRange(),
|
||||||
default=300.0, readonly=False),
|
default=300.0, readonly=False),
|
||||||
'value': PARAM('EPICS generic value', validator=float,
|
'value': PARAM('EPICS generic value', datatype=FloatRange(),
|
||||||
default=300.0,),
|
default=300.0,),
|
||||||
'epics_version': PARAM("EPICS version used, v3 or v4",
|
'epics_version': PARAM("EPICS version used, v3 or v4",
|
||||||
validator=str,),
|
datatype=StringType(),),
|
||||||
# 'private' parameters: not remotely accessible
|
# 'private' parameters: not remotely accessible
|
||||||
'target_pv': PARAM('EPICS pv_name of target', validator=str,
|
'target_pv': PARAM('EPICS pv_name of target', datatype=StringType(),
|
||||||
default="unset", export=False),
|
default="unset", export=False),
|
||||||
'value_pv': PARAM('EPICS pv_name of value', validator=str,
|
'value_pv': PARAM('EPICS pv_name of value', datatype=StringType(),
|
||||||
default="unset", export=False),
|
default="unset", export=False),
|
||||||
'status_pv': PARAM('EPICS pv_name of status', validator=str,
|
'status_pv': PARAM('EPICS pv_name of status', datatype=StringType(),
|
||||||
default="unset", export=False),
|
default="unset", export=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,15 +194,15 @@ class EpicsDriveable(Driveable):
|
|||||||
class EpicsTempCtrl(EpicsDriveable):
|
class EpicsTempCtrl(EpicsDriveable):
|
||||||
|
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
# TODO: restrict possible values with oneof validator
|
# TODO: restrict possible values with oneof datatype
|
||||||
'heaterrange': PARAM('Heater range', validator=str,
|
'heaterrange': PARAM('Heater range', datatype=StringType(),
|
||||||
default='Off', readonly=False,),
|
default='Off', readonly=False,),
|
||||||
'tolerance': PARAM('allowed deviation between value and target',
|
'tolerance': PARAM('allowed deviation between value and target',
|
||||||
validator=floatrange(1e-6, 1e6), default=0.1,
|
datatype=FloatRange(1e-6, 1e6), default=0.1,
|
||||||
readonly=False,),
|
readonly=False,),
|
||||||
# 'private' parameters: not remotely accessible
|
# 'private' parameters: not remotely accessible
|
||||||
'heaterrange_pv': PARAM('EPICS pv_name of heater range',
|
'heaterrange_pv': PARAM('EPICS pv_name of heater range',
|
||||||
validator=str, default="unset", export=False,),
|
datatype=StringType(), default="unset", export=False,),
|
||||||
}
|
}
|
||||||
|
|
||||||
def read_target(self, maxage=0):
|
def read_target(self, maxage=0):
|
||||||
|
@ -23,8 +23,7 @@
|
|||||||
import random
|
import random
|
||||||
|
|
||||||
from secop.devices.core import Readable, Driveable, PARAM
|
from secop.devices.core import Readable, Driveable, PARAM
|
||||||
from secop.validators import floatrange, positive
|
from secop.datatypes import FloatRange, StringType
|
||||||
|
|
||||||
|
|
||||||
class LN2(Readable):
|
class LN2(Readable):
|
||||||
"""Just a readable.
|
"""Just a readable.
|
||||||
@ -45,7 +44,7 @@ class Heater(Driveable):
|
|||||||
"""
|
"""
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
'maxheaterpower': PARAM('maximum allowed heater power',
|
'maxheaterpower': PARAM('maximum allowed heater power',
|
||||||
validator=floatrange(0, 100), unit='W',
|
datatype=FloatRange(0, 100), unit='W',
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,10 +63,10 @@ class Temp(Driveable):
|
|||||||
"""
|
"""
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
'sensor': PARAM("Sensor number or calibration id",
|
'sensor': PARAM("Sensor number or calibration id",
|
||||||
validator=str, readonly=True,
|
datatype=StringType(8,16), readonly=True,
|
||||||
),
|
),
|
||||||
'target': PARAM("Target temperature",
|
'target': PARAM("Target temperature",
|
||||||
default=300.0, validator=positive, readonly=False, unit='K',
|
default=300.0, datatype=FloatRange(0), readonly=False, unit='K',
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ from PyQt4.QtGui import QWidget, QLabel, QSizePolicy
|
|||||||
from PyQt4.QtCore import pyqtSignature as qtsig, Qt, pyqtSignal
|
from PyQt4.QtCore import pyqtSignature as qtsig, Qt, pyqtSignal
|
||||||
|
|
||||||
from secop.gui.util import loadUi
|
from secop.gui.util import loadUi
|
||||||
from secop.validators import validator_to_str
|
from secop.datatypes import get_datatype
|
||||||
|
|
||||||
|
|
||||||
class ParameterView(QWidget):
|
class ParameterView(QWidget):
|
||||||
@ -59,9 +59,6 @@ class ParameterView(QWidget):
|
|||||||
label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||||
|
|
||||||
# make 'display' label
|
# make 'display' label
|
||||||
if prop == 'validator':
|
|
||||||
view = QLabel(validator_to_str(props[prop]))
|
|
||||||
else:
|
|
||||||
view = QLabel(str(props[prop]))
|
view = QLabel(str(props[prop]))
|
||||||
view.setFont(self.font())
|
view.setFont(self.font())
|
||||||
view.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
view.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
||||||
|
@ -64,6 +64,73 @@ def mkthread(func, *args, **kwds):
|
|||||||
return t
|
return t
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import linecache
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
def formatExtendedFrame(frame):
|
||||||
|
ret = []
|
||||||
|
for key, value in frame.f_locals.iteritems():
|
||||||
|
try:
|
||||||
|
valstr = repr(value)[:256]
|
||||||
|
except Exception:
|
||||||
|
valstr = '<cannot be displayed>'
|
||||||
|
ret.append(' %-20s = %s\n' % (key, valstr))
|
||||||
|
ret.append('\n')
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def formatExtendedTraceback(etype, value, tb):
|
||||||
|
ret = ['Traceback (most recent call last):\n']
|
||||||
|
while tb is not None:
|
||||||
|
frame = tb.tb_frame
|
||||||
|
filename = frame.f_code.co_filename
|
||||||
|
item = ' File "%s", line %d, in %s\n' % (filename, tb.tb_lineno,
|
||||||
|
frame.f_code.co_name)
|
||||||
|
linecache.checkcache(filename)
|
||||||
|
line = linecache.getline(filename, tb.tb_lineno, frame.f_globals)
|
||||||
|
if line:
|
||||||
|
item = item + ' %s\n' % line.strip()
|
||||||
|
ret.append(item)
|
||||||
|
if filename not in ('<script>', '<string>'):
|
||||||
|
ret += formatExtendedFrame(tb.tb_frame)
|
||||||
|
tb = tb.tb_next
|
||||||
|
ret += traceback.format_exception_only(etype, value)
|
||||||
|
return ''.join(ret).rstrip('\n')
|
||||||
|
|
||||||
|
def formatExtendedStack(level=1):
|
||||||
|
f = sys._getframe(level)
|
||||||
|
ret = ['Stack trace (most recent call last):\n\n']
|
||||||
|
while f is not None:
|
||||||
|
lineno = f.f_lineno
|
||||||
|
co = f.f_code
|
||||||
|
filename = co.co_filename
|
||||||
|
name = co.co_name
|
||||||
|
item = ' File "%s", line %d, in %s\n' % (filename, lineno, name)
|
||||||
|
linecache.checkcache(filename)
|
||||||
|
line = linecache.getline(filename, lineno, f.f_globals)
|
||||||
|
if line:
|
||||||
|
item = item + ' %s\n' % line.strip()
|
||||||
|
ret.insert(1, item)
|
||||||
|
if filename != '<script>':
|
||||||
|
ret[2:2] = formatExtendedFrame(f)
|
||||||
|
f = f.f_back
|
||||||
|
return ''.join(ret).rstrip('\n')
|
||||||
|
|
||||||
|
def formatException(cut=0, exc_info=None):
|
||||||
|
"""Format an exception with traceback, but leave out the first `cut`
|
||||||
|
number of frames.
|
||||||
|
"""
|
||||||
|
if exc_info is None:
|
||||||
|
typ, val, tb = sys.exc_info()
|
||||||
|
else:
|
||||||
|
typ, val, tb = exc_info
|
||||||
|
res = ['Traceback (most recent call last):\n']
|
||||||
|
tbres = traceback.format_tb(tb, sys.maxsize)
|
||||||
|
res += tbres[cut:]
|
||||||
|
res += traceback.format_exception_only(typ, val)
|
||||||
|
return ''.join(res)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print "minimal testing: lib"
|
print "minimal testing: lib"
|
||||||
d = attrdict(a=1, b=2)
|
d = attrdict(a=1, b=2)
|
||||||
|
@ -43,6 +43,7 @@ import threading
|
|||||||
from messages import *
|
from messages import *
|
||||||
from errors import *
|
from errors import *
|
||||||
from secop.lib.parsing import format_time
|
from secop.lib.parsing import format_time
|
||||||
|
from secop.lib import formatExtendedStack, formatException
|
||||||
|
|
||||||
|
|
||||||
class Dispatcher(object):
|
class Dispatcher(object):
|
||||||
@ -88,6 +89,7 @@ class Dispatcher(object):
|
|||||||
errorclass=err.__class__.__name__,
|
errorclass=err.__class__.__name__,
|
||||||
errorinfo=[repr(err), str(msg)])
|
errorinfo=[repr(err), str(msg)])
|
||||||
except (ValueError, TypeError) as err:
|
except (ValueError, TypeError) as err:
|
||||||
|
self.log.exception(err)
|
||||||
reply = msg.get_error(
|
reply = msg.get_error(
|
||||||
errorclass='BadValue',
|
errorclass='BadValue',
|
||||||
errorinfo=[repr(err), str(msg)])
|
errorinfo=[repr(err), str(msg)])
|
||||||
@ -95,7 +97,7 @@ class Dispatcher(object):
|
|||||||
self.log.exception(err)
|
self.log.exception(err)
|
||||||
reply = msg.get_error(
|
reply = msg.get_error(
|
||||||
errorclass='InternalError',
|
errorclass='InternalError',
|
||||||
errorinfo=[repr(err), str(msg)])
|
errorinfo=[formatException(), str(msg), formatExtendedStack()])
|
||||||
else:
|
else:
|
||||||
self.log.debug('Can not handle msg %r' % msg)
|
self.log.debug('Can not handle msg %r' % msg)
|
||||||
reply = self.unhandled(conn, msg)
|
reply = self.unhandled(conn, msg)
|
||||||
|
@ -1,308 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# *****************************************************************************
|
|
||||||
#
|
|
||||||
# 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>
|
|
||||||
#
|
|
||||||
# *****************************************************************************
|
|
||||||
"""Define validators."""
|
|
||||||
|
|
||||||
# a Validator returns a validated object or raises an ValueError
|
|
||||||
# easy python validators: int(), float(), str()
|
|
||||||
# also validators should have a __repr__ returning a 'python' string
|
|
||||||
# which recreates them
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
from errors import ProgrammingError
|
|
||||||
|
|
||||||
|
|
||||||
class Validator(object):
|
|
||||||
# list of tuples: (name, converter)
|
|
||||||
params = []
|
|
||||||
valuetype = float
|
|
||||||
argstr = ''
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwds):
|
|
||||||
plist = self.params[:]
|
|
||||||
if len(args) > len(plist):
|
|
||||||
raise ProgrammingError('%s takes %d parameters only (%d given)' % (
|
|
||||||
self.__class__.__name__, len(plist), len(args)))
|
|
||||||
for pval in args:
|
|
||||||
pname, pconv = plist.pop(0)
|
|
||||||
if pname in kwds:
|
|
||||||
raise ProgrammingError('%s: positional parameter %s is given '
|
|
||||||
'as keyword!' %
|
|
||||||
(self.__class__.__name__, pname))
|
|
||||||
self.__dict__[pname] = pconv(pval)
|
|
||||||
for pname, pconv in plist:
|
|
||||||
if pname in kwds:
|
|
||||||
pval = kwds.pop(pname)
|
|
||||||
self.__dict__[pname] = pconv(pval)
|
|
||||||
else:
|
|
||||||
raise ProgrammingError('%s: param %s left unspecified!' %
|
|
||||||
(self.__class__.__name__, pname))
|
|
||||||
|
|
||||||
if kwds:
|
|
||||||
raise ProgrammingError('%s got unknown arguments: %s' % (
|
|
||||||
self.__class__.__name__, ', '.join(list(kwds.keys()))))
|
|
||||||
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 __repr__(self):
|
|
||||||
return self.to_string()
|
|
||||||
|
|
||||||
def to_string(self):
|
|
||||||
return ('%s(%s)' % (self.__class__.__name__, self.argstr))
|
|
||||||
|
|
||||||
|
|
||||||
class floatrange(Validator):
|
|
||||||
params = [('lower', float), ('upper', float)]
|
|
||||||
|
|
||||||
def check(self, value):
|
|
||||||
try:
|
|
||||||
value = float(value)
|
|
||||||
if self.lower <= value <= self.upper:
|
|
||||||
return value
|
|
||||||
raise ValueError(
|
|
||||||
'Floatrange: value %r must be a float within %f and %f' %
|
|
||||||
(value, self.lower, self.upper))
|
|
||||||
except TypeError:
|
|
||||||
raise ValueError(
|
|
||||||
'Floatrange: value %r must be a float within %f and %f' %
|
|
||||||
(value, self.lower, self.upper))
|
|
||||||
|
|
||||||
|
|
||||||
class intrange(Validator):
|
|
||||||
params = [('lower', int), ('upper', int)]
|
|
||||||
valuetype = int
|
|
||||||
|
|
||||||
def check(self, value):
|
|
||||||
if self.lower <= int(value) <= self.upper:
|
|
||||||
return value
|
|
||||||
raise ValueError(
|
|
||||||
'Intrange: value %r must be an integer within %f and %f' %
|
|
||||||
(value, self.lower, self.upper))
|
|
||||||
|
|
||||||
|
|
||||||
class array(Validator):
|
|
||||||
"""integral amount of data-elements which are described by the SAME validator
|
|
||||||
|
|
||||||
The size of the array can also be described by an validator
|
|
||||||
"""
|
|
||||||
valuetype = list
|
|
||||||
params = [('size', lambda x: x), ('datatype', lambda x: x)]
|
|
||||||
|
|
||||||
def check(self, values):
|
|
||||||
requested_size = len(values)
|
|
||||||
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)' %
|
|
||||||
(allowed_size, requested_size))
|
|
||||||
# apply data-type validator to all elements and return
|
|
||||||
res = []
|
|
||||||
for idx, el in enumerate(values):
|
|
||||||
try:
|
|
||||||
res.append(self.datatype(el))
|
|
||||||
except ValueError as e:
|
|
||||||
raise ValueError(
|
|
||||||
'Array Element %s (=%r) not conforming to %r: (%s)' %
|
|
||||||
(idx, el, self.datatype, e))
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
# more complicated validator may not be able to use validator base class
|
|
||||||
class vector(Validator):
|
|
||||||
"""fixed length, eache element has its own validator"""
|
|
||||||
valuetype = tuple
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
self.validators = args
|
|
||||||
self.argstr = ', '.join([validator_to_str(e) for e in args])
|
|
||||||
|
|
||||||
def __call__(self, args):
|
|
||||||
if type(args) in (str, unicode):
|
|
||||||
args = eval(args)
|
|
||||||
if len(args) != len(self.validators):
|
|
||||||
raise ValueError('Vector: need exactly %d elementes (got %d)' %
|
|
||||||
len(self.validators), len(args))
|
|
||||||
res = tuple(v(e) for v, e in zip(self.validators, args))
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
# XXX: fixme!
|
|
||||||
class record(Validator):
|
|
||||||
"""fixed length, eache element has its own name and validator"""
|
|
||||||
|
|
||||||
def __init__(self, **kwds):
|
|
||||||
self.validators = kwds
|
|
||||||
self.argstr = ', '.join(
|
|
||||||
['%s=%s' % (e[0], validator_to_str(e[1])) for e in kwds.items()])
|
|
||||||
|
|
||||||
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))
|
|
||||||
|
|
||||||
|
|
||||||
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) if callable(e) else repr(e) for e in args])
|
|
||||||
|
|
||||||
def __call__(self, arg):
|
|
||||||
for v in self.oneof:
|
|
||||||
if callable(v):
|
|
||||||
try:
|
|
||||||
if (v == int) and (float(arg) != int(arg)):
|
|
||||||
continue
|
|
||||||
return v(arg)
|
|
||||||
except ValueError:
|
|
||||||
pass # try next validator
|
|
||||||
elif v == arg:
|
|
||||||
return v
|
|
||||||
raise ValueError('Oneof: %r should be one of: %s' % (arg, self.argstr))
|
|
||||||
|
|
||||||
|
|
||||||
class enum(Validator):
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwds):
|
|
||||||
self.mapping = {}
|
|
||||||
# use given kwds directly
|
|
||||||
self.mapping.update(kwds)
|
|
||||||
# enumerate args
|
|
||||||
i = -1
|
|
||||||
args = list(args)
|
|
||||||
while args:
|
|
||||||
i += 1
|
|
||||||
if i in self.mapping:
|
|
||||||
continue
|
|
||||||
self.mapping[args.pop(0)] = i
|
|
||||||
# generate reverse mapping too for use by protocol
|
|
||||||
self.revmapping = {}
|
|
||||||
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:
|
|
||||||
obj = int(obj)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
if obj in self.mapping:
|
|
||||||
return obj
|
|
||||||
if obj in self.revmapping:
|
|
||||||
return self.revmapping[obj]
|
|
||||||
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 float(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 float(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):
|
|
||||||
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):
|
|
||||||
validator_str = validator_str.replace("<type 'str'>", "str")
|
|
||||||
validator_str = validator_str.replace("<type 'float'>", "float")
|
|
||||||
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