https://github.com/toastdriven/definite
Simple finite state machines.
https://github.com/toastdriven/definite
machine python state workflow
Last synced: 4 months ago
JSON representation
Simple finite state machines.
- Host: GitHub
- URL: https://github.com/toastdriven/definite
- Owner: toastdriven
- License: bsd-3-clause
- Created: 2022-06-15T05:26:00.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2022-07-07T00:47:26.000Z (over 3 years ago)
- Last Synced: 2025-06-15T19:12:58.578Z (4 months ago)
- Topics: machine, python, state, workflow
- Language: Python
- Homepage: https://definite.rtfd.io/
- Size: 65.4 KB
- Stars: 28
- Watchers: 3
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
[](https://definite.readthedocs.io/en/latest/?badge=latest)
# `definite`
Simple finite state machines.
Perfect for representing workflows.
## Quickstart
```python
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_` 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