Python StateMachine v3.1.2 Release Notes
Release Date: 2026-05-19 // 24 days ago-
🐛 Bug fixes in 3.1.2
pydotis optional againStarting in 3.1.0, defining any
StateMachineorStateChartsubclass
implicitly requiredpydotto be installed. The metaclass invokes
_expand_docstring()for every class, and that path eagerly imported the
diagram formatter, which in turn importedpydotat module load time, so
importof a user module failed withModuleNotFoundError: No module named 'pydot'even when no diagrams were rendered.⏪ Two changes restore the original behavior:
_expand_docstringnow short-circuits when the class docstring contains
no{statechart:FORMAT}placeholder, skipping the formatter import for
the common case.statemachine.contrib.diagramno longer re-exports renderer classes at
📦 the package top level, and renderer modules are only imported when
actually used. The dependency is only resolved whendot/svg
rendering is requested. Mermaid and Markdown/RST formats work without
pydot.
If your code imports
DotRenderer,DotRendererConfig,MermaidRenderer
orMermaidRendererConfigdirectly fromstatemachine.contrib.diagram,
import them from the renderer submodule instead:fromstatemachine.contrib.diagram.renderers.dotimportDotRendererfromstatemachine.contrib.diagram.renderers.mermaidimportMermaidRenderer#622.
Other changes in 3.1.2
🔒 Security and dependency bumps
Picked up from the dependency refresh applied to
main:- ruff
>=0.15.13 - ✅ pytest
>=9.0.3(CVE-2025, tmpdir handling) for py>=3.10 - django
>=5.2.14for py3.10/3.11 (multiple CVEs) - pillow
>=12.2.0for py>=3.10 (multiple CVEs) - transitive: urllib3
2.6.3 -> 2.7.0(GHSA-qccp-gfcp-xxvc), requests
2.33.1 -> 2.34.2
CI workflows:
actions/checkoutv4 → v5,actions/setup-pythonv5 → v6,
📌astral-sh/setup-uvv3 → v8 (pinned tov8.1.0),codecov/codecov-action
v4 → v6.
Previous changes from v3.1.1
-
StateChart 3.1.1
May 15, 2026
🐛 Bug fixes in 3.1.1
🔧 Thread-safety hardening of the configuration cache
🔧 Two races in
Configuration(introduced indirectly by the cache + no-copy
🛠 design in 3.1.0) have been fixed. Both surfaced under concurrent reads of
🔧machine.configurationwhile another thread is sending events to the same
🔀 state machine instance, a scenario explicitly supported by the sync engine.Cache read race.
Configuration.stateschecked
self._cached is not Noneand then returnedself._cached. Another
thread invalidating between the check and the return could cause the
property to returnNone, leading to aTypeErrorin callers that
🔧 iterate the result (e.g.,list(machine.configuration)). The getter now
snapshots the cache fields locally before the freshness check.
#620.In-place mutation race.
Configuration.add()and
🔧Configuration.discard()mutated theOrderedSetstored on the model
in place and rewrote the same reference. A concurrent reader iterating
🔧.configurationcould observe a partially mutated set (raising
RuntimeError: Set changed size during iteration) or read back a stale
cached resolution missing the new state. Both methods now use
copy-on-write, producing a freshOrderedSetper call. This affects
🔧 onlyStateChart(whereatomic_configuration_update=Falseis the
⚡️ default to support parallel regions). The atomic update path used by
StateMachinewas never affected.
#620.
✅ Both fixes are covered by new stress tests in
✅tests/test_threading.py::TestThreadSafety:
test_concurrent_send_and_read_configurationand
test_concurrent_parallel_region_send_and_read, plus a deterministic
➕ copy-on-write contract testtest_add_discard_produce_fresh_orderedset.🐎 Performance impact
Copy-on-write in
add()/discard()reintroduces an O(n) shallow copy of
🔧 the active configuration on every state entry and exit. For the typical
🔧 configuration sizes used in practice (1–7 states), this is sub-microsecond.🍎 Measured on macOS / Python 3.14, pytest-benchmark median, vs 3.1.0:
Benchmark 3.1.0 3.1.1 Δ test_parallel_region_events175.2 μs 184.5 μs +5.3% test_many_transitions_reset125.9 μs 139.5 μs +10.9% test_guarded_transitions70.0 μs 75.7 μs +8.2% test_history_pause_resume88.4 μs 91.4 μs +3.4% test_many_transitions_full_cycle156.9 μs 162.1 μs +3.3% test_flat_self_transition38.7 μs 39.1 μs +1.0% Overall 4.7x–7.7x event throughput improvement vs 3.0.0 (declared in
🚀 3.1.0 release notes)
is unchanged.