unify name and module on Attached property

- setting the attribute using the name of an attached module
- getting the attribute results in the module object

+ change names iodev to io, iodevClass to ioClass,
  sendRecv to communicate, HasIodev to HasIO

Change-Id: I200b63a5a7dc1453bf6ac998782b065645201900
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27575
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
2022-01-27 18:11:42 +01:00
parent b911bc1838
commit c1307cdd03
21 changed files with 301 additions and 209 deletions

View File

@@ -36,5 +36,5 @@ from secop.params import Command, Parameter
from secop.poller import AUTO, DYNAMIC, REGULAR, SLOW
from secop.properties import Property
from secop.proxy import Proxy, SecNode, proxy_class
from secop.io import HasIodev, StringIO, BytesIO
from secop.io import HasIO, StringIO, BytesIO, HasIodev # TODO: remove HasIodev (legacy stuff)
from secop.persistent import PersistentMixin, PersistentParam

View File

@@ -29,56 +29,79 @@ import time
import threading
from secop.lib.asynconn import AsynConn, ConnectionClosed
from secop.datatypes import ArrayOf, BLOBType, BoolType, FloatRange, IntRange, StringType, TupleOf, ValueType
from secop.errors import CommunicationFailedError, CommunicationSilentError, ConfigError
from secop.datatypes import ArrayOf, BLOBType, BoolType, FloatRange, IntRange, \
StringType, TupleOf, ValueType
from secop.errors import CommunicationFailedError, CommunicationSilentError, \
ConfigError, ProgrammingError
from secop.modules import Attached, Command, \
Communicator, Done, Module, Parameter, Property
from secop.poller import REGULAR
from secop.lib import generalConfig
generalConfig.defaults['legacy_hasiodev'] = False
HEX_CODE = re.compile(r'[0-9a-fA-F][0-9a-fA-F]$')
class HasIodev(Module):
class HasIO(Module):
"""Mixin for modules using a communicator"""
iodev = Attached()
io = Attached()
uri = Property('uri for automatic creation of the attached communication module',
StringType(), default='')
iodevDict = {}
ioDict = {}
ioClass = None
def __init__(self, name, logger, opts, srv):
iodev = opts.get('iodev')
io = opts.get('io')
super().__init__(name, logger, opts, srv)
if self.uri:
opts = {'uri': self.uri, 'description': 'communication device for %s' % name,
'export': False}
ioname = self.iodevDict.get(self.uri)
ioname = self.ioDict.get(self.uri)
if not ioname:
ioname = iodev or name + '_iodev'
iodev = self.iodevClass(ioname, srv.log.getChild(ioname), opts, srv)
iodev.callingModule = []
srv.modules[ioname] = iodev
self.iodevDict[self.uri] = ioname
self.iodev = ioname
elif not self.iodev:
raise ConfigError("Module %s needs a value for either 'uri' or 'iodev'" % name)
ioname = io or name + '_io'
io = self.ioClass(ioname, srv.log.getChild(ioname), opts, srv) # pylint: disable=not-callable
io.callingModule = []
srv.modules[ioname] = io
self.ioDict[self.uri] = ioname
self.io = ioname
elif not io:
raise ConfigError("Module %s needs a value for either 'uri' or 'io'" % name)
def initModule(self):
try:
self._iodev.read_is_connected()
self.io.read_is_connected()
except (CommunicationFailedError, AttributeError):
# AttributeError: for missing _iodev?
# AttributeError: read_is_connected is not required for an io object
pass
super().initModule()
def communicate(self, *args):
return self._iodev.communicate(*args)
return self.io.communicate(*args)
def multicomm(self, *args):
return self._iodev.multicomm(*args)
return self.io.multicomm(*args)
sendRecv = communicate # TODO: remove legacy stuff
class HasIodev(HasIO):
# TODO: remove this legacy mixin
iodevClass = None
@property
def _iodev(self):
return self.io
def __init__(self, name, logger, opts, srv):
self.ioClass = self.iodevClass
super().__init__(name, logger, opts, srv)
if generalConfig.legacy_hasiodev:
self.log.warn('using the HasIodev mixin is deprecated - use HasIO instead')
else:
self.log.error('legacy HasIodev no longer supported')
self.log.error('you may suppress this error message by running the server with --relaxed')
raise ProgrammingError('legacy HasIodev no longer supported')
self.sendRecv = self.communicate
class IOBase(Communicator):

View File

@@ -126,7 +126,7 @@ class CmdParser:
try:
argformat % ((0,) * len(casts)) # validate argformat
except ValueError as e:
raise ValueError("%s in %r" % (e, argformat))
raise ValueError("%s in %r" % (e, argformat)) from None
def format(self, *values):
return self.fmt % values
@@ -242,7 +242,7 @@ class IOHandler(IOHandlerBase):
contain the command separator at the end.
"""
querycmd = self.make_query(module)
reply = module.sendRecv(changecmd + querycmd)
reply = module.communicate(changecmd + querycmd)
return self.parse_reply(reply)
def send_change(self, module, *values):
@@ -253,7 +253,7 @@ class IOHandler(IOHandlerBase):
"""
changecmd = self.make_change(module, *values)
if self.CMDSEPARATOR is None:
module.sendRecv(changecmd) # ignore result
module.communicate(changecmd) # ignore result
return self.send_command(module)
return self.send_command(module, changecmd + self.CMDSEPARATOR)

