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 """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. The support for LakeShore and similar protocols is already included.
For read, instead of the methods read_<parameter> we write one method analyze_<group> For read, instead of the methods read_<parameter> we write one method analyze_<group>
@ -62,7 +63,7 @@ class CmdParser:
"""helper for parsing replies """helper for parsing replies
using a subset of old style python formatting. 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 # make a map of cast functions
@ -74,14 +75,12 @@ class CmdParser:
('xX', lambda x: int(x, 16)), ('xX', lambda x: int(x, 16)),
('eEfFgG', float), ('eEfFgG', float),
) for letter in letters} ) for letter in letters}
# pattern for chacaters to be escaped # pattern for characters to be escaped
ESC_PAT = re.compile('([\\%s])' % '\\'.join('|^$-.+*?()[]{}<>')) ESC_PAT = re.compile(r'([\|\^\$\-\.\+\*\?\(\)\[\]\{\}\<\>])')
# format pattern # format pattern
FMT_PAT = re.compile('(%%|%[^diouxXfFgGeEcrsa]*(?:.|$))') FMT_PAT = re.compile('(%%|%[^diouxXfFgGeEcrsa]*(?:.|$))')
def __init__(self, argformat): def __init__(self, argformat):
# replace named patterns
self.fmt = argformat self.fmt = argformat
spl = self.FMT_PAT.split(argformat) spl = self.FMT_PAT.split(argformat)
spl_iter = iter(spl) spl_iter = iter(spl)
@ -178,30 +177,66 @@ class Change:
class IOHandlerBase: class IOHandlerBase:
"""generic command handler""" """abstract IO handler
def __init__(self, group): IO handlers for parametrized access should inherit from this
# group is used for calling the proper analyze_<group> and change_<group> methods """
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.group = group
self.parameters = set() self.parameters = set()
self._module_class = None self._module_class = None
self.querycmd = querycmd
self.replyfmt = CmdParser(replyfmt)
self.changecmd = changecmd
def parse_reply(self, reply): def parse_reply(self, reply):
"""return values from a raw reply""" """return values from a raw reply"""
raise NotImplementedError return self.replyfmt.parse(reply)
def make_query(self, module): def make_query(self, module):
"""make a query""" """make a query"""
raise NotImplementedError return self.querycmd % {k: getattr(module, k, None) for k in self.CMDARGS}
def make_change(self, module, *values): def make_change(self, module, *values):
"""make a change command from values""" """make a change command"""
raise NotImplementedError 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=''): def send_command(self, module, changecmd=''):
"""send a command (query or change+query) and parse the reply into a list """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) querycmd = self.make_query(module)
reply = module.sendRecv(changecmd + querycmd) reply = module.sendRecv(changecmd + querycmd)
@ -210,13 +245,14 @@ class IOHandlerBase:
def send_change(self, module, *values): def send_change(self, module, *values):
"""compose and send a command from values """compose and send a command from values
and send a query afterwards. This method might be overriden, if the change command and send a query afterwards, or combine with a query command.
can be combined with a query command, or if the change command already includes Override this method, if the change command already includes a reply.
a reply.
""" """
changecmd = self.make_change(module, *values) changecmd = self.make_change(module, *values)
if self.CMDSEPARATOR is None:
module.sendRecv(changecmd) # ignore result module.sendRecv(changecmd) # ignore result
return self.send_command(module) return self.send_command(module)
return self.send_command(module, changecmd + self.CMDSEPARATOR)
def get_read_func(self, modclass, pname): def get_read_func(self, modclass, pname):
"""returns the read function passed to the metaclass """returns the read function passed to the metaclass
@ -253,7 +289,7 @@ class IOHandlerBase:
raise raise
return Done 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 """returns the write function passed to the metaclass
If pre_wfunc is given, it is to be called before change_<group>. If pre_wfunc is given, it is to be called before change_<group>.
@ -261,17 +297,6 @@ class IOHandlerBase:
""" """
self.change = getattr(self._module_class, 'change_' + self.group) 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): def wfunc(module, value, hdl=self, pname=pname):
hdl.write(module, pname, value) hdl.write(module, pname, value)
return Done return Done
@ -300,49 +325,3 @@ class IOHandlerBase:
result = self.analyze(module, *reply) result = self.analyze(module, *reply)
for k, v in result.items(): for k, v in result.items():
setattr(module, k, v) 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 break
rfunc = getattr(base, 'read_' + pname, None) 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: if rfunc is None or getattr(rfunc, '__wrapped__', False) is False:
def wrapped_rfunc(self, pname=pname, rfunc=rfunc): def wrapped_rfunc(self, pname=pname, rfunc=rfunc):
@ -157,36 +158,40 @@ class ModuleMeta(PropertyMeta):
setattr(self, pname, value) # important! trigger the setter setattr(self, pname, value) # important! trigger the setter
return value return value
if rfunc:
wrapped_rfunc.__doc__ = rfunc.__doc__ wrapped_rfunc.__doc__ = rfunc.__doc__
setattr(newtype, 'read_' + pname, wrapped_rfunc) setattr(newtype, 'read_' + pname, wrapped_rfunc)
wrapped_rfunc.__wrapped__ = True wrapped_rfunc.__wrapped__ = True
if not pobj.readonly: if not pobj.readonly:
wfunc = attrs.get('write_' + pname, None) 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: for base in bases:
if wfunc is not None: if wfunc is not None:
break break
wfunc = getattr(base, 'write_' + pname, None) 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: 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)) self.log.debug("check validity of %s = %r" % (pname, value))
pobj = self.accessibles[pname] pobj = self.accessibles[pname]
value = pobj.datatype(value) value = pobj.datatype(value)
if wfunc: for wfunc in filter(None, wfuncs):
self.log.debug('calling %r(%r)' % (wfunc, value)) self.log.debug('calling %s %r(%r)' % (wfunc.__name__, wfunc, value))
returned_value = wfunc(self, value) returned_value = wfunc(self, value)
if returned_value is Done: # the setter is already triggered if returned_value is Done: # the setter is already triggered
return getattr(self, pname) return getattr(self, pname)
if returned_value is not None: if returned_value is None: # goodie: accept missing return value
break # handler is not called in this case
value = returned_value value = returned_value
setattr(self, pname, value) setattr(self, pname, value)
return value return value
if wfunc:
wrapped_wfunc.__doc__ = wfunc.__doc__ wrapped_wfunc.__doc__ = wfunc.__doc__
setattr(newtype, 'write_' + pname, wrapped_wfunc) setattr(newtype, 'write_' + pname, wrapped_wfunc)
wrapped_wfunc.__wrapped__ = True wrapped_wfunc.__wrapped__ = True