moved some code from iohandler.py to metaclass.py

- code for calling write_<param> before write function from handler
  is moved to the metaclass
- moved some methods from IOBaseHandler to IOHandler

Change-Id: I733c7fe8d3d59d9013e7b5a33e170c4b3e386921
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/22098
Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
zolliker 2020-01-08 14:22:19 +01:00
parent f0a3306f9c
commit 859bf5e1a2
2 changed files with 74 additions and 90 deletions

View File

@ -20,7 +20,8 @@
# *****************************************************************************
"""IO handler
Utility class for cases, where multiple parameters are treated with a common command.
Utility class for cases, where multiple parameters are treated with a common command,
or in cases, where IO can be parametrized.
The support for LakeShore and similar protocols is already included.
For read, instead of the methods read_<parameter> we write one method analyze_<group>
@ -62,7 +63,7 @@ class CmdParser:
"""helper for parsing replies
using a subset of old style python formatting.
The same format can be used or formatting command arguments
The same format can be used for formatting command arguments
"""
# make a map of cast functions
@ -74,14 +75,12 @@ class CmdParser:
('xX', lambda x: int(x, 16)),
('eEfFgG', float),
) for letter in letters}
# pattern for chacaters to be escaped
ESC_PAT = re.compile('([\\%s])' % '\\'.join('|^$-.+*?()[]{}<>'))
# pattern for characters to be escaped
ESC_PAT = re.compile(r'([\|\^\$\-\.\+\*\?\(\)\[\]\{\}\<\>])')
# format pattern
FMT_PAT = re.compile('(%%|%[^diouxXfFgGeEcrsa]*(?:.|$))')
def __init__(self, argformat):
# replace named patterns
self.fmt = argformat
spl = self.FMT_PAT.split(argformat)
spl_iter = iter(spl)
@ -178,30 +177,66 @@ class Change:
class IOHandlerBase:
"""generic command handler"""
"""abstract IO handler
def __init__(self, group):
# group is used for calling the proper analyze_<group> and change_<group> methods
IO handlers for parametrized access should inherit from this
"""
def get_read_func(self, modclass, pname):
"""get the read function for parameter pname"""
raise NotImplementedError
def get_write_func(self, pname):
"""get the write function for parameter pname"""
raise NotImplementedError
class IOHandler(IOHandlerBase):
"""IO handler for cases, where multiple parameters are treated with a common command
This IO handler works for a syntax, where the reply of a query command has
the same format as the arguments for the change command.
Examples: devices from LakeShore, PPMS
implementing classes may override the following class variables
"""
CMDARGS = [] # list of properties or parameters to be used for building some of the the query and change commands
CMDSEPARATOR = None # if not None, it is possible to join a command and a query with the given separator
def __init__(self, group, querycmd, replyfmt, changecmd=None):
"""initialize the IO handler
group: the handler group (used for analyze_<group> and change_<group>)
querycmd: the command for a query, may contain named formats for cmdargs
replyfmt: the format for reading the reply with some scanf like behaviour
changecmd: the first part of the change command (without values), may be
omitted if no write happens
"""
self.group = group
self.parameters = set()
self._module_class = None
self.querycmd = querycmd
self.replyfmt = CmdParser(replyfmt)
self.changecmd = changecmd
def parse_reply(self, reply):
"""return values from a raw reply"""
raise NotImplementedError
return self.replyfmt.parse(reply)
def make_query(self, module):
"""make a query"""
raise NotImplementedError
return self.querycmd % {k: getattr(module, k, None) for k in self.CMDARGS}
def make_change(self, module, *values):
"""make a change command from values"""
raise NotImplementedError
"""make a change command"""
changecmd = self.changecmd % {k: getattr(module, k, None) for k in self.CMDARGS}
return changecmd + self.replyfmt.format(*values)
def send_command(self, module, changecmd=''):
"""send a command (query or change+query) and parse the reply into a list
If changecmd is given, it is prepended before the query.
If changecmd is given, it is prepended before the query. changecmd must
contain the command separator at the end.
"""
querycmd = self.make_query(module)
reply = module.sendRecv(changecmd + querycmd)
@ -210,13 +245,14 @@ class IOHandlerBase:
def send_change(self, module, *values):
"""compose and send a command from values
and send a query afterwards. This method might be overriden, if the change command
can be combined with a query command, or if the change command already includes
a reply.
and send a query afterwards, or combine with a query command.
Override this method, if the change command already includes a reply.
"""
changecmd = self.make_change(module, *values)
module.sendRecv(changecmd) # ignore result
return self.send_command(module)
if self.CMDSEPARATOR is None:
module.sendRecv(changecmd) # ignore result
return self.send_command(module)
return self.send_command(module, changecmd + self.CMDSEPARATOR)
def get_read_func(self, modclass, pname):
"""returns the read function passed to the metaclass
@ -253,7 +289,7 @@ class IOHandlerBase:
raise
return Done
def get_write_func(self, pname, pre_wfunc=None):
def get_write_func(self, pname):
"""returns the write function passed to the metaclass
If pre_wfunc is given, it is to be called before change_<group>.
@ -261,20 +297,9 @@ class IOHandlerBase:
"""
self.change = getattr(self._module_class, 'change_' + self.group)
if pre_wfunc:
def wfunc(module, value, hdl=self, pname=pname, wfunc=pre_wfunc):
value = wfunc(module, value)
if value is None or value is Done:
return value
hdl.write(module, pname, value)
return Done
else:
def wfunc(module, value, hdl=self, pname=pname):
hdl.write(module, pname, value)
return Done
def wfunc(module, value, hdl=self, pname=pname):
hdl.write(module, pname, value)
return Done
return wfunc
@ -300,49 +325,3 @@ class IOHandlerBase:
result = self.analyze(module, *reply)
for k, v in result.items():
setattr(module, k, v)
class IOHandler(IOHandlerBase):
"""more evolved command handler
This command handler works for a syntax, where the reply of a query command has
the same format as the arguments for the change command.
Examples: devices from LakeShore, PPMS
implementing classes have to define/override the following:
"""
CMDARGS = [] # list of properties or parameters to be used for building some of the the query and change commands
CMDSEPARATOR = ';' # if given, it is valid to join a command a a query with the given separator
def __init__(self, group, querycmd, replyfmt, changecmd=None):
"""initialize the command handler
group: the handler group (used for analyze_<group> and change_<group>)
querycmd: the command for a query, may contain named formats for cmdargs
replyfmt: the format for reading the reply with some scanf like behaviour
changecmd: the first part of the change command (without values), may be
omitted if no write happens
"""
super().__init__(group)
self.querycmd = querycmd
self.replyfmt = CmdParser(replyfmt)
self.changecmd = changecmd
def parse_reply(self, reply):
"""return values from a raw reply"""
return self.replyfmt.parse(reply)
def make_query(self, module):
"""make a query"""
return self.querycmd % {k: getattr(module, k, None) for k in self.CMDARGS}
def make_change(self, module, *values):
"""make a change command"""
changecmd = self.changecmd % {k: getattr(module, k, None) for k in self.CMDARGS}
return changecmd + self.replyfmt.format(*values)
def send_change(self, module, *values):
"""join change and query commands"""
if self.CMDSEPARATOR is None:
return super().send_change(module, *values)
return self.send_command(module, self.make_change(module, *values) + self.CMDSEPARATOR)

