python-statemachine
python-statemachine copied to clipboard
FRQ: special state for error handling
- Python State Machine version: 2.3.1
Description
Sometime action can't be completed and we need sort of cleanup code, which can be run after any action. In my case (for irrigation automation) : confirm the valve is closed and closed it if its not.
Idea to solve: Ability to issue special "from any state" transition to the cleanup handling final state. Specify the error handler in FSM configuration. Specify which states can be handled by the handler.
The current workaround
# the decorator to enter functions which could be handled
def catch_errors(handler=None):
def actual_decorator(func):
@wraps(func)
async def _wrapper(*args, **kwargs):
try:
return await func(*args, **kwargs)
except Exception as e:
if handler:
return await handler(*args, **kwargs)
return None
return _wrapper
return actual_decorator
class WorkItem(StateMachine):
created = State('created', initial=True)
open = State('open')
opened = State('opened')
close = State('close')
closed = State('closed', final=True)
error = State('error', final=True)
do_work = (created.to(open) |
open.to(opened, cond="is_open") |
opened.to(close, cond="can_close") |
close.to(closed, cond="is_closed"))
# the error transition map
do_error = (created.to(error) | open.to(error) | opened.to(error) | close.to(error))
# this will be sent if error (e.g. exception in enter handler occur)
async def on_error(self, *args, **kwargs):
await self.async_send("do_error")
# the decorated handler
@catch_errors(handler=on_error)
async def on_enter_created(self):
self._app.log(f"Entering 'created' state.")
if not self.is_closed():
async with asyncio.timeout(30):
self._app.error(f"Valve {self.zone.valve} is already open, closing it")
# this will throw exception
await self._app.call_service('homeassistant/turn_off1', entity_id=self.zone.valve)
# cleanup handler
async def on_enter_error(self):
self._app.error(f"Error in {self.zone.valve} {self.zone.moisture}")
try:
async with asyncio.timeout(60):
self._app.log(f"Closing valve {self.zone.valve} because of error")
await self._app.call_service('homeassistant/turn_off', entity_id=self.zone.valve)
while not self.is_closed():
await asyncio.sleep(1)
finally:
self._app.error(
f"Error in {self.zone.valve} {self.zone.moisture} the valve status is {self.zone.get_valve_state()}")
Hi @AlexMKX , how are you? Thanks for your suggestion. I think that in the end this relates to https://github.com/fgmacedo/python-statemachine/issues/386, as an alternative implementation solving the same issue.
Do you believe that some kind of a "finalize event handler" may solve your issue as well?