merge until "support write_ method on readonly param and more"
from gerrit Change-Id: I8d2ad2a381d3a37947d8afc5e17be0428d94df36
This commit is contained in:
@ -31,7 +31,7 @@ from secop.datatypes import ArrayOf, BLOBType, BoolType, EnumType, \
|
||||
from secop.iohandler import IOHandler, IOHandlerBase
|
||||
from secop.lib.enum import Enum
|
||||
from secop.modules import Attached, Communicator, \
|
||||
Done, Drivable, Module, Readable, Writable
|
||||
Done, Drivable, Module, Readable, Writable, HasAccessibles
|
||||
from secop.params import Command, Parameter
|
||||
from secop.properties import Property
|
||||
from secop.proxy import Proxy, SecNode, proxy_class
|
||||
@ -39,3 +39,8 @@ from secop.io import HasIO, StringIO, BytesIO, HasIodev # TODO: remove HasIodev
|
||||
from secop.persistent import PersistentMixin, PersistentParam
|
||||
from secop.rwhandler import ReadHandler, WriteHandler, CommonReadHandler, \
|
||||
CommonWriteHandler, nopoll
|
||||
|
||||
ERROR = Drivable.Status.ERROR
|
||||
WARN = Drivable.Status.WARN
|
||||
BUSY = Drivable.Status.BUSY
|
||||
IDLE = Drivable.Status.IDLE
|
||||
|
@ -22,7 +22,7 @@
|
||||
# *****************************************************************************
|
||||
"""Define validated data types."""
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
# pylint: disable=abstract-method, too-many-lines
|
||||
|
||||
|
||||
import sys
|
||||
@ -454,7 +454,10 @@ class EnumType(DataType):
|
||||
super().__init__()
|
||||
if members is not None:
|
||||
kwds.update(members)
|
||||
self._enum = Enum(enum_or_name, **kwds)
|
||||
if isinstance(enum_or_name, str):
|
||||
self._enum = Enum(enum_or_name, kwds) # allow 'self' as name
|
||||
else:
|
||||
self._enum = Enum(enum_or_name, **kwds)
|
||||
self.default = self._enum[self._enum.members[0]]
|
||||
|
||||
def copy(self):
|
||||
|
@ -178,7 +178,9 @@ class StateMachine:
|
||||
if self.stop_exc:
|
||||
raise self.stop_exc
|
||||
except Exception as e:
|
||||
self.log.info('%r raised in state %r', e, self.status_string)
|
||||
# Stop and Restart are not unusual -> no warning
|
||||
log = self.log.debug if isinstance(e, Stop) else self.log.warning
|
||||
log('%r raised in state %r', e, self.status_string)
|
||||
self.last_error = e
|
||||
ret = self.cleanup(self)
|
||||
self.log.debug('cleanup %r %r %r', self.cleanup, self.last_error, ret)
|
||||
|
@ -161,8 +161,8 @@ class HasAccessibles(HasProperties):
|
||||
new_rfunc.wrapped = True # indicate to subclasses that no more wrapping is needed
|
||||
setattr(cls, 'read_' + pname, new_rfunc)
|
||||
|
||||
if not pobj.readonly:
|
||||
wfunc = getattr(cls, 'write_' + pname, None)
|
||||
wfunc = getattr(cls, 'write_' + pname, None)
|
||||
if not pobj.readonly or wfunc: # allow write_ method even when pobj is not readonly
|
||||
wrapped = getattr(wfunc, 'wrapped', False) # meaning: wrapped or auto generated
|
||||
if (wfunc is None or wrapped) and pobj.handler:
|
||||
# ignore the handler, if a write function is present
|
||||
@ -181,14 +181,14 @@ class HasAccessibles(HasProperties):
|
||||
self.log.debug('validate %r for %r', value, pname)
|
||||
# we do not need to handle errors here, we do not
|
||||
# want to make a parameter invalid, when a write failed
|
||||
value = pobj.datatype(value)
|
||||
returned_value = wfunc(self, value)
|
||||
self.log.debug('write_%s(%r) returned %r', pname, value, returned_value)
|
||||
if returned_value is Done:
|
||||
new_value = pobj.datatype(value)
|
||||
new_value = wfunc(self, new_value)
|
||||
self.log.debug('write_%s(%r) returned %r', pname, value, new_value)
|
||||
if new_value is Done:
|
||||
# setattr(self, pname, getattr(self, pname))
|
||||
return getattr(self, pname)
|
||||
setattr(self, pname, value) # important! trigger the setter
|
||||
return value
|
||||
setattr(self, pname, new_value) # important! trigger the setter
|
||||
return new_value
|
||||
else:
|
||||
|
||||
def new_wfunc(self, value, pname=pname):
|
||||
@ -201,14 +201,14 @@ class HasAccessibles(HasProperties):
|
||||
setattr(cls, 'write_' + pname, new_wfunc)
|
||||
|
||||
# check for programming errors
|
||||
for attrname, attrvalue in cls.__dict__.items():
|
||||
for attrname in dir(cls):
|
||||
prefix, _, pname = attrname.partition('_')
|
||||
if not pname:
|
||||
continue
|
||||
if prefix == 'do':
|
||||
raise ProgrammingError('%r: old style command %r not supported anymore'
|
||||
% (cls.__name__, attrname))
|
||||
if prefix in ('read', 'write') and not getattr(attrvalue, 'wrapped', False):
|
||||
if prefix in ('read', 'write') and not getattr(getattr(cls, attrname), 'wrapped', False):
|
||||
raise ProgrammingError('%s.%s defined, but %r is no parameter'
|
||||
% (cls.__name__, attrname, pname))
|
||||
|
||||
@ -280,6 +280,7 @@ class Module(HasAccessibles):
|
||||
|
||||
# reference to the dispatcher (used for sending async updates)
|
||||
DISPATCHER = None
|
||||
attachedModules = None
|
||||
|
||||
def __init__(self, name, logger, cfgdict, srv):
|
||||
# remember the dispatcher object (for the async callbacks)
|
||||
@ -391,9 +392,8 @@ class Module(HasAccessibles):
|
||||
continue
|
||||
|
||||
if pname in cfgdict:
|
||||
if not pobj.readonly and pobj.initwrite is not False:
|
||||
if pobj.initwrite is not False and hasattr(self, 'write_' + pname):
|
||||
# parameters given in cfgdict have to call write_<pname>
|
||||
# TODO: not sure about readonly (why not a parameter which can only be written from config?)
|
||||
try:
|
||||
pobj.value = pobj.datatype(cfgdict[pname])
|
||||
self.writeDict[pname] = pobj.value
|
||||
@ -416,10 +416,8 @@ class Module(HasAccessibles):
|
||||
except BadValueError as e:
|
||||
# this should not happen, as the default is already checked in Parameter
|
||||
raise ProgrammingError('bad default for %s:%s: %s' % (name, pname, e)) from None
|
||||
if pobj.initwrite and not pobj.readonly:
|
||||
if pobj.initwrite and hasattr(self, 'write_' + pname):
|
||||
# we will need to call write_<pname>
|
||||
# if this is not desired, the default must not be given
|
||||
# TODO: not sure about readonly (why not a parameter which can only be written from config?)
|
||||
pobj.value = value
|
||||
self.writeDict[pname] = value
|
||||
else:
|
||||
@ -649,11 +647,11 @@ class Module(HasAccessibles):
|
||||
self.log.info('recovered after %d calls to doPoll (%r)', error_count, last_error)
|
||||
last_error = None
|
||||
except Exception as e:
|
||||
if type(e) != last_error:
|
||||
if repr(e) != last_error:
|
||||
error_count = 0
|
||||
self.log.error('error in doPoll: %r', e)
|
||||
error_count += 1
|
||||
last_error = e
|
||||
last_error = repr(e)
|
||||
now = time.time()
|
||||
# find ONE due slow poll and call it
|
||||
loop = True
|
||||
@ -803,14 +801,15 @@ class Attached(Property):
|
||||
assign a module name to this property in the cfg file,
|
||||
and the server will create an attribute with this module
|
||||
"""
|
||||
module = None
|
||||
|
||||
def __init__(self, description='attached module'):
|
||||
super().__init__(description, StringType(), mandatory=False)
|
||||
def __init__(self, basecls=Module, description='attached module', mandatory=True):
|
||||
self.basecls = basecls
|
||||
super().__init__(description, StringType(), mandatory=mandatory)
|
||||
|
||||
def __get__(self, obj, owner):
|
||||
if obj is None:
|
||||
return self
|
||||
if self.module is None:
|
||||
self.module = obj.DISPATCHER.get_module(super().__get__(obj, owner))
|
||||
return self.module
|
||||
if obj.attachedModules is None:
|
||||
# return the name of the module (called from Server on startup)
|
||||
return super().__get__(obj, owner)
|
||||
# return the module (called after startup)
|
||||
return obj.attachedModules.get(self.name) # return None if not given
|
||||
|
@ -75,7 +75,7 @@ class PersistentMixin(HasAccessibles):
|
||||
self.initData = {}
|
||||
for pname in self.parameters:
|
||||
pobj = self.parameters[pname]
|
||||
if not pobj.readonly and getattr(pobj, 'persistent', 0):
|
||||
if hasattr(self, 'write_' + pname) and getattr(pobj, 'persistent', 0):
|
||||
self.initData[pname] = pobj.value
|
||||
if pobj.persistent == 'auto':
|
||||
def cb(value, m=self):
|
||||
|
@ -23,8 +23,7 @@
|
||||
|
||||
from secop.client import SecopClient, decode_msg, encode_msg_frame
|
||||
from secop.datatypes import StringType
|
||||
from secop.errors import BadValueError, \
|
||||
CommunicationFailedError, ConfigError, make_secop_error
|
||||
from secop.errors import BadValueError, CommunicationFailedError, ConfigError
|
||||
from secop.lib import get_class
|
||||
from secop.modules import Drivable, Module, Readable, Writable
|
||||
from secop.params import Command, Parameter
|
||||
@ -47,8 +46,6 @@ class ProxyModule(HasIO, Module):
|
||||
if parameter not in self.parameters:
|
||||
return # ignore unknown parameters
|
||||
# should be done here: deal with clock differences
|
||||
if readerror:
|
||||
readerror = make_secop_error(*readerror)
|
||||
self.announceUpdate(parameter, value, readerror, timestamp)
|
||||
|
||||
def initModule(self):
|
||||
@ -201,7 +198,7 @@ def proxy_class(remote_class, name=None):
|
||||
def wfunc(self, value, pname=aname):
|
||||
value, _, readerror = self._secnode.setParameter(self.name, pname, value)
|
||||
if readerror:
|
||||
raise make_secop_error(*readerror)
|
||||
raise readerror
|
||||
return value
|
||||
|
||||
attrs['write_' + aname] = wfunc
|
||||
|
@ -30,10 +30,11 @@ import sys
|
||||
import traceback
|
||||
from collections import OrderedDict
|
||||
|
||||
from secop.errors import ConfigError
|
||||
from secop.errors import ConfigError, SECoPError
|
||||
from secop.lib import formatException, get_class, generalConfig
|
||||
from secop.lib.multievent import MultiEvent
|
||||
from secop.params import PREDEFINED_ACCESSIBLES
|
||||
from secop.modules import Attached
|
||||
|
||||
try:
|
||||
from daemon import DaemonContext
|
||||
@ -275,6 +276,24 @@ class Server:
|
||||
missing_super.add('%s was not called, probably missing super call'
|
||||
% modobj.earlyInit.__qualname__)
|
||||
|
||||
# handle attached modules
|
||||
for modname, modobj in self.modules.items():
|
||||
attached_modules = {}
|
||||
for propname, propobj in modobj.propertyDict.items():
|
||||
if isinstance(propobj, Attached):
|
||||
try:
|
||||
attname = getattr(modobj, propname)
|
||||
if attname: # attached module specified in cfg file
|
||||
attobj = self.dispatcher.get_module(attname)
|
||||
if isinstance(attobj, propobj.basecls):
|
||||
attached_modules[propname] = attobj
|
||||
else:
|
||||
errors.append('attached module %s=%r must inherit from %r'
|
||||
% (propname, attname, propobj.basecls.__qualname__))
|
||||
except SECoPError as e:
|
||||
errors.append('module %s, attached %s: %s' % (modname, propname, str(e)))
|
||||
modobj.attachedModules = attached_modules
|
||||
|
||||
# call init on each module after registering all
|
||||
for modname, modobj in self.modules.items():
|
||||
try:
|
||||
@ -287,18 +306,17 @@ class Server:
|
||||
failure_traceback = traceback.format_exc()
|
||||
errors.append('error initializing %s: %r' % (modname, e))
|
||||
|
||||
if self._testonly:
|
||||
return
|
||||
start_events = MultiEvent(default_timeout=30)
|
||||
for modname, modobj in self.modules.items():
|
||||
# startModule must return either a timeout value or None (default 30 sec)
|
||||
start_events.name = 'module %s' % modname
|
||||
modobj.startModule(start_events)
|
||||
if not modobj.startModuleDone:
|
||||
missing_super.add('%s was not called, probably missing super call'
|
||||
% modobj.startModule.__qualname__)
|
||||
if not self._testonly:
|
||||
start_events = MultiEvent(default_timeout=30)
|
||||
for modname, modobj in self.modules.items():
|
||||
# startModule must return either a timeout value or None (default 30 sec)
|
||||
start_events.name = 'module %s' % modname
|
||||
modobj.startModule(start_events)
|
||||
if not modobj.startModuleDone:
|
||||
missing_super.add('%s was not called, probably missing super call'
|
||||
% modobj.startModule.__qualname__)
|
||||
errors.extend(missing_super)
|
||||
|
||||
errors.extend(missing_super)
|
||||
if errors:
|
||||
for errtxt in errors:
|
||||
for line in errtxt.split('\n'):
|
||||
@ -310,6 +328,8 @@ class Server:
|
||||
sys.stderr.write(failure_traceback)
|
||||
sys.exit(1)
|
||||
|
||||
if self._testonly:
|
||||
return
|
||||
self.log.info('waiting for modules being started')
|
||||
start_events.name = None
|
||||
if not start_events.wait():
|
||||
|
Reference in New Issue
Block a user