Skip to main content

Simple finite state machines.

Project description

definite

Simple finite state machines.

Perfect for representing workflows.

Quickstart

from definite import FSM

# You define all the valid states, as well as what their allowed
# transitions are.
class Workflow(FSM):
    allowed_transitions = {
        "draft": ["awaiting_review", "rejected"],
        "awaiting_review": ["draft", "reviewed", "rejected"],
        "reviewed": ["published", "rejected"],
        "published": None,
        "rejected": ["draft"],
    }
    default_state = "draft"

# Right away, you can use the states/transitions as-is to enforce changes.
workflow = Workflow()
workflow.current_state() # "draft"

workflow.transition_to("awaiting_review")
workflow.transition_to("reviewed")

workflow.is_allowed("published") # True

# Invalid/disallowed transitions will throw an exception.
workflow.current_state() # "reviewed"
# ...which can only go to "published" or "rejected", but...
workflow.transition_to("awaiting_review")
# Traceback (most recent call last):
# ...
# workflow.TransitionNotAllowed: "reviewed" cannot transition to "awaiting_review"


# Additionally, you can set up extra code to fire on given state changes.
class Workflow(FSM):
    # Same transitions & default state.
    allowed_transitions = {
        "draft": ["awaiting_review", "rejected"],
        "awaiting_review": ["draft", "reviewed", "rejected"],
        "reviewed": ["published", "rejected"],
        "published": None,
        "rejected": ["draft"],
    }
    default_state = "draft"

    # Define a `handle_<state_name>` method on the class.
    def handle_awaiting_review(self, new_state):
        spell_check_results = check_spelling(self.obj.content)
        msg = (
            f"{self.obj.title} ready for review. "
            f"{len(spell_check_results)} spelling errors."
        )
        send_email(to=editor_email, message=msg)

    def handle_published(self, new_state):
        self.obj.pub_date = datetime.datetime.utcnow()
        self.obj.save()

    # You can also setup code that fires on **ANY** valid transition with the
    # special `handle_any` method.
    def handle_any(self, new_state):
        self.obj.state = new_state
        self.obj.save()


# We can pull in any Python object, like a database-backed model, that we
# want to associate with our FSM.
from news.models import NewsPost
news_post = NewsPost.objects.create(
    title="Hello world!",
    content="This iz our frist post!",
    state="draft",
)

# We start mostly the same, but this time pass an `obj` kwarg!
workflow = Workflow(obj=news_post)

# If you wanted to be explicit, you could also pass along the `initial_state`:
workflow = Workflow(
    obj=news_post,
    initial_state=news_post.state
)

workflow.current_state() # "draft"

# But when we trigger this change...
workflow.transition_to("awaiting_review")
# ...it triggers the spell check & the email we defined above, as well as
# hitting the `handle_any` method & updating the `state` field in the DB.
news_post.refresh_from_db()
news_post.state # "awaiting_review" !

Installation

pip install definite

Requirements

  • Python 3.6+

Testing

$ pytest .

License

New BSD

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

definite-1.0.0.tar.gz (6.1 kB view hashes)

Uploaded Source

Built Distribution

definite-1.0.0-py3-none-any.whl (6.4 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page