pytransitions / transitions

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

Ability to manage multiple states per Model;Custom state field for Model #372

Closed v1k45 closed 4 years ago

v1k45 commented 4 years ago

First of all, thanks for this powerful lib :)

Currently the library makes an assumption that all Models will track the "state" using the state attribute. This assumption leads to two problems:

  1. No ability to track the state changes in some other attribute/field.
  2. A Model cannot have multiple states.

For example, If a use-case were to have two different fields on the Model. Both of them would represent different states and would be controlled by different Machines.

Something like this:

lump = Matter()

lump.global_state
lump.pipeline_state

global_machine = Machine(lump, model_field='global_state', **kwargs)
pipeline_machine = Machine(lump, model_field='pipeline_state', **kwargs)

Is this out of scope of this library?

candale commented 4 years ago

I've ran into a similar situation where I need to use a field from the model with a name different than "state".

It would also be cool if the Machine could start with the initial state that the model already has, from the field specified. For example:

class Matter:
    def __init__(self):
        self.state = 'liquid'

lump = Matter()

machine = Machine(lump, states=['solid', 'liquid', 'gas'], transitions=[{'trigger': 'melt', 'source': 'solid', 'dest': 'liquid'}])

lump.state
>>> 'initial'

This would allow one to bind a Machine to a model and continue a workflow from the state that the model is already in.

Does this make sense?

aleneum commented 4 years ago

Hello @v1k45 and @candale,

I think @v1k45 presented a very good use case for why configurable field names should be a thing. It's feasible and also does not add much to the complexity of the base class. This is definitely a feature that will be implemented in the next release. I can't tell when exactly that will happen but I will use the next 'transition dev time frame' to make it happen. Hopefully January or at latest February.

aleneum commented 4 years ago

@candale: Considering your specific request:

You can pass a 'initial' parameter to the machine to define the initial state. This can be unique for every machine/model.

from transitions import Machine

class Matter:
    def __init__(self):
        self.state = 'liquid'

lump = Matter()
lump2 = Matter()

machine = Machine(lump, states=['solid', 'liquid', 'gas'],
                        transitions=[{'trigger': 'melt', 'source': 'solid', 
                                      'dest': 'liquid'}],
                        initial=lump.state)
machine.add_model(lump2, initial='solid')

assert lump.is_liquid()
assert lump2.is_solid()
>>> 'initial'
candale commented 4 years ago

Yes, that makes sense. Thank you!

v1k45 commented 4 years ago

@aleneum I'd be happy to work on this and create a PR if you don't mind.

aleneum commented 4 years ago

Hi @v1k45,

@aleneum I'd be happy to work on this and create a PR if you don't mind.

that would be great! While model_field is short and comprehensive I would suggest model_attribute as the parameter's name since it follows Python's terminology and appears more generic -- at least to me. I associate 'field' with data classes and/or models (in django). That might be just me though.