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.errors import ProgrammingError
from secop.lib.newstatemachine import StateMachine, Retry, Finish, Start, Stop
class status_code:
"""decorator for state methods"""
def __init__(self, code, text=None):
self.code = code
self.text = text
def status_code(code, text=None):
"""decorator, attaching a status to a state function
def __set_name__(self, owner, name):
if not issubclass(owner, HasStates):
raise ProgrammingError('when using decorator "status_code", %s must inherit HasStates' % owner.__name__)
self.cls = owner
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
:param code: the first element of the secop status tuple
:param text: the second element of the secop status tuple. if not given,
the name of the state function is used (underscores replaced by spaces)
:return: the decorator function
def __call__(self, func):
self.func = func
return self
if a state function has not attached status and is a method of the module running
the state machine, the status is inherited from an overridden method, if available
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:
@ -58,7 +55,7 @@ class HasStates:
all_status_changes = False # when true, send also updates for status changes within a cycle
_state_machine = None
_status = IDLE, ''
statusMap = {}
statusMap = None
def init_state_machine(self, **kwds):
self._state_machine = StateMachine(
@ -71,6 +68,7 @@ class HasStates:
def initModule(self):
super().initModule()
self.statusMap = {}
self.init_state_machine()
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')
else:
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:
status = default_code, name.replace('_', ' ')
return status
@ -108,8 +121,7 @@ class HasStates:
return Done
return sm.status
def doPoll(self):
super().doPoll()
def cycle_machine(self):
sm = self._state_machine
sm.cycle()
if sm.statefunc is None:
@ -118,6 +130,10 @@ class HasStates:
self.setFastPoll(False)
self.read_status()
def doPoll(self):
super().doPoll()
self.cycle_machine()
def on_cleanup(self, sm):
if isinstance(sm.cleanup_reason, Exception):
return self.on_error(sm)