### Install from source Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/installation.md Installs the python-statemachine library from local source using pip. ```shell python3 -m pip install -e . ``` -------------------------------- ### Environment setup Source: https://github.com/fgmacedo/python-statemachine/blob/develop/CLAUDE.md Commands to set up the development environment, including installing dependencies and pre-commit hooks. ```bash uv sync --all-extras --dev pre-commit install ``` -------------------------------- ### The `setup()` Protocol for Runtime Dependencies Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/listeners.md Illustrates how listeners can define a `setup()` method to receive runtime dependencies like database sessions or Redis clients during initialization. ```python >>> class DBListener: ... def __init__(self): ... self.session = None ... ... def setup(self, sm, session=None, **kwargs): ... self.session = session >>> class CacheListener: ... def __init__(self): ... self.redis = None ... ... def setup(self, sm, redis=None, **kwargs): ... self.redis = redis >>> class PersistentMachine(StateChart): ... listeners = [DBListener, CacheListener] ... ... s1 = State(initial=True) ... s2 = State(final=True) ... go = s1.to(s2) >>> sm = PersistentMachine(session="db_conn", redis="redis_conn") >>> sm.active_listeners[0].session 'db_conn' >>> sm.active_listeners[1].redis 'redis_conn' ``` -------------------------------- ### Install using poetry Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/installation.md Installs the latest release of python-statemachine using the poetry package manager. ```shell poetry add python-statemachine ``` -------------------------------- ### Installation Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/diagram.md Install the necessary packages for diagram generation, including pydot and Graphviz. ```bash pip install python-statemachine[diagrams] # installs pydot ``` ```bash sudo apt install graphviz ``` -------------------------------- ### Install using uv Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/installation.md Installs the latest release of python-statemachine using the uv package manager. ```shell uv add python-statemachine ``` -------------------------------- ### Install using pip Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/installation.md Installs the latest release of python-statemachine using pip. ```shell python3 -m pip install python-statemachine ``` -------------------------------- ### Install development dependencies Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/contributing.md Run uv sync to install all development dependencies and create a virtual environment. ```none uv sync --all-extras ``` -------------------------------- ### Install with pydot dependency Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/installation.md Installs python-statemachine along with the pydot dependency for diagram generation using pip. ```shell python3 -m pip install "python-statemachine[diagrams]" ``` -------------------------------- ### Install python-statemachine Source: https://github.com/fgmacedo/python-statemachine/blob/develop/README.md Install the python-statemachine package using pip. ```bash pip install python-statemachine ``` -------------------------------- ### Clone from GitHub Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/installation.md Clones the public repository of python-statemachine from GitHub. ```shell git clone git://github.com/fgmacedo/python-statemachine ``` -------------------------------- ### Install with diagram generation support Source: https://github.com/fgmacedo/python-statemachine/blob/develop/README.md Install python-statemachine with the 'diagrams' extra, which requires Graphviz. ```bash pip install python-statemachine[diagrams] ``` -------------------------------- ### Data Migration Example Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/integrations.md Example of how to set the state machine attribute manually on historical model objects during a data migration. ```python def backfill_data(apps, schema_editor): MyModel = apps.get_model("myapp", "MyModel") MyModel.state_machine_name = "myapp.statemachines.MyStateMachine" for obj in MyModel.objects.all(): obj.statemachine # now available ``` -------------------------------- ### Install pre-commit validations Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/contributing.md Install the pre-commit validations for the project. ```none pre-commit install ``` -------------------------------- ### prepare_event callback example Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/api.md Example of defining and using the `prepare_event` callback in a state machine subclass to pass additional keyword arguments to subsequent callbacks. ```python class MyMachine(StateChart): initial = State(initial=True) loop = initial.to.itself() def prepare_event(self): return {"request_id": generate_id()} def on_loop(self, request_id): # request_id is available here ... ``` -------------------------------- ### Download tarball Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/installation.md Downloads the tarball of the python-statemachine source from GitHub. ```shell curl -OL https://github.com/fgmacedo/python-statemachine/tarball/main ``` -------------------------------- ### Turnstile StateChart Example Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/concepts.md A minimal example demonstrating the core concepts of StateChart, State, Transition, Event, and Action. ```python >>> from statemachine import StateChart, State >>> class Turnstile(StateChart): ... locked = State(initial=True) ... unlocked = State() ... ... coin = locked.to(unlocked, on="thank_you") ... push = unlocked.to(locked) ... ... def thank_you(self): ... return "Welcome!" >>> sm = Turnstile() >>> sm.coin() 'Welcome!' ``` -------------------------------- ### from_() example Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/transitions.md Declares a transition from multiple sources to a single target state. ```python >>> class OrderSM(StateChart): ... pending = State(initial=True) ... processing = State() ... shipped = State(final=True) ... ... process = pending.to(processing) ... ship = shipped.from_(pending, processing) ``` -------------------------------- ### Testing sync and async engines example Source: https://github.com/fgmacedo/python-statemachine/blob/develop/CLAUDE.md Example of using the sm_runner fixture to test a statechart on both sync and async engines. ```python async def test_something(self, sm_runner): sm = await sm_runner.start(MyStateChart) await sm_runner.send(sm, "some_event") assert "expected_state" in sm.configuration_values ``` -------------------------------- ### Start a release branch Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/contributing.md Start a new release branch using git-flow. ```shell git checkout develop git pull origin develop git flow release start X.Y.Z ``` -------------------------------- ### Example Execution Source: https://github.com/fgmacedo/python-statemachine/blob/develop/tests/testcases/issue308.md Demonstrates the instantiation and sequential execution of the 'cycle' event on the TestSM, showing state changes and output. ```python >>> m = TestSM() enter state1 >>> m.state1.is_active, m.state2.is_active, m.state3.is_active, m.state4.is_active, list(m.configuration_values) (True, False, False, False, ['state1']) >>> _ = m.cycle() before cycle exit state1 on cycle enter state2 after cycle >>> m.state1.is_active, m.state2.is_active, m.state3.is_active, m.state4.is_active, list(m.configuration_values) (False, True, False, False, ['state2']) >>> _ = m.cycle() before cycle exit state2 on cycle enter state3 after cycle >>> m.state1.is_active, m.state2.is_active, m.state3.is_active, m.state4.is_active, list(m.configuration_values) (False, False, True, False, ['state3']) >>> _ = m.cycle() before cycle exit state3 on cycle enter state4 after cycle >>> m.state1.is_active, m.state2.is_active, m.state3.is_active, m.state4.is_active, list(m.configuration_values) (False, False, False, True, ['state4']) ``` -------------------------------- ### Typed Models Example Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/models.md Example demonstrating how to use typed models with StateChart for improved type checking and IDE support. ```python >>> from statemachine import State, StateChart >>> class OrderModel: ... order_id: str = "" ... total: float = 0.0 ... def confirm(self): ... return f"Order {self.order_id} confirmed: ${self.total}" >>> class OrderWorkflow(StateChart["OrderModel"]): ... draft = State(initial=True) ... confirmed = State(final=True) ... confirm = draft.to(confirmed, on="on_confirm") ... def on_confirm(self): ... return self.model.confirm() >>> model = OrderModel() >>> model.order_id = "A-123" >>> model.total = 49.90 >>> sm = OrderWorkflow(model=model) >>> sm.send("confirm") 'Order A-123 confirmed: $49.9' ``` -------------------------------- ### Classic State Pattern Example Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/how-to/coming_from_state_pattern.md An example of the classic State Pattern implementation for an order workflow with four states (draft, confirmed, shipped, delivered) and a guard condition. ```python from abc import ABC, abstractmethod class OrderState(ABC): """Abstract base for all order states.""" @abstractmethod def confirm(self, order): ... @abstractmethod def ship(self, order): ... @abstractmethod def deliver(self, order): ... class DraftState(OrderState): def confirm(self, order): if order.item_count <= 0: raise ValueError("Cannot confirm an empty order") order._state = ConfirmedState() print("Order confirmed") def ship(self, order): raise RuntimeError("Cannot ship a draft order") def deliver(self, order): raise RuntimeError("Cannot deliver a draft order") class ConfirmedState(OrderState): def confirm(self, order): raise RuntimeError("Order already confirmed") def ship(self, order): order._state = ShippedState() print("Order shipped") def deliver(self, order): raise RuntimeError("Cannot deliver before shipping") class ShippedState(OrderState): def confirm(self, order): raise RuntimeError("Cannot confirm a shipped order") def ship(self, order): raise RuntimeError("Order already shipped") def deliver(self, order): order._state = DeliveredState() print("Order delivered") class DeliveredState(OrderState): def confirm(self, order): raise RuntimeError("Order already delivered") def ship(self, order): raise RuntimeError("Order already delivered") def deliver(self, order): raise RuntimeError("Order already delivered") class Order: def __init__(self, item_count=0): self._state = DraftState() self.item_count = item_count def confirm(self): self._state.confirm(self) def ship(self): self._state.ship(self) def deliver(self): self._state.deliver(self) ``` -------------------------------- ### Sphinx Setup Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/diagram.md Add the statemachine diagram extension to your Sphinx configuration. ```python extensions = [ ... "statemachine.contrib.diagram.sphinx_ext", ] ``` -------------------------------- ### Writing async callbacks Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/async.md Example of defining and using async callbacks within a state machine. ```python >>> class AsyncStateMachine(StateChart): ... initial = State("Initial", initial=True) ... final = State("Final", final=True) ... ... keep = initial.to.itself(internal=True) ... advance = initial.to(final) ... ... async def on_advance(self): ... return 42 >>> async def run_sm(): ... sm = AsyncStateMachine() ... result = await sm.advance() ... print(f"Result is {result}") ... print(list(sm.configuration_values)) >>> asyncio.run(run_sm()) Result is 42 ['final'] ``` -------------------------------- ### States Definition Example Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/api.md Demonstrates how to define states using a list of tuples and create a StateChart. ```python >>> states_def = [('open', {'initial': True}), ('closed', {'final': True})] ``` ```python >>> from statemachine import StateChart >>> class SM(StateChart): ... ... states = States({ ... name: State(**params) for name, params in states_def ... }) ... ... close = states.open.to(states.closed) ``` ```python >>> sm = SM() >>> sm.send("close") >>> sm.closed.is_active True ``` -------------------------------- ### from_.any() example Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/transitions.md Creates a transition from every non-final state to a target state, useful for global events. ```python >>> class OrderWorkflow(StateChart): ... pending = State(initial=True) ... processing = State() ... done = State() ... completed = State(final=True) ... cancelled = State(final=True) ... ... process = pending.to(processing) ... complete = processing.to(done) ... finish = done.to(completed) ... cancel = cancelled.from_.any() >>> sm = OrderWorkflow() >>> sm.send("cancel") >>> "cancelled" in sm.configuration_values True ``` -------------------------------- ### Eventless Transitions Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/transitions.md Example demonstrating eventless transitions that fire automatically based on a guard condition. ```python >>> from statemachine import State, StateChart >>> class AutoEscalation(StateChart): ... normal = State(initial=True) ... escalated = State(final=True) ... normal.to(escalated, cond="should_escalate") ... report = normal.to.itself(internal=True, on="add_report") ... report_count = 0 ... def should_escalate(self): ... return self.report_count >= 3 ... def add_report(self): ... self.report_count += 1 >>> sm = AutoEscalation() >>> sm.send("report") >>> sm.send("report") >>> "normal" in sm.configuration_values True >>> sm.send("report") >>> "escalated" in sm.configuration_values True ``` -------------------------------- ### Creating machines from dicts Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/how-to/coming_from_transitions.md Example of creating a StateChart class from a dictionary definition using `create_machine_class_from_definition`. ```python from statemachine.io import create_machine_class_from_definition TrafficLight = create_machine_class_from_definition( "TrafficLight", states={ "green": { "initial": True, "on": {"change": [{"target": "yellow"}]} }, "yellow": { "on": {"change": [{"target": "red"}]} }, "red": { "on": {"change": [{"target": "green"}]} }, }, ) sm = TrafficLight() sm.send("change") sm.yellow.is_active >>> True sm.send("change") sm.red.is_active >>> True ``` -------------------------------- ### Getting DOT representation Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/diagram.md Example of how to get the DOT string representation of a state machine. ```python >>> from tests.examples.order_control_machine import OrderControl >>> sm = OrderControl() >>> print(sm._graph().to_string()) digraph OrderControl { ... } ``` -------------------------------- ### Instantiating and sending events Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/tutorial.md Demonstrates how to create an instance of the state machine and send events to trigger transitions. ```python >>> order = CoffeeOrder() >>> order.pending.is_active True >>> order.send("start") >>> order.preparing.is_active True >>> order.send("finish") >>> order.send("pick_up") >>> order.picked_up.is_active True ``` -------------------------------- ### Async State Machine Example Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/tutorial.md Demonstrates how to use async def for state machine callbacks, allowing for asynchronous operations within the state machine. ```python >>> import asyncio >>> from statemachine import StateChart, State >>> class CoffeeOrder(StateChart): ... pending = State(initial=True) ... preparing = State() ... ready = State(final=True) ... ... start = pending.to(preparing) ... finish = preparing.to(ready) ... ... async def on_start(self, drink: str = "coffee"): ... return f"Started making {drink}" ... ... async def on_finish(self): ... return "Drink is ready!" >>> async def main(): ... order = CoffeeOrder() ... result = await order.send("start", drink="latte") ... print(result) ... result = await order.send("finish") ... print(result) >>> asyncio.run(main()) Started making latte Drink is ready! ``` -------------------------------- ### Passing a class to the `invoke` parameter Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/invoke.md Example of passing a class implementing `IInvoke` to the `invoke` parameter, where each state machine instance gets a fresh handler. ```python >>> config_file = Path(tempfile.mktemp(suffix=".csv")) >>> _ = config_file.write_text("name,age\nAlice,30\nBob,25\n") >>> class CSVLoader(StateChart): ... loading = State(initial=True, invoke=FileReader) ... ready = State(final=True) ... done_invoke_loading = loading.to(ready) ... ... def __init__(self, file_path, **kwargs): ... self.file_path = file_path ... super().__init__(**kwargs) ... ... def on_enter_ready(self, data=None, **kwargs): ... self.content = data >>> sm = CSVLoader(file_path=str(config_file)) >>> time.sleep(0.2) >>> "ready" in sm.configuration_values True >>> sm.content 'name,age\nAlice,30\nBob,25\n' >>> config_file.unlink() ``` -------------------------------- ### Creating States from Enum Example Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/api.md Shows how to create states from an Enum class using States.from_enum() and define transitions. ```python >>> class Status(Enum): ... pending = 1 ... completed = 2 ``` ```python >>> from statemachine import StateChart >>> class ApprovalMachine(StateChart): ... ... _ = States.from_enum(Status, initial=Status.pending, final=Status.completed) ... ... finish = _.pending.to(_.completed) ... ... def on_enter_completed(self): ... print("Completed!") ``` ```python >>> sm = ApprovalMachine() >>> sm.pending.is_active True ``` ```python >>> sm.send("finish") Completed! ``` ```python >>> sm.completed.is_active True >>> sm.current_state_value ``` -------------------------------- ### Your first state machine Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/tutorial.md Defines a simple state machine for a coffee order with states and transitions. ```python >>> from statemachine import StateChart, State >>> class CoffeeOrder(StateChart): ... # Define the states ... pending = State(initial=True) ... preparing = State() ... ready = State() ... picked_up = State(final=True) ... ... # Define events — each one groups one or more transitions ... start = pending.to(preparing) ... finish = preparing.to(ready) ... pick_up = ready.to(picked_up) ``` -------------------------------- ### Implementing the `IInvoke` protocol Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/invoke.md Example showing how to implement the `IInvoke` protocol for advanced use cases, providing access to `InvokeContext`. ```python >>> from statemachine.invoke import IInvoke, InvokeContext >>> class FileReader: ... """Reads a file and returns its content. Supports cancellation.""" ... def run(self, ctx: InvokeContext): ... # ctx.invokeid — unique ID for this invocation ... # ctx.state_id — the state that triggered invoke ... # ctx.cancelled — threading.Event, set when state exits ... # ctx.send — send events to parent machine ... # ctx.machine — reference to parent machine ... # ctx.kwargs — keyword arguments from the triggering event ... path = ctx.machine.file_path ... return Path(path).read_text() ... ... def on_cancel(self): ... pass # cleanup resources if needed >>> isinstance(FileReader(), IInvoke) True ``` -------------------------------- ### Compound states: breaking complexity into levels Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/tutorial.md Example of modeling a compound state for drink preparation with internal sub-steps (grinding, brewing, serving) using the State.Compound class. ```python >>> from statemachine import StateChart, State >>> class CoffeeOrder(StateChart): ... pending = State(initial=True) ... ... class preparing(State.Compound): ... """Drink preparation with internal steps.""" ... grinding = State(initial=True) ... brewing = State() ... serving = State(final=True) ... ... grind = grinding.to(brewing) ... brew = brewing.to(serving) ... ... picked_up = State(final=True) ... ... start = pending.to(preparing) ... done_state_preparing = preparing.to(picked_up) >>> order = CoffeeOrder() >>> order.send("start") >>> set(order.configuration_values) == {"preparing", "grinding"} True >>> order.send("grind") >>> "brewing" in order.configuration_values True >>> order.send("brew") >>> order.picked_up.is_active True ``` -------------------------------- ### Guards: conditional transitions Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/tutorial.md Example demonstrating how to use guards to control state transitions based on conditions. The transition to 'preparing' only occurs if the 'paid' attribute is true. ```python >>> from statemachine import StateChart, State >>> class CoffeeOrder(StateChart): ... pending = State(initial=True) ... preparing = State() ... ready = State() ... picked_up = State(final=True) ... ... # Two transitions on the same event — checked in declaration order. ... # The first whose guard passes wins. ... start = ( ... pending.to(preparing, cond="is_paid") ... | pending.to(pending) # fallback: stay in pending ... ) ... finish = preparing.to(ready) ... pick_up = ready.to(picked_up) ... ... paid: bool = False ... ... def is_paid(self): ... return self.paid >>> order = CoffeeOrder() >>> order.send("start") # not paid — stays in pending >>> order.pending.is_active True >>> order.paid = True >>> order.send("start") # paid — moves to preparing >>> order.preparing.is_active True ``` -------------------------------- ### Adding behavior with actions Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/tutorial.md Illustrates how to add behavior to state entries, exits, and transitions using conventionally named methods. ```python >>> from statemachine import StateChart, State >>> class CoffeeOrder(StateChart): ... pending = State(initial=True) ... preparing = State() ... ready = State() ... picked_up = State(final=True) ... ... start = pending.to(preparing) ... finish = preparing.to(ready) ... pick_up = ready.to(picked_up) ... ... # Called when entering the "preparing" state ... def on_enter_preparing(self): ... print("Barista starts making the drink.") ... ... # Called when the "finish" event fires ... def on_finish(self): ... print("Drink is ready!") ... ... # Called when entering the "picked_up" state ... def on_enter_picked_up(self): ... print("Customer picked up the order. Enjoy!") >>> order = CoffeeOrder() >>> order.send("start") Barista starts making the drink. >>> order.send("finish") Drink is ready! >>> order.send("pick_up") Customer picked up the order. Enjoy! ``` -------------------------------- ### Adding listeners at runtime Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/tutorial.md Example showing how to add listeners to a state machine instance at runtime using the add_listener method. The AuditLog class logs transitions. ```python >>> class AuditLog: ... def after_transition(self, source: State, target: State, event: str): ... print(f"[audit] {source.id} →({event})→ {target.id}") >>> order = CoffeeOrder() [notify] Order is now: pending >>> _ = order.add_listener(AuditLog()) >>> order.send("start") [notify] Order is now: preparing [audit] pending →(start)→ preparing ``` -------------------------------- ### Text representations with format() Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/tutorial.md Demonstrates how to get text representations of state machines using Python's built-in format() or f-strings, supporting various formats like mermaid, md, rst, dot, and svg. ```python >>> from tests.machines.tutorial_coffee_order import CoffeeOrder >>> print(f"{CoffeeOrder:md}") | State | Event | Guard | Target | | --------- | ------- | ----- | --------- | | Pending | Start | | Preparing | | Preparing | Finish | | Ready | | Ready | Pick up | | Picked up | ``` ```python >>> print(f"{CoffeeOrder:mermaid}") stateDiagram-v2 direction LR state "Pending" as pending state "Preparing" as preparing state "Ready" as ready state "Picked up" as picked_up [*] --> pending picked_up --> [*] pending --> preparing : Start preparing --> ready : Finish ready --> picked_up : Pick up ``` -------------------------------- ### Install git-flow Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/contributing.md Install git-flow on macOS. ```shell brew install git-flow ``` -------------------------------- ### Parallel states: concurrent regions Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/tutorial.md Example demonstrating parallel states for concurrent regions, such as preparing a drink and a snack simultaneously, using the State.Parallel class. ```python >>> from statemachine import StateChart, State >>> class CoffeeOrder(StateChart): ... pending = State(initial=True) ... ... class preparing(State.Parallel): ... class drink(State.Compound): ... brewing = State(initial=True) ... drink_done = State(final=True) ... brew = brewing.to(drink_done) ... class snack(State.Compound): ... heating = State(initial=True) ... snack_done = State(final=True) ... heat = heating.to(snack_done) ... ... picked_up = State(final=True) ... ... start = pending.to(preparing) ... done_state_preparing = preparing.to(picked_up) >>> order = CoffeeOrder() >>> order.send("start") >>> "brewing" in order.configuration_values and "heating" in order.configuration_values True >>> order.send("brew") # drink done, snack still heating >>> "drink_done" in order.configuration_values and "heating" in order.configuration_values True >>> order.is_terminated # drink region finished, but snack hasn't False >>> order.send("heat") # both done — auto-transitions to picked_up >>> order.picked_up.is_active True >>> order.is_terminated True ``` -------------------------------- ### Highlighting current state with --events Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/diagram.md Demonstrates how to use the `--events` flag to instantiate the machine and send events before rendering for highlighting the current state. ```bash python -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine diagram.png --events cycle cycle cycle ``` -------------------------------- ### States from Enum example Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/states.md Example showing how to declare states from an Enum type. ```python >>> from statemachine import StateChart >>> class ApprovalMachine(StateChart): ... ... _ = States.from_enum(Status, initial=Status.pending, final=Status.completed) ... ... finish = _.pending.to(_.completed) ... ... def on_enter_completed(self): ... print("Completed!") ``` -------------------------------- ### Validation Examples Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/weighted_transitions.md Illustrative examples of invalid inputs to weighted_transitions() and the resulting errors. ```python >>> weighted_transitions(State(initial=True)) Traceback (most recent call last): ... ValueError: weighted_transitions() requires at least one (target, weight) destination >>> s1, s2 = State(initial=True), State() >>> weighted_transitions(s1, (s2, -5)) Traceback (most recent call last): ... ValueError: Destination 0: weight must be positive, got -5 >>> weighted_transitions(s1, (s2, "ten")) Traceback (most recent call last): ... TypeError: Destination 0: weight must be a positive number, got str ``` -------------------------------- ### Calling events as methods Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/tutorial.md Shows an alternative way to trigger events by calling them as methods on the state machine instance. ```python >>> order = CoffeeOrder() >>> order.start() >>> order.preparing.is_active True ``` -------------------------------- ### Passing keyword arguments to send() Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/tutorial.md Shows how to pass keyword arguments to the send() method, which can be accessed in callback functions. ```python >>> from statemachine import StateChart, State >>> class CoffeeOrder(StateChart): ... pending = State(initial=True) ... preparing = State() ... ready = State(final=True) ... ... start = pending.to(preparing) ... finish = preparing.to(ready) ... ... def on_start(self, drink: str = "coffee"): ... print(f"Making a {drink}.") >>> order = CoffeeOrder() >>> order.send("start", drink="cappuccino") Making a cappuccino. ``` -------------------------------- ### Listeners: observing state changes Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/tutorial.md Example demonstrating how to use listeners to react to state changes. The NotificationService class is used as a listener to print messages when states change. ```python >>> from statemachine import StateChart, State >>> class NotificationService: ... def on_enter_state(self, target: State): ... print(f"[notify] Order is now: {target.id}") >>> class CoffeeOrder(StateChart): ... listeners = [NotificationService] ... ... pending = State(initial=True) ... preparing = State() ... ready = State(final=True) ... ... start = pending.to(preparing) ... finish = preparing.to(ready) >>> order = CoffeeOrder() [notify] Order is now: pending >>> order.send("start") [notify] Order is now: preparing >>> order.send("finish") [notify] Order is now: ready ``` -------------------------------- ### Dependency injection in callbacks Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/tutorial.md Demonstrates how to declare parameters in callback methods, which are then automatically injected by the library. ```python >>> from statemachine import StateChart, State >>> class CoffeeOrder(StateChart): ... pending = State(initial=True) ... preparing = State() ... ready = State() ... picked_up = State(final=True) ... ... start = pending.to(preparing) ... finish = preparing.to(ready) ... pick_up = ready.to(picked_up) ... ... def on_enter_preparing(self, source: State, target: State): ... print(f"{source.id} → {target.id}") ... ... def on_finish(self): ... print("Done!") >>> order = CoffeeOrder() >>> order.send("start") pending → preparing >>> order.send("finish") Done! ``` -------------------------------- ### History states: remember where you left off Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/tutorial.md Example showcasing the use of `HistoryState` to remember the active child state when exiting a compound state, allowing resumption from the correct point. ```python >>> from statemachine import HistoryState, StateChart, State >>> class CoffeeOrder(StateChart): ... class preparing(State.Compound): ... grinding = State(initial=True) ... brewing = State() ... done = State(final=True) ... h = HistoryState() ... ... grind = grinding.to(brewing) ... brew = brewing.to(done) ... ... paused = State() ... finished = State(final=True) ... ... pause = preparing.to(paused) ... resume = paused.to(preparing.h) # ← return via history ... done_state_preparing = preparing.to(finished) >>> order = CoffeeOrder() >>> order.send("grind") # now in "brewing" >>> "brewing" in order.configuration_values True >>> order.send("pause") # leave preparing >>> order.send("resume") # history restores "brewing", not "grinding" >>> "brewing" in order.configuration_values True >>> order.send("brew") # finish preparation >>> order.finished.is_active True ``` -------------------------------- ### Generating diagrams programmatically Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/tutorial.md Code snippet to generate a state machine diagram and save it as a PNG file. ```python order = CoffeeOrder() order._graph().write_png("order.png") ``` -------------------------------- ### Quick start Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/invoke.md The simplest invoke is a plain callable passed to the `invoke` parameter. Here we read a config file in a background thread and transition to `ready` when the data is available. ```python import json import tempfile import time from pathlib import Path from statemachine import State, StateChart config_file = Path(tempfile.mktemp(suffix=".json")) _ = config_file.write_text('{"db_host": "localhost", "db_port": 5432}') def load_config(): return json.loads(config_file.read_text()) class ConfigLoader(StateChart): loading = State(initial=True, invoke=load_config) ready = State(final=True) done_invoke_loading = loading.to(ready) def on_enter_ready(self, data=None, **kwargs): self.config = data sm = ConfigLoader() time.sleep(0.2) "ready" in sm.configuration_values sm.config config_file.unlink() ``` -------------------------------- ### Guards with arguments Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/tutorial.md Example showing guards that accept arguments. The 'is_paid' guard checks if the provided 'amount' meets a minimum threshold. ```python >>> from statemachine import StateChart, State >>> class CoffeeOrder(StateChart): ... pending = State(initial=True) ... preparing = State(final=True) ... ... start = ( ... pending.to(preparing, cond="is_paid") ... | pending.to(pending) ... ) ... ... def is_paid(self, amount: float = 0): ... return amount >= 5.0 >>> order = CoffeeOrder() >>> order.send("start", amount=3.0) >>> order.pending.is_active True >>> order.send("start", amount=5.0) >>> order.preparing.is_active True ``` -------------------------------- ### Scheduling a delayed event Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/statechart.md Example of sending an event with a delay. ```python sm.send("timeout", delay=5000) ``` -------------------------------- ### prepare_event callback example Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/actions.md Demonstrates how the `prepare_event` callback can enrich event arguments by returning a dictionary that is merged into the keyword arguments. ```python >>> class OrderFlow(StateChart): ... pending = State(initial=True) ... confirmed = State(final=True) ... ... confirm = pending.to(confirmed) ... ... def prepare_event(self, order_id=None): ... if order_id is not None: ... return {"order_total": order_id * 10} ... return {} ... ... def on_confirm(self, order_total=0): ... return f"confirmed ${order_total}" >>> sm = OrderFlow() >>> sm.send("confirm", order_id=5) 'confirmed $50' ``` -------------------------------- ### Auto-activation of initial state Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/async.md Demonstrates that sending an event before explicitly activating the initial state can lead to auto-activation. ```python >>> async def auto_activate(): ... sm = AsyncStateMachine() ... await sm.keep() # activates initial state before handling the event ... print(list(sm.configuration_values)) >>> asyncio.run(auto_activate()) ['initial'] ``` -------------------------------- ### ServerConnection StateChart Example Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/processing_model.md Demonstrates a state machine with chained transitions using 'after' callbacks, illustrating the run-to-completion model. ```python >>> from statemachine import StateChart, State >>> class ServerConnection(StateChart): ... disconnected = State(initial=True) ... connecting = State() ... connected = State(final=True) ... ... connect = disconnected.to(connecting, after="connection_succeed") ... connection_succeed = connecting.to(connected) ... ... def on_connect(self): ... return "on_connect" ... ... def on_enter_state(self, event: str, state: State, source: State): ... print(f"enter '{state.id}' from '{source.id if source else ''}' given '{event}'") ... ... def on_exit_state(self, event: str, state: State, target: State): ... print(f"exit '{state.id}' to '{target.id}' given '{event}'") ... ... def on_transition(self, event: str, source: State, target: State): ... print(f"on '{event}' from '{source.id}' to '{target.id}'") ... return "on_transition" ... ... def after_transition(self, event: str, source: State, target: State): ... print(f"after '{event}' from '{source.id}' to '{target.id}'") ... return "after_transition" ``` ```python >>> sm = ServerConnection() enter 'disconnected' from '' given '__initial__' >>> sm.send("connect") exit 'disconnected' to 'connecting' given 'connect' on 'connect' from 'disconnected' to 'connecting' enter 'connecting' from 'disconnected' given 'connect' after 'connect' from 'disconnected' to 'connecting' exit 'connecting' to 'connected' given 'connection_succeed' on 'connection_succeed' from 'connecting' to 'connected' enter 'connected' from 'connecting' given 'connection_succeed' after 'connection_succeed' from 'connecting' to 'connected' ['on_transition', 'on_connect'] ``` -------------------------------- ### Unreachable states validation Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/validations.md Example demonstrating the validation error for unreachable states. ```python >>> try: ... class Bad(StateChart): ... red = State(initial=True) ... green = State() ... hazard = State() ... cycle = red.to(green) | green.to(red) ... blink = hazard.to.itself() ... except InvalidDefinition as e: ... print(e) There are unreachable states. The statemachine graph should have a single component. Disconnected states: ['hazard'] ``` -------------------------------- ### Import necessary classes Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/validations.md Importing StateChart, State, and InvalidDefinition for validation examples. ```python >>> from statemachine import StateChart, State >>> from statemachine.exceptions import InvalidDefinition ``` -------------------------------- ### State Initialization and Transitions Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/api.md Demonstrates how to create State objects and define transitions between them using the to() and from_() methods, including self-transitions and multiple targets. ```python >>> from statemachine import State ``` ```python >>> draft = State(name="Draft", initial=True) ``` ```python >>> producing = State("Producing") ``` ```python >>> closed = State('Closed', final=True) ``` ```python >>> draft.to(producing) TransitionList([Transition('Draft', 'Producing', event=[], internal=False, initial=False)]) ``` ```python >>> transitions = draft.to(draft) | producing.to(closed) >>> transitions |= closed.to(draft) >>> [(t.source.name, t.target.name) for t in transitions] [('Draft', 'Draft'), ('Producing', 'Closed'), ('Closed', 'Draft')] ``` ```python >>> draft.to.itself() TransitionList([Transition('Draft', 'Draft', event=[], internal=False, initial=False)]) ``` ```python >>> transitions = draft.to(draft, producing, closed) >>> [(t.source.name, t.target.name) for t in transitions] [('Draft', 'Draft'), ('Draft', 'Producing'), ('Draft', 'Closed')] ``` ```python >>> transitions = closed.from_(draft, producing, closed) >>> [(t.source.name, t.target.name) for t in transitions] [('Draft', 'Closed'), ('Producing', 'Closed'), ('Closed', 'Closed')] ``` -------------------------------- ### Initial state activation problem in async code Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/async.md Illustrates the issue where the initial state is not activated upon instantiation in async code, resulting in an empty configuration. ```python >>> async def show_problem(): ... sm = AsyncStateMachine() ... print(list(sm.configuration_values)) >>> asyncio.run(show_problem()) [] ``` -------------------------------- ### Diagram generation with DotGraphMachine Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/how-to/coming_from_transitions.md Example of using DotGraphMachine for more control over diagram generation. ```python from statemachine.contrib.diagram import DotGraphMachine graph = DotGraphMachine(Workflow) graph().write_png("diagram.png") ``` -------------------------------- ### Eventless transitions: react automatically Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/tutorial.md Demonstrates eventless transitions that fire automatically when their guard condition is met, allowing the machine to react to internal data changes without explicit events. ```python >>> from statemachine import StateChart, State >>> class CoffeeOrder(StateChart): ... pending = State(initial=True) ... preparing = State() ... ready = State() ... picked_up = State(final=True) ... ... # Eventless: fires automatically when the guard is satisfied ... pending.to(preparing, cond="is_paid") ... ready.to(picked_up, cond="was_picked_up") ... ... finish = preparing.to(ready) ... ... # A no-op event to re-enter the processing loop ... check = ( ... pending.to.itself(internal=True) ... | ready.to.itself(internal=True) ... ) ... ... paid: bool = False ... picked: bool = False ... ... def is_paid(self): ... return self.paid ... def was_picked_up(self): ... return self.picked >>> order = CoffeeOrder() >>> order.paid = True >>> order.send("check") # triggers the eventless transition >>> order.preparing.is_active True >>> order.send("finish") >>> order.picked = True >>> order.send("check") >>> order.picked_up.is_active True ``` -------------------------------- ### Self-transition example Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/transitions.md A self-transition that exits and re-enters the same state, running exit and entry actions. ```python >>> class RetryOrder(StateChart): ... processing = State(initial=True) ... done = State(final=True) ... ... retry = processing.to.itself(on="do_retry") ... finish = processing.to(done) ... ... attempts: int = 0 ... ... def do_retry(self): ... self.attempts += 1 >>> sm = RetryOrder() >>> sm.send("retry") >>> sm.send("retry") >>> sm.attempts 2 ``` -------------------------------- ### HistoryState usage Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/states.md Example demonstrating the usage of HistoryState within a Compound state. ```python >>> from statemachine import HistoryState, State, StateChart >>> class WithHistory(StateChart): ... class mode(State.Compound): ... a = State(initial=True) ... b = State() ... h = HistoryState() ... switch = a.to(b) ... outside = State() ... leave = mode.to(outside) ... resume = outside.to(mode.h) >>> sm = WithHistory() >>> sm.send("switch") >>> sm.send("leave") >>> sm.send("resume") >>> "b" in sm.configuration_values True ``` -------------------------------- ### Error handling as events Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/tutorial.md Illustrates how `StateChart` handles runtime exceptions in callbacks by converting them into `error.execution` events, which can be managed with regular transitions. ```python >>> from statemachine import StateChart, State >>> class CoffeeOrder(StateChart): ... preparing = State(initial=True) ... out_of_stock = State(final=True) ... ... make_drink = preparing.to(preparing, on="do_make_drink") ... error_execution = preparing.to(out_of_stock) ... ... def do_make_drink(self): ... raise RuntimeError("Out of oat milk!") ... ... def on_enter_out_of_stock(self, error=None): ... if error: ... print(f"Problem: {error}") >>> order = CoffeeOrder() >>> order.send("make_drink") Problem: Out of oat milk! >>> order.out_of_stock.is_active True ``` -------------------------------- ### Cancelling a delayed event Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/statechart.md Example of sending a delayed event with a send_id and then cancelling it. ```python sm.send("timeout", delay=5000, send_id="my_timeout") sm.cancel_event("my_timeout") ``` -------------------------------- ### State parameters Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/states.md Example demonstrating state parameters like 'value' and 'final'. ```python >>> class CampaignMachine(StateChart): ... draft = State("Draft", value=1, initial=True) ... producing = State("Being produced", value=2) ... closed = State("Closed", value=3, final=True) ... ... produce = draft.to(producing) ... deliver = producing.to(closed) >>> sm = CampaignMachine() >>> sm.send("produce") >>> list(sm.configuration_values) [2] ``` -------------------------------- ### TransitionNotAllowed Exception Handling Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/api.md Example of how to catch the TransitionNotAllowed exception when an inexistent event is sent. ```default try: sm.send("an-inexistent-event") except sm.TransitionNotAllowed: pass ``` -------------------------------- ### Diagram generation with transitions.GraphMachine Source: https://github.com/fgmacedo/python-statemachine/blob/develop/docs/how-to/coming_from_transitions.md Example of using GraphMachine from the transitions library to generate a diagram. ```python from transitions.extensions import GraphMachine machine = GraphMachine(model=model, states=states, transitions=transitions, initial="draft") machine.get_graph().draw("diagram.png", prog="dot") ```