-
-
Notifications
You must be signed in to change notification settings - Fork 103
Comparing changes
Open a pull request
base repository: fgmacedo/python-statemachine
base: main
head repository: fgmacedo/python-statemachine
compare: develop
- 13 commits
- 153 files changed
- 2 contributors
Commits on Feb 24, 2026
-
Configuration menu - View commit details
-
Copy full SHA for d1b712d - Browse repository at this point
Copy the full SHA d1b712dView commit details
Commits on Mar 1, 2026
-
Configuration menu - View commit details
-
Copy full SHA for b4897d7 - Browse repository at this point
Copy the full SHA b4897d7View commit details -
fix: validate Event() id parameter to prevent silent transition loss (#…
…588) * fix: validate Event() id parameter to prevent silent transition loss Passing multiple transitions as positional args to Event() (e.g., Event(t1, t2)) caused the second argument to be silently interpreted as the event id, leaving those transitions eventless (auto-firing). Now raises InvalidDefinition with a message suggesting the | operator.
Configuration menu - View commit details
-
Copy full SHA for cac607a - Browse repository at this point
Copy the full SHA cac607aView commit details
Commits on Mar 3, 2026
-
refactor: restructure diagram module and add Sphinx directive (#589)
* refactor: extract diagram module into package with IR + renderer separation Refactor `statemachine/contrib/diagram.py` (single file) into a package with separated concerns: - `model.py`: intermediate representation (DiagramGraph, DiagramState, DiagramTransition) as pure dataclasses - `extract.py`: state machine → IR extraction logic - `renderers/dot.py`: IR → pydot.Dot rendering with UML-inspired styling - `__init__.py`: backwards-compatible facade (DotGraphMachine, etc.) Key improvements: - States with actions render as HTML TABLE labels with UML compartments (name + separator + entry/exit/actions), inspired by state-machine-cat - States without actions use native rounded rectangles - Active/inactive states use consistent rounded shapes (no polygon fill regression) - Class diagrams no longer highlight initial state as active - SVG shape consistency tests catch visual regressions automatically - Visual showcase section in docs/diagram.md demonstrates all features The public API is fully preserved: DotGraphMachine, quickchart_write_svg, write_image, main, import_sm, and `python -m statemachine.contrib.diagram`. * fix: avoid instantiating StateChart class in diagram extraction The extract function now uses the class directly (never instantiates it) since all structural metadata (states, transitions, name) is already available on the class thanks to the metaclass. Active-state highlighting is only produced when an instance is passed. * feat: set default graph DPI to 200 for sharper diagram images Increase the default Graphviz DPI from 96 to 200, producing noticeably sharper PNG output. The setting is customizable via DotGraphMachine.graph_dpi. Regenerate all documentation images at the new resolution. * refactor: improve diagram layout and edge clipping - Use native shape for all states (with and without actions) so Graphviz clips edges at the actual rounded border - States with actions embed a border=0 HTML TABLE inside the native shape for UML compartment layout - Transition labels use HTML tables for better spacing - Escape guard conditions with HTML entities - Increase ranksep to 0.3, enable forcelabels, add labeldistance - Regenerate all documentation images * refactor: use is_initial flag and hide implicit initial transitions - Add is_initial field to DiagramState IR, extracted from state metadata - Use is_initial to determine which state gets the black-dot initial arrow instead of relying on document order heuristics - Skip rendering implicit initial transitions from compound/parallel states to their initial child — these are already represented by the black-dot initial node inside the cluster - Skip initial arrows for parallel area children (all are auto-initial) - Regenerate affected documentation images * refactor: improve compound state edge routing with bidirectional anchors - Compound states with both incoming and outgoing transitions get separate _anchor_in/_anchor_out nodes for cleaner edge routing - Move anchor nodes into the atomic subgraph so they share the same rank region as real states, avoiding blank space in clusters - Place inner initial dots in the atomic cluster for shorter arrows - Extract _render_initial_arrow helper to reduce _render_states complexity - Regenerate affected documentation images * refactor: reduce compound state padding and fix integrations doctest - Add margin="4" to compound/parallel subgraphs to reduce default Graphviz cluster padding (was 8pt default) - Place anchor nodes directly on parent graph when no atomic states exist at that level, avoiding empty cluster whitespace - Remove graph_dpi config (use Graphviz default) - Fix integrations.md doctest: register CampaignMachine in registry and skip Django autodiscovery to prevent ImproperlyConfigured error - Merge doctest blocks so context carries across examples - Regenerate all documentation images * refactor: enrich diagram IR to separate extraction from rendering Move domain analysis logic from the renderer (dot.py) into the extractor (extract.py) so the renderer becomes a pure IR→pydot mapping: - Add ActionType enum replacing free strings for DiagramAction.type - Add compound_state_ids and bidirectional_compound_ids to DiagramGraph - Add DiagramTransition.is_initial flag for implicit initial transitions - Remove redundant DiagramTransition.target (use targets list) - Move _collect_compound_ids, _collect_compound_bidir_ids from renderer to extractor - Add _mark_initial_transitions and _resolve_initial_states in extractor - Remove _is_initial_candidate from renderer (use state.is_initial) - Remove implicit transition filtering logic from renderer (use transition.is_initial) * feat: add statemachine-diagram Sphinx directive for inline diagram rendering Add a Sphinx extension that renders state machine diagrams inline in docs from an importable class path, eliminating manual DotGraphMachine/write_png boilerplate. Supports standard image/figure options (width, height, scale, align, target, class, name) and state-machine-specific options (events, caption, figclass). Key features: - Inline SVG rendering (no intermediate files) - :events: option to instantiate and advance the machine before rendering - :target: (empty) auto-generates a standalone SVG for full-size zoom - Responsive sizing: intrinsic width becomes max-width, scales down on narrow viewports - Compatible with any Sphinx theme (uses native align-center/left/right classes) Refactor diagram.md to use the directive for the visual showcase, replacing doctest+write_png workflow. Extract showcase machines to tests/machines/ and use literalinclude with :pyobject: to display source code. Reorganize the page narrative: _graph() as primary entry point, DotGraphMachine for advanced customization. * docs: add Sphinx directive release notes to 3.1.0 * test: add comprehensive tests for sphinx_ext directive (100% coverage) Cover all untested code paths: _prepare_svg, _build_svg_styles, _resolve_target, _build_wrapper_classes, _split_length, _align_spec, setup, and the full run() method with various option combinations (caption, events, alt, width, height, scale, align, target, class, figclass, name) plus error handling for invalid imports and render failures. * test: reach 100% branch coverage for diagram package Add tests for extract.py (deep history type, internal transitions with and without actions, bidirectional compound detection, invalid input, initial state fallback), dot.py (compound labels with actions, internal action format, targetless transitions, non-bidirectional anchors), and __main__.py (via runpy). Mark unreachable branch in extract.py (history states never have substates) with pragma: no cover, and add `if __name__` to coverage exclusions in pyproject.toml. * docs: add TDD and branch coverage requirements to AGENTS.md Document that tests are first-class planning requirements, not afterthoughts. 100% branch coverage is mandatory (enforced by CI), coverage must be verified before committing, and pytest fixtures should be used instead of hardcoded paths. * refactor: fix SonarCloud maintainability issues in diagram package - Use pytest.approx for float comparisons in _split_length tests - Rename unused variables to _ in _prepare_svg tests - Remove unused 'getter' parameter from _extract_transitions_from_state and _extract_all_transitions - Reduce cognitive complexity of _create_edges (30→~10) by extracting _create_single_edge, _resolve_edge_endpoints, and _build_edge_label - Reduce cognitive complexity of _render_states (17→~12) by extracting _place_extra_nodes static method * docs: replace inline diagram generation with statemachine-diagram directive Convert tutorial.md and transitions.md to use the directive instead of doctest write_png + image references. Extract CoffeeOrder, OrderWorkflow, and OrderWorkflowCompound to tests/machines/ for importability. Add tip about Graphviz requirement and seealso linking to diagram.md for deeper coverage (Sphinx directive, Jupyter, QuickChart, etc.). Delete the 3 PNG files that are no longer referenced.
Configuration menu - View commit details
-
Copy full SHA for 4953985 - Browse repository at this point
Copy the full SHA 4953985View commit details -
refactor: replace diagram doctests with directives and add pre-commit…
… hook (#590) - Replace inline doctests in docs/diagram.md and README.md with plain python code blocks and statemachine-diagram Sphinx directives - Add generate-images pre-commit hook to regenerate README PNG via CLI - Remove TestReadmeImageGeneration (side-effect replaced by hook) - Delete obsolete PNG images no longer referenced by docs - Update release notes to use directive instead of static image
Configuration menu - View commit details
-
Copy full SHA for 966500d - Browse repository at this point
Copy the full SHA 966500dView commit details -
refactor: extract test StateChart definitions to tests/machines/ (#591)
Move reusable StateChart class definitions from inline test code to dedicated modules under tests/machines/, organized by feature: - workflow/: CampaignMachine variants, ReverseTrafficLightMachine - error/: ErrorInGuardSC, ErrorInActionSC, ErrorInErrorHandlerSC, etc. - validators/: OrderValidation, MultiValidator, ValidatorWithCond, etc. - compound/: ShireToRivendell, MoriaExpedition, MiddleEarthJourney, etc. - history/: GollumPersonality variants, DeepMemoryOfMoria, ShallowMoria - parallel/: WarOfTheRing, TwoTowers, Session variants - eventless/: RingCorruption variants, BeaconChain, AutoAdvance, etc. - in_condition/: Fellowship, GateOfMoria, CombinedGuard, etc. - donedata/: DestroyTheRing, NestedQuestDoneData, QuestForErebor variants Test files now import from these modules instead of defining classes inline. Machines that test invalid definitions or use closures remain inline in test files.
Configuration menu - View commit details
-
Copy full SHA for 11ffa95 - Browse repository at this point
Copy the full SHA 11ffa95View commit details
Commits on Mar 8, 2026
-
Configuration menu - View commit details
-
Copy full SHA for 5a85209 - Browse repository at this point
Copy the full SHA 5a85209View commit details -
Configuration menu - View commit details
-
Copy full SHA for 19556ce - Browse repository at this point
Copy the full SHA 19556ceView commit details -
feat: Mermaid diagrams, transition tables, and format() support (#595)
* feat: add Mermaid diagrams, transition tables, and __format__ support - Add MermaidRenderer that converts DiagramGraph IR to Mermaid stateDiagram-v2 source (compound, parallel, history, guards, etc.) - Add TransitionTableRenderer for markdown and RST table output - Add MermaidGraphMachine facade mirroring DotGraphMachine - Add __format__ to StateChart and StateMachineMetaclass supporting dot, mermaid, md/markdown, and rst format specs - Extend CLI with --format option (mermaid, md, rst) and stdout support - Add :format: option to Sphinx statemachine-diagram directive with sphinxcontrib-mermaid integration - Update docs with new sections and doctests * refactor: introduce Formatter facade with decorator-based format registry Replace duplicated if/elif chains in StateChart.__format__ and StateMachineMetaclass.__format__ with a Formatter class that uses a decorator-based registry following the Open/Closed Principle. Adding a new format now requires only a decorated function — no changes to __format__, factory.py, or statemachine.py. * feat: add SVG text format and use formatter in Sphinx directive - Register "svg" format in Formatter (DOT → SVG decoded as str) - Refactor Sphinx directive to use formatter.render() for both SVG and Mermaid instead of calling DotGraphMachine/MermaidGraphMachine directly - Update _prepare_svg and _resolve_target to work with str (not bytes) * feat: auto-expand {statechart:FORMAT} placeholders in class docstrings The metaclass now detects {statechart:FORMAT} placeholders in docstrings and replaces them at class definition time with the rendered output. The docstring always stays in sync with the actual states and transitions. Any registered format works: md, rst, mermaid, dot, etc. Indentation of the placeholder line is preserved in the output. * docs: revise diagram.md for new features and narrative coherence - Reorganize into unified "Text representations" section with format table (name, aliases, description, dependencies) - Add formatter API section with render(), supported_formats(), and custom format registration example - Add live Mermaid directive example in Sphinx section - Add --format dot to CLI examples - Replace MermaidGraphMachine usage with formatter - Add autodoc integration example with SimpleSC - Add auto-expanding docstrings section with format recommendations - Update release notes * docs: mention f-string/format() text representations in README and tutorial * docs: revise 3.1.0 release notes and fix broken cross-references * fix: work around Mermaid crash for transitions inside parallel regions Mermaid's stateDiagram-v2 crashes when a transition targets or originates from a compound state inside a parallel region (mermaid-js/mermaid#4052). The MermaidRenderer now redirects such endpoints to the compound's initial child state. Also filter dot-form event aliases (e.g. done.invoke.X) from diagram output — the fix lives in the extractor so all renderers benefit. Closes #594 * fix: render cross-boundary transitions and restrict Mermaid compound workaround to parallel regions The Mermaid renderer had two issues: 1. Cross-scope transitions (e.g., an outer state targeting a history pseudo-state inside a compound) were silently dropped because `_render_scope_transitions` only rendered transitions where both endpoints were direct members of the same scope. Now the scope check expands to include descendants of compound states, while skipping transitions fully internal to a single compound (handled by the inner scope). 2. The compound→initial-child redirect (workaround for mermaid-js/mermaid#4052) was applied universally, but the bug only affects compound states inside parallel regions. Now the redirect is restricted to parallel descendants, leaving compound states outside parallel regions unchanged. Adds a ParallelCompoundSC showcase that exercises the Mermaid bug pattern (transition targeting a compound inside a parallel region), with Graphviz vs Mermaid comparison in the visual showcase docs. * test: cover missing branches in sphinx_ext.py and extract.py Add tests for the :name: directive option on Mermaid format (with and without caption). Mark the defensive dedup guard in _format_event_names as pragma: no branch since Events already deduplicates at the container level.Configuration menu - View commit details
-
Copy full SHA for 2476d20 - Browse repository at this point
Copy the full SHA 2476d20View commit details
Commits on Mar 11, 2026
-
fix: Configuration.add()/discard() write through model setter for per…
…sistence (#596) In-place mutation of the OrderedSet returned by self.value lost state changes when the model uses a deserializing property (e.g., Django model field backed by a DB column). Each getter call returned a fresh object, the in-place mutation modified a temporary, and the model was never updated. Changed add() and discard() to create a new OrderedSet and assign via self.value = new, which calls setattr on the model. This only affects atomic_configuration_update=False (StateChart default); the atomic path was never affected. Benchmark impact: ~4-5% on parallel region events (~2µs per event), negligible vs. callback execution in the same microstep. Signed-off-by: Fernando Macedo <[email protected]>
Configuration menu - View commit details
-
Copy full SHA for cd05c6e - Browse repository at this point
Copy the full SHA cd05c6eView commit details -
perf: eliminate allocation overhead in Configuration.add()/discard()
Mutate the OrderedSet in place and write back via the setter instead of creating a new OrderedSet on every call. This removes the ~4-5% overhead introduced by the persistence bugfix while preserving correctness for both plain-attribute and deserializing-property models.
Configuration menu - View commit details
-
Copy full SHA for 8d17ba9 - Browse repository at this point
Copy the full SHA 8d17ba9View commit details
Commits on Mar 16, 2026
-
Refactor/configuration normalize orderedset (#599)
* test: add API contract tests for Configuration across all topologies Systematic test matrix covering the observable behavior of all public Configuration APIs (current_state_value, configuration_values, configuration, current_state, model.state) across flat, compound, parallel, and complex parallel StateCharts. Verifies type contracts (scalar vs OrderedSet), value correctness, model identity, and the uninitialized async lifecycle. Uses pytest.parametrize over 14 topology x lifecycle scenarios x sync/async engines, plus setter and uninitialized edge cases (39 tests total). * refactor: normalize Configuration internals to always use OrderedSet Introduce two boundary functions (_read_from_model / _write_to_model) that confine the None|scalar|OrderedSet trichotomy to the model edge. All other methods (add, discard, states setter, values) now operate on a uniform OrderedSet, eliminating per-method type branching. The model still receives the same denormalized values as before (None for empty, scalar for single state, OrderedSet for multiple). Fixes edge case: configuration_values now returns OrderedSet() for uninitialized config instead of OrderedSet([None]), since None is not a valid state value. Prepares the codebase for the persistence protocol work (#597).
Configuration menu - View commit details
-
Copy full SHA for 67f2b0d - Browse repository at this point
Copy the full SHA 67f2b0dView commit details
Commits on Mar 21, 2026
-
fix: render human-readable Event name in diagrams (#601)
* chore: add tmp/ to .gitignore * fix: render human-readable Event name in diagrams (#600) Event.name is now auto-generated as a humanized form of the id (split by _ and . separators, first word capitalized) instead of echoing the raw identifier. Explicit name= passed by the user is preserved. - Add humanize_id() in utils.py (compiled regex, shared by Event and State) - Remove redundant name= from Event call sites in factory, events, and SCXML actions so auto-generation kicks in - Use event.name in diagram labels (_format_event_names) - Update docs and tests to reflect humanized names Closes #600 * docs: add event humanized name to 3.1.0 release notes
Configuration menu - View commit details
-
Copy full SHA for ee3607a - Browse repository at this point
Copy the full SHA ee3607aView commit details
This comparison is taking too long to generate.
Unfortunately it looks like we can’t render this comparison for you right now. It might be too big, or there might be something weird with your repository.
You can try running this command locally to see the comparison on your machine:
git diff main...develop