fix inheritance of status codes attached to state functions

This commit is contained in:
l_samenv 2022-12-21 10:38:11 +01:00
parent bd0f3a0b07
commit 79b8cd7b2d

View File

@ -27,30 +27,27 @@ handles status depending on statemachine state
from secop.core import BUSY, IDLE, ERROR, Parameter, Command, Done from secop.core import BUSY, IDLE, ERROR, Parameter, Command, Done
from secop.errors import ProgrammingError
from secop.lib.newstatemachine import StateMachine, Retry, Finish, Start, Stop from secop.lib.newstatemachine import StateMachine, Retry, Finish, Start, Stop
class status_code: def status_code(code, text=None):
"""decorator for state methods""" """decorator, attaching a status to a state function
def __init__(self, code, text=None):
self.code = code
self.text = text
def __set_name__(self, owner, name): :param code: the first element of the secop status tuple
if not issubclass(owner, HasStates): :param text: the second element of the secop status tuple. if not given,
raise ProgrammingError('when using decorator "status_code", %s must inherit HasStates' % owner.__name__) the name of the state function is used (underscores replaced by spaces)
self.cls = owner :return: the decorator function
self.name = name
if 'statusMap' not in owner.__dict__:
# we need a copy on each inheritance level
owner.statusMap = owner.statusMap.copy()
owner.statusMap[name] = self.code, name.replace('_', ' ') if self.text is None else self.text
setattr(owner, name, self.func) # replace with original method
def __call__(self, func): if a state function has not attached status and is a method of the module running
self.func = func the state machine, the status is inherited from an overridden method, if available
return self
a state function without attached status does not change the status, or, if it is
used as the start function, BUSY is taken as default status code
"""
def wrapper(func):
func.status = code, func.__name__.replace('_', ' ') if text is None else text
return func
return wrapper
class HasStates: class HasStates:
@ -58,7 +55,7 @@ class HasStates:
all_status_changes = False # when true, send also updates for status changes within a cycle all_status_changes = False # when true, send also updates for status changes within a cycle
_state_machine = None _state_machine = None
_status = IDLE, '' _status = IDLE, ''
statusMap = {} statusMap = None
def init_state_machine(self, **kwds): def init_state_machine(self, **kwds):
self._state_machine = StateMachine( self._state_machine = StateMachine(
@ -71,6 +68,7 @@ class HasStates:
def initModule(self): def initModule(self):
super().initModule() super().initModule()
self.statusMap = {}
self.init_state_machine() self.init_state_machine()
def state_transition(self, sm, newstate): def state_transition(self, sm, newstate):
@ -97,7 +95,22 @@ class HasStates:
status = self._state_machine.idle_status or (ERROR, 'Finish was returned without final status') status = self._state_machine.idle_status or (ERROR, 'Finish was returned without final status')
else: else:
name = statefunc.__name__ name = statefunc.__name__
status = self.statusMap.get(name) try:
# look up in statusMap cache
status = self.statusMap[name]
except KeyError:
# try to get status from method or inherited method
cls = type(self)
for base in cls.__mro__:
try:
status = getattr(base, name, None).status
break
except AttributeError:
pass
else:
status = None
# store it in the cache for all further calls
self.statusMap[name] = status
if status is None and default_code is not None: if status is None and default_code is not None:
status = default_code, name.replace('_', ' ') status = default_code, name.replace('_', ' ')
return status return status
@ -108,8 +121,7 @@ class HasStates:
return Done return Done
return sm.status return sm.status
def doPoll(self): def cycle_machine(self):
super().doPoll()
sm = self._state_machine sm = self._state_machine
sm.cycle() sm.cycle()
if sm.statefunc is None: if sm.statefunc is None:
@ -118,6 +130,10 @@ class HasStates:
self.setFastPoll(False) self.setFastPoll(False)
self.read_status() self.read_status()
def doPoll(self):
super().doPoll()
self.cycle_machine()
def on_cleanup(self, sm): def on_cleanup(self, sm):
if isinstance(sm.cleanup_reason, Exception): if isinstance(sm.cleanup_reason, Exception):
return self.on_error(sm) return self.on_error(sm)