View File

@ -136,6 +136,7 @@ class ModuleMeta(PropertyMeta):
break
rfunc = getattr(base, 'read_' + pname, None)
# create wrapper except when read function is already wrapped
if rfunc is None or getattr(rfunc, '__wrapped__', False) is False:
def wrapped_rfunc(self, pname=pname, rfunc=rfunc):
@ -157,37 +158,41 @@ class ModuleMeta(PropertyMeta):
setattr(self, pname, value) # important! trigger the setter
return value
wrapped_rfunc.__doc__ = rfunc.__doc__
if rfunc:
wrapped_rfunc.__doc__ = rfunc.__doc__
setattr(newtype, 'read_' + pname, wrapped_rfunc)
wrapped_rfunc.__wrapped__ = True
if not pobj.readonly:
wfunc = attrs.get('write_' + pname, None)
# if a handler and write_<param> is present, wfunc will be called
# by the handler first
wfunc = pobj.handler.get_write_func(pname, wfunc) if pobj.handler else wfunc
for base in bases:
if wfunc is not None:
break
wfunc = getattr(base, 'write_' + pname, None)
# create wrapper except when write function is already wrapped
if wfunc is None or getattr(wfunc, '__wrapped__', False) is False:
def wrapped_wfunc(self, value, pname=pname, wfunc=wfunc):
# append write function from handler, to be called after wfunc
wfuncs = (wfunc, pobj.handler.get_write_func(pname) if pobj.handler else None)
def wrapped_wfunc(self, value, pname=pname, wfuncs=wfuncs):
self.log.debug("check validity of %s = %r" % (pname, value))
pobj = self.accessibles[pname]
value = pobj.datatype(value)
if wfunc:
self.log.debug('calling %r(%r)' % (wfunc, value))
for wfunc in filter(None, wfuncs):
self.log.debug('calling %s %r(%r)' % (wfunc.__name__, wfunc, value))
returned_value = wfunc(self, value)
if returned_value is Done: # the setter is already triggered
return getattr(self, pname)
if returned_value is not None:
value = returned_value
if returned_value is None: # goodie: accept missing return value
break # handler is not called in this case
value = returned_value
setattr(self, pname, value)
return value
wrapped_wfunc.__doc__ = wfunc.__doc__
if wfunc:
wrapped_wfunc.__doc__ = wfunc.__doc__
setattr(newtype, 'write_' + pname, wrapped_wfunc)
wrapped_wfunc.__wrapped__ = True