fix inheritance of status codes attached to state functions
This commit is contained in:
parent
bd0f3a0b07
commit
79b8cd7b2d
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user