pytransitions / transitions

A lightweight, object-oriented finite state machine implementation in Python with many extensions
MIT License
5.52k stars 525 forks source link

Generate predicate methods in lowercase #385

Closed artofhuman closed 4 years ago

artofhuman commented 4 years ago

Hi, @aleneum.

What do you think about the additional option for generating predicate methods in lowercase?

For example if we use enums usually we add enum values in uppercase like:

class States(enum.Enum):
    ERROR = 0
    RED = 1
    YELLOW = 2
    GREEN = 3

m = Machine(states=States, transitions=transitions, initial=States.RED)

And machine generate predicate methods like: m.is_RED() where state name part in upperacese.

It dont allows switching from strings to enums without rewrite codebase.

For example if we have old machine m = Machine(states=['error', 'red'], transitions=transitions, initial='red') and want to switch to enums we should rewrite predicates from .is_error() to .is_ERROR()

In this case, I propose to add the option to machine auto_lowercase=True or same analog.

I can prepare PR for this. WDYT?

aleneum commented 4 years ago

Hi @artofhuman,

In this case, I propose to add the option to machine auto_lowercase=True or same analog.

currently, I would say thats some quite specific (yet reasonable) use case that does not justify the increase in code complexity and yet another parameter in Machine. If this becomes a much desired feature, I might change my oppinion.

For now: if it's just about transition from strings to enums, I'd suggest letting your IDE handle the renaming. If it is about general usage I'd recommend customization via subclassing. The easiest would be to override Machine._checked_assignment which handles the assignment of convenience functions to the model. This will also impact event triggers though (add_transition('CamelCaseEvent', 'A', 'B') -> m.camelcaseevent()):

import enum
from transitions import Machine

class States(enum.Enum):
    ERROR = 0
    RED = 1
    YELLOW = 2
    GREEN = 3

class LowerCaseMachine(Machine):

    def _checked_assignment(self, model, name, func):
        super(LowerCaseMachine, self)._checked_assignment(model, name.lower(), func)

m = LowerCaseMachine(states=States, initial=States.RED)
assert m.is_red()

Alternatively, if only state-related functions should be changed, one could override Machine._add_model_to_state instead:

class LowerCaseMachine(Machine):

    def _add_model_to_state(self, state, model):
        # change state.name to state.name.lower() here ...
        self._checked_assignment(model, 'is_%s' % state.name.lower(), partial(self.is_state, state.value, model))

        for callback in self.state_cls.dynamic_methods:
            # ... and maybe here
            method = "{0}_{1}".format(callback, state.name.lower())
            if hasattr(model, method) and inspect.ismethod(getattr(model, method)) and \
                    method not in getattr(state, callback):
                state.add_callback(callback[3:], method)
aleneum commented 4 years ago

Closing this since there hasnt been new input for more than 14 days. Feel free to comment if there is more to add. I will reopen the issue if necessary.