State machines in Python

Luca Marturana - Python Catania - 17/7/2025

Agenda

  • Qual è il problema
  • La libreria python-statemachine
  • Esempio di utilizzo
Luca Marturana - Python Catania - 17/7/2025

About me

Luca Marturana - Python Catania - 17/7/2025

Contesto

Luca Marturana - Python Catania - 17/7/2025

Problema

Luca Marturana - Python Catania - 17/7/2025

Problema

class TradeOrder:
  ...
  def process(trade_order):
    if trade_order.state != 'submitting':
      raise ValueError("Invalid transition")
    ...

  def cancel(application):
    if application.state not in ['created', 'submitting']:
      raise ValueError()
    ...
Luca Marturana - Python Catania - 17/7/2025

Problema

def process(trade_order_id):
  # BEGIN
  trade_order = ... # SELECT * from trade_orders where id = .. FOR UPDATE
  if trade_order.state != 'submitting':
    raise ValueError("Invalid transition")
  ...
  trade_order.state = 'submitted' # UPDATE
  # COMMIT
Luca Marturana - Python Catania - 17/7/2025

Problema

  • Controllo stato manuale
  • Rischio race-condition
  • Difficile da mantenere
Luca Marturana - Python Catania - 17/7/2025

A cosa serve una state machine

  • Descrive gli stati possibili
  • Descrive le transizioni
  • Organizzazione e correttezza del codice
Luca Marturana - Python Catania - 17/7/2025

python-statemachine

  • Buona API per definire state e transizioni
  • supporto nativo ad async/await
  • libreria con buona traction
  • layer di persistenza per Django, altri da implementare manualmente
Luca Marturana - Python Catania - 17/7/2025

Soluzione

class TradeOrderStateMachine(StateMachine):
    created = State('Created', initial=True)
    submitting = State('submitting')
    submitted = State('submitted')
    completed = State('completed', final=True)
    cancelled = State('cancelled', final=True)
    failed = State('failed', final=True)

    submit = created.to(submitting)
    process = submitting.to(submitted)
    complete = submitted.to(completed)

    cancel = created.to(cancelled) |
             submitting.to(cancelled)

    fail = submitting.to(failed)
Luca Marturana - Python Catania - 17/7/2025

Soluzione

class TradeOrderStateMachine(StateMachine):
  ...
  def on_process(self):
    trade_provider.submit_trade(
      ticker=self.ticker,
      quantity=self.quantity, ...
    )
  
  def after_enter_cancelled(self):
    notifications.send_email()
Luca Marturana - Python Catania - 17/7/2025

Soluzione

  # trade_order.status = CREATED
  sm = TradeOrderStateMachine(trade_order)
  sm.submit() # Segna l'ordine per il processamento
  sm.process() # Esegue l'ordine
  sm.cancel() # Fallisce perchè l'ordine è stato già eseguito
Luca Marturana - Python Catania - 17/7/2025

Recap

  • Codice più mantenibile
  • Più garanzie di correttezza
Luca Marturana - Python Catania - 17/7/2025

The End

Luca Marturana - Python Catania - 17/7/2025