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

Dynamic on_enter/exit_* methods #76

Closed
TheMysteriousX opened this issue Jan 29, 2016 · 6 comments
Closed

Dynamic on_enter/exit_* methods #76

TheMysteriousX opened this issue Jan 29, 2016 · 6 comments

Comments

@TheMysteriousX
Copy link
Contributor

The documentation says:

# Callbacks can also be added after initialization using 
# the dynamically added on_enter_ and on_exist_ methods.

I haven't looked into this in detail yet but a quick scan doesn't show a route through the code that creates on_enter/on_exit methods for each state after init has run.

Has this ever worked?

>>> from transitions import State, Machine
>>>
>>> class Matter(object):
...     def say_hello(self): print("hello, new state!")
...     def say_goodbye(self): print("goodbye, old state!")
...
>>> lump = Matter()
>>>
>>> states = [
...     State(name='solid', on_exit=['say_goodbye']),
...     'liquid',
...     { 'name': 'gas' }
...     ]
>>>
>>> machine = Machine(lump, states=states)
>>> machine.add_transition('sublimate', 'solid', 'gas')
>>>
>>> lump.on_exit_solid('say_goodbye')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Matter' object has no attribute 'on_exit_solid'
>>>
>>> machine.set_state('solid')
>>> lump.sublimate()
@aleneum
Copy link
Member

aleneum commented Jan 29, 2016

Hi,

they are created in core.py in __getattr__:

def __getattr__(self, name):
        if name.startswith('__'):
            if name in self.__dict__:
                return self.__dict__[name]
            else:
                raise AttributeError("{} does not exist".format(name))
        terms = name.split('_')
        if terms[0] in ['before', 'after']:
            name = '_'.join(terms[1:])
            if name not in self.events:
                raise MachineError('Event "%s" is not registered.' % name)
            return partial(self.events[name].add_callback, terms[0])

        # that's where the magic happens
        # on_enter / on_exit queries are concatenated with the current state 
        # and predefined with partial
        elif name.startswith('on_enter') or name.startswith('on_exit'):
            state = self.get_state('_'.join(terms[2:]))
            return partial(state.add_callback, terms[1])
        else:
            if name in self.__dict__:
                return self.__dict__[name]
            else:
                raise AttributeError("{} does not exist".format(name))

on_exit_solid has to be called from the machine, not from the model.

machine.on_exit_solid('say_goodbye')

should work.

@aleneum
Copy link
Member

aleneum commented Jan 29, 2016

Okay,

after some more careful reading what you said and what the documentation states, I guess you are right. Afaik, it does not work the way its stated in the documentation.

@tyarkoni
Copy link
Member

Good catch. I'll fix this right now. Thanks @TheMysteriousX!

@aleneum
Copy link
Member

aleneum commented Jan 29, 2016

in core.Machine.add_states there is a check if an on_enter_State method exists but it is not created otherwise.

#  Add enter/exit callbacks if there are existing bound methods
    enter_callback = 'on_enter_' + state_name
    if hasattr(self.model, enter_callback) and \
               inspect.ismethod(getattr(self.model, enter_callback)):
        state.add_callback('enter', enter_callback)
    exit_callback = 'on_exit_' + state_name
    if hasattr(self.model, exit_callback) and \
               inspect.ismethod(getattr(self.model, exit_callback)):
        state.add_callback('exit', exit_callback)

@tyarkoni
Copy link
Member

I assume we don't want to allow on_enter_* to work on the model itself, right? I mean, we definitely don't want to start patching an arbitrary model class's getattr method. So I'm just going to update the README to be clear about this.

@aleneum
Copy link
Member

aleneum commented Jan 29, 2016

👍 agree

TheMysteriousX pushed a commit to TheMysteriousX/transitions that referenced this issue Jan 30, 2016
The usage of the on_exit_* and on_enter_* callbacks was previously described incorrectly in the README.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants