StateMachine 2.6.0
StateMachine 2.6.0
February 2026
What's new in 2.6.0
This release adds the StateMachine.enabled_events method, Python 3.14 support,
a significant performance improvement for callback dispatch, and several bugfixes
for async condition expressions, type checker compatibility, and Django integration.
Python compatibility in 2.6.0
StateMachine 2.6.0 supports Python 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13, and 3.14.
Checking enabled events
A new StateMachine.enabled_events method lets you query which events have their
cond/unless guards currently satisfied, going beyond StateMachine.allowed_events
which only checks reachability from the current state.
This is particularly useful for UI scenarios where you want to enable or disable buttons
based on whether an event's conditions are met at runtime.
>>> class ApprovalMachine(StateMachine):
... pending = State(initial=True)
... approved = State(final=True)
... rejected = State(final=True)
...
... approve = pending.to(approved, cond="is_manager")
... reject = pending.to(rejected)
...
... is_manager = False
>>> sm = ApprovalMachine()
>>> [e.id for e in sm.allowed_events]
['approve', 'reject']
>>> [e.id for e in sm.enabled_events()]
['reject']
>>> sm.is_manager = True
>>> [e.id for e in sm.enabled_events()]
['approve', 'reject']Since conditions may depend on runtime arguments, any *args/**kwargs passed to
enabled_events() are forwarded to the condition callbacks:
>>> class TaskMachine(StateMachine):
... idle = State(initial=True)
... running = State(final=True)
...
... start = idle.to(running, cond="has_enough_resources")
...
... def has_enough_resources(self, cpu=0):
... return cpu >= 4
>>> sm = TaskMachine()
>>> sm.enabled_events()
[]
>>> [e.id for e in sm.enabled_events(cpu=8)]
['start']See Checking enabled events in the Guards documentation for more details.
Performance: cached signature binding
Callback dispatch is now significantly faster thanks to cached signature binding in
SignatureAdapter. The first call to a callback computes the argument binding and
caches a fast-path template; subsequent calls with the same argument shape skip the
full binding logic.
This results in approximately 60% faster bind_expected() calls and
around 30% end-to-end improvement on hot transition paths.
See #548 for benchmarks.
Bugfixes in 2.6.0
- Fixes #531 domain model
with falsy__bool__was being replaced by the defaultModel(). - Fixes #535 async predicates
in condition expressions (not,and,or) were not being awaited, causing guards to
silently return incorrect results. - Fixes #548
VAR_POSITIONALand kwargs precedence bugs in the signature binding cache introduced
by the performance optimization. - Fixes #511 Pyright/Pylance
false positive "Argument missing for parameter f" when calling events. Static analyzers
could not follow the metaclass transformation fromTransitionListtoEvent. - Fixes #551
MachineMixin
now gracefully skips state machine initialization for Django historical models in data
migrations, instead of raisingValueError. - Fixes #526 sanitize project
path on Windows for documentation builds.
Misc in 2.6.0
- Added Python 3.14 support #552.
- Upgraded dev dependencies: ruff to 0.15.0, mypy to 1.14.1
#552. - Clarified conditional transition evaluation order in documentation
#546. - Added pydot DPI resolution settings to diagram documentation
#514. - Fixed miscellaneous typos in documentation
#522. - Removed Python 3.7 from CI build matrix
ef351d5.