pytransitions / transitions

A lightweight, object-oriented finite state machine implementation in Python with many extensions
MIT License
5.48k stars 525 forks source link

Cannot use Pydantic `BaseModel` class for models #635

Closed mcvady closed 8 months ago

mcvady commented 8 months ago

Thank you for taking the time to report a bug! Your support is essential for the maintenance of this project. Please fill out the following fields to ease bug hunting and resolving this issue as soon as possible:

Describe the bug When trying to use a model based on the Pydantic BaseModel class, an exception is raised instantiating the state machine:

Traceback (most recent call last):
  File "/home/mcvady/fsm/fsm.py", line 11, in <module>
    machine = Machine(model=lump)
  File "/home/mcvady/fsm/.venv/lib/python3.10/site-packages/transitions/core.py", line 606, in __init__
    self.add_model(model)
  File "/home/mcvady/fsm/.venv/lib/python3.10/site-packages/transitions/core.py", line 620, in add_model
    self._checked_assignment(mod, 'trigger', partial(self._get_trigger, mod))
  File "/home/mcvady/fsm/.venv/lib/python3.10/site-packages/transitions/core.py", line 876, in _checked_assignment
    setattr(model, name, func)
  File "/home/mcvady/fsm/.venv/lib/python3.10/site-packages/pydantic/main.py", line 799, in __setattr__
    raise ValueError(f'"{self.__class__.__name__}" object has no field "{name}"')
ValueError: "Matter" object has no field "trigger"

Minimal working example

from pydantic import BaseModel
from transitions import Machine

class Matter(BaseModel):
    name: str

lump = Matter(name="Mr. Lumpy")

machine = Machine(model=lump)

Expected behavior The expected behavior was that it were possible to use Pydantic and transitions together.

Additional context

aleneum commented 8 months ago

Hello @mcvady,

transitions decorates models with trigger and state check methods as well as a generic trigger method to process events by name. I am not super familiar with pydantic but it seems like BaseModel does not allow to set attributes that haven't been explicitly defined. One way to deal with this is to allow extra attributes during model initialisation:

from pydantic import BaseModel, ConfigDict
from transitions import Machine

class Matter(BaseModel):
    model_config = ConfigDict(extra='allow')
    name: str

lump = Matter(name="Mr. Lumpy")

machine = Machine(model=lump)

Maybe there are ways in pedantic to define dynamically added attributes. Maybe it's even possible to define a model and pass it -- like for instance DocMachine -- to transitions for machine configuration. Currently, I would not say that this is a bug but rather the way transitions operates.

aleneum commented 8 months ago

Closing this since there has been no feedback for over 2 weeks. Feel free to comment if the issue still persists. I will reopen the issue if necessary.