2

I'm retrofitting the pytransitions state machine into an existing model, which happens to already have a column (the model also happens to be a SQLAlchemy model) that is named status.

I noticed that the transitions library injects a state field, but I'm not exactly sure if there's a way to change that field to my model's status column and have the transitions reflect on that particular field. If there's not a way currently, I'm thinking about using the machine.after_state_change callback and working from there.

Besides (ab)using that particular callback what would be a decent workaround?

CatarinaPBressan
  • 132
  • 2
  • 13

2 Answers2

2

As of 0.8.3 you can specify model_attribute on the Machine.

>>> from transitions import Machine 
  2 class Matter(object): 
  3     pass 
  4  
  5 lump = Matter() 
  6 transitions = [ 
  7     { 'trigger': 'melt', 'source': 'solid', 'dest': 'liquid' }, 
  8     { 'trigger': 'evaporate', 'source': 'liquid', 'dest': 'gas' }, 
  9     { 'trigger': 'sublimate', 'source': 'solid', 'dest': 'gas' }, 
 10     { 'trigger': 'ionize', 'source': 'gas', 'dest': 'plasma' } 
 11 ] 
 12 machine = Machine( 
 13     model=lump,  
 14     states=['solid', 'liquid', 'gas', 'plasma'],  
 15     initial='solid',  
 16     transitions=transitions,  
 17     model_attribute='my_state' 
 18 )
                                                       
>>> lump.state                                     
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Matter' object has no attribute 'state'

'Matter' object has no attribute 'state'

>>> lump.my_state                                  
'solid'
M.Vanderlee
  • 2,847
  • 2
  • 19
  • 16
1

As of transitions 0.7.1 there is no easy way to adjust the name of the model's state attribute. Overriding Transition._state_change, Machine._trigger and Machine.is_state is too complex, imho. You could 'alias' your status field by either using Model.state as a property:

class Model:

    @property
    def state(self):
        return self.status

    @state.setter
    def state(self, value):
        self.status = value

or maybe use SQLAlchemy's synonym:

from sqlalchemy import Column, String, Integer, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import synonym, sessionmaker
from transitions import Machine

engine = create_engine('sqlite:///:memory:')
Session = sessionmaker(bind=engine)
Base = declarative_base()


class Model(Base):
    __tablename__ = 'Model'
    id = Column(Integer, primary_key=True, autoincrement=True)
    status = Column(String, default='initial')
    state = synonym('status')

    def __repr__(self):
        return "<Model(id='{}', status='{}')>".format(self.id, self.status)


Base.metadata.create_all(engine)
session = Session()
m1 = Model()
m2 = Model()
session.add_all([m1, m2])

m = Machine(model=[m1, m2], states=['A', 'B'], initial='A')
print(session.query(Model).filter_by(status='A').all())
# >>> [<Model(id='1', status='A')>, <Model(id='2', status='A')>]
m1.to_B()
print(session.query(Model).filter_by(status='B').all())
# >>> [<Model(id='1', status='B')>]
aleneum
  • 2,083
  • 12
  • 29