From 859bf5e1a2f9cdbfa633d7ddfaa1da852cab9294 Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Wed, 8 Jan 2020 14:22:19 +0100 Subject: [PATCH] moved some code from iohandler.py to metaclass.py - code for calling write_ 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 Reviewed-by: Markus Zolliker --- secop/iohandler.py | 139 +++++++++++++++++++-------------------------- secop/metaclass.py | 25 ++++---- 2 files changed, 74 insertions(+), 90 deletions(-) diff --git a/secop/iohandler.py b/secop/iohandler.py index dd954b1..f6ff2c3 100644 --- a/secop/iohandler.py +++ b/secop/iohandler.py @@ -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_ we write one method analyze_ @@ -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_ and change_ 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_ and change_) + 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_. @@ -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_ and change_) - 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) diff --git a/secop/metaclass.py b/secop/metaclass.py index b0756a9..b67b2fc 100644 --- a/secop/metaclass.py +++ b/secop/metaclass.py @@ -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_ 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