question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Integrate with Sqlalchemy

See original GitHub issue

Hi.

I wanted to ask if you have any plans to make this work easily with Sqlalchemuy model? Let say we have a model which has a status which needs to be updated every time we change the status using the machine. Of course we can implement this at the moment by using the trigger callbacks and update the state manually. I’m just wandering if you have such plans for the future.

Thx.

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Comments:10 (5 by maintainers)

github_iconTop GitHub Comments

4reactions
averypmccommented, Apr 23, 2021

I found the @botzill’s answer super helpful! I’m now using that pattern throughout my app to drive model state changes off of state machine transitions. Thanks a ton!

For those who come after and are curious about exactly how/why this code works, I added a bit of commenting. Assume that on our core model, Model, we have a field which tracks the machine state, and call that field model_state.

class StateMachineMixins:

      # When we access the state property, we want to be shown the value of the model_state
      # field, not the state of the state machine. This ensures we're always driving changes
      # through the state machine, and reading out state through the model_state field.
      @property
      def state(self):
           return self.model_state
          
      # When the state machine writes to the state field (a side-effect that occurs when you've called
      # a transition and the machine was instantiated with a reference to the model), have it update
      # the underlying model_state field.
      @state.setter
      def state(self, value):
          if self.model_state != value:
              self.model_state = value
           

The rest is fairly self-explanatory. After a state change, actually persist the changes to the database, etc. Thanks again @botzill!

4reactions
botzillcommented, Sep 19, 2016

OK, just a small update here. The examples above works OK when we initialize the model object ourself(and __init__ is called). But when we make a query then sqlachemy will initialize the model object itself and __init__ is not called(which mean that machine is not initialized). The solution I found so far is to use the sqlachemy events, here is a complete example:

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

engine = create_engine('sqlite:///test.sqlite')

Base = declarative_base()
session = scoped_session(sessionmaker(bind=engine))
Base._session = session


class StateMixin(object):
    @declared_attr
    def __tablename__(cls):
        return cls.__name__.lower()

    @declared_attr
    def status(cls):
        return Column(String())

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

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

    def after_state_change(self):
        self._session.add(self)
        self._session.commit()

    @classmethod
    def init_state_machine(cls, obj, *args, **kwargs):
        # when we load data from the DB(via query) we need to set the proper initial state
        initial = obj.status or 'created'

        machine = Machine(model=obj, states=states, transitions=transitions, initial=initial,
                          after_state_change='after_state_change')

        # in case that we need to have machine obj in model obj
        setattr(obj, 'machine', machine)


class User(Base, StateMixin):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    name = Column(String)

event.listen(User, 'init', User.init_state_machine)
event.listen(User, 'load', User.init_state_machine)

if __name__ == '__main__':
    states = ['created', 'validated', 'active', 'inactive']

    transitions = [
        ['validated', 'created', 'validated'],
        ['enable', ['validated', 'created'], 'active'],
        ['disable', 'active', 'inactive'],
    ]

    # Base.metadata.drop_all(engine)
    Base.metadata.create_all(engine)

    user = User(name="User1")

    user.validated()

    print(user.state)
    # output: validated

    # extract object from DB and init state machine
    user_db = session.query(User).first()

    print(user_db.state)
    # output: validated

    user_db.enable()
    print(user.state)
    # output: active

Read more comments on GitHub >

github_iconTop Results From Across the Web

SQLAlchemy Documentation — SQLAlchemy 1.4 ...
SQLAlchemy 2.0 is functionally available as part of SQLAlchemy 1.4, and integrates Core and ORM working styles more closely than ever.
Read more >
Flask Database Integration with SQLAlchemy - Section.io
In this article we will understand how to work with SQLAlchemy in a Flask web application. Storing data is an integral component of...
Read more >
SQLAlchemy — Python Tutorial - Towards Data Science
We often encounter data as Relational Databases. To work with them we generally would need to write raw SQL queries, pass them to...
Read more >
SQLAlchemy for Python | Sentry Documentation
The SQLAlchemy integration captures queries from SQLAlchemy as breadcrumbs. The integration is being tested with SQLAlchemy 1.2 or later. Python.
Read more >
How to Use Flask-SQLAlchemy to Interact with Databases in a ...
You'll use SQLAlchemy with SQLite, although you can use it with other database engines too, such as PostgreSQL and MySQL. SQLite works well...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found