Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add on_exception callbacks #485

Closed
thedrow opened this issue Nov 17, 2020 · 7 comments
Closed

Add on_exception callbacks #485

thedrow opened this issue Nov 17, 2020 · 7 comments

Comments

@thedrow
Copy link
Contributor

thedrow commented Nov 17, 2020

It'd be useful to be able to handle exceptions raised inside callbacks using on_exception callbacks.

In my case, I want to transition to the crashed state whenever an unhandled exception is raised during transitions.

@aleneum
Copy link
Member

aleneum commented Nov 17, 2020

Hi @thedrow,

the current way to do this is using finalize_event callbacks to check for errors. Alternatively, you could extend your own Event for some convenience:

from transitions import Machine, Event


class ErrorEvent(Event):
    def _process(self, event_data):
        try:
            return super()._process(event_data)
        except Exception:
            event_data.args = [event_data.error]
            event_data.machine.callbacks(["on_exception"], event_data)


class ErrorMachine(Machine):

    event_cls = ErrorEvent

    def on_exception(self, error):
        print("Help:", error)

    def raise_exception(self):
        raise Exception("Oh noes!")


m = ErrorMachine(after_state_change='raise_exception')
m.to_initial()

Having on_exception in core would be useful but I fear that this could become quite complex to handle (should ALL exceptions be caught? should exceptions be raised? only when there is no on_exception attached to a model?). Let's see if there is some feedback considering this.

@OneHoopyFrood
Copy link

OneHoopyFrood commented Dec 29, 2020

I personally think there's room in the core for this sort of functionality.

should ALL exceptions be caught?

Need to think this through, but my initial thought is yes. I'd leave the handling up to the user.

should exceptions be raised?
only when there is no on_exception attached to a model?

Yes, that seems reasonable. Raise normally unless the callback is registered.

@jekel
Copy link

jekel commented Jan 26, 2021

I need same functionality now(but for AsyncMachine), to not wrap code inside each transition into try except block
it will be very useful to have on_exception callback where I can catch all errors inside async callbacks

aleneum added a commit that referenced this issue Jan 31, 2021
useful for optional callbacks (e.g. #485)
aleneum added a commit that referenced this issue Mar 11, 2021
@aleneum
Copy link
Member

aleneum commented Mar 11, 2021

I opted for a simpler less invasive version for on_exception (than ignore_invalid_callbacks) and pushed it to master

@aleneum
Copy link
Member

aleneum commented Apr 6, 2021

closing this since there has been no report of issues or improvement suggestions. If you experience issues with on_exception, please open a bug report.

@aleneum aleneum closed this as completed Apr 6, 2021
@e0lithic
Copy link

e0lithic commented Apr 1, 2023

Can you please showcase an example of using the on_exception callback to change the state of the model to a desired fail state?
On doing a transition like self.to_failure() in the exception callback , it essentially results in a infinite loop.

@aleneum
Copy link
Member

aleneum commented Apr 2, 2023

On doing a transition like self.to_failure() in the exception callback , it essentially results in a infinite loop.

Hello @e0lithic, please open a separate issue or a discussion instead of commenting on a two years old closed issue.
There is no specific magic involved in handling exceptions:

from transitions import Machine


class Model:

    def handle_error(self):
        self.to_failed()

    def on_enter_failing(self):
        raise RuntimeError("Failing!")


model = Model()
machine = Machine(model=model, states=['initial', 'failing', 'failed'], initial='initial',
                  on_exception='handle_error')

assert model.is_initial()
model.to_failing()
assert model.is_failed()

You should make sure that a failure recovering procedure does not raise exceptions itself. If you end up in an infinite loop, it is very likely that your to_failure trigger raises an exception though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants