transitions icon indicating copy to clipboard operation
transitions copied to clipboard

on_exception and before_state_change callbacks fail if created in-class

Open andrewvaughan opened this issue 2 years ago • 1 comments

Using the NarcolepticSuperhero pattern fails when trying to also set on_exception or before_state_change callbacks:

class Automator():
    
    # ... removed for brevity ...

    def __init__(self):
        """
        Create a new Automator.
        """

        self.__machine  = Machine(
            model               = self,

            before_state_change = '__check_login',
            on_exception        = '__handle_exception',
            send_event          = True,

            states              = Automator.__states,
            transitions         = Automator.__transitions,
            initial             = 'starting'
        )

    # ... removed for brevity ...

    def __check_login(self, event):
        """
        Checks if the user is logged in before each call (and logs in, if necessary).
        """

        self.__logger.info("CHECKING LOGIN")



    def __handle_exception(self, event):
        """
        Gracefully handle any exceptions during the automation.
        """

        self.__logger.error(f"Exception: {event.error}")

        self.to_shutdown()

This creates a cascade of errors when attempting to be called:

Traceback (most recent call last):
  File ".../lib/python3.9/site-packages/transitions/core.py", line 1158, in resolve_callable
    func = getattr(event_data.model, func)
AttributeError: 'Automator' object has no attribute '__check_login'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File ".../lib/python3.9/site-packages/transitions/core.py", line 1165, in resolve_callable
    mod, name = func.rsplit('.', 1)
ValueError: not enough values to unpack (expected 2, got 1)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File ".../lib/python3.9/site-packages/transitions/core.py", line 435, in _process
    if trans.execute(event_data):
  File ".../lib/python3.9/site-packages/transitions/core.py", line 272, in execute
    event_data.machine.callbacks(itertools.chain(event_data.machine.before_state_change, self.before), event_data)
  File ".../lib/python3.9/site-packages/transitions/core.py", line 1123, in callbacks
    self.callback(func, event_data)
  File ".../lib/python3.9/site-packages/transitions/core.py", line 1140, in callback
    func = self.resolve_callable(func, event_data)
  File ".../lib/python3.9/site-packages/transitions/core.py", line 1171, in resolve_callable
    raise AttributeError("Callable with name '%s' could neither be retrieved from the passed "
AttributeError: Callable with name '__check_login' could neither be retrieved from the passed model nor imported from a module.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File ".../lib/python3.9/site-packages/transitions/core.py", line 1158, in resolve_callable
    func = getattr(event_data.model, func)
AttributeError: 'Automator' object has no attribute '__handle_exception'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File ".../lib/python3.9/site-packages/transitions/core.py", line 1165, in resolve_callable
    mod, name = func.rsplit('.', 1)
ValueError: not enough values to unpack (expected 2, got 1)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "...", line 142, in run
    self.automator.next()
  File ".../lib/python3.9/site-packages/transitions/core.py", line 401, in trigger
    return self.machine._process(func)
  File ".../lib/python3.9/site-packages/transitions/core.py", line 1188, in _process
    return trigger()
  File ".../lib/python3.9/site-packages/transitions/core.py", line 426, in _trigger
    return self._process(event_data)
  File ".../lib/python3.9/site-packages/transitions/core.py", line 441, in _process
    self.machine.callbacks(self.machine.on_exception, event_data)
  File ".../lib/python3.9/site-packages/transitions/core.py", line 1123, in callbacks
    self.callback(func, event_data)
  File ".../lib/python3.9/site-packages/transitions/core.py", line 1140, in callback
    func = self.resolve_callable(func, event_data)
  File ".../lib/python3.9/site-packages/transitions/core.py", line 1171, in resolve_callable
    raise AttributeError("Callable with name '%s' could neither be retrieved from the passed "
AttributeError: Callable with name '__handle_exception' could neither be retrieved from the passed model nor imported from a module.

It seems like the Machine cannot resolve callbacks from __init__ when they're created in the NarcolepticSuperhero pattern.

andrewvaughan avatar Apr 22 '22 15:04 andrewvaughan

Upon further review, it's because I had __ in front of the class names as private methods... not sure if that's resolvable with this scope. This may be closeable.

andrewvaughan avatar Apr 22 '22 15:04 andrewvaughan

Hello @andrewvaughan,

when passed as strings, callbacks will be resolved during runtime. This does not work with double underscore functions since their names will get obfuscated. If you NEED double underscore functions you could pass them by reference:

from transitions import Machine


class Automator:

    def __init__(self):
        self.__machine = Machine(
            model=self,
            before_state_change=self.__check_login,
            on_exception=self.__handle_exception,
            send_event=True,
            states=['starting'],
            initial='starting'
        )

    # ... removed for brevity ...

    def __check_login(self, event):
        print("CHECKING LOGIN")

    def __handle_exception(self, event):
        print(f"Exception: {event.error}")

m = Automator()
m.to_starting()

aleneum avatar Aug 25 '22 15:08 aleneum