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:
@@ -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
|
||||
|
||||
63
secop/io.py
63
secop/io.py
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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():
|
||||
|
||||
Reference in New Issue
Block a user