View File

@@ -771,19 +771,19 @@ class Communicator(HasComlog, Module):
class Attached(Property):
"""a special property, defining an attached modle
"""a special property, defining an attached module
assign a module name to this property in the cfg file,
and the server will create an attribute with this module
:param attrname: the name of the to be created attribute. if not given
the attribute name is the property name prepended by an underscore.
"""
# we can not put this to properties.py, as it needs datatypes
def __init__(self, attrname=None):
self.attrname = attrname
# we can not make it mandatory, as the check in Module.__init__ will be before auto-assign in HasIodev
super().__init__('attached module', StringType(), mandatory=False)
module = None
def __repr__(self):
return 'Attached(%s)' % (repr(self.attrname) if self.attrname else '')
def __init__(self, description='attached module'):
super().__init__(description, StringType(), mandatory=False)
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

View File

@@ -30,7 +30,7 @@ Usage examples:
pollerClass = poller.Poller
...
modules having a parameter 'iodev' with the same value will share the same poller
modules having a parameter 'io' with the same value will share the same poller
"""
import time
@@ -58,11 +58,11 @@ class PollerBase:
table is a dict, with (<pollerClass>, <name>) as the key, and the
poller as value.
<name> is module.iodev or module.name, if iodev is not present
<name> is module.io.name or module.name, if io is not present
"""
# for modules with the same iodev, a common poller is used,
# modules without iodev all get their own poller
name = getattr(module, 'iodev', module.name)
# for modules with the same io, a common poller is used,
# modules without io all get their own poller
name = getattr(module, 'io', module).name
poller = table.get((cls, name), None)
if poller is None:
poller = cls(name)

View File

@@ -29,17 +29,17 @@ from secop.lib import get_class
from secop.modules import Drivable, Module, Readable, Writable
from secop.params import Command, Parameter
from secop.properties import Property
from secop.io import HasIodev
from secop.io import HasIO
class ProxyModule(HasIodev, Module):
class ProxyModule(HasIO, Module):
module = Property('remote module name', datatype=StringType(), default='')
pollerClass = None
_consistency_check_done = False
_secnode = None
def iodevClass(self, name, logger, opts, srv):
def ioClass(self, name, logger, opts, srv):
opts['description'] = 'secnode %s on %s' % (opts.get('module', name), opts['uri'])
return SecNode(name, logger, opts, srv)
@@ -54,7 +54,7 @@ class ProxyModule(HasIodev, Module):
def initModule(self):
if not self.module:
self.module = self.name
self._secnode = self._iodev.secnode
self._secnode = self.io.secnode
self._secnode.register_callback(self.module, self.updateEvent,
self.descriptiveDataChange, self.nodeStateChange)
super().initModule()
@@ -227,5 +227,5 @@ def Proxy(name, logger, cfgdict, srv):
remote_class = cfgdict.pop('remote_class')
if 'description' not in cfgdict:
cfgdict['description'] = 'remote module %s on %s' % (
cfgdict.get('module', name), cfgdict.get('iodev', '?'))
cfgdict.get('module', name), cfgdict.get('io', '?'))
return proxy_class(remote_class)(name, logger, cfgdict, srv)

View File

@@ -30,10 +30,9 @@ import sys
import traceback
from collections import OrderedDict
from secop.errors import ConfigError, SECoPError
from secop.errors import ConfigError
from secop.lib import formatException, get_class, generalConfig
from secop.lib.multievent import MultiEvent
from secop.modules import Attached
from secop.params import PREDEFINED_ACCESSIBLES
try:
@@ -271,24 +270,17 @@ class Server:
for modname, modobj in self.modules.items():
self.log.info('registering module %r' % modname)
self.dispatcher.register_module(modobj, modname, modobj.export)
if modobj.pollerClass is not None:
# a module might be explicitly excluded from polling by setting pollerClass to None
modobj.pollerClass.add_to_table(poll_table, modobj)
# also call earlyInit on the modules
modobj.earlyInit()
if not modobj.earlyInitDone:
missing_super.add('%s was not called, probably missing super call'
% modobj.earlyInit.__qualname__)
# handle attached modules
# handle polling
for modname, modobj in self.modules.items():
for propname, propobj in modobj.propertyDict.items():
if isinstance(propobj, Attached):
try:
setattr(modobj, propobj.attrname or '_' + propname,
self.dispatcher.get_module(getattr(modobj, propname)))
except SECoPError as e:
errors.append('module %s, attached %s: %s' % (modname, propname, str(e)))
if modobj.pollerClass is not None:
# a module might be explicitly excluded from polling by setting pollerClass to None
modobj.pollerClass.add_to_table(poll_table, modobj)
# call init on each module after registering all
for modname, modobj in self.modules.items():